seo 0.2.10 → 0.2.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -1,191 +1,273 @@
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,$t as t,A as n,At as r,B as i,Bt as a,C as o,Ct as s,D as c,Dt as l,E as u,Et as d,F as f,Ft as p,G as m,Gt as ee,H as h,Ht as te,I as ne,It as re,J as g,Jt as _,K as ie,Kt as ae,L as oe,Lt as se,M as ce,Mt as le,N as ue,Nt as de,O as fe,Ot as pe,P as me,Pt as he,Q as ge,Qt as _e,R as ve,Rt as ye,S as be,Sn as xe,St as Se,T as Ce,Tt as we,U as Te,Ut as Ee,V as De,Vt as Oe,W as ke,Wt as Ae,X as je,Xt as Me,Y as Ne,Yt as Pe,Z as Fe,Zt as Ie,_ as Le,_n as Re,_t as ze,a as Be,an as Ve,at as He,b as Ue,bn as v,bt as We,cn as Ge,ct as Ke,d as qe,dn as Je,dt as Ye,en as Xe,et as Ze,f as Qe,fn as $e,ft as et,g as tt,gn as nt,gt as rt,h as it,hn as at,ht as ot,i as st,in as ct,it as lt,j as ut,jt as dt,k as ft,kt as pt,l as mt,ln as ht,lt as gt,m as _t,mn as vt,mt as yt,nn as bt,nt as xt,o as St,on as Ct,ot as wt,p as Tt,pn as Et,pt as Dt,q as Ot,qt as kt,r as At,rn as jt,rt as Mt,sn as Nt,st as Pt,t as Ft,tn as It,tt as Lt,u as Rt,un as zt,ut as Bt,v as Vt,vn as Ht,vt as Ut,w as Wt,wt as Gt,x as Kt,xn as qt,xt as Jt,y as Yt,yn as Xt,yt as Zt,z as Qt,zt as $t}from"./dist-w2XtKwjW.js";import{constants as en,copyFileSync as tn,createWriteStream as nn,existsSync as y,mkdirSync as rn,readFileSync as an,readdirSync as on,realpathSync as sn,renameSync as cn,rmSync as ln,statSync as un,writeFileSync as dn}from"node:fs";import{homedir as fn}from"node:os";import{basename as pn,dirname as b,isAbsolute as mn,join as x,posix as hn,relative as gn,resolve as _n,win32 as vn}from"node:path";import{randomUUID as yn}from"node:crypto";import{spawn as bn,spawnSync as xn}from"node:child_process";import{fileURLToPath as Sn}from"node:url";import{isDeepStrictEqual as Cn,stripVTControlCharacters as wn}from"node:util";import{lstat as Tn,mkdir as En,mkdtemp as Dn,readFile as On,readdir as kn,rename as An,rm as jn,writeFile as Mn}from"node:fs/promises";import{defineCommand as S,runCommand as Nn,runMain as Pn}from"citty";import{cancel as Fn,confirm as In,intro as Ln,isCancel as Rn,log as zn,multiselect as Bn,note as C,outro as Vn,password as Hn,select as Un,text as Wn}from"@clack/prompts";import Gn from"picocolors";import Kn from"string-width";import qn from"cli-table3";import{applyEdits as Jn,modify as Yn,parse as Xn,printParseErrorCode as Zn}from"jsonc-parser";import{finished as Qn}from"node:stream/promises";import $n from"update-notifier";const er={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 tr(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 nr(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 rr(e){return[{filename:`postmortem-summary.csv`,headers:[...er.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:[...er.steps],rows:e.steps.map((e,t)=>({rank:t+1,tool:e.tool,status:e.status,summary:e.summary}))},{filename:`workflow-actions.csv`,headers:[...er.actions],rows:e.actions.map((e,t)=>({rank:t+1,title:e.title,confidence:e.confidence,action:e.action}))},{filename:`postmortem-findings.csv`,headers:[...er.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:[...er.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:[...er.evidence],rows:e.output.update.evidence.map((e,t)=>({rank:t+1,evidence:e}))},{filename:`postmortem-confounders.csv`,headers:[...er.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:[...er.segment],rows:tr(e,t)})),...[`page`,`query`,`device`,`country`].map(t=>({filename:`postmortem-segment-${t}-unmatched.csv`,headers:[...er.unmatchedSegment],rows:nr(e,t)}))]}function ir(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 ar(e){if(!e.startsWith(`sc-domain:`))return;let t=e.slice(10).trim().toLowerCase();if(!(!t||/[/:]/.test(t)))return t.replace(/\.$/,``)||void 0}function or(e,t){let n=ar(e);return n?t===n||t.endsWith(`.${n}`)?`domain`:void 0:ir(e)===t?`hostname`:void 0}function sr(e,t){return t.flatMap(t=>{let n=t.stream.webStreamData?.defaultUri;if(!n)return[];let r=ir(n);if(!r)return[];let i=or(e,r);return i?[{...t,defaultUri:n,hostname:r,match:i}]:[]})}function cr(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}.`}function lr(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 w(e,t){return{type:`boolean`,default:!0,description:e,negativeDescription:t}}function T(e){return typeof e==`string`?e:void 0}function E(e){let t=T(e.project),n=T(e.client);if(t&&n&&t!==n)throw Error(`Use either --project or --client, not both.`);return t??n}function D(e){return typeof e==`boolean`?e:void 0}function ur(e){let t=T(e.rendering);if(t!==void 0&&t!==`auto`&&t!==`on`&&t!==`off`)throw new v(`INVALID_INPUT`,`--rendering must be auto, on, or off.`);if(D(e.js)===!0&&t&&t!==`on`)throw new v(`INVALID_INPUT`,`Use either --js or --rendering on, not both with different modes.`);return t??(D(e.js)?`on`:`auto`)}function dr(e,t){let n=D(e[t]);if(n===!1)return!0;if(n===!0)return!1;let r=D(e[fr(t)]);if(r===!1)return!0;if(r===!0)return!1;if(D(e[`no-${t}`])===!0||D(e[`no${pr(t)}`])===!0)return!0}function fr(e){return e.replace(/-([a-z])/g,(e,t)=>t.toUpperCase())}function pr(e){let t=fr(e);return t?`${t[0]?.toUpperCase()}${t.slice(1)}`:``}function O(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 k(e,t){if(e===void 0||e===``)return;let n=O(e);if(n===void 0)throw new v(`INVALID_INPUT`,`${t} must be a number.`);return n}function A(e){if(typeof e!=`string`)return;let t=e.split(`,`).map(e=>e.trim()).filter(Boolean);return t.length?t:void 0}function j(e){return A(e)??[]}function mr(e){let t={concurrency:O(e[`fetch-concurrency`]),intervalCap:O(e[`fetch-interval-cap`]),intervalMs:O(e[`fetch-interval-ms`])};return Object.values(t).some(e=>e!==void 0)?t:void 0}function hr(e){let t={concurrency:k(e[`fetch-concurrency`],`--fetch-concurrency`),intervalCap:k(e[`fetch-interval-cap`],`--fetch-interval-cap`),intervalMs:k(e[`fetch-interval-ms`],`--fetch-interval-ms`)};return Object.values(t).some(e=>e!==void 0)?t:void 0}function M(e){return e.json===!0}async function gr(e,t){let n=T(e),r=T(t);if(n&&r)throw Error(`Use either --body or --body-file, not both.`);if(!n&&!r)return;let i=n??await On(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 _r(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 vr(e={}){let t=e.env??process.env,n=_r(e.columns)??_r(e.streamColumns)??_r(t.COLUMNS)??80;return Math.min(200,Math.max(40,n))}function N(e={}){let t=e.env??process.env,n=e.isTTY??!!process.stdout.isTTY,r=e.color??!!(n&&t.NO_COLOR===void 0);return{columns:vr({...e,env:t,streamColumns:e.streamColumns??process.stdout.columns}),colors:Gn.createColors(r),hasColor:r,isInteractive:n&&!t.CI}}function P(e){return Kn(wn(e))}function yr(e,t){let n=[],r=``;for(let i of e)r&&P(`${r}${i}`)>t?(n.push(r),r=i):r+=i;return r&&n.push(r),n}function br(e,t){if(!e)return[``];let n=e.trim().split(/\s+/u),r=[],i=``;for(let e of n){let n=P(e)>t?yr(e,t):[e];for(let e of n)i?P(`${i} ${e}`)<=t?i+=` ${e}`:(r.push(i),i=e):i=e}return i&&r.push(i),r.length>0?r:[``]}function F(e,t){let n=Math.max(1,t);return e.replaceAll(`\r
3
+ import{$ as e,$t as t,A as n,An as r,At as i,B as a,Bn as o,Bt as s,C as c,Cn as l,Ct as u,D as d,Dn as f,Dt as p,E as m,En as h,Et as ee,F as g,Fn as te,Ft as ne,G as re,Gt as _,H as v,Hn as y,Ht as ie,I as ae,In as oe,It as se,J as ce,Jt as le,K as ue,Kt as de,L as fe,Ln as pe,Lt as me,M as he,Mn as ge,Mt as _e,N as ve,Nn as ye,Nt as be,O as xe,On as Se,Ot as Ce,P as we,Pn as Te,Pt as Ee,Q as De,Qt as Oe,R as ke,Rn as Ae,Rt as je,S as Me,Sn as Ne,St as Pe,T as Fe,Tn as Ie,Tt as Le,U as Re,Un as ze,Ut as Be,V as Ve,Vn as He,Vt as Ue,W as We,Wn as Ge,Wt as Ke,X as qe,Xt as Je,Y as Ye,Yt as Xe,Z as Ze,Zt as Qe,_ as $e,_n as et,_t as tt,a as nt,an as rt,at as it,b as at,bn as ot,bt as st,cn as ct,ct as lt,d as ut,dn as dt,dt as ft,en as pt,et as mt,f as ht,fn as gt,ft as _t,g as vt,gn as yt,gt as bt,h as xt,hn as St,ht as Ct,i as wt,in as Tt,it as Et,j as Dt,jn as Ot,jt as kt,k as At,kn as jt,kt as Mt,l as Nt,ln as Pt,lt as Ft,m as It,mn as Lt,mt as Rt,nn as zt,nt as Bt,o as Vt,on as Ht,ot as Ut,p as Wt,pn as Gt,pt as Kt,q as qt,qt as Jt,r as Yt,rn as Xt,rt as Zt,sn as Qt,st as $t,t as en,tn,tt as nn,u as rn,un as an,ut as on,v as sn,vn as cn,vt as ln,w as un,wn as dn,wt as fn,x as pn,xn as mn,xt as hn,y as gn,yn as _n,yt as vn,z as yn,zn as bn,zt as xn}from"./dist-Dwdogl4j.js";import{constants as Sn,copyFileSync as Cn,createWriteStream as wn,existsSync as b,mkdirSync as Tn,readFileSync as En,readdirSync as Dn,realpathSync as On,renameSync as kn,rmSync as An,statSync as jn,writeFileSync as Mn}from"node:fs";import{homedir as Nn}from"node:os";import{basename as Pn,dirname as x,isAbsolute as Fn,join as S,posix as In,relative as Ln,resolve as Rn,win32 as zn}from"node:path";import{randomUUID as Bn}from"node:crypto";import{spawn as Vn,spawnSync as Hn}from"node:child_process";import{fileURLToPath as Un}from"node:url";import{isDeepStrictEqual as Wn,stripVTControlCharacters as Gn}from"node:util";import{lstat as Kn,mkdir as qn,mkdtemp as Jn,readFile as Yn,readdir as Xn,rename as Zn,rm as Qn,stat as $n,unlink as er,writeFile as tr}from"node:fs/promises";import{defineCommand as C,runCommand as nr,runMain as rr}from"citty";import{cancel as ir,confirm as ar,intro as or,isCancel as sr,log as cr,multiselect as lr,note as w,outro as ur,password as dr,select as fr,text as pr}from"@clack/prompts";import mr from"picocolors";import hr from"string-width";import gr from"cli-table3";import{applyEdits as _r,modify as vr,parse as yr,printParseErrorCode as br}from"jsonc-parser";import{finished as xr}from"node:stream/promises";import Sr from"update-notifier";function T(e){return e.replaceAll(`&`,`&amp;`).replaceAll(`<`,`&lt;`).replaceAll(`>`,`&gt;`).replaceAll(`"`,`&quot;`).replaceAll(`'`,`&#39;`)}function Cr(e){try{let t=new URL(e);return t.protocol===`http:`||t.protocol===`https:`?t.toString():void 0}catch{return}}function wr(e){let t=Cr(e),n=T(e);return t?`<a href="${T(t)}" rel="noreferrer">${n}</a>`:n}function Tr(e){return e.length===0?`<p class="empty">No items to report.</p>`:`<ul>${e.map(e=>`<li>${T(e)}</li>`).join(``)}</ul>`}function Er(e){return e.sections.map(e=>`<section class="panel">
4
+ <h2>${T(e.title)}</h2>
5
+ ${Tr(e.bullets)}
6
+ </section>`).join(``)}function Dr(e){return e.map(e=>`<section class="panel">
7
+ <h2>${T(e.title)}</h2>
8
+ ${e.summary?`<p>${T(e.summary)}</p>`:``}
9
+ ${Tr(e.items)}
10
+ </section>`).join(``)}function Or(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
+ <div class="priority-heading">
12
+ <strong>${T(e.title)}</strong>
13
+ <span class="confidence">${T(e.confidence)} confidence</span>
14
+ </div>
15
+ <p>${T(e.action)}</p>
16
+ </li>`).join(``)}</ol>`}function kr(e){let t=e.diagnosis.skippedSections??[],n=e.changeMeasurementAttempts;return`<section class="panel analyst-only">
17
+ <h2>Evidence coverage</h2>
18
+ <dl class="facts">
19
+ <div><dt>Data status</dt><dd>${T(e.dataStatus)}</dd></div>
20
+ <div><dt>Generated</dt><dd>${T(e.generatedAt)}</dd></div>
21
+ <div><dt>Saved changes checked</dt><dd>${n.length}</dd></div>
22
+ <div><dt>Unavailable sections</dt><dd>${t.length}</dd></div>
23
+ </dl>
24
+ ${t.length?`<h3>Unavailable sections</h3>${Tr(t.map(e=>`reason`in e&&typeof e.reason==`string`?`${e.section}: ${e.reason}`:e.section))}`:``}
25
+ </section>`}function Ar(e){let t=e.report,n=e.view??`client`,r=e.title??`SEO report`,i=t.dataStatus===`complete`?``:` open`,a=n===`analyst`?kr(t):``;return`<!doctype html>
26
+ <html lang="en">
27
+ <head>
28
+ <meta charset="utf-8">
29
+ <meta name="viewport" content="width=device-width, initial-scale=1">
30
+ <meta name="robots" content="noindex,nofollow">
31
+ <title>${T(r)}: ${T(t.site)}</title>
32
+ <style>
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
+ * { box-sizing: border-box; }
35
+ body { margin: 0; line-height: 1.55; }
36
+ a { color: inherit; text-decoration-color: #ff5b35; text-underline-offset: 0.18em; }
37
+ main { width: min(980px, calc(100% - 32px)); margin: 32px auto 64px; }
38
+ header { padding: clamp(28px, 6vw, 64px); color: #f9fbf7; background: #17211b; border-radius: 20px; }
39
+ .eyebrow { margin: 0 0 12px; color: #ff7958; font-size: 0.78rem; font-weight: 800; letter-spacing: 0.12em; text-transform: uppercase; }
40
+ h1 { max-width: 760px; margin: 0; font-size: clamp(2rem, 5vw, 4rem); line-height: 1.02; letter-spacing: -0.04em; }
41
+ header .headline { max-width: 760px; margin: 24px 0 0; font-size: clamp(1.05rem, 2vw, 1.3rem); color: #d9e1d9; }
42
+ .meta { display: flex; flex-wrap: wrap; gap: 10px 22px; margin-top: 28px; color: #b9c5ba; font-size: 0.9rem; }
43
+ .status { display: inline-flex; width: fit-content; margin-top: 22px; padding: 6px 10px; border: 1px solid #4f6153; border-radius: 999px; font-size: 0.8rem; font-weight: 700; text-transform: capitalize; }
44
+ .panel { margin-top: 20px; padding: clamp(22px, 4vw, 38px); background: #fff; border: 1px solid #dfe4dc; border-radius: 16px; box-shadow: 0 8px 30px rgba(23, 33, 27, 0.05); }
45
+ h2 { margin: 0 0 16px; font-size: clamp(1.25rem, 3vw, 1.65rem); letter-spacing: -0.02em; }
46
+ h3 { margin: 26px 0 10px; font-size: 1rem; }
47
+ p { margin: 0 0 14px; }
48
+ ul, ol { margin: 0; padding-left: 1.25rem; }
49
+ li + li { margin-top: 10px; }
50
+ .priorities { list-style: none; padding: 0; counter-reset: priority; }
51
+ .priorities > li { counter-increment: priority; padding: 18px 0 18px 52px; position: relative; border-top: 1px solid #e6eae4; }
52
+ .priorities > li:first-child { border-top: 0; padding-top: 4px; }
53
+ .priorities > li::before { content: counter(priority); position: absolute; left: 0; top: 16px; display: grid; width: 34px; height: 34px; place-items: center; border-radius: 50%; color: #17211b; background: #ff7958; font-weight: 800; }
54
+ .priorities > li:first-child::before { top: 2px; }
55
+ .priority-heading { display: flex; flex-wrap: wrap; align-items: baseline; justify-content: space-between; gap: 8px 20px; }
56
+ .confidence { color: #647067; font-size: 0.82rem; text-transform: capitalize; }
57
+ .priorities p { margin: 8px 0 0; color: #3e4941; }
58
+ .facts { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 12px; margin: 0; }
59
+ .facts div { padding: 14px; background: #f5f7f3; border-radius: 10px; }
60
+ dt { color: #647067; font-size: 0.78rem; font-weight: 700; text-transform: uppercase; }
61
+ dd { margin: 4px 0 0; font-weight: 700; }
62
+ details summary { cursor: pointer; font-weight: 750; }
63
+ details ul { margin-top: 14px; }
64
+ .empty { color: #647067; font-style: italic; }
65
+ footer { margin-top: 26px; color: #647067; font-size: 0.8rem; text-align: center; }
66
+ @media print { :root { background: #fff; } main { width: 100%; margin: 0; } header, .panel { break-inside: avoid; box-shadow: none; } header { border-radius: 0; } }
67
+ </style>
68
+ </head>
69
+ <body>
70
+ <main>
71
+ <header>
72
+ <p class="eyebrow">${T(r)}</p>
73
+ <h1>${wr(t.site)}</h1>
74
+ <p class="headline">${T(t.headline)}</p>
75
+ <div class="meta">
76
+ <span>${T(t.period.startDate)} to ${T(t.period.endDate)}</span>
77
+ <span>Generated ${T(t.generatedAt.slice(0,10))}</span>
78
+ <span>${T(n)} view</span>
79
+ </div>
80
+ <span class="status">${T(t.dataStatus)} data</span>
81
+ </header>
82
+
83
+ <section class="panel">
84
+ <h2>Priorities</h2>
85
+ ${Or(t)}
86
+ </section>
87
+
88
+ ${Er(t)}
89
+ ${Dr(e.additionalSections??[])}
90
+ ${a}
91
+
92
+ <section class="panel">
93
+ <details${i}>
94
+ <summary>Evidence notes and limitations</summary>
95
+ ${Tr(t.caveats)}
96
+ </details>
97
+ </section>
98
+
99
+ <footer>Generated locally from observed report evidence.</footer>
100
+ </main>
101
+ </body>
102
+ </html>`}const jr={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 Mr(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 Nr(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 Pr(e){return[{filename:`postmortem-summary.csv`,headers:[...jr.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:[...jr.steps],rows:e.steps.map((e,t)=>({rank:t+1,tool:e.tool,status:e.status,summary:e.summary}))},{filename:`workflow-actions.csv`,headers:[...jr.actions],rows:e.actions.map((e,t)=>({rank:t+1,title:e.title,confidence:e.confidence,action:e.action}))},{filename:`postmortem-findings.csv`,headers:[...jr.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:[...jr.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:[...jr.evidence],rows:e.output.update.evidence.map((e,t)=>({rank:t+1,evidence:e}))},{filename:`postmortem-confounders.csv`,headers:[...jr.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:[...jr.segment],rows:Mr(e,t)})),...[`page`,`query`,`device`,`country`].map(t=>({filename:`postmortem-segment-${t}-unmatched.csv`,headers:[...jr.unmatchedSegment],rows:Nr(e,t)}))]}function Fr(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 Ir(e){if(!e.startsWith(`sc-domain:`))return;let t=e.slice(10).trim().toLowerCase();if(!(!t||/[/:]/.test(t)))return t.replace(/\.$/,``)||void 0}function Lr(e,t){let n=Ir(e);return n?t===n||t.endsWith(`.${n}`)?`domain`:void 0:Fr(e)===t?`hostname`:void 0}function Rr(e,t){return t.flatMap(t=>{let n=t.stream.webStreamData?.defaultUri;if(!n)return[];let r=Fr(n);if(!r)return[];let i=Lr(e,r);return i?[{...t,defaultUri:n,hostname:r,match:i}]:[]})}function zr(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}.`}function Br(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 Vr(e,t){return{type:`boolean`,default:!0,description:e,negativeDescription:t}}function E(e){return typeof e==`string`?e:void 0}function D(e){let t=E(e.project),n=E(e.client);if(t&&n&&t!==n)throw Error(`Use either --project or --client, not both.`);return t??n}function O(e){return typeof e==`boolean`?e:void 0}function Hr(e){let t=E(e.rendering);if(t!==void 0&&t!==`auto`&&t!==`on`&&t!==`off`)throw new y(`INVALID_INPUT`,`--rendering must be auto, on, or off.`);if(O(e.js)===!0&&t&&t!==`on`)throw new y(`INVALID_INPUT`,`Use either --js or --rendering on, not both with different modes.`);return t??(O(e.js)?`on`:`auto`)}function Ur(e,t){let n=O(e[t]);if(n===!1)return!0;if(n===!0)return!1;let r=O(e[Wr(t)]);if(r===!1)return!0;if(r===!0)return!1;if(O(e[`no-${t}`])===!0||O(e[`no${Gr(t)}`])===!0)return!0}function Wr(e){return e.replace(/-([a-z])/g,(e,t)=>t.toUpperCase())}function Gr(e){let t=Wr(e);return t?`${t[0]?.toUpperCase()}${t.slice(1)}`:``}function k(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 A(e,t){if(e===void 0||e===``)return;let n=k(e);if(n===void 0)throw new y(`INVALID_INPUT`,`${t} must be a number.`);return n}function j(e){if(typeof e!=`string`)return;let t=e.split(`,`).map(e=>e.trim()).filter(Boolean);return t.length?t:void 0}function M(e){return j(e)??[]}function Kr(e){let t={concurrency:k(e[`fetch-concurrency`]),intervalCap:k(e[`fetch-interval-cap`]),intervalMs:k(e[`fetch-interval-ms`])};return Object.values(t).some(e=>e!==void 0)?t:void 0}function qr(e){let t={concurrency:A(e[`fetch-concurrency`],`--fetch-concurrency`),intervalCap:A(e[`fetch-interval-cap`],`--fetch-interval-cap`),intervalMs:A(e[`fetch-interval-ms`],`--fetch-interval-ms`)};return Object.values(t).some(e=>e!==void 0)?t:void 0}function N(e){return e.json===!0}async function Jr(e,t){let n=E(e),r=E(t);if(n&&r)throw Error(`Use either --body or --body-file, not both.`);if(!n&&!r)return;let i=n??await Yn(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 Yr(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 Xr(e={}){let t=e.env??process.env,n=Yr(e.columns)??Yr(e.streamColumns)??Yr(t.COLUMNS)??80;return Math.min(200,Math.max(40,n))}function P(e={}){let t=e.env??process.env,n=e.isTTY??!!process.stdout.isTTY,r=e.color??!!(n&&t.NO_COLOR===void 0);return{columns:Xr({...e,env:t,streamColumns:e.streamColumns??process.stdout.columns}),colors:mr.createColors(r),hasColor:r,isInteractive:n&&!t.CI}}function F(e){return hr(Gn(e))}function Zr(e,t){let n=[],r=``;for(let i of e)r&&F(`${r}${i}`)>t?(n.push(r),r=i):r+=i;return r&&n.push(r),n}function Qr(e,t){if(!e)return[``];let n=e.trim().split(/\s+/u),r=[],i=``;for(let e of n){let n=F(e)>t?Zr(e,t):[e];for(let e of n)i?F(`${i} ${e}`)<=t?i+=` ${e}`:(r.push(i),i=e):i=e}return i&&r.push(i),r.length>0?r:[``]}function I(e,t){let n=Math.max(1,t);return e.replaceAll(`\r
4
103
  `,`
5
104
  `).split(`
6
- `).flatMap(e=>br(e,n))}function xr(e,t){if(P(e)<=t)return e;let n=Math.max(1,t-P(`...`)),r=``;for(let t of e){if(P(`${r}${t}`)>n)break;r+=t}return`${r}...`}const Sr={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 Cr(e){return e===void 0?``:String(e)}function wr(e,t){return e.map((e,n)=>{let r=t.map(e=>Cr(e[n]));return Math.min(42,Math.max(P(e),...r.map(P),4))})}function Tr(e,t,n){return e.length===0||e.length>4?!1:wr(e,t).reduce((e,t)=>e+t+2,0)<=n.columns}function Er(e,t,n){let r=wr(e,t).map(e=>e+2),i=new qn({chars:Sr,colWidths:r,head:e.map(e=>n.colors.bold(e)),style:{border:[],head:[],"padding-left":0,"padding-right":2},wordWrap:!1});for(let n of t)i.push(e.map((e,t)=>F(Cr(n[t]),Math.max(1,(r[t]??3)-2)).join(`
7
- `)));return i.toString()}function Dr(e,t,n,r){let i=r.columns-n-2;return i<24?[r.colors.dim(e),...F(t,r.columns-2).map(e=>` ${e}`)]:F(t,i).map((t,i)=>{let a=i===0?e.padEnd(n):``.padEnd(n);return`${r.colors.dim(a)}${` `.repeat(2)}${t}`})}function Or(e,t,n,r={}){let i=Math.min(28,Math.max(...e.map(e=>P(e)),0)),a=r.rowNumbers??t.length>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(...Dr(a,Cr(t[r]),i,n));return s.join(`
105
+ `).flatMap(e=>Qr(e,n))}function $r(e,t){if(F(e)<=t)return e;let n=Math.max(1,t-F(`...`)),r=``;for(let t of e){if(F(`${r}${t}`)>n)break;r+=t}return`${r}...`}const ei={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 ti(e){return e===void 0?``:String(e)}function ni(e,t){return e.map((e,n)=>{let r=t.map(e=>ti(e[n]));return Math.min(42,Math.max(F(e),...r.map(F),4))})}function ri(e,t,n){return e.length===0||e.length>4||!e.map((e,n)=>t.every(e=>/^-?(?:\d+(?:\.\d+)?|\d+(?:\.\d+)?%)$/u.test(ti(e[n]).trim()))).some(Boolean)?!1:ni(e,t).reduce((e,t)=>e+t+2,0)<=n.columns}function ii(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 ai(e,t,n){let r=ni(e,t).map(e=>e+2),i=new gr({chars:ei,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)=>I(ii(e,ti(a[t]),n),Math.max(1,(r[t]??3)-2)).join(`
106
+ `)));return i.toString()}function oi(e,t,n,r){let i=r.columns-n-2;return i<24?[r.colors.dim(e),...I(t,r.columns-2).map(e=>` ${e}`)]:I(t,i).map((t,i)=>{let a=i===0?e.padEnd(n):``.padEnd(n);return`${r.colors.dim(a)}${` `.repeat(2)}${t}`})}function si(e,t,n,r={}){let i=Math.min(28,Math.max(...e.map(e=>F(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(...oi(a,ii(a,ti(t[r]),n),i,n));return s.join(`
8
107
  `)}).join(`
9
108
 
10
- `)}function kr(e,t,n,r={}){return t.length===0?n.colors.dim(`No results.`):r.layout===`table`||r.layout!==`records`&&Tr(e,t,n)?Er(e,t,n):Or(e,t,n,r)}function Ar(e,t){if(e.length===0)return``;let n=Math.min(28,Math.max(...e.map(([e])=>P(e))));return e.flatMap(([e,r])=>{let i=Dr(e,r,n,t);return i.length>0&&(i[0]=(i[0]??``).replace(e,t.colors.bold(e))),i}).join(`
11
- `)}function jr(e,t){let n=[t.colors.bold(e.title)];return e.body&&n.push(...F(e.body,t.columns)),e.command&&n.push(t.colors.cyan(xr(e.command,t.columns))),n.join(`
12
- `)}function Mr(e,t,n=`${t}s`){return e===1?t:n}function Nr(e,t,n){let r=` `.repeat(t);return F(e,Math.max(1,n-t)).map(e=>`${r}${e}`)}function Pr(e,t,n){return[t.colors.bold(e),...n?F(n,t.columns):[]].join(`
13
- `)}function Fr(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(...Nr(e.detail,6,t.columns)),e.fix){let n=F(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(`
109
+ `)}function ci(e,t,n,r={}){return t.length===0?n.colors.dim(`No results.`):r.layout===`table`||r.layout!==`records`&&ri(e,t,n)?ai(e,t,n):si(e,t,n,r)}function li(e,t){if(e.length===0)return``;let n=Math.min(28,Math.max(...e.map(([e])=>F(e))));return e.flatMap(([e,r])=>{let i=oi(e,r,n,t);return i.length>0&&(i[0]=(i[0]??``).replace(e,t.colors.bold(e))),i}).join(`
110
+ `)}function ui(e,t){let n=[t.colors.bold(e.title)];return e.body&&n.push(...I(e.body,t.columns)),e.command&&n.push(t.colors.cyan($r(e.command,t.columns))),n.join(`
111
+ `)}function di(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 fi(e){return e===`warning`?`WARN`:e===`notApplicable`?`N/A`:e.toUpperCase()}function pi(e,t,n){let r=` `.repeat(t);return I(e,Math.max(1,n-t)).map(e=>`${r}${e}`)}function mi(e,t){let n=di(e.status,t)(`[${fi(e.status)}]`.padEnd(10)),r=Math.max(1,t.columns-10),i=I(e.title,r).map((e,r)=>`${r===0?n:` `.repeat(10)}${t.colors.bold(e)}`);i.push(...pi(e.explanation,10,t.columns));for(let n of e.evidence??[]){let e=I(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=I(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 hi(e,t){return I(e.map(e=>{let n=String(e.value),r=e.status?di(e.status,t)(n):t.colors.bold(n);return`${e.label} ${r}`}).join(t.colors.dim(` · `)),t.columns).join(`
113
+ `)}function gi(e,t){let n=di(e.status,t),r=[t.colors.bold(e.title)];e.target&&r.push(t.colors.dim(e.target)),r.push(``),r.push(`${n(fi(e.status))} ${I(e.summary,Math.max(1,t.columns-6)).join(`
114
+ `)}`),e.metrics?.length&&r.push(hi(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=>mi(e,t)).join(`
115
+
116
+ `));if(e.notes?.length){r.push(``,t.colors.bold(`Notes`));for(let n of e.notes){let e=I(n,Math.max(1,t.columns-2));r.push(...e.map((e,t)=>`${t===0?`- `:` `}${e}`))}}return r.join(`
117
+ `)}function _i(e,t,n=`${t}s`){return e===1?t:n}function vi(e,t,n){let r=` `.repeat(t);return I(e,Math.max(1,n-t)).map(e=>`${r}${e}`)}function yi(e,t,n){return[t.colors.bold(e),...n?I(n,t.columns):[]].join(`
118
+ `)}function bi(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(...vi(e.detail,6,t.columns)),e.fix){let n=I(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(`
14
119
  `)}).join(`
15
120
 
16
- `)}function Ir(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} ${Mr(t,`check`)} passed, ${n} ${Mr(n,`warning`)}, ${r} failed.`}function Lr(e,t,n){let r=n.columns-t-2-2;return r<24||P(e.id)>t?[...Nr(e.id,2,n.columns).map(e=>n.colors.cyan(e)),...Nr(e.name,4,n.columns)].join(`
17
- `):F(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(`
18
- `)}function Rr(e){let t=e.replaceAll(`-`,` `);return t.charAt(0).toUpperCase()+t.slice(1)}function zr(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} ${Mr(e.length,n.noun)} across ${r.size} ${Mr(r.size,`category`,`categories`)}.`,a=Math.min(30,Math.max(...e.map(e=>P(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]??Rr(e);return[t.colors.bold(`${o} (${i.length})`),...i.map(e=>Lr(e,a,t))].join(`
121
+ `)}function xi(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} ${_i(t,`check`)} passed, ${n} ${_i(n,`warning`)}, ${r} failed.`}function Si(e,t,n){let r=n.columns-t-2-2;return r<24||F(e.id)>t?[...vi(e.id,2,n.columns).map(e=>n.colors.cyan(e)),...vi(e.name,4,n.columns)].join(`
122
+ `):I(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 Ci(e){let t=e.replaceAll(`-`,` `);return t.charAt(0).toUpperCase()+t.slice(1)}function wi(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} ${_i(e.length,n.noun)} across ${r.size} ${_i(r.size,`category`,`categories`)}.`,a=Math.min(30,Math.max(...e.map(e=>F(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]??Ci(e);return[t.colors.bold(`${o} (${i.length})`),...i.map(e=>Si(e,a,t))].join(`
19
124
  `)})].join(`
20
125
 
21
- `)}function Br(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(...Nr(e.description,2,t.columns));let r=e.meta?.filter(Boolean).join(` · `);return r&&n.push(...Nr(r,2,t.columns).map(e=>t.colors.dim(e))),n.join(`
126
+ `)}function Ti(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(...vi(e.description,2,t.columns));let r=e.meta?.filter(Boolean).join(` · `);return r&&n.push(...vi(r,2,t.columns).map(e=>t.colors.dim(e))),n.join(`
22
127
  `)}).join(`
23
128
 
24
- `)}function Vr(e,t,n){let r=t.filter(e=>e.trim().length>0).flatMap(e=>F(e,n.columns));return[n.colors.bold(e),...r].join(`
25
- `)}function Hr(e,t,n){let r=t.flatMap(e=>F(e,Math.max(1,n.columns-2)).map((e,t)=>`${t===0?`- `:` `}${e}`));return[n.colors.bold(e),...r].join(`
26
- `)}function Ur(e,t){return e.length===0?t.colors.dim(`No parameters.`):Br(e.map(e=>({title:e.name,description:e.description,meta:[e.type,e.required?`required`:`optional`]})),t,{empty:`No parameters.`})}function I(e={}){return!!(!e.json&&process.stdin.isTTY&&process.stdout.isTTY&&!process.env.CI)}function L(e){process.stdout.write(`${JSON.stringify(e,null,2)}\n`)}function R(e){let t=Ar(e,N());t&&process.stdout.write(`${t}\n`)}function z(e,t){process.stdout.write(`${kr(e,t,N())}\n`)}function Wr(e){process.stdout.write(`${jr(e,N())}\n`)}function Gr(e,t){process.stdout.write(`${Pr(e,N(),t)}\n`)}function Kr(e){process.stdout.write(`${Fr(e,N())}\n`)}function qr(e,t){process.stdout.write(`${zr(e,N(),t)}\n`)}function Jr(e,t){process.stdout.write(`${Br(e,N(),t)}\n`)}function B(e,...t){process.stdout.write(`${Vr(e,t,N())}\n`)}function Yr(e,t){process.stdout.write(`${Hr(e,t,N())}\n`)}function Xr(e){process.stdout.write(`${Ur(e,N())}\n`)}function Zr(e){return e<1024?`${e} B`:e<1024*1024?`${(e/1024).toFixed(1)} KB`:`${(e/(1024*1024)).toFixed(1)} MB`}function V(e){return typeof e==`symbol`&&process.exit(1),e}function Qr(e,t){let n=t.trim().toLowerCase();return!n||e.some(e=>e.toLowerCase().includes(n))}async function $r(e){let t=e.choices;if(e.choices.length>8){let n=V(await Wn({message:e.searchMessage,placeholder:`Type part of the name or URL, or leave blank`}));if(t=e.choices.filter(t=>Qr(e.searchValues(t),n)),!t.length)throw new v(`INVALID_INPUT`,e.emptyMessage)}return V(await Un({message:e.message,options:t.map(t=>{let n=e.hint?.(t);return{value:t,label:e.label(t),...n?{hint:n}:{}}})}))}async function H(e){if(e.site)return e.site;let t=vt(),n=!!t.defaultSite&&(t.clients.some(e=>e.siteUrl===t.defaultSite)||t.sites.some(e=>e.siteUrl===t.defaultSite));if(t.defaultSite&&n)return t.defaultSite;if(e.options?.json||!I())throw new v(`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 Me(e.options?.refresh);if(!r.length)throw new v(`PROPERTY_NOT_FOUND`,`No Search Console properties found for this Google login.`);return r.length===1?r[0]?.siteUrl??``:(await $r({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 U(e){if(e.client&&e.project&&e.client!==e.project)throw new v(`INVALID_INPUT`,`Use either --project or --client, not both.`);let t=e.project??e.client;if(t){let e=Kt(t);if(!e)throw new v(`INVALID_INPUT`,`Project not found: ${t}`);return{client:e,site:e.siteUrl}}let n=Kt();return!e.site&&n?{client:n,site:n.siteUrl}:{site:await H({site:e.site,options:e.options})}}async function ei(e){if(e.client&&e.project&&e.client!==e.project)throw new v(`INVALID_INPUT`,`Use either --project or --client, not both.`);let t=e.project??e.client;if(t){let e=Kt(t);if(!e)throw new v(`INVALID_INPUT`,`Project not found: ${t}`);return e}let n=o();if(!n.length)return;let r=Kt();if(r)return r;if(!(e.options?.json||!I()))return $r({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]})}async function ti(e){if(e.property)return e.property;let t=vt();if(t.analytics.google.defaultPropertyId)return t.analytics.google.defaultPropertyId;if(e.options?.json||!I())throw new v(`INVALID_INPUT`,`No Google Analytics property selected. Pass --property or run this command in a terminal to choose one.`);let n=(await jt()).flatMap(e=>e.propertySummaries.map(t=>({property:It(t.property),displayName:t.displayName??t.property,account:e.displayName??e.account})));if(!n.length)throw new v(`PROPERTY_NOT_FOUND`,`No Google Analytics properties found for this Google login.`);return n.length===1?n[0]?.property??``:(await $r({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}function W(e){return Math.round(e).toLocaleString(`en-GB`)}function ni(e){return e.toFixed(1)}function G(e){return`${(e*100).toFixed(1)}%`}function K(e,t=72){return xr(e,t)}function ri(e){return e.verification?.requested?`${e.verification.verified} checked, ${e.verification.failed} failed`:`off`}function q(e,t){z(e,t.slice(0,25)),t.length>25&&process.stdout.write(`Showing 25 of ${t.length}. Use --json for full data.\n`)}function J(e,t,n=5){let r=t.filter(e=>e.action.trim().length>0).slice(0,n);if(r.length){process.stdout.write(`\n${e}\n`);for(let[e,t]of r.entries()){let n=t.context?` (${t.context})`:``;process.stdout.write(`${e+1}. ${t.label}${n}: ${t.action}\n`)}t.length>r.length&&process.stdout.write(`Showing ${r.length} of ${t.length}. Use --json for full data.\n`)}}function ii(e){process.stdout.write(`\nNext: ${e}\n`)}function Y(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 ai(e,t={}){let n=T(e[`report-id`]);if(n){let e=p(n);if(!e)throw Error(`No saved crawl report found for ${n}.`);return e}let r=E(e),i=T(e.site),a=r||i?await U({client:r,site:i,options:t}):void 0,o=de(a?.site);if(!o)throw Error(a?.site?`No saved crawl report found for ${a.site}. Run \`seo crawl --project ${a.client?.id??a.site} --save\` first.`:"No saved crawl report found. Run `seo crawl <url> --save` first, or pass --report-id.");return o}function oi(e){process.stdout.write(`AI-search evidence for ${e.url}\n\n`),R([[`Assessment`,`evidence only`],[`Data`,e.dataStatus],[`Report`,e.reportId]]),process.stdout.write(`\n${e.headline}\n`),e.topActions.length&&(process.stdout.write(`
27
- Top actions
28
- `),z([`Status`,`Check`,`Action`],e.topActions.map(e=>[e.status,e.title,K(e.action,96)]))),e.botAccess.length&&(process.stdout.write(`
29
- Bot access
30
- `),z([`User agent`,`Allowed`,`Declared`],e.botAccess.map(e=>[e.userAgent,e.allowed===null?`unknown`:e.allowed?`yes`:`no`,e.declared?`yes`:e.coveredByWildcard?`wildcard`:`no`]))),Y(`Caveats`,e.caveats)}const si=S({meta:{name:`ai-readiness`,description:`Review AI-search technical evidence and optional observations`},args:{"report-id":{type:`string`,description:`Saved crawl report id to analyze.`},site:{type:`string`,description:`GSC property URL for selecting the latest saved crawl.`},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.`}},run:async({args:e})=>{let t=M(e),n=Oe(await ai(e,{json:t}));if(t){L(n);return}oi(n)}});function ci(e){let t=T(e.agentReadinessUrl),n=T(e.url);if(t&&n&&t!==n)throw Error(`Use either the positional URL or --url, not both.`);return t??n}function li(e){process.stdout.write(`AI agent readiness for ${e.url}\n\n`),R([[`Profile`,e.profile],[`Assessment`,`evidence only`],[`Data`,e.dataStatus],[`Report`,e.reportId],[`Passed`,String(e.summary.passed)],[`Needs review`,String(e.summary.warnings)],[`Failed`,String(e.summary.failed)],[`Unknown`,String(e.summary.unknown)],[`Information`,String(e.summary.information)]]),process.stdout.write(`\n${e.headline}\n`),process.stdout.write(`
31
- Profile scope
32
- `),z([`Profile`,`Status`,`Reason`],Object.entries(e.profileApplicability).map(([e,t])=>[e,t.status===`notApplicable`?`not applicable`:t.status,K(t.reason,96)])),e.topActions.length&&(process.stdout.write(`
33
- What to check next
34
- `),z([`Status`,`Check`,`Action`],e.topActions.map(e=>[e.status,e.title,K(e.action,96)]))),Y(`Caveats`,e.caveats)}const ui=S({meta:{name:`agent-readiness`,description:`Check content-site access, Markdown alternatives, discovery, and identity for AI agents`},args:{agentReadinessUrl:{type:`positional`,required:!1,description:`Public site URL to check.`},url:{type:`string`,description:`Public site URL to check.`},"report-id":{type:`string`,description:`Saved crawl report id to analyze without fetching again.`},site:{type:`string`,description:`GSC property URL for selecting the latest saved crawl.`},client:{type:`string`,description:`Legacy alias for --project.`},project:{type:`string`,description:`Saved project id or name.`},"max-pages":{type:`string`,description:`Maximum public HTML pages to crawl.`},"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.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=M(e),n=ci(e),r=T(e[`report-id`]);if(n&&r)throw Error(`Use either a URL or --report-id, not both.`);let i=Ee(n?await(async()=>{let r=E(e),i=T(e.site),a=r||i?await U({client:r,site:i,options:{json:t}}):void 0;return dt({url:n,projectId:a?.client?.id,site:a?.site,maxPages:O(e[`max-pages`]),fetchRate:mr(e),refresh:D(e.refresh),checkAgentDiscovery:!0,useSitemap:!0,checkExternal:!1})})():await ai(e,{json:t}));if(t){L(i);return}li(i)}}),di=S({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.`}},run:async({args:e})=>{let t=await jt(),n=t.flatMap(e=>e.propertySummaries.map(t=>({account:e.displayName??e.account,property:It(t.property),displayName:t.displayName??t.property}))),r=T(e.save);if(r){let e=vt();e.analytics.google.defaultPropertyId=r,nt(e)}if(M(e)){L({accountSummaries:t,properties:n,saved:r});return}r&&process.stdout.write(`Saved default Google Analytics property ${r}.\n`),z([`Property`,`Name`,`Account`],n.map(e=>[e.property,e.displayName,e.account]))}});async function fi(e,t={resolveClient:ei,resolveGoogleAnalyticsProperty:ti}){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:be(n),options:e.options})}const pi=S({meta:{name:`analytics`,description:`Read analytics data from connected providers`},subCommands:{google:S({meta:{name:`google`,description:`Read Google Analytics properties and reports`},subCommands:{properties:di,report:S({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.`},"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=M(e),n=await fi({property:T(e.property),project:E(e),options:{json:t}}),r=await Ve(n,await gr(e.body,e[`body-file`])??{dateRanges:[{startDate:T(e[`start-date`]),endDate:T(e[`end-date`])}],dimensions:(A(e.dimensions)??[]).map(e=>({name:e})),metrics:(A(e.metrics)??[]).map(e=>({name:e})),limit:T(e.limit)},{refresh:D(e.refresh)});if(t){L(r);return}let i=bt(r);if(R([[`Property`,n],[`Rows`,String(r.rowCount??i.length)]]),i.length){let e=Object.keys(i[0]??{});z(e,i.slice(0,25).map(t=>e.map(e=>t[e]??``)))}}})}})}});function mi(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 hi(e){return e.active===`keychain`?`system keychain`:e.configured===`keychain`?`private file fallback`:`private file`}function gi(e){return e===`shared`?`SEO Skill app`:`Local OAuth config`}function _i(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`,gi(e.clientSource)],[`Expires`,lr(e.expiresAt)]),t}const vi=S({meta:{name:`auth`,description:`Authentication commands`},subCommands:{login:S({meta:{name:`login`,description:`Run Google OAuth flow`},run:async()=>{if((await ht()).activeMode===`service-account`)throw new v(`AUTH_CONFIG_REQUIRED`,`A service account is active from the environment. Unset its credential variable before running browser OAuth.`);let e=await Ge();R(_i({accountEmail:e.account_email,clientSource:e.client_source,expiresAt:e.expires_at,scope:e.scope}))}}),logout:S({meta:{name:`logout`,description:`Delete locally stored tokens`},run:async()=>{let e=await ht();if(await Je(),e.activeMode===`service-account`){process.stdout.write(`Deleted local OAuth tokens. Service account credentials remain available through the environment.
129
+ `)}function Ei(e,t,n){let r=t.filter(e=>e.trim().length>0).flatMap(e=>I(e,n.columns));return[n.colors.bold(e),...r].join(`
130
+ `)}function Di(e,t,n){let r=t.flatMap(e=>I(e,Math.max(1,n.columns-2)).map((e,t)=>`${t===0?`- `:` `}${e}`));return[n.colors.bold(e),...r].join(`
131
+ `)}function Oi(e,t){return e.length===0?t.colors.dim(`No parameters.`):Ti(e.map(e=>({title:e.name,description:e.description,meta:[e.type,e.required?`required`:`optional`]})),t,{empty:`No parameters.`})}function L(e={}){return!!(!e.json&&process.stdin.isTTY&&process.stdout.isTTY&&!process.env.CI)}function R(e){process.stdout.write(`${JSON.stringify(e,null,2)}\n`)}function z(e){let t=li(e,P());t&&process.stdout.write(`${t}\n`)}function B(e,t){process.stdout.write(`${ci(e,t,P())}\n`)}function ki(e){process.stdout.write(`${ui(e,P())}\n`)}function Ai(e,t){process.stdout.write(`${yi(e,P(),t)}\n`)}function ji(e){process.stdout.write(`${bi(e,P())}\n`)}function Mi(e){process.stdout.write(`${gi(e,P())}\n`)}function Ni(e,t){process.stdout.write(`${wi(e,P(),t)}\n`)}function Pi(e,t){process.stdout.write(`${Ti(e,P(),t)}\n`)}function Fi(e,...t){process.stdout.write(`${Ei(e,t,P())}\n`)}function Ii(e,t){process.stdout.write(`${Di(e,t,P())}\n`)}function Li(e){process.stdout.write(`${Oi(e,P())}\n`)}function Ri(e){return e<1024?`${e} B`:e<1024*1024?`${(e/1024).toFixed(1)} KB`:`${(e/(1024*1024)).toFixed(1)} MB`}function V(e){return typeof e==`symbol`&&process.exit(1),e}function zi(e,t){let n=t.trim().toLowerCase();return!n||e.some(e=>e.toLowerCase().includes(n))}async function Bi(e){let t=e.choices;if(e.choices.length>8){let n=V(await pr({message:e.searchMessage,placeholder:`Type part of the name or URL, or leave blank`}));if(t=e.choices.filter(t=>zi(e.searchValues(t),n)),!t.length)throw new y(`INVALID_INPUT`,e.emptyMessage)}return V(await fr({message:e.message,options:t.map(t=>{let n=e.hint?.(t);return{value:t,label:e.label(t),...n?{hint:n}:{}}})}))}async function Vi(e){if(e.site)return e.site;let t=oe(),n=!!t.defaultSite&&(t.clients.some(e=>e.siteUrl===t.defaultSite)||t.sites.some(e=>e.siteUrl===t.defaultSite));if(t.defaultSite&&n)return t.defaultSite;if(e.options?.json||!L())throw new y(`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 cn(e.options?.refresh);if(!r.length)throw new y(`PROPERTY_NOT_FOUND`,`No Search Console properties found for this Google login.`);return r.length===1?r[0]?.siteUrl??``:(await Bi({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 H(e){if(e.client&&e.project&&e.client!==e.project)throw new y(`INVALID_INPUT`,`Use either --project or --client, not both.`);let t=e.project??e.client;if(t){let e=we(t);if(!e)throw new y(`INVALID_INPUT`,`Project not found: ${t}`);return{client:e,site:e.siteUrl}}let n=we();return!e.site&&n?{client:n,site:n.siteUrl}:{site:await Vi({site:e.site,options:e.options})}}async function Hi(e){if(e.client&&e.project&&e.client!==e.project)throw new y(`INVALID_INPUT`,`Use either --project or --client, not both.`);let t=e.project??e.client;if(t){let e=we(t);if(!e)throw new y(`INVALID_INPUT`,`Project not found: ${t}`);return e}let n=ae();if(!n.length)return;let r=we();if(r)return r;if(!(e.options?.json||!L()))return Bi({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]})}async function Ui(e){if(e.property)return e.property;let t=oe();if(t.analytics.google.defaultPropertyId)return t.analytics.google.defaultPropertyId;if(e.options?.json||!L())throw new y(`INVALID_INPUT`,`No Google Analytics property selected. Pass --property or run this command in a terminal to choose one.`);let n=(await Ie()).flatMap(e=>e.propertySummaries.map(t=>({property:l(t.property),displayName:t.displayName??t.property,account:e.displayName??e.account})));if(!n.length)throw new y(`PROPERTY_NOT_FOUND`,`No Google Analytics properties found for this Google login.`);return n.length===1?n[0]?.property??``:(await Bi({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}async function Wi(e,t={}){let n=E(e[`report-id`]);if(n){let e=Xt(n);if(!e)throw Error(`No saved crawl report found for ${n}.`);return e}let r=D(e),i=E(e.site),a=r||i?await H({client:r,site:i,options:t}):void 0,o=tn(a?.site);if(!o)throw Error(a?.site?`No saved crawl report found for ${a.site}. Run \`seo crawl --project ${a.client?.id??a.site} --save\` first.`:"No saved crawl report found. Run `seo crawl <url> --save` first, or pass --report-id.");return o}function Gi(e){let t=t=>e.checks.filter(e=>e.status===t).length,n=t(`fail`),r=t(`warning`),i=t(`unknown`),a=t(`pass`),o=n?`fail`:r?`warning`:i?`unknown`:`pass`;Mi({title:`AI search evidence`,target:e.url,status:o,summary:e.headline,metrics:[{label:`Passed`,value:a,status:`pass`},{label:`Review`,value:r,status:`warning`},{label:`Failed`,value:n,status:`fail`},{label:`Unknown`,value:i,status:`unknown`}],sections:e.sections.map(e=>({title:e.title,diagnostics:e.checks.map(e=>({status:e.status,title:e.title,explanation:e.plainEnglish,fix:e.status===`pass`?void 0:e.action,evidence:(e.urls??[]).slice(0,5)}))})),notes:[`Data: ${e.dataStatus}. Report: ${e.reportId}.`,...e.caveats]})}const Ki=C({meta:{name:`ai-readiness`,description:`Review AI-search technical evidence and optional observations`},args:{"report-id":{type:`string`,description:`Saved crawl report id to analyze.`},site:{type:`string`,description:`GSC property URL for selecting the latest saved crawl.`},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.`}},run:async({args:e})=>{let t=N(e),n=Pt(await Wi(e,{json:t}));if(t){R(n);return}Gi(n)}});function qi(e){let t=E(e.agentReadinessUrl),n=E(e.url);if(t&&n&&t!==n)throw Error(`Use either the positional URL or --url, not both.`);return t??n}function Ji(e){let t=e.summary.failed>0?`fail`:e.summary.warnings>0?`warning`:e.summary.unknown>0?`unknown`:`pass`;Mi({title:`AI agent readiness`,target:e.url,status:t,summary:e.headline,metrics:[{label:`Passed`,value:e.summary.passed,status:`pass`},{label:`Review`,value:e.summary.warnings,status:`warning`},{label:`Failed`,value:e.summary.failed,status:`fail`},{label:`Unknown`,value:e.summary.unknown,status:`unknown`}],sections:e.sections.map(e=>({title:e.title,diagnostics:e.checks.filter(e=>![`info`,`notApplicable`].includes(e.status)).map(e=>({status:e.status,title:e.title,explanation:e.plainEnglish,fix:e.status===`pass`?void 0:e.action,evidence:(e.urls??[]).slice(0,5)}))})),notes:[`Profile: ${e.profile}. Data: ${e.dataStatus}. Report: ${e.reportId}.`,...e.caveats]})}const Yi=C({meta:{name:`agent-readiness`,description:`Check content-site access, Markdown alternatives, discovery, and identity for AI agents`},args:{agentReadinessUrl:{type:`positional`,required:!1,description:`Public site URL to check.`},url:{type:`string`,description:`Public site URL to check.`},"report-id":{type:`string`,description:`Saved crawl report id to analyze without fetching again.`},site:{type:`string`,description:`GSC property URL for selecting the latest saved crawl.`},client:{type:`string`,description:`Legacy alias for --project.`},project:{type:`string`,description:`Saved project id or name.`},"max-pages":{type:`string`,description:`Maximum public HTML pages to crawl.`},"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.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let n=N(e),r=qi(e),i=E(e[`report-id`]);if(r&&i)throw Error(`Use either a URL or --report-id, not both.`);let a=dt(r?await(async()=>{let i=D(e),a=E(e.site),o=i||a?await H({client:i,site:a,options:{json:n}}):void 0;return t({url:r,projectId:o?.client?.id,site:o?.site,maxPages:k(e[`max-pages`]),fetchRate:Kr(e),refresh:O(e.refresh),checkAgentDiscovery:!0,useSitemap:!0,checkExternal:!1})})():await Wi(e,{json:n}));if(n){R(a);return}Ji(a)}}),Xi=C({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.`}},run:async({args:e})=>{let t=await Ie(),n=t.flatMap(e=>e.propertySummaries.map(t=>({account:e.displayName??e.account,property:l(t.property),displayName:t.displayName??t.property}))),r=E(e.save);if(r){let e=oe();e.analytics.google.defaultPropertyId=r,Ae(e)}if(N(e)){R({accountSummaries:t,properties:n,saved:r});return}r&&process.stdout.write(`Saved default Google Analytics property ${r}.\n`),B([`Property`,`Name`,`Account`],n.map(e=>[e.property,e.displayName,e.account]))}});function U(e){return Math.round(e).toLocaleString(`en-GB`)}function Zi(e){return e.toFixed(1)}function W(e){return`${(e*100).toFixed(1)}%`}function G(e,t=72){return $r(e,t)}function Qi(e){return e.verification?.requested?`${e.verification.verified} checked, ${e.verification.failed} failed`:`off`}function $i(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 K(e){Mi($i(e))}function q(e,t){B(e,t.slice(0,25)),t.length>25&&process.stdout.write(`Showing 25 of ${t.length}. Use --json for full data.\n`)}function J(e,t,n=5){let r=t.filter(e=>e.action.trim().length>0).slice(0,n),i=ea(e,r);i&&(process.stdout.write(`
132
+ `),Mi(i),t.length>r.length&&process.stdout.write(`Showing ${r.length} of ${t.length}. Use --json for full data.\n`))}function ea(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 ta(e){process.stdout.write(`\nNext: ${e}\n`)}function Y(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 na(e,t={resolveClient:Hi,resolveGoogleAnalyticsProperty:Ui}){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:g(n),options:e.options})}const ra=C({meta:{name:`analytics`,description:`Read analytics data from connected providers`},subCommands:{google:C({meta:{name:`google`,description:`Read Google Analytics properties and reports`},subCommands:{properties:Xi,report:C({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.`},"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=N(e),n=await na({property:E(e.property),project:D(e),options:{json:t}}),r=await f(n,await Jr(e.body,e[`body-file`])??{dateRanges:[{startDate:E(e[`start-date`]),endDate:E(e[`end-date`])}],dimensions:(j(e.dimensions)??[]).map(e=>({name:e})),metrics:(j(e.metrics)??[]).map(e=>({name:e})),limit:E(e.limit)},{refresh:O(e.refresh)});if(t){R(r);return}let i=dn(r);if(K({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]??{});B(e,i.slice(0,25).map(t=>e.map(e=>t[e]??``)))}}})}})}});function ia(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 aa(e){return e.active===`keychain`?`system keychain`:e.configured===`keychain`?`private file fallback`:`private file`}function oa(e){return e===`shared`?`SEO Skill app`:`Local OAuth config`}function sa(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`,oa(e.clientSource)],[`Expires`,Br(e.expiresAt)]),t}const ca=C({meta:{name:`auth`,description:`Authentication commands`},subCommands:{login:C({meta:{name:`login`,description:`Run Google OAuth flow`},run:async()=>{if((await Ot()).activeMode===`service-account`)throw new y(`AUTH_CONFIG_REQUIRED`,`A service account is active from the environment. Unset its credential variable before running browser OAuth.`);let e=await r();z(sa({accountEmail:e.account_email,clientSource:e.client_source,expiresAt:e.expires_at,scope:e.scope}))}}),logout:C({meta:{name:`logout`,description:`Delete locally stored tokens`},run:async()=>{let e=await Ot();if(await ye(),e.activeMode===`service-account`){process.stdout.write(`Deleted local OAuth tokens. Service account credentials remain available through the environment.
35
133
  `);return}process.stdout.write(`Deleted local OAuth tokens.
36
134
  Revoke at https://myaccount.google.com/permissions if you also want Google to forget the grant.
37
- `)}}),whoami:S({meta:{name:`whoami`,description:`Show the signed-in Google account`},args:{json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=await ht();if(t.activeMode===`service-account`){let n={mode:`service-account`,account:t.identity??null,source:mi(t.serviceAccount.source)};if(M(e)){L(n);return}R([[`Account type`,`Service account`],[`Account`,t.identity??`Invalid configuration`],[`Credentials`,n.source],[`Access`,`Search Console and Google Analytics, read only`]]);return}let n=t.tokens;if(!n){if(M(e)){L({mode:`oauth`,account:null});return}process.stdout.write(`Not logged in.
38
- `);return}if(M(e)){L({mode:`oauth`,account:n.account_email,scopes:n.scope.split(/\s+/u).filter(Boolean),clientSource:n.client_source,expiresAt:n.expires_at});return}R(_i({accountEmail:n.account_email,clientSource:n.client_source,expiresAt:n.expires_at,scope:n.scope}))}}),status:S({meta:{name:`status`,description:`Show local Google auth status`},args:{json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=await ht(),n=t.activeMode===`oauth`?await Et():void 0;if(M(e)){L({authenticated:t.activeMode===`oauth`?!!t.tokens:t.serviceAccount.configured,mode:t.activeMode,identity:t.identity,account:t.activeMode===`oauth`?t.tokens?.account_email:void 0,scopes:t.activeMode===`oauth`?t.tokens?.scope:void 0,clientSource:t.activeMode===`oauth`?t.tokens?.client_source:void 0,expiresAt:t.activeMode===`oauth`?t.tokens?.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},...n?{tokenStorage:n}:{}});return}if(t.activeMode===`service-account`){let e=[[`Mode`,`service account`],[`Identity`,t.identity??`unavailable`],[`Source`,mi(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]),R(e);return}if(!t.tokens){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 r=n??await Et();R([[`Account`,t.tokens.account_email],[`Scopes`,t.tokens.scope],[`Client`,gi(t.tokens.client_source)],[`Expires`,lr(t.tokens.expires_at)],[`Tokens file`,Xt().tokensFile],[`Token storage`,hi(r)],...r.reason?[[`Storage note`,r.reason]]:[],[`OAuth mode`,t.sharedConfigured?`SEO Skill app available`:t.byoConfigured?`Local OAuth config available`:`missing`],[`Revoke at`,`https://myaccount.google.com/permissions`]])}}),storage:S({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 v(`INVALID_INPUT`,`Choose either --keychain or --file, not both.`);let t=e.keychain||e.file?await at(e.keychain?`keychain`:`file`):await Et();if(M(e)){L(t);return}R([[`Configured storage`,t.configured],[`Active storage`,hi(t)],...t.reason?[[`Note`,t.reason]]:[]])}}),refresh:S({meta:{name:`refresh`,description:`Refresh the Google OAuth token`},run:async()=>{if((await ht()).activeMode===`service-account`){process.stdout.write(`Service accounts request short-lived Google tokens automatically. No local refresh token is stored.
39
- `);return}let e=await zt();process.stdout.write(`Refreshed. New expiry ${new Date(e.expires_at).toISOString()}.\n`)}}),"setup-client":S({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(!I({json:M(e)}))throw new v(`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.");Ln(`Local Google OAuth client`),C([`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(`
40
- `),`Advanced`),Re({clientId:V(await Wn({message:`Google Desktop OAuth client ID`,validate:e=>e?void 0:`Client ID is required`})),clientSecret:V(await Hn({message:`Google Desktop OAuth client secret`,validate:e=>e?void 0:`Client secret is required`}))}),Vn(`Saved local OAuth config to ${Xt().oauthClientFile}`)}})}}),yi=S({meta:{name:`cache`,description:`Cache helpers`},subCommands:{stats:S({meta:{name:`stats`,description:`Show local cache size and row counts`},run:async()=>{let e=Nt();R([[`DB`,e.dbPath],[`Size`,Zr(e.sizeBytes)],[`Cached data`,`${Zr(e.logicalSizeBytes)} of ${Zr(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)],[`Semrush`,String(e.counts.semrush_cache??0)],[`HTTP`,String(e.counts.http_cache??0)]])}}),clear:S({meta:{name:`clear`,description:`Clear cached API and HTTP data`},args:{provider:{type:`string`,description:`Optional cache provider: gsc, google-analytics, semrush, or http`}},run:async({args:e})=>{let t=Ct(e.provider);process.stdout.write(`Removed ${t} cached rows.\n`)}})}});async function bi(e,t={}){return(await U({client:e.client,project:e.project,site:e.site,options:t})).site}function xi(e){if(e.startsWith(`http://`)||e.startsWith(`https://`))return e;if(e.startsWith(`sc-domain:`))return`https://${e.slice(10)}/`}function Si(e){return e.replace(/^sc-domain:/,``).replace(/^https?:\/\//,``)}function Ci(e){return e.toLowerCase().replace(/^https?:\/\//,``).replace(/^sc-domain:/,``).replace(/[^a-z0-9]+/g,`-`).replace(/^-+|-+$/g,``).slice(0,60)}function wi(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 Ti(e){return e?e===`serp-framing`?`wording gap`:e===`content-gap`?`content gap`:e===`technical-check`?`technical check`:`covered`:`-`}function Ei(e){let t=e.platform===`win32`?vn:hn;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 Di(e={}){let t=e.home??fn(),n=e.platform??process.platform,r=Ei({home:t,platform:n,env:e.env??process.env}),i=n===`win32`?vn:hn,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 Oi(e,t={}){let n=t.platform??process.platform,r=t.exists??y,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=x(e,t);if(r(n))return n}for(let e of a){let t=x(b(i),e);if(r(t))return t}return e}function ki(e){return Oi(e)!==e}function Ai(e={}){let t=e.targets??Di(),n=e.hasCommand??ki,r=e.hasPath??y;return t.filter(e=>e.kind===`native`?e.commandNames.some(n):r(e.path)||e.detectionPaths.some(r)||e.commandNames.some(n))}const ji={insertSpaces:!0,tabSize:2,eol:`
41
- `};function Mi(){return new Date().toISOString().replace(/[:.]/g,`-`)}function Ni(e,t){rn(b(e),{recursive:!0,mode:448});let n=`${e}.${process.pid}.${Date.now()}.tmp`;dn(n,t,{encoding:`utf8`,mode:y(e)?un(e).mode&511:384}),cn(n,e)}function Pi(e){let t=`${e}.bak-${Mi()}`,n=t,r=2;for(;y(n);)n=`${t}-${r}`,r+=1;tn(e,n,en.COPYFILE_EXCL)}function Fi(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function Ii(e){if(!y(e.path))return{source:`{}
42
- `,config:{}};let t=an(e.path,`utf8`),n=[],r=Xn(t,n,{allowTrailingComma:!0});if(n.length>0){let t=n[0],r=t?`${Zn(t.error)} at offset ${t.offset}`:`unknown parse error`;throw Error(`Cannot update ${e.path}: invalid JSONC (${r}).`)}if(!Fi(r))throw Error(`Cannot update ${e.path}: config root must be an object.`);if(r.mcpServers!==void 0&&!Fi(r.mcpServers))throw Error(`Cannot update ${e.path}: mcpServers must be an object.`);return{source:t,config:r}}function Li(e){return e.includes(`mcp`)&&e.includes(`serve`)&&e.some(e=>e===`seo`||e.startsWith(`seo@`)||e===`@seo/cli`)}function Ri(e,t){return typeof e.command!=`string`||t.length!==3||t[1]!==`mcp`||t[2]!==`serve`||!Fi(e.env)?!1:e.env.SEO_MCP_CONFIGURED_BY===`seo`}function zi(e){if(!Fi(e)||!Array.isArray(e.args))return!1;let t=e.args.filter(e=>typeof e==`string`);return Li(t)||Ri(e,t)}function Bi(e){let t=Jn(e.source,Yn(e.source,[`mcpServers`,`seo`],e.value,{formattingOptions:ji}));y(e.target.path)&&Pi(e.target.path),Ni(e.target.path,t)}function Vi(e,t){let n=xn(e,t,{encoding:`utf8`});return{status:n.status,stdout:n.stdout??``,stderr:n.stderr??``,error:n.error}}function Hi(e){let t=e.replaceAll(`\\`,`/`).toLowerCase();return t.includes(`/_npx/`)||t.includes(`/.npm/_npx/`)||t.includes(`/.cache/pnpm/dlx/`)}function Ui(e){if(!e||!y(e))return;let t=sn(e);return Hi(t)?void 0:t}function Wi(e={}){let n=Ui(e.cliPath??process.argv[1]);if(n)return{command:e.nodePath??process.execPath,args:[n,`mcp`,`serve`]};let r=`seo@${e.version??t}`;return(e.platform??process.platform)===`win32`?{command:`cmd`,args:[`/c`,`npx`,`-y`,r,`mcp`,`serve`]}:{command:`npx`,args:[`-y`,r,`mcp`,`serve`]}}function Gi(e=!1,t=Wi()){let n=Xt(),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 Ki(e){return e.includes(`SEO_MCP_CONFIGURED_BY`)||/args:\s+-y\s+(?:seo(?:@\S+)?|@seo\/cli)\s+mcp\s+serve\b/.test(e)}function qi(e,t,n){let r=Oi(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&&!Ki(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=Xt(),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 Ji(e,t={}){let n={runCommand:t.runCommand??Vi,reinstall:t.reinstall??!1,serverCommand:t.serverCommand??Wi()};if(e.kind===`native`)return qi(e,`install`,n);let{source:r,config:i}=Ii(e),a=i.mcpServers?.seo,o=Gi(e.includeType,n.serverCommand);if(Cn(a,o)&&!n.reinstall)return{client:e.client,path:e.path,changed:!1};if(a!==void 0&&!zi(a))throw Error(`Cannot install SEO MCP: ${e.path} already has an unmanaged mcpServers.seo entry.`);return Bi({target:e,source:r,value:o}),{client:e.client,path:e.path,changed:!0}}function Yi(e,t={}){if(e.kind===`native`)return qi(e,`uninstall`,{runCommand:t.runCommand??Vi,reinstall:!1,serverCommand:Wi()});if(!y(e.path))return{client:e.client,path:e.path,changed:!1};let{source:n,config:r}=Ii(e),i=r.mcpServers;if(i?.seo===void 0)return{client:e.client,path:e.path,changed:!1};if(!zi(i.seo))throw Error(`Cannot uninstall SEO MCP: ${e.path} has an unmanaged mcpServers.seo entry.`);return Bi({target:e,source:n,value:void 0}),{client:e.client,path:e.path,changed:!0}}function Xi(e={}){let t=xn(`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 Zi(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 Qi(e){let t=[],n=!0,r=0;async function i(){for(;r<e.length;){let i=e[r];if(r+=1,i)try{let e=await ct(i.property);t.push(...e.filter(e=>e.webStreamData).map(e=>({account:i.account,property:i.property,propertyName:i.label,stream:e})))}catch{n=!1}}}return await Promise.all(Array.from({length:Math.min(e.length,4)},()=>i())),{candidates:t,complete:n}}async function $i(e){let t=await ht();if(t.activeMode===`service-account`)return`service-account`;if(t.tokens)return`already-connected`;if(e[`skip-auth`])return`skipped`;if(!I({json:e.json===!0}))throw new v(`AUTH_REQUIRED`,"Not logged in. Run `seo auth login`, or pass --skip-auth to save a project profile without connecting Google.");let n=V(await Un({message:`Connect Google now?`,options:Zi({sharedConfigured:t.sharedConfigured,byoConfigured:t.byoConfigured,canSkip:typeof e.site==`string`&&e.site.length>0})}));return n===`skip`?`skipped`:(n===`setup`&&Re({clientId:V(await Wn({message:`Google Desktop OAuth client ID`,validate:e=>e?void 0:`Client ID is required`})),clientSecret:V(await Hn({message:`Google Desktop OAuth client secret`,validate:e=>e?void 0:`Client secret is required`}))}),C(`Connected as ${(await Ge()).account_email}. seo has read-only access and cannot change your site.`,`Google connected`),`connected`)}async function ea(e){if(e.property)return{propertyId:e.property,selection:`explicit`,reason:`Set with --google-analytics-property.`};if(!(e.interactive??I()))return;let t=(await jt().catch(()=>[])).flatMap(e=>e.propertySummaries.map(t=>({property:It(t.property),label:t.displayName??t.property,account:e.displayName??e.account})));if(!t.length)return;let{candidates:n,complete:r}=await Qi(t),i=r?sr(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,selection:`matched`,reason:cr(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=V(await Un({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?cr(t.match,e.site):t.account}))]}));if(c)return{propertyId:c.property,selection:`manual`,reason:c.match?cr(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 ta(e){return e[`skip-skill`]||!I({json:e.json===!0})?{status:`skipped`}:V(await In({message:`Install the SEO skill so coding agents know how to run reports?`,initialValue:!0}))?Xi():{status:`declined`}}async function na(e){if(e[`skip-mcp`]||!I({json:e.json===!0}))return[];let t=Ai();if(t.length===0||!V(await In({message:`Install seo as an MCP server too?`,initialValue:!0})))return[];let n=V(await Bn({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 Ji(e)}catch(t){return{client:e.client,path:e.path,changed:!1,error:t instanceof Error?t.message:String(t)}}})}function ra(e){return e.status===`installed`?`installed`:e.status===`failed`?`install failed`:`not installed`}function ia(e){return/^[A-Za-z0-9_./:@-]+$/.test(e)?e:`'${e.replaceAll(`'`,`'\\''`)}'`}function aa(){process.stdout.write(`
43
- `),Wr({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 oa(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 sa(e){let t=e.filter(e=>!!e.error);if(t.length!==0)return t.map(e=>`${e.client}: ${e.error}`).join(`
44
- `)}function ca(e,t,n){if(n)return;let r=o().filter(t=>t.siteUrl===e);return r.length===1?r[0]:r.find(e=>e.isDefault)??r.find(e=>e.name.toLocaleLowerCase()===t.toLocaleLowerCase())}async function la(e){let t=M(e);if(t||Ln(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?L({dryRun:!0,next:e}):(C(e.join(`
45
- `),`This setup will guide you through`),Vn(`Dry run complete.`));return}Ht();let n={site:T(e.site),options:{json:t,refresh:D(e.refresh)}},r=t?await H(n):void 0,i=await $i(e),a=r??await H(n),o=Si(a);if(I({json:t})&&C(`A project profile remembers the site and report defaults so future commands stay short.`,`Project profile`),!(D(e[`skip-profile`])!==!0&&(!I({json:t})||V(await In({message:`Save this site as a project profile?`,initialValue:!0}))))){let n=await na(e),r=await ta(e),o=`--site ${ia(a)}`,s=[`seo report ${o}`,`seo refresh-priorities ${o} --verify-content`,`seo technical-watch ${o}`],c={site:a,auth:i,mcp:n,skill:r,next:s};if(t){L(c);return}R([[`Project profile`,`not saved`],[`GSC property`,a],[`Auth`,i],[`MCP`,oa(n)],[`SEO skill`,ra(r)]]);let l=sa(n);l&&C(l,`MCP setup skipped`),r.error&&C(r.error,`SEO skill install failed`),C(s.join(`
46
- `),`Try next`),Vn(`Setup complete.`),aa();return}let s=T(e.name)??(I({json:t})?V(await Wn({message:`Project name`,placeholder:o,defaultValue:o})):o),c=T(e.id),l=c??ca(a,s,c)?.id??Ci(s),u=xi(a)??``,d=T(e.url)??(I({json:t})?V(await Wn({message:`Website URL to crawl`,placeholder:u||`https://example.com`,defaultValue:u})):u||void 0),f=j(e.urls).length>0?j(e.urls):[],p=await ea({property:T(e[`google-analytics-property`]),site:a,interactive:I({json:t})}),m=p?.propertyId,ee=kt({id:l,name:s,siteUrl:a}),h=j(e.brand).length>0?j(e.brand):ee,te=O(e[`report-day`])??1,ne=O(e.weekday)??1,re=D(e.default)??!0,g=Wt({id:l,name:s,siteUrl:a,startUrl:d,watchUrls:f,brandTerms:h,analytics:m?{google:{propertyId:m}}:void 0,reportDay:te,technicalWeekday:ne,isDefault:re}),_=await na(e),ie=await ta(e),ae=[`seo report --project ${g.id}`,`seo refresh-priorities --project ${g.id} --verify-content`,`seo technical-watch --project ${g.id}`],oe={client:g,site:a,auth:i,googleAnalytics:p,mcp:_,skill:ie,next:ae};if(t){L(oe);return}R([[`Project profile`,`${g.name} (${g.id})`],[`GSC property`,g.siteUrl],[`Crawl URL`,g.startUrl??`not set`],[`Watch URLs`,String(g.watchUrls.length)],[`Brand terms`,g.brandTerms.join(`, `)||`not set`],[`Google Analytics property`,g.analytics.google?.propertyId??`not connected (optional)`],...p?[[`Google Analytics selection`,p.reason]]:[],[`Auth`,i],[`MCP`,oa(_)],[`SEO skill`,ra(ie)]]);let se=sa(_);se&&C(se,`MCP setup skipped`),ie.error&&C(ie.error,`SEO skill install failed`),C(ae.join(`
47
- `),`Try next`),Vn(`Setup complete.`),aa()}const ua=S({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.`},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.`},brand:{type:`string`,description:`Comma-separated branded query terms to exclude by default.`},"report-day":{type:`string`,description:`Preferred monthly report day. Defaults to 1.`},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})=>la(e)}),da=S({...ua,meta:{name:`start`,description:`Start here: connect Google, save a profile, and get the first report commands`}}),fa=S({...ua,meta:{name:`setup`,description:`Guided setup for one saved project profile`}}),pa=S({meta:{name:`brand`,description:`Detect and manage project brand query terms`},subCommands:{detect:S({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=M(e),n=Kt(E(e)),r=await _({site:await H({site:T(e.site)??n?.siteUrl,options:{json:t,refresh:D(e.refresh)}}),id:n?.id,name:n?.name,days:O(e.days),limit:O(e.limit),minImpressions:O(e[`min-impressions`]),refresh:D(e.refresh)}),i=D(e.save)&&n?Wt({id:n.id,name:n.name,siteUrl:n.siteUrl,startUrl:n.startUrl,watchUrls:n.watchUrls,analytics:n.analytics,brandTerms:r.suggestedTerms,reportDay:n.reportDay,technicalWeekday:n.technicalWeekday,isDefault:n.isDefault}):void 0;if(D(e.save)&&!n)throw Error(`Pass --project to save detected brand terms.`);if(t){L({...r,saved:i});return}R([[`Property`,r.site],[`Derived terms`,r.derivedTerms.join(`, `)],[`Suggested terms`,r.suggestedTerms.join(`, `)],[`Saved`,i?i.id:`no`]]),z([`Term`,`Score`,`Evidence`],r.candidates.map(e=>[e.term,e.score,e.evidence.map(e=>`${e.query} (${Math.round(e.clicks)} clicks)`).join(`, `)]))}})}});function ma(e){Jr(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 ha(e){R([[`ID`,e.id],[`Name`,e.name],[`GSC property`,e.siteUrl],[`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`],[`Report day`,e.reportDay?String(e.reportDay):`not set`],[`Technical weekday`,e.technicalWeekday===void 0?`not set`:String(e.technicalWeekday)],[`Default`,e.isDefault?`yes`:`no`]])}const ga=S({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.`},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.`},brand:{type:`string`,description:`Comma-separated branded query terms to exclude by default.`},"report-day":{type:`string`,description:`Preferred monthly report day, 1-31.`},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=M(e),n=Wt({id:T(e.id),name:T(e.name),siteUrl:await H({site:T(e.site),options:{json:t}}),startUrl:T(e.url),watchUrls:j(e.urls),brandTerms:j(e.brand),analytics:T(e[`google-analytics-property`])?{google:{propertyId:T(e[`google-analytics-property`])??``}}:void 0,reportDay:O(e[`report-day`]),technicalWeekday:O(e.weekday),isDefault:D(e.default)});if(t){L(n);return}ha(n)}}),_a=S({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=Ce(T(e.id)??``);if(M(e)){L(t);return}process.stdout.write(`Default project set to ${t.id}.\n`)}}),va=S({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=T(e.id)??``,n=Ue(t);if(M(e)){L({id:t,deleted:n});return}process.stdout.write(`${n?`Deleted`:`Not found`} ${t}.\n`)}}),ya=S({meta:{name:`client`,description:`Manage saved project profiles`},subCommands:{setup:fa,brand:pa,list:S({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=o();if(M(e)){L({clients:t});return}ma(t)}}),add:ga,show:S({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=Kt(T(e.id));if(!t)throw Error(`Project not found.`);if(M(e)){L(t);return}ha(t)}}),default:_a,delete:va}}),ba=S({...ya,meta:{name:`projects`,description:`Manage saved project profiles`}}),xa=S({meta:{name:`content`,description:`Optimize page content from real GSC queries`},subCommands:{optimize:S({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":w(`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=M(e),n=await U({client:E(e),site:T(e.site),options:{json:t,refresh:D(e.refresh)}}),r=await Ae({site:n.site,url:T(e.url)??``,days:O(e.days),limit:O(e.limit),minImpressions:O(e[`min-impressions`]),brandTerms:n.client?.brandTerms,includeBrand:D(e[`include-brand`]),verifyContent:D(e[`verify-content`])!==!1,js:D(e.js)?!0:`auto`,refresh:D(e.refresh)});if(t){L(r);return}R([[`Property`,r.site],[`URL`,r.url],[`Score`,`${r.summary.score}/100`],[`Primary intent`,r.summary.primaryIntent],[`Primary query`,r.summary.primaryQuery??`none`],[`Queries`,W(r.summary.queries)],[`Opportunities`,W(r.summary.opportunities)],[`Estimated lift`,W(r.summary.estimatedClickLift)],[`Verdict`,r.summary.verdict]]),J(`Top actions`,r.topActions.map(e=>({label:e.title,action:e.action,context:e.plainEnglish}))),r.brief.sections.length&&(process.stdout.write(`
135
+ `)}}),whoami:C({meta:{name:`whoami`,description:`Show the signed-in Google account`},args:{json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=await Ot();if(t.activeMode===`service-account`){let n={mode:`service-account`,account:t.identity??null,source:ia(t.serviceAccount.source)};if(N(e)){R(n);return}z([[`Account type`,`Service account`],[`Account`,t.identity??`Invalid configuration`],[`Credentials`,n.source],[`Access`,`Search Console and Google Analytics, read only`]]);return}let n=t.tokens;if(!n){if(N(e)){R({mode:`oauth`,account:null});return}process.stdout.write(`Not logged in.
136
+ `);return}if(N(e)){R({mode:`oauth`,account:n.account_email,scopes:n.scope.split(/\s+/u).filter(Boolean),clientSource:n.client_source,expiresAt:n.expires_at});return}z(sa({accountEmail:n.account_email,clientSource:n.client_source,expiresAt:n.expires_at,scope:n.scope}))}}),status:C({meta:{name:`status`,description:`Show local Google auth status`},args:{json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=await Ot(),n=t.activeMode===`oauth`?await te():void 0;if(N(e)){R({authenticated:t.activeMode===`oauth`?!!t.tokens:t.serviceAccount.configured,mode:t.activeMode,identity:t.identity,account:t.activeMode===`oauth`?t.tokens?.account_email:void 0,scopes:t.activeMode===`oauth`?t.tokens?.scope:void 0,clientSource:t.activeMode===`oauth`?t.tokens?.client_source:void 0,expiresAt:t.activeMode===`oauth`?t.tokens?.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},...n?{tokenStorage:n}:{}});return}if(t.activeMode===`service-account`){let e=[[`Mode`,`service account`],[`Identity`,t.identity??`unavailable`],[`Source`,ia(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]),z(e);return}if(!t.tokens){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 r=n??await te();z([[`Account`,t.tokens.account_email],[`Scopes`,t.tokens.scope],[`Client`,oa(t.tokens.client_source)],[`Expires`,Br(t.tokens.expires_at)],[`Tokens file`,He().tokensFile],[`Token storage`,aa(r)],...r.reason?[[`Storage note`,r.reason]]:[],[`OAuth mode`,t.sharedConfigured?`SEO Skill app available`:t.byoConfigured?`Local OAuth config available`:`missing`],[`Revoke at`,`https://myaccount.google.com/permissions`]])}}),storage:C({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 y(`INVALID_INPUT`,`Choose either --keychain or --file, not both.`);let t=e.keychain||e.file?await pe(e.keychain?`keychain`:`file`):await te();if(N(e)){R(t);return}z([[`Configured storage`,t.configured],[`Active storage`,aa(t)],...t.reason?[[`Note`,t.reason]]:[]])}}),refresh:C({meta:{name:`refresh`,description:`Refresh the Google OAuth token`},run:async()=>{if((await Ot()).activeMode===`service-account`){process.stdout.write(`Service accounts request short-lived Google tokens automatically. No local refresh token is stored.
137
+ `);return}let e=await ge();process.stdout.write(`Refreshed. New expiry ${new Date(e.expires_at).toISOString()}.\n`)}}),"setup-client":C({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(!L({json:N(e)}))throw new y(`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.");or(`Local Google OAuth client`),w([`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`),bn({clientId:V(await pr({message:`Google Desktop OAuth client ID`,validate:e=>e?void 0:`Client ID is required`})),clientSecret:V(await dr({message:`Google Desktop OAuth client secret`,validate:e=>e?void 0:`Client secret is required`}))}),ur(`Saved local OAuth config to ${He().oauthClientFile}`)}})}}),la=C({meta:{name:`cache`,description:`Cache helpers`},subCommands:{stats:C({meta:{name:`stats`,description:`Show local cache size and row counts`},run:async()=>{let e=jt();z([[`DB`,e.dbPath],[`Size`,Ri(e.sizeBytes)],[`Cached data`,`${Ri(e.logicalSizeBytes)} of ${Ri(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)],[`Semrush`,String(e.counts.semrush_cache??0)],[`HTTP`,String(e.counts.http_cache??0)]])}}),clear:C({meta:{name:`clear`,description:`Clear cached API and HTTP data`},args:{provider:{type:`string`,description:`Optional cache provider: gsc, google-analytics, semrush, or http`}},run:async({args:e})=>{let t=Se(e.provider);process.stdout.write(`Removed ${t} cached rows.\n`)}})}});async function ua(e,t={}){return(await H({client:e.client,project:e.project,site:e.site,options:t})).site}function da(e){if(e.startsWith(`http://`)||e.startsWith(`https://`))return e;if(e.startsWith(`sc-domain:`))return`https://${e.slice(10)}/`}function fa(e){return e.replace(/^sc-domain:/,``).replace(/^https?:\/\//,``)}function pa(e){return e.toLowerCase().replace(/^https?:\/\//,``).replace(/^sc-domain:/,``).replace(/[^a-z0-9]+/g,`-`).replace(/^-+|-+$/g,``).slice(0,60)}function ma(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 ha(e){return e?e===`serp-framing`?`wording gap`:e===`content-gap`?`content gap`:e===`technical-check`?`technical check`:`covered`:`-`}function ga(e){let t=e.platform===`win32`?zn:In;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 _a(e={}){let t=e.home??Nn(),n=e.platform??process.platform,r=ga({home:t,platform:n,env:e.env??process.env}),i=n===`win32`?zn:In,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 va(e,t={}){let n=t.platform??process.platform,r=t.exists??b,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=S(e,t);if(r(n))return n}for(let e of a){let t=S(x(i),e);if(r(t))return t}return e}function ya(e){return va(e)!==e}function ba(e={}){let t=e.targets??_a(),n=e.hasCommand??ya,r=e.hasPath??b;return t.filter(e=>e.kind===`native`?e.commandNames.some(n):r(e.path)||e.detectionPaths.some(r)||e.commandNames.some(n))}const xa={insertSpaces:!0,tabSize:2,eol:`
139
+ `};function Sa(){return new Date().toISOString().replace(/[:.]/g,`-`)}function Ca(e,t){Tn(x(e),{recursive:!0,mode:448});let n=`${e}.${process.pid}.${Date.now()}.tmp`;Mn(n,t,{encoding:`utf8`,mode:b(e)?jn(e).mode&511:384}),kn(n,e)}function wa(e){let t=`${e}.bak-${Sa()}`,n=t,r=2;for(;b(n);)n=`${t}-${r}`,r+=1;Cn(e,n,Sn.COPYFILE_EXCL)}function Ta(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function Ea(e){if(!b(e.path))return{source:`{}
140
+ `,config:{}};let t=En(e.path,`utf8`),n=[],r=yr(t,n,{allowTrailingComma:!0});if(n.length>0){let t=n[0],r=t?`${br(t.error)} at offset ${t.offset}`:`unknown parse error`;throw Error(`Cannot update ${e.path}: invalid JSONC (${r}).`)}if(!Ta(r))throw Error(`Cannot update ${e.path}: config root must be an object.`);if(r.mcpServers!==void 0&&!Ta(r.mcpServers))throw Error(`Cannot update ${e.path}: mcpServers must be an object.`);return{source:t,config:r}}function Da(e){return e.includes(`mcp`)&&e.includes(`serve`)&&e.some(e=>e===`seo`||e.startsWith(`seo@`)||e===`@seo/cli`)}function Oa(e,t){return typeof e.command!=`string`||t.length!==3||t[1]!==`mcp`||t[2]!==`serve`||!Ta(e.env)?!1:e.env.SEO_MCP_CONFIGURED_BY===`seo`}function ka(e){if(!Ta(e)||!Array.isArray(e.args))return!1;let t=e.args.filter(e=>typeof e==`string`);return Da(t)||Oa(e,t)}function Aa(e){let t=_r(e.source,vr(e.source,[`mcpServers`,`seo`],e.value,{formattingOptions:xa}));b(e.target.path)&&wa(e.target.path),Ca(e.target.path,t)}function ja(e,t){let n=Hn(e,t,{encoding:`utf8`});return{status:n.status,stdout:n.stdout??``,stderr:n.stderr??``,error:n.error}}function Ma(e){let t=e.replaceAll(`\\`,`/`).toLowerCase();return t.includes(`/_npx/`)||t.includes(`/.npm/_npx/`)||t.includes(`/.cache/pnpm/dlx/`)}function Na(e){if(!e||!b(e))return;let t=On(e);return Ma(t)?void 0:t}function Pa(e={}){let t=Na(e.cliPath??process.argv[1]);if(t)return{command:e.nodePath??process.execPath,args:[t,`mcp`,`serve`]};let n=`seo@${e.version??mn}`;return(e.platform??process.platform)===`win32`?{command:`cmd`,args:[`/c`,`npx`,`-y`,n,`mcp`,`serve`]}:{command:`npx`,args:[`-y`,n,`mcp`,`serve`]}}function Fa(e=!1,t=Pa()){let n=He(),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 Ia(e){return e.includes(`SEO_MCP_CONFIGURED_BY`)||/args:\s+-y\s+(?:seo(?:@\S+)?|@seo\/cli)\s+mcp\s+serve\b/.test(e)}function La(e,t,n){let r=va(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&&!Ia(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=He(),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 Ra(e,t={}){let n={runCommand:t.runCommand??ja,reinstall:t.reinstall??!1,serverCommand:t.serverCommand??Pa()};if(e.kind===`native`)return La(e,`install`,n);let{source:r,config:i}=Ea(e),a=i.mcpServers?.seo,o=Fa(e.includeType,n.serverCommand);if(Wn(a,o)&&!n.reinstall)return{client:e.client,path:e.path,changed:!1};if(a!==void 0&&!ka(a))throw Error(`Cannot install SEO MCP: ${e.path} already has an unmanaged mcpServers.seo entry.`);return Aa({target:e,source:r,value:o}),{client:e.client,path:e.path,changed:!0}}function za(e,t={}){if(e.kind===`native`)return La(e,`uninstall`,{runCommand:t.runCommand??ja,reinstall:!1,serverCommand:Pa()});if(!b(e.path))return{client:e.client,path:e.path,changed:!1};let{source:n,config:r}=Ea(e),i=r.mcpServers;if(i?.seo===void 0)return{client:e.client,path:e.path,changed:!1};if(!ka(i.seo))throw Error(`Cannot uninstall SEO MCP: ${e.path} has an unmanaged mcpServers.seo entry.`);return Aa({target:e,source:n,value:void 0}),{client:e.client,path:e.path,changed:!0}}function Ba(e={}){let t=Hn(`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 Va(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 Ha(e){let t=[],n=!0,r=0;async function i(){for(;r<e.length;){let i=e[r];if(r+=1,i)try{let e=await h(i.property);t.push(...e.filter(e=>e.webStreamData).map(e=>({account:i.account,property:i.property,propertyName:i.label,stream:e})))}catch{n=!1}}}return await Promise.all(Array.from({length:Math.min(e.length,4)},()=>i())),{candidates:t,complete:n}}async function Ua(e){let t=await Ot();if(t.activeMode===`service-account`)return`service-account`;if(t.tokens)return`already-connected`;if(e[`skip-auth`])return`skipped`;if(!L({json:e.json===!0}))throw new y(`AUTH_REQUIRED`,"Not logged in. Run `seo auth login`, or pass --skip-auth to save a project profile without connecting Google.");let n=V(await fr({message:`Connect Google now?`,options:Va({sharedConfigured:t.sharedConfigured,byoConfigured:t.byoConfigured,canSkip:typeof e.site==`string`&&e.site.length>0})}));return n===`skip`?`skipped`:(n===`setup`&&bn({clientId:V(await pr({message:`Google Desktop OAuth client ID`,validate:e=>e?void 0:`Client ID is required`})),clientSecret:V(await dr({message:`Google Desktop OAuth client secret`,validate:e=>e?void 0:`Client secret is required`}))}),w(`Connected as ${(await r()).account_email}. seo has read-only access and cannot change your site.`,`Google connected`),`connected`)}async function Wa(e){if(e.property)return{propertyId:e.property,selection:`explicit`,reason:`Set with --google-analytics-property.`};if(!(e.interactive??L()))return;let t=(await Ie().catch(()=>[])).flatMap(e=>e.propertySummaries.map(t=>({property:l(t.property),label:t.displayName??t.property,account:e.displayName??e.account})));if(!t.length)return;let{candidates:n,complete:r}=await Ha(t),i=r?Rr(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,selection:`matched`,reason:zr(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=V(await fr({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?zr(t.match,e.site):t.account}))]}));if(c)return{propertyId:c.property,selection:`manual`,reason:c.match?zr(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 Ga(e){return e[`skip-skill`]||!L({json:e.json===!0})?{status:`skipped`}:V(await ar({message:`Install the SEO skill so coding agents know how to run reports?`,initialValue:!0}))?Ba():{status:`declined`}}async function Ka(e){if(e[`skip-mcp`]||!L({json:e.json===!0}))return[];let t=ba();if(t.length===0||!V(await ar({message:`Install seo as an MCP server too?`,initialValue:!0})))return[];let n=V(await lr({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 Ra(e)}catch(t){return{client:e.client,path:e.path,changed:!1,error:t instanceof Error?t.message:String(t)}}})}function qa(e){return e.status===`installed`?`installed`:e.status===`failed`?`install failed`:`not installed`}function Ja(e){return/^[A-Za-z0-9_./:@-]+$/.test(e)?e:`'${e.replaceAll(`'`,`'\\''`)}'`}function Ya(){process.stdout.write(`
141
+ `),ki({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 Xa(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 Za(e){let t=e.filter(e=>!!e.error);if(t.length!==0)return t.map(e=>`${e.client}: ${e.error}`).join(`
142
+ `)}function Qa(e,t,n){if(n)return;let r=ae().filter(t=>t.siteUrl===e);return r.length===1?r[0]:r.find(e=>e.isDefault)??r.find(e=>e.name.toLocaleLowerCase()===t.toLocaleLowerCase())}async function $a(e){let t=N(e);if(t||or(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?R({dryRun:!0,next:e}):(w(e.join(`
143
+ `),`This setup will guide you through`),ur(`Dry run complete.`));return}o();let n={site:E(e.site),options:{json:t,refresh:O(e.refresh)}},r=t?await Vi(n):void 0,i=await Ua(e),a=r??await Vi(n),s=fa(a);if(L({json:t})&&w(`A project profile remembers the site and report defaults so future commands stay short.`,`Project profile`),!(O(e[`skip-profile`])!==!0&&(!L({json:t})||V(await ar({message:`Save this site as a project profile?`,initialValue:!0}))))){let n=await Ka(e),r=await Ga(e),o=`--site ${Ja(a)}`,s=[`seo report ${o}`,`seo refresh-priorities ${o} --verify-content`,`seo technical-watch ${o}`],c={site:a,auth:i,mcp:n,skill:r,next:s};if(t){R(c);return}z([[`Project profile`,`not saved`],[`GSC property`,a],[`Auth`,i],[`MCP`,Xa(n)],[`SEO skill`,qa(r)]]);let l=Za(n);l&&w(l,`MCP setup skipped`),r.error&&w(r.error,`SEO skill install failed`),w(s.join(`
144
+ `),`Try next`),ur(`Setup complete.`),Ya();return}let c=E(e.name)??(L({json:t})?V(await pr({message:`Project name`,placeholder:s,defaultValue:s})):s),l=E(e.id),u=l??Qa(a,c,l)?.id??pa(c),d=da(a)??``,f=E(e.url)??(L({json:t})?V(await pr({message:`Website URL to crawl`,placeholder:d||`https://example.com`,defaultValue:d})):d||void 0),p=M(e.urls).length>0?M(e.urls):[],m=await Wa({property:E(e[`google-analytics-property`]),site:a,interactive:L({json:t})}),h=m?.propertyId,ee=St({id:u,name:c,siteUrl:a}),g=M(e.brand).length>0?M(e.brand):ee,te=k(e[`report-day`])??1,ne=k(e.weekday)??1,re=O(e.default)??!0,_=fe({id:u,name:c,siteUrl:a,startUrl:f,watchUrls:p,brandTerms:g,analytics:h?{google:{propertyId:h}}:void 0,reportDay:te,technicalWeekday:ne,isDefault:re}),v=await Ka(e),y=await Ga(e),ie=[`seo report --project ${_.id}`,`seo refresh-priorities --project ${_.id} --verify-content`,`seo technical-watch --project ${_.id}`],ae={client:_,site:a,auth:i,googleAnalytics:m,mcp:v,skill:y,next:ie};if(t){R(ae);return}z([[`Project profile`,`${_.name} (${_.id})`],[`GSC property`,_.siteUrl],[`Crawl URL`,_.startUrl??`not set`],[`Watch URLs`,String(_.watchUrls.length)],[`Brand terms`,_.brandTerms.join(`, `)||`not set`],[`Google Analytics property`,_.analytics.google?.propertyId??`not connected (optional)`],...m?[[`Google Analytics selection`,m.reason]]:[],[`Auth`,i],[`MCP`,Xa(v)],[`SEO skill`,qa(y)]]);let oe=Za(v);oe&&w(oe,`MCP setup skipped`),y.error&&w(y.error,`SEO skill install failed`),w(ie.join(`
145
+ `),`Try next`),ur(`Setup complete.`),Ya()}const eo=C({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.`},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.`},brand:{type:`string`,description:`Comma-separated branded query terms to exclude by default.`},"report-day":{type:`string`,description:`Preferred monthly report day. Defaults to 1.`},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})=>$a(e)}),to=C({...eo,meta:{name:`start`,description:`Start here: connect Google, save a profile, and get the first report commands`}}),no=C({...eo,meta:{name:`setup`,description:`Guided setup for one saved project profile`}}),ro=C({meta:{name:`brand`,description:`Detect and manage project brand query terms`},subCommands:{detect:C({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=N(e),n=we(D(e)),r=await yt({site:await Vi({site:E(e.site)??n?.siteUrl,options:{json:t,refresh:O(e.refresh)}}),id:n?.id,name:n?.name,days:k(e.days),limit:k(e.limit),minImpressions:k(e[`min-impressions`]),refresh:O(e.refresh)}),i=O(e.save)&&n?fe({id:n.id,name:n.name,siteUrl:n.siteUrl,startUrl:n.startUrl,watchUrls:n.watchUrls,analytics:n.analytics,brandTerms:r.suggestedTerms,reportDay:n.reportDay,technicalWeekday:n.technicalWeekday,isDefault:n.isDefault}):void 0;if(O(e.save)&&!n)throw Error(`Pass --project to save detected brand terms.`);if(t){R({...r,saved:i});return}z([[`Property`,r.site],[`Derived terms`,r.derivedTerms.join(`, `)],[`Suggested terms`,r.suggestedTerms.join(`, `)],[`Saved`,i?i.id:`no`]]),B([`Term`,`Score`,`Evidence`],r.candidates.map(e=>[e.term,e.score,e.evidence.map(e=>`${e.query} (${Math.round(e.clicks)} clicks)`).join(`, `)]))}})}});function io(e){Pi(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 ao(e){z([[`ID`,e.id],[`Name`,e.name],[`GSC property`,e.siteUrl],[`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`],[`Report day`,e.reportDay?String(e.reportDay):`not set`],[`Technical weekday`,e.technicalWeekday===void 0?`not set`:String(e.technicalWeekday)],[`Default`,e.isDefault?`yes`:`no`]])}const oo=C({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.`},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.`},brand:{type:`string`,description:`Comma-separated branded query terms to exclude by default.`},"report-day":{type:`string`,description:`Preferred monthly report day, 1-31.`},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=N(e),n=fe({id:E(e.id),name:E(e.name),siteUrl:await Vi({site:E(e.site),options:{json:t}}),startUrl:E(e.url),watchUrls:M(e.urls),brandTerms:M(e.brand),analytics:E(e[`google-analytics-property`])?{google:{propertyId:E(e[`google-analytics-property`])??``}}:void 0,reportDay:k(e[`report-day`]),technicalWeekday:k(e.weekday),isDefault:O(e.default)});if(t){R(n);return}ao(n)}}),so=C({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=yn(E(e.id)??``);if(N(e)){R(t);return}process.stdout.write(`Default project set to ${t.id}.\n`)}}),co=C({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=E(e.id)??``,n=ve(t);if(N(e)){R({id:t,deleted:n});return}process.stdout.write(`${n?`Deleted`:`Not found`} ${t}.\n`)}}),lo=C({meta:{name:`client`,description:`Manage saved project profiles`},subCommands:{setup:no,brand:ro,list:C({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=ae();if(N(e)){R({clients:t});return}io(t)}}),add:oo,show:C({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=we(E(e.id));if(!t)throw Error(`Project not found.`);if(N(e)){R(t);return}ao(t)}}),default:so,delete:co}}),uo=C({...lo,meta:{name:`projects`,description:`Manage saved project profiles`}}),fo=C({meta:{name:`content`,description:`Optimize page content from real GSC queries`},subCommands:{optimize:C({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":Vr(`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=N(e),n=await H({client:D(e),site:E(e.site),options:{json:t,refresh:O(e.refresh)}}),r=await gt({site:n.site,url:E(e.url)??``,days:k(e.days),limit:k(e.limit),minImpressions:k(e[`min-impressions`]),brandTerms:n.client?.brandTerms,includeBrand:O(e[`include-brand`]),verifyContent:O(e[`verify-content`])!==!1,js:O(e.js)?!0:`auto`,refresh:O(e.refresh)});if(t){R(r);return}K({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:U(r.summary.queries)},{label:`Opportunities`,value:U(r.summary.opportunities)},{label:`Estimated lift`,value:U(r.summary.estimatedClickLift)}]}),J(`Top actions`,r.topActions.map(e=>({label:e.title,action:e.action,context:e.plainEnglish}))),r.brief.sections.length&&(process.stdout.write(`
48
146
  Suggested sections
49
- `),q([`Heading`,`Queries`],r.brief.sections.map(e=>[K(e.heading,48),K(e.queries.join(`, `),80)]))),r.intentMix.length&&(process.stdout.write(`
147
+ `),q([`Heading`,`Queries`],r.brief.sections.map(e=>[G(e.heading,48),G(e.queries.join(`, `),80)]))),r.intentMix.length&&(process.stdout.write(`
50
148
  Intent mix
51
- `),q([`Intent`,`Queries`,`Impressions`,`Clicks`],r.intentMix.map(e=>[e.intent,W(e.queries),W(e.impressions),W(e.clicks)]))),Y(`Caveats`,r.caveats)}})}});var Sa=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 Ca(e){return` `.repeat(e)}function wa(e){return e&&typeof e==`object`&&`toJSON`in e&&typeof e.toJSON==`function`?e.toJSON():e}async function Ta(e,t,n,r,i=!1){let a=wa(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(`[
149
+ `),q([`Intent`,`Queries`,`Impressions`,`Clicks`],r.intentMix.map(e=>[e.intent,U(e.queries),U(e.impressions),U(e.clicks)]))),Y(`Caveats`,r.caveats)}})}});var po=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 mo(e){return` `.repeat(e)}function ho(e){return e&&typeof e==`object`&&`toJSON`in e&&typeof e.toJSON==`function`?e.toJSON():e}async function go(e,t,n,r,i=!1){let a=ho(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(`[
52
150
  `);for(let t=0;t<a.length;t+=1)t>0&&await e.write(`,
53
- `),await e.write(Ca(n+1)),await Ta(e,a[t],n+1,r,!0);return await e.write(`\n${Ca(n)}]`),!0}let t=Object.entries(a).filter(([,e])=>{let t=wa(e);return![`undefined`,`function`,`symbol`].includes(typeof t)});if(t.length===0)return await e.write(`{}`),!0;await e.write(`{
151
+ `),await e.write(mo(n+1)),await go(e,a[t],n+1,r,!0);return await e.write(`\n${mo(n)}]`),!0}let t=Object.entries(a).filter(([,e])=>{let t=ho(e);return![`undefined`,`function`,`symbol`].includes(typeof t)});if(t.length===0)return await e.write(`{}`),!0;await e.write(`{
54
152
  `);for(let i=0;i<t.length;i+=1){let[a,o]=t[i];i>0&&await e.write(`,
55
- `),await e.write(`${Ca(n+1)}${JSON.stringify(a)}: `),await Ta(e,o,n+1,r)}return await e.write(`\n${Ca(n)}}`),!0}finally{r.delete(a)}}async function Ea(e,t){let n=new Sa(e);await Ta(n,t,0,new Set)||await n.write(`undefined`),await n.write(`
56
- `),await n.flush()}async function Da(e,t){if(!e){await Ea(process.stdout,t);return}await En(b(e),{recursive:!0});let n=nn(e);try{await Ea(n,t),n.end(),await Qn(n)}catch(e){throw n.destroy(),e}process.stdout.write(`Wrote ${e}\n`)}const Oa={high:3,medium:2,low:1},ka=S({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.`},"google-analytics-property":{type:`string`,description:`Google Analytics property ID for landing-page sessions. Defaults from --project when saved.`},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=M(e),n=Na(e.format,t),r=T(e.output),i=Pa(e.csv),a=ja(e.severity),o=ja(e[`fail-on`]),c=E(e),l=await Fa(e),u=Ma(e),d=T(e[`sitemap-url`]),f=D(e.health),p=Aa(e.mode);if(n===`junit`&&!f)throw Error(`--format junit requires --health.`);if((f||d)&&p&&p!==`sitemap`)throw Error(`--health and --sitemap-url require sitemap mode.`);if(f&&dr(e,`sitemap`))throw Error(`--health cannot be combined with --no-sitemap.`);if((f||d)&&l.length)throw Error(`--health and --sitemap-url cannot be combined with URL list input.`);let m=T(e.site)||c||!u&&!l.length&&!d?await U({client:c,site:T(e.site),options:{json:t}}):void 0,ee=u??m?.client?.startUrl??(m?.site?xi(m.site):void 0)??(d?new URL(`/`,d).toString():void 0)??l[0];if(!ee)throw Error(`No crawl URL selected. Pass a URL, --url, or use --project with a saved crawl URL.`);let h=await dt({url:ee,projectId:m?.client?.id,site:m?.site,mode:p??(f||d?`sitemap`:l.length?`list`:void 0),strategy:f?`health`:`full`,sitemapUrl:d,urls:l,maxPages:O(e[`max-pages`]),maxDepth:O(e[`max-depth`]),concurrency:O(e.concurrency),refresh:D(e.refresh),fetchRate:mr({...e,"fetch-concurrency":e.concurrency}),include:A(e.include),exclude:A(e.exclude),googleAnalyticsPropertyId:T(e[`google-analytics-property`])??m?.client?.analytics.google?.propertyId,useSitemap:!dr(e,`sitemap`),respectRobots:!dr(e,`robots`),checkExternal:!dr(e,`external`),js:ur(e)}),te=D(e.save)?re(h):void 0,ne=Gt(h,{severity:a}),g=o?h.issues.some(e=>Oa[e.severity]>=Oa[o]):!1,_=h.status===`failed`,ie={...h,topFixes:ne,reviewObservations:s(h,{severity:a}),...o?{failOn:o,failedThreshold:g}:{},...te?{saved:te}:{}};if(n===`json`){await Da(r,ie),(_||g)&&(process.exitCode=1);return}if(n===`csv`){await La(r,i===`pages`?Jt(h):ze(h)),(_||g)&&(process.exitCode=1);return}if(n===`html`){await La(r,Ut(h,ne)),(_||g)&&(process.exitCode=1);return}if(n===`junit`){await La(r,Zt(h)),(_||g)&&(process.exitCode=1);return}if(n===`markdown`){await La(r,We(h,ne)),(_||g)&&(process.exitCode=1);return}if(r){await La(r,Se(h,ne)),(_||g)&&(process.exitCode=1);return}process.stdout.write(Se(h,ne)),(te||o)&&(process.stdout.write(`
153
+ `),await e.write(`${mo(n+1)}${JSON.stringify(a)}: `),await go(e,o,n+1,r)}return await e.write(`\n${mo(n)}}`),!0}finally{r.delete(a)}}async function _o(e,t){let n=new po(e);await go(n,t,0,new Set)||await n.write(`undefined`),await n.write(`
154
+ `),await n.flush()}async function vo(e,t){if(!e){await _o(process.stdout,t);return}await qn(x(e),{recursive:!0});let n=wn(e);try{await _o(n,t),n.end(),await xr(n)}catch(e){throw n.destroy(),e}process.stdout.write(`Wrote ${e}\n`)}function yo(e){return e===`high`?`fail`:`warning`}function bo(e,t=de(e)){let n=_(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:yo(e.severity),title:`${e.title} on ${e.count} ${e.count===1?`URL`:`URLs`}`,explanation:rt(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:rt(e.ruleId)?.whyItMatters??e.whyThisRanks,evidence:e.sampleUrls.slice(0,5),fix:e.howToFix}));Mi({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 xo={high:3,medium:2,low:1},So=C({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.`},"google-analytics-property":{type:`string`,description:`Google Analytics property ID for landing-page sessions. Defaults from --project when saved.`},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 n=N(e),r=Eo(e.format,n),i=E(e.output),a=Do(e.csv),o=wo(e.severity),c=wo(e[`fail-on`]),l=D(e),u=await Oo(e),d=To(e),f=E(e[`sitemap-url`]),p=O(e.health),m=Co(e.mode);if(r===`junit`&&!p)throw Error(`--format junit requires --health.`);if((p||f)&&m&&m!==`sitemap`)throw Error(`--health and --sitemap-url require sitemap mode.`);if(p&&Ur(e,`sitemap`))throw Error(`--health cannot be combined with --no-sitemap.`);if((p||f)&&u.length)throw Error(`--health and --sitemap-url cannot be combined with URL list input.`);let h=E(e.site)||l||!d&&!u.length&&!f?await H({client:l,site:E(e.site),options:{json:n}}):void 0,ee=d??h?.client?.startUrl??(h?.site?da(h.site):void 0)??(f?new URL(`/`,f).toString():void 0)??u[0];if(!ee)throw Error(`No crawl URL selected. Pass a URL, --url, or use --project with a saved crawl URL.`);let g=await t({url:ee,projectId:h?.client?.id,site:h?.site,mode:m??(p||f?`sitemap`:u.length?`list`:void 0),strategy:p?`health`:`full`,sitemapUrl:f,urls:u,maxPages:k(e[`max-pages`]),maxDepth:k(e[`max-depth`]),concurrency:k(e.concurrency),refresh:O(e.refresh),fetchRate:Kr({...e,"fetch-concurrency":e.concurrency}),include:j(e.include),exclude:j(e.exclude),googleAnalyticsPropertyId:E(e[`google-analytics-property`])??h?.client?.analytics.google?.propertyId,useSitemap:!Ur(e,`sitemap`),respectRobots:!Ur(e,`robots`),checkExternal:!Ur(e,`external`),js:Hr(e)}),te=O(e.save)?Tt(g):void 0,ne=de(g,{severity:o}),re=c?g.issues.some(e=>xo[e.severity]>=xo[c]):!1,v=g.status===`failed`,y={...g,topFixes:ne,reviewObservations:_(g,{severity:o}),...c?{failOn:c,failedThreshold:re}:{},...te?{saved:te}:{}};if(r===`json`){await vo(i,y),(v||re)&&(process.exitCode=1);return}if(r===`csv`){await Ao(i,a===`pages`?Be(g):xn(g)),(v||re)&&(process.exitCode=1);return}if(r===`html`){await Ao(i,s(g,ne)),(v||re)&&(process.exitCode=1);return}if(r===`junit`){await Ao(i,Ue(g)),(v||re)&&(process.exitCode=1);return}if(r===`markdown`){await Ao(i,ie(g,ne)),(v||re)&&(process.exitCode=1);return}if(i){await Ao(i,Ke(g,ne)),(v||re)&&(process.exitCode=1);return}bo(g,ne),(te||c)&&(process.stdout.write(`
57
155
  Run metadata
58
- `),R([[`Saved report`,te?.id??`no`],[`Fail threshold`,o??`off`]])),(_||g)&&(process.exitCode=1)}});function Aa(e){let t=T(e);if(t){if([`site`,`page`,`list`,`sitemap`].includes(t))return t;throw Error(`Mode must be one of: site, page, list, sitemap.`)}}function ja(e){let t=T(e);if(t){if([`low`,`medium`,`high`].includes(t))return t;throw Error(`Severity must be one of: low, medium, high.`)}}function Ma(e){let t=T(e.crawlUrl),n=T(e.url);if(t&&n&&t!==n)throw Error(`Use either a URL argument or --url, not both.`);return t??n}function Na(e,t){let n=T(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 Pa(e){let t=T(e);if(!t)return`issues`;if([`issues`,`pages`].includes(t))return t;throw Error(`CSV table must be one of: issues, pages.`)}async function Fa(e){let t=A(e.urls)??[],n=T(e[`urls-file`]);return n&&t.push(...Ia(await On(n,`utf8`))),[...new Set(t)]}function Ia(e){return e.split(/[\n,]/).map(e=>e.trim()).filter(e=>e&&!e.startsWith(`#`))}async function La(e,t){if(!e){process.stdout.write(t);return}await En(b(e),{recursive:!0}),await Mn(e,t),process.stdout.write(`Wrote ${e}\n`)}async function Ra(e,t){let n=E(e),r=T(e.site);if(!(!n&&!r))return(await U({client:n,site:r,options:{json:t}})).site}function za(e){Jr(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 Ba(e){let t=[[`ID`,e.id],[`URL`,e.config.url],[`Site`,e.site??`-`],[`Status`,e.status],[`Generated`,e.generatedAt],[`Pages`,String(e.summary.totalPages)],[`Discovered`,String(e.summary.discoveredUrls)],[`Queued`,String(e.summary.queuedUrls)],[`Skipped`,String(e.summary.skippedUrls)],[`Failed fetches`,String(e.summary.failedUrls)],[`Observed internal links`,String(e.summary.observedInternalLinks)],[`Indexable`,String(e.summary.indexablePages)],[`Issues`,String(e.issues.length)],[`High`,String(e.summary.highIssues)],[`Medium`,String(e.summary.mediumIssues)],[`Low`,String(e.summary.lowIssues)]];if(e.externalLinkVerification){let n=e.externalLinkVerification;t.push([`External links`,`${n.fetchedUrls} fetched, ${n.failedUrls} failed, ${n.deferredUrls} deferred (${n.dataStatus})`])}R(t),e.issueGroups.length&&(process.stdout.write(`
59
- Top issues
60
- `),z([`Severity`,`Rule`,`Count`,`Sample URL`],e.issueGroups.slice(0,10).map(e=>[e.severity,e.ruleId,String(e.count),K(e.sampleUrls[0]??``,64)]))),Y(`Warnings`,e.warnings.slice(0,10)),Y(`Caveats`,e.caveats)}function Va(e){if(R([[`Before`,`${e.before.id} (${e.before.generatedAt})`],[`After`,`${e.after.id} (${e.after.generatedAt})`],[`Comparability`,e.comparability.status],[`Completeness`,e.completeness.status],[`Truncated`,e.completeness.truncated?`yes`:`no`],[`Headline`,e.headline],[`Pages`,`${e.after.summary.totalPages} (${e.summary.pageDelta>=0?`+`:``}${e.summary.pageDelta})`],[`Issues`,`${e.after.summary.highIssues+e.after.summary.mediumIssues+e.after.summary.lowIssues} (${e.summary.issueDelta>=0?`+`:``}${e.summary.issueDelta})`],[`New status errors`,String(e.summary.newStatusErrors)],[`Fixed status errors`,String(e.summary.fixedStatusErrors)],[`Issue groups worse`,String(e.summary.issueGroupsWorse)],[`Issue groups better`,String(e.summary.issueGroupsBetter)],[`Changed pages`,String(e.summary.changedPages)],[`Added pages`,String(e.summary.addedPages)],[`Removed pages`,String(e.summary.removedPages)]]),e.topActions.length){process.stdout.write(`
61
- Top actions
62
- `);for(let[t,n]of e.topActions.entries())process.stdout.write(`${t+1}. ${n.title}: ${n.action}\n`),process.stdout.write(` ${n.plainEnglish}\n`)}e.pageChanges.length&&(process.stdout.write(`
156
+ `),z([[`Saved report`,te?.id??`no`],[`Fail threshold`,c??`off`]])),(v||re)&&(process.exitCode=1)}});function Co(e){let t=E(e);if(t){if([`site`,`page`,`list`,`sitemap`].includes(t))return t;throw Error(`Mode must be one of: site, page, list, sitemap.`)}}function wo(e){let t=E(e);if(t){if([`low`,`medium`,`high`].includes(t))return t;throw Error(`Severity must be one of: low, medium, high.`)}}function To(e){let t=E(e.crawlUrl),n=E(e.url);if(t&&n&&t!==n)throw Error(`Use either a URL argument or --url, not both.`);return t??n}function Eo(e,t){let n=E(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 Do(e){let t=E(e);if(!t)return`issues`;if([`issues`,`pages`].includes(t))return t;throw Error(`CSV table must be one of: issues, pages.`)}async function Oo(e){let t=j(e.urls)??[],n=E(e[`urls-file`]);return n&&t.push(...ko(await Yn(n,`utf8`))),[...new Set(t)]}function ko(e){return e.split(/[\n,]/).map(e=>e.trim()).filter(e=>e&&!e.startsWith(`#`))}async function Ao(e,t){if(!e){process.stdout.write(t);return}await qn(x(e),{recursive:!0}),await tr(e,t),process.stdout.write(`Wrote ${e}\n`)}async function jo(e,t){let n=D(e),r=E(e.site);if(!(!n&&!r))return(await H({client:n,site:r,options:{json:t}})).site}function Mo(e){Pi(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 No(e){bo(e)}function Po(e){K({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}]}),J(`Top actions`,e.topActions.map(e=>({label:e.title,context:e.plainEnglish,action:e.action})),e.topActions.length),e.pageChanges.length&&(process.stdout.write(`
63
157
  Changed pages
64
- `),z([`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(`
158
+ `),B([`Kind`,`Changes`,`URL`],e.pageChanges.slice(0,15).map(e=>[e.kind,e.changes.join(`, `),G(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(`
65
159
  Issue movement
66
- `),z([`Rule`,`Before`,`After`,`Delta`],e.issueChanges.slice(0,10).map(e=>[e.ruleId,String(e.before),String(e.after),`${e.delta>=0?`+`:``}${e.delta}`]))),Y(`Caveats`,e.caveats)}function Ha(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 Ua(e){if(e.value!==`latest`&&e.value!==`previous`)return p(e.value);let t=Ha(he({site:e.site,limit:20}),{value:e.value,skipId:e.skipId});return t?p(t.id):void 0}const Wa=S({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=M(e),n=await Ra(e,t),r=T(e.delete),i=T(e.rerun),a=T(e.compare),o=T(e.id);if(r){let e=le(r);if(t){L({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`?de(n):p(i);if(!e)throw Error(i===`latest`?`No saved crawl reports found.`:`No saved crawl report found for ${i}.`);let r=await dt({...e.config,projectId:e.projectId,site:e.site,googleAnalyticsPropertyId:e.googleAnalyticsPropertyId}),a=re(r);if(t){L({...r,rerunOf:e.id,saved:a,topFixes:Gt(r),reviewObservations:s(r)});return}process.stdout.write(`Reran crawl report ${e.id}.\n\n`),Ba(r);return}if(a){let r=Ua({value:a,site:n});if(!r)throw Error(`No saved crawl report found for ${a}.`);let i=T(e.against)??`previous`,o=Ua({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=rt({before:o,after:r});if(t){L(s);return}Va(s);return}if(o||D(e.latest)){let e=o?p(o):de(n);if(!e)throw Error(o?`No saved crawl report found for ${o}.`:`No saved crawl reports found.`);if(t){L(e);return}Ba(e);return}let c=he({site:n,limit:O(e.limit)});if(t){L({reports:c});return}za(c)}}),Ga=S({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.`},"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 gr(e.body,e[`body-file`])??{startDate:T(e[`start-date`]),endDate:T(e[`end-date`]),dimensions:A(e.dimensions)??[`query`,`page`],type:T(e.type)??`web`,rowLimit:O(e.limit),dataState:`final`},n=M(e),r=await bi({client:E(e),site:T(e.site)??T(t.siteUrl)},{json:n,refresh:D(e.refresh)});delete t.siteUrl;let i=await Ie(r,t,{refresh:D(e.refresh)}),a=O(e.limit),o=a?i.rows.slice(0,a):i.rows;if(n){L({site:r,request:t,...i,rows:o,rowsReturned:o.length});return}R([[`Property`,r],[`Rows returned`,String(o.length)],[`Rows fetched`,String(i.rowsFetched)],[`API calls`,String(i.calls)]]),z([`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)]))}}),Ka=S({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.`},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=M(e),n=await bi({client:E(e),site:T(e.site)},{json:t}),r=T(e.url);if(!r)throw Error(`Pass --url.`);let i=await _e({siteUrl:n,inspectionUrl:r,languageCode:T(e.language)});if(t){L(i);return}let a=i.inspectionResult?.indexStatusResult;R([[`Property`,n],[`URL`,r],[`Verdict`,a?.verdict??`unknown`],[`Coverage`,a?.coverageState??`unknown`],[`Robots`,a?.robotsTxtState??`unknown`],[`Last crawl`,a?.lastCrawlTime??`unknown`],[`Google canonical`,a?.googleCanonical??`unknown`]])}}),qa={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 X(e,t={}){return Object.fromEntries(e.map(e=>{let n=qa[e];return[n.cli,{...n.arg,...t[e]}]}))}function Ja(e){return e.toLocaleString(`en-GB`,{maximumFractionDigits:e>=100?0:1})}function Ya(e){if(e===null)return`n/a`;let t=Math.abs(e)>=10?e.toFixed(0):e.toFixed(1);return`${e>0?`+`:``}${t}%`}const Xa=S({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.`},...X([`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:t})=>{let n=M(t),r=await e({site:await bi({client:E(t),site:T(t.site)},{json:n,refresh:D(t.refresh)}),days:O(t.days),recentDays:O(t.recent),refresh:D(t.refresh)});if(n){L(r);return}q([`Metric`,`Direction`,`Baseline/day`,`Recent/day`,`Change`,`z`,`Significant`],r.anomalies.map(e=>[e.metric,e.direction,Ja(e.baselineMean),Ja(e.comparisonMean),Ya(e.percentChange),e.zScore??`n/a`,e.significant?`yes`:`no`])),r.coverage&&Y(`Evidence coverage`,[`${r.coverage.observedDays} of ${r.coverage.expectedDays} requested calendar days returned date aggregates.`,...r.coverage.caveats])}}),Za=S({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.`},...X([`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=M(e),n=E(e),r=await bi({client:n,site:T(e.site)},{json:t,refresh:D(e.refresh)}),i=await Ze({site:r,days:O(e.days),recentDays:O(e.recent),paddingDays:O(e[`padding-days`]),knownConfounders:T(e[`known-change`])?[T(e[`known-change`])??``]:void 0,includeChangeLog:!D(e[`ignore-change-log`]),refresh:D(e.refresh)});if(t){L(i);return}R([[`Classification`,i.classification.replaceAll(`-`,` `)],[`Causal attribution`,i.attribution.replaceAll(`-`,` `)],[`Causal confidence`,i.confidence],[`Updates matched`,String(i.overlappingUpdates.length)],[`Known confounders`,String(i.confounders.length)],[`Source`,i.source.name]]),process.stdout.write(`\n${i.summary}\n`),Y(`Evidence`,i.evidence),Y(`Report caveats`,i.caveats),Y(`Recommended next checks`,i.actions),q([`Metric`,`Direction`,`Baseline/day`,`Recent/day`,`Change`,`z`],i.anomalies.map(e=>[e.metric,e.direction,Ja(e.baselineMean),Ja(e.comparisonMean),Ya(e.percentChange),e.zScore??`n/a`])),i.overlappingUpdates.length&&(process.stdout.write(`
160
+ `),B([`Rule`,`Before`,`After`,`Delta`],e.issueChanges.slice(0,10).map(e=>[e.ruleId,String(e.before),String(e.after),`${e.delta>=0?`+`:``}${e.delta}`]))),Y(`Caveats`,e.caveats)}function Fo(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 Io(e){if(e.value!==`latest`&&e.value!==`previous`)return Xt(e.value);let t=Fo(zt({site:e.site,limit:20}),{value:e.value,skipId:e.skipId});return t?Xt(t.id):void 0}const Lo=C({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 n=N(e),r=await jo(e,n),i=E(e.delete),a=E(e.rerun),o=E(e.compare),s=E(e.id);if(i){let e=pt(i);if(n){R({id:i,deleted:e});return}process.stdout.write(e?`Deleted crawl report ${i}.\n`:`No saved crawl report found for ${i}.\n`);return}if(a){let e=a===`latest`?tn(r):Xt(a);if(!e)throw Error(a===`latest`?`No saved crawl reports found.`:`No saved crawl report found for ${a}.`);let i=await t({...e.config,projectId:e.projectId,site:e.site,googleAnalyticsPropertyId:e.googleAnalyticsPropertyId}),o=Tt(i);if(n){R({...i,rerunOf:e.id,saved:o,topFixes:de(i),reviewObservations:_(i)});return}process.stdout.write(`Reran crawl report ${e.id}.\n\n`),No(i);return}if(o){let t=Io({value:o,site:r});if(!t)throw Error(`No saved crawl report found for ${o}.`);let i=E(e.against)??`previous`,a=Io({value:i,site:t.site??r,skipId:t.id});if(!a)throw Error(`No baseline crawl report found for ${i}. Save at least two reports or pass --against <id>.`);let s=je({before:a,after:t});if(n){R(s);return}Po(s);return}if(s||O(e.latest)){let e=s?Xt(s):tn(r);if(!e)throw Error(s?`No saved crawl report found for ${s}.`:`No saved crawl reports found.`);if(n){R(e);return}No(e);return}let c=zt({site:r,limit:k(e.limit)});if(n){R({reports:c});return}Mo(c)}}),Ro=C({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.`},"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 Jr(e.body,e[`body-file`])??{startDate:E(e[`start-date`]),endDate:E(e[`end-date`]),dimensions:j(e.dimensions)??[`query`,`page`],type:E(e.type)??`web`,rowLimit:k(e.limit),dataState:`final`},n=N(e),r=await ua({client:D(e),site:E(e.site)??E(t.siteUrl)},{json:n,refresh:O(e.refresh)});delete t.siteUrl;let i=await _n(r,t,{refresh:O(e.refresh)}),a=k(e.limit),o=a?i.rows.slice(0,a):i.rows;if(n){R({site:r,request:t,...i,rows:o,rowsReturned:o.length});return}K({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}]}),B([`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)]))}}),zo=C({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.`},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=N(e),n=await ua({client:D(e),site:E(e.site)},{json:t}),r=E(e.url);if(!r)throw Error(`Pass --url.`);let i=await ot({siteUrl:n,inspectionUrl:r,languageCode:E(e.language)});if(t){R(i);return}let a=i.inspectionResult?.indexStatusResult;K({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`}]})}}),Bo={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 X(e,t={}){return Object.fromEntries(e.map(e=>{let n=Bo[e];return[n.cli,{...n.arg,...t[e]}]}))}function Vo(e){return e.toLocaleString(`en-GB`,{maximumFractionDigits:e>=100?0:1})}function Ho(e){if(e===null)return`n/a`;let t=Math.abs(e)>=10?e.toFixed(0):e.toFixed(1);return`${e>0?`+`:``}${t}%`}const Uo=C({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.`},...X([`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=N(e),n=await hn({site:await ua({client:D(e),site:E(e.site)},{json:t,refresh:O(e.refresh)}),days:k(e.days),recentDays:k(e.recent),refresh:O(e.refresh)});if(t){R(n);return}K({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`}]}),q([`Metric`,`Direction`,`Baseline/day`,`Recent/day`,`Change`,`z`,`Significant`],n.anomalies.map(e=>[e.metric,e.direction,Vo(e.baselineMean),Vo(e.comparisonMean),Ho(e.percentChange),e.zScore??`n/a`,e.significant?`yes`:`no`])),n.coverage&&Y(`Evidence coverage`,[`${n.coverage.observedDays} of ${n.coverage.expectedDays} requested calendar days returned date aggregates.`,...n.coverage.caveats])}}),Wo=C({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.`},...X([`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=N(e),n=D(e),r=await ua({client:n,site:E(e.site)},{json:t,refresh:O(e.refresh)}),i=await Pe({site:r,days:k(e.days),recentDays:k(e.recent),paddingDays:k(e[`padding-days`]),knownConfounders:E(e[`known-change`])?[E(e[`known-change`])??``]:void 0,includeChangeLog:!O(e[`ignore-change-log`]),refresh:O(e.refresh)});if(t){R(i);return}K({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}]}),Y(`Evidence`,i.evidence),Y(`Report caveats`,i.caveats),Y(`Recommended next checks`,i.actions),q([`Metric`,`Direction`,`Baseline/day`,`Recent/day`,`Change`,`z`],i.anomalies.map(e=>[e.metric,e.direction,Vo(e.baselineMean),Vo(e.comparisonMean),Ho(e.percentChange),e.zScore??`n/a`])),i.overlappingUpdates.length&&(process.stdout.write(`
67
161
  Updates
68
- `),z([`Start`,`End`,`Type`,`Name`],i.overlappingUpdates.map(e=>[e.start.slice(0,10),e.end?.slice(0,10)??`open`,e.type,e.name]))),ii(`seo segment-impact ${n?`--project ${JSON.stringify(n)}`:`--site ${JSON.stringify(r)}`} --dimension page`)}});function Qa(e){process.stdout.write(`Entity evidence for ${e.url}\n\n`),R([[`Assessment`,`evidence only`],[`Data`,e.dataStatus],[`Evaluated pages`,`${e.evaluatedPages}/${e.crawlPages}`],[`Report`,e.reportId],[`sameAs profiles`,String(e.entities.sameAs.length)],[`Social profiles`,String(e.entities.socialProfiles.length)],[`Authors`,String(e.entities.authors.length)]]),process.stdout.write(`\n${e.headline}\n`);let t=Object.entries(e.entities.schemaTypes).sort((e,t)=>t[1]-e[1]).slice(0,10);t.length&&(process.stdout.write(`
69
- Schema types
70
- `),z([`Type`,`Pages`],t.map(([e,t])=>[e,String(t)]))),Y(`sameAs`,e.entities.sameAs.slice(0,10)),Y(`Social profiles`,e.entities.socialProfiles.slice(0,10))}const $a=S({meta:{name:`entity-readiness`,description:`Check schema, sameAs, social, author, and naming signals from a saved crawl`},args:{"report-id":{type:`string`,description:`Saved crawl report id to analyze.`},site:{type:`string`,description:`GSC property URL for selecting the latest saved crawl.`},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.`}},run:async({args:e})=>{let t=M(e),n=te(await ai(e,{json:t}));if(t){L(n);return}Qa(n)}});function eo(){return new Date().toISOString().slice(0,10)}function to(e){let t=T(e)??`page`;if(t!==`site`&&t!==`page`&&t!==`query`&&t!==`group`)throw Error(`Invalid --scope. Use site, page, query, or group.`);return t}const no=S({meta:{name:`change-log`,description:`Annotate SEO changes and measure before/after impact`},subCommands:{list:S({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=xt({site:T(e.site),limit:O(e.limit)});if(M(e)){L({changes:t});return}z([`ID`,`Date`,`Site`,`Scope`,`Target`,`Title`],t.map(e=>[e.id,e.changedAt,e.site,e.scope,e.target,e.title]))}}),add:S({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=M(e),n=Mt({site:await H({site:T(e.site),options:{json:t}}),scope:to(e.scope),target:T(e.target)??``,title:T(e.title)??``,description:T(e.description),changedAt:T(e.date)??eo()});if(t){L(n);return}R([[`ID`,n.id],[`Date`,n.changedAt],[`Site`,n.site],[`Scope`,n.scope],[`Target`,n.target],[`Title`,n.title]])}}),delete:S({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=T(e.id)??``,n=Lt(t);if(M(e)){L({id:t,deleted:n});return}process.stdout.write(`${n?`Deleted`:`Not found`} ${t}.\n`)}}),measure:S({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=M(e),n=T(e.id);if(!n&&(!T(e.site)||!T(e.scope)||!T(e.target)||!T(e.date)))throw Error(`Pass --id, or pass --site, --scope, --target, and --date for an ad hoc measurement.`);let r=await g({id:n,site:n?void 0:await H({site:T(e.site),options:{json:t}}),scope:n?void 0:to(e.scope),target:n?void 0:T(e.target),title:T(e.title),changedAt:n?void 0:T(e.date),beforeDays:k(e.before,`--before`),afterDays:k(e.after,`--after`),refresh:D(e.refresh)});if(t){L(r);return}R([[`Change`,r.change.title],[`Data status`,r.dataStatus],[`Verdict`,r.verdict],[`Confidence`,r.confidence],[`Before`,`${r.before.startDate} to ${r.before.endDate}`],[`After`,`${r.after.startDate} to ${r.after.endDate}`],[`Window`,`${r.window.effectiveDays}/${r.window.requestedDays} finalized days`],[`Note`,r.note]]),z([`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`]]),Y(`Warnings`,r.warnings),Y(`Caveats`,r.caveats)}})}});function ro(e){let t=T(e)??`page`;if(t!==`page`&&t!==`query`)throw Error(`Invalid --dimension. Use page or query.`);return t}function io(e){let t=T(e)??`contains`;if(t!==`equals`&&t!==`contains`&&t!==`regex`)throw Error(`Invalid --match. Use equals, contains, or regex.`);return t}const ao=S({meta:{name:`content-groups`,description:`Create and manage reusable page/query groups`},subCommands:{list:S({meta:{name:`list`,description:`List saved content groups`},args:{site:{type:`string`},json:{type:`boolean`,default:!1}},run:async({args:e})=>{let t=Fe(T(e.site));if(M(e)){L({groups:t});return}z([`ID`,`Site`,`Name`,`Dimension`,`Match`,`Pattern`],t.map(e=>[e.id,e.site,e.name,e.dimension,e.matchType,e.pattern]))}}),add:S({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=M(e),n=Ne({site:await H({site:T(e.site),options:{json:t}}),name:T(e.name)??``,dimension:ro(e.dimension),matchType:io(e.match),pattern:T(e.pattern)??``});if(t){L(n);return}R([[`ID`,n.id],[`Site`,n.site],[`Name`,n.name],[`Filter`,`${n.dimension} ${n.matchType} ${n.pattern}`]])}}),delete:S({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=T(e.id)??``,n=je(t);if(M(e)){L({id:t,deleted:n});return}process.stdout.write(`${n?`Deleted`:`Not found`} ${t}.\n`)}})}});function oo(){return new Date().toISOString().slice(0,10)}function so(e){let t=T(e)??`page`;if(t!==`site`&&t!==`page`&&t!==`query`&&t!==`group`)throw Error(`Invalid --scope. Use site, page, query, or group.`);return t}function co(e){return e?so(e):void 0}function lo(e){return e===null?`n/a`:`${e.toFixed(1)}%`}function uo(e){return e==null?`unavailable`:W(e)}function fo(e,t){return e===null?`unavailable`:`${W(e)} (${lo(t)})`}function po(e,t=1){return e==null?`unavailable`:e.toFixed(t)}function mo(e){R([[`Test`,e.change.title],[`Data status`,e.dataStatus],[`Verdict`,e.verdict],[`Confidence`,e.confidence],[`Before`,`${e.before.startDate} to ${e.before.endDate}`],[`After`,`${e.after.startDate} to ${e.after.endDate}`],[`Window`,`${e.window.effectiveDays}/${e.window.requestedDays} finalized days`],[`Note`,e.note]]),z([`Metric`,`Before`,`After`,`Delta`],[[`GSC clicks`,uo(e.before.metrics?.clicks),uo(e.after.metrics?.clicks),fo(e.delta.clicks,e.delta.clickPct)],[`GSC impressions`,uo(e.before.metrics?.impressions),uo(e.after.metrics?.impressions),fo(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`,po(e.before.metrics?.position),po(e.after.metrics?.position),po(e.delta.position)]]),e.analytics&&(process.stdout.write(`
162
+ `),B([`Start`,`End`,`Type`,`Name`],i.overlappingUpdates.map(e=>[e.start.slice(0,10),e.end?.slice(0,10)??`open`,e.type,e.name]))),ta(`seo segment-impact ${n?`--project ${JSON.stringify(n)}`:`--site ${JSON.stringify(r)}`} --dimension page`)}});function Go(e){Mi({title:`Entity evidence`,target:e.url,status:e.dataStatus===`partial`?`unknown`:`info`,summary:e.headline,metrics:[{label:`Pages`,value:`${e.evaluatedPages}/${e.crawlPages}`},{label:`sameAs`,value:e.entities.sameAs.length},{label:`Social`,value:e.entities.socialProfiles.length},{label:`Authors`,value:e.entities.authors.length}],sections:[{title:`Observed entity signals`,diagnostics:e.checks.map(e=>({status:e.status,title:e.title,explanation:e.plainEnglish,fix:e.action,evidence:(e.urls??[]).slice(0,5)}))}],notes:[`Data: ${e.dataStatus}. Report: ${e.reportId}.`,...e.caveats]})}const Ko=C({meta:{name:`entity-readiness`,description:`Check schema, sameAs, social, author, and naming signals from a saved crawl`},args:{"report-id":{type:`string`,description:`Saved crawl report id to analyze.`},site:{type:`string`,description:`GSC property URL for selecting the latest saved crawl.`},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.`}},run:async({args:e})=>{let t=N(e),n=an(await Wi(e,{json:t}));if(t){R(n);return}Go(n)}});function qo(){return new Date().toISOString().slice(0,10)}function Jo(e){let t=E(e)??`page`;if(t!==`site`&&t!==`page`&&t!==`query`&&t!==`group`)throw Error(`Invalid --scope. Use site, page, query, or group.`);return t}const Yo=C({meta:{name:`change-log`,description:`Annotate SEO changes and measure before/after impact`},subCommands:{list:C({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=fn({site:E(e.site),limit:k(e.limit)});if(N(e)){R({changes:t});return}B([`ID`,`Date`,`Site`,`Scope`,`Target`,`Title`],t.map(e=>[e.id,e.changedAt,e.site,e.scope,e.target,e.title]))}}),add:C({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=N(e),n=Le({site:await Vi({site:E(e.site),options:{json:t}}),scope:Jo(e.scope),target:E(e.target)??``,title:E(e.title)??``,description:E(e.description),changedAt:E(e.date)??qo()});if(t){R(n);return}z([[`ID`,n.id],[`Date`,n.changedAt],[`Site`,n.site],[`Scope`,n.scope],[`Target`,n.target],[`Title`,n.title]])}}),delete:C({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=E(e.id)??``,n=u(t);if(N(e)){R({id:t,deleted:n});return}process.stdout.write(`${n?`Deleted`:`Not found`} ${t}.\n`)}}),measure:C({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=N(e),n=E(e.id);if(!n&&(!E(e.site)||!E(e.scope)||!E(e.target)||!E(e.date)))throw Error(`Pass --id, or pass --site, --scope, --target, and --date for an ad hoc measurement.`);let r=await bt({id:n,site:n?void 0:await Vi({site:E(e.site),options:{json:t}}),scope:n?void 0:Jo(e.scope),target:n?void 0:E(e.target),title:E(e.title),changedAt:n?void 0:E(e.date),beforeDays:A(e.before,`--before`),afterDays:A(e.after,`--after`),refresh:O(e.refresh)});if(t){R(r);return}K({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`}]}),B([`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`]]),Y(`Warnings`,r.warnings),Y(`Caveats`,r.caveats)}})}});function Xo(e){let t=E(e)??`page`;if(t!==`page`&&t!==`query`)throw Error(`Invalid --dimension. Use page or query.`);return t}function Zo(e){let t=E(e)??`contains`;if(t!==`equals`&&t!==`contains`&&t!==`regex`)throw Error(`Invalid --match. Use equals, contains, or regex.`);return t}const Qo=C({meta:{name:`content-groups`,description:`Create and manage reusable page/query groups`},subCommands:{list:C({meta:{name:`list`,description:`List saved content groups`},args:{site:{type:`string`},json:{type:`boolean`,default:!1}},run:async({args:e})=>{let t=vn(E(e.site));if(N(e)){R({groups:t});return}B([`ID`,`Site`,`Name`,`Dimension`,`Match`,`Pattern`],t.map(e=>[e.id,e.site,e.name,e.dimension,e.matchType,e.pattern]))}}),add:C({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=N(e),n=tt({site:await Vi({site:E(e.site),options:{json:t}}),name:E(e.name)??``,dimension:Xo(e.dimension),matchType:Zo(e.match),pattern:E(e.pattern)??``});if(t){R(n);return}z([[`ID`,n.id],[`Site`,n.site],[`Name`,n.name],[`Filter`,`${n.dimension} ${n.matchType} ${n.pattern}`]])}}),delete:C({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=E(e.id)??``,n=ln(t);if(N(e)){R({id:t,deleted:n});return}process.stdout.write(`${n?`Deleted`:`Not found`} ${t}.\n`)}})}});function $o(){return new Date().toISOString().slice(0,10)}function es(e){let t=E(e)??`page`;if(t!==`site`&&t!==`page`&&t!==`query`&&t!==`group`)throw Error(`Invalid --scope. Use site, page, query, or group.`);return t}function ts(e){return e?es(e):void 0}function ns(e){return e===null?`n/a`:`${e.toFixed(1)}%`}function rs(e){return e==null?`unavailable`:U(e)}function is(e,t){return e===null?`unavailable`:`${U(e)} (${ns(t)})`}function as(e,t=1){return e==null?`unavailable`:e.toFixed(t)}function os(e){K({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`}]}),B([`Metric`,`Before`,`After`,`Delta`],[[`GSC clicks`,rs(e.before.metrics?.clicks),rs(e.after.metrics?.clicks),is(e.delta.clicks,e.delta.clickPct)],[`GSC impressions`,rs(e.before.metrics?.impressions),rs(e.after.metrics?.impressions),is(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`,as(e.before.metrics?.position),as(e.after.metrics?.position),as(e.delta.position)]]),e.analytics&&(process.stdout.write(`
71
163
  Google Analytics impact
72
- `),z([`Metric`,`Before`,`After`,`Delta`],[[`Sessions`,W(e.analytics.before.metrics.sessions),W(e.analytics.after.metrics.sessions),`${W(e.analytics.delta.sessions)} (${lo(e.analytics.delta.sessionPct)})`],[`Conversions`,W(e.analytics.before.metrics.conversions),W(e.analytics.after.metrics.conversions),`${W(e.analytics.delta.conversions)} (${lo(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)} (${lo(e.analytics.delta.revenuePct)})`]])),e.control&&(process.stdout.write(`
164
+ `),B([`Metric`,`Before`,`After`,`Delta`],[[`Sessions`,U(e.analytics.before.metrics.sessions),U(e.analytics.after.metrics.sessions),`${U(e.analytics.delta.sessions)} (${ns(e.analytics.delta.sessionPct)})`],[`Conversions`,U(e.analytics.before.metrics.conversions),U(e.analytics.after.metrics.conversions),`${U(e.analytics.delta.conversions)} (${ns(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)} (${ns(e.analytics.delta.revenuePct)})`]])),e.control&&(process.stdout.write(`
73
165
  Control comparison
74
- `),R([[`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]])),Y(`Warnings`,e.warnings),Y(`Caveats`,e.caveats)}const ho=S({meta:{name:`tests`,description:`Create and report local SEO tests from Search Console and Google Analytics data`},subCommands:{list:S({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=M(e),n=E(e),r=xt({site:(n||T(e.site)?await U({client:n,site:T(e.site),options:{json:t}}):void 0)?.site,limit:O(e.limit)});if(t){L({tests:r});return}z([`ID`,`Date`,`Scope`,`Target`,`Title`],r.map(e=>[e.id,e.changedAt,e.scope,e.target,e.title]))}}),create:S({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=M(e),n=await U({client:E(e),site:T(e.site),options:{json:t}}),r=Mt({site:n.site,scope:so(e.scope),target:T(e.target)??``,title:T(e.title)??``,description:T(e.description),changedAt:T(e.date)??oo()});if(t){L(r);return}R([[`ID`,r.id],[`Date`,r.changedAt],[`Project`,n.client?.name??n.site],[`Scope`,r.scope],[`Target`,r.target],[`Title`,r.title]])}}),report:S({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=M(e),n=T(e.id),r=!n||E(e)||T(e.site)?await U({client:E(e),site:T(e.site),options:{json:t,refresh:D(e.refresh)}}):void 0,i=await g({id:n,site:n?void 0:r?.site,scope:n?void 0:co(e.scope),target:n?void 0:T(e.target),title:T(e.title),changedAt:n?void 0:T(e.date),googleAnalyticsPropertyId:T(e.property)??r?.client?.analytics.google?.propertyId,controlScope:co(e[`control-scope`]),controlTarget:T(e[`control-target`]),controlTitle:T(e[`control-title`]),beforeDays:k(e.before,`--before`),afterDays:k(e.after,`--after`),refresh:D(e.refresh)});if(t){L(i);return}mo(i)}})}});function go(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 _o={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.`}},vo={...X([`includeBrand`,`verifyContent`,`verifyLimit`,`js`,`fetchConcurrency`,`fetchIntervalCap`,`fetchIntervalMs`,`refresh`]),json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}};function yo(e){return{includeBrand:D(e[`include-brand`]),verifyContent:D(e[`verify-content`]),verifyLimit:O(e[`verify-limit`])??3,js:D(e.js)?!0:void 0,rate:mr(e),refresh:D(e.refresh)}}function bo(e){return{client:E(e),site:T(e.site),refresh:D(e.refresh)}}const{json:xo,...So}=vo,Co=So,wo={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 To(e){return{type:`string`,description:`Output folder. Defaults to ./seo-export/<project>-${e}-YYYY-MM-DD.`}}function Eo(e){return e.toLowerCase().replace(/^sc-domain:/,``).replace(/^https?:\/\//,``).replace(/[^a-z0-9._-]+/g,`-`).replace(/^-+|-+$/g,``)}function Do(e){let t=new Date().toISOString().slice(0,10),n=Eo(e.clientId??e.site);return _n(process.cwd(),`seo-export`,`${n}-${e.report}-${t}`)}async function Oo(e,t){await En(e,{recursive:!0});let n=[];for(let r of t){let t=_n(e,r.filename);await Mn(t,pt(r.rows,r.headers),`utf8`),n.push(t)}return n}function ko(e,t){process.stdout.write(`Wrote ${ot(t.length,`CSV file`)} to ${e}\n`);for(let e of t)process.stdout.write(`- ${e}\n`)}const Ao=S({meta:{name:`diagnose`,description:`Export diagnosis tables: priorities, anomalies, movement, decay, cannibalisation, striking-distance, and quick wins.`},args:{...wo,out:To(`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:E(e),site:T(e.site)}),n=await ge({site:t.site,days:O(e.days),recentDays:O(e.recent),limit:O(e.limit),brandTerms:t.client?.brandTerms,includeBrand:D(e[`include-brand`]),verifyContent:D(e[`verify-content`]),verifyLimit:O(e[`verify-limit`]),js:D(e.js)?!0:`auto`,rate:mr(e),refresh:D(e.refresh),progress:go(!0)}),r=T(e.out)??Do({report:`diagnose`,clientId:t.client?.id,site:t.site});ko(r,await Oo(r,we(n)))}}),jo=`.seo-okf.json`,Mo=`${JSON.stringify({generator:`seo`,format:`okf`,version:1})}\n`;function No(e,t){let n=_n(e,t),r=gn(_n(e),n);if(!r||r.startsWith(`..`)||mn(r))throw new v(`INVALID_INPUT`,`Unsafe OKF file path: ${t}`);return n}async function Po(e,t){for(let n of t){let t=No(e,n.path);await En(b(t),{recursive:!0}),await Mn(t,n.content)}await Mn(x(e,jo),Mo)}async function Fo(e){let t=[];async function n(e,r=``){let i=await kn(e,{withFileTypes:!0});for(let a of i){let i=r?`${r}/${a.name}`:a.name,o=x(e,a.name);a.isDirectory()?await n(o,i):a.isFile()&&a.name.endsWith(`.md`)&&t.push({path:i,content:await On(o,`utf8`)})}}return await n(e),t.sort((e,t)=>e.path<t.path?-1:+(e.path>t.path))}async function Io(e){try{let t=await Tn(e);if(t.isSymbolicLink()||!t.isDirectory())throw new v(`INVALID_INPUT`,`OKF output must be a real directory: ${e}`);if(!(await kn(e)).length)return!0;try{if(await On(x(e,jo),`utf8`)===Mo)return!0}catch{}throw new v(`INVALID_INPUT`,`Refusing to replace non-empty unmanaged directory: ${e}`)}catch(e){if(e.code===`ENOENT`)return!1;throw e}}async function Lo(e,t){if(!yt(t).valid)throw new v(`INVALID_INPUT`,`Generated bundle did not pass seo OKF checks and was not written.`);let n=_n(e),r=b(n);await En(r,{recursive:!0});let i=await Io(n),a=await Dn(x(r,`.${pn(n)}-staging-`)),o=x(r,`.${pn(n)}-backup-${yn()}`),s=!1;try{await Po(a,t);let e=yt(await Fo(a));if(!e.valid)throw new v(`INTERNAL_ERROR`,`Staged OKF files failed validation after writing.`);return i&&(await An(n,o),s=!0),await An(a,n),s&&await jn(o,{recursive:!0,force:!0}),e}catch(e){throw await jn(a,{recursive:!0,force:!0}),s&&(await jn(n,{recursive:!0,force:!0}),await An(o,n)),e}}async function Ro(e,t){if(!e){process.stdout.write(t);return}await En(b(e),{recursive:!0}),await Mn(e,t),process.stdout.write(`Wrote ${e}\n`)}function zo(e){let t=T(e)??`okf`;if([`okf`,`markdown`,`json`].includes(t))return t;throw Error(`Format must be one of: okf, markdown, json.`)}function Bo(e){return{reportId:e.id,url:e.config.url,generatedAt:e.generatedAt,crawl:{summary:e.summary,caveats:e.caveats,warnings:e.warnings,pages:e.pages.map(e=>({url:e.finalUrl,title:e.title,metaDescription:e.metaDescription,status:e.status,indexable:e.indexable,wordCount:e.wordCount,schemaTypes:e.schemaTypes,sameAs:e.schemaSameAs,internalInlinks:e.internalInlinkCount,contentSample:e.contentSample}))},aiReadiness:Oe(e),entityReadiness:te(e),llmsTxt:$t(e)}}function Vo(e){let t=Bo(e);return`${[`# Site Knowledge Export`,``,`Source: ${e.config.url}`,`Crawl report: ${e.id}`,`Generated: ${t.generatedAt}`,``,`## Summary`,``,`- Documents: ${e.summary.totalPages}${e.requestEvidenceStatus===`available`?` from ${e.requests.length} URL requests`:e.requestEvidenceStatus===`partial`?` from ${e.requests.length} observed URL requests (request evidence is partial)`:` (request evidence unavailable for this legacy report)`}`,`- Indexable pages: ${e.summary.indexablePages}`,`- Crawl findings: ${e.issues.length} (${e.summary.highIssues} high, ${e.summary.mediumIssues} medium, ${e.summary.lowIssues} low)`,`- AI-search evidence: ${t.aiReadiness.dataStatus} (${t.aiReadiness.assessment})`,`- Entity evidence: ${t.entityReadiness.dataStatus} (${t.entityReadiness.assessment})`,``,`## Top AI Readiness Actions`,``,...t.aiReadiness.topActions.length?t.aiReadiness.topActions.map(e=>`- ${e.title}: ${e.action}`):[`- No AI-search technical actions in this crawl.`],``,`## Entity Signals`,``,`- sameAs profiles: ${t.entityReadiness.entities.sameAs.length}`,`- Social profiles: ${t.entityReadiness.entities.socialProfiles.length}`,`- Authors: ${t.entityReadiness.entities.authors.length}`,``,`## Important Pages`,``,...e.pages.filter(e=>e.indexable&&e.status<400).slice(0,100).map(e=>`- [${e.title??e.finalUrl}](${e.finalUrl}) - ${e.metaDescription??e.contentSample??`No summary available.`}`),``,`# Citations`,``,`- [Crawl start URL](${e.config.url})`,``].join(`
75
- `)}\n`}const Ho=S({meta:{name:`knowledge`,description:`Export site knowledge from a saved crawl as OKF, Markdown, or JSON`},args:{"report-id":{type:`string`,description:`Saved crawl report id to export.`},site:{type:`string`,description:`GSC property URL for selecting the latest saved crawl.`},client:{type:`string`,description:`Legacy alias for --project.`},project:{type:`string`,description:`Saved project id or name.`},format:{type:`string`,description:`Output format: okf, markdown, or json. Defaults to okf.`},output:{type:`string`,description:`Output directory for OKF, or output file for Markdown/JSON.`},"max-concepts":{type:`string`,description:`Maximum OKF concept files. Defaults to 500.`},json:{type:`boolean`,default:!1,description:`Print command metadata as JSON.`}},run:async({args:e})=>{let t=M(e),n=zo(e.format),r=n===`okf`?Dt(k(e[`max-concepts`],`--max-concepts`)):void 0,i=await ai(e,{json:t}),a=T(e.output);if(n===`okf`){let e=Ye(i,{maxConcepts:r}),o=a??`./okf`,s=await Lo(o,e.files);if(t){L({format:n,output:o,bundle:e,validation:s});return}process.stdout.write(`Wrote OKF bundle to ${o}\n\n`),R([[`Files`,String(e.files.length)],[`Concepts`,String(e.conceptCount)],[`Valid`,s.valid?`yes`:`no`]]);return}if(n===`json`){await Ro(a,`${JSON.stringify(Bo(i),null,2)}\n`);return}await Ro(a,Vo(i))}}),Uo=S({meta:{name:`monthly`,description:`Export monthly report tables plus the underlying diagnosis detail CSVs.`},args:{...wo,out:To(`monthly`),month:{type:`string`,description:`Report month as YYYY-MM. Defaults to the latest month with final GSC data.`},...X([`limit`],{limit:{description:`Maximum rows per diagnostic section. Defaults to 10.`}}),...Co},run:async({args:e})=>{let t=await U({client:E(e),site:T(e.site),options:{refresh:D(e.refresh)}}),n=await ue({site:t.site,month:T(e.month),limit:O(e.limit),brandTerms:t.client?.brandTerms,...yo(e),progress:go(!0)}),r=T(e.out)??Do({report:`monthly`,clientId:t.client?.id,site:t.site});ko(r,await Oo(r,d(n)))}}),Wo=S({meta:{name:`narrative`,description:`Export report narrative tables plus the underlying diagnosis detail CSVs.`},args:{...wo,out:To(`narrative`),...X([`days`,`recentDays`],{days:{description:`Diagnosis window length in days. Defaults to 90.`}}),"start-date":{type:`string`,description:`Optional report start date as YYYY-MM-DD.`},"end-date":{type:`string`,description:`Optional report end date as YYYY-MM-DD.`},...X([`limit`],{limit:{description:`Maximum rows per diagnostic section. Defaults to 10.`}}),"change-limit":{type:`string`,description:`Maximum saved changes to measure. Defaults to 5.`},...Co},run:async({args:e})=>{let t=await U({client:E(e),site:T(e.site),options:{refresh:D(e.refresh)}}),n=await me({site:t.site,days:O(e.days),recentDays:O(e.recent),startDate:T(e[`start-date`]),endDate:T(e[`end-date`]),limit:O(e.limit),changeLimit:O(e[`change-limit`]),brandTerms:t.client?.brandTerms,...yo(e),progress:go(!0)}),r=T(e.out)??Do({report:`narrative`,clientId:t.client?.id,site:t.site});ko(r,await Oo(r,d(n)))}});function Go(e){return ne({days:k(e.days,`--days`),sitemaps:A(e.sitemap),maxSitemapUrls:k(e[`max-sitemap-urls`],`--max-sitemap-urls`),templateLimit:k(e.limit,`--limit`),minimumTemplateUrls:k(e[`minimum-template-urls`],`--minimum-template-urls`),minimumTemplateShare:k(e[`minimum-template-share`],`--minimum-template-share`),minimumTemplateImpressions:k(e[`minimum-template-impressions`],`--minimum-template-impressions`),crawlSamples:k(e[`crawl-samples`],`--crawl-samples`),inspectSamples:k(e[`inspect-samples`],`--inspect-samples`),brandTerms:A(e[`brand-terms`]),includeBrand:D(e[`include-brand`]),js:D(e.js)?!0:`auto`,refresh:D(e.refresh),rate:hr(e)})}const Ko=S({meta:{name:`pseo`,description:`Export pSEO audit tables: templates, demand patterns, top queries, sample coverage, crawl samples, and inspection samples.`},args:{...wo,out:To(`pseo`),days:{type:`string`,description:`GSC lookback window. Defaults to 28.`},limit:{type:`string`,description:`Maximum pSEO templates to export.`},sitemap:{type:`string`,description:`Comma-separated sitemap URLs. Can be passed once.`},"crawl-samples":{type:`string`,description:`Number of representative URLs to fetch per template. Defaults to 0.`},"inspect-samples":{type:`string`,description:`Number of representative URLs to inspect in GSC per template. Defaults to 0.`},"max-sitemap-urls":{type:`string`,description:`Maximum discovered URLs per sitemap. Defaults to 50000.`},"minimum-template-urls":{type:`string`,description:`Minimum repeated URLs required for a template. Defaults to 3.`},"minimum-template-share":{type:`string`,description:`Minimum share of discovered URLs from 0 to 1. Defaults to 0.`},"minimum-template-impressions":{type:`string`,description:`Minimum retained page impressions for a template. Defaults to 0.`},"brand-terms":{type:`string`,description:`Comma-separated brand terms to exclude from query evidence.`},"include-brand":{type:`boolean`,default:!1,description:`Include branded queries in pSEO demand analysis.`},js:{type:`boolean`,default:!1,description:`Force JavaScript rendering for crawled samples.`},"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=Go(e),n=await U({client:E(e),site:T(e.site)}),r=await oe({site:n.site,...t,brandTerms:t.brandTerms??n.client?.brandTerms,progress:go(!0)}),i=T(e.out)??Do({report:`pseo`,clientId:n.client?.id,site:n.site});ko(i,await Oo(i,l(r)))}}),qo=S({meta:{name:`refresh-priorities`,description:`Export the ranked SEO priority queue, score breakdowns, grouped findings, and diagnosis detail CSVs.`},args:{...wo,out:To(`refresh-priorities`),...X([`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.`}}),"google-analytics-property":{type:`string`,description:`Google Analytics property ID to use for analytics value. Defaults from the selected project.`},"verify-content":w(`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:E(e),site:T(e.site),options:{refresh:D(e.refresh)}}),n=await fe({site:t.site,days:O(e.days),recentDays:O(e.recent),limit:O(e.limit),brandTerms:t.client?.brandTerms,includeBrand:D(e[`include-brand`]),googleAnalyticsPropertyId:T(e[`google-analytics-property`])??t.client?.analytics.google?.propertyId,verifyContent:D(e[`verify-content`]),verifyLimit:O(e[`verify-limit`]),refresh:D(e.refresh)}),r=T(e.out)??Do({report:`refresh-priorities`,clientId:t.client?.id,site:t.site});ko(r,await Oo(r,pe(n)))}}),Jo=S({meta:{name:`export`,description:`Export full report data to CSV files`},subCommands:{diagnose:Ao,knowledge:Ho,narrative:Wo,monthly:Uo,"update-postmortem":S({meta:{name:`update-postmortem`,description:`Export update postmortem attribution, findings, template movement, and segment movers.`},args:{...wo,out:To(`update-postmortem`),...X([`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:E(e),site:T(e.site),options:{refresh:D(e.refresh)}}),n=await u({site:t.site,days:O(e.days),recentDays:O(e.recent),limit:O(e.limit),brandTerms:t.client?.brandTerms,includeBrand:D(e[`include-brand`]),knownConfounders:T(e[`known-change`])?[T(e[`known-change`])??``]:void 0,includeChangeLog:!D(e[`ignore-change-log`]),refresh:D(e.refresh)}),r=T(e.out)??Do({report:`update-postmortem`,clientId:t.client?.id,site:t.site});ko(r,await Oo(r,rr(n)))}}),"refresh-priorities":qo,pseo:Ko}}),Yo=S({...da,meta:{name:`init`,description:"Legacy alias for `seo start`."}});async function Xo(e,t){if(!e){process.stdout.write(t);return}await En(b(e),{recursive:!0}),await Mn(e,t),process.stdout.write(`Wrote ${e}\n`)}const Zo={"report-id":{type:`string`,description:`Saved crawl report id to use.`},site:{type:`string`,description:`GSC property URL for selecting the latest saved crawl.`},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.`}},Qo=S({meta:{name:`llms`,description:`Inspect or generate optional llms.txt from saved crawl data`},subCommands:{audit:S({meta:{name:`audit`,description:`Inspect optional llms.txt presence from a saved crawl`},args:Zo,run:async({args:e})=>{let t=M(e),n=$t(await ai(e,{json:t}));if(t){L(n);return}process.stdout.write(`llms.txt audit for ${n.url}\n\n`),R([[`SEO impact`,n.googleSearchImpact],[`Optional`,n.optional?`yes`:`no`],[`Found`,n.exists?`yes`:`no`],[`URL`,n.llmsTxtUrl],[`Status`,n.status?String(n.status):`-`]]),process.stdout.write(`\n${n.headline}\n`),n.issues.length&&(process.stdout.write(`
76
- Issues
77
- `),z([`Severity`,`Issue`,`Action`],n.issues.map(e=>[e.severity,e.title,K(e.action,96)]))),Y(`Recommended pages`,n.recommendedPages.slice(0,10).map(e=>`${e.section}: ${e.url}`))}}),generate:S({meta:{name:`generate`,description:`Generate an llms.txt draft from a saved crawl`},args:{...Zo,output:{type:`string`,description:`Write llms.txt to this path instead of stdout.`},"max-urls":{type:`string`,description:`Maximum URLs to include. Defaults to 250.`},"token-budget":{type:`string`,description:`Approximate token budget. Defaults to 12000.`},exclude:{type:`string`,description:`Comma-separated URL patterns to exclude.`},title:{type:`string`,description:`Override the llms.txt title.`},description:{type:`string`,description:`Override the llms.txt description.`}},run:async({args:e})=>{let t=M(e),n=a(await ai(e,{json:t}),{maxUrls:O(e[`max-urls`]),tokenBudget:O(e[`token-budget`]),exclude:A(e.exclude),title:T(e.title),description:T(e.description)});if(t){L(n);return}await Xo(T(e.output),n.content),T(e.output)&&R([[`URLs`,String(n.includedUrls)],[`Estimated tokens`,String(n.estimatedTokens)]])}})}}),$o=S({meta:{name:`logs`,description:`Manage local log retention`},subCommands:{prune:S({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=Vt();if(M(e)){L(t);return}e.quiet||process.stdout.write(`Rotated ${t.rotated} and removed ${t.removed} log files. ${Zr(t.sizeBytes)} retained.\n`)}})}}),es=[{client:`claude-desktop`,flag:`claude-desktop`},{client:`claude-code`,flag:`claude-code`},{client:`codex`,flag:`codex`},{client:`cursor`,flag:`cursor`}];function ts(e,t){return D(e.all)?t:es.filter(({flag:t})=>D(e[t])).map(({client:e})=>e)}const ns=S({meta:{name:`mcp`,description:`MCP server helpers`},subCommands:{serve:S({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-CBY8QOnv.js`);await t({test:D(e.test)})}}),install:S({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(D(e.uninstall)&&D(e.reinstall))throw new v(`INVALID_INPUT`,`Use either --uninstall or --reinstall, not both.`);let t=Di(),n=Ai({targets:t}),r=M(e),i=ts(e,n.map(e=>e.client));if(i.length===0){if(!I({json:r}))throw new v(`INVALID_INPUT`,`Choose an MCP client with --claude-desktop, --claude-code, --codex, --cursor, or --all.`);let a=n.length>0?n:t;i=V(await Bn({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 v(`INVALID_INPUT`,`MCP setup is not supported for ${a.join(`, `)} on ${process.platform}.`);let o=t.filter(e=>i.includes(e.client)).map(t=>D(e.uninstall)?Yi(t):Ji(t,{reinstall:D(e.reinstall)}));if(r){L({operation:D(e.uninstall)?`uninstall`:`install`,results:o});return}for(let e of o)process.stdout.write(`${e.changed?`updated`:`skipped`} ${e.client} · ${e.path}\n`)}})}}),rs=S({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 ie({startUrl:T(e.url)??``,site:T(e.site),limit:O(e.limit),refresh:D(e.refresh),js:D(e.js)?!0:`auto`});if(M(e)){L(t);return}if(R([[`Run`,t.run.id],[`Start URL`,t.run.startUrl],[`Crawled`,String(t.summary.crawled)],[`Previous run`,t.previousRun?.id??`none`],[`Added`,String(t.summary.added)],[`Removed`,String(t.summary.removed)],[`Changed`,String(t.summary.changed)],[`New errors`,String(t.summary.newErrors)],[`Indexability flips`,String(t.summary.indexabilityFlips)],[`High-priority actions`,String(t.summary.highPriorityRecommendations)]]),t.recommendations.length&&(process.stdout.write(`
166
+ `),z([[`Control`,e.control.change.title],[`Adjusted clicks`,e.control.adjusted.clickDelta===null?`n/a`:U(e.control.adjusted.clickDelta)],[`Adjusted click %`,e.control.adjusted.clickPctPoints===null?`n/a`:`${e.control.adjusted.clickPctPoints.toFixed(1)} pts`],[`Note`,e.control.note]])),Y(`Warnings`,e.warnings),Y(`Caveats`,e.caveats)}const ss=C({meta:{name:`tests`,description:`Create and report local SEO tests from Search Console and Google Analytics data`},subCommands:{list:C({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=N(e),n=D(e),r=fn({site:(n||E(e.site)?await H({client:n,site:E(e.site),options:{json:t}}):void 0)?.site,limit:k(e.limit)});if(t){R({tests:r});return}B([`ID`,`Date`,`Scope`,`Target`,`Title`],r.map(e=>[e.id,e.changedAt,e.scope,e.target,e.title]))}}),create:C({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=N(e),n=await H({client:D(e),site:E(e.site),options:{json:t}}),r=Le({site:n.site,scope:es(e.scope),target:E(e.target)??``,title:E(e.title)??``,description:E(e.description),changedAt:E(e.date)??$o()});if(t){R(r);return}z([[`ID`,r.id],[`Date`,r.changedAt],[`Project`,n.client?.name??n.site],[`Scope`,r.scope],[`Target`,r.target],[`Title`,r.title]])}}),report:C({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=N(e),n=E(e.id),r=!n||D(e)||E(e.site)?await H({client:D(e),site:E(e.site),options:{json:t,refresh:O(e.refresh)}}):void 0,i=await bt({id:n,site:n?void 0:r?.site,scope:n?void 0:ts(e.scope),target:n?void 0:E(e.target),title:E(e.title),changedAt:n?void 0:E(e.date),googleAnalyticsPropertyId:E(e.property)??r?.client?.analytics.google?.propertyId,controlScope:ts(e[`control-scope`]),controlTarget:E(e[`control-target`]),controlTitle:E(e[`control-title`]),beforeDays:A(e.before,`--before`),afterDays:A(e.after,`--after`),refresh:O(e.refresh)});if(t){R(i);return}os(i)}})}});function cs(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 ls={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.`}},us={...X([`includeBrand`,`verifyContent`,`verifyLimit`,`js`,`fetchConcurrency`,`fetchIntervalCap`,`fetchIntervalMs`,`refresh`]),json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}};function ds(e){return{includeBrand:O(e[`include-brand`]),verifyContent:O(e[`verify-content`]),verifyLimit:k(e[`verify-limit`])??3,js:O(e.js)?!0:void 0,rate:Kr(e),refresh:O(e.refresh)}}function fs(e){return{client:D(e),site:E(e.site),refresh:O(e.refresh)}}const{json:ps,...ms}=us,hs=ms,gs={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 _s(e){return{type:`string`,description:`Output folder. Defaults to ./seo-export/<project>-${e}-YYYY-MM-DD.`}}function vs(e){return e.toLowerCase().replace(/^sc-domain:/,``).replace(/^https?:\/\//,``).replace(/[^a-z0-9._-]+/g,`-`).replace(/^-+|-+$/g,``)}function ys(e){let t=new Date().toISOString().slice(0,10),n=vs(e.clientId??e.site);return Rn(process.cwd(),`seo-export`,`${n}-${e.report}-${t}`)}async function bs(e,t){await qn(e,{recursive:!0});let n=[];for(let r of t){let t=Rn(e,r.filename);await tr(t,Qe(r.rows,r.headers),`utf8`),n.push(t)}return n}function xs(e,t){process.stdout.write(`Wrote ${me(t.length,`CSV file`)} to ${e}\n`);for(let e of t)process.stdout.write(`- ${e}\n`)}const Ss=C({meta:{name:`diagnose`,description:`Export diagnosis tables: priorities, anomalies, movement, decay, cannibalisation, striking-distance, and quick wins.`},args:{...gs,out:_s(`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 H({client:D(e),site:E(e.site)}),n=await st({site:t.site,days:k(e.days),recentDays:k(e.recent),limit:k(e.limit),brandTerms:t.client?.brandTerms,includeBrand:O(e[`include-brand`]),verifyContent:O(e[`verify-content`]),verifyLimit:k(e[`verify-limit`]),js:O(e.js)?!0:`auto`,rate:Kr(e),refresh:O(e.refresh),progress:cs(!0)}),r=E(e.out)??ys({report:`diagnose`,clientId:t.client?.id,site:t.site});xs(r,await bs(r,Jt(n)))}}),Cs=`.seo-okf.json`,ws=`${JSON.stringify({generator:`seo`,format:`okf`,version:1})}\n`;function Ts(e,t){let n=Rn(e,t),r=Ln(Rn(e),n);if(!r||r.startsWith(`..`)||Fn(r))throw new y(`INVALID_INPUT`,`Unsafe OKF file path: ${t}`);return n}async function Es(e,t){for(let n of t){let t=Ts(e,n.path);await qn(x(t),{recursive:!0}),await tr(t,n.content)}await tr(S(e,Cs),ws)}async function Ds(e){let t=[];async function n(e,r=``){let i=await Xn(e,{withFileTypes:!0});for(let a of i){let i=r?`${r}/${a.name}`:a.name,o=S(e,a.name);a.isDirectory()?await n(o,i):a.isFile()&&a.name.endsWith(`.md`)&&t.push({path:i,content:await Yn(o,`utf8`)})}}return await n(e),t.sort((e,t)=>e.path<t.path?-1:+(e.path>t.path))}async function Os(e){try{let t=await Kn(e);if(t.isSymbolicLink()||!t.isDirectory())throw new y(`INVALID_INPUT`,`OKF output must be a real directory: ${e}`);if(!(await Xn(e)).length)return!0;try{if(await Yn(S(e,Cs),`utf8`)===ws)return!0}catch{}throw new y(`INVALID_INPUT`,`Refusing to replace non-empty unmanaged directory: ${e}`)}catch(e){if(e.code===`ENOENT`)return!1;throw e}}async function ks(e,t){if(!se(t).valid)throw new y(`INVALID_INPUT`,`Generated bundle did not pass seo OKF checks and was not written.`);let n=Rn(e),r=x(n);await qn(r,{recursive:!0});let i=await Os(n),a=await Jn(S(r,`.${Pn(n)}-staging-`)),o=S(r,`.${Pn(n)}-backup-${Bn()}`),s=!1;try{await Es(a,t);let e=se(await Ds(a));if(!e.valid)throw new y(`INTERNAL_ERROR`,`Staged OKF files failed validation after writing.`);return i&&(await Zn(n,o),s=!0),await Zn(a,n),s&&await Qn(o,{recursive:!0,force:!0}),e}catch(e){throw await Qn(a,{recursive:!0,force:!0}),s&&(await Qn(n,{recursive:!0,force:!0}),await Zn(o,n)),e}}async function As(e,t){if(!e){process.stdout.write(t);return}await qn(x(e),{recursive:!0}),await tr(e,t),process.stdout.write(`Wrote ${e}\n`)}function js(e){let t=E(e)??`okf`;if([`okf`,`markdown`,`json`].includes(t))return t;throw Error(`Format must be one of: okf, markdown, json.`)}function Ms(e){return{reportId:e.id,url:e.config.url,generatedAt:e.generatedAt,crawl:{summary:e.summary,caveats:e.caveats,warnings:e.warnings,pages:e.pages.map(e=>({url:e.finalUrl,title:e.title,metaDescription:e.metaDescription,status:e.status,indexable:e.indexable,wordCount:e.wordCount,schemaTypes:e.schemaTypes,sameAs:e.schemaSameAs,internalInlinks:e.internalInlinkCount,contentSample:e.contentSample}))},aiReadiness:Pt(e),entityReadiness:an(e),llmsTxt:Qt(e)}}function Ns(e){let t=Ms(e);return`${[`# Site Knowledge Export`,``,`Source: ${e.config.url}`,`Crawl report: ${e.id}`,`Generated: ${t.generatedAt}`,``,`## Summary`,``,`- Documents: ${e.summary.totalPages}${e.requestEvidenceStatus===`available`?` from ${e.requests.length} URL requests`:e.requestEvidenceStatus===`partial`?` from ${e.requests.length} observed URL requests (request evidence is partial)`:` (request evidence unavailable for this legacy report)`}`,`- Indexable pages: ${e.summary.indexablePages}`,`- Crawl findings: ${e.issues.length} (${e.summary.highIssues} high, ${e.summary.mediumIssues} medium, ${e.summary.lowIssues} low)`,`- AI-search evidence: ${t.aiReadiness.dataStatus} (${t.aiReadiness.assessment})`,`- Entity evidence: ${t.entityReadiness.dataStatus} (${t.entityReadiness.assessment})`,``,`## Top AI Readiness Actions`,``,...t.aiReadiness.topActions.length?t.aiReadiness.topActions.map(e=>`- ${e.title}: ${e.action}`):[`- No AI-search technical actions in this crawl.`],``,`## Entity Signals`,``,`- sameAs profiles: ${t.entityReadiness.entities.sameAs.length}`,`- Social profiles: ${t.entityReadiness.entities.socialProfiles.length}`,`- Authors: ${t.entityReadiness.entities.authors.length}`,``,`## Important Pages`,``,...e.pages.filter(e=>e.indexable&&e.status<400).slice(0,100).map(e=>`- [${e.title??e.finalUrl}](${e.finalUrl}) - ${e.metaDescription??e.contentSample??`No summary available.`}`),``,`# Citations`,``,`- [Crawl start URL](${e.config.url})`,``].join(`
167
+ `)}\n`}const Ps=C({meta:{name:`knowledge`,description:`Export site knowledge from a saved crawl as OKF, Markdown, or JSON`},args:{"report-id":{type:`string`,description:`Saved crawl report id to export.`},site:{type:`string`,description:`GSC property URL for selecting the latest saved crawl.`},client:{type:`string`,description:`Legacy alias for --project.`},project:{type:`string`,description:`Saved project id or name.`},format:{type:`string`,description:`Output format: okf, markdown, or json. Defaults to okf.`},output:{type:`string`,description:`Output directory for OKF, or output file for Markdown/JSON.`},"max-concepts":{type:`string`,description:`Maximum OKF concept files. Defaults to 500.`},json:{type:`boolean`,default:!1,description:`Print command metadata as JSON.`}},run:async({args:e})=>{let t=N(e),n=js(e.format),r=n===`okf`?ne(A(e[`max-concepts`],`--max-concepts`)):void 0,i=await Wi(e,{json:t}),a=E(e.output);if(n===`okf`){let e=be(i,{maxConcepts:r}),o=a??`./okf`,s=await ks(o,e.files);if(t){R({format:n,output:o,bundle:e,validation:s});return}process.stdout.write(`Wrote OKF bundle to ${o}\n\n`),z([[`Files`,String(e.files.length)],[`Concepts`,String(e.conceptCount)],[`Valid`,s.valid?`yes`:`no`]]);return}if(n===`json`){await As(a,`${JSON.stringify(Ms(i),null,2)}\n`);return}await As(a,Ns(i))}}),Fs=C({meta:{name:`monthly`,description:`Export monthly report tables plus the underlying diagnosis detail CSVs.`},args:{...gs,out:_s(`monthly`),month:{type:`string`,description:`Report month as YYYY-MM. Defaults to the latest month with final GSC data.`},...X([`limit`],{limit:{description:`Maximum rows per diagnostic section. Defaults to 10.`}}),...hs},run:async({args:e})=>{let t=await H({client:D(e),site:E(e.site),options:{refresh:O(e.refresh)}}),n=await nn({site:t.site,month:E(e.month),limit:k(e.limit),brandTerms:t.client?.brandTerms,...ds(e),progress:cs(!0)}),r=E(e.out)??ys({report:`monthly`,clientId:t.client?.id,site:t.site});xs(r,await bs(r,le(n)))}}),Is=C({meta:{name:`narrative`,description:`Export report narrative tables plus the underlying diagnosis detail CSVs.`},args:{...gs,out:_s(`narrative`),...X([`days`,`recentDays`],{days:{description:`Diagnosis window length in days. Defaults to 90.`}}),"start-date":{type:`string`,description:`Optional report start date as YYYY-MM-DD.`},"end-date":{type:`string`,description:`Optional report end date as YYYY-MM-DD.`},...X([`limit`],{limit:{description:`Maximum rows per diagnostic section. Defaults to 10.`}}),"change-limit":{type:`string`,description:`Maximum saved changes to measure. Defaults to 5.`},...hs},run:async({args:e})=>{let t=await H({client:D(e),site:E(e.site),options:{refresh:O(e.refresh)}}),n=await Bt({site:t.site,days:k(e.days),recentDays:k(e.recent),startDate:E(e[`start-date`]),endDate:E(e[`end-date`]),limit:k(e.limit),changeLimit:k(e[`change-limit`]),brandTerms:t.client?.brandTerms,...ds(e),progress:cs(!0)}),r=E(e.out)??ys({report:`narrative`,clientId:t.client?.id,site:t.site});xs(r,await bs(r,le(n)))}});function Ls(e){return Et({days:A(e.days,`--days`),sitemaps:j(e.sitemap),maxSitemapUrls:A(e[`max-sitemap-urls`],`--max-sitemap-urls`),templateLimit:A(e.limit,`--limit`),minimumTemplateUrls:A(e[`minimum-template-urls`],`--minimum-template-urls`),minimumTemplateShare:A(e[`minimum-template-share`],`--minimum-template-share`),minimumTemplateImpressions:A(e[`minimum-template-impressions`],`--minimum-template-impressions`),crawlSamples:A(e[`crawl-samples`],`--crawl-samples`),inspectSamples:A(e[`inspect-samples`],`--inspect-samples`),brandTerms:j(e[`brand-terms`]),includeBrand:O(e[`include-brand`]),js:O(e.js)?!0:`auto`,refresh:O(e.refresh),rate:qr(e)})}const Rs=C({meta:{name:`pseo`,description:`Export pSEO audit tables: templates, demand patterns, top queries, sample coverage, crawl samples, and inspection samples.`},args:{...gs,out:_s(`pseo`),days:{type:`string`,description:`GSC lookback window. Defaults to 28.`},limit:{type:`string`,description:`Maximum pSEO templates to export.`},sitemap:{type:`string`,description:`Comma-separated sitemap URLs. Can be passed once.`},"crawl-samples":{type:`string`,description:`Number of representative URLs to fetch per template. Defaults to 0.`},"inspect-samples":{type:`string`,description:`Number of representative URLs to inspect in GSC per template. Defaults to 0.`},"max-sitemap-urls":{type:`string`,description:`Maximum discovered URLs per sitemap. Defaults to 50000.`},"minimum-template-urls":{type:`string`,description:`Minimum repeated URLs required for a template. Defaults to 3.`},"minimum-template-share":{type:`string`,description:`Minimum share of discovered URLs from 0 to 1. Defaults to 0.`},"minimum-template-impressions":{type:`string`,description:`Minimum retained page impressions for a template. Defaults to 0.`},"brand-terms":{type:`string`,description:`Comma-separated brand terms to exclude from query evidence.`},"include-brand":{type:`boolean`,default:!1,description:`Include branded queries in pSEO demand analysis.`},js:{type:`boolean`,default:!1,description:`Force JavaScript rendering for crawled samples.`},"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=Ls(e),n=await H({client:D(e),site:E(e.site)}),r=await it({site:n.site,...t,brandTerms:t.brandTerms??n.client?.brandTerms,progress:cs(!0)}),i=E(e.out)??ys({report:`pseo`,clientId:n.client?.id,site:n.site});xs(i,await bs(i,Xe(r)))}}),zs=C({meta:{name:`refresh-priorities`,description:`Export the ranked SEO priority queue, score breakdowns, grouped findings, and diagnosis detail CSVs.`},args:{...gs,out:_s(`refresh-priorities`),...X([`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.`}}),"google-analytics-property":{type:`string`,description:`Google Analytics property ID to use for analytics value. Defaults from the selected project.`},"verify-content":Vr(`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 H({client:D(e),site:E(e.site),options:{refresh:O(e.refresh)}}),n=await qe({site:t.site,days:k(e.days),recentDays:k(e.recent),limit:k(e.limit),brandTerms:t.client?.brandTerms,includeBrand:O(e[`include-brand`]),googleAnalyticsPropertyId:E(e[`google-analytics-property`])??t.client?.analytics.google?.propertyId,verifyContent:O(e[`verify-content`]),verifyLimit:k(e[`verify-limit`]),refresh:O(e.refresh)}),r=E(e.out)??ys({report:`refresh-priorities`,clientId:t.client?.id,site:t.site});xs(r,await bs(r,Je(n)))}}),Bs=C({meta:{name:`export`,description:`Export full report data to CSV files`},subCommands:{diagnose:Ss,knowledge:Ps,narrative:Is,monthly:Fs,"update-postmortem":C({meta:{name:`update-postmortem`,description:`Export update postmortem attribution, findings, template movement, and segment movers.`},args:{...gs,out:_s(`update-postmortem`),...X([`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 H({client:D(e),site:E(e.site),options:{refresh:O(e.refresh)}}),n=await ce({site:t.site,days:k(e.days),recentDays:k(e.recent),limit:k(e.limit),brandTerms:t.client?.brandTerms,includeBrand:O(e[`include-brand`]),knownConfounders:E(e[`known-change`])?[E(e[`known-change`])??``]:void 0,includeChangeLog:!O(e[`ignore-change-log`]),refresh:O(e.refresh)}),r=E(e.out)??ys({report:`update-postmortem`,clientId:t.client?.id,site:t.site});xs(r,await bs(r,Pr(n)))}}),"refresh-priorities":zs,pseo:Rs}}),Vs={project:{type:`string`,description:`Saved project id or name.`},client:{type:`string`,description:`Legacy alias for --project.`},site:{type:`string`,description:`Site URL.`}};async function Hs(e){let t=E(e.site);if(t)return t;let n=await Hi({project:D(e),options:{json:N(e)}});if(!n)throw new y(`INVALID_INPUT`,`Pass --site or select a saved project.`);return n.siteUrl}async function Us(e){let t=E(e.url),n=j(e.urls)??[],r=E(e.file);if(!t&&n.length===0&&!r)throw new y(`INVALID_INPUT`,`Pass --url, comma-separated --urls, or a newline-delimited --file.`);let i=[...t?[t]:[],...n];if(r){let e=await $n(r).catch(()=>void 0);if(!e)throw new y(`INVALID_INPUT`,`IndexNow URL file not found: ${r}`);if(!e.isFile()||e.size>21e5)throw new y(`INVALID_INPUT`,`IndexNow URL files must be regular files no larger than 2.1 MB.`);i.push(...(await Yn(r,`utf8`)).split(/\r?\n/).map(e=>e.trim()).filter(Boolean))}return i}const Ws=C({meta:{name:`indexnow`,description:`Set up and submit changed URLs with IndexNow`},subCommands:{setup:C({meta:{name:`setup`,description:`Generate an IndexNow key file for one site`},args:{...Vs,output:{type:`string`,description:`Public asset directory for the generated key file.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=E(e.output);if(!t)throw new y(`INVALID_INPUT`,`Pass --output with the public asset directory that deploys at the site root.`);let r=m({site:await Hs(e)}),i=Rn(t),a=S(i,`${r.key}.txt`);await qn(i,{recursive:!0});try{await tr(a,`${r.key}\n`,{encoding:`utf8`,flag:`wx`,mode:384})}catch(e){throw e.code===`EEXIST`?new y(`INVALID_INPUT`,`Refusing to overwrite the existing file at ${a}.`):e}let o;try{o=await n(r)}catch(e){throw await er(a).catch(()=>void 0),e}let s={configured:!0,host:r.host,keyLocation:r.keyLocation,keyFile:a,credentialSource:o,nextStep:`Deploy ${a}, then run seo indexnow verify --site ${new URL(r.keyLocation).origin}`};N(e)?R(s):z([[`Host`,s.host],[`Key file`,s.keyFile],[`Public URL`,s.keyLocation],[`Saved in`,s.credentialSource],[`Next`,s.nextStep]])}}),status:C({meta:{name:`status`,description:`Show saved IndexNow hosts`},args:{json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=await d(),n={configuredSites:t.length,sites:t};N(e)?R(n):B([`Host`,`Key location`,`Created`],t.map(e=>[e.host,e.keyLocation,e.createdAt]))}}),verify:C({meta:{name:`verify`,description:`Check the public IndexNow key file`},args:{...Vs,json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=await At({site:await Hs(e)}),n={...await Fe({record:t.record}),credentialSource:t.source};N(e)?R(n):z([[`Verified`,n.verified?`yes`:`no`],[`HTTP status`,String(n.status)],[`Key location`,n.keyLocation],[`Credential`,n.credentialSource]]),n.verified||(process.exitCode=4)}}),submit:C({meta:{name:`submit`,description:`Notify IndexNow about changed URLs`},args:{...Vs,url:{type:`string`,description:`One changed URL.`},urls:{type:`string`,description:`Comma-separated changed URLs.`},file:{type:`string`,description:`Newline-delimited changed URL file.`},"dry-run":{type:`boolean`,default:!1,description:`Validate without notifying IndexNow.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=await At({site:await Hs(e)}),n=await un({record:t.record,urls:await Us(e),dryRun:O(e[`dry-run`])}),r={...n,credentialSource:t.source};if(N(e)){R(r);return}z([[`Host`,n.host],[`Status`,n.status],[`URLs`,String(n.submittedUrls)],[`Dry run`,n.dryRun?`yes`:`no`],[`Key location`,n.keyLocation]]);for(let e of n.caveats)process.stdout.write(`\n${e}\n`)}}),remove:C({meta:{name:`remove`,description:`Remove a saved IndexNow key`},args:{...Vs,json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=await xe(await Hs(e)),n={removed:t,note:`The deployed key file was not removed.`};N(e)?R(n):process.stdout.write(`${t?`Saved key removed.`:`No saved key found.`} ${n.note}\n`)}})}}),Gs=C({...to,meta:{name:`init`,description:"Legacy alias for `seo start`."}}),Ks=C({meta:{name:`links`,description:`Review bounded referring-link evidence from Bing or a file`},args:{project:{type:`string`,description:`Saved project id or name.`},client:{type:`string`,description:`Legacy alias for --project.`},site:{type:`string`,description:`Verified Bing Webmaster site URL.`},file:{type:`string`,description:`Local CSV, JSON, JSONL, or NDJSON link export.`},format:{type:`string`,description:`Import format override: csv, json, or jsonl.`},"row-limit":{type:`string`,description:`Maximum imported or Bing link rows. Defaults to 500 for Bing and 10000 for files.`},"target-limit":{type:`string`,description:`Maximum Bing target pages to inspect. Defaults to 20.`},"detail-pages":{type:`string`,description:`Maximum Bing result pages per target. Defaults to 1.`},limit:{type:`string`,description:`Maximum link rows returned. Defaults to 100.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=N(e),n=E(e.file),r=E(e.format);if(r&&![`csv`,`json`,`jsonl`].includes(r))throw new y(`INVALID_INPUT`,`--format must be csv, json, or jsonl.`);let i=A(e[`row-limit`],`--row-limit`),a;if(n)a=await Me({file:n,format:r,rowLimit:i});else{let n=await Hi({project:D(e),options:{json:t}}),r=E(e.site)??he(n);if(!r)throw new y(`INVALID_INPUT`,`Pass --site, connect Bing to a saved project, or import a link file with --file.`);a=await c({site:r,rowLimit:i,targetLimit:A(e[`target-limit`],`--target-limit`),detailPagesPerTarget:A(e[`detail-pages`],`--detail-pages`)})}let o=pn({evidence:a,limit:A(e.limit,`--limit`)});if(t){R(o);return}K({title:`Inbound link evidence`,target:o.provenance.provider,status:o.dataStatus===`complete`?`info`:`unknown`,summary:`${U(o.summary.observedLinks)} observed links from ${U(o.summary.referringDomains)} referring domains.`,metrics:[{label:`Evidence`,value:o.dataStatus},{label:`Observed links`,value:U(o.summary.observedLinks)},{label:`Referring domains`,value:U(o.summary.referringDomains)},{label:`Target pages`,value:U(o.summary.targetPages)},{label:`Returned rows`,value:`${U(o.selection.returnedRows)} of ${U(o.selection.availableRows)}`}]}),o.links.length&&q([`Source`,`Target`,`Anchor`],o.links.map(e=>[G(e.sourceUrl,58),G(e.targetUrl,58),G(e.anchorText??``,36)])),Y(`Warnings`,o.warnings),Y(`Report caveats`,o.caveats)}});async function qs(e,t){if(!e){process.stdout.write(t);return}await qn(x(e),{recursive:!0}),await tr(e,t),process.stdout.write(`Wrote ${e}\n`)}const Js={"report-id":{type:`string`,description:`Saved crawl report id to use.`},site:{type:`string`,description:`GSC property URL for selecting the latest saved crawl.`},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.`}},Ys=C({meta:{name:`llms`,description:`Inspect or generate optional llms.txt from saved crawl data`},subCommands:{audit:C({meta:{name:`audit`,description:`Inspect optional llms.txt presence from a saved crawl`},args:Js,run:async({args:e})=>{let t=N(e),n=Qt(await Wi(e,{json:t}));if(t){R(n);return}K({title:`llms.txt audit`,target:n.url,status:n.issues.length>0?`warning`:`pass`,summary:n.headline,metrics:[{label:`SEO impact`,value:n.googleSearchImpact},{label:`Optional`,value:n.optional?`Yes`:`No`},{label:`Found`,value:n.exists?`Yes`:`No`},{label:`URL`,value:n.llmsTxtUrl},{label:`HTTP status`,value:n.status?String(n.status):`Unavailable`}],diagnostics:[{title:`Issues`,items:n.issues.map(e=>({status:e.severity===`high`?`fail`:`warning`,title:e.title,explanation:e.plainEnglish,fix:e.action}))}]}),Y(`Recommended pages`,n.recommendedPages.slice(0,10).map(e=>`${e.section}: ${e.url}`))}}),generate:C({meta:{name:`generate`,description:`Generate an llms.txt draft from a saved crawl`},args:{...Js,output:{type:`string`,description:`Write llms.txt to this path instead of stdout.`},"max-urls":{type:`string`,description:`Maximum URLs to include. Defaults to 250.`},"token-budget":{type:`string`,description:`Approximate token budget. Defaults to 12000.`},exclude:{type:`string`,description:`Comma-separated URL patterns to exclude.`},title:{type:`string`,description:`Override the llms.txt title.`},description:{type:`string`,description:`Override the llms.txt description.`}},run:async({args:e})=>{let t=N(e),n=ct(await Wi(e,{json:t}),{maxUrls:k(e[`max-urls`]),tokenBudget:k(e[`token-budget`]),exclude:j(e.exclude),title:E(e.title),description:E(e.description)});if(t){R(n);return}await qs(E(e.output),n.content),E(e.output)&&z([[`URLs`,String(n.includedUrls)],[`Estimated tokens`,String(n.estimatedTokens)]])}})}}),Xs=C({meta:{name:`logs`,description:`Manage local log retention`},subCommands:{prune:C({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=at();if(N(e)){R(t);return}e.quiet||process.stdout.write(`Rotated ${t.rotated} and removed ${t.removed} log files. ${Ri(t.sizeBytes)} retained.\n`)}})}}),Zs=[{client:`claude-desktop`,flag:`claude-desktop`},{client:`claude-code`,flag:`claude-code`},{client:`codex`,flag:`codex`},{client:`cursor`,flag:`cursor`}];function Qs(e,t){return O(e.all)?t:Zs.filter(({flag:t})=>O(e[t])).map(({client:e})=>e)}const $s=C({meta:{name:`mcp`,description:`MCP server helpers`},subCommands:{serve:C({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-BB0yH7qK.js`);await t({test:O(e.test)})}}),install:C({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(O(e.uninstall)&&O(e.reinstall))throw new y(`INVALID_INPUT`,`Use either --uninstall or --reinstall, not both.`);let t=_a(),n=ba({targets:t}),r=N(e),i=Qs(e,n.map(e=>e.client));if(i.length===0){if(!L({json:r}))throw new y(`INVALID_INPUT`,`Choose an MCP client with --claude-desktop, --claude-code, --codex, --cursor, or --all.`);let a=n.length>0?n:t;i=V(await lr({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 y(`INVALID_INPUT`,`MCP setup is not supported for ${a.join(`, `)} on ${process.platform}.`);let o=t.filter(e=>i.includes(e.client)).map(t=>O(e.uninstall)?za(t):Ra(t,{reinstall:O(e.reinstall)}));if(r){R({operation:O(e.uninstall)?`uninstall`:`install`,results:o});return}for(let e of o)process.stdout.write(`${e.changed?`updated`:`skipped`} ${e.client} · ${e.path}\n`)}})}}),ec=C({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 Rt({startUrl:E(e.url)??``,site:E(e.site),limit:k(e.limit),refresh:O(e.refresh),js:O(e.js)?!0:`auto`});if(N(e)){R(t);return}if(K({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(`
78
168
  Recommended actions
79
- `),z([`Severity`,`Category`,`URL`,`Action`],t.recommendations.slice(0,10).map(e=>[e.severity,e.category,K(e.url,56),K(e.action,72)])),J(`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
+ `),B([`Severity`,`Category`,`URL`,`Action`],t.recommendations.slice(0,10).map(e=>[e.severity,e.category,G(e.url,56),G(e.action,72)])),J(`Top crawl actions`,t.recommendations.map(e=>({label:e.category,context:`${e.severity}, ${G(e.url,96)}`,action:e.action})))),t.items.length&&(process.stdout.write(`
80
170
  Changed URLs
81
- `),z([`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
+ `),B([`Kind`,`URL`,`Changes`,`Recommendation`],t.items.slice(0,50).map(e=>[e.kind,G(e.url,56),e.changes.join(`, `),e.recommendation?.title??`-`]))),t.warnings.length){process.stdout.write(`
82
172
  Warnings
83
- `);for(let e of t.warnings.slice(0,10))process.stdout.write(`- ${e}\n`)}}});function is(){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":w(`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 as(){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 os(e){R([[`Workflow`,e.workflow],[`Property`,e.site],[`Summary`,e.summary]]),z([`Tool`,`Status`,`Summary`],e.steps.map(e=>[e.tool,e.status,e.summary])),e.actions.length&&(z([`Priority`,`Confidence`,`Action`],e.actions.map(e=>[e.title,e.confidence,e.action])),J(`Priority action details`,e.actions.map(e=>({label:e.title,context:e.confidence,action:e.action}))))}function ss(e){if(os(e),e.output.crawl?.recommendations.length&&(process.stdout.write(`
173
+ `);for(let e of t.warnings.slice(0,10))process.stdout.write(`- ${e}\n`)}}});function tc(){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":Vr(`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 nc(){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 rc(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 ic(e){let t=e.steps.map(e=>rc(e.status)),n=t.includes(`fail`)?`fail`:t.includes(`warning`)?`warning`:t.includes(`unknown`)?`unknown`:`pass`;Mi({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:rc(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 ac(e){if(ic(e),e.output.crawl?.recommendations.length&&(process.stdout.write(`
84
174
  Crawl actions
85
- `),z([`Severity`,`URL`,`Action`],e.output.crawl.recommendations.slice(0,10).map(e=>[e.severity,K(e.url,56),K(e.action,72)])),J(`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
+ `),B([`Severity`,`URL`,`Action`],e.output.crawl.recommendations.slice(0,10).map(e=>[e.severity,G(e.url,56),G(e.action,72)])),J(`Top crawl actions`,e.output.crawl.recommendations.map(e=>({label:e.severity,context:G(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(`
86
176
  Index reviews, changes, and failed checks
87
- `),z([`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
+ `),B([`Check`,`Change`,`Index state`,`Evidence`,`URL`],t.map(e=>[e.inspectionStatus,e.changeKind,e.indexStatus,G((e.errorCode??e.issueCodes.join(`, `))||`none`,40),G(e.url,64)])))}e.output.recovery?.items.length&&(process.stdout.write(`
88
178
  Recoverable URLs
89
- `),z([`Severity`,`Issue`,`Clicks`,`Impr`,`URL`,`Action`],e.output.recovery.items.slice(0,10).map(e=>[e.severity,e.issue,W(e.clicks),W(e.impressions),K(e.url,56),K(e.recommendation.action,72)])),J(`Top recovery actions`,e.output.recovery.items.map(e=>({label:K(e.url,96),context:`${e.severity}, ${W(e.clicks)} clicks at risk`,action:e.recommendation.action}))))}function cs(e){R([[`Property`,e.site],[`Health`,e.health],[`Attention`,String(e.summary.attention)],[`Stale`,String(e.summary.stale)],[`Not run`,String(e.summary.notRun)]]),z([`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
+ `),B([`Severity`,`Issue`,`Clicks`,`Impr`,`URL`,`Action`],e.output.recovery.items.slice(0,10).map(e=>[e.severity,e.issue,U(e.clicks),U(e.impressions),G(e.url,56),G(e.recommendation.action,72)])),J(`Top recovery actions`,e.output.recovery.items.map(e=>({label:G(e.url,96),context:`${e.severity}, ${U(e.clicks)} clicks at risk`,action:e.recommendation.action}))))}function oc(e){K({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}]}),B([`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(`
90
180
  Next actions
91
- `),z([`Check`,`Action`],t.map(e=>[e.name,K(e.action??``,96)])),J(`Next action details`,t.map(e=>({label:e.name,action:e.action??``}))))}function Z(e,t){let n=T(t);return n?`--${e} ${JSON.stringify(n)}`:void 0}function ls(e,t){return D(t)?`--${e}`:void 0}function us(e){return D(e)===!1?`--recover-links=false`:void 0}const ds=S({meta:{name:`monitoring`,description:`Run and review recurring technical SEO monitoring`},subCommands:{run:S({meta:{name:`run`,description:`Run crawl, index, and link recovery monitoring for a property`},args:is(),run:async({args:e})=>{let t=M(e),n=await U({client:E(e),site:T(e.site),options:{json:t,refresh:D(e.refresh)}}),r=j(e.urls),i=j(e.sitemaps),a=T(e.url)??n.client?.startUrl??xi(n.site),o=await c({site:n.site,startUrl:a,urls:r.length?r:n.client?.watchUrls,sitemaps:i.length?i:void 0,properties:j(e.properties).length?j(e.properties):void 0,limit:O(e.limit),languageCode:T(e.language),dailyLimit:O(e[`daily-limit`]),inspectLimit:O(e[`inspect-limit`]),maxUrls:O(e[`max-urls`]),refresh:D(e.refresh),js:D(e.js)?!0:`auto`,recoverLinks:D(e[`recover-links`]),recoverDays:O(e[`recover-days`]),recoverLimit:O(e[`recover-limit`]),recoverMinClicks:O(e[`recover-min-clicks`]),recoverMinImpressions:O(e[`recover-min-impressions`])});if(t){L(o);return}ss(o)}}),status:S({meta:{name:`status`,description:`Show the latest saved monitoring state for a property`},args:as(),run:async({args:e})=>{let t=M(e),n=Qt({site:(await U({client:E(e),site:T(e.site),options:{json:t,refresh:D(e.refresh)}})).site,staleAfterDays:O(e[`stale-days`])});if(t){L(n);return}cs(n)}}),cron:S({meta:{name:`cron`,description:`Print a local cron line for recurring monitoring`},args:{...is(),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=M(e),n=await U({client:E(e),site:T(e.site),options:{json:t,refresh:D(e.refresh)}}),r=O(e.hour)??9,i=O(e.minute)??0,a=O(e.weekday)??n.client?.technicalWeekday??1,o=[`seo monitoring run`,n.client?`--project ${JSON.stringify(n.client.id)}`:`--site ${JSON.stringify(n.site)}`,Z(`url`,e.url),Z(`urls`,e.urls),Z(`sitemaps`,e.sitemaps),Z(`properties`,e.properties),Z(`limit`,e.limit),Z(`daily-limit`,e[`daily-limit`]),Z(`inspect-limit`,e[`inspect-limit`]),Z(`max-urls`,e[`max-urls`]),Z(`language`,e.language),Z(`recover-days`,e[`recover-days`]),Z(`recover-limit`,e[`recover-limit`]),Z(`recover-min-clicks`,e[`recover-min-clicks`]),Z(`recover-min-impressions`,e[`recover-min-impressions`]),us(e[`recover-links`]),ls(`refresh`,e.refresh),ls(`js`,e.js),`--json`].filter(Boolean).join(` `),s={name:`monitoring`,cron:`${i} ${r} * * ${a}`,command:o};if(t){L({site:n.site,line:s});return}R([[`Property`,n.site],[`Install`,`crontab -e`]]),process.stdout.write(`${s.cron} ${s.command}\n`)}})}}),fs=S({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=M(e),n=await U({client:E(e),site:T(e.site),options:{json:t,refresh:D(e.refresh)}}),r=j(e.sitemaps),i=await m({site:n.site,crawlReportId:T(e[`crawl-report-id`]),sitemaps:r.length?r:void 0,days:O(e.days),rowLimit:O(e[`row-limit`]),maxSitemapUrls:O(e[`max-sitemap-urls`]),itemsPerSection:O(e.limit),refresh:D(e.refresh)});if(t){L(i);return}if(R([[`Project site`,i.site],[`Crawl report`,i.input.crawlReport.id],[`Crawl evidence`,i.sources.crawl.completeness],[`Sitemap evidence`,i.sources.sitemap.completeness],[`Search Console window`,`${i.sources.searchConsole.startDate} to ${i.sources.searchConsole.endDate}`],[`Search Console evidence`,i.sources.searchConsole.completeness],[`Pages in returned Search Console data`,String(i.summary.retainedSearchVisibleUrls)],[`Crawlable pages to review`,String(i.summary.crawlableCandidatesWithoutRetainedSearchVisibility)],[`Blocked or non-indexable crawl pages`,String(i.summary.blockedOrNonIndexableCrawlUrls)],[`Sitemap-only pages`,String(i.summary.sitemapOnlyUrls)],[`Search Console-only pages`,String(i.summary.searchConsoleOnlyUrls)]]),i.crawlableWithoutRetainedSearchVisibility.items.length&&(process.stdout.write(`
181
+ `),B([`Check`,`Action`],t.map(e=>[e.name,G(e.action??``,96)])),J(`Next action details`,t.map(e=>({label:e.name,action:e.action??``}))))}function Z(e,t){let n=E(t);return n?`--${e} ${JSON.stringify(n)}`:void 0}function sc(e,t){return O(t)?`--${e}`:void 0}function cc(e){return O(e)===!1?`--recover-links=false`:void 0}const lc=C({meta:{name:`monitoring`,description:`Run and review recurring technical SEO monitoring`},subCommands:{run:C({meta:{name:`run`,description:`Run crawl, index, and link recovery monitoring for a property`},args:tc(),run:async({args:e})=>{let t=N(e),n=await H({client:D(e),site:E(e.site),options:{json:t,refresh:O(e.refresh)}}),r=M(e.urls),i=M(e.sitemaps),a=E(e.url)??n.client?.startUrl??da(n.site),o=await Ye({site:n.site,startUrl:a,urls:r.length?r:n.client?.watchUrls,sitemaps:i.length?i:void 0,properties:M(e.properties).length?M(e.properties):void 0,limit:k(e.limit),languageCode:E(e.language),dailyLimit:k(e[`daily-limit`]),inspectLimit:k(e[`inspect-limit`]),maxUrls:k(e[`max-urls`]),refresh:O(e.refresh),js:O(e.js)?!0:`auto`,recoverLinks:O(e[`recover-links`]),recoverDays:k(e[`recover-days`]),recoverLimit:k(e[`recover-limit`]),recoverMinClicks:k(e[`recover-min-clicks`]),recoverMinImpressions:k(e[`recover-min-impressions`])});if(t){R(o);return}ac(o)}}),status:C({meta:{name:`status`,description:`Show the latest saved monitoring state for a property`},args:nc(),run:async({args:e})=>{let t=N(e),n=$t({site:(await H({client:D(e),site:E(e.site),options:{json:t,refresh:O(e.refresh)}})).site,staleAfterDays:k(e[`stale-days`])});if(t){R(n);return}oc(n)}}),cron:C({meta:{name:`cron`,description:`Print a local cron line for recurring monitoring`},args:{...tc(),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=N(e),n=await H({client:D(e),site:E(e.site),options:{json:t,refresh:O(e.refresh)}}),r=k(e.hour)??9,i=k(e.minute)??0,a=k(e.weekday)??n.client?.technicalWeekday??1,o=[`seo monitoring run`,n.client?`--project ${JSON.stringify(n.client.id)}`:`--site ${JSON.stringify(n.site)}`,Z(`url`,e.url),Z(`urls`,e.urls),Z(`sitemaps`,e.sitemaps),Z(`properties`,e.properties),Z(`limit`,e.limit),Z(`daily-limit`,e[`daily-limit`]),Z(`inspect-limit`,e[`inspect-limit`]),Z(`max-urls`,e[`max-urls`]),Z(`language`,e.language),Z(`recover-days`,e[`recover-days`]),Z(`recover-limit`,e[`recover-limit`]),Z(`recover-min-clicks`,e[`recover-min-clicks`]),Z(`recover-min-impressions`,e[`recover-min-impressions`]),cc(e[`recover-links`]),sc(`refresh`,e.refresh),sc(`js`,e.js),`--json`].filter(Boolean).join(` `),s={name:`monitoring`,cron:`${i} ${r} * * ${a}`,command:o};if(t){R({site:n.site,line:s});return}z([[`Property`,n.site],[`Install`,`crontab -e`]]),process.stdout.write(`${s.cron} ${s.command}\n`)}})}}),uc=C({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=N(e),n=await H({client:D(e),site:E(e.site),options:{json:t,refresh:O(e.refresh)}}),r=M(e.sitemaps),i=await Kt({site:n.site,crawlReportId:E(e[`crawl-report-id`]),sitemaps:r.length?r:void 0,days:k(e.days),rowLimit:k(e[`row-limit`]),maxSitemapUrls:k(e[`max-sitemap-urls`]),itemsPerSection:k(e.limit),refresh:O(e.refresh)});if(t){R(i);return}K({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(`
92
182
  Representative URL Inspection candidates
93
- `),z([`URL`,`Status`,`In sitemap`],i.crawlableWithoutRetainedSearchVisibility.items.map(e=>[K(e.url,80),e.status,e.inSitemap?`yes`:`no`]))),i.caveats.length){process.stdout.write(`
94
- How to read this report
95
- `);for(let e of i.caveats)process.stdout.write(`- ${e}\n`)}}}),ps=S({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=M(e),n=j(e.urls),r=await H({site:T(e.site),options:{json:t}}),i=j(e.sitemaps);if(D(e.plan)){if(!i.length)throw Error(`Pass at least one sitemap with --sitemaps.`);let n=await ke({site:r,sitemaps:i,properties:j(e.properties).length?j(e.properties):void 0,dailyLimit:O(e[`daily-limit`]),targetCycleDays:O(e[`target-days`]),maxUrls:O(e[`max-urls`])});if(t){L(n);return}if(R([[`Project site`,n.site],[`Sitemap URLs`,String(n.summary.urlCount)],[`Properties used`,String(n.summary.properties)],[`Daily capacity`,String(n.summary.dailyCapacity)],[`Estimated cycle`,ot(n.summary.estimatedCycleDays,`day`)],[`Target cycle`,ot(n.summary.targetCycleDays,`day`)],[`Suggested properties`,String(n.summary.suggestedProperties)]]),n.properties.length&&z([`Property`,`URLs`,`Cycle`,`Sample URL`],n.properties.map(e=>[e.property,e.urlCount,ot(e.cycleDays,`day`),e.sampleUrls[0]??`-`])),n.suggestions.length&&(process.stdout.write(`
183
+ `),B([`URL`,`Status`,`In sitemap`],i.crawlableWithoutRetainedSearchVisibility.items.map(e=>[G(e.url,80),e.status,e.inSitemap?`yes`:`no`]))),Y(`How to read this report`,i.caveats)}}),dc=C({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=N(e),n=M(e.urls),r=await Vi({site:E(e.site),options:{json:t}}),i=M(e.sitemaps);if(O(e.plan)){if(!i.length)throw Error(`Pass at least one sitemap with --sitemaps.`);let n=await _t({site:r,sitemaps:i,properties:M(e.properties).length?M(e.properties):void 0,dailyLimit:k(e[`daily-limit`]),targetCycleDays:k(e[`target-days`]),maxUrls:k(e[`max-urls`])});if(t){R(n);return}K({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 ${me(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:me(n.summary.targetCycleDays,`day`)},{label:`Suggested properties`,value:n.summary.suggestedProperties}]}),n.properties.length&&B([`Property`,`URLs`,`Cycle`,`Sample URL`],n.properties.map(e=>[e.property,e.urlCount,me(e.cycleDays,`day`),e.sampleUrls[0]??`-`])),n.suggestions.length&&(process.stdout.write(`
96
184
  Suggested URL-prefix properties
97
- `),z([`Property`,`URLs`,`Current property`,`Cycle`,`Reason`],n.suggestions.map(e=>[e.property,e.urlCount,e.currentProperty,ot(e.estimatedCycleDays,`day`),e.reason]))),n.warnings.length){process.stdout.write(`
98
- Warnings
99
- `);for(let e of n.warnings.slice(0,10))process.stdout.write(`- ${e}\n`)}return}if(i.length){let n=await h({site:r,sitemaps:i,properties:j(e.properties).length?j(e.properties):void 0,dailyLimit:O(e[`daily-limit`]),inspectLimit:O(e[`inspect-limit`]),maxUrls:O(e[`max-urls`]),languageCode:T(e.language)});if(t){L(n);return}R([[`Project site`,n.site],[`Discovered sitemap URLs`,String(n.source.discoveredUrls)],[`Invalid sitemap URLs`,String(n.source.invalidUrls)],[`Inventory URLs`,String(n.summary.inventoryUrls)],[`Properties used`,String(n.summary.properties)],[`Daily capacity`,String(n.summary.dailyCapacity)],[`Never attempted`,String(n.summary.neverAttempted)],[`Never succeeded`,String(n.summary.neverSucceeded)],[`Retry waiting`,String(n.summary.retryWaiting)],[`Fresh`,String(n.summary.fresh)],[`Stale`,String(n.summary.stale)],[`Due`,String(n.summary.due)],[`Selected this run`,String(n.summary.selected)],[`Due but not selected`,String(n.summary.unselectedDue)],[`Attempted`,String(n.summary.attempted)],[`Inspected`,String(n.summary.inspected)],[`Failed`,String(n.summary.failed)],[`Quota blocked`,String(n.summary.quotaBlocked)],[`Deferred`,String(n.summary.deferred)],[`Current reviews`,String(n.summary.currentIssues)],[`Regressions`,String(n.summary.regressions)],[`Recoveries`,String(n.summary.recoveries)],[`Changed`,String(n.summary.changed)],[`New alerts`,String(n.summary.alerts)],[`Skipped`,String(n.summary.skipped)]]),n.properties.length&&z([`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);if(a.length&&(process.stdout.write(`
185
+ `),B([`Property`,`URLs`,`Current property`,`Cycle`,`Reason`],n.suggestions.map(e=>[e.property,e.urlCount,e.currentProperty,me(e.estimatedCycleDays,`day`),e.reason]))),Y(`Warnings`,n.warnings.slice(0,10));return}if(i.length){let n=await on({site:r,sitemaps:i,properties:M(e.properties).length?M(e.properties):void 0,dailyLimit:k(e[`daily-limit`]),inspectLimit:k(e[`inspect-limit`]),maxUrls:k(e[`max-urls`]),languageCode:E(e.language)});if(t){R(n);return}K({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&&B([`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(`
100
186
  Index reviews, changes, and failed checks
101
- `),z([`Check`,`Change`,`Index state`,`Evidence`,`URL`],a.map(e=>[e.inspectionStatus,e.changeKind,e.indexStatus,(e.errorCode??e.issueCodes.join(`, `))||`none`,e.url]))),n.warnings.length){process.stdout.write(`
102
- Warnings
103
- `);for(let e of n.warnings.slice(0,10))process.stdout.write(`- ${e}\n`)}return}if(!n.length)throw Error(`Pass at least one URL with --urls.`);let a=await Te({site:r,urls:n,languageCode:T(e.language),dailyLimit:O(e[`daily-limit`])});if(t){L(a);return}R([[`Project site`,a.site],[`Inspection property`,a.source.property],[`Attempted`,String(a.summary.attempted)],[`Inspected`,String(a.summary.inspected)],[`Failed`,String(a.summary.failed)],[`Quota blocked`,String(a.summary.quotaBlocked)],[`Deferred`,String(a.summary.deferred)],[`Current reviews`,String(a.summary.currentIssues)],[`Regressions`,String(a.summary.regressions)],[`Recoveries`,String(a.summary.recoveries)],[`Changed`,String(a.summary.changed)],[`New alerts`,String(a.summary.alerts)]]),z([`Check`,`Change`,`Index state`,`Evidence`,`URL`],a.items.map(e=>[e.inspectionStatus,e.changeKind,e.indexStatus,(e.errorCode??e.issueCodes.join(`, `))||`none`,e.url]))}}),ms=S({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.`},...X([`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=M(e),n=await i({site:await bi({client:E(e),site:T(e.site)},{json:t}),days:O(e.days),limit:O(e.limit),minClicks:O(e[`min-clicks`]),minImpressions:O(e[`min-impressions`]),refresh:D(e.refresh),js:D(e.js)?!0:`auto`});if(t){L(n);return}if(R([[`Site`,n.site],[`Range`,`${n.range.startDate} to ${n.range.endDate}`],[`Checked`,W(n.summary.checked)],[`Recoverable`,W(n.summary.recoverable)],[`High severity`,W(n.summary.high)],[`Clicks at risk`,W(n.summary.clicksAtRisk)],[`Impressions at risk`,W(n.summary.impressionsAtRisk)]]),n.items.length&&(q([`Severity`,`Issue`,`Clicks`,`Impr`,`URL`,`Action`],n.items.map(e=>[e.severity,e.issue,W(e.clicks),W(e.impressions),K(e.url,56),K(e.recommendation.action,72)])),J(`Top recovery actions`,n.items.map(e=>({label:K(e.url,96),context:`${e.severity}, ${W(e.clicks)} clicks at risk`,action:e.recommendation.action})))),n.warnings.length){process.stdout.write(`
104
- Warnings
105
- `);for(let e of n.warnings.slice(0,10))process.stdout.write(`- ${e}\n`)}}}),hs=S({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 De({url:T(e.url)??``,maxHops:O(e[`max-hops`]),refresh:D(e.refresh),js:D(e.js)?!0:`auto`});if(M(e)){L(t);return}if(R([[`URL`,t.url],[`Final URL`,t.finalUrl],[`Final status`,String(t.summary.finalStatus)],[`Indexable`,t.summary.finalIndexable===void 0?`unknown`:t.summary.finalIndexable?`yes`:`no`],[`Hops`,String(t.summary.hops)],[`Issues`,t.summary.issues.length?t.summary.issues.join(`, `):`none`]]),z([`#`,`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
+ `),B([`Check`,`Change`,`Index state`,`Evidence`,`URL`],a.map(e=>[e.inspectionStatus,e.changeKind,e.indexStatus,(e.errorCode??e.issueCodes.join(`, `))||`none`,e.url]))),Y(`Warnings`,n.warnings.slice(0,10));return}if(!n.length)throw Error(`Pass at least one URL with --urls.`);let a=await ft({site:r,urls:n,languageCode:E(e.language),dailyLimit:k(e[`daily-limit`])});if(t){R(a);return}K({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}]}),B([`Check`,`Change`,`Index state`,`Evidence`,`URL`],a.items.map(e=>[e.inspectionStatus,e.changeKind,e.indexStatus,(e.errorCode??e.issueCodes.join(`, `))||`none`,e.url]))}}),fc=C({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.`},...X([`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=N(e),n=await lt({site:await ua({client:D(e),site:E(e.site)},{json:t}),days:k(e.days),limit:k(e.limit),minClicks:k(e[`min-clicks`]),minImpressions:k(e[`min-impressions`]),refresh:O(e.refresh),js:O(e.js)?!0:`auto`});if(t){R(n);return}K({title:`Link recovery`,target:n.site,status:n.warnings.length>0?`unknown`:n.summary.recoverable>0?`warning`:`pass`,summary:n.summary.recoverable>0?`${U(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:U(n.summary.checked)},{label:`Recoverable`,value:U(n.summary.recoverable)},{label:`High severity`,value:U(n.summary.high)},{label:`Clicks at risk`,value:U(n.summary.clicksAtRisk)},{label:`Impressions at risk`,value:U(n.summary.impressionsAtRisk)}]}),n.items.length&&(q([`Severity`,`Issue`,`Clicks`,`Impr`,`URL`,`Action`],n.items.map(e=>[e.severity,e.issue,U(e.clicks),U(e.impressions),G(e.url,56),G(e.recommendation.action,72)])),J(`Top recovery actions`,n.items.map(e=>({label:G(e.url,96),context:`${e.severity}, ${U(e.clicks)} clicks at risk`,action:e.recommendation.action})))),Y(`Warnings`,n.warnings.slice(0,10))}}),pc=C({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 Ft({url:E(e.url)??``,maxHops:k(e[`max-hops`]),refresh:O(e.refresh),js:O(e.js)?!0:`auto`});if(N(e)){R(t);return}K({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}]}),B([`#`,`Status`,`URL`,`Next`],t.chain.map((e,t)=>[t+1,e.status,G(e.url,72),G(e.nextUrl??`-`,72)])),t.finalPage&&(process.stdout.write(`
106
188
  Final page
107
- `),R([[`Title`,t.finalPage.title??`-`],[`Canonical`,t.finalPage.canonical??`-`],[`Meta robots`,t.finalPage.metaRobots??`-`]])),t.warnings.length){process.stdout.write(`
108
- Warnings
109
- `);for(let e of t.warnings.slice(0,10))process.stdout.write(`- ${e}\n`)}}});function gs(e,t){process.stdout.write(`OKF validation for ${e}\n\n`),R([[`Valid`,t.valid?`yes`:`no`],[`Files`,String(t.files)],[`Concepts`,String(t.concepts)],[`Errors`,String(t.issues.filter(e=>e.severity===`error`).length)],[`Warnings`,String(t.issues.filter(e=>e.severity===`warning`).length)]]),t.issues.length&&(process.stdout.write(`
189
+ `),z([[`Title`,t.finalPage.title??`-`],[`Canonical`,t.finalPage.canonical??`-`],[`Meta robots`,t.finalPage.metaRobots??`-`]])),Y(`Warnings`,t.warnings.slice(0,10))}});function mc(e,t){process.stdout.write(`OKF validation for ${e}\n\n`),z([[`Valid`,t.valid?`yes`:`no`],[`Files`,String(t.files)],[`Concepts`,String(t.concepts)],[`Errors`,String(t.issues.filter(e=>e.severity===`error`).length)],[`Warnings`,String(t.issues.filter(e=>e.severity===`warning`).length)]]),t.issues.length&&(process.stdout.write(`
110
190
  Issues
111
- `),z([`Severity`,`Path`,`Message`],t.issues.slice(0,25).map(e=>[e.severity,e.path,e.message])))}const _s=S({meta:{name:`okf`,description:`Export, validate, and explain OKF site knowledge bundles`},subCommands:{export:S({meta:{name:`export`,description:`Export a saved crawl as an OKF bundle`},args:{"report-id":{type:`string`,description:`Saved crawl report id to export.`},site:{type:`string`,description:`GSC property URL for selecting the latest saved crawl.`},client:{type:`string`,description:`Legacy alias for --project.`},project:{type:`string`,description:`Saved project id or name.`},output:{type:`string`,description:`Output directory. Defaults to ./okf.`},"max-concepts":{type:`string`,description:`Maximum concept files to export. Defaults to 500.`},title:{type:`string`,description:`Override the root bundle title.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=M(e),n=Dt(k(e[`max-concepts`],`--max-concepts`)),r=Ye(await ai(e,{json:t}),{maxConcepts:n,title:T(e.title)}),i=T(e.output)??`./okf`,a=await Lo(i,r.files);if(t){L({output:i,bundle:r,validation:a});return}process.stdout.write(`Wrote OKF bundle to ${i}\n\n`),R([[`Files`,String(r.files.length)],[`Concepts`,String(r.conceptCount)],[`Valid`,a.valid?`yes`:`no`]])}}),validate:S({meta:{name:`validate`,description:`Validate an OKF bundle directory`},args:{path:{type:`positional`,required:!0,description:`OKF bundle directory.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=T(e.path);if(!t)throw Error(`Pass an OKF bundle directory.`);let n=yt(await Fo(t));if(n.valid||(process.exitCode=1),M(e)){L(n);return}gs(t,n)}}),explain:S({meta:{name:`explain`,description:`Explain OKF validation results in plain English`},args:{path:{type:`positional`,required:!0,description:`OKF bundle directory.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=T(e.path);if(!t)throw Error(`Pass an OKF bundle directory.`);let n=et(yt(await Fo(t)));if(n.valid||(process.exitCode=1),M(e)){L(n);return}if(process.stdout.write(`${n.summary}\n\n`),R([[`Valid`,n.valid?`yes`:`no`],[`Files`,String(n.files)],[`Concepts`,String(n.concepts)],[`Errors`,String(n.errors)],[`Warnings`,String(n.warnings)]]),n.nextActions.length){process.stdout.write(`
191
+ `),B([`Severity`,`Path`,`Message`],t.issues.slice(0,25).map(e=>[e.severity,e.path,e.message])))}const hc=C({meta:{name:`okf`,description:`Export, validate, and explain OKF site knowledge bundles`},subCommands:{export:C({meta:{name:`export`,description:`Export a saved crawl as an OKF bundle`},args:{"report-id":{type:`string`,description:`Saved crawl report id to export.`},site:{type:`string`,description:`GSC property URL for selecting the latest saved crawl.`},client:{type:`string`,description:`Legacy alias for --project.`},project:{type:`string`,description:`Saved project id or name.`},output:{type:`string`,description:`Output directory. Defaults to ./okf.`},"max-concepts":{type:`string`,description:`Maximum concept files to export. Defaults to 500.`},title:{type:`string`,description:`Override the root bundle title.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=N(e),n=ne(A(e[`max-concepts`],`--max-concepts`)),r=be(await Wi(e,{json:t}),{maxConcepts:n,title:E(e.title)}),i=E(e.output)??`./okf`,a=await ks(i,r.files);if(t){R({output:i,bundle:r,validation:a});return}process.stdout.write(`Wrote OKF bundle to ${i}\n\n`),z([[`Files`,String(r.files.length)],[`Concepts`,String(r.conceptCount)],[`Valid`,a.valid?`yes`:`no`]])}}),validate:C({meta:{name:`validate`,description:`Validate an OKF bundle directory`},args:{path:{type:`positional`,required:!0,description:`OKF bundle directory.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=E(e.path);if(!t)throw Error(`Pass an OKF bundle directory.`);let n=se(await Ds(t));if(n.valid||(process.exitCode=1),N(e)){R(n);return}mc(t,n)}}),explain:C({meta:{name:`explain`,description:`Explain OKF validation results in plain English`},args:{path:{type:`positional`,required:!0,description:`OKF bundle directory.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=E(e.path);if(!t)throw Error(`Pass an OKF bundle directory.`);let n=Ee(se(await Ds(t)));if(n.valid||(process.exitCode=1),N(e)){R(n);return}if(process.stdout.write(`${n.summary}\n\n`),z([[`Valid`,n.valid?`yes`:`no`],[`Files`,String(n.files)],[`Concepts`,String(n.concepts)],[`Errors`,String(n.errors)],[`Warnings`,String(n.warnings)]]),n.nextActions.length){process.stdout.write(`
112
192
  Next actions
113
- `);for(let e of n.nextActions)process.stdout.write(`- ${e}\n`)}}})}}),vs=S({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=M(e),n=await ei({client:E(e),options:{json:t}}),r=await Xe({property:await ti({property:T(e.property)??n?.analytics.google?.propertyId,options:{json:t}}),startDate:T(e[`start-date`]),endDate:T(e[`end-date`]),maxRows:k(e[`max-rows`],`--max-rows`),resultLimit:k(e[`result-limit`],`--result-limit`),limit:k(e.limit,`--limit`),refresh:e.refresh===!0});if(t){L(r);return}if(R([[`Property`,r.property],[`Evidence`,r.dataStatus===`complete`?`Complete`:`Partial`],[`Verdict`,r.summary.verdict],[`AI sessions`,W(r.summary.sessions)],[`AI users`,r.summary.totalUsers===null?`Unavailable`:W(r.summary.totalUsers)],[`Sources`,W(r.summary.sources)],[`Landing pages`,W(r.summary.landingPages)],[`Landing-page output`,`${W(r.selection.landingPages.returnedRows)} returned / ${W(r.selection.landingPages.retainedRows)} retained`],[`Source rows`,W(r.dataSource.sourceDiscovery.returnedRows)]]),r.sources.length&&(process.stdout.write(`
193
+ `);for(let e of n.nextActions)process.stdout.write(`- ${e}\n`)}}})}}),gc=C({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=N(e),n=await Hi({client:D(e),options:{json:t}}),r=await Ne({property:await Ui({property:E(e.property)??n?.analytics.google?.propertyId,options:{json:t}}),startDate:E(e[`start-date`]),endDate:E(e[`end-date`]),maxRows:A(e[`max-rows`],`--max-rows`),resultLimit:A(e[`result-limit`],`--result-limit`),limit:A(e.limit,`--limit`),refresh:e.refresh===!0});if(t){R(r);return}if(K({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:U(r.summary.sessions)},{label:`AI users`,value:r.summary.totalUsers===null?`Unavailable`:U(r.summary.totalUsers)},{label:`Sources`,value:U(r.summary.sources)},{label:`Landing pages`,value:U(r.summary.landingPages)},{label:`Landing-page output`,value:`${U(r.selection.landingPages.returnedRows)} returned / ${U(r.selection.landingPages.retainedRows)} retained`},{label:`Source rows`,value:U(r.dataSource.sourceDiscovery.returnedRows)}]}),r.sources.length&&(process.stdout.write(`
114
194
  AI sources
115
- `),q([`Source`,`Sessions`,`Events`,`Share of AI sessions`],r.sources.map(e=>[e.label,W(e.sessions),W(e.eventCount),G(e.shareOfAiSessions)]))),r.landingPages.length&&(process.stdout.write(`
195
+ `),q([`Source`,`Sessions`,`Events`,`Share of AI sessions`],r.sources.map(e=>[e.label,U(e.sessions),U(e.eventCount),W(e.shareOfAiSessions)]))),r.landingPages.length&&(process.stdout.write(`
116
196
  Landing pages
117
- `),q([`Landing page`,`Sessions`,`Events`,`Top source`],r.landingPages.map(e=>[K(e.landingPage,64),W(e.sessions),W(e.eventCount),e.topSourceDetails.label]))),r.dataSource.partialReasons.length){process.stdout.write(`
197
+ `),q([`Landing page`,`Sessions`,`Events`,`Top source`],r.landingPages.map(e=>[G(e.landingPage,64),U(e.sessions),U(e.eventCount),e.topSourceDetails.label]))),r.dataSource.partialReasons.length){process.stdout.write(`
118
198
  Evidence warnings
119
- `);for(let e of r.dataSource.partialReasons)process.stdout.write(`- ${e}\n`)}process.stdout.write(`\nNote: ${r.summary.caveat}\n`)}}),ys=S({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.`},...X([`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=M(e),n=D(e.refresh),r=await U({client:E(e),site:T(e.site),options:{json:t,refresh:n}}),i=await Ke({site:r.site,days:k(e.days,`--days`),limit:k(e.limit,`--limit`),minImpressions:k(e[`min-impressions`],`--min-impressions`),brandTerms:[...r.client?.brandTerms??[],...A(e[`brand-terms`])??[]],includeBrand:D(e[`include-brand`]),refresh:n});if(t){L(i);return}R([[`Site`,i.site],[`Status`,i.dataStatus],[`Range`,`${i.range.startDate} to ${i.range.endDate}`],[`Eligible candidates`,W(i.summary.eligibleClusters)],[`Returned candidates`,W(i.summary.returnedClusters)],[`Suppressed brand queries`,W(i.summary.suppressedQueries)],[`GSC completeness`,i.source.completeness],[`Brand queries`,D(e[`include-brand`])?`included`:`excluded`],[`Verdict`,i.summary.verdict]]),q([`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)])),J(`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}))),Y(`Report caveats`,i.caveats),Y(`Provider usage`,[i.ledgerSummary])}}),bs=S({meta:{name:`community-intent`,description:`Find GSC queries with forum, review, and comparison intent`},args:{site:{type:`string`},project:{type:`string`,description:`Saved project id or name.`},client:{type:`string`,description:`Legacy alias for --project.`},"start-date":{type:`string`,description:`Exact GSC start date (YYYY-MM-DD). Use with --end-date.`},"end-date":{type:`string`,description:`Exact GSC end date (YYYY-MM-DD). Use with --start-date.`},"max-rows":{type:`string`,description:`Maximum retained GSC query rows. Defaults to 50000.`},"brand-terms":{type:`string`,description:`Comma-separated brand terms to exclude.`},...X([`days`,`limit`,`minImpressions`,`includeBrand`,`refresh`],{days:{description:`GSC lookback within the current 16-month retention.`},limit:{description:`Maximum intent queries to print. Defaults to 25.`},minImpressions:{description:`Minimum query impressions. Defaults to 20.`},includeBrand:{description:`Include branded queries in community-intent reports.`},refresh:{description:`Bypass local GSC cache.`}}),json:{type:`boolean`,default:!1}},run:async({args:e})=>{let t=M(e),n=await U({client:E(e),site:T(e.site),options:{json:t,refresh:D(e.refresh)}}),r=await ae({site:n.site,days:k(e.days,`--days`),startDate:T(e[`start-date`]),endDate:T(e[`end-date`]),limit:k(e.limit,`--limit`),minImpressions:k(e[`min-impressions`],`--min-impressions`),maxRows:k(e[`max-rows`],`--max-rows`),brandTerms:[...n.client?.brandTerms??[],...A(e[`brand-terms`])??[]],includeBrand:D(e[`include-brand`]),refresh:D(e.refresh)});if(t){L(r);return}R([[`Property`,r.site],[`Status`,r.dataStatus],[`Verdict`,r.summary.verdict],[`Classified queries`,W(r.summary.classifiedQueries)],[`Returned queries`,W(r.summary.returnedQueries)],[`Returned impressions`,W(r.summary.returnedImpressions)],[`Returned clicks`,W(r.summary.returnedClicks)],[`GSC completeness`,r.source.completeness]]),q([`Intent`,`Query`,`Impr`,`Clicks`,`Confidence`,`Action`],r.items.map(e=>[e.intent,K(e.query,42),W(e.impressions),W(e.clicks),e.confidence,K(e.action,76)])),J(`Top community-intent actions`,r.items.map(e=>({label:e.query,context:`${e.intent}, ${W(e.impressions)} impressions`,action:e.action}))),Y(`Report caveats`,r.caveats),Y(`Warnings`,r.warnings)}}),xs=S({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.`},...X([`includeBrand`,`minImpressions`],{minImpressions:{description:`Minimum query impressions. Defaults to 200.`}}),json:{type:`boolean`,default:!1}},run:async({args:e})=>{let t=M(e),n=await U({client:E(e),site:T(e.site),options:{json:t}}),r=await Bt({site:n.site,minImpressions:O(e[`min-impressions`]),brandTerms:n.client?.brandTerms,includeBrand:D(e[`include-brand`])});if(t){L(r);return}R([[`Site`,r.site],[`Underperformers`,W(r.summary.underperformers)],[`Estimated click gap`,W(r.summary.estimatedClickShortfall)],[`Brand queries`,r.summary.brandFiltering],[`Verdict`,r.summary.verdict]]),Y(`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.`]),Y(`Recommended actions`,r.recommendations),Y(`Report caveats`,r.caveats),q([`Query`,`URL`,`Pos`,`Impr`,`CTR`,`Expected`,`Gap`,`Action`],r.items.map(e=>[K(e.query,36),K(e.url,48),ni(e.position),W(e.impressions),G(e.actualCtr),G(e.expectedCtr),W(e.clickShortfall),K(e.recommendation.action,72)])),J(`Top CTR actions`,r.items.map(e=>({label:e.query,context:`${W(e.clickShortfall)} click gap, ${G(e.actualCtr)} CTR`,action:e.recommendation.action})))}});function Ss(e){return e>0?ni(e):`n/a`}const Cs=S({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.`},...X([`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=M(e),n=D(e.refresh),r=T(e.comparison),i=k(e.days,`--days`),a=k(e.limit,`--limit`),o=k(e[`min-drop-pct`],`--min-drop-pct`),s=k(e[`min-previous-clicks`],`--min-previous-clicks`),c=k(e[`min-click-loss`],`--min-click-loss`);if(r!==void 0&&r!==`previous-period`&&r!==`year-over-year`)throw new v(`INVALID_INPUT`,`--comparison must be previous-period or year-over-year.`);let l=await U({client:E(e),site:T(e.site),options:{json:t,refresh:n}}),u=await Pt({site:l.site,days:i,limit:a,comparison:r,brandTerms:[...l.client?.brandTerms??[],...A(e[`brand-terms`])??[]],includeBrand:D(e[`include-brand`]),minDropPct:o,minPreviousClicks:s,minClickLoss:c,refresh:n});if(t){L(u);return}R([[`Site`,u.site],[`Status`,u.dataStatus],[`Comparison`,u.comparison],[`Observed query/page declines`,W(u.summary.returnedRows)],[`Eligible declines`,W(u.summary.eligibleRows)],[`Decay clusters`,W(u.summary.groups)],[`Eligible observed click decline`,W(u.summary.observedRetainedQueryClickLoss)],[`Returned observed click decline`,W(u.summary.returnedObservedRetainedQueryClickLoss)],[`Brand queries`,u.summary.brandFiltering],[`Min drop`,`${u.filters.minDropPct}%`],[`Min previous clicks`,W(u.filters.minPreviousClicks)],[`GSC completeness`,u.source.completeness],[`Verdict`,u.summary.verdict]]),Y(`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.`]),Y(`Recommended actions`,u.recommendations),Y(`Report caveats`,u.caveats),Y(`Warnings`,u.warnings),u.items.length&&(u.groups.length&&(q([`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)])),J(`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})))),q([`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)}`,`${Ss(e.previous.position)} -> ${Ss(e.current.position)}`,K(e.recommendation.action,64)])),J(`Top decay actions`,u.items.map(e=>({label:e.query,context:`${e.template.label}, ${W(e.clickLoss)} fewer observed clicks`,action:e.recommendation.action}))))}}),ws=S({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.`},...X([`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=M(e),n=D(e.refresh),r=k(e.days,`--days`),i=k(e.limit,`--limit`),a=k(e[`check-limit`],`--check-limit`),o=k(e[`min-impressions`],`--min-impressions`),s=hr(e),c=await U({client:E(e),site:T(e.site),options:{json:t,refresh:n}}),l=await Ot({site:c.site,targetUrl:T(e.url)??``,days:r,limit:i,checkLimit:a,minImpressions:o,brandTerms:[...c.client?.brandTerms??[],...A(e[`brand-terms`])??[]],includeBrand:D(e[`include-brand`]),js:D(e.js)?!0:void 0,rate:s,refresh:n});if(t){L(l);return}R([[`Site`,l.site],[`Target`,l.targetUrl],[`Preferred target`,l.target.preferredUrl],[`Target state`,l.target.verification],[`Target queries`,W(l.summary.targetQueries)],[`Matched sources`,W(l.summary.candidateSources)],[`Attempted sources`,W(l.summary.attemptedSources)],[`Checked sources`,W(l.summary.checkedSources)],[`Review candidates`,W(l.summary.returnedSources)],[`Existing contextual links`,W(l.summary.existingLinksObserved)],[`Technical exclusions`,W(l.summary.technicalExclusions)],[`Failed checks`,W(l.summary.failedChecks)],[`Unchecked sources`,W(l.summary.uncheckedCandidates)],[`GSC completeness`,l.source.completeness],[`Brand queries`,l.summary.brandFiltering],[`Verdict`,l.summary.verdict]]),Y(`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.`]),Y(`Recommended actions`,l.recommendations),Y(`Report caveats`,l.caveats),l.items.length&&(q([`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])),J(`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}))),Y(`Provider usage`,[l.ledgerSummary]))}}),Ts=S({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},...X([`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":w(`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=M(e),n=await U({client:E(e),site:T(e.site),options:{json:t,refresh:D(e.refresh)}}),r=await ee({site:n.site,url:T(e.url)??``,days:O(e.days),limit:O(e.limit),minImpressions:O(e[`min-impressions`]),brandTerms:n.client?.brandTerms,includeBrand:D(e[`include-brand`]),verifyContent:D(e[`verify-content`])!==!1,js:D(e.js)?!0:`auto`,refresh:D(e.refresh)});if(t){L(r);return}if(R([[`Property`,r.site],[`URL`,r.url],[`Title`,r.page?.title??`not fetched`],[`Fetch`,wi(r.page?.fetchDiagnostics)],[`Verification`,r.verification.status],[`Rows`,`${W(r.selection.returnedRows)} returned / ${W(r.selection.eligibleRows)} eligible / ${W(r.selection.sourceRows)} source`],[`Queries`,W(r.summary.queries)],[`Impressions`,W(r.summary.impressions)],[`Opportunities`,W(r.summary.opportunities)],[`CTR shortfall`,W(r.summary.estimatedCtrClickShortfall)],[`Focus`,r.summary.focus],[`Verdict`,r.summary.verdict]]),Y(`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.`]),Y(`Recommended actions`,r.recommendations),Y(`Report caveats`,r.caveats),!r.items.length){process.stdout.write(r.dataStatus===`empty`?`No GSC query rows found for this exact URL.
199
+ `);for(let e of r.dataSource.partialReasons)process.stdout.write(`- ${e}\n`)}process.stdout.write(`\nNote: ${r.summary.caveat}\n`)}}),_c=C({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.`},...X([`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=N(e),n=O(e.refresh),r=await H({client:D(e),site:E(e.site),options:{json:t,refresh:n}}),a=await i({site:r.site,days:A(e.days,`--days`),limit:A(e.limit,`--limit`),minImpressions:A(e[`min-impressions`],`--min-impressions`),brandTerms:[...r.client?.brandTerms??[],...j(e[`brand-terms`])??[]],includeBrand:O(e[`include-brand`]),refresh:n});if(t){R(a);return}K({title:`Cannibalisation report`,target:a.site,status:a.dataStatus===`complete`?`info`:`unknown`,summary:a.summary.verdict,metrics:[{label:`Evidence`,value:a.dataStatus},{label:`Range`,value:`${a.range.startDate} to ${a.range.endDate}`},{label:`Eligible`,value:U(a.summary.eligibleClusters)},{label:`Returned`,value:U(a.summary.returnedClusters)},{label:`Suppressed brand queries`,value:U(a.summary.suppressedQueries)},{label:`GSC completeness`,value:a.source.completeness},{label:`Brand queries`,value:O(e[`include-brand`])?`included`:`excluded`}]}),q([`Query`,`URLs`,`Property impr`,`Exposure`,`HHI`,`Review first`],a.items.map(e=>[G(e.query,42),e.pages.length,e.propertyImpressions===void 0?`n/a`:U(e.propertyImpressions),U(e.pageExposureImpressions),e.hhi.toFixed(2),G(e.suggestedOwnerUrl,56)])),J(`Top cannibalisation actions`,a.items.map(e=>({label:e.query,context:`${e.pages.length} material URLs; first review ${G(e.suggestedOwnerUrl,64)}`,action:e.recommendation.action}))),Y(`Report caveats`,a.caveats),Y(`Provider usage`,[a.ledgerSummary])}}),vc=C({meta:{name:`community-intent`,description:`Find GSC queries with forum, review, and comparison intent`},args:{site:{type:`string`},project:{type:`string`,description:`Saved project id or name.`},client:{type:`string`,description:`Legacy alias for --project.`},"start-date":{type:`string`,description:`Exact GSC start date (YYYY-MM-DD). Use with --end-date.`},"end-date":{type:`string`,description:`Exact GSC end date (YYYY-MM-DD). Use with --start-date.`},"max-rows":{type:`string`,description:`Maximum retained GSC query rows. Defaults to 50000.`},"brand-terms":{type:`string`,description:`Comma-separated brand terms to exclude.`},...X([`days`,`limit`,`minImpressions`,`includeBrand`,`refresh`],{days:{description:`GSC lookback within the current 16-month retention.`},limit:{description:`Maximum intent queries to print. Defaults to 25.`},minImpressions:{description:`Minimum query impressions. Defaults to 20.`},includeBrand:{description:`Include branded queries in community-intent reports.`},refresh:{description:`Bypass local GSC cache.`}}),json:{type:`boolean`,default:!1}},run:async({args:e})=>{let t=N(e),n=await H({client:D(e),site:E(e.site),options:{json:t,refresh:O(e.refresh)}}),r=await Lt({site:n.site,days:A(e.days,`--days`),startDate:E(e[`start-date`]),endDate:E(e[`end-date`]),limit:A(e.limit,`--limit`),minImpressions:A(e[`min-impressions`],`--min-impressions`),maxRows:A(e[`max-rows`],`--max-rows`),brandTerms:[...n.client?.brandTerms??[],...j(e[`brand-terms`])??[]],includeBrand:O(e[`include-brand`]),refresh:O(e.refresh)});if(t){R(r);return}K({title:`Community intent report`,target:r.site,status:r.dataStatus===`available`||r.dataStatus===`empty`?`info`:`unknown`,summary:r.summary.verdict,metrics:[{label:`Evidence`,value:r.dataStatus},{label:`Classified queries`,value:U(r.summary.classifiedQueries)},{label:`Returned queries`,value:U(r.summary.returnedQueries)},{label:`Impressions`,value:U(r.summary.returnedImpressions)},{label:`Clicks`,value:U(r.summary.returnedClicks)},{label:`GSC completeness`,value:r.source.completeness}]}),q([`Intent`,`Query`,`Impr`,`Clicks`,`Confidence`,`Action`],r.items.map(e=>[e.intent,G(e.query,42),U(e.impressions),U(e.clicks),e.confidence,G(e.action,76)])),J(`Top community-intent actions`,r.items.map(e=>({label:e.query,context:`${e.intent}, ${U(e.impressions)} impressions`,action:e.action}))),Y(`Report caveats`,r.caveats),Y(`Warnings`,r.warnings)}}),yc=C({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.`},...X([`includeBrand`,`minImpressions`],{minImpressions:{description:`Minimum query impressions. Defaults to 200.`}}),json:{type:`boolean`,default:!1}},run:async({args:e})=>{let t=N(e),n=await H({client:D(e),site:E(e.site),options:{json:t}}),r=await _e({site:n.site,minImpressions:k(e[`min-impressions`]),brandTerms:n.client?.brandTerms,includeBrand:O(e[`include-brand`])});if(t){R(r);return}K({title:`CTR underperformers`,target:r.site,status:r.dataStatus===`partial`?`unknown`:`info`,summary:r.summary.verdict,metrics:[{label:`Underperformers`,value:U(r.summary.underperformers)},{label:`Estimated click gap`,value:U(r.summary.estimatedClickShortfall)},{label:`Brand queries`,value:r.summary.brandFiltering},{label:`Evidence`,value:r.dataStatus},{label:`GSC completeness`,value:r.source.completeness}]}),Y(`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.`]),Y(`Recommended actions`,r.recommendations),Y(`Report caveats`,r.caveats),q([`Query`,`URL`,`Pos`,`Impr`,`CTR`,`Expected`,`Gap`,`Action`],r.items.map(e=>[G(e.query,36),G(e.url,48),Zi(e.position),U(e.impressions),W(e.actualCtr),W(e.expectedCtr),U(e.clickShortfall),G(e.recommendation.action,72)])),J(`Top CTR actions`,r.items.map(e=>({label:e.query,context:`${U(e.clickShortfall)} click gap, ${W(e.actualCtr)} CTR`,action:e.recommendation.action})))}});function bc(e){return e>0?Zi(e):`n/a`}const xc=C({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.`},...X([`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=N(e),n=O(e.refresh),r=E(e.comparison),i=A(e.days,`--days`),a=A(e.limit,`--limit`),o=A(e[`min-drop-pct`],`--min-drop-pct`),s=A(e[`min-previous-clicks`],`--min-previous-clicks`),c=A(e[`min-click-loss`],`--min-click-loss`);if(r!==void 0&&r!==`previous-period`&&r!==`year-over-year`)throw new y(`INVALID_INPUT`,`--comparison must be previous-period or year-over-year.`);let l=await H({client:D(e),site:E(e.site),options:{json:t,refresh:n}}),u=await Mt({site:l.site,days:i,limit:a,comparison:r,brandTerms:[...l.client?.brandTerms??[],...j(e[`brand-terms`])??[]],includeBrand:O(e[`include-brand`]),minDropPct:o,minPreviousClicks:s,minClickLoss:c,refresh:n});if(t){R(u);return}K({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:U(u.summary.returnedRows)},{label:`Eligible declines`,value:U(u.summary.eligibleRows)},{label:`Decay clusters`,value:U(u.summary.groups)},{label:`Observed click decline`,value:U(u.summary.returnedObservedRetainedQueryClickLoss)},{label:`Eligible click decline`,value:U(u.summary.observedRetainedQueryClickLoss)},{label:`Brand queries`,value:u.summary.brandFiltering},{label:`Minimum drop`,value:`${u.filters.minDropPct}%`},{label:`Minimum previous clicks`,value:U(u.filters.minPreviousClicks)},{label:`GSC completeness`,value:u.source.completeness}]}),Y(`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.`]),Y(`Recommended actions`,u.recommendations),Y(`Report caveats`,u.caveats),Y(`Warnings`,u.warnings),u.items.length&&(u.groups.length&&(q([`Cluster`,`Rows`,`Observed loss`,`Drop`,`Sample query`,`Action`],u.groups.map(e=>[G(e.label,42),U(e.count),U(e.totalClickLoss),`${e.averageDropPct.toFixed(1)}%`,G(e.sampleQueries[0]??`-`,40),G(e.recommendation,72)])),J(`Top decay cluster actions`,u.groups.map(e=>({label:e.label,context:`${U(e.count)} retained rows, ${U(e.totalClickLoss)} observed click decline`,action:e.recommendation})))),q([`Query`,`Template`,`URL`,`Signals`,`Decline`,`Clicks`,`Impr`,`CTR`,`Pos`,`Action`],u.items.map(e=>[G(e.query,32),G(e.template.label,26),G(e.url,44),e.signals.join(`, `),U(e.clickLoss),`${U(e.previous.clicks)} -> ${U(e.current.clicks)}`,`${U(e.previous.impressions)} -> ${U(e.current.impressions)}`,`${W(e.previous.ctr)} -> ${W(e.current.ctr)}`,`${bc(e.previous.position)} -> ${bc(e.current.position)}`,G(e.recommendation.action,64)])),J(`Top decay actions`,u.items.map(e=>({label:e.query,context:`${e.template.label}, ${U(e.clickLoss)} fewer observed clicks`,action:e.recommendation.action}))))}}),Sc=C({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.`},...X([`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=N(e),n=O(e.refresh),r=A(e.days,`--days`),i=A(e.limit,`--limit`),a=A(e[`check-limit`],`--check-limit`),o=A(e[`min-impressions`],`--min-impressions`),s=qr(e),c=await H({client:D(e),site:E(e.site),options:{json:t,refresh:n}}),l=await Ct({site:c.site,targetUrl:E(e.url)??``,days:r,limit:i,checkLimit:a,minImpressions:o,brandTerms:[...c.client?.brandTerms??[],...j(e[`brand-terms`])??[]],includeBrand:O(e[`include-brand`]),js:O(e.js)?!0:void 0,rate:s,refresh:n});if(t){R(l);return}K({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:U(l.summary.targetQueries)},{label:`Matched sources`,value:U(l.summary.candidateSources)},{label:`Checked sources`,value:U(l.summary.checkedSources)},{label:`Attempted sources`,value:U(l.summary.attemptedSources)},{label:`Review candidates`,value:U(l.summary.returnedSources)},{label:`Technical exclusions`,value:U(l.summary.technicalExclusions)},{label:`Existing links`,value:U(l.summary.existingLinksObserved)},{label:`Failed checks`,value:U(l.summary.failedChecks)},{label:`Unchecked sources`,value:U(l.summary.uncheckedCandidates)},{label:`GSC completeness`,value:l.source.completeness},{label:`Brand queries`,value:l.summary.brandFiltering}]}),Y(`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.`]),Y(`Recommended actions`,l.recommendations),Y(`Report caveats`,l.caveats),l.items.length&&(q([`Source URL`,`Matched impr`,`Match`,`Link evidence`,`Action`],l.items.map(e=>[G(e.sourceUrl,52),U(e.matchedQueryImpressions),e.bestMatchKind,e.linkEvidence.status,e.actionType])),J(`Top internal-link reviews`,l.items.map(e=>({label:G(e.sourceUrl,96),context:`${U(e.matchedQueryImpressions)} matched-query impressions; ${e.bestMatchKind}`,action:e.recommendation.action}))),Y(`Provider usage`,[l.ledgerSummary]))}}),Cc=C({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},...X([`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":Vr(`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=N(e),n=await H({client:D(e),site:E(e.site),options:{json:t,refresh:O(e.refresh)}}),r=await Gt({site:n.site,url:E(e.url)??``,days:k(e.days),limit:k(e.limit),minImpressions:k(e[`min-impressions`]),brandTerms:n.client?.brandTerms,includeBrand:O(e[`include-brand`]),verifyContent:O(e[`verify-content`])!==!1,js:O(e.js)?!0:`auto`,refresh:O(e.refresh)});if(t){R(r);return}if(K({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:ma(r.page?.fetchDiagnostics)},{label:`Verification`,value:r.verification.status},{label:`Rows`,value:`${U(r.selection.returnedRows)} returned / ${U(r.selection.eligibleRows)} eligible / ${U(r.selection.sourceRows)} source`},{label:`Queries`,value:U(r.summary.queries)},{label:`Impressions`,value:U(r.summary.impressions)},{label:`Opportunities`,value:U(r.summary.opportunities)},{label:`CTR shortfall`,value:U(r.summary.estimatedCtrClickShortfall)},{label:`Focus`,value:r.summary.focus},{label:`Evidence`,value:r.dataStatus}]}),Y(`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.`]),Y(`Recommended actions`,r.recommendations),Y(`Report caveats`,r.caveats),!r.items.length){process.stdout.write(r.dataStatus===`empty`?`No GSC query rows found for this exact URL.
120
200
  `:`GSC rows were found, but none met the report criteria.
121
- `);return}q([`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),G(e.ctr),e.expectedCtr===void 0?`n/a`:G(e.expectedCtr),W(e.impressions),e.estimatedCtrClickShortfall===void 0?`n/a`:W(e.estimatedCtrClickShortfall),K(e.recommendation,72)])),J(`Top page actions`,r.items.map(e=>({label:e.query,context:`${e.opportunityType}, ${W(e.impressions)} impressions`,action:e.recommendation})))}}),Es=S({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`},...X([`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=M(e),n=await U({client:E(e),site:T(e.site),options:{json:t}}),r=await f({site:n.site,scope:T(e.scope),brand:n.client?.brandTerms?.[0],brandTerms:n.client?.brandTerms,includeBrand:D(e[`include-brand`]),minImpressions:O(e[`min-impressions`]),limit:O(e.limit)});if(t){L(r);return}R([[`Site`,r.site],[`Scope`,r.scope??`all pages`],[`Clusters`,W(r.summary.clusters)],[`Queries`,W(r.summary.queries)],[`Impressions`,W(r.summary.impressions)],[`Clicks`,W(r.summary.clicks)],[`Minimum impressions`,W(r.summary.minImpressions)],[`Result limit`,W(r.summary.limit)],[`Brand queries`,r.summary.brandFiltering],[`High-opportunity clusters`,W(r.summary.highOpportunityClusters)],[`Verdict`,r.summary.verdict]]),Y(`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.`]),Y(`Recommended actions`,r.recommendations),Y(`Report caveats`,r.caveats),q([`Cluster`,`Intent`,`Queries`,`Impr`,`CTR`,`Expected`,`Lift`,`Action`],r.clusters.map(e=>[K(e.label,32),e.intent,e.queries.length,W(e.totals?.impressions??0),G(e.totals?.ctr??0),G(e.benchmark?.expectedCtr??0),e.estimatedClickLift===void 0?`-`:W(e.estimatedClickLift),K(e.recommendation??``,72)])),J(`Top cluster actions`,r.clusters.map(e=>({label:e.label,context:`${W(e.totals?.impressions??0)} impressions, ${e.estimatedClickLift===void 0?`ranking opportunity`:`${W(e.estimatedClickLift)} estimated click lift`}`,action:e.recommendation??``})))}}),Ds=S({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.`},...X([`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=M(e),n=O(e[`verify-limit`]),r=D(e[`verify-content`])===!0||n!==void 0,i=await U({client:E(e),site:T(e.site),options:{json:t}}),a=await wt({site:i.site,days:O(e.days),limit:O(e.limit),minImpressions:O(e[`min-impressions`]),brandTerms:[...i.client?.brandTerms??[],...A(e[`brand-terms`])??[]],includeBrand:D(e[`include-brand`]),verifyContent:r,verifyLimit:n,js:D(e.js)?!0:void 0,rate:mr(e),refresh:D(e.refresh)});if(t){L(a);return}R([[`Site`,a.site],[`Eligible rows`,W(a.summary.eligibleRows)],[`Returned rows`,W(a.summary.returnedRows)],[`Repeated-query clusters`,W(a.summary.repeatedQueryGroups)],[`Template patterns`,W(a.summary.templatePatterns)],[`CTR click shortfall`,W(a.summary.returnedEstimatedCtrClickShortfall)],[`Brand queries`,a.summary.brandFiltering],[`Verification`,ri(a)],[`Verdict`,a.summary.verdict]]),Y(`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.`]),Y(`Recommended actions`,a.recommendations),Y(`Report caveats`,a.caveats),a.items.length&&(a.templateRecommendations.length&&(q([`Template`,`URLs`,`Rows`,`Shortfall`,`Impr`,`Action`],a.templateRecommendations.map(e=>[K(e.templateLabel,34),W(e.urlCount),W(e.rowCount),W(e.totalEstimatedCtrClickShortfall),W(e.totalImpressions),K(e.action,72)])),J(`Top template actions`,a.templateRecommendations.map(e=>({label:e.templateLabel,context:`${W(e.urlCount)} URLs, ${W(e.totalEstimatedCtrClickShortfall)} heuristic CTR click shortfall`,action:`${e.action} ${e.evidence}`})))),a.groups.length&&(q([`Cluster`,`URLs`,`Rows`,`Shortfall`,`Impr`,`Sample URL`,`Action`],a.groups.map(e=>[K(e.label,44),W(e.urlCount),W(e.rowCount),W(e.totalEstimatedCtrClickShortfall),W(e.totalImpressions),K(e.sampleUrls[0]??`-`,46),K(e.recommendation,72)])),J(`Top cluster actions`,a.groups.map(e=>({label:e.label,context:`${W(e.urlCount)} URLs, ${W(e.totalEstimatedCtrClickShortfall)} heuristic CTR click shortfall`,action:e.recommendation})))),q([`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),ni(e.position),W(e.impressions),`${G(e.ctr)} / ${G(e.targetCtr)}`,W(e.estimatedCtrClickShortfall),wi(e.contentVerification?.fetchDiagnostics),Ti(e.contentVerification?.classification),K(e.recommendation.action,64)])),J(`Top opportunity actions`,a.items.map(e=>({label:e.query,context:`${e.template.label}, pos ${ni(e.position)}, ${W(e.impressions)} impressions`,action:e.recommendation.action}))))}}),Os=S({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.`},...X([`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=M(e),n=O(e[`verify-limit`]),r=D(e[`verify-content`])===!0||n!==void 0,i=await U({client:E(e),site:T(e.site),options:{json:t,refresh:D(e.refresh)}}),a=await ce({site:i.site,range:O(e.days),minImpressions:O(e[`min-impressions`]),limit:T(e.limit)?Number(T(e.limit)):10,brandTerms:i.client?.brandTerms,includeBrand:D(e[`include-brand`]),verifyContent:r,verifyLimit:n,js:D(e.js)?!0:void 0,rate:mr(e),refresh:D(e.refresh)});if(t){L(a);return}if(R([[`Site`,a.site],[`Eligible pages`,W(a.summary.eligiblePages)],[`Returned pages`,W(a.summary.returnedPages)],[`Eligible queries`,W(a.summary.eligibleQueries)],[`Eligible impressions`,W(a.summary.eligibleImpressions)],[`Content issues`,W(a.summary.contentIssues)],[`Technical issues`,W(a.summary.technicalIssues)],[`Fetch failures`,W(a.summary.fetchFailures)],[`Brand queries`,a.summary.brandFiltering],[`Verification`,ri(a)],[`Verdict`,a.summary.verdict]]),Y(`Recommended actions`,a.recommendations),Y(`Report caveats`,a.caveats),!a.items.length){process.stdout.write(`No average-position candidates matched this report.
122
- `),process.stdout.write(`${a.ledgerSummary}\n`);return}q([`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),W(e.queryCount),wi(e.fetchDiagnostics),Ti(e.contentVerification?.classification),e.recommendation.action])),J(`Top second-page actions`,a.items.map(e=>({label:e.primaryQuery,context:`${e.template.label}, pos ${ni(e.position)}, ${W(e.impressions)} impressions`,action:e.recommendation.action}))),process.stdout.write(`${a.ledgerSummary}\n`)}}),ks=S({meta:{name:`seo-to-ai-query`,description:`Convert GSC search queries into AI-style monitoring prompts`},args:{site:{type:`string`},project:{type:`string`,description:`Saved project id or name.`},client:{type:`string`,description:`Legacy alias for --project.`},"start-date":{type:`string`,description:`Exact GSC start date (YYYY-MM-DD). Use with --end-date.`},"end-date":{type:`string`,description:`Exact GSC end date (YYYY-MM-DD). Use with --start-date.`},"max-rows":{type:`string`,description:`Maximum retained GSC query rows. Defaults to 50000.`},"brand-terms":{type:`string`,description:`Comma-separated brand terms to exclude.`},...X([`days`,`limit`,`minImpressions`,`includeBrand`,`refresh`],{days:{description:`GSC lookback within the current 16-month retention.`},limit:{description:`Maximum source queries to convert. Defaults to 25.`},minImpressions:{description:`Minimum query impressions. Defaults to 20.`},includeBrand:{description:`Include branded queries in prompt generation.`},refresh:{description:`Bypass local GSC cache.`}}),json:{type:`boolean`,default:!1}},run:async({args:e})=>{let t=M(e),n=await U({client:E(e),site:T(e.site),options:{json:t,refresh:D(e.refresh)}}),r=await ut({site:n.site,days:k(e.days,`--days`),startDate:T(e[`start-date`]),endDate:T(e[`end-date`]),limit:k(e.limit,`--limit`),minImpressions:k(e[`min-impressions`],`--min-impressions`),maxRows:k(e[`max-rows`],`--max-rows`),brandTerms:[...n.client?.brandTerms??[],...A(e[`brand-terms`])??[]],includeBrand:D(e[`include-brand`]),refresh:D(e.refresh)});if(t){L(r);return}R([[`Property`,r.site],[`Status`,r.dataStatus],[`Verdict`,r.summary.verdict],[`Eligible queries`,W(r.summary.eligibleQueries)],[`Returned queries`,W(r.summary.returnedQueries)],[`Prompts`,W(r.summary.prompts)],[`GSC completeness`,r.source.completeness]]),q([`Query`,`Impr`,`Prompt`],r.items.flatMap(e=>e.prompts.map(t=>[K(e.query,42),W(e.impressions),K(t,86)]))),Y(`Report caveats`,r.caveats),Y(`Warnings`,r.warnings)}}),As=S({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=E(e),n=T(e.site),r=t?await ei({project:t,options:{json:M(e)}}):void 0,i=t||n?void 0:await ei({options:{json:M(e)}}),a=await Pe({url:T(e.url)??``,site:r?.siteUrl??n??i?.siteUrl,js:D(e.js)?!0:`auto`,refresh:D(e.refresh)});if(M(e)){L(a);return}R([[`URL`,a.url],[`Final URL`,a.page.finalUrl],[`Title`,a.page.title??`missing`],[`Meta description`,a.page.metaDescription??`missing`],[`Word count`,String(a.page.wordCount)],[`Fetch`,wi(a.fetchDiagnostics)]]),a.issues.length&&(process.stdout.write(`
123
- Issues
124
- `),z([`Code`,`Severity`,`Principle`,`Detail`],a.issues.map(e=>[e.code,e.severity,e.principle,e.detail])))}});function js(e){let t=T(e)??`mobile`;if(t!==`mobile`&&t!==`desktop`)throw new v(`INVALID_INPUT`,`Invalid --strategy. Use mobile or desktop.`);return t}function Ms(e){return e?e.displayValue??String(e.value??`not measured`):`not measured`}function Ns(e){if(!e)return`not available`;let t=e.unit===`milliseconds`?`ms`:``;return`${e.p75}${t} (${e.rating})`}const Ps=S({meta:{name:`perf`,description:`Audit lab performance and field Core Web Vitals`},subCommands:{audit:S({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=M(e),n=await ei({client:E(e),options:{json:t}}),r=T(e.url)??n?.startUrl;if(!r)throw Error(`No URL selected. Pass --url or use --project with a crawl URL.`);let i=await ve({url:r,strategy:js(e.strategy),lighthouseBin:T(e[`lighthouse-bin`]),cruxApiKey:T(e[`crux-key`]),refresh:D(e.refresh),includeRaw:t&&D(e.raw)});if(t){L(i);return}R([[`URL`,i.url],[`Source`,i.source],[`Strategy`,i.strategy],[`Lab score`,i.score===void 0?`unknown`:`${i.score}/100`],[`Lab grade`,i.grade],[`Data status`,i.dataStatus],[`Headline`,i.headline],[`FCP`,Ms(i.metrics.firstContentfulPaint)],[`LCP`,Ms(i.metrics.largestContentfulPaint)],[`TBT`,Ms(i.metrics.totalBlockingTime)],[`CLS`,Ms(i.metrics.cumulativeLayoutShift)],[`INP`,Ms(i.metrics.interactionToNextPaint)],[`Server response`,Ms(i.metrics.serverResponseTime)],[`Fallback fetch`,Ms(i.metrics.fallbackFetchDuration)],[`Field data`,i.fieldData?`${i.fieldData.scope} ${i.fieldData.formFactor.toLowerCase()} (${i.fieldData.assessment.status})`:i.fieldDataStatus.status],[`Field period`,i.fieldData?.collectionPeriod?.firstDate&&i.fieldData.collectionPeriod.lastDate?`${i.fieldData.collectionPeriod.firstDate} to ${i.fieldData.collectionPeriod.lastDate}`:`not available`],[`Field LCP p75`,Ns(i.fieldData?.metrics.largestContentfulPaint)],[`Field INP p75`,Ns(i.fieldData?.metrics.interactionToNextPaint)],[`Field CLS p75`,Ns(i.fieldData?.metrics.cumulativeLayoutShift)]]),J(`Top actions`,i.topActions.map(e=>({label:e.title,action:e.action,context:e.plainEnglish}))),Y(`Caveats`,i.caveats)}})}}),Fs=S({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.`},...X([`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=M(e),n=await U({client:E(e),site:T(e.site),options:{json:t,refresh:D(e.refresh)}}),r=await ge({site:n.site,days:O(e.days),recentDays:O(e.recent),limit:O(e.limit),brandTerms:n.client?.brandTerms,includeBrand:D(e[`include-brand`]),refresh:D(e.refresh),progress:go(!t)});if(t){L(r);return}R([[`Property`,r.site],[`Data status`,r.dataStatus],[`Update overlap`,r.summary.updateAttribution.replaceAll(`-`,` `)],[`Significant anomalies`,String(r.summary.significantAnomalies)],[`Update matches`,String(r.summary.updateMatches)],[`Decay items`,String(r.summary.decayItems)],[`Cannibal items`,String(r.summary.cannibalItems)],[`Striking distance`,String(r.summary.strikingDistanceItems)],[`Quick wins`,String(r.summary.quickWinItems)]]),z([`Priority`,`Confidence`,`Reason`,`Action`],r.priorities.map(e=>[e.label,e.confidence,e.reason,e.action])),r.skippedSections?.length&&z([`Skipped section`,`Reason`],r.skippedSections.map(e=>[e.section,e.reason])),J(`Priority action details`,r.priorities.map(e=>({label:e.label,context:e.confidence,action:e.action})))}}),Is=S({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 Yt();if(M(e)){L(t),t.ok||(process.exitCode=1);return}Gr(`SEO doctor`,Ir(t.checks)),process.stdout.write(`
125
- `),Kr(t.checks),process.stdout.write(`\nChecked ${t.generatedAt}\n`),t.ok||(process.exitCode=1)}}),Ls=e=>{let t=T(e)??`page`;if(t!==`page`&&t!==`query`&&t!==`country`&&t!==`device`)throw Error(`Invalid --dimension. Use page, query, country, or device.`);return t},Rs=S({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.`},...X([`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=M(e),n=Ls(e.dimension),r=k(e.days,`--days`),i=k(e.compare,`--compare`),a=k(e.limit,`--limit`),o=k(e[`unmatched-limit`],`--unmatched-limit`),s=k(e[`max-rows`],`--max-rows`),c=await gt({site:(await U({client:E(e),site:T(e.site),options:{json:t,refresh:D(e.refresh)}})).site,dimension:n,days:r,compareDays:i,startDate:T(e[`start-date`]),endDate:T(e[`end-date`]),limit:a,unmatchedLimit:o,maxRows:s,refresh:D(e.refresh)});if(t){L(c);return}R([[`Property`,c.site],[`Dimension`,c.dimension],[`Status`,c.dataStatus],[`Before`,`${c.before.startDate} to ${c.before.endDate}`],[`After`,`${c.after.startDate} to ${c.after.endDate}`],[`Verdict`,c.summary.verdict]]),z([`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(`
201
+ `);return}q([`Type`,`Query`,`Evidence`,`Pos`,`CTR`,`Expected`,`Impr`,`Shortfall`,`Action`],r.items.map(e=>[e.opportunityType,G(e.query,38),e.verification.signals.length?e.verification.signals.join(`, `):e.verification.status,e.position.toFixed(1),W(e.ctr),e.expectedCtr===void 0?`n/a`:W(e.expectedCtr),U(e.impressions),e.estimatedCtrClickShortfall===void 0?`n/a`:U(e.estimatedCtrClickShortfall),G(e.recommendation,72)])),J(`Top page actions`,r.items.map(e=>({label:e.query,context:`${e.opportunityType}, ${U(e.impressions)} impressions`,action:e.recommendation})))}}),wc=C({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`},...X([`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=N(e),n=await H({client:D(e),site:E(e.site),options:{json:t}}),r=await Zt({site:n.site,scope:E(e.scope),brand:n.client?.brandTerms?.[0],brandTerms:n.client?.brandTerms,includeBrand:O(e[`include-brand`]),minImpressions:k(e[`min-impressions`]),limit:k(e.limit)});if(t){R(r);return}K({title:`Query clusters`,target:r.site,status:`info`,summary:r.summary.verdict,metrics:[{label:`Scope`,value:r.scope??`All pages`},{label:`Clusters`,value:U(r.summary.clusters)},{label:`Queries`,value:U(r.summary.queries)},{label:`Impressions`,value:U(r.summary.impressions)},{label:`Clicks`,value:U(r.summary.clicks)},{label:`Minimum impressions`,value:U(r.summary.minImpressions)},{label:`Result limit`,value:U(r.summary.limit)},{label:`High opportunity`,value:U(r.summary.highOpportunityClusters)},{label:`Brand queries`,value:r.summary.brandFiltering}]}),Y(`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.`]),Y(`Recommended actions`,r.recommendations),Y(`Report caveats`,r.caveats),q([`Cluster`,`Intent`,`Queries`,`Impr`,`CTR`,`Expected`,`Lift`,`Action`],r.clusters.map(e=>[G(e.label,32),e.intent,e.queries.length,U(e.totals?.impressions??0),W(e.totals?.ctr??0),W(e.benchmark?.expectedCtr??0),e.estimatedClickLift===void 0?`-`:U(e.estimatedClickLift),G(e.recommendation??``,72)])),J(`Top cluster actions`,r.clusters.map(e=>({label:e.label,context:`${U(e.totals?.impressions??0)} impressions, ${e.estimatedClickLift===void 0?`ranking opportunity`:`${U(e.estimatedClickLift)} estimated click lift`}`,action:e.recommendation??``})))}}),Tc=C({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.`},...X([`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=N(e),n=k(e[`verify-limit`]),r=O(e[`verify-content`])===!0||n!==void 0,i=await H({client:D(e),site:E(e.site),options:{json:t}}),a=await Ce({site:i.site,days:k(e.days),limit:k(e.limit),minImpressions:k(e[`min-impressions`]),brandTerms:[...i.client?.brandTerms??[],...j(e[`brand-terms`])??[]],includeBrand:O(e[`include-brand`]),verifyContent:r,verifyLimit:n,js:O(e.js)?!0:void 0,rate:Kr(e),refresh:O(e.refresh)});if(t){R(a);return}K({title:`Quick wins`,target:a.site,status:a.source.possiblyTruncated?`unknown`:`info`,summary:a.summary.verdict,metrics:[{label:`Eligible rows`,value:U(a.summary.eligibleRows)},{label:`Returned rows`,value:U(a.summary.returnedRows)},{label:`Query clusters`,value:U(a.summary.repeatedQueryGroups)},{label:`Template patterns`,value:U(a.summary.templatePatterns)},{label:`CTR click shortfall`,value:U(a.summary.returnedEstimatedCtrClickShortfall)},{label:`Brand queries`,value:a.summary.brandFiltering},{label:`Evidence`,value:a.dataStatus},{label:`Verification`,value:Qi(a)}]}),Y(`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.`]),Y(`Recommended actions`,a.recommendations),Y(`Report caveats`,a.caveats),a.items.length&&(a.templateRecommendations.length&&(q([`Template`,`URLs`,`Rows`,`Shortfall`,`Impr`,`Action`],a.templateRecommendations.map(e=>[G(e.templateLabel,34),U(e.urlCount),U(e.rowCount),U(e.totalEstimatedCtrClickShortfall),U(e.totalImpressions),G(e.action,72)])),J(`Top template actions`,a.templateRecommendations.map(e=>({label:e.templateLabel,context:`${U(e.urlCount)} URLs, ${U(e.totalEstimatedCtrClickShortfall)} heuristic CTR click shortfall`,action:`${e.action} ${e.evidence}`})))),a.groups.length&&(q([`Cluster`,`URLs`,`Rows`,`Shortfall`,`Impr`,`Sample URL`,`Action`],a.groups.map(e=>[G(e.label,44),U(e.urlCount),U(e.rowCount),U(e.totalEstimatedCtrClickShortfall),U(e.totalImpressions),G(e.sampleUrls[0]??`-`,46),G(e.recommendation,72)])),J(`Top cluster actions`,a.groups.map(e=>({label:e.label,context:`${U(e.urlCount)} URLs, ${U(e.totalEstimatedCtrClickShortfall)} heuristic CTR click shortfall`,action:e.recommendation})))),q([`Query`,`Template`,`URL`,`Pos`,`Impr`,`CTR / target`,`Shortfall`,`Fetch`,`Check`,`Action`],a.items.map(e=>[G(e.query,36),G(e.template.label,24),G(e.url,48),Zi(e.position),U(e.impressions),`${W(e.ctr)} / ${W(e.targetCtr)}`,U(e.estimatedCtrClickShortfall),ma(e.contentVerification?.fetchDiagnostics),ha(e.contentVerification?.classification),G(e.recommendation.action,64)])),J(`Top opportunity actions`,a.items.map(e=>({label:e.query,context:`${e.template.label}, pos ${Zi(e.position)}, ${U(e.impressions)} impressions`,action:e.recommendation.action}))))}}),Ec=C({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.`},...X([`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=N(e),n=k(e[`verify-limit`]),r=O(e[`verify-content`])===!0||n!==void 0,i=await H({client:D(e),site:E(e.site),options:{json:t,refresh:O(e.refresh)}}),a=await mt({site:i.site,range:k(e.days),minImpressions:k(e[`min-impressions`]),limit:E(e.limit)?Number(E(e.limit)):10,brandTerms:i.client?.brandTerms,includeBrand:O(e[`include-brand`]),verifyContent:r,verifyLimit:n,js:O(e.js)?!0:void 0,rate:Kr(e),refresh:O(e.refresh)});if(t){R(a);return}if(K({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:U(a.summary.eligiblePages)},{label:`Returned pages`,value:U(a.summary.returnedPages)},{label:`Eligible queries`,value:U(a.summary.eligibleQueries)},{label:`Impressions`,value:U(a.summary.eligibleImpressions)},{label:`Content issues`,value:U(a.summary.contentIssues)},{label:`Technical issues`,value:U(a.summary.technicalIssues)},{label:`Fetch failures`,value:U(a.summary.fetchFailures)},{label:`Brand queries`,value:a.summary.brandFiltering},{label:`Evidence`,value:a.dataStatus},{label:`Verification`,value:Qi(a)}]}),Y(`Recommended actions`,a.recommendations),Y(`Report caveats`,a.caveats),!a.items.length){process.stdout.write(`No average-position candidates matched this report.
202
+ `),process.stdout.write(`${a.ledgerSummary}\n`);return}q([`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),U(e.queryCount),ma(e.fetchDiagnostics),ha(e.contentVerification?.classification),e.recommendation.action])),J(`Top second-page actions`,a.items.map(e=>({label:e.primaryQuery,context:`${e.template.label}, pos ${Zi(e.position)}, ${U(e.impressions)} impressions`,action:e.recommendation.action}))),process.stdout.write(`${a.ledgerSummary}\n`)}}),Dc=C({meta:{name:`seo-to-ai-query`,description:`Convert GSC search queries into AI-style monitoring prompts`},args:{site:{type:`string`},project:{type:`string`,description:`Saved project id or name.`},client:{type:`string`,description:`Legacy alias for --project.`},"start-date":{type:`string`,description:`Exact GSC start date (YYYY-MM-DD). Use with --end-date.`},"end-date":{type:`string`,description:`Exact GSC end date (YYYY-MM-DD). Use with --start-date.`},"max-rows":{type:`string`,description:`Maximum retained GSC query rows. Defaults to 50000.`},"brand-terms":{type:`string`,description:`Comma-separated brand terms to exclude.`},...X([`days`,`limit`,`minImpressions`,`includeBrand`,`refresh`],{days:{description:`GSC lookback within the current 16-month retention.`},limit:{description:`Maximum source queries to convert. Defaults to 25.`},minImpressions:{description:`Minimum query impressions. Defaults to 20.`},includeBrand:{description:`Include branded queries in prompt generation.`},refresh:{description:`Bypass local GSC cache.`}}),json:{type:`boolean`,default:!1}},run:async({args:t})=>{let n=N(t),r=await H({client:D(t),site:E(t.site),options:{json:n,refresh:O(t.refresh)}}),i=await e({site:r.site,days:A(t.days,`--days`),startDate:E(t[`start-date`]),endDate:E(t[`end-date`]),limit:A(t.limit,`--limit`),minImpressions:A(t[`min-impressions`],`--min-impressions`),maxRows:A(t[`max-rows`],`--max-rows`),brandTerms:[...r.client?.brandTerms??[],...j(t[`brand-terms`])??[]],includeBrand:O(t[`include-brand`]),refresh:O(t.refresh)});if(n){R(i);return}K({title:`SEO to AI query prompts`,target:i.site,status:i.dataStatus===`available`||i.dataStatus===`empty`?`info`:`unknown`,summary:i.summary.verdict,metrics:[{label:`Evidence`,value:i.dataStatus},{label:`Eligible queries`,value:U(i.summary.eligibleQueries)},{label:`Returned queries`,value:U(i.summary.returnedQueries)},{label:`Prompts`,value:U(i.summary.prompts)},{label:`GSC completeness`,value:i.source.completeness}]}),q([`Query`,`Impr`,`Prompt`],i.items.flatMap(e=>e.prompts.map(t=>[G(e.query,42),U(e.impressions),G(t,86)]))),Y(`Report caveats`,i.caveats),Y(`Warnings`,i.warnings)}}),Oc=C({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=D(e),n=E(e.site),r=t?await Hi({project:t,options:{json:N(e)}}):void 0,i=t||n?void 0:await Hi({options:{json:N(e)}}),a=await et({url:E(e.url)??``,site:r?.siteUrl??n??i?.siteUrl,js:O(e.js)?!0:`auto`,refresh:O(e.refresh)});if(N(e)){R(a);return}K({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:ma(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 kc(e){let t=E(e)??`mobile`;if(t!==`mobile`&&t!==`desktop`)throw new y(`INVALID_INPUT`,`Invalid --strategy. Use mobile or desktop.`);return t}function Ac(e){return e?e.displayValue??String(e.value??`not measured`):`not measured`}function jc(e){if(!e)return`not available`;let t=e.unit===`milliseconds`?`ms`:``;return`${e.p75}${t} (${e.rating})`}const Mc=C({meta:{name:`perf`,description:`Audit lab performance and field Core Web Vitals`},subCommands:{audit:C({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=N(e),n=await Hi({client:D(e),options:{json:t}}),r=E(e.url)??n?.startUrl;if(!r)throw Error(`No URL selected. Pass --url or use --project with a crawl URL.`);let i=await Ut({url:r,strategy:kc(e.strategy),lighthouseBin:E(e[`lighthouse-bin`]),cruxApiKey:E(e[`crux-key`]),refresh:O(e.refresh),includeRaw:t&&O(e.raw)});if(t){R(i);return}K({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:Ac(i.metrics.firstContentfulPaint)},{label:`LCP`,value:Ac(i.metrics.largestContentfulPaint)},{label:`TBT`,value:Ac(i.metrics.totalBlockingTime)},{label:`CLS`,value:Ac(i.metrics.cumulativeLayoutShift)},{label:`INP`,value:Ac(i.metrics.interactionToNextPaint)},{label:`Server response`,value:Ac(i.metrics.serverResponseTime)},{label:`Fallback fetch`,value:Ac(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:jc(i.fieldData?.metrics.largestContentfulPaint)},{label:`Field INP p75`,value:jc(i.fieldData?.metrics.interactionToNextPaint)},{label:`Field CLS p75`,value:jc(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`}]}),J(`Top actions`,i.topActions.map(e=>({label:e.title,action:e.action,context:e.plainEnglish}))),Y(`Caveats`,i.caveats)}})}}),Nc=C({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.`},...X([`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=N(e),n=await H({client:D(e),site:E(e.site),options:{json:t,refresh:O(e.refresh)}}),r=await st({site:n.site,days:k(e.days),recentDays:k(e.recent),limit:k(e.limit),brandTerms:n.client?.brandTerms,includeBrand:O(e[`include-brand`]),refresh:O(e.refresh),progress:cs(!t)});if(t){R(r);return}K({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}]}),B([`Priority`,`Confidence`,`Reason`,`Action`],r.priorities.map(e=>[e.label,e.confidence,e.reason,e.action])),r.skippedSections?.length&&B([`Skipped section`,`Reason`],r.skippedSections.map(e=>[e.section,e.reason])),J(`Priority action details`,r.priorities.map(e=>({label:e.label,context:e.confidence,action:e.action})))}}),Pc=C({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 Dt();if(N(e)){R(t),t.ok||(process.exitCode=1);return}Ai(`SEO doctor`,xi(t.checks)),process.stdout.write(`
203
+ `),ji(t.checks),process.stdout.write(`\nChecked ${t.generatedAt}\n`),t.ok||(process.exitCode=1)}}),Fc=e=>{let t=E(e)??`page`;if(t!==`page`&&t!==`query`&&t!==`country`&&t!==`device`)throw Error(`Invalid --dimension. Use page, query, country, or device.`);return t},Ic=C({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.`},...X([`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=N(e),n=Fc(e.dimension),r=A(e.days,`--days`),i=A(e.compare,`--compare`),a=A(e.limit,`--limit`),o=A(e[`unmatched-limit`],`--unmatched-limit`),s=A(e[`max-rows`],`--max-rows`),c=await kt({site:(await H({client:D(e),site:E(e.site),options:{json:t,refresh:O(e.refresh)}})).site,dimension:n,days:r,compareDays:i,startDate:E(e[`start-date`]),endDate:E(e[`end-date`]),limit:a,unmatchedLimit:o,maxRows:s,refresh:O(e.refresh)});if(t){R(c);return}K({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}`}]}),B([`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(`
126
204
  One-window evidence (not treated as zero)
127
- `),z([`Segment`,`Retained in`,`Clicks`,`Impressions`],c.unmatchedSegments.map(e=>[e.key,e.retainedIn,e.clicks,e.impressions]))),Y(`Warnings`,c.warnings)}});function zs(e){return`${W(e)} ${e===1?`row`:`rows`}`}const Bs=S({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.`},...X([`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=M(e),n=O(e[`verify-limit`]),r=D(e[`verify-content`])===!0||n!==void 0,i=await U({client:E(e),site:T(e.site),options:{json:t,refresh:D(e.refresh)}}),a=await He({site:i.site,days:O(e.days),minImpressions:O(e[`min-impressions`]),limit:O(e.limit),brandTerms:i.client?.brandTerms,includeBrand:D(e[`include-brand`]),verifyContent:r,verifyLimit:n,js:D(e.js)?!0:void 0,rate:mr(e),refresh:D(e.refresh)});if(t){L(a);return}if(R([[`Site`,a.site],[`Eligible rows`,W(a.summary.eligibleRows)],[`Returned rows`,W(a.summary.returnedRows)],[`Template groups`,W(a.summary.groups)],[`Eligible impressions`,W(a.summary.eligibleImpressions)],[`Brand queries`,a.summary.brandFiltering],[`Verification`,a.verification.requested?`requested`:`off`],[`Verdict`,a.summary.verdict]]),Y(`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.`]),Y(`Recommended actions`,a.recommendations),Y(`Report caveats`,a.caveats),a.groups.length&&(q([`Group`,`Rows`,`URLs`,`Impr`,`Avg pos`,`Scope`,`Action`],a.groups.map(e=>[K(e.label,36),W(e.rowCount),W(e.uniqueUrls),W(e.totalImpressions),ni(e.impressionWeightedPosition),e.actionScope,K(e.recommendation.action,72)])),J(`Top striking-distance group actions`,a.groups.map(e=>({label:e.label,context:`${zs(e.rowCount)}, ${W(e.totalImpressions)} impressions`,action:e.recommendation.action})))),!a.items.length){process.stdout.write(`No striking-distance opportunities matched these filters.
128
- `);return}q([`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),ni(e.position),e.priority.score,wi(e.contentVerification?.fetchDiagnostics),Ti(e.contentVerification?.classification),K(e.recommendation.action,72)])),J(`Top striking-distance actions`,a.items.map(e=>({label:e.query,context:`${e.template.label}, pos ${ni(e.position)}, ${W(e.impressions)} impressions`,action:e.recommendation.action})))}});function Vs(e){let t=e.indexed+e.notIndexed+e.unknown+e.warnings;return t?`${e.indexed}/${t} indexed`:`-`}function Hs(e){if(!e.samples.length)return`-`;let t=e.medianWordCount===void 0?`words ?`:`${W(e.medianWordCount)} words`,n=e.weakQueryCoverage?`; ${e.weakQueryCoverage} weak coverage`:``;return`${t}; ${e.usable}/${e.samples.length} usable${n}`}function Us(e){return e===`serp-framing-review`?`wording review`:e===`body-term-review`?`literal term review`:`covered terms`}function Ws(e){let t=e.staticSegments.map(e=>`/${e.value}`).join(``),n=e.variableSegments.slice(0,3).map(e=>{let t=e.examples.slice(0,3).join(`, `);return`${e.placeholder}@${e.index+1}${t?` (${t})`:``}`});return[t?`static ${t}`:`no fixed path prefix`,n.length?`variables ${n.join(`; `)}`:`no variables`].join(`; `)}function Gs(e){if(e.checkedQueries)return`${Math.round(e.impressionShare*100)}% impression fit across ${ot(e.checkedQueries,`checked query/page row`)}`}function Ks(e){return e.startsWith(`theme: `)?`${e.slice(7)}-related`:e===`general`?`broad`:e}function qs(e){for(let t of e.slice(0,5)){process.stdout.write(`\n${t.signature}\n`),t.evidence.length&&process.stdout.write(` Evidence: ${t.evidence.join(`; `)}.\n`),process.stdout.write(` Shape: ${Ws(t.shape)}\n`);let e=Gs(t.metrics.entityFit);e&&process.stdout.write(` Entity fit: ${e}\n`);let n=t.metrics.queryPatterns.slice(0,3).map(e=>`${Ks(e.label)} (${W(e.impressions)} impr: ${e.examples.slice(0,2).join(`; `)})`);n.length&&process.stdout.write(` Demand: ${n.join(` | `)}\n`);let r=t.metrics.topQueries.slice(0,3);if(r.length){process.stdout.write(` Top queries:
129
- `);for(let e of r)process.stdout.write(` - ${K(e.query,72)} (${W(e.impressions)} impr, pos ${ni(e.position)})\n`)}let i=t.crawl.samples.map(e=>e.queryCoverage).filter(e=>!!e).slice(0,3);if(i.length){process.stdout.write(` Sample coverage:
130
- `);for(let e of i){let t=e.missingTerms.length?` missing ${e.missingTerms.slice(0,4).join(`, `)}`:``;process.stdout.write(` - ${Us(e.classification)}: ${K(e.query,64)} (body ${(e.bodyCoverage*100).toFixed(0)}%${t})\n`)}}let a=t.metrics.entityFit.weakExamples.slice(0,2);if(a.length){process.stdout.write(` Weak entity-fit examples:
131
- `);for(let e of a){let t=e.pathTerms.slice(0,4);process.stdout.write(` - ${K(e.query,64)} -> expected ${ot(t.length,`path term`)}: ${t.join(`, `)}\n`)}}process.stdout.write(` Action: ${t.recommendation}\n`)}}const Js=S({meta:{name:`pseo`,description:`Programmatic SEO audits that cluster URL patterns and sample template pages`},subCommands:{audit:S({meta:{name:`audit`,description:`Group GSC/sitemap URLs into pSEO templates, then sample pages for crawl and index evidence`},args:{site:{type:`string`},project:{type:`string`,description:`Saved project id or name.`},client:{type:`string`,description:`Legacy alias for --project.`},...X([`days`,`limit`,`includeBrand`,`js`,`refresh`],{limit:{description:`Maximum templates to show. Defaults to 25.`},includeBrand:{description:`Include branded queries in template metrics.`},js:{description:`Force JavaScript rendering for crawl samples.`},refresh:{description:`Bypass local GSC and HTTP cache.`}}),sitemap:{type:`string`,description:`Comma-separated sitemap URLs to include in template counts.`},"crawl-samples":{type:`string`,description:`Sample URLs to fetch per detected template. This is not crawl depth.`},"inspect-samples":{type:`string`,description:`Sample URLs to check per detected template with URL Inspection.`},"max-sitemap-urls":{type:`string`,description:`Maximum discovered URLs per sitemap. Defaults to 50000.`},"minimum-template-urls":{type:`string`,description:`Minimum repeated URLs required for a template. Defaults to 3.`},"minimum-template-share":{type:`string`,description:`Minimum share of discovered URLs from 0 to 1. Defaults to 0.`},"minimum-template-impressions":{type:`string`,description:`Minimum retained page impressions for a template. Defaults to 0.`},"brand-terms":{type:`string`,description:`Comma-separated brand terms to exclude from query evidence.`},"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.`},json:{type:`boolean`,default:!1}},run:async({args:e})=>{let t=M(e),n=Go(e),r=await U({client:E(e),site:T(e.site),options:{json:t,refresh:D(e.refresh)}}),i=await oe({site:r.site,...n,brandTerms:n.brandTerms??r.client?.brandTerms,progress:go(!t)});if(t){L(i);return}if(R([[`Property`,i.site],[`Templates`,W(i.summary.templates)],[`GSC pages`,W(i.summary.gscPages)],[`Sitemap URLs`,W(i.summary.sitemapUrls)],[`Clicks`,W(i.summary.clicks)],[`Impressions`,W(i.summary.impressions)],[`Usable crawls`,`${W(i.summary.crawledUrls)}/${W(i.summary.crawlAttempts)}`],[`Inspection responses`,`${W(i.summary.inspectedUrls)}/${W(i.summary.inspectionAttempts)}`]]),Y(`Report caveats`,i.caveats),!i.templates.length){process.stdout.write(`No pSEO templates found from GSC/sitemap data.
132
- `);return}q([`Verdict`,`Template`,`URLs`,`Clicks`,`Impr`,`CTR`,`Pos`,`Index`,`Crawl`,`Action`],i.templates.map(e=>[`${e.verdict} (${e.confidence})`,K(e.signature,28),W(e.urlCount),W(e.metrics.clicks),W(e.metrics.impressions),G(e.metrics.ctr),e.metrics.position.toFixed(1),Vs(e.inspection),Hs(e.crawl),K(e.recommendation,72)])),qs(i.templates),i.warnings.length&&process.stdout.write(`Warnings: ${i.warnings.join(`; `)}\n`),ii(`seo export pseo ${r.client?`--project ${JSON.stringify(r.client.id)}`:`--site ${JSON.stringify(r.site)}`}`)}})}}),Ys={"ai-search":`AI search`,crawl:`Crawling and technical checks`,diagnosis:`Diagnosis`,experiments:`Experiments`,monitoring:`Monitoring`,opportunities:`Opportunities`,reporting:`Reporting`,setup:`Setup`,workflows:`Workflows`};function Xs(e){let t=T(e);if(t){if(!Ft.includes(t))throw new v(`INVALID_INPUT`,`Unknown report category: ${t}. Choose ${Ft.join(`, `)}.`);return t}}async function Zs(e,t){let n=T(e),r=T(t);if(n&&r)throw new v(`INVALID_INPUT`,`Use either --params or --params-file, not both.`);if(!n&&!r)return{};let i=n??await On(r??``,`utf8`),a;try{a=JSON.parse(i)}catch{throw new v(`INVALID_INPUT`,`Report parameters must be valid JSON.`)}if(!a||typeof a!=`object`||Array.isArray(a))throw new v(`INVALID_INPUT`,`Report parameters must be an object.`);return a}function Qs(e){if(!e.isError)return;let t=e.structuredContent;throw new v(t?.error?.code??`INTERNAL_ERROR`,t?.error?.message??`The report could not be completed.`)}function $s(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 ec=S({meta:{name:`reports`,description:`Discover and run the complete report catalog`},subCommands:{list:S({meta:{name:`list`,description:`List report ids available to the CLI and MCP server`},args:{category:{type:`string`,description:`Filter by category: ${Ft.join(`, `)}.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=St(Xs(e.category));if(M(e)){L({reports:t,categories:Ft});return}qr(t,{noun:`report`,categoryLabels:Ys}),process.stdout.write(`
133
- `),Wr({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:S({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`."},check:{type:`string`,description:`Show fix guidance for one check id from the report output.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=T(e.check);if(t){let n=st(T(e.id)??``,t);if(M(e)){L({report:n});return}Gr(`${n.name}: ${n.check}`,n.checkFix.goal),process.stdout.write(`
134
- `),B(`Fix`,n.checkFix.fix),process.stdout.write(`
135
- `),B(`Agent prompt`,n.checkFix.prompt),process.stdout.write(`
136
- `),B(`Verify`,n.checkFix.verify),n.checkFix.resources.length>0&&(process.stdout.write(`
137
- `),Yr(`Resources`,n.checkFix.resources.map(e=>`${e.title}: ${e.url}`))),process.stdout.write(`
138
- `);return}let n=At(T(e.id)??``);if(M(e)){L({report:n});return}Gr(n.name,n.description),process.stdout.write(`
139
- `),R([[`ID`,n.id],[`Category`,n.category],[`Outcome`,n.outcome]]),process.stdout.write(`
140
- `),Yr(`Use when`,n.useWhen),process.stdout.write(`
141
- `),Yr(`Avoid when`,n.avoidWhen),process.stdout.write(`
142
- `),Yr(`Read in order`,n.readOrder),process.stdout.write(`
143
- `),Yr(`Do not claim`,n.doNotClaim),process.stdout.write(`
144
- `),B(`Verify`,n.verify),n.related.length>0&&(process.stdout.write(`
145
- `),Yr(`Related reports`,n.related.map(e=>`${e.id}: ${e.reason}`))),process.stdout.write(`
205
+ `),B([`Segment`,`Retained in`,`Clicks`,`Impressions`],c.unmatchedSegments.map(e=>[e.key,e.retainedIn,e.clicks,e.impressions]))),Y(`Warnings`,c.warnings)}});function Lc(e){return`${U(e)} ${e===1?`row`:`rows`}`}const Rc=C({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.`},...X([`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=N(e),n=k(e[`verify-limit`]),r=O(e[`verify-content`])===!0||n!==void 0,i=await H({client:D(e),site:E(e.site),options:{json:t,refresh:O(e.refresh)}}),a=await p({site:i.site,days:k(e.days),minImpressions:k(e[`min-impressions`]),limit:k(e.limit),brandTerms:i.client?.brandTerms,includeBrand:O(e[`include-brand`]),verifyContent:r,verifyLimit:n,js:O(e.js)?!0:void 0,rate:Kr(e),refresh:O(e.refresh)});if(t){R(a);return}if(K({title:`Striking-distance opportunities`,target:a.site,status:a.source.possiblyTruncated?`unknown`:`info`,summary:a.summary.verdict,metrics:[{label:`Eligible rows`,value:U(a.summary.eligibleRows)},{label:`Returned rows`,value:U(a.summary.returnedRows)},{label:`Template groups`,value:U(a.summary.groups)},{label:`Impressions`,value:U(a.summary.eligibleImpressions)},{label:`Brand queries`,value:a.summary.brandFiltering},{label:`Verification`,value:a.verification.requested?`Requested`:`Off`},{label:`Evidence`,value:a.dataStatus}]}),Y(`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.`]),Y(`Recommended actions`,a.recommendations),Y(`Report caveats`,a.caveats),a.groups.length&&(q([`Group`,`Rows`,`URLs`,`Impr`,`Avg pos`,`Scope`,`Action`],a.groups.map(e=>[G(e.label,36),U(e.rowCount),U(e.uniqueUrls),U(e.totalImpressions),Zi(e.impressionWeightedPosition),e.actionScope,G(e.recommendation.action,72)])),J(`Top striking-distance group actions`,a.groups.map(e=>({label:e.label,context:`${Lc(e.rowCount)}, ${U(e.totalImpressions)} impressions`,action:e.recommendation.action})))),!a.items.length){process.stdout.write(`No striking-distance opportunities matched these filters.
206
+ `);return}q([`Query`,`Template`,`URL`,`Impr`,`CTR`,`Pos`,`Score`,`Fetch`,`Check`,`Action`],a.items.map(e=>[G(e.query,36),G(e.template.label,24),G(e.url,48),U(e.impressions),W(e.ctr),Zi(e.position),e.priority.score,ma(e.contentVerification?.fetchDiagnostics),ha(e.contentVerification?.classification),G(e.recommendation.action,72)])),J(`Top striking-distance actions`,a.items.map(e=>({label:e.query,context:`${e.template.label}, pos ${Zi(e.position)}, ${U(e.impressions)} impressions`,action:e.recommendation.action})))}});function zc(e){return e.filter(e=>e.isVerified)}function Bc(e){if(e.startsWith(`sc-domain:`))return e.slice(10).replace(/^www\./,``).toLowerCase();try{return new URL(e).hostname.replace(/^www\./,``).toLowerCase()}catch{return}}function Vc(e,t){let n=Bc(e);if(!n)return;let r=zc(t).filter(e=>Bc(e.url)===n);return r.length===1?r[0]:void 0}async function Hc(e){let t=zc(e.sites),n=e.explicitSite??e.savedSite;if(n){let e=t.find(e=>e.url===n);if(!e)throw new y(`PROPERTY_NOT_FOUND`,`Bing Webmaster did not return a verified site matching ${n}.`);return e.url}let r=e.projectSite?Vc(e.projectSite,t):void 0;if(r)return r.url;if(t.length===1)return t[0]?.url;if(t.length!==0){if(!L({json:e.json}))throw new y(`INVALID_INPUT`,`Several verified Bing sites are available. Pass --site to choose one.`);return V(await fr({message:`Choose a verified Bing Webmaster site`,options:t.map(e=>({value:e.url,label:e.url}))}))}}const Uc={project:{type:`string`,description:`Saved project id or name.`},client:{type:`string`,description:`Legacy alias for --project.`},site:{type:`string`,description:`Verified Bing Webmaster site URL.`}},Wc=C({meta:{name:`providers`,description:`Connect optional data providers`},subCommands:{bing:C({meta:{name:`bing`,description:`Connect and report on Bing Webmaster`},subCommands:{connect:C({meta:{name:`connect`,description:`Connect Bing Webmaster and attach a verified site`},args:{...Uc,json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=N(e);if(!L({json:t}))throw new y(`AUTH_REQUIRED`,"Run `seo providers bing connect` in a terminal. Agents and CI can set SEO_BING_API_KEY and run the report directly.");or(`Connect Bing Webmaster`),w(`Paste the API key from Bing Webmaster Tools Settings, then API Access. The key stays on this machine.`,`API key`);let n=V(await dr({message:`Bing Webmaster API key`,validate:e=>e?.trim()?void 0:`API key is required`})),r=await new qt({apiKey:n}).listSites(),i=await Hi({project:D(e),options:{json:t}}),a=await Hc({explicitSite:E(e.site),projectSite:i?.siteUrl,savedSite:he(i),sites:r.sites,json:t});if(!a)throw new y(`PROPERTY_NOT_FOUND`,`No verified Bing Webmaster sites were found for this API key.`);await ue(v,n);let o=i?ke(i.id,a):void 0;w(`${a}${o?` is attached to ${o.name}.`:` is ready for direct reports.`}`,`Bing connected`),ur(o?`Run seo providers bing report --project ${o.id}`:`Run seo providers bing report --site ${a}`)}}),status:C({meta:{name:`status`,description:`Show the local Bing connection`},args:{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=await re({name:v,envVar:Ve}),n=await Hi({project:D(e),options:{json:N(e)}}),r={connected:!!t,credentialSource:t?.source,project:n?{id:n.id,name:n.name,site:he(n)}:void 0};if(N(e)){R(r);return}z([[`Connected`,r.connected?`yes`:`no`],[`Credential`,r.credentialSource??`missing`],[`Project`,r.project?.name??`not selected`],[`Bing site`,r.project?.site??`not attached`]])}}),disconnect:C({meta:{name:`disconnect`,description:`Remove the saved Bing Webmaster credential`},args:{json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{await We(v);let t={disconnected:!0,note:`Saved project site mappings were kept for a later reconnect.`};N(e)?R(t):process.stdout.write(`${t.note}\n`)}}),sites:C({meta:{name:`sites`,description:`List Bing Webmaster sites`},args:{json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let{client:t,credentialSource:n}=await Re(),r=await t.listSites(),i={...r,credentialSource:n};if(N(e)){R(i);return}B([`Site`,`Verified`],r.sites.map(e=>[e.url,e.isVerified?`yes`:`no`]))}}),report:C({meta:{name:`report`,description:`Find Bing traffic, crawl, query, and page insights`},args:{...Uc,json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=N(e),n=await Hi({project:D(e),options:{json:t}}),r=E(e.site)??he(n);if(!r)throw new y(`INVALID_INPUT`,`Pass --site or connect Bing Webmaster to a saved project first.`);let i=await a({site:r});if(t){R(i);return}let o=i.traffic.status===`unavailable`?void 0:i.traffic.data,s=i.crawl.status===`unavailable`?void 0:i.crawl.data,c=o?.analysis;K({title:`Bing Webmaster report`,target:i.site,status:i.dataStatus===`complete`?i.summary.warnings>0?`warning`:`pass`:`unknown`,summary:i.summary.findings>0?`${i.summary.findings} findings need review across Bing traffic and crawl evidence.`:`No findings were returned by the available Bing evidence.`,metrics:[{label:`Evidence`,value:i.dataStatus},{label:`28-day clicks`,value:c?.current.clicks??`Unavailable`},{label:`Click change`,value:c?.changes.clicksPercent===null||c?.changes.clicksPercent===void 0?`Unavailable`:`${c.changes.clicksPercent.toFixed(1)}%`},{label:`28-day impressions`,value:c?.current.impressions??`Unavailable`},{label:`Impression change`,value:c?.changes.impressionsPercent===null||c?.changes.impressionsPercent===void 0?`Unavailable`:`${c.changes.impressionsPercent.toFixed(1)}%`},{label:`Crawled pages`,value:s?.latest?.crawledPages??`Unavailable`},{label:`4xx responses`,value:s?.latest?.code4xx??`Unavailable`},{label:`Findings`,value:i.summary.findings}],diagnostics:[{title:`Review first`,items:i.findings.slice(0,5).map(e=>({status:e.severity===`warning`?`warning`:`info`,title:e.title,explanation:e.interpretation,evidence:[e.verification]}))}],notes:i.caveats})}})}})}});function Gc(e){let t=e.indexed+e.notIndexed+e.unknown+e.warnings;return t?`${e.indexed}/${t} indexed`:`-`}function Kc(e){if(!e.samples.length)return`-`;let t=e.medianWordCount===void 0?`words ?`:`${U(e.medianWordCount)} words`,n=e.weakQueryCoverage?`; ${e.weakQueryCoverage} weak coverage`:``;return`${t}; ${e.usable}/${e.samples.length} usable${n}`}function qc(e){return e===`serp-framing-review`?`wording review`:e===`body-term-review`?`literal term review`:`covered terms`}function Jc(e){let t=e.staticSegments.map(e=>`/${e.value}`).join(``),n=e.variableSegments.slice(0,3).map(e=>{let t=e.examples.slice(0,3).join(`, `);return`${e.placeholder}@${e.index+1}${t?` (${t})`:``}`});return[t?`static ${t}`:`no fixed path prefix`,n.length?`variables ${n.join(`; `)}`:`no variables`].join(`; `)}function Yc(e){if(e.checkedQueries)return`${Math.round(e.impressionShare*100)}% impression fit across ${me(e.checkedQueries,`checked query/page row`)}`}function Xc(e){return e.startsWith(`theme: `)?`${e.slice(7)}-related`:e===`general`?`broad`:e}function Zc(e){for(let t of e.slice(0,5)){process.stdout.write(`\n${t.signature}\n`),t.evidence.length&&process.stdout.write(` Evidence: ${t.evidence.join(`; `)}.\n`),process.stdout.write(` Shape: ${Jc(t.shape)}\n`);let e=Yc(t.metrics.entityFit);e&&process.stdout.write(` Entity fit: ${e}\n`);let n=t.metrics.queryPatterns.slice(0,3).map(e=>`${Xc(e.label)} (${U(e.impressions)} impr: ${e.examples.slice(0,2).join(`; `)})`);n.length&&process.stdout.write(` Demand: ${n.join(` | `)}\n`);let r=t.metrics.topQueries.slice(0,3);if(r.length){process.stdout.write(` Top queries:
207
+ `);for(let e of r)process.stdout.write(` - ${G(e.query,72)} (${U(e.impressions)} impr, pos ${Zi(e.position)})\n`)}let i=t.crawl.samples.map(e=>e.queryCoverage).filter(e=>!!e).slice(0,3);if(i.length){process.stdout.write(` Sample coverage:
208
+ `);for(let e of i){let t=e.missingTerms.length?` missing ${e.missingTerms.slice(0,4).join(`, `)}`:``;process.stdout.write(` - ${qc(e.classification)}: ${G(e.query,64)} (body ${(e.bodyCoverage*100).toFixed(0)}%${t})\n`)}}let a=t.metrics.entityFit.weakExamples.slice(0,2);if(a.length){process.stdout.write(` Weak entity-fit examples:
209
+ `);for(let e of a){let t=e.pathTerms.slice(0,4);process.stdout.write(` - ${G(e.query,64)} -> expected ${me(t.length,`path term`)}: ${t.join(`, `)}\n`)}}process.stdout.write(` Action: ${t.recommendation}\n`)}}const Qc=C({meta:{name:`pseo`,description:`Programmatic SEO audits that cluster URL patterns and sample template pages`},subCommands:{audit:C({meta:{name:`audit`,description:`Group GSC/sitemap URLs into pSEO templates, then sample pages for crawl and index evidence`},args:{site:{type:`string`},project:{type:`string`,description:`Saved project id or name.`},client:{type:`string`,description:`Legacy alias for --project.`},...X([`days`,`limit`,`includeBrand`,`js`,`refresh`],{limit:{description:`Maximum templates to show. Defaults to 25.`},includeBrand:{description:`Include branded queries in template metrics.`},js:{description:`Force JavaScript rendering for crawl samples.`},refresh:{description:`Bypass local GSC and HTTP cache.`}}),sitemap:{type:`string`,description:`Comma-separated sitemap URLs to include in template counts.`},"crawl-samples":{type:`string`,description:`Sample URLs to fetch per detected template. This is not crawl depth.`},"inspect-samples":{type:`string`,description:`Sample URLs to check per detected template with URL Inspection.`},"max-sitemap-urls":{type:`string`,description:`Maximum discovered URLs per sitemap. Defaults to 50000.`},"minimum-template-urls":{type:`string`,description:`Minimum repeated URLs required for a template. Defaults to 3.`},"minimum-template-share":{type:`string`,description:`Minimum share of discovered URLs from 0 to 1. Defaults to 0.`},"minimum-template-impressions":{type:`string`,description:`Minimum retained page impressions for a template. Defaults to 0.`},"brand-terms":{type:`string`,description:`Comma-separated brand terms to exclude from query evidence.`},"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.`},json:{type:`boolean`,default:!1}},run:async({args:e})=>{let t=N(e),n=Ls(e),r=await H({client:D(e),site:E(e.site),options:{json:t,refresh:O(e.refresh)}}),i=await it({site:r.site,...n,brandTerms:n.brandTerms??r.client?.brandTerms,progress:cs(!t)});if(t){R(i);return}if(K({title:`Programmatic SEO audit`,target:i.site,status:i.templates.length>0?`info`:`unknown`,summary:i.templates.length>0?`${U(i.summary.templates)} page templates were analysed.`:`No page templates could be established from the available evidence.`,metrics:[{label:`Templates`,value:U(i.summary.templates)},{label:`GSC pages`,value:U(i.summary.gscPages)},{label:`Sitemap URLs`,value:U(i.summary.sitemapUrls)},{label:`Clicks`,value:U(i.summary.clicks)},{label:`Impressions`,value:U(i.summary.impressions)},{label:`Usable crawls`,value:`${U(i.summary.crawledUrls)}/${U(i.summary.crawlAttempts)}`},{label:`Inspections`,value:`${U(i.summary.inspectedUrls)}/${U(i.summary.inspectionAttempts)}`}]}),Y(`Report caveats`,i.caveats),!i.templates.length){process.stdout.write(`No pSEO templates found from GSC/sitemap data.
210
+ `);return}q([`Verdict`,`Template`,`URLs`,`Clicks`,`Impr`,`CTR`,`Pos`,`Index`,`Crawl`,`Action`],i.templates.map(e=>[`${e.verdict} (${e.confidence})`,G(e.signature,28),U(e.urlCount),U(e.metrics.clicks),U(e.metrics.impressions),W(e.metrics.ctr),e.metrics.position.toFixed(1),Gc(e.inspection),Kc(e.crawl),G(e.recommendation,72)])),Zc(i.templates),i.warnings.length&&process.stdout.write(`Warnings: ${i.warnings.join(`; `)}\n`),ta(`seo export pseo ${r.client?`--project ${JSON.stringify(r.client.id)}`:`--site ${JSON.stringify(r.site)}`}`)}})}}),$c={"ai-search":`AI search`,crawl:`Crawling and technical checks`,diagnosis:`Diagnosis`,experiments:`Experiments`,monitoring:`Monitoring`,opportunities:`Opportunities`,reporting:`Reporting`,setup:`Setup`,workflows:`Workflows`};function el(e){let t=E(e);if(t){if(!en.includes(t))throw new y(`INVALID_INPUT`,`Unknown report category: ${t}. Choose ${en.join(`, `)}.`);return t}}async function tl(e,t){let n=E(e),r=E(t);if(n&&r)throw new y(`INVALID_INPUT`,`Use either --params or --params-file, not both.`);if(!n&&!r)return{};let i=n??await Yn(r??``,`utf8`),a;try{a=JSON.parse(i)}catch{throw new y(`INVALID_INPUT`,`Report parameters must be valid JSON.`)}if(!a||typeof a!=`object`||Array.isArray(a))throw new y(`INVALID_INPUT`,`Report parameters must be an object.`);return a}function nl(e){if(!e.isError)return;let t=e.structuredContent;throw new y(t?.error?.code??`INTERNAL_ERROR`,t?.error?.message??`The report could not be completed.`)}function rl(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 il=C({meta:{name:`reports`,description:`Discover and run the complete report catalog`},subCommands:{list:C({meta:{name:`list`,description:`List report ids available to the CLI and MCP server`},args:{category:{type:`string`,description:`Filter by category: ${en.join(`, `)}.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=Vt(el(e.category));if(N(e)){R({reports:t,categories:en});return}Ni(t,{noun:`report`,categoryLabels:$c}),process.stdout.write(`
211
+ `),ki({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:C({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`."},check:{type:`string`,description:`Show fix guidance for one check id from the report output.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=E(e.check);if(t){let n=wt(E(e.id)??``,t);if(N(e)){R({report:n});return}Ai(`${n.name}: ${n.check}`,n.checkFix.goal),process.stdout.write(`
212
+ `),Fi(`Fix`,n.checkFix.fix),process.stdout.write(`
213
+ `),Fi(`Agent prompt`,n.checkFix.prompt),process.stdout.write(`
214
+ `),Fi(`Verify`,n.checkFix.verify),n.checkFix.resources.length>0&&(process.stdout.write(`
215
+ `),Ii(`Resources`,n.checkFix.resources.map(e=>`${e.title}: ${e.url}`))),process.stdout.write(`
216
+ `);return}let n=Yt(E(e.id)??``);if(N(e)){R({report:n});return}Ai(n.name,n.description),process.stdout.write(`
217
+ `),z([[`ID`,n.id],[`Category`,n.category],[`Outcome`,n.outcome]]),process.stdout.write(`
218
+ `),Ii(`Use when`,n.useWhen),process.stdout.write(`
219
+ `),Ii(`Avoid when`,n.avoidWhen),process.stdout.write(`
220
+ `),Ii(`Read in order`,n.readOrder),process.stdout.write(`
221
+ `),Ii(`Do not claim`,n.doNotClaim),process.stdout.write(`
222
+ `),Fi(`Verify`,n.verify),n.related.length>0&&(process.stdout.write(`
223
+ `),Ii(`Related reports`,n.related.map(e=>`${e.id}: ${e.reason}`))),process.stdout.write(`
146
224
  Parameters
147
- `),Xr($s(n.inputSchema)),n.fixableChecks&&(process.stdout.write(`
148
- `),Wr({title:`Fix a failed check`,body:`Get the fix steps, an agent prompt, and spec links for one check id from the report output.`,command:`seo reports describe ${n.id} --check <check-id>`})),process.stdout.write(`
149
- `),Wr({title:`Need the exact schema?`,command:`seo reports describe ${n.id} --json`})}}),run:S({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.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=await Be(T(e.id)??``,await Zs(e.params,e[`params-file`]));if(Qs(t),M(e)){L(t.structuredContent??{});return}for(let e of t.content)process.stdout.write(`${e.text}\n`)}})}}),tc=S({meta:{name:`monthly-report`,description:`Generate a monthly SEO report narrative`},args:{..._o,month:{type:`string`,description:`Report month as YYYY-MM. Defaults to the latest month with final GSC data.`},...X([`limit`],{limit:{description:`Maximum rows per diagnostic section. Defaults to 10.`}}),...vo},run:async({args:e})=>{let t=M(e),n=bo(e),r=await U({client:n.client,site:n.site,options:{json:t,refresh:n.refresh}}),i=await ue({site:r.site,month:T(e.month),limit:O(e.limit),brandTerms:r.client?.brandTerms,...yo(e),progress:go(!t)});if(t){L(i);return}process.stdout.write(`${i.markdown}\n`);let a=r.client?`--project ${JSON.stringify(r.client.id)}`:`--site ${JSON.stringify(r.site)}`,o=T(e.month);process.stdout.write(`
150
- `),ii(`seo export monthly ${a}${o?` --month ${JSON.stringify(o)}`:``}`)}}),nc=S({meta:{name:`report-narrative`,description:`Generate a client-ready SEO narrative from diagnosis data`},args:{..._o,...X([`days`,`recentDays`],{days:{description:`Diagnosis window length in days. Defaults to 90.`}}),"start-date":{type:`string`,description:`Optional report start date as YYYY-MM-DD.`},"end-date":{type:`string`,description:`Optional report end date as YYYY-MM-DD.`},...X([`limit`],{limit:{description:`Maximum rows per diagnostic section. Defaults to 10.`}}),"change-limit":{type:`string`,description:`Maximum saved changes to measure. Defaults to 5.`},...vo},run:async({args:e})=>{let t=M(e),n=bo(e),r=await U({client:n.client,site:n.site,options:{json:t,refresh:n.refresh}}),i=await me({site:r.site,days:O(e.days),recentDays:O(e.recent),startDate:T(e[`start-date`]),endDate:T(e[`end-date`]),limit:O(e.limit),changeLimit:O(e[`change-limit`]),brandTerms:r.client?.brandTerms,...yo(e),progress:go(!t)});if(t){L(i);return}process.stdout.write(`${i.markdown}\n`);let a=r.client?`--project ${JSON.stringify(r.client.id)}`:`--site ${JSON.stringify(r.site)}`;process.stdout.write(`
151
- `),ii(`seo export narrative ${a}`)}}),rc=S({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=T(e.category),n=ye().filter(e=>!t||e.category===t);if(M(e)){L({rules:n});return}qr(n.map(e=>({id:e.id,category:e.category,name:`${e.title} · ${e.defaultSeverity}`})),{noun:`rule`})}}),ic=S({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=T(e.rule)??``,n=se(t);if(!n)throw Error(`Unknown rule: ${t}`);if(M(e)){L(n);return}R([[`Rule`,n.id],[`Title`,n.title],[`Category`,n.category],[`Severity`,n.defaultSeverity]]),process.stdout.write(`
152
- `),B(`Why it matters`,n.whyItMatters),process.stdout.write(`
153
- `),B(`How to fix`,n.howToFix),process.stdout.write(`
154
- `),B(`Impact if ignored`,n.impactIfIgnored),process.stdout.write(`
155
- `),B(`How to verify`,n.howToVerify)}});function ac(e){return JSON.stringify(e)}const oc=S({meta:{name:`schedule`,description:`Print local cron entries for recurring SEO workflows`},subCommands:{cron:S({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.`},day:{type:`string`,description:`Monthly report day. Defaults to client setting, then 1.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=M(e),n=await U({client:E(e),site:T(e.site),options:{json:t}}),r=n.site,i=T(e.url)??n.client?.startUrl??xi(n.site),a=O(e.hour)??9,o=O(e.minute)??0,s=O(e.weekday)??n.client?.technicalWeekday??1,c=O(e.day)??n.client?.reportDay??1,l=n.client?`--project ${ac(n.client.id)}`:`--site ${ac(r)}`,u=T(e.urls)??n.client?.watchUrls.join(`,`),d=[{name:`technical-watch`,cron:`${o} ${a} * * ${s}`,command:[`seo technical-watch`,l,i?`--url ${ac(i)}`:void 0,u?`--urls ${ac(u)}`:void 0,`--json`].filter(Boolean).join(` `)},{name:`monthly-report`,cron:`${o} ${a} ${c} * *`,command:[`seo monthly-report`,l,`--json`].join(` `)}];if(t){L({site:r,lines:d});return}let f=Ht();R([[`Property`,r],[`Install`,`crontab -e`]]);for(let e of d)process.stdout.write(`${e.cron} seo logs prune --quiet; ${e.command} >> ${ac(`${f.logDir}/${e.name}.log`)} 2>&1\n`)}})}});function sc(){return lc(`skills`,process.env.SEO_SKILLS_DIR)}function cc(){return lc(`evals`,process.env.SEO_EVALS_DIR)}function lc(e,t){if(t)return _n(t);let n=b(Sn(import.meta.url));for(let t=0;t<6;t+=1){let t=x(n,e);if(y(x(t,`README.md`)))return t;n=b(n)}throw new v(`INTERNAL_ERROR`,`Packaged ${e} could not be found. Reinstall \`seo\` and try again.`)}function uc(e){return an(x(e,`SKILL.md`),`utf8`).match(/^description:\s*(.+)$/m)?.[1]?.trim()??``}const dc=`claude -p`;function fc(){let e=cc();return on(e,{withFileTypes:!0}).filter(e=>e.isFile()&&e.name.endsWith(`.json`)).map(t=>({subject:pn(t.name,`.json`),path:x(e,t.name)})).sort((e,t)=>e.subject<t.subject?-1:+(e.subject>t.subject))}function pc(e,t){let n;try{n=an(t,`utf8`)}catch{throw new v(`INVALID_INPUT`,`Cannot read eval file for ${e}.`)}let r;try{r=JSON.parse(n)}catch{throw new v(`INVALID_INPUT`,`Eval file ${e}.json is not valid JSON.`)}if(!r||typeof r!=`object`||Array.isArray(r))throw new v(`INVALID_INPUT`,`Eval file ${e}.json must be an object.`);let i=r;if(i.subject!==e)throw new v(`INVALID_INPUT`,`Eval file ${e}.json must set subject to "${e}".`);if(!Array.isArray(i.evals)||i.evals.length===0)throw new v(`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 v(`INVALID_INPUT`,`${i}: id must be an integer.`);let o=r.id;if(a.has(o))throw new v(`INVALID_INPUT`,`${i}: duplicate id ${o}.`);if(a.add(o),typeof r.prompt!=`string`||r.prompt.trim().length===0)throw new v(`INVALID_INPUT`,`${i}: prompt must be a non-empty string.`);if(typeof r.expected_output!=`string`||r.expected_output.trim().length===0)throw new v(`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 v(`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 v(`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 mc(){let e=x(sc(),`seo`,`SKILL.md`);if(!y(e))throw new v(`INTERNAL_ERROR`,"The seo router skill could not be found. Reinstall `seo` or use --no-skill.");return an(e,`utf8`)}function hc(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===`
156
- `||s===`\r`){r&&=(t.push(n),n=``,!1);continue}n+=s,r=!0}if(i||a)throw new v(`INVALID_INPUT`,`Unbalanced quote in agent command.`);return r&&t.push(n),t}function gc(e,t){let n=hc(e);if(n.length===0)throw new v(`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 _c(e,t){return e?`${e.trim()}\n\n---\n\nUser request:\n${t}`:t}function vc(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(`
225
+ `),Li(rl(n.inputSchema)),n.fixableChecks&&(process.stdout.write(`
226
+ `),ki({title:`Fix a failed check`,body:`Get the fix steps, an agent prompt, and spec links for one check id from the report output.`,command:`seo reports describe ${n.id} --check <check-id>`})),process.stdout.write(`
227
+ `),ki({title:`Need the exact schema?`,command:`seo reports describe ${n.id} --json`})}}),run:C({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.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=await nt(E(e.id)??``,await tl(e.params,e[`params-file`]));if(nl(t),N(e)){R(t.structuredContent??{});return}for(let e of t.content)process.stdout.write(`${e.text}\n`)}})}}),al={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 ol(e){let t=E(e.format)??`terminal`;if(t!==`terminal`&&t!==`html`)throw new y(`INVALID_INPUT`,`Choose terminal or html for --format.`);if(t===`terminal`){if(E(e.output)||E(e.view))throw new y(`INVALID_INPUT`,`Use --output and --view with --format html.`);return}if(O(e.json))throw new y(`INVALID_INPUT`,`Use either --json or --format html, not both.`);let n=E(e.view)??`client`;if(n!==`client`&&n!==`analyst`)throw new y(`INVALID_INPUT`,`Choose client or analyst for --view.`);return{output:E(e.output),view:n}}function sl(e){return e.toLowerCase().replace(/^sc-domain:/,``).replace(/^https?:\/\//,``).replace(/[^a-z0-9._-]+/g,`-`).replace(/^-+|-+$/g,``).slice(0,80)}function cl(e){let t=(e.date??new Date).toISOString().slice(0,10),n=sl(e.projectId??e.site)||`site`;return Rn(process.cwd(),`${n}-${sl(e.reportName)}-${t}.html`)}async function ll(e){let t=Rn(e.options.output??cl({reportName:e.reportName,projectId:e.projectId,site:e.report.site}));return await qn(x(t),{recursive:!0}),await tr(t,Ar({report:e.report,title:e.title,view:e.options.view,additionalSections:e.additionalSections}),`utf8`),t}function ul(e){process.stdout.write(`Wrote HTML report to ${e}\n`)}const dl=C({meta:{name:`monthly-report`,description:`Generate a monthly SEO report narrative`},args:{...ls,month:{type:`string`,description:`Report month as YYYY-MM. Defaults to the latest month with final GSC data.`},...X([`limit`],{limit:{description:`Maximum rows per diagnostic section. Defaults to 10.`}}),...us,...al},run:async({args:e})=>{let t=N(e),n=ol(e),r=fs(e),i=await H({client:r.client,site:r.site,options:{json:t,refresh:r.refresh}}),a=await nn({site:i.site,month:E(e.month),limit:k(e.limit),brandTerms:i.client?.brandTerms,...ds(e),progress:cs(!t)});if(t){R(a);return}if(n){ul(await ll({report:a,reportName:`monthly-report`,title:`Monthly SEO report for ${a.month}`,options:n,projectId:i.client?.id}));return}process.stdout.write(`${a.markdown}\n`);let o=i.client?`--project ${JSON.stringify(i.client.id)}`:`--site ${JSON.stringify(i.site)}`,s=E(e.month);process.stdout.write(`
228
+ `),ta(`seo export monthly ${o}${s?` --month ${JSON.stringify(s)}`:``}`)}}),fl=C({meta:{name:`report-narrative`,description:`Generate a client-ready SEO narrative from diagnosis data`},args:{...ls,...X([`days`,`recentDays`],{days:{description:`Diagnosis window length in days. Defaults to 90.`}}),"start-date":{type:`string`,description:`Optional report start date as YYYY-MM-DD.`},"end-date":{type:`string`,description:`Optional report end date as YYYY-MM-DD.`},...X([`limit`],{limit:{description:`Maximum rows per diagnostic section. Defaults to 10.`}}),"change-limit":{type:`string`,description:`Maximum saved changes to measure. Defaults to 5.`},...us,...al},run:async({args:e})=>{let t=N(e),n=ol(e),r=fs(e),i=await H({client:r.client,site:r.site,options:{json:t,refresh:r.refresh}}),a=await Bt({site:i.site,days:k(e.days),recentDays:k(e.recent),startDate:E(e[`start-date`]),endDate:E(e[`end-date`]),limit:k(e.limit),changeLimit:k(e[`change-limit`]),brandTerms:i.client?.brandTerms,...ds(e),progress:cs(!t)});if(t){R(a);return}if(n){ul(await ll({report:a,reportName:`narrative-report`,title:`SEO narrative report`,options:n,projectId:i.client?.id}));return}process.stdout.write(`${a.markdown}\n`);let o=i.client?`--project ${JSON.stringify(i.client.id)}`:`--site ${JSON.stringify(i.site)}`;process.stdout.write(`
229
+ `),ta(`seo export narrative ${o}`)}}),pl=C({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=E(e.category),n=Ht().filter(e=>!t||e.category===t);if(N(e)){R({rules:n});return}Ni(n.map(e=>({id:e.id,category:e.category,name:`${e.title} · ${e.defaultSeverity}`})),{noun:`rule`})}}),ml=C({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=E(e.rule)??``,n=rt(t);if(!n)throw Error(`Unknown rule: ${t}`);if(N(e)){R(n);return}z([[`Rule`,n.id],[`Title`,n.title],[`Category`,n.category],[`Severity`,n.defaultSeverity]]),process.stdout.write(`
230
+ `),Fi(`Why it matters`,n.whyItMatters),process.stdout.write(`
231
+ `),Fi(`How to fix`,n.howToFix),process.stdout.write(`
232
+ `),Fi(`Impact if ignored`,n.impactIfIgnored),process.stdout.write(`
233
+ `),Fi(`How to verify`,n.howToVerify)}});function hl(e){return JSON.stringify(e)}const gl=C({meta:{name:`schedule`,description:`Print local cron entries for recurring SEO workflows`},subCommands:{cron:C({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.`},day:{type:`string`,description:`Monthly report day. Defaults to client setting, then 1.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=N(e),n=await H({client:D(e),site:E(e.site),options:{json:t}}),r=n.site,i=E(e.url)??n.client?.startUrl??da(n.site),a=k(e.hour)??9,s=k(e.minute)??0,c=k(e.weekday)??n.client?.technicalWeekday??1,l=k(e.day)??n.client?.reportDay??1,u=n.client?`--project ${hl(n.client.id)}`:`--site ${hl(r)}`,d=E(e.urls)??n.client?.watchUrls.join(`,`),f=[{name:`technical-watch`,cron:`${s} ${a} * * ${c}`,command:[`seo technical-watch`,u,i?`--url ${hl(i)}`:void 0,d?`--urls ${hl(d)}`:void 0,`--json`].filter(Boolean).join(` `)},{name:`monthly-report`,cron:`${s} ${a} ${l} * *`,command:[`seo monthly-report`,u,`--json`].join(` `)}];if(t){R({site:r,lines:f});return}let p=o();z([[`Property`,r],[`Install`,`crontab -e`]]);for(let e of f)process.stdout.write(`${e.cron} seo logs prune --quiet; ${e.command} >> ${hl(`${p.logDir}/${e.name}.log`)} 2>&1\n`)}})}}),_l=C({meta:{name:`server-logs`,description:`Analyze local web server access logs`},subCommands:{analyze:C({meta:{name:`analyze`,description:`Stream a local access log into bounded crawler evidence`},args:{file:{type:`string`,description:`Local combined, JSONL, or NDJSON access log.`,required:!0},format:{type:`string`,description:`Input format override: combined or jsonl.`},"row-limit":{type:`string`,description:`Maximum rows to parse. Defaults to 1000000.`},"path-limit":{type:`string`,description:`Maximum crawler and path aggregates to retain. Defaults to 25000.`},limit:{type:`string`,description:`Maximum crawler path rows returned. Defaults to 100.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=E(e.file);if(!t)throw new y(`INVALID_INPUT`,`Pass --file.`);let n=E(e.format);if(n&&![`combined`,`jsonl`].includes(n))throw new y(`INVALID_INPUT`,`--format must be combined or jsonl.`);let r=sn({evidence:await gn({file:t,format:n,rowLimit:A(e[`row-limit`],`--row-limit`),pathLimit:A(e[`path-limit`],`--path-limit`)}),limit:A(e.limit,`--limit`)});if(N(e)){R(r);return}K({title:`Server log report`,status:r.dataStatus===`complete`?r.summary.invalidRows>0?`warning`:`info`:`unknown`,summary:`${U(r.summary.crawlerRows)} crawler requests found in ${U(r.summary.parsedRows)} parsed requests.`,metrics:[{label:`Evidence`,value:r.dataStatus},{label:`Parsed requests`,value:U(r.summary.parsedRows)},{label:`Crawler requests`,value:U(r.summary.crawlerRows)},{label:`Crawler families`,value:U(r.crawlers.length)},{label:`Invalid rows`,value:U(r.summary.invalidRows)},{label:`Returned paths`,value:`${U(r.selection.returnedCrawlerPaths)} of ${U(r.selection.availableCrawlerPaths)}`}]}),r.crawlers.length&&(process.stdout.write(`
234
+ Crawler activity
235
+ `),q([`Crawler`,`Type`,`Requests`,`4xx`,`5xx`],r.crawlers.map(e=>[e.family,e.category,String(e.requests),String(e.clientError),String(e.serverError)]))),r.crawlerPaths.length&&(process.stdout.write(`
236
+ Crawler paths
237
+ `),q([`Crawler`,`Path`,`Requests`,`4xx`,`5xx`],r.crawlerPaths.map(e=>[e.family,G(e.path,72),String(e.requests),String(e.clientError),String(e.serverError)])));for(let e of r.warnings)process.stdout.write(`\nWarning: ${e}\n`);process.stdout.write(`\nNote: ${r.caveats[0]}\n`)}})}});function vl(){return bl(`skills`,process.env.SEO_SKILLS_DIR)}function yl(){return bl(`evals`,process.env.SEO_EVALS_DIR)}function bl(e,t){if(t)return Rn(t);let n=x(Un(import.meta.url));for(let t=0;t<6;t+=1){let t=S(n,e);if(b(S(t,`README.md`)))return t;n=x(n)}throw new y(`INTERNAL_ERROR`,`Packaged ${e} could not be found. Reinstall \`seo\` and try again.`)}function xl(e){return En(S(e,`SKILL.md`),`utf8`).match(/^description:\s*(.+)$/m)?.[1]?.trim()??``}const Sl=`claude -p`;function Cl(){let e=yl();return Dn(e,{withFileTypes:!0}).filter(e=>e.isFile()&&e.name.endsWith(`.json`)).map(t=>({subject:Pn(t.name,`.json`),path:S(e,t.name)})).sort((e,t)=>e.subject<t.subject?-1:+(e.subject>t.subject))}function wl(e,t){let n;try{n=En(t,`utf8`)}catch{throw new y(`INVALID_INPUT`,`Cannot read eval file for ${e}.`)}let r;try{r=JSON.parse(n)}catch{throw new y(`INVALID_INPUT`,`Eval file ${e}.json is not valid JSON.`)}if(!r||typeof r!=`object`||Array.isArray(r))throw new y(`INVALID_INPUT`,`Eval file ${e}.json must be an object.`);let i=r;if(i.subject!==e)throw new y(`INVALID_INPUT`,`Eval file ${e}.json must set subject to "${e}".`);if(!Array.isArray(i.evals)||i.evals.length===0)throw new y(`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 y(`INVALID_INPUT`,`${i}: id must be an integer.`);let o=r.id;if(a.has(o))throw new y(`INVALID_INPUT`,`${i}: duplicate id ${o}.`);if(a.add(o),typeof r.prompt!=`string`||r.prompt.trim().length===0)throw new y(`INVALID_INPUT`,`${i}: prompt must be a non-empty string.`);if(typeof r.expected_output!=`string`||r.expected_output.trim().length===0)throw new y(`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 y(`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 y(`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 Tl(){let e=S(vl(),`seo`,`SKILL.md`);if(!b(e))throw new y(`INTERNAL_ERROR`,"The seo router skill could not be found. Reinstall `seo` or use --no-skill.");return En(e,`utf8`)}function El(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===`
238
+ `||s===`\r`){r&&=(t.push(n),n=``,!1);continue}n+=s,r=!0}if(i||a)throw new y(`INVALID_INPUT`,`Unbalanced quote in agent command.`);return r&&t.push(n),t}function Dl(e,t){let n=El(e);if(n.length===0)throw new y(`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 Ol(e,t){return e?`${e.trim()}\n\n---\n\nUser request:\n${t}`:t}function kl(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(`
157
239
  `),`</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(`
158
- `);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 yc(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 bc(e,t){let n=yc(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 xc(e,t){return new Promise(n=>{let r=Date.now(),[i,...a]=e,o;try{o=bn(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 Sc(e,t,n){let{argv:r,usesPlaceholder:i}=gc(e,t);return xc(r,{input:i?void 0:t,timeoutMs:n})}async function Cc(e,t){let n=Date.now(),r=_c(t.skillBody,e.prompt),i=await Sc(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:wc(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:wc(e.assertions)};if(a.length===0)return{...o,status:`errored`,error:`Agent returned no output on stdout.`,durationMs:Date.now()-n,assertions:wc(e.assertions)};if(!t.judge)return{...o,status:`unjudged`,durationMs:Date.now()-n,assertions:wc(e.assertions)};let s=0,c=``;for(let r=0;r<2;r+=1){let i=vc(e.prompt,a,e.assertions,r>0),l=await Sc(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:wc(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:wc(e.assertions)};let u=bc(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:wc(e.assertions)}}function wc(e){return e.map(e=>({assertion:e,verdict:`unjudged`}))}async function Tc(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 Ec(e){let t={passed:0,failed:0,errored:0,unjudged:0};for(let n of e)t[n.status]+=1;return t}function Dc(e){let t=[],n=T(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 Oc(e){let t=fc();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 v(`INVALID_INPUT`,`Unknown eval subject: ${e}.${n?` Known subjects: ${n}.`:``}`)}return r})}function kc(e){let t=fc().map(e=>{let t=pc(e.subject,e.path);return{subject:e.subject,evals:t.evals.length}});if(e){L({schemaVersion:1,subjects:t});return}if(t.length===0){process.stdout.write(`No eval subjects found.
240
+ `);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 Al(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 jl(e,t){let n=Al(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 Ml(e,t){return new Promise(n=>{let r=Date.now(),[i,...a]=e,o;try{o=Vn(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 Nl(e,t,n){let{argv:r,usesPlaceholder:i}=Dl(e,t);return Ml(r,{input:i?void 0:t,timeoutMs:n})}async function Pl(e,t){let n=Date.now(),r=Ol(t.skillBody,e.prompt),i=await Nl(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:Fl(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:Fl(e.assertions)};if(a.length===0)return{...o,status:`errored`,error:`Agent returned no output on stdout.`,durationMs:Date.now()-n,assertions:Fl(e.assertions)};if(!t.judge)return{...o,status:`unjudged`,durationMs:Date.now()-n,assertions:Fl(e.assertions)};let s=0,c=``;for(let r=0;r<2;r+=1){let i=kl(e.prompt,a,e.assertions,r>0),l=await Nl(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:Fl(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:Fl(e.assertions)};let u=jl(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:Fl(e.assertions)}}function Fl(e){return e.map(e=>({assertion:e,verdict:`unjudged`}))}async function Il(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 Ll(e){let t={passed:0,failed:0,errored:0,unjudged:0};for(let n of e)t[n.status]+=1;return t}function Rl(e){let t=[],n=E(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 zl(e){let t=Cl();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 y(`INVALID_INPUT`,`Unknown eval subject: ${e}.${n?` Known subjects: ${n}.`:``}`)}return r})}function Bl(e){let t=Cl().map(e=>{let t=wl(e.subject,e.path);return{subject:e.subject,evals:t.evals.length}});if(e){R({schemaVersion:1,subjects:t});return}if(t.length===0){process.stdout.write(`No eval subjects found.
159
241
  `);return}let n=Math.max(...t.map(e=>e.subject.length));process.stdout.write(`Eval subjects
160
- `);for(let e of t){let t=e.evals===1?`eval`:`evals`;process.stdout.write(` ${e.subject.padEnd(n)} ${e.evals} ${t}\n`)}}function Ac(e){return e===`passed`?`PASS`:e===`failed`?`FAIL`:e===`errored`?`ERROR`:`UNJUDGED`}function jc(e,t){t.judged?process.stdout.write(`Assertion verdicts below are judge verdicts, model judgments from the judge agent, not ground truth.
242
+ `);for(let e of t){let t=e.evals===1?`eval`:`evals`;process.stdout.write(` ${e.subject.padEnd(n)} ${e.evals} ${t}\n`)}}function Vl(e){return e===`passed`?`PASS`:e===`failed`?`FAIL`:e===`errored`?`ERROR`:`UNJUDGED`}function Hl(e,t){t.judged?process.stdout.write(`Assertion verdicts below are judge verdicts, model judgments from the judge agent, not ground truth.
161
243
 
162
244
  `):process.stdout.write(`Judging was skipped. Review each agent reply against the assertion checklist by hand.
163
245
 
164
- `);for(let n of e){process.stdout.write(`# ${n.subject}\n`);for(let e of n.evals){if(process.stdout.write(`\n[${Ac(e.status)}] eval ${e.id} (${e.durationMs}ms)\n`),process.stdout.write(` prompt: ${Nc(e.prompt,100)}\n`),e.error&&process.stdout.write(` error: ${e.error}\n`),!t.judged){process.stdout.write(` reply: ${Nc(e.reply,200)}\n`),process.stdout.write(` assertions to review:
165
- `);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=Ec(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=Mc(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 Mc(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 Nc(e,t){let n=e.replace(/\s+/g,` `).trim();return n.length>t?`${n.slice(0,t-1)}…`:n}const Pc=S({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:dc,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:w(`Judge assertions with an agent.`,`Skip judging and print replies with an assertion checklist.`),skill:w(`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=M(e);if(e.list===!0){kc(t);return}let n=Oc(Dc(e)),r=O(e.id);if(r!==void 0&&n.length!==1)throw new v(`INVALID_INPUT`,`--id needs exactly one subject. Name the subject to filter.`);let i=O(e.timeout)??300;if(!(i>0))throw new v(`INVALID_INPUT`,`--timeout must be a positive number of seconds.`);let a=Math.max(1,Math.min(4,O(e.concurrency)??1)),o=T(e.agent)??dc,s=e.judge===!1?null:T(e[`judge-agent`])??o,c=e.skill===!1?null:mc(),l={agent:o,judge:s,skillBody:c,timeoutMs:i*1e3},u=[];for(let e of n){let n=pc(e.subject,e.path).evals;if(r!==void 0&&(n=n.filter(e=>e.id===r),n.length===0))throw new v(`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 Tc(n,a,e=>Cc(e,l)),o=Ec(i);u.push({subject:e.subject,...o,evals:i})}let d=Mc(u),f=d.failed>0||d.errored>0;t?L({schemaVersion:1,agent:o,judge:s,judged:s!==null,skillContext:c!==null,timeoutSeconds:i,concurrency:a,subjects:u,totals:d}):jc(u,{judged:s!==null,agent:o,judge:s}),f&&(process.exitCode=1)}});function Fc(){let e=sc();return on(e,{withFileTypes:!0}).filter(t=>t.isDirectory()&&y(x(e,t.name,`SKILL.md`))).map(t=>({name:t.name,description:uc(x(e,t.name)),path:x(e,t.name)})).sort((e,t)=>e.name<t.name?-1:+(e.name>t.name))}function Ic(e){let t=Fc();if(!e)return t;let n=t.find(t=>t.name===e);if(!n)throw new v(`INVALID_INPUT`,`Unknown skill: ${e}.`);return[n]}const Lc=S({meta:{name:`skill`,description:`Inspect the packaged SEO skill and run its evals`},subCommands:{list:S({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=Fc();if(M(e)){L({skills:t});return}Gr(`SEO skill`,`Packaged with this CLI.`),process.stdout.write(`
166
- `);for(let e of t)R([[`Name`,e.name],[`Purpose`,`Helps agents choose and run the right SEO audit or report.`],[`Path`,e.path]]);process.stdout.write(`
167
- `),Wr({title:`Add it to an agent`,body:`Run setup to install the skill and connect your first site.`,command:`seo start`})}}),path:S({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=T(e.name),n=t?Ic(t)[0]?.path:sc();M(e)?L({name:t,path:n}):process.stdout.write(`${n}\n`)}}),install:S({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=M(e),n=Xi({quiet:t});if(t){L(n),n.status===`failed`&&(process.exitCode=1);return}if(n.status===`failed`)throw new v(`INTERNAL_ERROR`,`Skill install failed: ${n.error}`);process.stdout.write(`SEO skill installed. Agents that support skills can discover it now.
168
- `)}}),eval:Pc}}),Rc=S({meta:{name:`privacy`,description:`Show local config, token, cache, and log storage paths`},run:async()=>{let e=$e(),t=Nt();R(e.map(e=>[e.label,`${e.path} · ${Zr(e.sizeBytes)} · ${e.mode}`])),process.stdout.write(`
169
- `),R(Object.entries(t.counts).map(([e,t])=>[e,String(t)]))}}),zc=S({meta:{name:`reset`,description:`Delete local seo config, tokens, cache, and logs`},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=Xt(),n=M(e);if(!e.yes&&!I({json:n}))throw new v(`INVALID_INPUT`,`Cannot prompt here. Pass --yes to confirm reset.`);if(!(e.yes||V(await In({message:`Delete config, tokens, cache, and logs?`,initialValue:!1})))){Fn(`Reset aborted.`);return}await Je(),ln(t.configDir,{recursive:!0,force:!0}),ln(t.cacheDir,{recursive:!0,force:!0}),ln(t.logDir,{recursive:!0,force:!0}),n?L({reset:!0}):process.stdout.write(`Reset complete.
170
- `)}}),Bc=S({meta:{name:`sites`,description:`List Search Console properties available to this Google login`},args:{json:{type:`boolean`,default:!1},refresh:{type:`boolean`,default:!1}},run:async({args:e})=>{let t=await Me(D(e.refresh));if(M(e)){L({sites:t});return}z([`Property`,`Permission`],t.map(e=>[e.siteUrl,e.permissionLevel??`unknown`]))}}),Vc=S({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 lt({product:T(e.product),limit:O(e.limit)});if(M(e)){L({updates:t});return}Jr(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.`})}}),Hc={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 Uc(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 Wc(e,t){let n=Uc(e);if(t){L(n);return}R([[`Anonymous telemetry`,e.enabled?`enabled`:`disabled`],[`Reason`,e.reason?Hc[e.reason]:`local setting`],[`State file`,e.stateFile],[`Install cohort`,e.state?.cohort??`not created`]]),process.stdout.write(`
246
+ `);for(let n of e){process.stdout.write(`# ${n.subject}\n`);for(let e of n.evals){if(process.stdout.write(`\n[${Vl(e.status)}] eval ${e.id} (${e.durationMs}ms)\n`),process.stdout.write(` prompt: ${Wl(e.prompt,100)}\n`),e.error&&process.stdout.write(` error: ${e.error}\n`),!t.judged){process.stdout.write(` reply: ${Wl(e.reply,200)}\n`),process.stdout.write(` assertions to review:
247
+ `);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=Ll(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=Ul(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 Ul(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 Wl(e,t){let n=e.replace(/\s+/g,` `).trim();return n.length>t?`${n.slice(0,t-1)}…`:n}const Gl=C({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:Sl,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:Vr(`Judge assertions with an agent.`,`Skip judging and print replies with an assertion checklist.`),skill:Vr(`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=N(e);if(e.list===!0){Bl(t);return}let n=zl(Rl(e)),r=k(e.id);if(r!==void 0&&n.length!==1)throw new y(`INVALID_INPUT`,`--id needs exactly one subject. Name the subject to filter.`);let i=k(e.timeout)??300;if(!(i>0))throw new y(`INVALID_INPUT`,`--timeout must be a positive number of seconds.`);let a=Math.max(1,Math.min(4,k(e.concurrency)??1)),o=E(e.agent)??Sl,s=e.judge===!1?null:E(e[`judge-agent`])??o,c=e.skill===!1?null:Tl(),l={agent:o,judge:s,skillBody:c,timeoutMs:i*1e3},u=[];for(let e of n){let n=wl(e.subject,e.path).evals;if(r!==void 0&&(n=n.filter(e=>e.id===r),n.length===0))throw new y(`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 Il(n,a,e=>Pl(e,l)),o=Ll(i);u.push({subject:e.subject,...o,evals:i})}let d=Ul(u),f=d.failed>0||d.errored>0;t?R({schemaVersion:1,agent:o,judge:s,judged:s!==null,skillContext:c!==null,timeoutSeconds:i,concurrency:a,subjects:u,totals:d}):Hl(u,{judged:s!==null,agent:o,judge:s}),f&&(process.exitCode=1)}});function Kl(){let e=vl();return Dn(e,{withFileTypes:!0}).filter(t=>t.isDirectory()&&b(S(e,t.name,`SKILL.md`))).map(t=>({name:t.name,description:xl(S(e,t.name)),path:S(e,t.name)})).sort((e,t)=>e.name<t.name?-1:+(e.name>t.name))}function ql(e){let t=Kl();if(!e)return t;let n=t.find(t=>t.name===e);if(!n)throw new y(`INVALID_INPUT`,`Unknown skill: ${e}.`);return[n]}const Jl=C({meta:{name:`skill`,description:`Inspect the packaged SEO skill and run its evals`},subCommands:{list:C({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=Kl();if(N(e)){R({skills:t});return}Ai(`SEO skill`,`Packaged with this CLI.`),process.stdout.write(`
248
+ `);for(let e of t)z([[`Name`,e.name],[`Purpose`,`Helps agents choose and run the right SEO audit or report.`],[`Path`,e.path]]);process.stdout.write(`
249
+ `),ki({title:`Add it to an agent`,body:`Run setup to install the skill and connect your first site.`,command:`seo start`})}}),path:C({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=E(e.name),n=t?ql(t)[0]?.path:vl();N(e)?R({name:t,path:n}):process.stdout.write(`${n}\n`)}}),install:C({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=N(e),n=Ba({quiet:t});if(t){R(n),n.status===`failed`&&(process.exitCode=1);return}if(n.status===`failed`)throw new y(`INTERNAL_ERROR`,`Skill install failed: ${n.error}`);process.stdout.write(`SEO skill installed. Agents that support skills can discover it now.
250
+ `)}}),eval:Gl}}),Yl=C({meta:{name:`privacy`,description:`Show local config, token, cache, and log storage paths`},run:async()=>{let e=Te(),t=jt();z(e.map(e=>[e.label,`${e.path} · ${Ri(e.sizeBytes)} · ${e.mode}`])),process.stdout.write(`
251
+ `),z(Object.entries(t.counts).map(([e,t])=>[e,String(t)]))}}),Xl=C({meta:{name:`reset`,description:`Delete local seo config, tokens, cache, and logs`},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=He(),n=N(e);if(!e.yes&&!L({json:n}))throw new y(`INVALID_INPUT`,`Cannot prompt here. Pass --yes to confirm reset.`);if(!(e.yes||V(await ar({message:`Delete config, tokens, cache, and logs?`,initialValue:!1})))){ir(`Reset aborted.`);return}await ye(),An(t.configDir,{recursive:!0,force:!0}),An(t.cacheDir,{recursive:!0,force:!0}),An(t.logDir,{recursive:!0,force:!0}),n?R({reset:!0}):process.stdout.write(`Reset complete.
252
+ `)}}),Zl=C({meta:{name:`sites`,description:`List Search Console properties available to this Google login`},args:{json:{type:`boolean`,default:!1},refresh:{type:`boolean`,default:!1}},run:async({args:e})=>{let t=await cn(O(e.refresh));if(N(e)){R({sites:t});return}B([`Property`,`Permission`],t.map(e=>[e.siteUrl,e.permissionLevel??`unknown`]))}}),Ql=C({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 ee({product:E(e.product),limit:k(e.limit)});if(N(e)){R({updates:t});return}Pi(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.`})}}),$l={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 eu(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 tu(e,t){let n=eu(e);if(t){R(n);return}z([[`Anonymous telemetry`,e.enabled?`enabled`:`disabled`],[`Reason`,e.reason?$l[e.reason]:`local setting`],[`State file`,e.stateFile],[`Install cohort`,e.state?.cohort??`not created`]]),process.stdout.write(`
171
253
  Only tool usage is sent. URLs, report data, Google data, and identifiers are never sent.
172
254
  `),process.stdout.write(`Details: https://seoskill.dev/telemetry
173
- `)}const Gc={type:`boolean`,default:!1,description:`Print machine-readable JSON.`},Kc=S({meta:{name:`telemetry`,description:`Check or change anonymous usage telemetry`},subCommands:{status:S({meta:{name:`status`,description:`Show whether anonymous usage telemetry is enabled`},args:{json:Gc},run:async({args:e})=>Wc(Rt(),M(e))}),enable:S({meta:{name:`enable`,description:`Enable anonymous usage telemetry in local settings`},args:{json:Gc},run:async({args:e})=>{Wc(Qe(!0),M(e))}}),disable:S({meta:{name:`disable`,description:`Disable anonymous usage telemetry in local settings`},args:{json:Gc},run:async({args:e})=>{Wc(Qe(!1),M(e))}})}}),qc=S({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.`},"google-analytics-property":{type:`string`,description:`Google Analytics property ID for landing-page sessions. Defaults from --project when saved.`},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=M(e),r=E(e),i=await Xc(e),a=Yc(e),o=T(e.site)||r||!a&&!i.length?await U({client:r,site:T(e.site),options:{json:t}}):void 0,s=a??o?.client?.startUrl??(o?.site?xi(o.site):void 0)??i[0];if(!s)throw Error(`No crawl URL selected. Pass a URL, --url, or use --project with a saved crawl URL.`);let c=await n({url:s,projectId:o?.client?.id,site:o?.site,mode:Jc(e.mode)??(i.length?`list`:void 0),urls:i,maxPages:O(e[`max-pages`]),maxDepth:O(e[`max-depth`]),concurrency:O(e.concurrency),refresh:D(e.refresh),fetchRate:mr({...e,"fetch-concurrency":e.concurrency}),include:A(e.include),exclude:A(e.exclude),googleAnalyticsPropertyId:T(e[`google-analytics-property`])??o?.client?.analytics.google?.propertyId,useSitemap:!dr(e,`sitemap`),respectRobots:!dr(e,`robots`),checkExternal:!dr(e,`external`),js:!!D(e.js),limit:O(e.limit)});if(t){L(c);return}os(c),z([`Rank`,`Score`,`Rule`,`Severity`,`Affected`,`Target`,`Action`],c.output.queue.map((e,t)=>[t+1,e.score,e.ruleId,e.severity,e.affectedUrls,e.target,e.action])),J(`Implementation queue details`,c.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 Jc(e){let t=T(e);if(t){if([`site`,`page`,`list`,`sitemap`].includes(t))return t;throw Error(`Mode must be one of: site, page, list, sitemap.`)}}function Yc(e){let t=T(e.crawlUrl),n=T(e.url);if(t&&n&&t!==n)throw Error(`Use either a URL argument or --url, not both.`);return t??n}async function Xc(e){let t=A(e.urls)??[],n=T(e[`urls-file`]);return n&&t.push(...Zc(await On(n,`utf8`))),[...new Set(t)]}function Zc(e){return e.split(/[\n,]/).map(e=>e.trim()).filter(e=>e&&!e.startsWith(`#`))}function Qc(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 $c(e){return/^[A-Za-z0-9_./:@-]+$/.test(e)?e:`'${e.replaceAll(`'`,`'\\''`)}'`}function el(e){try{return new URL(e).origin}catch{throw new v(`INVALID_INPUT`,`Pass a valid absolute URL with --url.`)}}function Q(e,t,n){e.some(e=>e.command===t)||e.push({command:t,why:n})}function tl(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 nl(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:Gt(t,{limit:5}),reviewObservations:s(t,{limit:5})}:{status:e.status,...e.reason?{reason:e.reason}:{}}}function rl(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 il(e){return`topFixes`in e?{...e,topFixes:e.topFixes.slice(0,3).map(rl),reviewObservations:e.reviewObservations.slice(0,3).map(rl)}:e}function al(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 tl(e.dataSources))process.stdout.write(`${t}\n`);if(!e.topFixes.length){process.stdout.write(`No prioritised technical fixes were found.
255
+ `)}const nu={type:`boolean`,default:!1,description:`Print machine-readable JSON.`},ru=C({meta:{name:`telemetry`,description:`Check or change anonymous usage telemetry`},subCommands:{status:C({meta:{name:`status`,description:`Show whether anonymous usage telemetry is enabled`},args:{json:nu},run:async({args:e})=>tu(rn(),N(e))}),enable:C({meta:{name:`enable`,description:`Enable anonymous usage telemetry in local settings`},args:{json:nu},run:async({args:e})=>{tu(ht(!0),N(e))}}),disable:C({meta:{name:`disable`,description:`Disable anonymous usage telemetry in local settings`},args:{json:nu},run:async({args:e})=>{tu(ht(!1),N(e))}})}}),iu=C({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.`},"google-analytics-property":{type:`string`,description:`Google Analytics property ID for landing-page sessions. Defaults from --project when saved.`},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=N(e),n=D(e),r=await su(e),i=ou(e),a=E(e.site)||n||!i&&!r.length?await H({client:n,site:E(e.site),options:{json:t}}):void 0,o=i??a?.client?.startUrl??(a?.site?da(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 De({url:o,projectId:a?.client?.id,site:a?.site,mode:au(e.mode)??(r.length?`list`:void 0),urls:r,maxPages:k(e[`max-pages`]),maxDepth:k(e[`max-depth`]),concurrency:k(e.concurrency),refresh:O(e.refresh),fetchRate:Kr({...e,"fetch-concurrency":e.concurrency}),include:j(e.include),exclude:j(e.exclude),googleAnalyticsPropertyId:E(e[`google-analytics-property`])??a?.client?.analytics.google?.propertyId,useSitemap:!Ur(e,`sitemap`),respectRobots:!Ur(e,`robots`),checkExternal:!Ur(e,`external`),js:!!O(e.js),limit:k(e.limit)});if(t){R(s);return}ic(s),B([`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])),J(`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 au(e){let t=E(e);if(t){if([`site`,`page`,`list`,`sitemap`].includes(t))return t;throw Error(`Mode must be one of: site, page, list, sitemap.`)}}function ou(e){let t=E(e.crawlUrl),n=E(e.url);if(t&&n&&t!==n)throw Error(`Use either a URL argument or --url, not both.`);return t??n}async function su(e){let t=j(e.urls)??[],n=E(e[`urls-file`]);return n&&t.push(...cu(await Yn(n,`utf8`))),[...new Set(t)]}function cu(e){return e.split(/[\n,]/).map(e=>e.trim()).filter(e=>e&&!e.startsWith(`#`))}function lu(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 uu(e){return/^[A-Za-z0-9_./:@-]+$/.test(e)?e:`'${e.replaceAll(`'`,`'\\''`)}'`}function du(e){try{return new URL(e).origin}catch{throw new y(`INVALID_INPUT`,`Pass a valid absolute URL with --url.`)}}function Q(e,t,n){e.some(e=>e.command===t)||e.push({command:t,why:n})}function fu(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 pu(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:de(t,{limit:5}),reviewObservations:_(t,{limit:5})}:{status:e.status,...e.reason?{reason:e.reason}:{}}}function mu(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 hu(e){return`topFixes`in e?{...e,topFixes:e.topFixes.slice(0,3).map(mu),reviewObservations:e.reviewObservations.slice(0,3).map(mu)}:e}function gu(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 fu(e.dataSources))process.stdout.write(`${t}\n`);if(!e.topFixes.length){process.stdout.write(`No prioritised technical fixes were found.
174
256
  `),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?`
175
257
  Prioritised technical fixes
176
258
  `:e.searchDataJoined?`
177
259
  Technical fixes with search value
178
260
  `:`
179
261
  Technical fixes (no Search Console data joined)
180
- `),e.searchDataJoined?z([`Score`,`Rule`,`Severity`,`Search value`,`Verify`],e.topFixes.map(e=>[e.score,e.ruleId,e.severity,`${e.scoreFactors.clicks} clicks / ${e.scoreFactors.sessions} sessions / ${e.scoreFactors.conversions} conv.`,K(e.howToVerify,72)])):z([`#`,`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&&process.stdout.write(`\n${e.reviewObservations.length} review observation${e.reviewObservations.length===1?``:`s`} were kept out of this action queue.\n`)}function ol(e){process.stdout.write(`
262
+ `),e.searchDataJoined?B([`Score`,`Rule`,`Severity`,`Search value`,`Verify`],e.topFixes.map(e=>[e.score,e.ruleId,e.severity,`${e.scoreFactors.clicks} clicks / ${e.scoreFactors.sessions} sessions / ${e.scoreFactors.conversions} conv.`,G(e.howToVerify,72)])):B([`#`,`Rule`,`Severity`,`Affected URLs`,`Verify`],e.topFixes.map((e,t)=>[t+1,e.ruleId,e.severity,e.count,G(e.howToVerify,72)])),e.reviewObservations.length&&process.stdout.write(`\n${e.reviewObservations.length} review observation${e.reviewObservations.length===1?``:`s`} were kept out of this action queue.\n`)}function _u(e){process.stdout.write(`
181
263
  Provider-backed sections skipped
182
- `);let t=`dataSources`in e?tl(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 sl(e){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 t=e.status===`reused`?`Loaded`:`Completed`,n=e.summary.crawledUrls;return`${t} a technical crawl of ${n} ${n===1?`page`:`pages`}. Search Console and Google Analytics sections were skipped because they are not connected.`}function cl(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 ll(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 ${$c(t.crawlStartUrl)} --health`,`Run the fast sitemap response and access gate before fetching page bodies.`),Q(e,`seo crawl --url ${$c(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 ${$c(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 ${$c(t.projectId)}`:`--site ${$c(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 ${$c(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 ${$c(t.crawlStartUrl)} --health`,`Run the fast sitemap response and access gate before fetching page bodies.`),Q(a,`seo crawl --url ${$c(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 ul(e){e.length&&(process.stdout.write(`
264
+ `);let t=`dataSources`in e?fu(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 vu(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}`),...fu(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 yu(e){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 t=e.status===`reused`?`Loaded`:`Completed`,n=e.summary.crawledUrls;return`${t} a technical crawl of ${n} ${n===1?`page`:`pages`}. Search Console and Google Analytics sections were skipped because they are not connected.`}function bu(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 xu(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 ${uu(t.crawlStartUrl)} --health`,`Run the fast sitemap response and access gate before fetching page bodies.`),Q(e,`seo crawl --url ${uu(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 ${uu(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 ${uu(t.projectId)}`:`--site ${uu(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 ${uu(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 ${uu(t.crawlStartUrl)} --health`,`Run the fast sitemap response and access gate before fetching page bodies.`),Q(a,`seo crawl --url ${uu(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 Su(e){e.length&&(process.stdout.write(`
183
265
  Recommended next commands
184
- `),z([`Command`,`Why`],e.map(e=>[e.command,e.why])))}function dl(e){process.stdout.write(`# Technical SEO report: ${e}\n\n`)}function fl(e){return S({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.`},...X([`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:w(`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.`}}:{},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:t})=>{let n=M(t),i=e.printFollowups&&D(t.full),a=E(t),o=T(t.site),s=e.printFollowups?T(t.url):void 0,c=!!(a||o||!s),l=c?await U({client:a,site:o,options:{json:n,refresh:D(t.refresh)}}):void 0,u=l?.site??el(s??``),d=await ft({site:u,days:O(t.days),recentDays:O(t.recent),limit:O(t.limit),brandTerms:l?.client?.brandTerms,includeBrand:D(t[`include-brand`]),refresh:D(t.refresh),skipSearchData:!c}),f=e.workflowName?{...d,workflow:e.workflowName}:d,p=e.printFollowups?await r({site:u,searchSite:l?.site,url:s??l?.client?.startUrl??xi(u),projectId:l?.client?.id,googleAnalyticsPropertyId:l?.client?.analytics.google?.propertyId,crawl:!dr(t,`crawl`),refresh:D(t.refresh),maxPages:O(t[`crawl-max-pages`]),maxDepth:O(t[`crawl-max-depth`])}):void 0,m=p?nl(p):void 0,ee=e.printFollowups?ll(f,{crawlStartUrl:s??l?.client?.startUrl??xi(u),projectId:l?.client?.id,technicalBaselineStatus:p?.status,searchDataAvailable:c}):void 0;if(n){let t=e.printFollowups&&!i?Qc(d,e.workflowName):f,n=!c&&m?{...t,summary:sl(m),steps:[cl(m),...t.steps]}:t;L(m||ee?{...n,...e.printFollowups?{detail:i?`full`:`summary`}:{},...m?{technicalCrawl:e.printFollowups&&!i?il(m):m}:{},...ee?{nextCommands:ee}:{}}:t);return}c?(process.stdout.write(`${f.output.narrative.markdown}\n\n`),os(f)):dl(u),m&&(al(m,{providerFree:!c}),c||ol(m)),ee&&ul(ee)}})}const pl=fl({name:`diagnose-property`,description:`Find what changed in Google Search and what to inspect next`}),ml=fl({name:`report`,workflowName:`report`,printFollowups:!0,description:`Run the main SEO report and recommend the next best follow-up commands`}),hl=S({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.`},...X([`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.`}}),"google-analytics-property":{type:`string`,description:`Google Analytics property ID to use for analytics value. Defaults from the selected project.`},"verify-content":w(`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=M(e),n=await U({client:E(e),site:T(e.site),options:{json:t,refresh:D(e.refresh)}}),r=await fe({site:n.site,days:O(e.days),recentDays:O(e.recent),limit:O(e.limit),brandTerms:n.client?.brandTerms,includeBrand:D(e[`include-brand`]),googleAnalyticsPropertyId:T(e[`google-analytics-property`])??n.client?.analytics.google?.propertyId,verifyContent:D(e[`verify-content`]),verifyLimit:O(e[`verify-limit`]),refresh:D(e.refresh)});if(t){L(r);return}os(r),z([`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])),J(`Queue action details`,r.output.queue.map(e=>({label:e.target,context:`${e.category}, score ${e.score}`,action:e.action})))}}),gl=S({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":w(`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=M(e),n=await U({client:E(e),site:T(e.site),options:{json:t,refresh:D(e.refresh)}}),r=j(e.urls),i=j(e.sitemaps),a=T(e.url)??n.client?.startUrl??xi(n.site),o=await c({site:n.site,startUrl:a,urls:r.length?r:n.client?.watchUrls,sitemaps:i.length?i:void 0,properties:j(e.properties).length?j(e.properties):void 0,limit:O(e.limit),languageCode:T(e.language),dailyLimit:O(e[`daily-limit`]),inspectLimit:O(e[`inspect-limit`]),maxUrls:O(e[`max-urls`]),refresh:D(e.refresh),js:D(e.js)?!0:`auto`,recoverLinks:D(e[`recover-links`]),recoverDays:O(e[`recover-days`]),recoverLimit:O(e[`recover-limit`]),recoverMinClicks:O(e[`recover-min-clicks`]),recoverMinImpressions:O(e[`recover-min-impressions`])});if(t){L(o);return}ss(o)}});function _l(e){return e===null?`-`:e.toLocaleString(`en-GB`,{maximumFractionDigits:e>=100?0:1})}function vl(e){return[...e.winners.map(e=>({direction:`winner`,item:e})),...e.losers.map(e=>({direction:`loser`,item:e}))]}function yl(e,t){let n=vl(t);n.length&&(process.stdout.write(`\n${e}\n`),z([`Direction`,`Key`,`Clicks`,`Impr.`,`Position`],n.map(({direction:e,item:t})=>[e,t.key,_l(t.clickDelta),_l(t.impressionDelta),_l(t.positionDelta)])))}function bl(e){process.stdout.write(`
266
+ `),B([`Command`,`Why`],e.map(e=>[e.command,e.why])))}function Cu(e){process.stdout.write(`# Technical SEO report: ${e}\n\n`)}function wu(e){return C({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.`},...X([`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:Vr(`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.`},...al}:{},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:t})=>{let n=N(t),r=e.printFollowups?ol(t):void 0,i=e.printFollowups&&O(t.full),a=D(t),o=E(t.site),s=e.printFollowups?E(t.url):void 0,c=!!(a||o||!s),l=c?await H({client:a,site:o,options:{json:n,refresh:O(t.refresh)}}):void 0,u=l?.site??du(s??``),d=await Ze({site:u,days:k(t.days),recentDays:k(t.recent),limit:k(t.limit),brandTerms:l?.client?.brandTerms,includeBrand:O(t[`include-brand`]),refresh:O(t.refresh),skipSearchData:!c}),f=e.workflowName?{...d,workflow:e.workflowName}:d,p=e.printFollowups?await Oe({site:u,searchSite:l?.site,url:s??l?.client?.startUrl??da(u),projectId:l?.client?.id,googleAnalyticsPropertyId:l?.client?.analytics.google?.propertyId,crawl:!Ur(t,`crawl`),refresh:O(t.refresh),maxPages:k(t[`crawl-max-pages`]),maxDepth:k(t[`crawl-max-depth`])}):void 0,m=p?pu(p):void 0,h=e.printFollowups?xu(f,{crawlStartUrl:s??l?.client?.startUrl??da(u),projectId:l?.client?.id,technicalBaselineStatus:p?.status,searchDataAvailable:c}):void 0;if(n){let t=e.printFollowups&&!i?lu(d,e.workflowName):f,n=!c&&m?{...t,summary:yu(m),steps:[bu(m),...t.steps]}:t;R(m||h?{...n,...e.printFollowups?{detail:i?`full`:`summary`}:{},...m?{technicalCrawl:e.printFollowups&&!i?hu(m):m}:{},...h?{nextCommands:h}:{}}:t);return}if(r){ul(await ll({report:f.output.narrative,reportName:`report`,title:c?`SEO report`:`Technical SEO report`,options:r,projectId:l?.client?.id,additionalSections:vu({technicalCrawl:m,followups:h})}));return}c?(process.stdout.write(`${f.output.narrative.markdown}\n\n`),ic(f)):Cu(u),m&&(gu(m,{providerFree:!c}),c||_u(m)),h&&Su(h)}})}const Tu=wu({name:`diagnose-property`,description:`Find what changed in Google Search and what to inspect next`}),Eu=wu({name:`report`,workflowName:`report`,printFollowups:!0,description:`Run the main SEO report and recommend the next best follow-up commands`}),Du=C({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.`},...X([`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.`}}),"google-analytics-property":{type:`string`,description:`Google Analytics property ID to use for analytics value. Defaults from the selected project.`},"verify-content":Vr(`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=N(e),n=await H({client:D(e),site:E(e.site),options:{json:t,refresh:O(e.refresh)}}),r=await qe({site:n.site,days:k(e.days),recentDays:k(e.recent),limit:k(e.limit),brandTerms:n.client?.brandTerms,includeBrand:O(e[`include-brand`]),googleAnalyticsPropertyId:E(e[`google-analytics-property`])??n.client?.analytics.google?.propertyId,verifyContent:O(e[`verify-content`]),verifyLimit:k(e[`verify-limit`]),refresh:O(e.refresh)});if(t){R(r);return}ic(r),B([`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])),J(`Queue action details`,r.output.queue.map(e=>({label:e.target,context:`${e.category}, score ${e.score}`,action:e.action})))}}),Ou=C({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":Vr(`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=N(e),n=await H({client:D(e),site:E(e.site),options:{json:t,refresh:O(e.refresh)}}),r=M(e.urls),i=M(e.sitemaps),a=E(e.url)??n.client?.startUrl??da(n.site),o=await Ye({site:n.site,startUrl:a,urls:r.length?r:n.client?.watchUrls,sitemaps:i.length?i:void 0,properties:M(e.properties).length?M(e.properties):void 0,limit:k(e.limit),languageCode:E(e.language),dailyLimit:k(e[`daily-limit`]),inspectLimit:k(e[`inspect-limit`]),maxUrls:k(e[`max-urls`]),refresh:O(e.refresh),js:O(e.js)?!0:`auto`,recoverLinks:O(e[`recover-links`]),recoverDays:k(e[`recover-days`]),recoverLimit:k(e[`recover-limit`]),recoverMinClicks:k(e[`recover-min-clicks`]),recoverMinImpressions:k(e[`recover-min-impressions`])});if(t){R(o);return}ac(o)}});function ku(e){return e===null?`-`:e.toLocaleString(`en-GB`,{maximumFractionDigits:e>=100?0:1})}function Au(e){return[...e.winners.map(e=>({direction:`winner`,item:e})),...e.losers.map(e=>({direction:`loser`,item:e}))]}function ju(e,t){let n=Au(t);n.length&&(process.stdout.write(`\n${e}\n`),B([`Direction`,`Key`,`Clicks`,`Impr.`,`Position`],n.map(({direction:e,item:t})=>[e,t.key,ku(t.clickDelta),ku(t.impressionDelta),ku(t.positionDelta)])))}function Mu(e){process.stdout.write(`
185
267
  Segment coverage
186
- `),z([`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 xl(e){e.length&&(process.stdout.write(`
268
+ `),B([`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 Nu(e){e.length&&(process.stdout.write(`
187
269
  Template movement
188
- `),z([`Direction`,`Template`,`Pattern confidence`,`URLs`,`Clicks`,`Returned share`,`Common terms`],e.map(e=>[e.direction,e.signature,e.confidence,e.urlCount,_l(e.clickDelta),`${Math.round(e.movementShare*100)}%`,e.commonTerms.join(`, `)||`-`])),Y(`Template actions`,e.map(e=>e.summary)),Y(`Template evidence scope`,[`Shares use returned directional page click movement. Pattern confidence applies only to returned matched-retained page segments.`]))}const Sl=S({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.`},...X([`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=M(e),n=await U({client:E(e),site:T(e.site),options:{json:t,refresh:D(e.refresh)}}),r=await u({site:n.site,days:O(e.days),recentDays:O(e.recent),limit:O(e.limit),brandTerms:n.client?.brandTerms,includeBrand:D(e[`include-brand`]),knownConfounders:T(e[`known-change`])?[T(e[`known-change`])??``]:void 0,includeChangeLog:!D(e[`ignore-change-log`]),refresh:D(e.refresh)});if(t){L(r);return}R([[`Workflow`,r.workflow],[`Property`,r.site],[`Summary`,r.summary],[`Causal attribution`,r.output.update.attribution.replaceAll(`-`,` `)],[`Confidence`,r.output.update.confidence],[`Known confounders`,String(r.output.update.confounders.length)]]),Y(`Recommended actions`,r.actions.map(e=>`${e.title} (${e.confidence}): ${e.action}`)),Y(`Postmortem findings`,r.output.insights.map(e=>e.summary)),Y(`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)),xl(r.output.templateMovement),Y(`Update evidence`,r.output.update.evidence),bl(r.output.segments),yl(`Page winners and losers`,r.output.segments.page),yl(`Query winners and losers`,r.output.segments.query),yl(`Device winners and losers`,r.output.segments.device),yl(`Country winners and losers`,r.output.segments.country),ii(`seo export update-postmortem ${n.client?`--project ${JSON.stringify(n.client.id)}`:`--site ${JSON.stringify(n.site)}`}`)}});function Cl(e){return e.replaceAll(`\\`,`/`).toLowerCase()}function wl(e,t){return t===`win32`?`${e}.cmd`:e}function Tl(e,t=process.platform){let n=Cl(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:wl(`pnpm`,t),args:[`add`,`--global`,`seo@latest`]};if(n.includes(`/.bun/install/global/`))return{command:wl(`bun`,t),args:[`add`,`--global`,`seo@latest`]};if(n.includes(`/.config/yarn/global/`))return{command:wl(`yarn`,t),args:[`global`,`add`,`seo@latest`]};if(n.includes(`/node_modules/seo/`))return{command:wl(`npm`,t),args:[`install`,`--global`,`seo@latest`]}}}function El(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 Dl(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 Ol(e){if(e)try{return sn(e)}catch{return e}}async function kl(e){let t=await Un({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 Rn(t)?`later`:t}function Al(e){return xn(e.command,e.args,{stdio:`inherit`}).status}async function jl(e,t={}){if(!Dl({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=Ol(t.cliPath??process.argv[1]),r=n?Tl(n,t.platform??process.platform):void 0;if(!r)return;let i=(t.createNotifier??(e=>$n({pkg:e,shouldNotifyInNpmScript:!1})))(e).update;if(!(!i||!El(i.current,i.latest))&&await(t.prompt??kl)({command:r,update:i})!==`later`)return(t.run??Al)(r)===0?(zn.success(`Updated seo to ${i.latest}. Run your command again.`),0):(zn.error(`The update failed. Your current version is unchanged.`),1)}const Ml={name:`seo`,version:t},Nl=[{title:`Start here`,commands:[[`seo start`,`Connect Google and save a project profile`],[`seo report`,`Run the main SEO report for the default project`],[`seo report --site sc-domain:example.com`,`Run without a profile`],[`seo report --url https://example.com`,`Start with a local technical report`]]},{title:`Projects`,commands:[[`seo projects list`,`List saved project profiles`],[`seo projects add`,`Create or update a project profile`],[`seo sites`,`List Search Console properties`],[`seo doctor`,`Check local auth and config`]]},{title:`Act on a report`,commands:[[`seo refresh-priorities`,`Rank the next best SEO actions`],[`seo quick-wins`,`Find ranking 4-10 low-CTR wins`],[`seo second-page`,`Investigate URLs averaging positions 10-20`],[`seo technical-watch`,`Crawl and index-monitor a site`]]},{title:`Agent and power tools`,commands:[[`seo report --json`,`Run the main report as structured JSON`],[`seo export diagnose`,`Export report data to CSV`],[`seo mcp install`,`Install SEO tools into MCP clients`],[`seo skill list`,`Show the packaged SEO skill`],[`seo reports list`,`Discover every structured report`]]}],Pl=[...Nl,{title:`Deeper analysis`,commands:[[`seo decaying`,`Find pages and queries losing clicks`],[`seo cannibal`,`Review queries exposed across multiple URLs`],[`seo ctr-underperformers`,`Find weak CTR by ranking position`],[`seo query-cluster`,`Cluster repeated demand themes`],[`seo page-opportunities`,`Analyze one URL for growth ideas`],[`seo content optimize`,`Build a content optimization report`],[`seo perf audit`,`Run a page performance audit`],[`seo internal-links`,`Find internal link opportunities`],[`seo community-intent`,`Find forum, review, and comparison intent`],[`seo ai-referrals`,`Find AI referral traffic in Google Analytics`],[`seo seo-to-ai-query`,`Convert searches into AI-monitor prompts`]]},{title:`Technical and data`,commands:[[`seo audit-page`,`Audit one page`],[`seo crawl --sitemap-url <url> --health`,`Run the fast sitemap gate before a full crawl`],[`seo crawl`,`Run a full crawl for page and content evidence`],[`seo agent-readiness`,`Check content-site readiness for AI agents`],[`seo ai-readiness`,`Review AI-search technical evidence`],[`seo llms audit`,`Inspect optional llms.txt presence`],[`seo entity-readiness`,`Check brand/entity signals from a saved crawl`],[`seo okf export`,`Export a saved crawl as an OKF knowledge bundle`],[`seo crawl-queue`,`Run a crawl and rank the implementation queue`],[`seo crawl-reports`,`List saved crawl reports`],[`seo rules`,`List crawler rule ids`],[`seo explain --rule missing_title`,`Explain a crawler rule`],[`seo crawl-diff`,`Compare crawl changes`],[`seo index-coverage`,`Choose pages for URL Inspection`],[`seo index-watch`,`Check URL Inspection status`],[`seo link-recover`,`Find broken search-value URLs`],[`seo redirect-trace`,`Trace redirects`],[`seo gsc-query`,`Run a raw GSC query`],[`seo url-inspect`,`Run URL Inspection`],[`seo analytics google properties`,`List Google Analytics properties`],[`seo analytics google report`,`Run a Google Analytics report`],[`seo updates`,`List official Google ranking updates`]]},{title:`Reports and ops`,commands:[[`seo monthly-report`,`Generate a monthly narrative`],[`seo report-narrative`,`Generate a client-ready narrative`],[`seo update-postmortem`,`Analyze update winners and losers`],[`seo schedule cron`,`Print cron entries`],[`seo monitoring`,`Run and review monitoring`],[`seo change-log`,`Track SEO changes`],[`seo tests`,`Create and report local SEO tests`],[`seo content-groups`,`Manage reusable page/query groups`],[`seo pseo`,`Audit programmatic SEO templates`],[`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 local SEO data`]]}];function Fl(e){Gr(`seo v${Ml.version}`,`Run SEO audits, find what needs fixing, and ship the changes with your agent.`),process.stdout.write(`
189
- `),qr(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(`
190
- `),B(`More help`,"Use `seo help <command>` or `seo <command> --help` for command help.","Use `seo help all` for the longer command list.")}const Il=S({meta:{name:`seo`,version:Ml.version,description:`Local-first SEO CLI and MCP server`},subCommands:{init:Yo,analytics:pi,llms:Qo,auth:vi,mcp:ns,okf:_s,doctor:Is,privacy:Rc,telemetry:Kc,reset:zc,cache:yi,logs:$o,crawl:ka,"crawl-queue":qc,"crawl-reports":Wa,client:ya,content:xa,project:ba,projects:ba,"change-log":no,tests:ho,"content-groups":ao,"crawl-diff":rs,diagnose:Fs,"diagnose-property":pl,export:Jo,"index-coverage":fs,"index-watch":ps,"link-recover":ms,"monthly-report":tc,monitoring:ds,"report-narrative":nc,rules:rc,explain:ic,"refresh-priorities":hl,"redirect-trace":hs,schedule:oc,skill:Lc,start:da,setup:ua,report:ml,reports:ec,"segment-impact":Rs,"striking-distance":Bs,"technical-watch":gl,"update-postmortem":Sl,sites:Bc,"gsc-query":Ga,"url-inspect":Ka,updates:Vc,"traffic-anomaly":Xa,"update-correlate":Za,"ai-referrals":vs,"agent-readiness":ui,"ai-readiness":si,"entity-readiness":$a,"audit-page":As,"second-page":Os,cannibal:ys,"community-intent":bs,decaying:Cs,"quick-wins":Ds,"page-opportunities":Ts,perf:Ps,"internal-links":ws,"ctr-underperformers":xs,"query-cluster":Es,"seo-to-ai-query":ks,pseo:Js},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")}}),Ll={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`,"monthly-report":`monthly-report`,"query-cluster":`query-clusters`,report:`search-performance-overview`,"report-narrative":`narrative-report`,rules:`crawler-rules`,"update-correlate":`update-correlation`},Rl=new Set([`agent-readiness`,`ai-readiness`,`ai-referrals`,`audit-page`,`community-intent`,`crawl-diff`,`ctr-underperformers`,`entity-readiness`,`index-coverage`,`index-watch`,`internal-links`,`page-opportunities`,`quick-wins`,`redirect-trace`,`refresh-priorities`,`second-page`,`segment-impact`,`seo-to-ai-query`,`striking-distance`,`technical-watch`,`traffic-anomaly`,`update-postmortem`]),zl=new Set(St().map(e=>e.id));function Bl(e){let[t,n,r]=e;if(t)return t===`reports`&&n===`run`?r&&zl.has(r)?r:void 0:t===`content`&&n===`optimize`?`content-optimization`:t===`change-log`&&n===`measure`?`measure-change`:t===`llms`&&n===`audit`?`llms-txt-audit`:t===`llms`&&n===`generate`?`generate-llms-txt`:t===`okf`&&n===`export`?`okf-build`:t===`okf`&&n===`validate`?`okf-validate`:t===`monitoring`&&n===`run`?`technical-watch`:t===`perf`&&n===`audit`?`performance-audit`:t===`pseo`&&n===`audit`?`pseo-audit`:t===`tests`&&n===`report`?`measure-change`:Ll[t]??(Rl.has(t)?t:void 0)}const $=process.argv.slice(2),Vl={agent:mt()},Hl=$[0]===`telemetry`,Ul=$[0]===`mcp`&&$[1]===`serve`;!Hl&&!Ul&&qe(Vl);const Wl=await jl(Ml,{argv:$});Wl!==void 0&&process.exit(Wl),($.length===0||$.length===1&&[`help`,`--help`,`-h`].includes($[0]??``))&&(Fl(Nl),process.exit(0)),$[0]===`help`&&$[1]===`all`&&(Fl(Pl),process.exit(0));const Gl=$[0]===`help`&&$.length>1?[...$.slice(1),`--help`]:$;Gl!==$&&(process.argv=[process.argv[0]??`node`,process.argv[1]??`seo`,...Gl]);const Kl=Gl.some(e=>[`--help`,`-h`].includes(e)),ql=$.length===1&&[`--version`,`-v`].includes($[0]??``),Jl=Kl||ql?void 0:Bl(Gl);if(Kl||ql)await Pn(Il);else{Jl&&tt(Jl,Vl);try{await Nn(Il,{rawArgs:Gl}),Jl&&_t(Jl,Vl),[`init`,`setup`,`start`].includes(Gl[0]??``)&&!Gl.includes(`--dry-run`)&&Le(Vl)}catch(e){Jl&&it(Jl,Tt(e),Vl);let t=xe(e);$.includes(`--json`)?process.stdout.write(`${JSON.stringify(qt(t),null,2)}\n`):process.stderr.write(`Error: ${t.message}\n`),process.exitCode=t.exitCode}}export{};
270
+ `),B([`Direction`,`Template`,`Pattern confidence`,`URLs`,`Clicks`,`Returned share`,`Common terms`],e.map(e=>[e.direction,e.signature,e.confidence,e.urlCount,ku(e.clickDelta),`${Math.round(e.movementShare*100)}%`,e.commonTerms.join(`, `)||`-`])),Y(`Template actions`,e.map(e=>e.summary)),Y(`Template evidence scope`,[`Shares use returned directional page click movement. Pattern confidence applies only to returned matched-retained page segments.`]))}const Pu=C({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.`},...X([`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=N(e),n=await H({client:D(e),site:E(e.site),options:{json:t,refresh:O(e.refresh)}}),r=await ce({site:n.site,days:k(e.days),recentDays:k(e.recent),limit:k(e.limit),brandTerms:n.client?.brandTerms,includeBrand:O(e[`include-brand`]),knownConfounders:E(e[`known-change`])?[E(e[`known-change`])??``]:void 0,includeChangeLog:!O(e[`ignore-change-log`]),refresh:O(e.refresh)});if(t){R(r);return}K({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}]}),Y(`Recommended actions`,r.actions.map(e=>`${e.title} (${e.confidence}): ${e.action}`)),Y(`Postmortem findings`,r.output.insights.map(e=>e.summary)),Y(`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)),Nu(r.output.templateMovement),Y(`Update evidence`,r.output.update.evidence),Mu(r.output.segments),ju(`Page winners and losers`,r.output.segments.page),ju(`Query winners and losers`,r.output.segments.query),ju(`Device winners and losers`,r.output.segments.device),ju(`Country winners and losers`,r.output.segments.country),ta(`seo export update-postmortem ${n.client?`--project ${JSON.stringify(n.client.id)}`:`--site ${JSON.stringify(n.site)}`}`)}});function Fu(e){return e.replaceAll(`\\`,`/`).toLowerCase()}function Iu(e,t){return t===`win32`?`${e}.cmd`:e}function Lu(e,t=process.platform){let n=Fu(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:Iu(`pnpm`,t),args:[`add`,`--global`,`seo@latest`]};if(n.includes(`/.bun/install/global/`))return{command:Iu(`bun`,t),args:[`add`,`--global`,`seo@latest`]};if(n.includes(`/.config/yarn/global/`))return{command:Iu(`yarn`,t),args:[`global`,`add`,`seo@latest`]};if(n.includes(`/node_modules/seo/`))return{command:Iu(`npm`,t),args:[`install`,`--global`,`seo@latest`]}}}function Ru(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 zu(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 Bu(e){if(e)try{return On(e)}catch{return e}}async function Vu(e){let t=await fr({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 sr(t)?`later`:t}function Hu(e){return Hn(e.command,e.args,{stdio:`inherit`}).status}async function Uu(e,t={}){if(!zu({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=Bu(t.cliPath??process.argv[1]),r=n?Lu(n,t.platform??process.platform):void 0;if(!r)return;let i=(t.createNotifier??(e=>Sr({pkg:e,shouldNotifyInNpmScript:!1})))(e).update;if(!(!i||!Ru(i.current,i.latest))&&await(t.prompt??Vu)({command:r,update:i})!==`later`)return(t.run??Hu)(r)===0?(cr.success(`Updated seo to ${i.latest}. Run your command again.`),0):(cr.error(`The update failed. Your current version is unchanged.`),1)}const Wu={name:`seo`,version:mn},Gu=[{title:`Start here`,commands:[[`seo start`,`Connect Google and save a project profile`],[`seo report`,`Run the main SEO report for the default project`],[`seo report --site sc-domain:example.com`,`Run without a profile`],[`seo report --url https://example.com`,`Start with a local technical report`]]},{title:`Projects`,commands:[[`seo projects list`,`List saved project profiles`],[`seo projects add`,`Create or update a project profile`],[`seo sites`,`List Search Console properties`],[`seo doctor`,`Check local auth and config`]]},{title:`Act on a report`,commands:[[`seo refresh-priorities`,`Rank the next best SEO actions`],[`seo quick-wins`,`Find ranking 4-10 low-CTR wins`],[`seo second-page`,`Investigate URLs averaging positions 10-20`],[`seo technical-watch`,`Crawl and index-monitor a site`]]},{title:`Agent and power tools`,commands:[[`seo report --json`,`Run the main report as structured JSON`],[`seo export diagnose`,`Export report data to CSV`],[`seo mcp install`,`Install SEO tools into MCP clients`],[`seo skill list`,`Show the packaged SEO skill`],[`seo reports list`,`Discover every structured report`]]}],Ku=[...Gu,{title:`Deeper analysis`,commands:[[`seo decaying`,`Find pages and queries losing clicks`],[`seo cannibal`,`Review queries exposed across multiple URLs`],[`seo ctr-underperformers`,`Find weak CTR by ranking position`],[`seo query-cluster`,`Cluster repeated demand themes`],[`seo page-opportunities`,`Analyze one URL for growth ideas`],[`seo content optimize`,`Build a content optimization report`],[`seo perf audit`,`Run a page performance audit`],[`seo internal-links`,`Find internal link opportunities`],[`seo community-intent`,`Find forum, review, and comparison intent`],[`seo ai-referrals`,`Find AI referral traffic in Google Analytics`],[`seo seo-to-ai-query`,`Convert searches into AI-monitor prompts`]]},{title:`Technical and data`,commands:[[`seo audit-page`,`Audit one page`],[`seo crawl --sitemap-url <url> --health`,`Run the fast sitemap gate before a full crawl`],[`seo crawl`,`Run a full crawl for page and content evidence`],[`seo agent-readiness`,`Check content-site readiness for AI agents`],[`seo ai-readiness`,`Review AI-search technical evidence`],[`seo llms audit`,`Inspect optional llms.txt presence`],[`seo entity-readiness`,`Check brand/entity signals from a saved crawl`],[`seo okf export`,`Export a saved crawl as an OKF knowledge bundle`],[`seo crawl-queue`,`Run a crawl and rank the implementation queue`],[`seo crawl-reports`,`List saved crawl reports`],[`seo rules`,`List crawler rule ids`],[`seo explain --rule missing_title`,`Explain a crawler rule`],[`seo crawl-diff`,`Compare crawl changes`],[`seo index-coverage`,`Choose pages for URL Inspection`],[`seo index-watch`,`Check URL Inspection status`],[`seo indexnow submit`,`Notify IndexNow about changed URLs`],[`seo link-recover`,`Find broken search-value URLs`],[`seo links`,`Review bounded referring-link evidence`],[`seo server-logs analyze`,`Review crawler requests in an access log`],[`seo redirect-trace`,`Trace redirects`],[`seo gsc-query`,`Run a raw GSC query`],[`seo url-inspect`,`Run URL Inspection`],[`seo analytics google properties`,`List Google Analytics properties`],[`seo analytics google report`,`Run a Google Analytics report`],[`seo updates`,`List official Google ranking updates`]]},{title:`Reports and ops`,commands:[[`seo monthly-report`,`Generate a monthly narrative`],[`seo report-narrative`,`Generate a client-ready narrative`],[`seo update-postmortem`,`Analyze update winners and losers`],[`seo schedule cron`,`Print cron entries`],[`seo monitoring`,`Run and review monitoring`],[`seo change-log`,`Track SEO changes`],[`seo tests`,`Create and report local SEO tests`],[`seo content-groups`,`Manage reusable page/query groups`],[`seo pseo`,`Audit programmatic SEO templates`],[`seo auth`,`Manage Google auth`],[`seo providers bing`,`Connect and report on Bing Webmaster`],[`seo cache`,`Manage local cache`],[`seo privacy`,`Show local storage paths`],[`seo telemetry status`,`Check anonymous usage telemetry`],[`seo reset`,`Delete local SEO data`]]}];function qu(e){Ai(`seo v${Wu.version}`,`Run SEO audits, find what needs fixing, and ship the changes with your agent.`),process.stdout.write(`
271
+ `),Ni(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(`
272
+ `),Fi(`More help`,"Use `seo help <command>` or `seo <command> --help` for command help.","Use `seo help all` for the longer command list.")}const Ju=C({meta:{name:`seo`,version:Wu.version,description:`Local-first SEO CLI and MCP server`},subCommands:{init:Gs,indexnow:Ws,analytics:ra,llms:Ys,auth:ca,mcp:$s,okf:hc,doctor:Pc,privacy:Yl,telemetry:ru,reset:Xl,cache:la,logs:Xs,crawl:So,"crawl-queue":iu,"crawl-reports":Lo,client:lo,content:fo,project:uo,projects:uo,providers:Wc,"change-log":Yo,tests:ss,"content-groups":Qo,"crawl-diff":ec,diagnose:Nc,"diagnose-property":Tu,export:Bs,"index-coverage":uc,"index-watch":dc,"link-recover":fc,links:Ks,"monthly-report":dl,monitoring:lc,"report-narrative":fl,rules:pl,explain:ml,"refresh-priorities":Du,"redirect-trace":pc,schedule:gl,"server-logs":_l,skill:Jl,start:to,setup:eo,report:Eu,reports:il,"segment-impact":Ic,"striking-distance":Rc,"technical-watch":Ou,"update-postmortem":Pu,sites:Zl,"gsc-query":Ro,"url-inspect":zo,updates:Ql,"traffic-anomaly":Uo,"update-correlate":Wo,"ai-referrals":gc,"agent-readiness":Yi,"ai-readiness":Ki,"entity-readiness":Ko,"audit-page":Oc,"second-page":Ec,cannibal:_c,"community-intent":vc,decaying:xc,"quick-wins":Tc,"page-opportunities":Cc,perf:Mc,"internal-links":Sc,"ctr-underperformers":yc,"query-cluster":wc,"seo-to-ai-query":Dc,pseo:Qc},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")}}),Yu={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`,"monthly-report":`monthly-report`,"query-cluster":`query-clusters`,report:`search-performance-overview`,"report-narrative":`narrative-report`,rules:`crawler-rules`,"update-correlate":`update-correlation`},Xu=new Set([`agent-readiness`,`ai-readiness`,`ai-referrals`,`audit-page`,`community-intent`,`crawl-diff`,`ctr-underperformers`,`entity-readiness`,`index-coverage`,`index-watch`,`internal-links`,`link-evidence`,`page-opportunities`,`quick-wins`,`redirect-trace`,`refresh-priorities`,`second-page`,`segment-impact`,`seo-to-ai-query`,`striking-distance`,`technical-watch`,`traffic-anomaly`,`update-postmortem`]),Zu=new Set(Vt().map(e=>e.id));function Qu(e){let[t,n,r]=e;if(t)return t===`reports`&&n===`run`?r&&Zu.has(r)?r:void 0:t===`content`&&n===`optimize`?`content-optimization`:t===`change-log`&&n===`measure`?`measure-change`:t===`llms`&&n===`audit`?`llms-txt-audit`:t===`links`?`link-evidence`:t===`llms`&&n===`generate`?`generate-llms-txt`:t===`okf`&&n===`export`?`okf-build`:t===`okf`&&n===`validate`?`okf-validate`:t===`monitoring`&&n===`run`?`technical-watch`:t===`perf`&&n===`audit`?`performance-audit`:t===`pseo`&&n===`audit`?`pseo-audit`:t===`tests`&&n===`report`?`measure-change`:Yu[t]??(Xu.has(t)?t:void 0)}const $=process.argv.slice(2),$u={agent:Nt()},ed=$[0]===`telemetry`,td=$[0]===`mcp`&&$[1]===`serve`;!ed&&!td&&ut($u);const nd=await Uu(Wu,{argv:$});nd!==void 0&&process.exit(nd),($.length===0||$.length===1&&[`help`,`--help`,`-h`].includes($[0]??``))&&(qu(Gu),process.exit(0)),$[0]===`help`&&$[1]===`all`&&(qu(Ku),process.exit(0));const rd=$[0]===`help`&&$.length>1?[...$.slice(1),`--help`]:$;rd!==$&&(process.argv=[process.argv[0]??`node`,process.argv[1]??`seo`,...rd]);const id=rd.some(e=>[`--help`,`-h`].includes(e)),ad=$.length===1&&[`--version`,`-v`].includes($[0]??``),od=id||ad?void 0:Qu(rd);if(id||ad)await rr(Ju);else{od&&vt(od,$u);try{await nr(Ju,{rawArgs:rd}),od&&It(od,$u),[`init`,`setup`,`start`].includes(rd[0]??``)&&!rd.includes(`--dry-run`)&&$e($u)}catch(e){od&&xt(od,Wt(e),$u);let t=Ge(e);$.includes(`--json`)?process.stdout.write(`${JSON.stringify(ze(t),null,2)}\n`):process.stderr.write(`Error: ${t.message}\n`),process.exitCode=t.exitCode}}export{};
191
273
  //# sourceMappingURL=cli.js.map