seo 0.2.1 → 0.2.3

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,103 +1,107 @@
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 h,H as g,Ht as ee,I as te,It as ne,J as re,Jt as ie,K as ae,Kt as oe,L as se,Lt as ce,M as le,Mt as ue,N as de,Nt as fe,O as pe,Ot as me,P as he,Pt as ge,Q as _e,Qt as ve,R as ye,Rt as be,S 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,_t as Re,a as ze,an as Be,at as Ve,b as He,bt as Ue,c as We,cn as Ge,ct as Ke,d as qe,dt as Je,en as Ye,et as Xe,f as Ze,ft as Qe,g as $e,gt as et,h as tt,ht as nt,i as rt,in as it,it as at,j as ot,jt as st,k as ct,kt as lt,l as ut,lt as dt,m as ft,mt as pt,nn as mt,nt as ht,on as _,ot as gt,p as _t,pt as vt,q as yt,qt as bt,r as xt,rn as St,rt as Ct,sn as wt,st as Tt,t as Et,tn as Dt,tt as Ot,u as kt,ut as At,v as jt,vt as Mt,w as Nt,wt as Pt,x as Ft,xt as It,y as Lt,yt as Rt,z as zt,zt as Bt}from"./dist-CTBhWxn6.js";import{constants as Vt,copyFileSync as Ht,existsSync as v,mkdirSync as Ut,readFileSync as Wt,readdirSync as Gt,realpathSync as Kt,renameSync as qt,rmSync as Jt,statSync as Yt,writeFileSync as Xt}from"node:fs";import{homedir as Zt}from"node:os";import{basename as Qt,dirname as y,isAbsolute as $t,join as b,posix as en,relative as tn,resolve as nn,win32 as rn}from"node:path";import{randomUUID as an}from"node:crypto";import{spawn as on,spawnSync as sn}from"node:child_process";import{fileURLToPath as cn}from"node:url";import{isDeepStrictEqual as ln}from"node:util";import{lstat as un,mkdir as dn,mkdtemp as fn,readFile as pn,readdir as mn,rename as hn,rm as gn,writeFile as _n}from"node:fs/promises";import{defineCommand as x,runCommand as vn,runMain as yn}from"citty";import{cancel as bn,confirm as xn,intro as Sn,multiselect as Cn,note as S,outro as wn,password as Tn,select as En,text as Dn}from"@clack/prompts";import On from"cli-table3";import kn from"picocolors";import An from"update-notifier";import{applyEdits as jn,modify as Mn,parse as Nn,printParseErrorCode as Pn}from"jsonc-parser";const C={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 Fn(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 In(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 Ln(e){return[{filename:`postmortem-summary.csv`,headers:[...C.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:[...C.steps],rows:e.steps.map((e,t)=>({rank:t+1,tool:e.tool,status:e.status,summary:e.summary}))},{filename:`workflow-actions.csv`,headers:[...C.actions],rows:e.actions.map((e,t)=>({rank:t+1,title:e.title,confidence:e.confidence,action:e.action}))},{filename:`postmortem-findings.csv`,headers:[...C.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:[...C.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:[...C.evidence],rows:e.output.update.evidence.map((e,t)=>({rank:t+1,evidence:e}))},{filename:`postmortem-confounders.csv`,headers:[...C.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:[...C.segment],rows:Fn(e,t)})),...[`page`,`query`,`device`,`country`].map(t=>({filename:`postmortem-segment-${t}-unmatched.csv`,headers:[...C.unmatchedSegment],rows:In(e,t)}))]}function Rn(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 zn(e){if(!e.startsWith(`sc-domain:`))return;let t=e.slice(10).trim().toLowerCase();if(!(!t||/[/:]/.test(t)))return t.replace(/\.$/,``)||void 0}function Bn(e,t){let n=zn(e);return n?t===n||t.endsWith(`.${n}`)?`domain`:void 0:Rn(e)===t?`hostname`:void 0}function Vn(e,t){return t.flatMap(t=>{let n=t.stream.webStreamData?.defaultUri;if(!n)return[];let r=Rn(n);if(!r)return[];let i=Bn(e,r);return i?[{...t,defaultUri:n,hostname:r,match:i}]:[]})}function Hn(e,t){let n=e.stream.displayName??e.defaultUri,r=e.match===`domain`?`is inside`:`matches`;return`Matched GA4 web stream ${n} (${e.defaultUri}): its hostname ${r} ${t}.`}function Un(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 Wn(e){let t=T(e.rendering);if(t!==void 0&&t!==`auto`&&t!==`on`&&t!==`off`)throw new _(`INVALID_INPUT`,`--rendering must be auto, on, or off.`);if(D(e.js)===!0&&t&&t!==`on`)throw new _(`INVALID_INPUT`,`Use either --js or --rendering on, not both with different modes.`);return t??(D(e.js)?`on`:`auto`)}function O(e,t){let n=D(e[t]);if(n===!1)return!0;if(n===!0)return!1;let r=D(e[Gn(t)]);if(r===!1)return!0;if(r===!0)return!1;if(D(e[`no-${t}`])===!0||D(e[`no${Kn(t)}`])===!0)return!0}function Gn(e){return e.replace(/-([a-z])/g,(e,t)=>t.toUpperCase())}function Kn(e){let t=Gn(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 _(`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 qn(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 Jn(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 Yn(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 pn(r??``,`utf8`),a=JSON.parse(i);if(!a||typeof a!=`object`||Array.isArray(a))throw Error(`JSON body must be an object.`);return a}const Xn=kn.createColors(!!(process.stdout.isTTY&&!process.env.NO_COLOR));function Zn(e){process.env.CI||process.env.NO_UPDATE_NOTIFIER||An({pkg:e,shouldNotifyInNpmScript:!1}).notify()}function P(e={}){return!!(!e.json&&process.stdin.isTTY&&process.stdout.isTTY&&!process.env.CI)}function F(e){process.stdout.write(`${JSON.stringify(e,null,2)}\n`)}function I(e){let t=Math.max(...e.map(([e])=>e.length),0);for(let[n,r]of e)process.stdout.write(`${Xn.bold(n.padEnd(t))} ${r}\n`)}function L(e,t){let n=new On({head:e});for(let e of t)n.push(e);process.stdout.write(`${n.toString()}\n`)}function Qn(e){return e<1024?`${e} B`:e<1024*1024?`${(e/1024).toFixed(1)} KB`:`${(e/(1024*1024)).toFixed(1)} MB`}function R(e){return typeof e==`symbol`&&process.exit(1),e}function $n(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 er(e){return e.active===`keychain`?`system keychain`:e.configured===`keychain`?`private file fallback`:`private file`}const tr=x({meta:{name:`auth`,description:`Authentication commands`},subCommands:{login:x({meta:{name:`login`,description:`Run Google OAuth flow`},run:async()=>{if((await Pe()).activeMode===`service-account`)throw new _(`AUTH_CONFIG_REQUIRED`,`A service account is active from the environment. Unset its credential variable before running browser OAuth.`);let e=await ie();process.stdout.write(`${e.account_email} · ${e.scope.replace(`https://www.googleapis.com/auth/`,``)} · ${e.client_source===`shared`?`shared seo app`:`BYO client`}\n`)}}),logout:x({meta:{name:`logout`,description:`Delete locally stored tokens`},run:async()=>{let e=await Pe();if(await Ie(),e.activeMode===`service-account`){process.stdout.write(`Deleted local OAuth tokens. Service account credentials remain available through the environment.
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 h,H as g,Ht as _,I as ee,It as te,J as ne,Jt as re,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,St as xe,T as Se,Tt as Ce,U as we,Ut as Te,V as Ee,Vt as De,W as Oe,Wt as ke,X as Ae,Xt as je,Y as Me,Yt as Ne,Z as Pe,Zt as Fe,_ as Ie,_t as Le,a as Re,an as ze,at as Be,b as Ve,bt as He,c as Ue,cn as We,ct as Ge,d as Ke,dt as qe,en as Je,et as Ye,f as Xe,ft as Ze,g as Qe,gt as $e,h as et,ht as tt,i as nt,in as rt,it,j as at,jt as ot,k as st,kt as ct,l as lt,ln as ut,lt as dt,m as ft,mt as pt,nn as mt,nt as ht,on as gt,ot as _t,p as vt,pt as yt,q as bt,qt as xt,r as St,rn as Ct,rt as wt,sn as v,st as Tt,t as Et,tn as Dt,tt as Ot,u as kt,ut as At,v as jt,vt as Mt,w as Nt,wt as Pt,x as Ft,xt as It,y as Lt,yt as Rt,z as zt,zt as Bt}from"./dist-BnXs3Jen.js";import{constants as Vt,copyFileSync as Ht,existsSync as y,mkdirSync as Ut,readFileSync as Wt,readdirSync as Gt,realpathSync as Kt,renameSync as qt,rmSync as Jt,statSync as Yt,writeFileSync as Xt}from"node:fs";import{homedir as Zt}from"node:os";import{basename as Qt,dirname as b,isAbsolute as $t,join as x,posix as en,relative as tn,resolve as nn,win32 as rn}from"node:path";import{randomUUID as an}from"node:crypto";import{spawn as on,spawnSync as sn}from"node:child_process";import{fileURLToPath as cn}from"node:url";import{isDeepStrictEqual as ln}from"node:util";import{lstat as un,mkdir as dn,mkdtemp as fn,readFile as pn,readdir as mn,rename as hn,rm as gn,writeFile as _n}from"node:fs/promises";import{defineCommand as S,runCommand as vn,runMain as yn}from"citty";import{cancel as bn,confirm as xn,intro as Sn,multiselect as Cn,note as C,outro as wn,password as Tn,select as En,text as Dn}from"@clack/prompts";import On from"cli-table3";import kn from"picocolors";import An from"update-notifier";import{applyEdits as jn,modify as Mn,parse as Nn,printParseErrorCode as Pn}from"jsonc-parser";const w={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 Fn(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 In(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 Ln(e){return[{filename:`postmortem-summary.csv`,headers:[...w.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:[...w.steps],rows:e.steps.map((e,t)=>({rank:t+1,tool:e.tool,status:e.status,summary:e.summary}))},{filename:`workflow-actions.csv`,headers:[...w.actions],rows:e.actions.map((e,t)=>({rank:t+1,title:e.title,confidence:e.confidence,action:e.action}))},{filename:`postmortem-findings.csv`,headers:[...w.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:[...w.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:[...w.evidence],rows:e.output.update.evidence.map((e,t)=>({rank:t+1,evidence:e}))},{filename:`postmortem-confounders.csv`,headers:[...w.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:[...w.segment],rows:Fn(e,t)})),...[`page`,`query`,`device`,`country`].map(t=>({filename:`postmortem-segment-${t}-unmatched.csv`,headers:[...w.unmatchedSegment],rows:In(e,t)}))]}function Rn(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 zn(e){if(!e.startsWith(`sc-domain:`))return;let t=e.slice(10).trim().toLowerCase();if(!(!t||/[/:]/.test(t)))return t.replace(/\.$/,``)||void 0}function Bn(e,t){let n=zn(e);return n?t===n||t.endsWith(`.${n}`)?`domain`:void 0:Rn(e)===t?`hostname`:void 0}function Vn(e,t){return t.flatMap(t=>{let n=t.stream.webStreamData?.defaultUri;if(!n)return[];let r=Rn(n);if(!r)return[];let i=Bn(e,r);return i?[{...t,defaultUri:n,hostname:r,match:i}]:[]})}function Hn(e,t){let n=e.stream.displayName??e.defaultUri,r=e.match===`domain`?`is inside`:`matches`;return`Matched GA4 web stream ${n} (${e.defaultUri}): its hostname ${r} ${t}.`}function Un(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 T(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 Wn(e){let t=E(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(O(e.js)===!0&&t&&t!==`on`)throw new v(`INVALID_INPUT`,`Use either --js or --rendering on, not both with different modes.`);return t??(O(e.js)?`on`:`auto`)}function Gn(e,t){let n=O(e[t]);if(n===!1)return!0;if(n===!0)return!1;let r=O(e[Kn(t)]);if(r===!1)return!0;if(r===!0)return!1;if(O(e[`no-${t}`])===!0||O(e[`no${qn(t)}`])===!0)return!0}function Kn(e){return e.replace(/-([a-z])/g,(e,t)=>t.toUpperCase())}function qn(e){let t=Kn(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 v(`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 N(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 Jn(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 P(e){return e.json===!0}async function Yn(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 pn(r??``,`utf8`),a=JSON.parse(i);if(!a||typeof a!=`object`||Array.isArray(a))throw Error(`JSON body must be an object.`);return a}const Xn=kn.createColors(!!(process.stdout.isTTY&&!process.env.NO_COLOR));function Zn(e){process.env.CI||process.env.NO_UPDATE_NOTIFIER||An({pkg:e,shouldNotifyInNpmScript:!1}).notify()}function F(e={}){return!!(!e.json&&process.stdin.isTTY&&process.stdout.isTTY&&!process.env.CI)}function I(e){process.stdout.write(`${JSON.stringify(e,null,2)}\n`)}function L(e){let t=Math.max(...e.map(([e])=>e.length),0);for(let[n,r]of e)process.stdout.write(`${Xn.bold(n.padEnd(t))} ${r}\n`)}function R(e,t){let n=new On({head:e});for(let e of t)n.push(e);process.stdout.write(`${n.toString()}\n`)}function Qn(e){return e<1024?`${e} B`:e<1024*1024?`${(e/1024).toFixed(1)} KB`:`${(e/(1024*1024)).toFixed(1)} MB`}function z(e){return typeof e==`symbol`&&process.exit(1),e}function $n(e,t){let n=t.trim().toLowerCase();return!n||e.some(e=>e.toLowerCase().includes(n))}async function er(e){let t=e.choices;if(e.choices.length>8){let n=z(await Dn({message:e.searchMessage,placeholder:`Type part of the name or URL, or leave blank`}));if(t=e.choices.filter(t=>$n(e.searchValues(t),n)),!t.length)throw new v(`INVALID_INPUT`,e.emptyMessage)}return z(await En({message:e.message,options:t.map(t=>{let n=e.hint?.(t);return{value:t,label:e.label(t),...n?{hint:n}:{}}})}))}async function B(e){if(e.site)return e.site;let t=Dt(),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||!F())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 ye(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 er({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 V(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=Ke(t);if(!e)throw new v(`INVALID_INPUT`,`Project not found: ${t}`);return{client:e,site:e.siteUrl}}let n=Ke();return!e.site&&n?{client:n,site:n.siteUrl}:{site:await B({site:e.site,options:e.options})}}async function tr(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=Ke(t);if(!e)throw new v(`INVALID_INPUT`,`Project not found: ${t}`);return e}let n=Xe();if(!n.length)return;let r=Ke();if(r)return r;if(!(e.options?.json||!F()))return er({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 nr(e){if(e.property)return e.property;let t=Dt();if(t.google.defaultGa4PropertyId)return t.google.defaultGa4PropertyId;if(e.options?.json||!F())throw new v(`INVALID_INPUT`,`No GA4 property selected. Pass --property or run this command in a terminal to choose one.`);let n=(await ke()).flatMap(e=>e.propertySummaries.map(t=>({property:_(t.property),displayName:t.displayName??t.property,account:e.displayName??e.account})));if(!n.length)throw new v(`PROPERTY_NOT_FOUND`,`No GA4 properties found for this Google login.`);return n.length===1?n[0]?.property??``:(await er({message:`Choose a GA4 property`,searchMessage:`Search GA4 properties`,emptyMessage:`No GA4 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 H(e){return Math.round(e).toLocaleString(`en-GB`)}function U(e){return e.toFixed(1)}function W(e){return`${(e*100).toFixed(1)}%`}function G(e,t=72){return e.length<=t?e:`${e.slice(0,t-3)}...`}function rr(e){return e.verification?.requested?`${e.verification.verified} checked, ${e.verification.failed} failed`:`off`}function K(e,t){R(e,t.slice(0,25)),t.length>25&&process.stdout.write(`Showing 25 of ${t.length}. Use --json for full data.\n`)}function q(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 ir(e){process.stdout.write(`\nNext: ${e}\n`)}function J(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 ar(e,t={}){let n=E(e[`report-id`]);if(n){let e=s(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 V({client:r,site:i,options:t}):void 0,o=It(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 or(e){process.stdout.write(`AI-search evidence for ${e.url}\n\n`),L([[`Assessment`,`evidence only`],[`Data`,e.dataStatus],[`Report`,e.reportId]]),process.stdout.write(`\n${e.headline}\n`),e.topActions.length&&(process.stdout.write(`
4
+ Top actions
5
+ `),R([`Status`,`Check`,`Action`],e.topActions.map(e=>[e.status,e.title,G(e.action,96)]))),e.botAccess.length&&(process.stdout.write(`
6
+ Bot access
7
+ `),R([`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`]))),J(`Caveats`,e.caveats)}const sr=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=P(e),n=ct(await ar(e,{json:t}));if(t){I(n);return}or(n)}});function cr(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 lr(e){process.stdout.write(`AI agent readiness for ${e.url}\n\n`),L([[`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(`
8
+ Profile scope
9
+ `),R([`Profile`,`Status`,`Reason`],Object.entries(e.profileApplicability).map(([e,t])=>[e,t.status===`notApplicable`?`not applicable`:t.status,G(t.reason,96)])),e.topActions.length&&(process.stdout.write(`
10
+ What to check next
11
+ `),R([`Status`,`Check`,`Action`],e.topActions.map(e=>[e.status,e.title,G(e.action,96)]))),J(`Caveats`,e.caveats)}const ur=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=P(e),n=cr(e),r=E(e[`report-id`]);if(n&&r)throw Error(`Use either a URL or --report-id, not both.`);let i=ot(n?await(async()=>{let r=D(e),i=E(e.site),a=r||i?await V({client:r,site:i,options:{json:t}}):void 0;return Rt({url:n,projectId:a?.client?.id,site:a?.site,maxPages:k(e[`max-pages`]),fetchRate:N(e),refresh:O(e.refresh),checkAgentDiscovery:!0,useSitemap:!0,checkExternal:!1})})():await ar(e,{json:t}));if(t){I(i);return}lr(i)}});function dr(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 fr(e){return e.active===`keychain`?`system keychain`:e.configured===`keychain`?`private file fallback`:`private file`}const pr=S({meta:{name:`auth`,description:`Authentication commands`},subCommands:{login:S({meta:{name:`login`,description:`Run Google OAuth flow`},run:async()=>{if((await je()).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 Ne();process.stdout.write(`${e.account_email} · ${e.scope.replace(`https://www.googleapis.com/auth/`,``)} · ${e.client_source===`shared`?`shared seo app`:`BYO client`}\n`)}}),logout:S({meta:{name:`logout`,description:`Delete locally stored tokens`},run:async()=>{let e=await je();if(await _e(),e.activeMode===`service-account`){process.stdout.write(`Deleted local OAuth tokens. Service account credentials remain available through the environment.
4
12
  `);return}process.stdout.write(`Deleted local OAuth tokens.
5
13
  Revoke at https://myaccount.google.com/permissions if you also want Google to forget the grant.
6
- `)}}),whoami:x({meta:{name:`whoami`,description:`Show the signed-in Google account`},run:async()=>{let e=await Pe();if(e.activeMode===`service-account`){process.stdout.write(`service account · `+(e.identity??`invalid configuration`)+` · `+$n(e.serviceAccount.source)+`
14
+ `)}}),whoami:S({meta:{name:`whoami`,description:`Show the signed-in Google account`},run:async()=>{let e=await je();if(e.activeMode===`service-account`){process.stdout.write(`service account · `+(e.identity??`invalid configuration`)+` · `+dr(e.serviceAccount.source)+`
7
15
  `);return}let t=e.tokens;if(!t){process.stdout.write(`Not logged in.
8
- `);return}process.stdout.write(`${t.account_email} · ${t.scope.replace(`https://www.googleapis.com/auth/`,``)} · ${Un(t.expires_at)} · ${t.client_source===`shared`?`shared seo app`:`BYO client`}\n`)}}),status:x({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 n=await Pe(),r=n.activeMode===`oauth`?await t():void 0;if(N(e)){F({authenticated:n.activeMode===`oauth`?!!n.tokens:n.serviceAccount.configured,mode:n.activeMode,identity:n.identity,account:n.activeMode===`oauth`?n.tokens?.account_email:void 0,scopes:n.activeMode===`oauth`?n.tokens?.scope:void 0,clientSource:n.activeMode===`oauth`?n.tokens?.client_source:void 0,expiresAt:n.activeMode===`oauth`?n.tokens?.expires_at:void 0,sharedConfigured:n.sharedConfigured,byoConfigured:n.byoConfigured,serviceAccount:{configured:n.serviceAccount.configured,identity:n.serviceAccount.identity,source:n.serviceAccount.source,error:n.serviceAccount.error},...r?{tokenStorage:r}:{}});return}if(n.activeMode===`service-account`){let e=[[`Mode`,`service account`],[`Identity`,n.identity??`unavailable`],[`Source`,$n(n.serviceAccount.source)],[`Scopes`,`Search Console readonly, GA4 readonly`],[`Storage`,`credentials stay in the environment`]];n.serviceAccount.error&&e.push([`Status`,n.serviceAccount.error]),I(e);return}if(!n.tokens){let e=n.serviceAccount.error?n.serviceAccount.error:n.sharedConfigured?`Shared seo app available`:n.byoConfigured?`BYO client configured`:`No OAuth client configured`;process.stdout.write(`Not logged in. ${e}. Run \`seo auth login\` to connect Google.\n`);return}let i=r??await t();I([[`Account`,n.tokens.account_email],[`Scopes`,n.tokens.scope],[`Client`,n.tokens.client_source===`shared`?`shared seo app`:`BYO client`],[`Expires`,Un(n.tokens.expires_at)],[`Tokens file`,Be().tokensFile],[`Token storage`,er(i)],...i.reason?[[`Storage note`,i.reason]]:[],[`OAuth mode`,n.sharedConfigured?`shared app available`:n.byoConfigured?`BYO client configured`:`missing`],[`Revoke at`,`https://myaccount.google.com/permissions`]])}}),storage:x({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 _(`INVALID_INPUT`,`Choose either --keychain or --file, not both.`);let n=e.keychain||e.file?await Dt(e.keychain?`keychain`:`file`):await t();if(N(e)){F(n);return}I([[`Configured storage`,n.configured],[`Active storage`,er(n)],...n.reason?[[`Note`,n.reason]]:[]])}}),refresh:x({meta:{name:`refresh`,description:`Refresh the Google OAuth token`},run:async()=>{if((await Pe()).activeMode===`service-account`){process.stdout.write(`Service accounts request short-lived Google tokens automatically. No local refresh token is stored.
9
- `);return}let e=await Me();process.stdout.write(`Refreshed. New expiry ${new Date(e.expires_at).toISOString()}.\n`)}}),"setup-client":x({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(!P({json:N(e)}))throw new _(`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.");Sn(`seo BYO OAuth client`),S([`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(`
10
- `),`Advanced`),St({clientId:R(await Dn({message:`Google Desktop OAuth client ID`,validate:e=>e?void 0:`Client ID is required`})),clientSecret:R(await Tn({message:`Google Desktop OAuth client secret`,validate:e=>e?void 0:`Client secret is required`}))}),wn(`Saved BYO OAuth client to ${Be().oauthClientFile}`)}})}}),nr=x({meta:{name:`cache`,description:`Cache helpers`},subCommands:{stats:x({meta:{name:`stats`,description:`Show local cache size and row counts`},run:async()=>{let e=bt();I([[`DB`,e.dbPath],[`Size`,Qn(e.sizeBytes)],[`sites`,String(e.counts.sites??0)],[`gsc_cache`,String(e.counts.gsc_cache??0)],[`ga4_cache`,String(e.counts.ga4_cache??0)],[`semrush_cache`,String(e.counts.semrush_cache??0)],[`http_cache`,String(e.counts.http_cache??0)]])}}),clear:x({meta:{name:`clear`,description:`Clear cached API and HTTP data`},args:{provider:{type:`string`,description:`Optional cache provider: gsc, ga4, semrush, or http`}},run:async({args:e})=>{let t=oe(e.provider);process.stdout.write(`Removed ${t} cached rows.\n`)}})}});function rr(e,t){let n=t.trim().toLowerCase();return!n||e.some(e=>e.toLowerCase().includes(n))}async function ir(e){let t=e.choices;if(e.choices.length>8){let n=R(await Dn({message:e.searchMessage,placeholder:`Type part of the name or URL, or leave blank`}));if(t=e.choices.filter(t=>rr(e.searchValues(t),n)),!t.length)throw new _(`INVALID_INPUT`,e.emptyMessage)}return R(await En({message:e.message,options:t.map(t=>{let n=e.hint?.(t);return{value:t,label:e.label(t),...n?{hint:n}:{}}})}))}async function z(e){if(e.site)return e.site;let t=Ye(),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||!P())throw new _(`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 ce(e.options?.refresh);if(!r.length)throw new _(`PROPERTY_NOT_FOUND`,`No Search Console properties found for this Google login.`);return r.length===1?r[0]?.siteUrl??``:(await ir({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 B(e){if(e.client&&e.project&&e.client!==e.project)throw new _(`INVALID_INPUT`,`Use either --project or --client, not both.`);let t=e.project??e.client;if(t){let e=qe(t);if(!e)throw new _(`INVALID_INPUT`,`Project not found: ${t}`);return{client:e,site:e.siteUrl}}let n=qe();return!e.site&&n?{client:n,site:n.siteUrl}:{site:await z({site:e.site,options:e.options})}}async function ar(e){if(e.client&&e.project&&e.client!==e.project)throw new _(`INVALID_INPUT`,`Use either --project or --client, not both.`);let t=e.project??e.client;if(t){let e=qe(t);if(!e)throw new _(`INVALID_INPUT`,`Project not found: ${t}`);return e}let n=Ze();if(!n.length)return;let r=qe();if(r)return r;if(!(e.options?.json||!P()))return ir({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 or(e){if(e.property)return e.property;let t=Ye();if(t.google.defaultGa4PropertyId)return t.google.defaultGa4PropertyId;if(e.options?.json||!P())throw new _(`INVALID_INPUT`,`No GA4 property selected. Pass --property or run this command in a terminal to choose one.`);let n=(await Ee()).flatMap(e=>e.propertySummaries.map(t=>({property:Oe(t.property),displayName:t.displayName??t.property,account:e.displayName??e.account})));if(!n.length)throw new _(`PROPERTY_NOT_FOUND`,`No GA4 properties found for this Google login.`);return n.length===1?n[0]?.property??``:(await ir({message:`Choose a GA4 property`,searchMessage:`Search GA4 properties`,emptyMessage:`No GA4 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 sr(e,t={}){return(await B({client:e.client,project:e.project,site:e.site,options:t})).site}function V(e){if(e.startsWith(`http://`)||e.startsWith(`https://`))return e;if(e.startsWith(`sc-domain:`))return`https://${e.slice(10)}/`}function cr(e){return e.replace(/^sc-domain:/,``).replace(/^https?:\/\//,``)}function lr(e){return e.toLowerCase().replace(/^https?:\/\//,``).replace(/^sc-domain:/,``).replace(/[^a-z0-9]+/g,`-`).replace(/^-+|-+$/g,``).slice(0,60)}function ur(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 dr(e){return e?e===`serp-framing`?`wording gap`:e===`content-gap`?`content gap`:e===`technical-check`?`technical check`:`covered`:`-`}function fr(e){let t=e.platform===`win32`?rn:en;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 pr(e={}){let t=e.home??Zt(),n=e.platform??process.platform,r=fr({home:t,platform:n,env:e.env??process.env}),i=n===`win32`?rn:en,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 mr(e,t={}){let n=t.platform??process.platform,r=t.exists??v,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=b(e,t);if(r(n))return n}for(let e of a){let t=b(y(i),e);if(r(t))return t}return e}function hr(e){return mr(e)!==e}function gr(e={}){let t=e.targets??pr(),n=e.hasCommand??hr,r=e.hasPath??v;return t.filter(e=>e.kind===`native`?e.commandNames.some(n):r(e.path)||e.detectionPaths.some(r)||e.commandNames.some(n))}const _r={insertSpaces:!0,tabSize:2,eol:`
11
- `};function vr(){return new Date().toISOString().replace(/[:.]/g,`-`)}function yr(e,t){Ut(y(e),{recursive:!0,mode:448});let n=`${e}.${process.pid}.${Date.now()}.tmp`;Xt(n,t,{encoding:`utf8`,mode:v(e)?Yt(e).mode&511:384}),qt(n,e)}function br(e){let t=`${e}.bak-${vr()}`,n=t,r=2;for(;v(n);)n=`${t}-${r}`,r+=1;Ht(e,n,Vt.COPYFILE_EXCL)}function xr(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function Sr(e){if(!v(e.path))return{source:`{}
12
- `,config:{}};let t=Wt(e.path,`utf8`),n=[],r=Nn(t,n,{allowTrailingComma:!0});if(n.length>0){let t=n[0],r=t?`${Pn(t.error)} at offset ${t.offset}`:`unknown parse error`;throw Error(`Cannot update ${e.path}: invalid JSONC (${r}).`)}if(!xr(r))throw Error(`Cannot update ${e.path}: config root must be an object.`);if(r.mcpServers!==void 0&&!xr(r.mcpServers))throw Error(`Cannot update ${e.path}: mcpServers must be an object.`);return{source:t,config:r}}function Cr(e){return e.includes(`mcp`)&&e.includes(`serve`)&&e.some(e=>e===`seo`||e.startsWith(`seo@`)||e===`@seo/cli`)}function wr(e,t){return typeof e.command!=`string`||t.length!==3||t[1]!==`mcp`||t[2]!==`serve`||!xr(e.env)?!1:e.env.SEO_MCP_CONFIGURED_BY===`seo`}function Tr(e){if(!xr(e)||!Array.isArray(e.args))return!1;let t=e.args.filter(e=>typeof e==`string`);return Cr(t)||wr(e,t)}function Er(e){let t=jn(e.source,Mn(e.source,[`mcpServers`,`seo`],e.value,{formattingOptions:_r}));v(e.target.path)&&br(e.target.path),yr(e.target.path,t)}function Dr(e,t){let n=sn(e,t,{encoding:`utf8`});return{status:n.status,stdout:n.stdout??``,stderr:n.stderr??``,error:n.error}}function Or(e){let t=e.replaceAll(`\\`,`/`).toLowerCase();return t.includes(`/_npx/`)||t.includes(`/.npm/_npx/`)||t.includes(`/.cache/pnpm/dlx/`)}function kr(e){if(!e||!v(e))return;let t=Kt(e);return Or(t)?void 0:t}function Ar(e={}){let t=kr(e.cliPath??process.argv[1]);if(t)return{command:e.nodePath??process.execPath,args:[t,`mcp`,`serve`]};let n=`seo@${e.version??We}`;return(e.platform??process.platform)===`win32`?{command:`cmd`,args:[`/c`,`npx`,`-y`,n,`mcp`,`serve`]}:{command:`npx`,args:[`-y`,n,`mcp`,`serve`]}}function jr(e=!1,t=Ar()){let n=Be(),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 Mr(e){return e.includes(`SEO_MCP_CONFIGURED_BY`)||/args:\s+-y\s+(?:seo(?:@\S+)?|@seo\/cli)\s+mcp\s+serve\b/.test(e)}function Nr(e,t,n){let r=mr(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&&!Mr(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=Be(),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 Pr(e,t={}){let n={runCommand:t.runCommand??Dr,reinstall:t.reinstall??!1,serverCommand:t.serverCommand??Ar()};if(e.kind===`native`)return Nr(e,`install`,n);let{source:r,config:i}=Sr(e),a=i.mcpServers?.seo,o=jr(e.includeType,n.serverCommand);if(ln(a,o)&&!n.reinstall)return{client:e.client,path:e.path,changed:!1};if(a!==void 0&&!Tr(a))throw Error(`Cannot install SEO MCP: ${e.path} already has an unmanaged mcpServers.seo entry.`);return Er({target:e,source:r,value:o}),{client:e.client,path:e.path,changed:!0}}function Fr(e,t={}){if(e.kind===`native`)return Nr(e,`uninstall`,{runCommand:t.runCommand??Dr,reinstall:!1,serverCommand:Ar()});if(!v(e.path))return{client:e.client,path:e.path,changed:!1};let{source:n,config:r}=Sr(e),i=r.mcpServers;if(i?.seo===void 0)return{client:e.client,path:e.path,changed:!1};if(!Tr(i.seo))throw Error(`Cannot uninstall SEO MCP: ${e.path} has an unmanaged mcpServers.seo entry.`);return Er({target:e,source:n,value:void 0}),{client:e.client,path:e.path,changed:!0}}function Ir(e={}){let t=sn(`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 Lr(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 GA4 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 Rr(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 Ae(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 zr(e){let t=await Pe();if(t.activeMode===`service-account`)return`service-account`;if(t.tokens)return`already-connected`;if(e[`skip-auth`])return`skipped`;if(!P({json:e.json===!0}))throw new _(`AUTH_REQUIRED`,"Not logged in. Run `seo auth login`, or pass --skip-auth to save a project profile without connecting Google.");let n=R(await En({message:`Connect Google now?`,options:Lr({sharedConfigured:t.sharedConfigured,byoConfigured:t.byoConfigured,canSkip:typeof e.site==`string`&&e.site.length>0})}));return n===`skip`?`skipped`:(n===`setup`&&St({clientId:R(await Dn({message:`Google Desktop OAuth client ID`,validate:e=>e?void 0:`Client ID is required`})),clientSecret:R(await Tn({message:`Google Desktop OAuth client secret`,validate:e=>e?void 0:`Client secret is required`}))}),S(`Connected as ${(await ie()).account_email}. seo has read-only access and cannot change your site.`,`Google connected`),`connected`)}async function Br(e){if(e.property)return{propertyId:e.property,selection:`explicit`,reason:`Set with --ga4.`};if(!(e.interactive??P()))return;let t=(await Ee().catch(()=>[])).flatMap(e=>e.propertySummaries.map(t=>({property:Oe(t.property),label:t.displayName??t.property,account:e.displayName??e.account})));if(!t.length)return;let{candidates:n,complete:r}=await Rr(t),i=r?Vn(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:Hn(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=R(await En({message:o.length?`Several GA4 properties match this site. Which property should seo use?`:r?`No GA4 web stream clearly matches this site. Attach a property?`:`Some GA4 web streams could not be read. Attach a property?`,options:[{value:``,label:`Skip GA4 for now`},...s.map(t=>({value:t,label:`${t.label} (${t.property})`,hint:t.match?Hn(t.match,e.site):t.account}))]}));if(c)return{propertyId:c.property,selection:`manual`,reason:c.match?Hn(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 GA4 web stream, so it did not guess a match.`}}async function Vr(e){return e[`skip-skill`]||!P({json:e.json===!0})?{status:`skipped`}:R(await xn({message:`Install the SEO skill so coding agents know how to run reports?`,initialValue:!0}))?Ir():{status:`declined`}}async function Hr(e){if(e[`skip-mcp`]||!P({json:e.json===!0}))return[];let t=gr();if(t.length===0||!R(await xn({message:`Install seo as an MCP server too?`,initialValue:!0})))return[];let n=R(await Cn({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 Pr(e)}catch(t){return{client:e.client,path:e.path,changed:!1,error:t instanceof Error?t.message:String(t)}}})}function Ur(e){return e.status===`installed`?`installed`:e.status===`failed`?`install failed`:`not installed`}function Wr(e){return/^[A-Za-z0-9_./:@-]+$/.test(e)?e:`'${e.replaceAll(`'`,`'\\''`)}'`}function Gr(){console.log(kn.dim(`Find seo useful? A star helps other people discover it: https://github.com/iannuttall/seo`))}function Kr(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 qr(e){let t=e.filter(e=>!!e.error);if(t.length!==0)return t.map(e=>`${e.client}: ${e.error}`).join(`
13
- `)}function Jr(e,t,n){if(n)return;let r=Ze().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 Yr(e){let t=N(e);if(t||Sn(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?F({dryRun:!0,next:e}):(S(e.join(`
14
- `),`This setup will guide you through`),wn(`Dry run complete.`));return}it();let n={site:T(e.site),options:{json:t,refresh:D(e.refresh)}},r=t?await z(n):void 0,i=await zr(e),a=r??await z(n),o=cr(a);if(P({json:t})&&S(`A project profile remembers the site and report defaults so future commands stay short.`,`Project profile`),!(D(e[`skip-profile`])!==!0&&(!P({json:t})||R(await xn({message:`Save this site as a project profile?`,initialValue:!0}))))){let n=await Hr(e),r=await Vr(e),o=`--site ${Wr(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){F(c);return}I([[`Project profile`,`not saved`],[`GSC property`,a],[`Auth`,i],[`MCP`,Kr(n)],[`SEO skill`,Ur(r)]]);let l=qr(n);l&&S(l,`MCP setup skipped`),r.error&&S(r.error,`SEO skill install failed`),S(s.join(`
15
- `),`Try next`),wn(`Setup complete.`),Gr();return}let s=T(e.name)??(P({json:t})?R(await Dn({message:`Project name`,placeholder:o,defaultValue:o})):o),c=T(e.id),l=c??Jr(a,s,c)?.id??lr(s),u=V(a)??``,d=T(e.url)??(P({json:t})?R(await Dn({message:`Website URL to crawl`,placeholder:u||`https://example.com`,defaultValue:u})):u||void 0),f=M(e.urls).length>0?M(e.urls):[],p=await Br({property:T(e.ga4),site:a,interactive:P({json:t})}),m=p?.propertyId,h=ge({id:l,name:s,siteUrl:a}),g=_t({id:l,name:s,siteUrl:a,startUrl:d,watchUrls:f,brandTerms:M(e.brand).length>0?M(e.brand):h,ga4PropertyId:m,reportDay:k(e[`report-day`])??1,technicalWeekday:k(e.weekday)??1,isDefault:D(e.default)??!0}),ee=await Hr(e),te=await Vr(e),ne=[`seo report --project ${g.id}`,`seo refresh-priorities --project ${g.id} --verify-content`,`seo technical-watch --project ${g.id}`],re={client:g,site:a,auth:i,ga4:p,mcp:ee,skill:te,next:ne};if(t){F(re);return}I([[`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`],[`GA4 property`,g.ga4PropertyId??`not connected (optional)`],...p?[[`GA4 selection`,p.reason]]:[],[`Auth`,i],[`MCP`,Kr(ee)],[`SEO skill`,Ur(te)]]);let ie=qr(ee);ie&&S(ie,`MCP setup skipped`),te.error&&S(te.error,`SEO skill install failed`),S(ne.join(`
16
- `),`Try next`),wn(`Setup complete.`),Gr()}const Xr=x({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.`},ga4:{type:`string`,description:`Optional GA4 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})=>Yr(e)}),Zr=x({...Xr,meta:{name:`start`,description:`Start here: connect Google, save a profile, and get the first report commands`}}),Qr=x({...Xr,meta:{name:`setup`,description:`Guided setup for one saved project profile`}}),$r=x({meta:{name:`brand`,description:`Detect and manage project brand query terms`},subCommands:{detect:x({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=qe(E(e)),r=await p({site:await z({site:T(e.site)??n?.siteUrl,options:{json:t,refresh:D(e.refresh)}}),id:n?.id,name:n?.name,days:k(e.days),limit:k(e.limit),minImpressions:k(e[`min-impressions`]),refresh:D(e.refresh)}),i=D(e.save)&&n?_t({id:n.id,name:n.name,siteUrl:n.siteUrl,startUrl:n.startUrl,watchUrls:n.watchUrls,ga4PropertyId:n.ga4PropertyId,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){F({...r,saved:i});return}I([[`Property`,r.site],[`Derived terms`,r.derivedTerms.join(`, `)],[`Suggested terms`,r.suggestedTerms.join(`, `)],[`Saved`,i?i.id:`no`]]),L([`Term`,`Score`,`Evidence`],r.candidates.map(e=>[e.term,e.score,e.evidence.map(e=>`${e.query} (${Math.round(e.clicks)} clicks)`).join(`, `)]))}})}});function ei(e){L([`Default`,`ID`,`Name`,`GSC property`,`Crawl URL`,`Watch URLs`,`Brand terms`],e.map(e=>[e.isDefault?`yes`:``,e.id,e.name,e.siteUrl,e.startUrl??``,e.watchUrls.length,e.brandTerms.join(`, `)]))}function ti(e){I([[`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`],[`GA4 property`,e.ga4PropertyId??`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 ni=x({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.`},ga4:{type:`string`,description:`Optional GA4 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=_t({id:T(e.id),name:T(e.name),siteUrl:await z({site:T(e.site),options:{json:t}}),startUrl:T(e.url),watchUrls:M(e.urls),brandTerms:M(e.brand),ga4PropertyId:T(e.ga4),reportDay:k(e[`report-day`]),technicalWeekday:k(e.weekday),isDefault:D(e.default)});if(t){F(n);return}ti(n)}}),ri=x({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=ft(T(e.id)??``);if(N(e)){F(t);return}process.stdout.write(`Default project set to ${t.id}.\n`)}}),ii=x({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=kt(t);if(N(e)){F({id:t,deleted:n});return}process.stdout.write(`${n?`Deleted`:`Not found`} ${t}.\n`)}}),ai=x({meta:{name:`client`,description:`Manage saved project profiles`},subCommands:{setup:Qr,brand:$r,list:x({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=Ze();if(N(e)){F({clients:t});return}ei(t)}}),add:ni,show:x({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=qe(T(e.id));if(!t)throw Error(`Project not found.`);if(N(e)){F(t);return}ti(t)}}),default:ri,delete:ii}}),oi=x({...ai,meta:{name:`projects`,description:`Manage saved project profiles`}});function H(e){return Math.round(e).toLocaleString(`en-GB`)}function U(e){return e.toFixed(1)}function W(e){return`${(e*100).toFixed(1)}%`}function G(e,t=72){return e.length<=t?e:`${e.slice(0,t-3)}...`}function si(e){return e.verification?.requested?`${e.verification.verified} checked, ${e.verification.failed} failed`:`off`}function K(e,t){L(e,t.slice(0,25)),t.length>25&&process.stdout.write(`Showing 25 of ${t.length}. Use --json for full data.\n`)}function q(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 ci(e){process.stdout.write(`\nNext: ${e}\n`)}function J(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`)}}const li=x({meta:{name:`content`,description:`Optimize page content from real GSC queries`},subCommands:{optimize:x({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=N(e),n=await B({client:E(e),site:T(e.site),options:{json:t,refresh:D(e.refresh)}}),r=await st({site:n.site,url:T(e.url)??``,days:k(e.days),limit:k(e.limit),minImpressions:k(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){F(r);return}I([[`Property`,r.site],[`URL`,r.url],[`Score`,`${r.summary.score}/100`],[`Primary intent`,r.summary.primaryIntent],[`Primary query`,r.summary.primaryQuery??`none`],[`Queries`,H(r.summary.queries)],[`Opportunities`,H(r.summary.opportunities)],[`Estimated lift`,H(r.summary.estimatedClickLift)],[`Verdict`,r.summary.verdict]]),q(`Top actions`,r.topActions.map(e=>({label:e.title,action:e.action,context:e.plainEnglish}))),r.brief.sections.length&&(process.stdout.write(`
16
+ `);return}process.stdout.write(`${t.account_email} · ${t.scope.replace(`https://www.googleapis.com/auth/`,``)} · ${Un(t.expires_at)} · ${t.client_source===`shared`?`shared seo app`:`BYO client`}\n`)}}),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 je(),n=t.activeMode===`oauth`?await Je():void 0;if(P(e)){I({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`,dr(t.serviceAccount.source)],[`Scopes`,`Search Console readonly, GA4 readonly`],[`Storage`,`credentials stay in the environment`]];t.serviceAccount.error&&e.push([`Status`,t.serviceAccount.error]),L(e);return}if(!t.tokens){let e=t.serviceAccount.error?t.serviceAccount.error:t.sharedConfigured?`Shared seo app available`:t.byoConfigured?`BYO client configured`:`No OAuth client configured`;process.stdout.write(`Not logged in. ${e}. Run \`seo auth login\` to connect Google.\n`);return}let r=n??await Je();L([[`Account`,t.tokens.account_email],[`Scopes`,t.tokens.scope],[`Client`,t.tokens.client_source===`shared`?`shared seo app`:`BYO client`],[`Expires`,Un(t.tokens.expires_at)],[`Tokens file`,gt().tokensFile],[`Token storage`,fr(r)],...r.reason?[[`Storage note`,r.reason]]:[],[`OAuth mode`,t.sharedConfigured?`shared app available`:t.byoConfigured?`BYO client configured`:`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 mt(e.keychain?`keychain`:`file`):await Je();if(P(e)){I(t);return}L([[`Configured storage`,t.configured],[`Active storage`,fr(t)],...t.reason?[[`Note`,t.reason]]:[]])}}),refresh:S({meta:{name:`refresh`,description:`Refresh the Google OAuth token`},run:async()=>{if((await je()).activeMode===`service-account`){process.stdout.write(`Service accounts request short-lived Google tokens automatically. No local refresh token is stored.
17
+ `);return}let e=await Fe();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(!F({json:P(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.");Sn(`seo BYO 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(`
18
+ `),`Advanced`),rt({clientId:z(await Dn({message:`Google Desktop OAuth client ID`,validate:e=>e?void 0:`Client ID is required`})),clientSecret:z(await Tn({message:`Google Desktop OAuth client secret`,validate:e=>e?void 0:`Client secret is required`}))}),wn(`Saved BYO OAuth client to ${gt().oauthClientFile}`)}})}}),mr=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=re();L([[`DB`,e.dbPath],[`Size`,Qn(e.sizeBytes)],[`sites`,String(e.counts.sites??0)],[`gsc_cache`,String(e.counts.gsc_cache??0)],[`ga4_cache`,String(e.counts.ga4_cache??0)],[`semrush_cache`,String(e.counts.semrush_cache??0)],[`http_cache`,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, ga4, semrush, or http`}},run:async({args:e})=>{let t=xt(e.provider);process.stdout.write(`Removed ${t} cached rows.\n`)}})}});async function hr(e,t={}){return(await V({client:e.client,project:e.project,site:e.site,options:t})).site}function Y(e){if(e.startsWith(`http://`)||e.startsWith(`https://`))return e;if(e.startsWith(`sc-domain:`))return`https://${e.slice(10)}/`}function gr(e){return e.replace(/^sc-domain:/,``).replace(/^https?:\/\//,``)}function _r(e){return e.toLowerCase().replace(/^https?:\/\//,``).replace(/^sc-domain:/,``).replace(/[^a-z0-9]+/g,`-`).replace(/^-+|-+$/g,``).slice(0,60)}function vr(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 yr(e){return e?e===`serp-framing`?`wording gap`:e===`content-gap`?`content gap`:e===`technical-check`?`technical check`:`covered`:`-`}function br(e){let t=e.platform===`win32`?rn:en;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 xr(e={}){let t=e.home??Zt(),n=e.platform??process.platform,r=br({home:t,platform:n,env:e.env??process.env}),i=n===`win32`?rn:en,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 Sr(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 Cr(e){return Sr(e)!==e}function wr(e={}){let t=e.targets??xr(),n=e.hasCommand??Cr,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 Tr={insertSpaces:!0,tabSize:2,eol:`
19
+ `};function Er(){return new Date().toISOString().replace(/[:.]/g,`-`)}function Dr(e,t){Ut(b(e),{recursive:!0,mode:448});let n=`${e}.${process.pid}.${Date.now()}.tmp`;Xt(n,t,{encoding:`utf8`,mode:y(e)?Yt(e).mode&511:384}),qt(n,e)}function Or(e){let t=`${e}.bak-${Er()}`,n=t,r=2;for(;y(n);)n=`${t}-${r}`,r+=1;Ht(e,n,Vt.COPYFILE_EXCL)}function kr(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function Ar(e){if(!y(e.path))return{source:`{}
20
+ `,config:{}};let t=Wt(e.path,`utf8`),n=[],r=Nn(t,n,{allowTrailingComma:!0});if(n.length>0){let t=n[0],r=t?`${Pn(t.error)} at offset ${t.offset}`:`unknown parse error`;throw Error(`Cannot update ${e.path}: invalid JSONC (${r}).`)}if(!kr(r))throw Error(`Cannot update ${e.path}: config root must be an object.`);if(r.mcpServers!==void 0&&!kr(r.mcpServers))throw Error(`Cannot update ${e.path}: mcpServers must be an object.`);return{source:t,config:r}}function jr(e){return e.includes(`mcp`)&&e.includes(`serve`)&&e.some(e=>e===`seo`||e.startsWith(`seo@`)||e===`@seo/cli`)}function Mr(e,t){return typeof e.command!=`string`||t.length!==3||t[1]!==`mcp`||t[2]!==`serve`||!kr(e.env)?!1:e.env.SEO_MCP_CONFIGURED_BY===`seo`}function Nr(e){if(!kr(e)||!Array.isArray(e.args))return!1;let t=e.args.filter(e=>typeof e==`string`);return jr(t)||Mr(e,t)}function Pr(e){let t=jn(e.source,Mn(e.source,[`mcpServers`,`seo`],e.value,{formattingOptions:Tr}));y(e.target.path)&&Or(e.target.path),Dr(e.target.path,t)}function Fr(e,t){let n=sn(e,t,{encoding:`utf8`});return{status:n.status,stdout:n.stdout??``,stderr:n.stderr??``,error:n.error}}function Ir(e){let t=e.replaceAll(`\\`,`/`).toLowerCase();return t.includes(`/_npx/`)||t.includes(`/.npm/_npx/`)||t.includes(`/.cache/pnpm/dlx/`)}function Lr(e){if(!e||!y(e))return;let t=Kt(e);return Ir(t)?void 0:t}function Rr(e={}){let t=Lr(e.cliPath??process.argv[1]);if(t)return{command:e.nodePath??process.execPath,args:[t,`mcp`,`serve`]};let n=`seo@${e.version??Ue}`;return(e.platform??process.platform)===`win32`?{command:`cmd`,args:[`/c`,`npx`,`-y`,n,`mcp`,`serve`]}:{command:`npx`,args:[`-y`,n,`mcp`,`serve`]}}function zr(e=!1,t=Rr()){let n=gt(),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 Br(e){return e.includes(`SEO_MCP_CONFIGURED_BY`)||/args:\s+-y\s+(?:seo(?:@\S+)?|@seo\/cli)\s+mcp\s+serve\b/.test(e)}function Vr(e,t,n){let r=Sr(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&&!Br(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=gt(),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 Hr(e,t={}){let n={runCommand:t.runCommand??Fr,reinstall:t.reinstall??!1,serverCommand:t.serverCommand??Rr()};if(e.kind===`native`)return Vr(e,`install`,n);let{source:r,config:i}=Ar(e),a=i.mcpServers?.seo,o=zr(e.includeType,n.serverCommand);if(ln(a,o)&&!n.reinstall)return{client:e.client,path:e.path,changed:!1};if(a!==void 0&&!Nr(a))throw Error(`Cannot install SEO MCP: ${e.path} already has an unmanaged mcpServers.seo entry.`);return Pr({target:e,source:r,value:o}),{client:e.client,path:e.path,changed:!0}}function Ur(e,t={}){if(e.kind===`native`)return Vr(e,`uninstall`,{runCommand:t.runCommand??Fr,reinstall:!1,serverCommand:Rr()});if(!y(e.path))return{client:e.client,path:e.path,changed:!1};let{source:n,config:r}=Ar(e),i=r.mcpServers;if(i?.seo===void 0)return{client:e.client,path:e.path,changed:!1};if(!Nr(i.seo))throw Error(`Cannot uninstall SEO MCP: ${e.path} has an unmanaged mcpServers.seo entry.`);return Pr({target:e,source:n,value:void 0}),{client:e.client,path:e.path,changed:!0}}function Wr(e={}){let t=sn(`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 Gr(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 GA4 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 Kr(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 qr(e){let t=await je();if(t.activeMode===`service-account`)return`service-account`;if(t.tokens)return`already-connected`;if(e[`skip-auth`])return`skipped`;if(!F({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=z(await En({message:`Connect Google now?`,options:Gr({sharedConfigured:t.sharedConfigured,byoConfigured:t.byoConfigured,canSkip:typeof e.site==`string`&&e.site.length>0})}));return n===`skip`?`skipped`:(n===`setup`&&rt({clientId:z(await Dn({message:`Google Desktop OAuth client ID`,validate:e=>e?void 0:`Client ID is required`})),clientSecret:z(await Tn({message:`Google Desktop OAuth client secret`,validate:e=>e?void 0:`Client secret is required`}))}),C(`Connected as ${(await Ne()).account_email}. seo has read-only access and cannot change your site.`,`Google connected`),`connected`)}async function Jr(e){if(e.property)return{propertyId:e.property,selection:`explicit`,reason:`Set with --ga4.`};if(!(e.interactive??F()))return;let t=(await ke().catch(()=>[])).flatMap(e=>e.propertySummaries.map(t=>({property:_(t.property),label:t.displayName??t.property,account:e.displayName??e.account})));if(!t.length)return;let{candidates:n,complete:r}=await Kr(t),i=r?Vn(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:Hn(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=z(await En({message:o.length?`Several GA4 properties match this site. Which property should seo use?`:r?`No GA4 web stream clearly matches this site. Attach a property?`:`Some GA4 web streams could not be read. Attach a property?`,options:[{value:``,label:`Skip GA4 for now`},...s.map(t=>({value:t,label:`${t.label} (${t.property})`,hint:t.match?Hn(t.match,e.site):t.account}))]}));if(c)return{propertyId:c.property,selection:`manual`,reason:c.match?Hn(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 GA4 web stream, so it did not guess a match.`}}async function Yr(e){return e[`skip-skill`]||!F({json:e.json===!0})?{status:`skipped`}:z(await xn({message:`Install the SEO skill so coding agents know how to run reports?`,initialValue:!0}))?Wr():{status:`declined`}}async function Xr(e){if(e[`skip-mcp`]||!F({json:e.json===!0}))return[];let t=wr();if(t.length===0||!z(await xn({message:`Install seo as an MCP server too?`,initialValue:!0})))return[];let n=z(await Cn({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 Hr(e)}catch(t){return{client:e.client,path:e.path,changed:!1,error:t instanceof Error?t.message:String(t)}}})}function Zr(e){return e.status===`installed`?`installed`:e.status===`failed`?`install failed`:`not installed`}function Qr(e){return/^[A-Za-z0-9_./:@-]+$/.test(e)?e:`'${e.replaceAll(`'`,`'\\''`)}'`}function $r(){console.log(kn.dim(`Find seo useful? A star helps other people discover it: https://github.com/iannuttall/seo`))}function ei(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 ti(e){let t=e.filter(e=>!!e.error);if(t.length!==0)return t.map(e=>`${e.client}: ${e.error}`).join(`
21
+ `)}function ni(e,t,n){if(n)return;let r=Xe().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 ri(e){let t=P(e);if(t||Sn(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?I({dryRun:!0,next:e}):(C(e.join(`
22
+ `),`This setup will guide you through`),wn(`Dry run complete.`));return}ze();let n={site:E(e.site),options:{json:t,refresh:O(e.refresh)}},r=t?await B(n):void 0,i=await qr(e),a=r??await B(n),o=gr(a);if(F({json:t})&&C(`A project profile remembers the site and report defaults so future commands stay short.`,`Project profile`),!(O(e[`skip-profile`])!==!0&&(!F({json:t})||z(await xn({message:`Save this site as a project profile?`,initialValue:!0}))))){let n=await Xr(e),r=await Yr(e),o=`--site ${Qr(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){I(c);return}L([[`Project profile`,`not saved`],[`GSC property`,a],[`Auth`,i],[`MCP`,ei(n)],[`SEO skill`,Zr(r)]]);let l=ti(n);l&&C(l,`MCP setup skipped`),r.error&&C(r.error,`SEO skill install failed`),C(s.join(`
23
+ `),`Try next`),wn(`Setup complete.`),$r();return}let s=E(e.name)??(F({json:t})?z(await Dn({message:`Project name`,placeholder:o,defaultValue:o})):o),c=E(e.id),l=c??ni(a,s,c)?.id??_r(s),u=Y(a)??``,d=E(e.url)??(F({json:t})?z(await Dn({message:`Website URL to crawl`,placeholder:u||`https://example.com`,defaultValue:u})):u||void 0),f=M(e.urls).length>0?M(e.urls):[],m=await Jr({property:E(e.ga4),site:a,interactive:F({json:t})}),h=m?.propertyId,g=p({id:l,name:s,siteUrl:a}),_=vt({id:l,name:s,siteUrl:a,startUrl:d,watchUrls:f,brandTerms:M(e.brand).length>0?M(e.brand):g,ga4PropertyId:h,reportDay:k(e[`report-day`])??1,technicalWeekday:k(e.weekday)??1,isDefault:O(e.default)??!0}),ee=await Xr(e),te=await Yr(e),ne=[`seo report --project ${_.id}`,`seo refresh-priorities --project ${_.id} --verify-content`,`seo technical-watch --project ${_.id}`],re={client:_,site:a,auth:i,ga4:m,mcp:ee,skill:te,next:ne};if(t){I(re);return}L([[`Project profile`,`${_.name} (${_.id})`],[`GSC property`,_.siteUrl],[`Crawl URL`,_.startUrl??`not set`],[`Watch URLs`,String(_.watchUrls.length)],[`Brand terms`,_.brandTerms.join(`, `)||`not set`],[`GA4 property`,_.ga4PropertyId??`not connected (optional)`],...m?[[`GA4 selection`,m.reason]]:[],[`Auth`,i],[`MCP`,ei(ee)],[`SEO skill`,Zr(te)]]);let ie=ti(ee);ie&&C(ie,`MCP setup skipped`),te.error&&C(te.error,`SEO skill install failed`),C(ne.join(`
24
+ `),`Try next`),wn(`Setup complete.`),$r()}const ii=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.`},ga4:{type:`string`,description:`Optional GA4 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})=>ri(e)}),ai=S({...ii,meta:{name:`start`,description:`Start here: connect Google, save a profile, and get the first report commands`}}),oi=S({...ii,meta:{name:`setup`,description:`Guided setup for one saved project profile`}}),si=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=P(e),n=Ke(D(e)),r=await te({site:await B({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?vt({id:n.id,name:n.name,siteUrl:n.siteUrl,startUrl:n.startUrl,watchUrls:n.watchUrls,ga4PropertyId:n.ga4PropertyId,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){I({...r,saved:i});return}L([[`Property`,r.site],[`Derived terms`,r.derivedTerms.join(`, `)],[`Suggested terms`,r.suggestedTerms.join(`, `)],[`Saved`,i?i.id:`no`]]),R([`Term`,`Score`,`Evidence`],r.candidates.map(e=>[e.term,e.score,e.evidence.map(e=>`${e.query} (${Math.round(e.clicks)} clicks)`).join(`, `)]))}})}});function ci(e){R([`Default`,`ID`,`Name`,`GSC property`,`Crawl URL`,`Watch URLs`,`Brand terms`],e.map(e=>[e.isDefault?`yes`:``,e.id,e.name,e.siteUrl,e.startUrl??``,e.watchUrls.length,e.brandTerms.join(`, `)]))}function li(e){L([[`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`],[`GA4 property`,e.ga4PropertyId??`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 ui=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.`},ga4:{type:`string`,description:`Optional GA4 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=P(e),n=vt({id:E(e.id),name:E(e.name),siteUrl:await B({site:E(e.site),options:{json:t}}),startUrl:E(e.url),watchUrls:M(e.urls),brandTerms:M(e.brand),ga4PropertyId:E(e.ga4),reportDay:k(e[`report-day`]),technicalWeekday:k(e.weekday),isDefault:O(e.default)});if(t){I(n);return}li(n)}}),di=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=ft(E(e.id)??``);if(P(e)){I(t);return}process.stdout.write(`Default project set to ${t.id}.\n`)}}),fi=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=E(e.id)??``,n=kt(t);if(P(e)){I({id:t,deleted:n});return}process.stdout.write(`${n?`Deleted`:`Not found`} ${t}.\n`)}}),pi=S({meta:{name:`client`,description:`Manage saved project profiles`},subCommands:{setup:oi,brand:si,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=Xe();if(P(e)){I({clients:t});return}ci(t)}}),add:ui,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=Ke(E(e.id));if(!t)throw Error(`Project not found.`);if(P(e)){I(t);return}li(t)}}),default:di,delete:fi}}),mi=S({...pi,meta:{name:`projects`,description:`Manage saved project profiles`}}),hi=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":T(`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=P(e),n=await V({client:D(e),site:E(e.site),options:{json:t,refresh:O(e.refresh)}}),r=await le({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){I(r);return}L([[`Property`,r.site],[`URL`,r.url],[`Score`,`${r.summary.score}/100`],[`Primary intent`,r.summary.primaryIntent],[`Primary query`,r.summary.primaryQuery??`none`],[`Queries`,H(r.summary.queries)],[`Opportunities`,H(r.summary.opportunities)],[`Estimated lift`,H(r.summary.estimatedClickLift)],[`Verdict`,r.summary.verdict]]),q(`Top actions`,r.topActions.map(e=>({label:e.title,action:e.action,context:e.plainEnglish}))),r.brief.sections.length&&(process.stdout.write(`
17
25
  Suggested sections
18
26
  `),K([`Heading`,`Queries`],r.brief.sections.map(e=>[G(e.heading,48),G(e.queries.join(`, `),80)]))),r.intentMix.length&&(process.stdout.write(`
19
27
  Intent mix
20
- `),K([`Intent`,`Queries`,`Impressions`,`Clicks`],r.intentMix.map(e=>[e.intent,H(e.queries),H(e.impressions),H(e.clicks)]))),J(`Caveats`,r.caveats)}})}}),ui={high:3,medium:2,low:1},di=x({meta:{name:`crawl`,description:`Crawl a site and run technical SEO checks`},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.`},"ga4-property":{type:`string`,description:`GA4 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.`},"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, or markdown.`},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=N(e),n=hi(e.format,t),r=T(e.output),i=gi(e.csv),a=pi(e.severity),o=pi(e[`fail-on`]),s=E(e),c=await _i(e),l=mi(e),u=T(e.site)||s||!l&&!c.length?await B({client:s,site:T(e.site),options:{json:t}}):void 0,d=l??u?.client?.startUrl??(u?.site?V(u.site):void 0)??c[0];if(!d)throw Error(`No crawl URL selected. Pass a URL, --url, or use --project with a saved crawl URL.`);let f=await Rt({url:d,projectId:u?.client?.id,site:u?.site,mode:fi(e.mode)??(c.length?`list`:void 0),urls:c,maxPages:k(e[`max-pages`]),maxDepth:k(e[`max-depth`]),concurrency:k(e.concurrency),refresh:D(e.refresh),fetchRate:qn({...e,"fetch-concurrency":e.concurrency}),include:j(e.include),exclude:j(e.exclude),ga4PropertyId:T(e[`ga4-property`])??u?.client?.ga4PropertyId,useSitemap:!O(e,`sitemap`),respectRobots:!O(e,`robots`),checkExternal:!O(e,`external`),js:Wn(e)}),p=D(e.save)?Pt(f):void 0,m=Qe(f,{severity:a}),h=o?f.issues.some(e=>ui[e.severity]>=ui[o]):!1,g=f.status===`failed`,ee={...f,topFixes:m,reviewObservations:Je(f,{severity:a}),...o?{failOn:o,failedThreshold:h}:{},...p?{saved:p}:{}};if(n===`json`){await yi(r,`${JSON.stringify(ee,null,2)}\n`),(g||h)&&(process.exitCode=1);return}if(n===`csv`){await yi(r,i===`pages`?dt(f):gt(f)),(g||h)&&(process.exitCode=1);return}if(n===`html`){await yi(r,Tt(f,m)),(g||h)&&(process.exitCode=1);return}if(n===`markdown`){await yi(r,Ke(f,m)),(g||h)&&(process.exitCode=1);return}if(r){await yi(r,At(f,m)),(g||h)&&(process.exitCode=1);return}process.stdout.write(At(f,m)),(p||o)&&(process.stdout.write(`
28
+ `),K([`Intent`,`Queries`,`Impressions`,`Clicks`],r.intentMix.map(e=>[e.intent,H(e.queries),H(e.impressions),H(e.clicks)]))),J(`Caveats`,r.caveats)}})}}),gi={high:3,medium:2,low:1},_i=S({meta:{name:`crawl`,description:`Crawl a site and run technical SEO checks`},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.`},"ga4-property":{type:`string`,description:`GA4 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.`},"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, or markdown.`},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=P(e),n=xi(e.format,t),r=E(e.output),i=Si(e.csv),a=yi(e.severity),o=yi(e[`fail-on`]),s=D(e),c=await Ci(e),l=bi(e),u=E(e.site)||s||!l&&!c.length?await V({client:s,site:E(e.site),options:{json:t}}):void 0,d=l??u?.client?.startUrl??(u?.site?Y(u.site):void 0)??c[0];if(!d)throw Error(`No crawl URL selected. Pass a URL, --url, or use --project with a saved crawl URL.`);let f=await Rt({url:d,projectId:u?.client?.id,site:u?.site,mode:vi(e.mode)??(c.length?`list`:void 0),urls:c,maxPages:k(e[`max-pages`]),maxDepth:k(e[`max-depth`]),concurrency:k(e.concurrency),refresh:O(e.refresh),fetchRate:N({...e,"fetch-concurrency":e.concurrency}),include:j(e.include),exclude:j(e.exclude),ga4PropertyId:E(e[`ga4-property`])??u?.client?.ga4PropertyId,useSitemap:!Gn(e,`sitemap`),respectRobots:!Gn(e,`robots`),checkExternal:!Gn(e,`external`),js:Wn(e)}),p=O(e.save)?Pt(f):void 0,m=Ze(f,{severity:a}),h=o?f.issues.some(e=>gi[e.severity]>=gi[o]):!1,g=f.status===`failed`,_={...f,topFixes:m,reviewObservations:qe(f,{severity:a}),...o?{failOn:o,failedThreshold:h}:{},...p?{saved:p}:{}};if(n===`json`){await Ti(r,`${JSON.stringify(_,null,2)}\n`),(g||h)&&(process.exitCode=1);return}if(n===`csv`){await Ti(r,i===`pages`?dt(f):_t(f)),(g||h)&&(process.exitCode=1);return}if(n===`html`){await Ti(r,Tt(f,m)),(g||h)&&(process.exitCode=1);return}if(n===`markdown`){await Ti(r,Ge(f,m)),(g||h)&&(process.exitCode=1);return}if(r){await Ti(r,At(f,m)),(g||h)&&(process.exitCode=1);return}process.stdout.write(At(f,m)),(p||o)&&(process.stdout.write(`
21
29
  Run metadata
22
- `),I([[`Saved report`,p?.id??`no`],[`Fail threshold`,o??`off`]])),(g||h)&&(process.exitCode=1)}});function fi(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 pi(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 mi(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 hi(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`].includes(n))return n;throw Error(`Format must be one of: pretty, json, csv, html, markdown.`)}function gi(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 _i(e){let t=j(e.urls)??[],n=T(e[`urls-file`]);return n&&t.push(...vi(await pn(n,`utf8`))),[...new Set(t)]}function vi(e){return e.split(/[\n,]/).map(e=>e.trim()).filter(e=>e&&!e.startsWith(`#`))}async function yi(e,t){if(!e){process.stdout.write(t);return}await dn(y(e),{recursive:!0}),await _n(e,t),process.stdout.write(`Wrote ${e}\n`)}async function bi(e,t){let n=E(e),r=T(e.site);if(!(!n&&!r))return(await B({client:n,site:r,options:{json:t}})).site}function xi(e){if(!e.length){process.stdout.write(`No saved crawl reports found.
23
- `);return}L([`Created`,`Status`,`Pages`,`Issues`,`Site`,`ID`],e.map(e=>[e.createdAt,e.status,String(e.totalPages),String(e.issueCount),e.site??`-`,e.id]))}function Si(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})`])}I(t),e.issueGroups.length&&(process.stdout.write(`
30
+ `),L([[`Saved report`,p?.id??`no`],[`Fail threshold`,o??`off`]])),(g||h)&&(process.exitCode=1)}});function vi(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 yi(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 bi(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 xi(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`].includes(n))return n;throw Error(`Format must be one of: pretty, json, csv, html, markdown.`)}function Si(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 Ci(e){let t=j(e.urls)??[],n=E(e[`urls-file`]);return n&&t.push(...wi(await pn(n,`utf8`))),[...new Set(t)]}function wi(e){return e.split(/[\n,]/).map(e=>e.trim()).filter(e=>e&&!e.startsWith(`#`))}async function Ti(e,t){if(!e){process.stdout.write(t);return}await dn(b(e),{recursive:!0}),await _n(e,t),process.stdout.write(`Wrote ${e}\n`)}async function Ei(e,t){let n=D(e),r=E(e.site);if(!(!n&&!r))return(await V({client:n,site:r,options:{json:t}})).site}function Di(e){if(!e.length){process.stdout.write(`No saved crawl reports found.
31
+ `);return}R([`Created`,`Status`,`Pages`,`Issues`,`Site`,`ID`],e.map(e=>[e.createdAt,e.status,String(e.totalPages),String(e.issueCount),e.site??`-`,e.id]))}function Oi(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})`])}L(t),e.issueGroups.length&&(process.stdout.write(`
24
32
  Top issues
25
- `),L([`Severity`,`Rule`,`Count`,`Sample URL`],e.issueGroups.slice(0,10).map(e=>[e.severity,e.ruleId,String(e.count),G(e.sampleUrls[0]??``,64)]))),J(`Warnings`,e.warnings.slice(0,10)),J(`Caveats`,e.caveats)}function Ci(e){if(I([[`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(`
33
+ `),R([`Severity`,`Rule`,`Count`,`Sample URL`],e.issueGroups.slice(0,10).map(e=>[e.severity,e.ruleId,String(e.count),G(e.sampleUrls[0]??``,64)]))),J(`Warnings`,e.warnings.slice(0,10)),J(`Caveats`,e.caveats)}function ki(e){if(L([[`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(`
26
34
  Top actions
27
35
  `);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(`
28
36
  Changed pages
29
- `),L([`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(`
37
+ `),R([`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(`
30
38
  Issue movement
31
- `),L([`Rule`,`Before`,`After`,`Delta`],e.issueChanges.slice(0,10).map(e=>[e.ruleId,String(e.before),String(e.after),`${e.delta>=0?`+`:``}${e.delta}`]))),J(`Caveats`,e.caveats)}function wi(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 Ti(e){if(e.value!==`latest`&&e.value!==`previous`)return s(e.value);let t=wi(Se({site:e.site,limit:20}),{value:e.value,skipId:e.skipId});return t?s(t.id):void 0}const Ei=x({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=N(e),n=await bi(e,t),r=T(e.delete),i=T(e.rerun),a=T(e.compare),o=T(e.id);if(r){let e=Ue(r);if(t){F({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`?It(n):s(i);if(!e)throw Error(i===`latest`?`No saved crawl reports found.`:`No saved crawl report found for ${i}.`);let r=await Rt({...e.config,projectId:e.projectId,site:e.site,ga4PropertyId:e.ga4PropertyId}),a=Pt(r);if(t){F({...r,rerunOf:e.id,saved:a,topFixes:Qe(r),reviewObservations:Je(r)});return}process.stdout.write(`Reran crawl report ${e.id}.\n\n`),Si(r);return}if(a){let r=Ti({value:a,site:n});if(!r)throw Error(`No saved crawl report found for ${a}.`);let i=T(e.against)??`previous`,o=Ti({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=Ve({before:o,after:r});if(t){F(s);return}Ci(s);return}if(o||D(e.latest)){let e=o?s(o):It(n);if(!e)throw Error(o?`No saved crawl report found for ${o}.`:`No saved crawl reports found.`);if(t){F(e);return}Si(e);return}let c=Se({site:n,limit:k(e.limit)});if(t){F({reports:c});return}xi(c)}}),Di=x({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 Yn(e.body,e[`body-file`])??{startDate:T(e[`start-date`]),endDate:T(e[`end-date`]),dimensions:j(e.dimensions)??[`query`,`page`],type:T(e.type)??`web`,rowLimit:k(e.limit),dataState:`final`},n=N(e),r=await sr({client:E(e),site:T(e.site)??T(t.siteUrl)},{json:n,refresh:D(e.refresh)});delete t.siteUrl;let i=await be(r,t,{refresh:D(e.refresh)}),a=k(e.limit),o=a?i.rows.slice(0,a):i.rows;if(n){F({site:r,request:t,...i,rows:o,rowsReturned:o.length});return}I([[`Property`,r],[`Rows returned`,String(o.length)],[`Rows fetched`,String(i.rowsFetched)],[`API calls`,String(i.calls)]]),L([`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)]))}}),Oi=x({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 sr({client:E(e),site:T(e.site)},{json:t}),r=T(e.url);if(!r)throw Error(`Pass --url.`);let i=await Bt({siteUrl:n,inspectionUrl:r,languageCode:T(e.language)});if(t){F(i);return}let a=i.inspectionResult?.indexStatusResult;I([[`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`]])}}),ki=x({meta:{name:`ga4-report`,description:`Run a GA4 Data API report`},args:{property:{type:`string`,description:`GA4 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 GA4 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 local GA4 cache.`},json:{type:`boolean`,default:!1}},run:async({args:e})=>{let t=N(e),n=await ar({client:E(e),options:{json:t}}),r=await or({property:T(e.property)??n?.ga4PropertyId,options:{json:t}}),i=await h(r,await Yn(e.body,e[`body-file`])??{dateRanges:[{startDate:T(e[`start-date`]),endDate:T(e[`end-date`])}],dimensions:(j(e.dimensions)??[]).map(e=>({name:e})),metrics:(j(e.metrics)??[]).map(e=>({name:e})),limit:T(e.limit)},{refresh:D(e.refresh)});if(t){F(i);return}let a=ee(i);if(I([[`Property`,r],[`Rows`,String(i.rowCount??a.length)]]),a.length){let e=Object.keys(a[0]??{});L(e,a.slice(0,25).map(t=>e.map(e=>t[e]??``)))}}}),Ai={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. 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 Y(e,t={}){return Object.fromEntries(e.map(e=>{let n=Ai[e];return[n.cli,{...n.arg,...t[e]}]}))}function ji(e){return e.toLocaleString(`en-GB`,{maximumFractionDigits:e>=100?0:1})}function Mi(e){if(e===null)return`n/a`;let t=Math.abs(e)>=10?e.toFixed(0):e.toFixed(1);return`${e>0?`+`:``}${t}%`}const Ni=x({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.`},...Y([`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 g({site:await sr({client:E(e),site:T(e.site)},{json:t,refresh:D(e.refresh)}),days:k(e.days),recentDays:k(e.recent),refresh:D(e.refresh)});if(t){F(n);return}K([`Metric`,`Direction`,`Baseline/day`,`Recent/day`,`Change`,`z`,`Significant`],n.anomalies.map(e=>[e.metric,e.direction,ji(e.baselineMean),ji(e.comparisonMean),Mi(e.percentChange),e.zScore??`n/a`,e.significant?`yes`:`no`])),n.coverage&&J(`Evidence coverage`,[`${n.coverage.observedDays} of ${n.coverage.expectedDays} requested calendar days returned date aggregates.`,...n.coverage.caveats])}}),Pi=x({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.`},...Y([`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=E(e),r=await sr({client:n,site:T(e.site)},{json:t,refresh:D(e.refresh)}),i=await Te({site:r,days:k(e.days),recentDays:k(e.recent),paddingDays:k(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){F(i);return}I([[`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`),J(`Evidence`,i.evidence),J(`Report caveats`,i.caveats),J(`Recommended next checks`,i.actions),K([`Metric`,`Direction`,`Baseline/day`,`Recent/day`,`Change`,`z`],i.anomalies.map(e=>[e.metric,e.direction,ji(e.baselineMean),ji(e.comparisonMean),Mi(e.percentChange),e.zScore??`n/a`])),i.overlappingUpdates.length&&(process.stdout.write(`
39
+ `),R([`Rule`,`Before`,`After`,`Delta`],e.issueChanges.slice(0,10).map(e=>[e.ruleId,String(e.before),String(e.after),`${e.delta>=0?`+`:``}${e.delta}`]))),J(`Caveats`,e.caveats)}function Ai(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 ji(e){if(e.value!==`latest`&&e.value!==`previous`)return s(e.value);let t=Ai(xe({site:e.site,limit:20}),{value:e.value,skipId:e.skipId});return t?s(t.id):void 0}const Mi=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=P(e),n=await Ei(e,t),r=E(e.delete),i=E(e.rerun),a=E(e.compare),o=E(e.id);if(r){let e=He(r);if(t){I({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`?It(n):s(i);if(!e)throw Error(i===`latest`?`No saved crawl reports found.`:`No saved crawl report found for ${i}.`);let r=await Rt({...e.config,projectId:e.projectId,site:e.site,ga4PropertyId:e.ga4PropertyId}),a=Pt(r);if(t){I({...r,rerunOf:e.id,saved:a,topFixes:Ze(r),reviewObservations:qe(r)});return}process.stdout.write(`Reran crawl report ${e.id}.\n\n`),Oi(r);return}if(a){let r=ji({value:a,site:n});if(!r)throw Error(`No saved crawl report found for ${a}.`);let i=E(e.against)??`previous`,o=ji({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=Be({before:o,after:r});if(t){I(s);return}ki(s);return}if(o||O(e.latest)){let e=o?s(o):It(n);if(!e)throw Error(o?`No saved crawl report found for ${o}.`:`No saved crawl reports found.`);if(t){I(e);return}Oi(e);return}let c=xe({site:n,limit:k(e.limit)});if(t){I({reports:c});return}Di(c)}}),Ni=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 Yn(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=P(e),r=await hr({client:D(e),site:E(e.site)??E(t.siteUrl)},{json:n,refresh:O(e.refresh)});delete t.siteUrl;let i=await Bt(r,t,{refresh:O(e.refresh)}),a=k(e.limit),o=a?i.rows.slice(0,a):i.rows;if(n){I({site:r,request:t,...i,rows:o,rowsReturned:o.length});return}L([[`Property`,r],[`Rows returned`,String(o.length)],[`Rows fetched`,String(i.rowsFetched)],[`API calls`,String(i.calls)]]),R([`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)]))}}),Pi=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=P(e),n=await hr({client:D(e),site:E(e.site)},{json:t}),r=E(e.url);if(!r)throw Error(`Pass --url.`);let i=await a({siteUrl:n,inspectionUrl:r,languageCode:E(e.language)});if(t){I(i);return}let o=i.inspectionResult?.indexStatusResult;L([[`Property`,n],[`URL`,r],[`Verdict`,o?.verdict??`unknown`],[`Coverage`,o?.coverageState??`unknown`],[`Robots`,o?.robotsTxtState??`unknown`],[`Last crawl`,o?.lastCrawlTime??`unknown`],[`Google canonical`,o?.googleCanonical??`unknown`]])}}),Fi=S({meta:{name:`ga4-report`,description:`Run a GA4 Data API report`},args:{property:{type:`string`,description:`GA4 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 GA4 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 local GA4 cache.`},json:{type:`boolean`,default:!1}},run:async({args:e})=>{let t=P(e),n=await tr({client:D(e),options:{json:t}}),r=await nr({property:E(e.property)??n?.ga4PropertyId,options:{json:t}}),i=await ae(r,await Yn(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){I(i);return}let a=Te(i);if(L([[`Property`,r],[`Rows`,String(i.rowCount??a.length)]]),a.length){let e=Object.keys(a[0]??{});R(e,a.slice(0,25).map(t=>e.map(e=>t[e]??``)))}}}),Ii={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. 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=Ii[e];return[n.cli,{...n.arg,...t[e]}]}))}function Li(e){return e.toLocaleString(`en-GB`,{maximumFractionDigits:e>=100?0:1})}function Ri(e){if(e===null)return`n/a`;let t=Math.abs(e)>=10?e.toFixed(0):e.toFixed(1);return`${e>0?`+`:``}${t}%`}const zi=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:e})=>{let t=P(e),n=await g({site:await hr({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){I(n);return}K([`Metric`,`Direction`,`Baseline/day`,`Recent/day`,`Change`,`z`,`Significant`],n.anomalies.map(e=>[e.metric,e.direction,Li(e.baselineMean),Li(e.comparisonMean),Ri(e.percentChange),e.zScore??`n/a`,e.significant?`yes`:`no`])),n.coverage&&J(`Evidence coverage`,[`${n.coverage.observedDays} of ${n.coverage.expectedDays} requested calendar days returned date aggregates.`,...n.coverage.caveats])}}),Bi=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=P(e),n=D(e),r=await hr({client:n,site:E(e.site)},{json:t,refresh:O(e.refresh)}),i=await we({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){I(i);return}L([[`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`),J(`Evidence`,i.evidence),J(`Report caveats`,i.caveats),J(`Recommended next checks`,i.actions),K([`Metric`,`Direction`,`Baseline/day`,`Recent/day`,`Change`,`z`],i.anomalies.map(e=>[e.metric,e.direction,Li(e.baselineMean),Li(e.comparisonMean),Ri(e.percentChange),e.zScore??`n/a`])),i.overlappingUpdates.length&&(process.stdout.write(`
32
40
  Updates
33
- `),L([`Start`,`End`,`Type`,`Name`],i.overlappingUpdates.map(e=>[e.start.slice(0,10),e.end?.slice(0,10)??`open`,e.type,e.name]))),ci(`seo segment-impact ${n?`--project ${JSON.stringify(n)}`:`--site ${JSON.stringify(r)}`} --dimension page`)}});async function Fi(e,t={}){let n=T(e[`report-id`]);if(n){let e=s(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 B({client:r,site:i,options:t}):void 0,o=It(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 Ii(e){process.stdout.write(`AI-search evidence for ${e.url}\n\n`),I([[`Assessment`,`evidence only`],[`Data`,e.dataStatus],[`Report`,e.reportId]]),process.stdout.write(`\n${e.headline}\n`),e.topActions.length&&(process.stdout.write(`
34
- Top actions
35
- `),L([`Status`,`Check`,`Action`],e.topActions.map(e=>[e.status,e.title,G(e.action,96)]))),e.botAccess.length&&(process.stdout.write(`
36
- Bot access
37
- `),L([`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`]))),J(`Caveats`,e.caveats)}const Li=x({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=r(await Fi(e,{json:t}));if(t){F(n);return}Ii(n)}});function Ri(e){process.stdout.write(`Entity evidence for ${e.url}\n\n`),I([[`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(`
41
+ `),R([`Start`,`End`,`Type`,`Name`],i.overlappingUpdates.map(e=>[e.start.slice(0,10),e.end?.slice(0,10)??`open`,e.type,e.name]))),ir(`seo segment-impact ${n?`--project ${JSON.stringify(n)}`:`--site ${JSON.stringify(r)}`} --dimension page`)}});function Vi(e){process.stdout.write(`Entity evidence for ${e.url}\n\n`),L([[`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(`
38
42
  Schema types
39
- `),L([`Type`,`Pages`],t.map(([e,t])=>[e,String(t)]))),J(`sameAs`,e.entities.sameAs.slice(0,10)),J(`Social profiles`,e.entities.socialProfiles.slice(0,10))}const zi=x({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=lt(await Fi(e,{json:t}));if(t){F(n);return}Ri(n)}});function Bi(){return new Date().toISOString().slice(0,10)}function Vi(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 Hi=x({meta:{name:`change-log`,description:`Annotate SEO changes and measure before/after impact`},subCommands:{list:x({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=m({site:T(e.site),limit:k(e.limit)});if(N(e)){F({changes:t});return}L([`ID`,`Date`,`Site`,`Scope`,`Target`,`Title`],t.map(e=>[e.id,e.changedAt,e.site,e.scope,e.target,e.title]))}}),add:x({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=ae({site:await z({site:T(e.site),options:{json:t}}),scope:Vi(e.scope),target:T(e.target)??``,title:T(e.title)??``,description:T(e.description),changedAt:T(e.date)??Bi()});if(t){F(n);return}I([[`ID`,n.id],[`Date`,n.changedAt],[`Site`,n.site],[`Scope`,n.scope],[`Target`,n.target],[`Title`,n.title]])}}),delete:x({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=ke(t);if(N(e)){F({id:t,deleted:n});return}process.stdout.write(`${n?`Deleted`:`Not found`} ${t}.\n`)}}),measure:x({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=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 se({id:n,site:n?void 0:await z({site:T(e.site),options:{json:t}}),scope:n?void 0:Vi(e.scope),target:n?void 0:T(e.target),title:T(e.title),changedAt:n?void 0:T(e.date),beforeDays:A(e.before,`--before`),afterDays:A(e.after,`--after`),refresh:D(e.refresh)});if(t){F(r);return}I([[`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]]),L([`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`]]),J(`Warnings`,r.warnings),J(`Caveats`,r.caveats)}})}});function Ui(e){let t=T(e)??`page`;if(t!==`page`&&t!==`query`)throw Error(`Invalid --dimension. Use page or query.`);return t}function Wi(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 Gi=x({meta:{name:`content-groups`,description:`Create and manage reusable page/query groups`},subCommands:{list:x({meta:{name:`list`,description:`List saved content groups`},args:{site:{type:`string`},json:{type:`boolean`,default:!1}},run:async({args:e})=>{let t=i(T(e.site));if(N(e)){F({groups:t});return}L([`ID`,`Site`,`Name`,`Dimension`,`Match`,`Pattern`],t.map(e=>[e.id,e.site,e.name,e.dimension,e.matchType,e.pattern]))}}),add:x({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=ye({site:await z({site:T(e.site),options:{json:t}}),name:T(e.name)??``,dimension:Ui(e.dimension),matchType:Wi(e.match),pattern:T(e.pattern)??``});if(t){F(n);return}I([[`ID`,n.id],[`Site`,n.site],[`Name`,n.name],[`Filter`,`${n.dimension} ${n.matchType} ${n.pattern}`]])}}),delete:x({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=zt(t);if(N(e)){F({id:t,deleted:n});return}process.stdout.write(`${n?`Deleted`:`Not found`} ${t}.\n`)}})}});function Ki(){return new Date().toISOString().slice(0,10)}function qi(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 Ji(e){return e?qi(e):void 0}function Yi(e){return e===null?`n/a`:`${e.toFixed(1)}%`}function Xi(e){return e==null?`unavailable`:H(e)}function Zi(e,t){return e===null?`unavailable`:`${H(e)} (${Yi(t)})`}function Qi(e,t=1){return e==null?`unavailable`:e.toFixed(t)}function $i(e){I([[`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]]),L([`Metric`,`Before`,`After`,`Delta`],[[`GSC clicks`,Xi(e.before.metrics?.clicks),Xi(e.after.metrics?.clicks),Zi(e.delta.clicks,e.delta.clickPct)],[`GSC impressions`,Xi(e.before.metrics?.impressions),Xi(e.after.metrics?.impressions),Zi(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`,Qi(e.before.metrics?.position),Qi(e.after.metrics?.position),Qi(e.delta.position)]]),e.analytics&&(process.stdout.write(`
43
+ `),R([`Type`,`Pages`],t.map(([e,t])=>[e,String(t)]))),J(`sameAs`,e.entities.sameAs.slice(0,10)),J(`Social profiles`,e.entities.socialProfiles.slice(0,10))}const Hi=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=P(e),n=r(await ar(e,{json:t}));if(t){I(n);return}Vi(n)}});function Ui(){return new Date().toISOString().slice(0,10)}function Wi(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 Gi=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=m({site:E(e.site),limit:k(e.limit)});if(P(e)){I({changes:t});return}R([`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=P(e),n=ie({site:await B({site:E(e.site),options:{json:t}}),scope:Wi(e.scope),target:E(e.target)??``,title:E(e.title)??``,description:E(e.description),changedAt:E(e.date)??Ui()});if(t){I(n);return}L([[`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=E(e.id)??``,n=Oe(t);if(P(e)){I({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=P(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 oe({id:n,site:n?void 0:await B({site:E(e.site),options:{json:t}}),scope:n?void 0:Wi(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){I(r);return}L([[`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]]),R([`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`]]),J(`Warnings`,r.warnings),J(`Caveats`,r.caveats)}})}});function Ki(e){let t=E(e)??`page`;if(t!==`page`&&t!==`query`)throw Error(`Invalid --dimension. Use page or query.`);return t}function qi(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 Ji=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=i(E(e.site));if(P(e)){I({groups:t});return}R([`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=P(e),n=ve({site:await B({site:E(e.site),options:{json:t}}),name:E(e.name)??``,dimension:Ki(e.dimension),matchType:qi(e.match),pattern:E(e.pattern)??``});if(t){I(n);return}L([[`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=E(e.id)??``,n=zt(t);if(P(e)){I({id:t,deleted:n});return}process.stdout.write(`${n?`Deleted`:`Not found`} ${t}.\n`)}})}});function Yi(){return new Date().toISOString().slice(0,10)}function Xi(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 Zi(e){return e?Xi(e):void 0}function Qi(e){return e===null?`n/a`:`${e.toFixed(1)}%`}function $i(e){return e==null?`unavailable`:H(e)}function ea(e,t){return e===null?`unavailable`:`${H(e)} (${Qi(t)})`}function ta(e,t=1){return e==null?`unavailable`:e.toFixed(t)}function na(e){L([[`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]]),R([`Metric`,`Before`,`After`,`Delta`],[[`GSC clicks`,$i(e.before.metrics?.clicks),$i(e.after.metrics?.clicks),ea(e.delta.clicks,e.delta.clickPct)],[`GSC impressions`,$i(e.before.metrics?.impressions),$i(e.after.metrics?.impressions),ea(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`,ta(e.before.metrics?.position),ta(e.after.metrics?.position),ta(e.delta.position)]]),e.analytics&&(process.stdout.write(`
40
44
  GA4 impact
41
- `),L([`Metric`,`Before`,`After`,`Delta`],[[`Sessions`,H(e.analytics.before.metrics.sessions),H(e.analytics.after.metrics.sessions),`${H(e.analytics.delta.sessions)} (${Yi(e.analytics.delta.sessionPct)})`],[`Conversions`,H(e.analytics.before.metrics.conversions),H(e.analytics.after.metrics.conversions),`${H(e.analytics.delta.conversions)} (${Yi(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)} (${Yi(e.analytics.delta.revenuePct)})`]])),e.control&&(process.stdout.write(`
45
+ `),R([`Metric`,`Before`,`After`,`Delta`],[[`Sessions`,H(e.analytics.before.metrics.sessions),H(e.analytics.after.metrics.sessions),`${H(e.analytics.delta.sessions)} (${Qi(e.analytics.delta.sessionPct)})`],[`Conversions`,H(e.analytics.before.metrics.conversions),H(e.analytics.after.metrics.conversions),`${H(e.analytics.delta.conversions)} (${Qi(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)} (${Qi(e.analytics.delta.revenuePct)})`]])),e.control&&(process.stdout.write(`
42
46
  Control comparison
43
- `),I([[`Control`,e.control.change.title],[`Adjusted clicks`,e.control.adjusted.clickDelta===null?`n/a`:H(e.control.adjusted.clickDelta)],[`Adjusted click %`,e.control.adjusted.clickPctPoints===null?`n/a`:`${e.control.adjusted.clickPctPoints.toFixed(1)} pts`],[`Note`,e.control.note]])),J(`Warnings`,e.warnings),J(`Caveats`,e.caveats)}const ea=x({meta:{name:`tests`,description:`Create and report local SEO tests from GSC and GA4 data`},subCommands:{list:x({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=E(e),r=m({site:(n||T(e.site)?await B({client:n,site:T(e.site),options:{json:t}}):void 0)?.site,limit:k(e.limit)});if(t){F({tests:r});return}L([`ID`,`Date`,`Scope`,`Target`,`Title`],r.map(e=>[e.id,e.changedAt,e.scope,e.target,e.title]))}}),create:x({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 B({client:E(e),site:T(e.site),options:{json:t}}),r=ae({site:n.site,scope:qi(e.scope),target:T(e.target)??``,title:T(e.title)??``,description:T(e.description),changedAt:T(e.date)??Ki()});if(t){F(r);return}I([[`ID`,r.id],[`Date`,r.changedAt],[`Project`,n.client?.name??n.site],[`Scope`,r.scope],[`Target`,r.target],[`Title`,r.title]])}}),report:x({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:`GA4 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 local GSC and GA4 cache.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=N(e),n=T(e.id),r=!n||E(e)||T(e.site)?await B({client:E(e),site:T(e.site),options:{json:t,refresh:D(e.refresh)}}):void 0,i=await se({id:n,site:n?void 0:r?.site,scope:n?void 0:Ji(e.scope),target:n?void 0:T(e.target),title:T(e.title),changedAt:n?void 0:T(e.date),ga4PropertyId:T(e.property)??r?.client?.ga4PropertyId,controlScope:Ji(e[`control-scope`]),controlTarget:T(e[`control-target`]),controlTitle:T(e[`control-title`]),beforeDays:A(e.before,`--before`),afterDays:A(e.after,`--after`),refresh:D(e.refresh)});if(t){F(i);return}$i(i)}})}});function X(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 ta={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.`}},na={...Y([`includeBrand`,`verifyContent`,`verifyLimit`,`js`,`fetchConcurrency`,`fetchIntervalCap`,`fetchIntervalMs`,`refresh`]),json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}};function ra(e){return{includeBrand:D(e[`include-brand`]),verifyContent:D(e[`verify-content`]),verifyLimit:k(e[`verify-limit`])??3,js:D(e.js)?!0:void 0,rate:qn(e),refresh:D(e.refresh)}}function ia(e){return{client:E(e),site:T(e.site),refresh:D(e.refresh)}}const{json:aa,...oa}=na,sa=oa,ca={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 la(e){return{type:`string`,description:`Output folder. Defaults to ./seo-export/<project>-${e}-YYYY-MM-DD.`}}function ua(e){return e.toLowerCase().replace(/^sc-domain:/,``).replace(/^https?:\/\//,``).replace(/[^a-z0-9._-]+/g,`-`).replace(/^-+|-+$/g,``)}function da(e){let t=new Date().toISOString().slice(0,10),n=ua(e.clientId??e.site);return nn(process.cwd(),`seo-export`,`${n}-${e.report}-${t}`)}async function fa(e,t){await dn(e,{recursive:!0});let n=[];for(let r of t){let t=nn(e,r.filename);await _n(t,Re(r.rows,r.headers),`utf8`),n.push(t)}return n}function pa(t,n){process.stdout.write(`Wrote ${e(n.length,`CSV file`)} to ${t}\n`);for(let e of n)process.stdout.write(`- ${e}\n`)}const ma=x({meta:{name:`diagnose`,description:`Export diagnosis tables: priorities, anomalies, movement, decay, cannibalisation, striking-distance, and quick wins.`},args:{...ca,out:la(`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 B({client:E(e),site:T(e.site)}),n=await De({site:t.site,days:k(e.days),recentDays:k(e.recent),limit:k(e.limit),brandTerms:t.client?.brandTerms,includeBrand:D(e[`include-brand`]),verifyContent:D(e[`verify-content`]),verifyLimit:k(e[`verify-limit`]),js:D(e.js)?!0:`auto`,rate:qn(e),refresh:D(e.refresh),progress:X(!0)}),r=T(e.out)??da({report:`diagnose`,clientId:t.client?.id,site:t.site});pa(r,await fa(r,vt(n)))}}),ha=`.seo-okf.json`,ga=`${JSON.stringify({generator:`seo`,format:`okf`,version:1})}\n`;function _a(e,t){let n=nn(e,t),r=tn(nn(e),n);if(!r||r.startsWith(`..`)||$t(r))throw new _(`INVALID_INPUT`,`Unsafe OKF file path: ${t}`);return n}async function va(e,t){for(let n of t){let t=_a(e,n.path);await dn(y(t),{recursive:!0}),await _n(t,n.content)}await _n(b(e,ha),ga)}async function ya(e){let t=[];async function n(e,r=``){let i=await mn(e,{withFileTypes:!0});for(let a of i){let i=r?`${r}/${a.name}`:a.name,o=b(e,a.name);a.isDirectory()?await n(o,i):a.isFile()&&a.name.endsWith(`.md`)&&t.push({path:i,content:await pn(o,`utf8`)})}}return await n(e),t.sort((e,t)=>e.path<t.path?-1:+(e.path>t.path))}async function ba(e){try{let t=await un(e);if(t.isSymbolicLink()||!t.isDirectory())throw new _(`INVALID_INPUT`,`OKF output must be a real directory: ${e}`);if(!(await mn(e)).length)return!0;try{if(await pn(b(e,ha),`utf8`)===ga)return!0}catch{}throw new _(`INVALID_INPUT`,`Refusing to replace non-empty unmanaged directory: ${e}`)}catch(e){if(e.code===`ENOENT`)return!1;throw e}}async function xa(e,t){if(!at(t).valid)throw new _(`INVALID_INPUT`,`Generated bundle did not pass seo OKF checks and was not written.`);let n=nn(e),r=y(n);await dn(r,{recursive:!0});let i=await ba(n),a=await fn(b(r,`.${Qt(n)}-staging-`)),o=b(r,`.${Qt(n)}-backup-${an()}`),s=!1;try{await va(a,t);let e=at(await ya(a));if(!e.valid)throw new _(`INTERNAL_ERROR`,`Staged OKF files failed validation after writing.`);return i&&(await hn(n,o),s=!0),await hn(a,n),s&&await gn(o,{recursive:!0,force:!0}),e}catch(e){throw await gn(a,{recursive:!0,force:!0}),s&&(await gn(n,{recursive:!0,force:!0}),await hn(o,n)),e}}async function Sa(e,t){if(!e){process.stdout.write(t);return}await dn(y(e),{recursive:!0}),await _n(e,t),process.stdout.write(`Wrote ${e}\n`)}function Ca(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 wa(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:r(e),entityReadiness:lt(e),llmsTxt:l(e)}}function Ta(e){let t=wa(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(`
44
- `)}\n`}const Ea=x({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=Ca(e.format),r=n===`okf`?Ct(A(e[`max-concepts`],`--max-concepts`)):void 0,i=await Fi(e,{json:t}),a=T(e.output);if(n===`okf`){let e=Ot(i,{maxConcepts:r}),o=a??`./okf`,s=await xa(o,e.files);if(t){F({format:n,output:o,bundle:e,validation:s});return}process.stdout.write(`Wrote OKF bundle to ${o}\n\n`),I([[`Files`,String(e.files.length)],[`Concepts`,String(e.conceptCount)],[`Valid`,s.valid?`yes`:`no`]]);return}if(n===`json`){await Sa(a,`${JSON.stringify(wa(i),null,2)}\n`);return}await Sa(a,Ta(i))}}),Da=x({meta:{name:`monthly`,description:`Export monthly report tables plus the underlying diagnosis detail CSVs.`},args:{...ca,out:la(`monthly`),month:{type:`string`,description:`Report month as YYYY-MM. Defaults to the latest month with final GSC data.`},...Y([`limit`],{limit:{description:`Maximum rows per diagnostic section. Defaults to 10.`}}),...sa},run:async({args:e})=>{let t=await B({client:E(e),site:T(e.site),options:{refresh:D(e.refresh)}}),n=await xe({site:t.site,month:T(e.month),limit:k(e.limit),brandTerms:t.client?.brandTerms,...ra(e),progress:X(!0)}),r=T(e.out)??da({report:`monthly`,clientId:t.client?.id,site:t.site});pa(r,await fa(r,pt(n)))}}),Oa=x({meta:{name:`narrative`,description:`Export report narrative tables plus the underlying diagnosis detail CSVs.`},args:{...ca,out:la(`narrative`),...Y([`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.`},...Y([`limit`],{limit:{description:`Maximum rows per diagnostic section. Defaults to 10.`}}),"change-limit":{type:`string`,description:`Maximum saved changes to measure. Defaults to 5.`},...sa},run:async({args:e})=>{let t=await B({client:E(e),site:T(e.site),options:{refresh:D(e.refresh)}}),n=await o({site:t.site,days:k(e.days),recentDays:k(e.recent),startDate:T(e[`start-date`]),endDate:T(e[`end-date`]),limit:k(e.limit),changeLimit:k(e[`change-limit`]),brandTerms:t.client?.brandTerms,...ra(e),progress:X(!0)}),r=T(e.out)??da({report:`narrative`,clientId:t.client?.id,site:t.site});pa(r,await fa(r,pt(n)))}});function ka(e){return Ce({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:D(e[`include-brand`]),js:D(e.js)?!0:`auto`,refresh:D(e.refresh),rate:Jn(e)})}const Aa=x({meta:{name:`pseo`,description:`Export pSEO audit tables: templates, demand patterns, top queries, sample coverage, crawl samples, and inspection samples.`},args:{...ca,out:la(`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=ka(e),n=await B({client:E(e),site:T(e.site)}),r=await u({site:n.site,...t,brandTerms:t.brandTerms??n.client?.brandTerms,progress:X(!0)}),i=T(e.out)??da({report:`pseo`,clientId:n.client?.id,site:n.site});pa(i,await fa(i,nt(r)))}}),ja=x({meta:{name:`refresh-priorities`,description:`Export the ranked SEO priority queue, score breakdowns, grouped findings, and diagnosis detail CSVs.`},args:{...ca,out:la(`refresh-priorities`),...Y([`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.`}}),"ga4-property":{type:`string`,description:`GA4 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 B({client:E(e),site:T(e.site),options:{refresh:D(e.refresh)}}),n=await Le({site:t.site,days:k(e.days),recentDays:k(e.recent),limit:k(e.limit),brandTerms:t.client?.brandTerms,includeBrand:D(e[`include-brand`]),ga4PropertyId:T(e[`ga4-property`])??t.client?.ga4PropertyId,verifyContent:D(e[`verify-content`]),verifyLimit:k(e[`verify-limit`]),refresh:D(e.refresh)}),r=T(e.out)??da({report:`refresh-priorities`,clientId:t.client?.id,site:t.site});pa(r,await fa(r,et(n)))}}),Ma=x({meta:{name:`export`,description:`Export full report data to CSV files`},subCommands:{diagnose:ma,knowledge:Ea,narrative:Oa,monthly:Da,"update-postmortem":x({meta:{name:`update-postmortem`,description:`Export update postmortem attribution, findings, template movement, and segment movers.`},args:{...ca,out:la(`update-postmortem`),...Y([`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 B({client:E(e),site:T(e.site),options:{refresh:D(e.refresh)}}),n=await tt({site:t.site,days:k(e.days),recentDays:k(e.recent),limit:k(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)??da({report:`update-postmortem`,clientId:t.client?.id,site:t.site});pa(r,await fa(r,Ln(n)))}}),"refresh-priorities":ja,pseo:Aa}}),Na=x({...Zr,meta:{name:`init`,description:"Legacy alias for `seo start`."}});async function Pa(e,t){if(!e){process.stdout.write(t);return}await dn(y(e),{recursive:!0}),await _n(e,t),process.stdout.write(`Wrote ${e}\n`)}const Fa={"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.`}},Ia=x({meta:{name:`llms`,description:`Inspect or generate optional llms.txt from saved crawl data`},subCommands:{audit:x({meta:{name:`audit`,description:`Inspect optional llms.txt presence from a saved crawl`},args:Fa,run:async({args:e})=>{let t=N(e),n=l(await Fi(e,{json:t}));if(t){F(n);return}process.stdout.write(`llms.txt audit for ${n.url}\n\n`),I([[`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(`
47
+ `),L([[`Control`,e.control.change.title],[`Adjusted clicks`,e.control.adjusted.clickDelta===null?`n/a`:H(e.control.adjusted.clickDelta)],[`Adjusted click %`,e.control.adjusted.clickPctPoints===null?`n/a`:`${e.control.adjusted.clickPctPoints.toFixed(1)} pts`],[`Note`,e.control.note]])),J(`Warnings`,e.warnings),J(`Caveats`,e.caveats)}const ra=S({meta:{name:`tests`,description:`Create and report local SEO tests from GSC and GA4 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=P(e),n=D(e),r=m({site:(n||E(e.site)?await V({client:n,site:E(e.site),options:{json:t}}):void 0)?.site,limit:k(e.limit)});if(t){I({tests:r});return}R([`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=P(e),n=await V({client:D(e),site:E(e.site),options:{json:t}}),r=ie({site:n.site,scope:Xi(e.scope),target:E(e.target)??``,title:E(e.title)??``,description:E(e.description),changedAt:E(e.date)??Yi()});if(t){I(r);return}L([[`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:`GA4 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 local GSC and GA4 cache.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=P(e),n=E(e.id),r=!n||D(e)||E(e.site)?await V({client:D(e),site:E(e.site),options:{json:t,refresh:O(e.refresh)}}):void 0,i=await oe({id:n,site:n?void 0:r?.site,scope:n?void 0:Zi(e.scope),target:n?void 0:E(e.target),title:E(e.title),changedAt:n?void 0:E(e.date),ga4PropertyId:E(e.property)??r?.client?.ga4PropertyId,controlScope:Zi(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){I(i);return}na(i)}})}});function ia(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 aa={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.`}},oa={...X([`includeBrand`,`verifyContent`,`verifyLimit`,`js`,`fetchConcurrency`,`fetchIntervalCap`,`fetchIntervalMs`,`refresh`]),json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}};function sa(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:N(e),refresh:O(e.refresh)}}function ca(e){return{client:D(e),site:E(e.site),refresh:O(e.refresh)}}const{json:la,...ua}=oa,da=ua,fa={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 pa(e){return{type:`string`,description:`Output folder. Defaults to ./seo-export/<project>-${e}-YYYY-MM-DD.`}}function ma(e){return e.toLowerCase().replace(/^sc-domain:/,``).replace(/^https?:\/\//,``).replace(/[^a-z0-9._-]+/g,`-`).replace(/^-+|-+$/g,``)}function ha(e){let t=new Date().toISOString().slice(0,10),n=ma(e.clientId??e.site);return nn(process.cwd(),`seo-export`,`${n}-${e.report}-${t}`)}async function ga(e,t){await dn(e,{recursive:!0});let n=[];for(let r of t){let t=nn(e,r.filename);await _n(t,Le(r.rows,r.headers),`utf8`),n.push(t)}return n}function _a(t,n){process.stdout.write(`Wrote ${e(n.length,`CSV file`)} to ${t}\n`);for(let e of n)process.stdout.write(`- ${e}\n`)}const va=S({meta:{name:`diagnose`,description:`Export diagnosis tables: priorities, anomalies, movement, decay, cannibalisation, striking-distance, and quick wins.`},args:{...fa,out:pa(`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 V({client:D(e),site:E(e.site)}),n=await Ee({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:N(e),refresh:O(e.refresh),progress:ia(!0)}),r=E(e.out)??ha({report:`diagnose`,clientId:t.client?.id,site:t.site});_a(r,await ga(r,yt(n)))}}),ya=`.seo-okf.json`,ba=`${JSON.stringify({generator:`seo`,format:`okf`,version:1})}\n`;function xa(e,t){let n=nn(e,t),r=tn(nn(e),n);if(!r||r.startsWith(`..`)||$t(r))throw new v(`INVALID_INPUT`,`Unsafe OKF file path: ${t}`);return n}async function Sa(e,t){for(let n of t){let t=xa(e,n.path);await dn(b(t),{recursive:!0}),await _n(t,n.content)}await _n(x(e,ya),ba)}async function Ca(e){let t=[];async function n(e,r=``){let i=await mn(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 pn(o,`utf8`)})}}return await n(e),t.sort((e,t)=>e.path<t.path?-1:+(e.path>t.path))}async function wa(e){try{let t=await un(e);if(t.isSymbolicLink()||!t.isDirectory())throw new v(`INVALID_INPUT`,`OKF output must be a real directory: ${e}`);if(!(await mn(e)).length)return!0;try{if(await pn(x(e,ya),`utf8`)===ba)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 Ta(e,t){if(!it(t).valid)throw new v(`INVALID_INPUT`,`Generated bundle did not pass seo OKF checks and was not written.`);let n=nn(e),r=b(n);await dn(r,{recursive:!0});let i=await wa(n),a=await fn(x(r,`.${Qt(n)}-staging-`)),o=x(r,`.${Qt(n)}-backup-${an()}`),s=!1;try{await Sa(a,t);let e=it(await Ca(a));if(!e.valid)throw new v(`INTERNAL_ERROR`,`Staged OKF files failed validation after writing.`);return i&&(await hn(n,o),s=!0),await hn(a,n),s&&await gn(o,{recursive:!0,force:!0}),e}catch(e){throw await gn(a,{recursive:!0,force:!0}),s&&(await gn(n,{recursive:!0,force:!0}),await hn(o,n)),e}}async function Ea(e,t){if(!e){process.stdout.write(t);return}await dn(b(e),{recursive:!0}),await _n(e,t),process.stdout.write(`Wrote ${e}\n`)}function Da(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 Oa(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:ct(e),entityReadiness:r(e),llmsTxt:l(e)}}function ka(e){let t=Oa(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(`
48
+ `)}\n`}const Aa=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=P(e),n=Da(e.format),r=n===`okf`?wt(A(e[`max-concepts`],`--max-concepts`)):void 0,i=await ar(e,{json:t}),a=E(e.output);if(n===`okf`){let e=Ot(i,{maxConcepts:r}),o=a??`./okf`,s=await Ta(o,e.files);if(t){I({format:n,output:o,bundle:e,validation:s});return}process.stdout.write(`Wrote OKF bundle to ${o}\n\n`),L([[`Files`,String(e.files.length)],[`Concepts`,String(e.conceptCount)],[`Valid`,s.valid?`yes`:`no`]]);return}if(n===`json`){await Ea(a,`${JSON.stringify(Oa(i),null,2)}\n`);return}await Ea(a,ka(i))}}),ja=S({meta:{name:`monthly`,description:`Export monthly report tables plus the underlying diagnosis detail CSVs.`},args:{...fa,out:pa(`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.`}}),...da},run:async({args:e})=>{let t=await V({client:D(e),site:E(e.site),options:{refresh:O(e.refresh)}}),n=await be({site:t.site,month:E(e.month),limit:k(e.limit),brandTerms:t.client?.brandTerms,...sa(e),progress:ia(!0)}),r=E(e.out)??ha({report:`monthly`,clientId:t.client?.id,site:t.site});_a(r,await ga(r,pt(n)))}}),Ma=S({meta:{name:`narrative`,description:`Export report narrative tables plus the underlying diagnosis detail CSVs.`},args:{...fa,out:pa(`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.`},...da},run:async({args:e})=>{let t=await V({client:D(e),site:E(e.site),options:{refresh:O(e.refresh)}}),n=await o({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,...sa(e),progress:ia(!0)}),r=E(e.out)??ha({report:`narrative`,clientId:t.client?.id,site:t.site});_a(r,await ga(r,pt(n)))}});function Na(e){return Se({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:Jn(e)})}const Pa=S({meta:{name:`pseo`,description:`Export pSEO audit tables: templates, demand patterns, top queries, sample coverage, crawl samples, and inspection samples.`},args:{...fa,out:pa(`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=Na(e),n=await V({client:D(e),site:E(e.site)}),r=await u({site:n.site,...t,brandTerms:t.brandTerms??n.client?.brandTerms,progress:ia(!0)}),i=E(e.out)??ha({report:`pseo`,clientId:n.client?.id,site:n.site});_a(i,await ga(i,tt(r)))}}),Fa=S({meta:{name:`refresh-priorities`,description:`Export the ranked SEO priority queue, score breakdowns, grouped findings, and diagnosis detail CSVs.`},args:{...fa,out:pa(`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.`}}),"ga4-property":{type:`string`,description:`GA4 property ID to use for analytics value. Defaults from the selected project.`},"verify-content":T(`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 V({client:D(e),site:E(e.site),options:{refresh:O(e.refresh)}}),n=await Ie({site:t.site,days:k(e.days),recentDays:k(e.recent),limit:k(e.limit),brandTerms:t.client?.brandTerms,includeBrand:O(e[`include-brand`]),ga4PropertyId:E(e[`ga4-property`])??t.client?.ga4PropertyId,verifyContent:O(e[`verify-content`]),verifyLimit:k(e[`verify-limit`]),refresh:O(e.refresh)}),r=E(e.out)??ha({report:`refresh-priorities`,clientId:t.client?.id,site:t.site});_a(r,await ga(r,$e(n)))}}),Ia=S({meta:{name:`export`,description:`Export full report data to CSV files`},subCommands:{diagnose:va,knowledge:Aa,narrative:Ma,monthly:ja,"update-postmortem":S({meta:{name:`update-postmortem`,description:`Export update postmortem attribution, findings, template movement, and segment movers.`},args:{...fa,out:pa(`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 V({client:D(e),site:E(e.site),options:{refresh:O(e.refresh)}}),n=await et({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)??ha({report:`update-postmortem`,clientId:t.client?.id,site:t.site});_a(r,await ga(r,Ln(n)))}}),"refresh-priorities":Fa,pseo:Pa}}),La=S({...ai,meta:{name:`init`,description:"Legacy alias for `seo start`."}});async function Ra(e,t){if(!e){process.stdout.write(t);return}await dn(b(e),{recursive:!0}),await _n(e,t),process.stdout.write(`Wrote ${e}\n`)}const za={"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.`}},Ba=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:za,run:async({args:e})=>{let t=P(e),n=l(await ar(e,{json:t}));if(t){I(n);return}process.stdout.write(`llms.txt audit for ${n.url}\n\n`),L([[`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(`
45
49
  Issues
46
- `),L([`Severity`,`Issue`,`Action`],n.issues.map(e=>[e.severity,e.title,G(e.action,96)]))),J(`Recommended pages`,n.recommendedPages.slice(0,10).map(e=>`${e.section}: ${e.url}`))}}),generate:x({meta:{name:`generate`,description:`Generate an llms.txt draft from a saved crawl`},args:{...Fa,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=me(await Fi(e,{json:t}),{maxUrls:k(e[`max-urls`]),tokenBudget:k(e[`token-budget`]),exclude:j(e.exclude),title:T(e.title),description:T(e.description)});if(t){F(n);return}await Pa(T(e.output),n.content),T(e.output)&&I([[`URLs`,String(n.includedUrls)],[`Estimated tokens`,String(n.estimatedTokens)]])}})}}),La=[{client:`claude-desktop`,flag:`claude-desktop`},{client:`claude-code`,flag:`claude-code`},{client:`codex`,flag:`codex`},{client:`cursor`,flag:`cursor`}];function Ra(e,t){return D(e.all)?t:La.filter(({flag:t})=>D(e[t])).map(({client:e})=>e)}const za=x({meta:{name:`mcp`,description:`MCP server helpers`},subCommands:{serve:x({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-eiewVfiS.js`);await t({test:D(e.test)})}}),install:x({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 _(`INVALID_INPUT`,`Use either --uninstall or --reinstall, not both.`);let t=pr(),n=gr({targets:t}),r=N(e),i=Ra(e,n.map(e=>e.client));if(i.length===0){if(!P({json:r}))throw new _(`INVALID_INPUT`,`Choose an MCP client with --claude-desktop, --claude-code, --codex, --cursor, or --all.`);let a=n.length>0?n:t;i=R(await Cn({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 _(`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)?Fr(t):Pr(t,{reinstall:D(e.reinstall)}));if(r){F({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`)}})}}),Ba=x({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 f({startUrl:T(e.url)??``,site:T(e.site),limit:k(e.limit),refresh:D(e.refresh),js:D(e.js)?!0:`auto`});if(N(e)){F(t);return}if(I([[`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(`
50
+ `),R([`Severity`,`Issue`,`Action`],n.issues.map(e=>[e.severity,e.title,G(e.action,96)]))),J(`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:{...za,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=P(e),n=pe(await ar(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){I(n);return}await Ra(E(e.output),n.content),E(e.output)&&L([[`URLs`,String(n.includedUrls)],[`Estimated tokens`,String(n.estimatedTokens)]])}})}}),Va=[{client:`claude-desktop`,flag:`claude-desktop`},{client:`claude-code`,flag:`claude-code`},{client:`codex`,flag:`codex`},{client:`cursor`,flag:`cursor`}];function Ha(e,t){return O(e.all)?t:Va.filter(({flag:t})=>O(e[t])).map(({client:e})=>e)}const Ua=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-BG734YzY.js`);await t({test:O(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(O(e.uninstall)&&O(e.reinstall))throw new v(`INVALID_INPUT`,`Use either --uninstall or --reinstall, not both.`);let t=xr(),n=wr({targets:t}),r=P(e),i=Ha(e,n.map(e=>e.client));if(i.length===0){if(!F({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=z(await Cn({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=>O(e.uninstall)?Ur(t):Hr(t,{reinstall:O(e.reinstall)}));if(r){I({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`)}})}}),Wa=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 f({startUrl:E(e.url)??``,site:E(e.site),limit:k(e.limit),refresh:O(e.refresh),js:O(e.js)?!0:`auto`});if(P(e)){I(t);return}if(L([[`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(`
47
51
  Recommended actions
48
- `),L([`Severity`,`Category`,`URL`,`Action`],t.recommendations.slice(0,10).map(e=>[e.severity,e.category,G(e.url,56),G(e.action,72)])),q(`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(`
52
+ `),R([`Severity`,`Category`,`URL`,`Action`],t.recommendations.slice(0,10).map(e=>[e.severity,e.category,G(e.url,56),G(e.action,72)])),q(`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(`
49
53
  Changed URLs
50
- `),L([`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(`
54
+ `),R([`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(`
51
55
  Warnings
52
- `);for(let e of t.warnings.slice(0,10))process.stdout.write(`- ${e}\n`)}}});function Va(){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 Ha(){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 Ua(e){I([[`Workflow`,e.workflow],[`Property`,e.site],[`Summary`,e.summary]]),L([`Tool`,`Status`,`Summary`],e.steps.map(e=>[e.tool,e.status,e.summary])),e.actions.length&&(L([`Priority`,`Confidence`,`Action`],e.actions.map(e=>[e.title,e.confidence,e.action])),q(`Priority action details`,e.actions.map(e=>({label:e.title,context:e.confidence,action:e.action}))))}function Wa(e){if(Ua(e),e.output.crawl?.recommendations.length&&(process.stdout.write(`
56
+ `);for(let e of t.warnings.slice(0,10))process.stdout.write(`- ${e}\n`)}}});function Ga(){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":T(`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 Ka(){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 qa(e){L([[`Workflow`,e.workflow],[`Property`,e.site],[`Summary`,e.summary]]),R([`Tool`,`Status`,`Summary`],e.steps.map(e=>[e.tool,e.status,e.summary])),e.actions.length&&(R([`Priority`,`Confidence`,`Action`],e.actions.map(e=>[e.title,e.confidence,e.action])),q(`Priority action details`,e.actions.map(e=>({label:e.title,context:e.confidence,action:e.action}))))}function Ja(e){if(qa(e),e.output.crawl?.recommendations.length&&(process.stdout.write(`
53
57
  Crawl actions
54
- `),L([`Severity`,`URL`,`Action`],e.output.crawl.recommendations.slice(0,10).map(e=>[e.severity,G(e.url,56),G(e.action,72)])),q(`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(`
58
+ `),R([`Severity`,`URL`,`Action`],e.output.crawl.recommendations.slice(0,10).map(e=>[e.severity,G(e.url,56),G(e.action,72)])),q(`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(`
55
59
  Index reviews, changes, and failed checks
56
- `),L([`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(`
60
+ `),R([`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(`
57
61
  Recoverable URLs
58
- `),L([`Severity`,`Issue`,`Clicks`,`Impr`,`URL`,`Action`],e.output.recovery.items.slice(0,10).map(e=>[e.severity,e.issue,H(e.clicks),H(e.impressions),G(e.url,56),G(e.recommendation.action,72)])),q(`Top recovery actions`,e.output.recovery.items.map(e=>({label:G(e.url,96),context:`${e.severity}, ${H(e.clicks)} clicks at risk`,action:e.recommendation.action}))))}function Ga(e){I([[`Property`,e.site],[`Health`,e.health],[`Attention`,String(e.summary.attention)],[`Stale`,String(e.summary.stale)],[`Not run`,String(e.summary.notRun)]]),L([`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(`
62
+ `),R([`Severity`,`Issue`,`Clicks`,`Impr`,`URL`,`Action`],e.output.recovery.items.slice(0,10).map(e=>[e.severity,e.issue,H(e.clicks),H(e.impressions),G(e.url,56),G(e.recommendation.action,72)])),q(`Top recovery actions`,e.output.recovery.items.map(e=>({label:G(e.url,96),context:`${e.severity}, ${H(e.clicks)} clicks at risk`,action:e.recommendation.action}))))}function Ya(e){L([[`Property`,e.site],[`Health`,e.health],[`Attention`,String(e.summary.attention)],[`Stale`,String(e.summary.stale)],[`Not run`,String(e.summary.notRun)]]),R([`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(`
59
63
  Next actions
60
- `),L([`Check`,`Action`],t.map(e=>[e.name,G(e.action??``,96)])),q(`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 Ka(e,t){return D(t)?`--${e}`:void 0}function qa(e){return D(e)===!1?`--recover-links=false`:void 0}const Ja=x({meta:{name:`monitoring`,description:`Run and review recurring technical SEO monitoring`},subCommands:{run:x({meta:{name:`run`,description:`Run crawl, index, and link recovery monitoring for a property`},args:Va(),run:async({args:e})=>{let t=N(e),n=await B({client:E(e),site:T(e.site),options:{json:t,refresh:D(e.refresh)}}),r=M(e.urls),i=M(e.sitemaps),a=T(e.url)??n.client?.startUrl??V(n.site),o=await $e({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:T(e.language),dailyLimit:k(e[`daily-limit`]),inspectLimit:k(e[`inspect-limit`]),maxUrls:k(e[`max-urls`]),refresh:D(e.refresh),js:D(e.js)?!0:`auto`,recoverLinks:D(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){F(o);return}Wa(o)}}),status:x({meta:{name:`status`,description:`Show the latest saved monitoring state for a property`},args:Ha(),run:async({args:e})=>{let t=N(e),n=pe({site:(await B({client:E(e),site:T(e.site),options:{json:t,refresh:D(e.refresh)}})).site,staleAfterDays:k(e[`stale-days`])});if(t){F(n);return}Ga(n)}}),cron:x({meta:{name:`cron`,description:`Print a local cron line for recurring monitoring`},args:{...Va(),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 B({client:E(e),site:T(e.site),options:{json:t,refresh:D(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`]),qa(e[`recover-links`]),Ka(`refresh`,e.refresh),Ka(`js`,e.js),`--json`].filter(Boolean).join(` `),s={name:`monitoring`,cron:`${i} ${r} * * ${a}`,command:o};if(t){F({site:n.site,line:s});return}I([[`Property`,n.site],[`Install`,`crontab -e`]]),process.stdout.write(`${s.cron} ${s.command}\n`)}})}}),Ya=x({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 B({client:E(e),site:T(e.site),options:{json:t,refresh:D(e.refresh)}}),r=M(e.sitemaps),i=await he({site:n.site,crawlReportId:T(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:D(e.refresh)});if(t){F(i);return}if(I([[`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(`
64
+ `),R([`Check`,`Action`],t.map(e=>[e.name,G(e.action??``,96)])),q(`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 Xa(e,t){return O(t)?`--${e}`:void 0}function Za(e){return O(e)===!1?`--recover-links=false`:void 0}const Qa=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:Ga(),run:async({args:e})=>{let t=P(e),n=await V({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??Y(n.site),o=await Qe({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){I(o);return}Ja(o)}}),status:S({meta:{name:`status`,description:`Show the latest saved monitoring state for a property`},args:Ka(),run:async({args:e})=>{let t=P(e),n=fe({site:(await V({client:D(e),site:E(e.site),options:{json:t,refresh:O(e.refresh)}})).site,staleAfterDays:k(e[`stale-days`])});if(t){I(n);return}Ya(n)}}),cron:S({meta:{name:`cron`,description:`Print a local cron line for recurring monitoring`},args:{...Ga(),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=P(e),n=await V({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`]),Za(e[`recover-links`]),Xa(`refresh`,e.refresh),Xa(`js`,e.js),`--json`].filter(Boolean).join(` `),s={name:`monitoring`,cron:`${i} ${r} * * ${a}`,command:o};if(t){I({site:n.site,line:s});return}L([[`Property`,n.site],[`Install`,`crontab -e`]]),process.stdout.write(`${s.cron} ${s.command}\n`)}})}}),$a=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=P(e),n=await V({client:D(e),site:E(e.site),options:{json:t,refresh:O(e.refresh)}}),r=M(e.sitemaps),i=await me({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){I(i);return}if(L([[`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(`
61
65
  Representative URL Inspection candidates
62
- `),L([`URL`,`Status`,`In sitemap`],i.crawlableWithoutRetainedSearchVisibility.items.map(e=>[G(e.url,80),e.status,e.inSitemap?`yes`:`no`]))),i.caveats.length){process.stdout.write(`
66
+ `),R([`URL`,`Status`,`In sitemap`],i.crawlableWithoutRetainedSearchVisibility.items.map(e=>[G(e.url,80),e.status,e.inSitemap?`yes`:`no`]))),i.caveats.length){process.stdout.write(`
63
67
  How to read this report
64
- `);for(let e of i.caveats)process.stdout.write(`- ${e}\n`)}}}),Xa=x({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:t})=>{let n=N(t),r=M(t.urls),i=await z({site:T(t.site),options:{json:n}}),a=M(t.sitemaps);if(D(t.plan)){if(!a.length)throw Error(`Pass at least one sitemap with --sitemaps.`);let r=await de({site:i,sitemaps:a,properties:M(t.properties).length?M(t.properties):void 0,dailyLimit:k(t[`daily-limit`]),targetCycleDays:k(t[`target-days`]),maxUrls:k(t[`max-urls`])});if(n){F(r);return}if(I([[`Project site`,r.site],[`Sitemap URLs`,String(r.summary.urlCount)],[`Properties used`,String(r.summary.properties)],[`Daily capacity`,String(r.summary.dailyCapacity)],[`Estimated cycle`,e(r.summary.estimatedCycleDays,`day`)],[`Target cycle`,e(r.summary.targetCycleDays,`day`)],[`Suggested properties`,String(r.summary.suggestedProperties)]]),r.properties.length&&L([`Property`,`URLs`,`Cycle`,`Sample URL`],r.properties.map(t=>[t.property,t.urlCount,e(t.cycleDays,`day`),t.sampleUrls[0]??`-`])),r.suggestions.length&&(process.stdout.write(`
68
+ `);for(let e of i.caveats)process.stdout.write(`- ${e}\n`)}}}),eo=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:t})=>{let n=P(t),r=M(t.urls),i=await B({site:E(t.site),options:{json:n}}),a=M(t.sitemaps);if(O(t.plan)){if(!a.length)throw Error(`Pass at least one sitemap with --sitemaps.`);let r=await ue({site:i,sitemaps:a,properties:M(t.properties).length?M(t.properties):void 0,dailyLimit:k(t[`daily-limit`]),targetCycleDays:k(t[`target-days`]),maxUrls:k(t[`max-urls`])});if(n){I(r);return}if(L([[`Project site`,r.site],[`Sitemap URLs`,String(r.summary.urlCount)],[`Properties used`,String(r.summary.properties)],[`Daily capacity`,String(r.summary.dailyCapacity)],[`Estimated cycle`,e(r.summary.estimatedCycleDays,`day`)],[`Target cycle`,e(r.summary.targetCycleDays,`day`)],[`Suggested properties`,String(r.summary.suggestedProperties)]]),r.properties.length&&R([`Property`,`URLs`,`Cycle`,`Sample URL`],r.properties.map(t=>[t.property,t.urlCount,e(t.cycleDays,`day`),t.sampleUrls[0]??`-`])),r.suggestions.length&&(process.stdout.write(`
65
69
  Suggested URL-prefix properties
66
- `),L([`Property`,`URLs`,`Current property`,`Cycle`,`Reason`],r.suggestions.map(t=>[t.property,t.urlCount,t.currentProperty,e(t.estimatedCycleDays,`day`),t.reason]))),r.warnings.length){process.stdout.write(`
70
+ `),R([`Property`,`URLs`,`Current property`,`Cycle`,`Reason`],r.suggestions.map(t=>[t.property,t.urlCount,t.currentProperty,e(t.estimatedCycleDays,`day`),t.reason]))),r.warnings.length){process.stdout.write(`
67
71
  Warnings
68
- `);for(let e of r.warnings.slice(0,10))process.stdout.write(`- ${e}\n`)}return}if(a.length){let e=await ot({site:i,sitemaps:a,properties:M(t.properties).length?M(t.properties):void 0,dailyLimit:k(t[`daily-limit`]),inspectLimit:k(t[`inspect-limit`]),maxUrls:k(t[`max-urls`]),languageCode:T(t.language)});if(n){F(e);return}I([[`Project site`,e.site],[`Discovered sitemap URLs`,String(e.source.discoveredUrls)],[`Invalid sitemap URLs`,String(e.source.invalidUrls)],[`Inventory URLs`,String(e.summary.inventoryUrls)],[`Properties used`,String(e.summary.properties)],[`Daily capacity`,String(e.summary.dailyCapacity)],[`Never attempted`,String(e.summary.neverAttempted)],[`Never succeeded`,String(e.summary.neverSucceeded)],[`Retry waiting`,String(e.summary.retryWaiting)],[`Fresh`,String(e.summary.fresh)],[`Stale`,String(e.summary.stale)],[`Due`,String(e.summary.due)],[`Selected this run`,String(e.summary.selected)],[`Due but not selected`,String(e.summary.unselectedDue)],[`Attempted`,String(e.summary.attempted)],[`Inspected`,String(e.summary.inspected)],[`Failed`,String(e.summary.failed)],[`Quota blocked`,String(e.summary.quotaBlocked)],[`Deferred`,String(e.summary.deferred)],[`Current reviews`,String(e.summary.currentIssues)],[`Regressions`,String(e.summary.regressions)],[`Recoveries`,String(e.summary.recoveries)],[`Changed`,String(e.summary.changed)],[`New alerts`,String(e.summary.alerts)],[`Skipped`,String(e.summary.skipped)]]),e.properties.length&&L([`Property`,`Inventory`,`Selected`,`Inspected`,`Failed`,`Current`,`Alerts`],e.properties.map(e=>[e.property,e.inventoryUrls,e.selectedUrls,e.inspected,e.failed+e.quotaBlocked,e.currentIssues,e.alerts]));let r=e.items.filter(e=>e.currentIssue||e.changed||e.inspectionStatus!==`succeeded`).slice(0,50);if(r.length&&(process.stdout.write(`
72
+ `);for(let e of r.warnings.slice(0,10))process.stdout.write(`- ${e}\n`)}return}if(a.length){let e=await at({site:i,sitemaps:a,properties:M(t.properties).length?M(t.properties):void 0,dailyLimit:k(t[`daily-limit`]),inspectLimit:k(t[`inspect-limit`]),maxUrls:k(t[`max-urls`]),languageCode:E(t.language)});if(n){I(e);return}L([[`Project site`,e.site],[`Discovered sitemap URLs`,String(e.source.discoveredUrls)],[`Invalid sitemap URLs`,String(e.source.invalidUrls)],[`Inventory URLs`,String(e.summary.inventoryUrls)],[`Properties used`,String(e.summary.properties)],[`Daily capacity`,String(e.summary.dailyCapacity)],[`Never attempted`,String(e.summary.neverAttempted)],[`Never succeeded`,String(e.summary.neverSucceeded)],[`Retry waiting`,String(e.summary.retryWaiting)],[`Fresh`,String(e.summary.fresh)],[`Stale`,String(e.summary.stale)],[`Due`,String(e.summary.due)],[`Selected this run`,String(e.summary.selected)],[`Due but not selected`,String(e.summary.unselectedDue)],[`Attempted`,String(e.summary.attempted)],[`Inspected`,String(e.summary.inspected)],[`Failed`,String(e.summary.failed)],[`Quota blocked`,String(e.summary.quotaBlocked)],[`Deferred`,String(e.summary.deferred)],[`Current reviews`,String(e.summary.currentIssues)],[`Regressions`,String(e.summary.regressions)],[`Recoveries`,String(e.summary.recoveries)],[`Changed`,String(e.summary.changed)],[`New alerts`,String(e.summary.alerts)],[`Skipped`,String(e.summary.skipped)]]),e.properties.length&&R([`Property`,`Inventory`,`Selected`,`Inspected`,`Failed`,`Current`,`Alerts`],e.properties.map(e=>[e.property,e.inventoryUrls,e.selectedUrls,e.inspected,e.failed+e.quotaBlocked,e.currentIssues,e.alerts]));let r=e.items.filter(e=>e.currentIssue||e.changed||e.inspectionStatus!==`succeeded`).slice(0,50);if(r.length&&(process.stdout.write(`
69
73
  Index reviews, changes, and failed checks
70
- `),L([`Check`,`Change`,`Index state`,`Evidence`,`URL`],r.map(e=>[e.inspectionStatus,e.changeKind,e.indexStatus,(e.errorCode??e.issueCodes.join(`, `))||`none`,e.url]))),e.warnings.length){process.stdout.write(`
74
+ `),R([`Check`,`Change`,`Index state`,`Evidence`,`URL`],r.map(e=>[e.inspectionStatus,e.changeKind,e.indexStatus,(e.errorCode??e.issueCodes.join(`, `))||`none`,e.url]))),e.warnings.length){process.stdout.write(`
71
75
  Warnings
72
- `);for(let t of e.warnings.slice(0,10))process.stdout.write(`- ${t}\n`)}return}if(!r.length)throw Error(`Pass at least one URL with --urls.`);let o=await le({site:i,urls:r,languageCode:T(t.language),dailyLimit:k(t[`daily-limit`])});if(n){F(o);return}I([[`Project site`,o.site],[`Inspection property`,o.source.property],[`Attempted`,String(o.summary.attempted)],[`Inspected`,String(o.summary.inspected)],[`Failed`,String(o.summary.failed)],[`Quota blocked`,String(o.summary.quotaBlocked)],[`Deferred`,String(o.summary.deferred)],[`Current reviews`,String(o.summary.currentIssues)],[`Regressions`,String(o.summary.regressions)],[`Recoveries`,String(o.summary.recoveries)],[`Changed`,String(o.summary.changed)],[`New alerts`,String(o.summary.alerts)]]),L([`Check`,`Change`,`Index state`,`Evidence`,`URL`],o.items.map(e=>[e.inspectionStatus,e.changeKind,e.indexStatus,(e.errorCode??e.issueCodes.join(`, `))||`none`,e.url]))}}),Za=x({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.`},...Y([`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 ct({site:await sr({client:E(e),site:T(e.site)},{json:t}),days:k(e.days),limit:k(e.limit),minClicks:k(e[`min-clicks`]),minImpressions:k(e[`min-impressions`]),refresh:D(e.refresh),js:D(e.js)?!0:`auto`});if(t){F(n);return}if(I([[`Site`,n.site],[`Range`,`${n.range.startDate} to ${n.range.endDate}`],[`Checked`,H(n.summary.checked)],[`Recoverable`,H(n.summary.recoverable)],[`High severity`,H(n.summary.high)],[`Clicks at risk`,H(n.summary.clicksAtRisk)],[`Impressions at risk`,H(n.summary.impressionsAtRisk)]]),n.items.length&&(K([`Severity`,`Issue`,`Clicks`,`Impr`,`URL`,`Action`],n.items.map(e=>[e.severity,e.issue,H(e.clicks),H(e.impressions),G(e.url,56),G(e.recommendation.action,72)])),q(`Top recovery actions`,n.items.map(e=>({label:G(e.url,96),context:`${e.severity}, ${H(e.clicks)} clicks at risk`,action:e.recommendation.action})))),n.warnings.length){process.stdout.write(`
76
+ `);for(let t of e.warnings.slice(0,10))process.stdout.write(`- ${t}\n`)}return}if(!r.length)throw Error(`Pass at least one URL with --urls.`);let o=await ce({site:i,urls:r,languageCode:E(t.language),dailyLimit:k(t[`daily-limit`])});if(n){I(o);return}L([[`Project site`,o.site],[`Inspection property`,o.source.property],[`Attempted`,String(o.summary.attempted)],[`Inspected`,String(o.summary.inspected)],[`Failed`,String(o.summary.failed)],[`Quota blocked`,String(o.summary.quotaBlocked)],[`Deferred`,String(o.summary.deferred)],[`Current reviews`,String(o.summary.currentIssues)],[`Regressions`,String(o.summary.regressions)],[`Recoveries`,String(o.summary.recoveries)],[`Changed`,String(o.summary.changed)],[`New alerts`,String(o.summary.alerts)]]),R([`Check`,`Change`,`Index state`,`Evidence`,`URL`],o.items.map(e=>[e.inspectionStatus,e.changeKind,e.indexStatus,(e.errorCode??e.issueCodes.join(`, `))||`none`,e.url]))}}),to=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=P(e),n=await st({site:await hr({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){I(n);return}if(L([[`Site`,n.site],[`Range`,`${n.range.startDate} to ${n.range.endDate}`],[`Checked`,H(n.summary.checked)],[`Recoverable`,H(n.summary.recoverable)],[`High severity`,H(n.summary.high)],[`Clicks at risk`,H(n.summary.clicksAtRisk)],[`Impressions at risk`,H(n.summary.impressionsAtRisk)]]),n.items.length&&(K([`Severity`,`Issue`,`Clicks`,`Impr`,`URL`,`Action`],n.items.map(e=>[e.severity,e.issue,H(e.clicks),H(e.impressions),G(e.url,56),G(e.recommendation.action,72)])),q(`Top recovery actions`,n.items.map(e=>({label:G(e.url,96),context:`${e.severity}, ${H(e.clicks)} clicks at risk`,action:e.recommendation.action})))),n.warnings.length){process.stdout.write(`
73
77
  Warnings
74
- `);for(let e of n.warnings.slice(0,10))process.stdout.write(`- ${e}\n`)}}}),Qa=x({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 n({url:T(e.url)??``,maxHops:k(e[`max-hops`]),refresh:D(e.refresh),js:D(e.js)?!0:`auto`});if(N(e)){F(t);return}if(I([[`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`]]),L([`#`,`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(`
78
+ `);for(let e of n.warnings.slice(0,10))process.stdout.write(`- ${e}\n`)}}}),no=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 n({url:E(e.url)??``,maxHops:k(e[`max-hops`]),refresh:O(e.refresh),js:O(e.js)?!0:`auto`});if(P(e)){I(t);return}if(L([[`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`]]),R([`#`,`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(`
75
79
  Final page
76
- `),I([[`Title`,t.finalPage.title??`-`],[`Canonical`,t.finalPage.canonical??`-`],[`Meta robots`,t.finalPage.metaRobots??`-`]])),t.warnings.length){process.stdout.write(`
80
+ `),L([[`Title`,t.finalPage.title??`-`],[`Canonical`,t.finalPage.canonical??`-`],[`Meta robots`,t.finalPage.metaRobots??`-`]])),t.warnings.length){process.stdout.write(`
77
81
  Warnings
78
- `);for(let e of t.warnings.slice(0,10))process.stdout.write(`- ${e}\n`)}}});function $a(e,t){process.stdout.write(`OKF validation for ${e}\n\n`),I([[`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(`
82
+ `);for(let e of t.warnings.slice(0,10))process.stdout.write(`- ${e}\n`)}}});function ro(e,t){process.stdout.write(`OKF validation for ${e}\n\n`),L([[`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(`
79
83
  Issues
80
- `),L([`Severity`,`Path`,`Message`],t.issues.slice(0,25).map(e=>[e.severity,e.path,e.message])))}const eo=x({meta:{name:`okf`,description:`Export, validate, and explain OKF site knowledge bundles`},subCommands:{export:x({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=Ct(A(e[`max-concepts`],`--max-concepts`)),r=Ot(await Fi(e,{json:t}),{maxConcepts:n,title:T(e.title)}),i=T(e.output)??`./okf`,a=await xa(i,r.files);if(t){F({output:i,bundle:r,validation:a});return}process.stdout.write(`Wrote OKF bundle to ${i}\n\n`),I([[`Files`,String(r.files.length)],[`Concepts`,String(r.conceptCount)],[`Valid`,a.valid?`yes`:`no`]])}}),validate:x({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=at(await ya(t));if(n.valid||(process.exitCode=1),N(e)){F(n);return}$a(t,n)}}),explain:x({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=ht(at(await ya(t)));if(n.valid||(process.exitCode=1),N(e)){F(n);return}if(process.stdout.write(`${n.summary}\n\n`),I([[`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(`
84
+ `),R([`Severity`,`Path`,`Message`],t.issues.slice(0,25).map(e=>[e.severity,e.path,e.message])))}const io=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=P(e),n=wt(A(e[`max-concepts`],`--max-concepts`)),r=Ot(await ar(e,{json:t}),{maxConcepts:n,title:E(e.title)}),i=E(e.output)??`./okf`,a=await Ta(i,r.files);if(t){I({output:i,bundle:r,validation:a});return}process.stdout.write(`Wrote OKF bundle to ${i}\n\n`),L([[`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=E(e.path);if(!t)throw Error(`Pass an OKF bundle directory.`);let n=it(await Ca(t));if(n.valid||(process.exitCode=1),P(e)){I(n);return}ro(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=E(e.path);if(!t)throw Error(`Pass an OKF bundle directory.`);let n=ht(it(await Ca(t)));if(n.valid||(process.exitCode=1),P(e)){I(n);return}if(process.stdout.write(`${n.summary}\n\n`),L([[`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(`
81
85
  Next actions
82
- `);for(let e of n.nextActions)process.stdout.write(`- ${e}\n`)}}})}}),to=x({meta:{name:`ai-referrals`,description:`Find AI referral traffic detected in GA4`},args:{property:{type:`string`,description:`GA4 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 GA4 property.`},"start-date":{type:`string`,default:`28daysAgo`},"end-date":{type:`string`,default:`yesterday`},"max-rows":{type:`string`,description:`Maximum GA4 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 GA4 responses.`},json:{type:`boolean`,default:!1}},run:async({args:e})=>{let t=N(e),n=await ar({client:E(e),options:{json:t}}),r=await a({property:await or({property:T(e.property)??n?.ga4PropertyId,options:{json:t}}),startDate:T(e[`start-date`]),endDate:T(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){F(r);return}if(I([[`Property`,r.property],[`Evidence`,r.dataStatus===`complete`?`Complete`:`Partial`],[`Verdict`,r.summary.verdict],[`AI sessions`,H(r.summary.sessions)],[`AI users`,r.summary.totalUsers===null?`Unavailable`:H(r.summary.totalUsers)],[`Sources`,H(r.summary.sources)],[`Landing pages`,H(r.summary.landingPages)],[`Landing-page output`,`${H(r.selection.landingPages.returnedRows)} returned / ${H(r.selection.landingPages.retainedRows)} retained`],[`Source rows`,H(r.dataSource.sourceDiscovery.returnedRows)]]),r.sources.length&&(process.stdout.write(`
86
+ `);for(let e of n.nextActions)process.stdout.write(`- ${e}\n`)}}})}}),ao=S({meta:{name:`ai-referrals`,description:`Find AI referral traffic detected in GA4`},args:{property:{type:`string`,description:`GA4 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 GA4 property.`},"start-date":{type:`string`,default:`28daysAgo`},"end-date":{type:`string`,default:`yesterday`},"max-rows":{type:`string`,description:`Maximum GA4 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 GA4 responses.`},json:{type:`boolean`,default:!1}},run:async({args:e})=>{let t=P(e),n=await tr({client:D(e),options:{json:t}}),r=await De({property:await nr({property:E(e.property)??n?.ga4PropertyId,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){I(r);return}if(L([[`Property`,r.property],[`Evidence`,r.dataStatus===`complete`?`Complete`:`Partial`],[`Verdict`,r.summary.verdict],[`AI sessions`,H(r.summary.sessions)],[`AI users`,r.summary.totalUsers===null?`Unavailable`:H(r.summary.totalUsers)],[`Sources`,H(r.summary.sources)],[`Landing pages`,H(r.summary.landingPages)],[`Landing-page output`,`${H(r.selection.landingPages.returnedRows)} returned / ${H(r.selection.landingPages.retainedRows)} retained`],[`Source rows`,H(r.dataSource.sourceDiscovery.returnedRows)]]),r.sources.length&&(process.stdout.write(`
83
87
  AI sources
84
88
  `),K([`Source`,`Sessions`,`Events`,`Share of AI sessions`],r.sources.map(e=>[e.label,H(e.sessions),H(e.eventCount),W(e.shareOfAiSessions)]))),r.landingPages.length&&(process.stdout.write(`
85
89
  Landing pages
86
90
  `),K([`Landing page`,`Sessions`,`Events`,`Top source`],r.landingPages.map(e=>[G(e.landingPage,64),H(e.sessions),H(e.eventCount),e.topSourceDetails.label]))),r.dataSource.partialReasons.length){process.stdout.write(`
87
91
  Evidence warnings
88
- `);for(let e of r.dataSource.partialReasons)process.stdout.write(`- ${e}\n`)}process.stdout.write(`\nNote: ${r.summary.caveat}\n`)}}),no=x({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.`},...Y([`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=D(e.refresh),r=await B({client:E(e),site:T(e.site),options:{json:t,refresh:n}}),i=await Fe({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:D(e[`include-brand`]),refresh:n});if(t){F(i);return}I([[`Site`,i.site],[`Status`,i.dataStatus],[`Range`,`${i.range.startDate} to ${i.range.endDate}`],[`Eligible candidates`,H(i.summary.eligibleClusters)],[`Returned candidates`,H(i.summary.returnedClusters)],[`Suppressed brand queries`,H(i.summary.suppressedQueries)],[`GSC completeness`,i.source.completeness],[`Brand queries`,D(e[`include-brand`])?`included`:`excluded`],[`Verdict`,i.summary.verdict]]),K([`Query`,`URLs`,`Property impr`,`Exposure`,`HHI`,`Review first`],i.items.map(e=>[G(e.query,42),e.pages.length,e.propertyImpressions===void 0?`n/a`:H(e.propertyImpressions),H(e.pageExposureImpressions),e.hhi.toFixed(2),G(e.suggestedOwnerUrl,56)])),q(`Top cannibalisation actions`,i.items.map(e=>({label:e.query,context:`${e.pages.length} material URLs; first review ${G(e.suggestedOwnerUrl,64)}`,action:e.recommendation.action}))),J(`Report caveats`,i.caveats),J(`Provider usage`,[i.ledgerSummary])}}),ro=x({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.`},...Y([`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 B({client:E(e),site:T(e.site),options:{json:t,refresh:D(e.refresh)}}),r=await fe({site:n.site,days:A(e.days,`--days`),startDate:T(e[`start-date`]),endDate:T(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:D(e[`include-brand`]),refresh:D(e.refresh)});if(t){F(r);return}I([[`Property`,r.site],[`Status`,r.dataStatus],[`Verdict`,r.summary.verdict],[`Classified queries`,H(r.summary.classifiedQueries)],[`Returned queries`,H(r.summary.returnedQueries)],[`Returned impressions`,H(r.summary.returnedImpressions)],[`Returned clicks`,H(r.summary.returnedClicks)],[`GSC completeness`,r.source.completeness]]),K([`Intent`,`Query`,`Impr`,`Clicks`,`Confidence`,`Action`],r.items.map(e=>[e.intent,G(e.query,42),H(e.impressions),H(e.clicks),e.confidence,G(e.action,76)])),q(`Top community-intent actions`,r.items.map(e=>({label:e.query,context:`${e.intent}, ${H(e.impressions)} impressions`,action:e.action}))),J(`Report caveats`,r.caveats),J(`Warnings`,r.warnings)}}),io=x({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.`},...Y([`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 B({client:E(e),site:T(e.site),options:{json:t}}),r=await Xe({site:n.site,minImpressions:k(e[`min-impressions`]),brandTerms:n.client?.brandTerms,includeBrand:D(e[`include-brand`])});if(t){F(r);return}I([[`Site`,r.site],[`Underperformers`,H(r.summary.underperformers)],[`Estimated click gap`,H(r.summary.estimatedClickShortfall)],[`Brand queries`,r.summary.brandFiltering],[`Verdict`,r.summary.verdict]]),J(`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.`]),J(`Recommended actions`,r.recommendations),J(`Report caveats`,r.caveats),K([`Query`,`URL`,`Pos`,`Impr`,`CTR`,`Expected`,`Gap`,`Action`],r.items.map(e=>[G(e.query,36),G(e.url,48),U(e.position),H(e.impressions),W(e.actualCtr),W(e.expectedCtr),H(e.clickShortfall),G(e.recommendation.action,72)])),q(`Top CTR actions`,r.items.map(e=>({label:e.query,context:`${H(e.clickShortfall)} click gap, ${W(e.actualCtr)} CTR`,action:e.recommendation.action})))}});function ao(e){return e>0?U(e):`n/a`}const oo=x({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.`},...Y([`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=D(e.refresh),r=T(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 _(`INVALID_INPUT`,`--comparison must be previous-period or year-over-year.`);let l=await B({client:E(e),site:T(e.site),options:{json:t,refresh:n}}),u=await je({site:l.site,days:i,limit:a,comparison:r,brandTerms:[...l.client?.brandTerms??[],...j(e[`brand-terms`])??[]],includeBrand:D(e[`include-brand`]),minDropPct:o,minPreviousClicks:s,minClickLoss:c,refresh:n});if(t){F(u);return}I([[`Site`,u.site],[`Status`,u.dataStatus],[`Comparison`,u.comparison],[`Observed query/page declines`,H(u.summary.returnedRows)],[`Eligible declines`,H(u.summary.eligibleRows)],[`Decay clusters`,H(u.summary.groups)],[`Eligible observed click decline`,H(u.summary.observedRetainedQueryClickLoss)],[`Returned observed click decline`,H(u.summary.returnedObservedRetainedQueryClickLoss)],[`Brand queries`,u.summary.brandFiltering],[`Min drop`,`${u.filters.minDropPct}%`],[`Min previous clicks`,H(u.filters.minPreviousClicks)],[`GSC completeness`,u.source.completeness],[`Verdict`,u.summary.verdict]]),J(`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.`]),J(`Recommended actions`,u.recommendations),J(`Report caveats`,u.caveats),J(`Warnings`,u.warnings),u.items.length&&(u.groups.length&&(K([`Cluster`,`Rows`,`Observed loss`,`Drop`,`Sample query`,`Action`],u.groups.map(e=>[G(e.label,42),H(e.count),H(e.totalClickLoss),`${e.averageDropPct.toFixed(1)}%`,G(e.sampleQueries[0]??`-`,40),G(e.recommendation,72)])),q(`Top decay cluster actions`,u.groups.map(e=>({label:e.label,context:`${H(e.count)} retained rows, ${H(e.totalClickLoss)} observed click decline`,action:e.recommendation})))),K([`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(`, `),H(e.clickLoss),`${H(e.previous.clicks)} -> ${H(e.current.clicks)}`,`${H(e.previous.impressions)} -> ${H(e.current.impressions)}`,`${W(e.previous.ctr)} -> ${W(e.current.ctr)}`,`${ao(e.previous.position)} -> ${ao(e.current.position)}`,G(e.recommendation.action,64)])),q(`Top decay actions`,u.items.map(e=>({label:e.query,context:`${e.template.label}, ${H(e.clickLoss)} fewer observed clicks`,action:e.recommendation.action}))))}}),so=x({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.`},...Y([`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=D(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=Jn(e),c=await B({client:E(e),site:T(e.site),options:{json:t,refresh:n}}),l=await te({site:c.site,targetUrl:T(e.url)??``,days:r,limit:i,checkLimit:a,minImpressions:o,brandTerms:[...c.client?.brandTerms??[],...j(e[`brand-terms`])??[]],includeBrand:D(e[`include-brand`]),js:D(e.js)?!0:void 0,rate:s,refresh:n});if(t){F(l);return}I([[`Site`,l.site],[`Target`,l.targetUrl],[`Preferred target`,l.target.preferredUrl],[`Target state`,l.target.verification],[`Target queries`,H(l.summary.targetQueries)],[`Matched sources`,H(l.summary.candidateSources)],[`Attempted sources`,H(l.summary.attemptedSources)],[`Checked sources`,H(l.summary.checkedSources)],[`Review candidates`,H(l.summary.returnedSources)],[`Existing contextual links`,H(l.summary.existingLinksObserved)],[`Technical exclusions`,H(l.summary.technicalExclusions)],[`Failed checks`,H(l.summary.failedChecks)],[`Unchecked sources`,H(l.summary.uncheckedCandidates)],[`GSC completeness`,l.source.completeness],[`Brand queries`,l.summary.brandFiltering],[`Verdict`,l.summary.verdict]]),J(`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.`]),J(`Recommended actions`,l.recommendations),J(`Report caveats`,l.caveats),l.items.length&&(K([`Source URL`,`Matched impr`,`Match`,`Link evidence`,`Action`],l.items.map(e=>[G(e.sourceUrl,52),H(e.matchedQueryImpressions),e.bestMatchKind,e.linkEvidence.status,e.actionType])),q(`Top internal-link reviews`,l.items.map(e=>({label:G(e.sourceUrl,96),context:`${H(e.matchedQueryImpressions)} matched-query impressions; ${e.bestMatchKind}`,action:e.recommendation.action}))),J(`Provider usage`,[l.ledgerSummary]))}}),co=x({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},...Y([`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=N(e),n=await B({client:E(e),site:T(e.site),options:{json:t,refresh:D(e.refresh)}}),r=await ue({site:n.site,url:T(e.url)??``,days:k(e.days),limit:k(e.limit),minImpressions:k(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){F(r);return}if(I([[`Property`,r.site],[`URL`,r.url],[`Title`,r.page?.title??`not fetched`],[`Fetch`,ur(r.page?.fetchDiagnostics)],[`Verification`,r.verification.status],[`Rows`,`${H(r.selection.returnedRows)} returned / ${H(r.selection.eligibleRows)} eligible / ${H(r.selection.sourceRows)} source`],[`Queries`,H(r.summary.queries)],[`Impressions`,H(r.summary.impressions)],[`Opportunities`,H(r.summary.opportunities)],[`CTR shortfall`,H(r.summary.estimatedCtrClickShortfall)],[`Focus`,r.summary.focus],[`Verdict`,r.summary.verdict]]),J(`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.`]),J(`Recommended actions`,r.recommendations),J(`Report caveats`,r.caveats),!r.items.length){process.stdout.write(r.dataStatus===`empty`?`No GSC query rows found for this exact URL.
92
+ `);for(let e of r.dataSource.partialReasons)process.stdout.write(`- ${e}\n`)}process.stdout.write(`\nNote: ${r.summary.caveat}\n`)}}),oo=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=P(e),n=O(e.refresh),r=await V({client:D(e),site:E(e.site),options:{json:t,refresh:n}}),i=await Pe({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){I(i);return}L([[`Site`,i.site],[`Status`,i.dataStatus],[`Range`,`${i.range.startDate} to ${i.range.endDate}`],[`Eligible candidates`,H(i.summary.eligibleClusters)],[`Returned candidates`,H(i.summary.returnedClusters)],[`Suppressed brand queries`,H(i.summary.suppressedQueries)],[`GSC completeness`,i.source.completeness],[`Brand queries`,O(e[`include-brand`])?`included`:`excluded`],[`Verdict`,i.summary.verdict]]),K([`Query`,`URLs`,`Property impr`,`Exposure`,`HHI`,`Review first`],i.items.map(e=>[G(e.query,42),e.pages.length,e.propertyImpressions===void 0?`n/a`:H(e.propertyImpressions),H(e.pageExposureImpressions),e.hhi.toFixed(2),G(e.suggestedOwnerUrl,56)])),q(`Top cannibalisation actions`,i.items.map(e=>({label:e.query,context:`${e.pages.length} material URLs; first review ${G(e.suggestedOwnerUrl,64)}`,action:e.recommendation.action}))),J(`Report caveats`,i.caveats),J(`Provider usage`,[i.ledgerSummary])}}),so=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=P(e),n=await V({client:D(e),site:E(e.site),options:{json:t,refresh:O(e.refresh)}}),r=await he({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){I(r);return}L([[`Property`,r.site],[`Status`,r.dataStatus],[`Verdict`,r.summary.verdict],[`Classified queries`,H(r.summary.classifiedQueries)],[`Returned queries`,H(r.summary.returnedQueries)],[`Returned impressions`,H(r.summary.returnedImpressions)],[`Returned clicks`,H(r.summary.returnedClicks)],[`GSC completeness`,r.source.completeness]]),K([`Intent`,`Query`,`Impr`,`Clicks`,`Confidence`,`Action`],r.items.map(e=>[e.intent,G(e.query,42),H(e.impressions),H(e.clicks),e.confidence,G(e.action,76)])),q(`Top community-intent actions`,r.items.map(e=>({label:e.query,context:`${e.intent}, ${H(e.impressions)} impressions`,action:e.action}))),J(`Report caveats`,r.caveats),J(`Warnings`,r.warnings)}}),co=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=P(e),n=await V({client:D(e),site:E(e.site),options:{json:t}}),r=await Ye({site:n.site,minImpressions:k(e[`min-impressions`]),brandTerms:n.client?.brandTerms,includeBrand:O(e[`include-brand`])});if(t){I(r);return}L([[`Site`,r.site],[`Underperformers`,H(r.summary.underperformers)],[`Estimated click gap`,H(r.summary.estimatedClickShortfall)],[`Brand queries`,r.summary.brandFiltering],[`Verdict`,r.summary.verdict]]),J(`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.`]),J(`Recommended actions`,r.recommendations),J(`Report caveats`,r.caveats),K([`Query`,`URL`,`Pos`,`Impr`,`CTR`,`Expected`,`Gap`,`Action`],r.items.map(e=>[G(e.query,36),G(e.url,48),U(e.position),H(e.impressions),W(e.actualCtr),W(e.expectedCtr),H(e.clickShortfall),G(e.recommendation.action,72)])),q(`Top CTR actions`,r.items.map(e=>({label:e.query,context:`${H(e.clickShortfall)} click gap, ${W(e.actualCtr)} CTR`,action:e.recommendation.action})))}});function lo(e){return e>0?U(e):`n/a`}const uo=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=P(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 v(`INVALID_INPUT`,`--comparison must be previous-period or year-over-year.`);let l=await V({client:D(e),site:E(e.site),options:{json:t,refresh:n}}),u=await Ae({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){I(u);return}L([[`Site`,u.site],[`Status`,u.dataStatus],[`Comparison`,u.comparison],[`Observed query/page declines`,H(u.summary.returnedRows)],[`Eligible declines`,H(u.summary.eligibleRows)],[`Decay clusters`,H(u.summary.groups)],[`Eligible observed click decline`,H(u.summary.observedRetainedQueryClickLoss)],[`Returned observed click decline`,H(u.summary.returnedObservedRetainedQueryClickLoss)],[`Brand queries`,u.summary.brandFiltering],[`Min drop`,`${u.filters.minDropPct}%`],[`Min previous clicks`,H(u.filters.minPreviousClicks)],[`GSC completeness`,u.source.completeness],[`Verdict`,u.summary.verdict]]),J(`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.`]),J(`Recommended actions`,u.recommendations),J(`Report caveats`,u.caveats),J(`Warnings`,u.warnings),u.items.length&&(u.groups.length&&(K([`Cluster`,`Rows`,`Observed loss`,`Drop`,`Sample query`,`Action`],u.groups.map(e=>[G(e.label,42),H(e.count),H(e.totalClickLoss),`${e.averageDropPct.toFixed(1)}%`,G(e.sampleQueries[0]??`-`,40),G(e.recommendation,72)])),q(`Top decay cluster actions`,u.groups.map(e=>({label:e.label,context:`${H(e.count)} retained rows, ${H(e.totalClickLoss)} observed click decline`,action:e.recommendation})))),K([`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(`, `),H(e.clickLoss),`${H(e.previous.clicks)} -> ${H(e.current.clicks)}`,`${H(e.previous.impressions)} -> ${H(e.current.impressions)}`,`${W(e.previous.ctr)} -> ${W(e.current.ctr)}`,`${lo(e.previous.position)} -> ${lo(e.current.position)}`,G(e.recommendation.action,64)])),q(`Top decay actions`,u.items.map(e=>({label:e.query,context:`${e.template.label}, ${H(e.clickLoss)} fewer observed clicks`,action:e.recommendation.action}))))}}),fo=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=P(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=Jn(e),c=await V({client:D(e),site:E(e.site),options:{json:t,refresh:n}}),l=await ee({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){I(l);return}L([[`Site`,l.site],[`Target`,l.targetUrl],[`Preferred target`,l.target.preferredUrl],[`Target state`,l.target.verification],[`Target queries`,H(l.summary.targetQueries)],[`Matched sources`,H(l.summary.candidateSources)],[`Attempted sources`,H(l.summary.attemptedSources)],[`Checked sources`,H(l.summary.checkedSources)],[`Review candidates`,H(l.summary.returnedSources)],[`Existing contextual links`,H(l.summary.existingLinksObserved)],[`Technical exclusions`,H(l.summary.technicalExclusions)],[`Failed checks`,H(l.summary.failedChecks)],[`Unchecked sources`,H(l.summary.uncheckedCandidates)],[`GSC completeness`,l.source.completeness],[`Brand queries`,l.summary.brandFiltering],[`Verdict`,l.summary.verdict]]),J(`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.`]),J(`Recommended actions`,l.recommendations),J(`Report caveats`,l.caveats),l.items.length&&(K([`Source URL`,`Matched impr`,`Match`,`Link evidence`,`Action`],l.items.map(e=>[G(e.sourceUrl,52),H(e.matchedQueryImpressions),e.bestMatchKind,e.linkEvidence.status,e.actionType])),q(`Top internal-link reviews`,l.items.map(e=>({label:G(e.sourceUrl,96),context:`${H(e.matchedQueryImpressions)} matched-query impressions; ${e.bestMatchKind}`,action:e.recommendation.action}))),J(`Provider usage`,[l.ledgerSummary]))}}),po=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":T(`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=P(e),n=await V({client:D(e),site:E(e.site),options:{json:t,refresh:O(e.refresh)}}),r=await de({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){I(r);return}if(L([[`Property`,r.site],[`URL`,r.url],[`Title`,r.page?.title??`not fetched`],[`Fetch`,vr(r.page?.fetchDiagnostics)],[`Verification`,r.verification.status],[`Rows`,`${H(r.selection.returnedRows)} returned / ${H(r.selection.eligibleRows)} eligible / ${H(r.selection.sourceRows)} source`],[`Queries`,H(r.summary.queries)],[`Impressions`,H(r.summary.impressions)],[`Opportunities`,H(r.summary.opportunities)],[`CTR shortfall`,H(r.summary.estimatedCtrClickShortfall)],[`Focus`,r.summary.focus],[`Verdict`,r.summary.verdict]]),J(`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.`]),J(`Recommended actions`,r.recommendations),J(`Report caveats`,r.caveats),!r.items.length){process.stdout.write(r.dataStatus===`empty`?`No GSC query rows found for this exact URL.
89
93
  `:`GSC rows were found, but none met the report criteria.
90
- `);return}K([`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),H(e.impressions),e.estimatedCtrClickShortfall===void 0?`n/a`:H(e.estimatedCtrClickShortfall),G(e.recommendation,72)])),q(`Top page actions`,r.items.map(e=>({label:e.query,context:`${e.opportunityType}, ${H(e.impressions)} impressions`,action:e.recommendation})))}}),lo=x({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`},...Y([`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 B({client:E(e),site:T(e.site),options:{json:t}}),r=await Nt({site:n.site,scope:T(e.scope),brand:n.client?.brandTerms?.[0],brandTerms:n.client?.brandTerms,includeBrand:D(e[`include-brand`]),minImpressions:k(e[`min-impressions`]),limit:k(e.limit)});if(t){F(r);return}I([[`Site`,r.site],[`Scope`,r.scope??`all pages`],[`Clusters`,H(r.summary.clusters)],[`Queries`,H(r.summary.queries)],[`Impressions`,H(r.summary.impressions)],[`Clicks`,H(r.summary.clicks)],[`Minimum impressions`,H(r.summary.minImpressions)],[`Result limit`,H(r.summary.limit)],[`Brand queries`,r.summary.brandFiltering],[`High-opportunity clusters`,H(r.summary.highOpportunityClusters)],[`Verdict`,r.summary.verdict]]),J(`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.`]),J(`Recommended actions`,r.recommendations),J(`Report caveats`,r.caveats),K([`Cluster`,`Intent`,`Queries`,`Impr`,`CTR`,`Expected`,`Lift`,`Action`],r.clusters.map(e=>[G(e.label,32),e.intent,e.queries.length,H(e.totals?.impressions??0),W(e.totals?.ctr??0),W(e.benchmark?.expectedCtr??0),e.estimatedClickLift===void 0?`-`:H(e.estimatedClickLift),G(e.recommendation??``,72)])),q(`Top cluster actions`,r.clusters.map(e=>({label:e.label,context:`${H(e.totals?.impressions??0)} impressions, ${e.estimatedClickLift===void 0?`ranking opportunity`:`${H(e.estimatedClickLift)} estimated click lift`}`,action:e.recommendation??``})))}}),uo=x({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.`},...Y([`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=D(e[`verify-content`])===!0||n!==void 0,i=await B({client:E(e),site:T(e.site),options:{json:t}}),a=await Ne({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:D(e[`include-brand`]),verifyContent:r,verifyLimit:n,js:D(e.js)?!0:void 0,rate:qn(e),refresh:D(e.refresh)});if(t){F(a);return}I([[`Site`,a.site],[`Eligible rows`,H(a.summary.eligibleRows)],[`Returned rows`,H(a.summary.returnedRows)],[`Repeated-query clusters`,H(a.summary.repeatedQueryGroups)],[`Template patterns`,H(a.summary.templatePatterns)],[`CTR click shortfall`,H(a.summary.returnedEstimatedCtrClickShortfall)],[`Brand queries`,a.summary.brandFiltering],[`Verification`,si(a)],[`Verdict`,a.summary.verdict]]),J(`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.`]),J(`Recommended actions`,a.recommendations),J(`Report caveats`,a.caveats),a.items.length&&(a.templateRecommendations.length&&(K([`Template`,`URLs`,`Rows`,`Shortfall`,`Impr`,`Action`],a.templateRecommendations.map(e=>[G(e.templateLabel,34),H(e.urlCount),H(e.rowCount),H(e.totalEstimatedCtrClickShortfall),H(e.totalImpressions),G(e.action,72)])),q(`Top template actions`,a.templateRecommendations.map(e=>({label:e.templateLabel,context:`${H(e.urlCount)} URLs, ${H(e.totalEstimatedCtrClickShortfall)} heuristic CTR click shortfall`,action:`${e.action} ${e.evidence}`})))),a.groups.length&&(K([`Cluster`,`URLs`,`Rows`,`Shortfall`,`Impr`,`Sample URL`,`Action`],a.groups.map(e=>[G(e.label,44),H(e.urlCount),H(e.rowCount),H(e.totalEstimatedCtrClickShortfall),H(e.totalImpressions),G(e.sampleUrls[0]??`-`,46),G(e.recommendation,72)])),q(`Top cluster actions`,a.groups.map(e=>({label:e.label,context:`${H(e.urlCount)} URLs, ${H(e.totalEstimatedCtrClickShortfall)} heuristic CTR click shortfall`,action:e.recommendation})))),K([`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),U(e.position),H(e.impressions),`${W(e.ctr)} / ${W(e.targetCtr)}`,H(e.estimatedCtrClickShortfall),ur(e.contentVerification?.fetchDiagnostics),dr(e.contentVerification?.classification),G(e.recommendation.action,64)])),q(`Top opportunity actions`,a.items.map(e=>({label:e.query,context:`${e.template.label}, pos ${U(e.position)}, ${H(e.impressions)} impressions`,action:e.recommendation.action}))))}}),fo=x({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.`},...Y([`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=D(e[`verify-content`])===!0||n!==void 0,i=await B({client:E(e),site:T(e.site),options:{json:t,refresh:D(e.refresh)}}),a=await Ft({site:i.site,range:k(e.days),minImpressions:k(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:qn(e),refresh:D(e.refresh)});if(t){F(a);return}if(I([[`Site`,a.site],[`Eligible pages`,H(a.summary.eligiblePages)],[`Returned pages`,H(a.summary.returnedPages)],[`Eligible queries`,H(a.summary.eligibleQueries)],[`Eligible impressions`,H(a.summary.eligibleImpressions)],[`Content issues`,H(a.summary.contentIssues)],[`Technical issues`,H(a.summary.technicalIssues)],[`Fetch failures`,H(a.summary.fetchFailures)],[`Brand queries`,a.summary.brandFiltering],[`Verification`,si(a)],[`Verdict`,a.summary.verdict]]),J(`Recommended actions`,a.recommendations),J(`Report caveats`,a.caveats),!a.items.length){process.stdout.write(`No average-position candidates matched this report.
91
- `),process.stdout.write(`${a.ledgerSummary}\n`);return}K([`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),H(e.queryCount),ur(e.fetchDiagnostics),dr(e.contentVerification?.classification),e.recommendation.action])),q(`Top second-page actions`,a.items.map(e=>({label:e.primaryQuery,context:`${e.template.label}, pos ${U(e.position)}, ${H(e.impressions)} impressions`,action:e.recommendation.action}))),process.stdout.write(`${a.ledgerSummary}\n`)}}),po=x({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.`},...Y([`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=N(e),n=await B({client:E(e),site:T(e.site),options:{json:t,refresh:D(e.refresh)}}),r=await He({site:n.site,days:A(e.days,`--days`),startDate:T(e[`start-date`]),endDate:T(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:D(e[`include-brand`]),refresh:D(e.refresh)});if(t){F(r);return}I([[`Property`,r.site],[`Status`,r.dataStatus],[`Verdict`,r.summary.verdict],[`Eligible queries`,H(r.summary.eligibleQueries)],[`Returned queries`,H(r.summary.returnedQueries)],[`Prompts`,H(r.summary.prompts)],[`GSC completeness`,r.source.completeness]]),K([`Query`,`Impr`,`Prompt`],r.items.flatMap(e=>e.prompts.map(t=>[G(e.query,42),H(e.impressions),G(t,86)]))),J(`Report caveats`,r.caveats),J(`Warnings`,r.warnings)}}),mo=x({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 ar({project:t,options:{json:N(e)}}):void 0,i=t||n?void 0:await ar({options:{json:N(e)}}),a=await ne({url:T(e.url)??``,site:r?.siteUrl??n??i?.siteUrl,js:D(e.js)?!0:`auto`,refresh:D(e.refresh)});if(N(e)){F(a);return}I([[`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`,ur(a.fetchDiagnostics)]]),a.issues.length&&(process.stdout.write(`
94
+ `);return}K([`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),H(e.impressions),e.estimatedCtrClickShortfall===void 0?`n/a`:H(e.estimatedCtrClickShortfall),G(e.recommendation,72)])),q(`Top page actions`,r.items.map(e=>({label:e.query,context:`${e.opportunityType}, ${H(e.impressions)} impressions`,action:e.recommendation})))}}),mo=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=P(e),n=await V({client:D(e),site:E(e.site),options:{json:t}}),r=await Nt({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){I(r);return}L([[`Site`,r.site],[`Scope`,r.scope??`all pages`],[`Clusters`,H(r.summary.clusters)],[`Queries`,H(r.summary.queries)],[`Impressions`,H(r.summary.impressions)],[`Clicks`,H(r.summary.clicks)],[`Minimum impressions`,H(r.summary.minImpressions)],[`Result limit`,H(r.summary.limit)],[`Brand queries`,r.summary.brandFiltering],[`High-opportunity clusters`,H(r.summary.highOpportunityClusters)],[`Verdict`,r.summary.verdict]]),J(`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.`]),J(`Recommended actions`,r.recommendations),J(`Report caveats`,r.caveats),K([`Cluster`,`Intent`,`Queries`,`Impr`,`CTR`,`Expected`,`Lift`,`Action`],r.clusters.map(e=>[G(e.label,32),e.intent,e.queries.length,H(e.totals?.impressions??0),W(e.totals?.ctr??0),W(e.benchmark?.expectedCtr??0),e.estimatedClickLift===void 0?`-`:H(e.estimatedClickLift),G(e.recommendation??``,72)])),q(`Top cluster actions`,r.clusters.map(e=>({label:e.label,context:`${H(e.totals?.impressions??0)} impressions, ${e.estimatedClickLift===void 0?`ranking opportunity`:`${H(e.estimatedClickLift)} estimated click lift`}`,action:e.recommendation??``})))}}),ho=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=P(e),n=k(e[`verify-limit`]),r=O(e[`verify-content`])===!0||n!==void 0,i=await V({client:D(e),site:E(e.site),options:{json:t}}),a=await Me({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:N(e),refresh:O(e.refresh)});if(t){I(a);return}L([[`Site`,a.site],[`Eligible rows`,H(a.summary.eligibleRows)],[`Returned rows`,H(a.summary.returnedRows)],[`Repeated-query clusters`,H(a.summary.repeatedQueryGroups)],[`Template patterns`,H(a.summary.templatePatterns)],[`CTR click shortfall`,H(a.summary.returnedEstimatedCtrClickShortfall)],[`Brand queries`,a.summary.brandFiltering],[`Verification`,rr(a)],[`Verdict`,a.summary.verdict]]),J(`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.`]),J(`Recommended actions`,a.recommendations),J(`Report caveats`,a.caveats),a.items.length&&(a.templateRecommendations.length&&(K([`Template`,`URLs`,`Rows`,`Shortfall`,`Impr`,`Action`],a.templateRecommendations.map(e=>[G(e.templateLabel,34),H(e.urlCount),H(e.rowCount),H(e.totalEstimatedCtrClickShortfall),H(e.totalImpressions),G(e.action,72)])),q(`Top template actions`,a.templateRecommendations.map(e=>({label:e.templateLabel,context:`${H(e.urlCount)} URLs, ${H(e.totalEstimatedCtrClickShortfall)} heuristic CTR click shortfall`,action:`${e.action} ${e.evidence}`})))),a.groups.length&&(K([`Cluster`,`URLs`,`Rows`,`Shortfall`,`Impr`,`Sample URL`,`Action`],a.groups.map(e=>[G(e.label,44),H(e.urlCount),H(e.rowCount),H(e.totalEstimatedCtrClickShortfall),H(e.totalImpressions),G(e.sampleUrls[0]??`-`,46),G(e.recommendation,72)])),q(`Top cluster actions`,a.groups.map(e=>({label:e.label,context:`${H(e.urlCount)} URLs, ${H(e.totalEstimatedCtrClickShortfall)} heuristic CTR click shortfall`,action:e.recommendation})))),K([`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),U(e.position),H(e.impressions),`${W(e.ctr)} / ${W(e.targetCtr)}`,H(e.estimatedCtrClickShortfall),vr(e.contentVerification?.fetchDiagnostics),yr(e.contentVerification?.classification),G(e.recommendation.action,64)])),q(`Top opportunity actions`,a.items.map(e=>({label:e.query,context:`${e.template.label}, pos ${U(e.position)}, ${H(e.impressions)} impressions`,action:e.recommendation.action}))))}}),go=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=P(e),n=k(e[`verify-limit`]),r=O(e[`verify-content`])===!0||n!==void 0,i=await V({client:D(e),site:E(e.site),options:{json:t,refresh:O(e.refresh)}}),a=await Ft({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:N(e),refresh:O(e.refresh)});if(t){I(a);return}if(L([[`Site`,a.site],[`Eligible pages`,H(a.summary.eligiblePages)],[`Returned pages`,H(a.summary.returnedPages)],[`Eligible queries`,H(a.summary.eligibleQueries)],[`Eligible impressions`,H(a.summary.eligibleImpressions)],[`Content issues`,H(a.summary.contentIssues)],[`Technical issues`,H(a.summary.technicalIssues)],[`Fetch failures`,H(a.summary.fetchFailures)],[`Brand queries`,a.summary.brandFiltering],[`Verification`,rr(a)],[`Verdict`,a.summary.verdict]]),J(`Recommended actions`,a.recommendations),J(`Report caveats`,a.caveats),!a.items.length){process.stdout.write(`No average-position candidates matched this report.
95
+ `),process.stdout.write(`${a.ledgerSummary}\n`);return}K([`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),H(e.queryCount),vr(e.fetchDiagnostics),yr(e.contentVerification?.classification),e.recommendation.action])),q(`Top second-page actions`,a.items.map(e=>({label:e.primaryQuery,context:`${e.template.label}, pos ${U(e.position)}, ${H(e.impressions)} impressions`,action:e.recommendation.action}))),process.stdout.write(`${a.ledgerSummary}\n`)}}),_o=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=P(e),n=await V({client:D(e),site:E(e.site),options:{json:t,refresh:O(e.refresh)}}),r=await Ve({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){I(r);return}L([[`Property`,r.site],[`Status`,r.dataStatus],[`Verdict`,r.summary.verdict],[`Eligible queries`,H(r.summary.eligibleQueries)],[`Returned queries`,H(r.summary.returnedQueries)],[`Prompts`,H(r.summary.prompts)],[`GSC completeness`,r.source.completeness]]),K([`Query`,`Impr`,`Prompt`],r.items.flatMap(e=>e.prompts.map(t=>[G(e.query,42),H(e.impressions),G(t,86)]))),J(`Report caveats`,r.caveats),J(`Warnings`,r.warnings)}}),vo=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=D(e),n=E(e.site),r=t?await tr({project:t,options:{json:P(e)}}):void 0,i=t||n?void 0:await tr({options:{json:P(e)}}),a=await se({url:E(e.url)??``,site:r?.siteUrl??n??i?.siteUrl,js:O(e.js)?!0:`auto`,refresh:O(e.refresh)});if(P(e)){I(a);return}L([[`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`,vr(a.fetchDiagnostics)]]),a.issues.length&&(process.stdout.write(`
92
96
  Issues
93
- `),L([`Code`,`Severity`,`Principle`,`Detail`],a.issues.map(e=>[e.code,e.severity,e.principle,e.detail])))}});function ho(e){let t=T(e)??`mobile`;if(t!==`mobile`&&t!==`desktop`)throw new _(`INVALID_INPUT`,`Invalid --strategy. Use mobile or desktop.`);return t}function go(e){return e?e.displayValue??String(e.value??`not measured`):`not measured`}function _o(e){if(!e)return`not available`;let t=e.unit===`milliseconds`?`ms`:``;return`${e.p75}${t} (${e.rating})`}const vo=x({meta:{name:`perf`,description:`Audit lab performance and field Core Web Vitals`},subCommands:{audit:x({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 ar({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 c({url:r,strategy:ho(e.strategy),lighthouseBin:T(e[`lighthouse-bin`]),cruxApiKey:T(e[`crux-key`]),refresh:D(e.refresh),includeRaw:t&&D(e.raw)});if(t){F(i);return}I([[`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`,go(i.metrics.firstContentfulPaint)],[`LCP`,go(i.metrics.largestContentfulPaint)],[`TBT`,go(i.metrics.totalBlockingTime)],[`CLS`,go(i.metrics.cumulativeLayoutShift)],[`INP`,go(i.metrics.interactionToNextPaint)],[`Server response`,go(i.metrics.serverResponseTime)],[`Fallback fetch`,go(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`,_o(i.fieldData?.metrics.largestContentfulPaint)],[`Field INP p75`,_o(i.fieldData?.metrics.interactionToNextPaint)],[`Field CLS p75`,_o(i.fieldData?.metrics.cumulativeLayoutShift)]]),q(`Top actions`,i.topActions.map(e=>({label:e.title,action:e.action,context:e.plainEnglish}))),J(`Caveats`,i.caveats)}})}}),yo=x({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.`},...Y([`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 B({client:E(e),site:T(e.site),options:{json:t,refresh:D(e.refresh)}}),r=await De({site:n.site,days:k(e.days),recentDays:k(e.recent),limit:k(e.limit),brandTerms:n.client?.brandTerms,includeBrand:D(e[`include-brand`]),refresh:D(e.refresh),progress:X(!t)});if(t){F(r);return}I([[`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)]]),L([`Priority`,`Confidence`,`Reason`,`Action`],r.priorities.map(e=>[e.label,e.confidence,e.reason,e.action])),r.skippedSections?.length&&L([`Skipped section`,`Reason`],r.skippedSections.map(e=>[e.section,e.reason])),q(`Priority action details`,r.priorities.map(e=>({label:e.label,context:e.confidence,action:e.action})))}}),bo=x({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 ut();if(N(e)){F(t);return}I([[`Status`,t.ok?`ok`:`needs attention`],[`Generated`,t.generatedAt]]),L([`Check`,`Status`,`Detail`,`Fix`],t.checks.map(e=>[e.label,e.status,e.detail,e.fix??``]))}}),xo=x({meta:{name:`ga4-properties`,description:`List GA4 accounts and properties available to Google OAuth`},args:{json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`},save:{type:`string`,description:`Save this numeric GA4 property ID as the default.`}},run:async({args:e})=>{let t=await Ee(),n=t.flatMap(e=>e.propertySummaries.map(t=>({account:e.displayName??e.account,property:Oe(t.property),displayName:t.displayName??t.property}))),r=T(e.save);if(r){let e=Ye();e.google.defaultGa4PropertyId=r,mt(e)}if(N(e)){F({accountSummaries:t,properties:n,saved:r});return}r&&process.stdout.write(`Saved default GA4 property ${r}.\n`),L([`Property`,`Name`,`Account`],n.map(e=>[e.property,e.displayName,e.account]))}}),So=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},Co=x({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.`},...Y([`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=So(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 _e({site:(await B({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){F(c);return}I([[`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]]),L([`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(`
97
+ `),R([`Code`,`Severity`,`Principle`,`Detail`],a.issues.map(e=>[e.code,e.severity,e.principle,e.detail])))}});function yo(e){let t=E(e)??`mobile`;if(t!==`mobile`&&t!==`desktop`)throw new v(`INVALID_INPUT`,`Invalid --strategy. Use mobile or desktop.`);return t}function bo(e){return e?e.displayValue??String(e.value??`not measured`):`not measured`}function xo(e){if(!e)return`not available`;let t=e.unit===`milliseconds`?`ms`:``;return`${e.p75}${t} (${e.rating})`}const So=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=P(e),n=await tr({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 c({url:r,strategy:yo(e.strategy),lighthouseBin:E(e[`lighthouse-bin`]),cruxApiKey:E(e[`crux-key`]),refresh:O(e.refresh),includeRaw:t&&O(e.raw)});if(t){I(i);return}L([[`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`,bo(i.metrics.firstContentfulPaint)],[`LCP`,bo(i.metrics.largestContentfulPaint)],[`TBT`,bo(i.metrics.totalBlockingTime)],[`CLS`,bo(i.metrics.cumulativeLayoutShift)],[`INP`,bo(i.metrics.interactionToNextPaint)],[`Server response`,bo(i.metrics.serverResponseTime)],[`Fallback fetch`,bo(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`,xo(i.fieldData?.metrics.largestContentfulPaint)],[`Field INP p75`,xo(i.fieldData?.metrics.interactionToNextPaint)],[`Field CLS p75`,xo(i.fieldData?.metrics.cumulativeLayoutShift)]]),q(`Top actions`,i.topActions.map(e=>({label:e.title,action:e.action,context:e.plainEnglish}))),J(`Caveats`,i.caveats)}})}}),Co=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=P(e),n=await V({client:D(e),site:E(e.site),options:{json:t,refresh:O(e.refresh)}}),r=await Ee({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:ia(!t)});if(t){I(r);return}L([[`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)]]),R([`Priority`,`Confidence`,`Reason`,`Action`],r.priorities.map(e=>[e.label,e.confidence,e.reason,e.action])),r.skippedSections?.length&&R([`Skipped section`,`Reason`],r.skippedSections.map(e=>[e.section,e.reason])),q(`Priority action details`,r.priorities.map(e=>({label:e.label,context:e.confidence,action:e.action})))}}),wo=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 lt();if(P(e)){I(t);return}L([[`Status`,t.ok?`ok`:`needs attention`],[`Generated`,t.generatedAt]]),R([`Check`,`Status`,`Detail`,`Fix`],t.checks.map(e=>[e.label,e.status,e.detail,e.fix??``]))}}),To=S({meta:{name:`ga4-properties`,description:`List GA4 accounts and properties available to Google OAuth`},args:{json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`},save:{type:`string`,description:`Save this numeric GA4 property ID as the default.`}},run:async({args:e})=>{let t=await ke(),n=t.flatMap(e=>e.propertySummaries.map(t=>({account:e.displayName??e.account,property:_(t.property),displayName:t.displayName??t.property}))),r=E(e.save);if(r){let e=Dt();e.google.defaultGa4PropertyId=r,Ct(e)}if(P(e)){I({accountSummaries:t,properties:n,saved:r});return}r&&process.stdout.write(`Saved default GA4 property ${r}.\n`),R([`Property`,`Name`,`Account`],n.map(e=>[e.property,e.displayName,e.account]))}}),Eo=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},Do=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=P(e),n=Eo(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 ge({site:(await V({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){I(c);return}L([[`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]]),R([`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(`
94
98
  One-window evidence (not treated as zero)
95
- `),L([`Segment`,`Retained in`,`Clicks`,`Impressions`],c.unmatchedSegments.map(e=>[e.key,e.retainedIn,e.clicks,e.impressions]))),J(`Warnings`,c.warnings)}});function wo(e){return`${H(e)} ${e===1?`row`:`rows`}`}const To=x({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.`},...Y([`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=D(e[`verify-content`])===!0||n!==void 0,i=await B({client:E(e),site:T(e.site),options:{json:t,refresh:D(e.refresh)}}),a=await re({site:i.site,days:k(e.days),minImpressions:k(e[`min-impressions`]),limit:k(e.limit),brandTerms:i.client?.brandTerms,includeBrand:D(e[`include-brand`]),verifyContent:r,verifyLimit:n,js:D(e.js)?!0:void 0,rate:qn(e),refresh:D(e.refresh)});if(t){F(a);return}if(I([[`Site`,a.site],[`Eligible rows`,H(a.summary.eligibleRows)],[`Returned rows`,H(a.summary.returnedRows)],[`Template groups`,H(a.summary.groups)],[`Eligible impressions`,H(a.summary.eligibleImpressions)],[`Brand queries`,a.summary.brandFiltering],[`Verification`,a.verification.requested?`requested`:`off`],[`Verdict`,a.summary.verdict]]),J(`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.`]),J(`Recommended actions`,a.recommendations),J(`Report caveats`,a.caveats),a.groups.length&&(K([`Group`,`Rows`,`URLs`,`Impr`,`Avg pos`,`Scope`,`Action`],a.groups.map(e=>[G(e.label,36),H(e.rowCount),H(e.uniqueUrls),H(e.totalImpressions),U(e.impressionWeightedPosition),e.actionScope,G(e.recommendation.action,72)])),q(`Top striking-distance group actions`,a.groups.map(e=>({label:e.label,context:`${wo(e.rowCount)}, ${H(e.totalImpressions)} impressions`,action:e.recommendation.action})))),!a.items.length){process.stdout.write(`No striking-distance opportunities matched these filters.
96
- `);return}K([`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),H(e.impressions),W(e.ctr),U(e.position),e.priority.score,ur(e.contentVerification?.fetchDiagnostics),dr(e.contentVerification?.classification),G(e.recommendation.action,72)])),q(`Top striking-distance actions`,a.items.map(e=>({label:e.query,context:`${e.template.label}, pos ${U(e.position)}, ${H(e.impressions)} impressions`,action:e.recommendation.action})))}});function Eo(e){let t=e.indexed+e.notIndexed+e.unknown+e.warnings;return t?`${e.indexed}/${t} indexed`:`-`}function Do(e){if(!e.samples.length)return`-`;let t=e.medianWordCount===void 0?`words ?`:`${H(e.medianWordCount)} words`,n=e.weakQueryCoverage?`; ${e.weakQueryCoverage} weak coverage`:``;return`${t}; ${e.usable}/${e.samples.length} usable${n}`}function Oo(e){return e===`serp-framing-review`?`wording review`:e===`body-term-review`?`literal term review`:`covered terms`}function ko(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 Ao(t){if(t.checkedQueries)return`${Math.round(t.impressionShare*100)}% impression fit across ${e(t.checkedQueries,`checked query/page row`)}`}function jo(e){return e.startsWith(`theme: `)?`${e.slice(7)}-related`:e===`general`?`broad`:e}function Mo(t){for(let n of t.slice(0,5)){process.stdout.write(`\n${n.signature}\n`),n.evidence.length&&process.stdout.write(` Evidence: ${n.evidence.join(`; `)}.\n`),process.stdout.write(` Shape: ${ko(n.shape)}\n`);let t=Ao(n.metrics.entityFit);t&&process.stdout.write(` Entity fit: ${t}\n`);let r=n.metrics.queryPatterns.slice(0,3).map(e=>`${jo(e.label)} (${H(e.impressions)} impr: ${e.examples.slice(0,2).join(`; `)})`);r.length&&process.stdout.write(` Demand: ${r.join(` | `)}\n`);let i=n.metrics.topQueries.slice(0,3);if(i.length){process.stdout.write(` Top queries:
99
+ `),R([`Segment`,`Retained in`,`Clicks`,`Impressions`],c.unmatchedSegments.map(e=>[e.key,e.retainedIn,e.clicks,e.impressions]))),J(`Warnings`,c.warnings)}});function Oo(e){return`${H(e)} ${e===1?`row`:`rows`}`}const ko=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=P(e),n=k(e[`verify-limit`]),r=O(e[`verify-content`])===!0||n!==void 0,i=await V({client:D(e),site:E(e.site),options:{json:t,refresh:O(e.refresh)}}),a=await ne({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:N(e),refresh:O(e.refresh)});if(t){I(a);return}if(L([[`Site`,a.site],[`Eligible rows`,H(a.summary.eligibleRows)],[`Returned rows`,H(a.summary.returnedRows)],[`Template groups`,H(a.summary.groups)],[`Eligible impressions`,H(a.summary.eligibleImpressions)],[`Brand queries`,a.summary.brandFiltering],[`Verification`,a.verification.requested?`requested`:`off`],[`Verdict`,a.summary.verdict]]),J(`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.`]),J(`Recommended actions`,a.recommendations),J(`Report caveats`,a.caveats),a.groups.length&&(K([`Group`,`Rows`,`URLs`,`Impr`,`Avg pos`,`Scope`,`Action`],a.groups.map(e=>[G(e.label,36),H(e.rowCount),H(e.uniqueUrls),H(e.totalImpressions),U(e.impressionWeightedPosition),e.actionScope,G(e.recommendation.action,72)])),q(`Top striking-distance group actions`,a.groups.map(e=>({label:e.label,context:`${Oo(e.rowCount)}, ${H(e.totalImpressions)} impressions`,action:e.recommendation.action})))),!a.items.length){process.stdout.write(`No striking-distance opportunities matched these filters.
100
+ `);return}K([`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),H(e.impressions),W(e.ctr),U(e.position),e.priority.score,vr(e.contentVerification?.fetchDiagnostics),yr(e.contentVerification?.classification),G(e.recommendation.action,72)])),q(`Top striking-distance actions`,a.items.map(e=>({label:e.query,context:`${e.template.label}, pos ${U(e.position)}, ${H(e.impressions)} impressions`,action:e.recommendation.action})))}});function Ao(e){let t=e.indexed+e.notIndexed+e.unknown+e.warnings;return t?`${e.indexed}/${t} indexed`:`-`}function jo(e){if(!e.samples.length)return`-`;let t=e.medianWordCount===void 0?`words ?`:`${H(e.medianWordCount)} words`,n=e.weakQueryCoverage?`; ${e.weakQueryCoverage} weak coverage`:``;return`${t}; ${e.usable}/${e.samples.length} usable${n}`}function Mo(e){return e===`serp-framing-review`?`wording review`:e===`body-term-review`?`literal term review`:`covered terms`}function No(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 Po(t){if(t.checkedQueries)return`${Math.round(t.impressionShare*100)}% impression fit across ${e(t.checkedQueries,`checked query/page row`)}`}function Fo(e){return e.startsWith(`theme: `)?`${e.slice(7)}-related`:e===`general`?`broad`:e}function Io(t){for(let n of t.slice(0,5)){process.stdout.write(`\n${n.signature}\n`),n.evidence.length&&process.stdout.write(` Evidence: ${n.evidence.join(`; `)}.\n`),process.stdout.write(` Shape: ${No(n.shape)}\n`);let t=Po(n.metrics.entityFit);t&&process.stdout.write(` Entity fit: ${t}\n`);let r=n.metrics.queryPatterns.slice(0,3).map(e=>`${Fo(e.label)} (${H(e.impressions)} impr: ${e.examples.slice(0,2).join(`; `)})`);r.length&&process.stdout.write(` Demand: ${r.join(` | `)}\n`);let i=n.metrics.topQueries.slice(0,3);if(i.length){process.stdout.write(` Top queries:
97
101
  `);for(let e of i)process.stdout.write(` - ${G(e.query,72)} (${H(e.impressions)} impr, pos ${U(e.position)})\n`)}let a=n.crawl.samples.map(e=>e.queryCoverage).filter(e=>!!e).slice(0,3);if(a.length){process.stdout.write(` Sample coverage:
98
- `);for(let e of a){let t=e.missingTerms.length?` missing ${e.missingTerms.slice(0,4).join(`, `)}`:``;process.stdout.write(` - ${Oo(e.classification)}: ${G(e.query,64)} (body ${(e.bodyCoverage*100).toFixed(0)}%${t})\n`)}}let o=n.metrics.entityFit.weakExamples.slice(0,2);if(o.length){process.stdout.write(` Weak entity-fit examples:
99
- `);for(let t of o){let n=t.pathTerms.slice(0,4);process.stdout.write(` - ${G(t.query,64)} -> expected ${e(n.length,`path term`)}: ${n.join(`, `)}\n`)}}process.stdout.write(` Action: ${n.recommendation}\n`)}}const No=x({meta:{name:`pseo`,description:`Programmatic SEO audits that cluster URL patterns and sample template pages`},subCommands:{audit:x({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.`},...Y([`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=ka(e),r=await B({client:E(e),site:T(e.site),options:{json:t,refresh:D(e.refresh)}}),i=await u({site:r.site,...n,brandTerms:n.brandTerms??r.client?.brandTerms,progress:X(!t)});if(t){F(i);return}if(I([[`Property`,i.site],[`Templates`,H(i.summary.templates)],[`GSC pages`,H(i.summary.gscPages)],[`Sitemap URLs`,H(i.summary.sitemapUrls)],[`Clicks`,H(i.summary.clicks)],[`Impressions`,H(i.summary.impressions)],[`Usable crawls`,`${H(i.summary.crawledUrls)}/${H(i.summary.crawlAttempts)}`],[`Inspection responses`,`${H(i.summary.inspectedUrls)}/${H(i.summary.inspectionAttempts)}`]]),J(`Report caveats`,i.caveats),!i.templates.length){process.stdout.write(`No pSEO templates found from GSC/sitemap data.
100
- `);return}K([`Verdict`,`Template`,`URLs`,`Clicks`,`Impr`,`CTR`,`Pos`,`Index`,`Crawl`,`Action`],i.templates.map(e=>[`${e.verdict} (${e.confidence})`,G(e.signature,28),H(e.urlCount),H(e.metrics.clicks),H(e.metrics.impressions),W(e.metrics.ctr),e.metrics.position.toFixed(1),Eo(e.inspection),Do(e.crawl),G(e.recommendation,72)])),Mo(i.templates),i.warnings.length&&process.stdout.write(`Warnings: ${i.warnings.join(`; `)}\n`),ci(`seo export pseo ${r.client?`--project ${JSON.stringify(r.client.id)}`:`--site ${JSON.stringify(r.site)}`}`)}})}});function Po(e){let t=T(e);if(t){if(!Et.includes(t))throw new _(`INVALID_INPUT`,`Unknown report category: ${t}. Choose ${Et.join(`, `)}.`);return t}}async function Fo(e,t){let n=T(e),r=T(t);if(n&&r)throw new _(`INVALID_INPUT`,`Use either --params or --params-file, not both.`);if(!n&&!r)return{};let i=n??await pn(r??``,`utf8`),a;try{a=JSON.parse(i)}catch{throw new _(`INVALID_INPUT`,`Report parameters must be valid JSON.`)}if(!a||typeof a!=`object`||Array.isArray(a))throw new _(`INVALID_INPUT`,`Report parameters must be an object.`);return a}function Io(e){if(!e.isError)return;let t=e.structuredContent;throw new _(t?.error?.code??`INTERNAL_ERROR`,t?.error?.message??`The report could not be completed.`)}const Lo=x({meta:{name:`reports`,description:`Discover and run the complete report catalog`},subCommands:{list:x({meta:{name:`list`,description:`List report ids available to the CLI and MCP server`},args:{category:{type:`string`,description:`Filter by category: ${Et.join(`, `)}.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=ze(Po(e.category));if(N(e)){F({reports:t,categories:Et});return}L([`ID`,`Category`,`Name`,`Description`],t.map(e=>[e.id,e.category,e.name,e.description]))}}),describe:x({meta:{name:`describe`,description:`Show the purpose and parameter schema for one report`},args:{id:{type:`positional`,required:!0,description:"Report id from `seo reports list`."},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=xt(T(e.id)??``);if(N(e)){F({report:t});return}I([[`ID`,t.id],[`Category`,t.category],[`Name`,t.name],[`Description`,t.description],[`Outcome`,t.outcome]]),process.stdout.write(`
102
+ `);for(let e of a){let t=e.missingTerms.length?` missing ${e.missingTerms.slice(0,4).join(`, `)}`:``;process.stdout.write(` - ${Mo(e.classification)}: ${G(e.query,64)} (body ${(e.bodyCoverage*100).toFixed(0)}%${t})\n`)}}let o=n.metrics.entityFit.weakExamples.slice(0,2);if(o.length){process.stdout.write(` Weak entity-fit examples:
103
+ `);for(let t of o){let n=t.pathTerms.slice(0,4);process.stdout.write(` - ${G(t.query,64)} -> expected ${e(n.length,`path term`)}: ${n.join(`, `)}\n`)}}process.stdout.write(` Action: ${n.recommendation}\n`)}}const Lo=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=P(e),n=Na(e),r=await V({client:D(e),site:E(e.site),options:{json:t,refresh:O(e.refresh)}}),i=await u({site:r.site,...n,brandTerms:n.brandTerms??r.client?.brandTerms,progress:ia(!t)});if(t){I(i);return}if(L([[`Property`,i.site],[`Templates`,H(i.summary.templates)],[`GSC pages`,H(i.summary.gscPages)],[`Sitemap URLs`,H(i.summary.sitemapUrls)],[`Clicks`,H(i.summary.clicks)],[`Impressions`,H(i.summary.impressions)],[`Usable crawls`,`${H(i.summary.crawledUrls)}/${H(i.summary.crawlAttempts)}`],[`Inspection responses`,`${H(i.summary.inspectedUrls)}/${H(i.summary.inspectionAttempts)}`]]),J(`Report caveats`,i.caveats),!i.templates.length){process.stdout.write(`No pSEO templates found from GSC/sitemap data.
104
+ `);return}K([`Verdict`,`Template`,`URLs`,`Clicks`,`Impr`,`CTR`,`Pos`,`Index`,`Crawl`,`Action`],i.templates.map(e=>[`${e.verdict} (${e.confidence})`,G(e.signature,28),H(e.urlCount),H(e.metrics.clicks),H(e.metrics.impressions),W(e.metrics.ctr),e.metrics.position.toFixed(1),Ao(e.inspection),jo(e.crawl),G(e.recommendation,72)])),Io(i.templates),i.warnings.length&&process.stdout.write(`Warnings: ${i.warnings.join(`; `)}\n`),ir(`seo export pseo ${r.client?`--project ${JSON.stringify(r.client.id)}`:`--site ${JSON.stringify(r.site)}`}`)}})}});function Ro(e){let t=E(e);if(t){if(!Et.includes(t))throw new v(`INVALID_INPUT`,`Unknown report category: ${t}. Choose ${Et.join(`, `)}.`);return t}}async function zo(e,t){let n=E(e),r=E(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 pn(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 Bo(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.`)}const Vo=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: ${Et.join(`, `)}.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=Re(Ro(e.category));if(P(e)){I({reports:t,categories:Et});return}R([`ID`,`Category`,`Name`,`Description`],t.map(e=>[e.id,e.category,e.name,e.description]))}}),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`."},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=St(E(e.id)??``);if(P(e)){I({report:t});return}L([[`ID`,t.id],[`Category`,t.category],[`Name`,t.name],[`Description`,t.description],[`Outcome`,t.outcome]]),process.stdout.write(`
101
105
  Use when
102
106
  `);for(let e of t.useWhen)process.stdout.write(`- ${e}\n`);process.stdout.write(`
103
107
  Avoid when
@@ -109,34 +113,38 @@ Do not claim
109
113
  Related reports
110
114
  `);for(let e of t.related)process.stdout.write(`- ${e.id}: ${e.reason}\n`)}process.stdout.write(`
111
115
  Parameters (JSON Schema)
112
- `),F(t.inputSchema)}}),run:x({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 rt(T(e.id)??``,await Fo(e.params,e[`params-file`]));if(Io(t),N(e)){F(t.structuredContent??{});return}for(let e of t.content)process.stdout.write(`${e.text}\n`)}})}}),Ro=x({meta:{name:`monthly-report`,description:`Generate a monthly SEO report narrative`},args:{...ta,month:{type:`string`,description:`Report month as YYYY-MM. Defaults to the latest month with final GSC data.`},...Y([`limit`],{limit:{description:`Maximum rows per diagnostic section. Defaults to 10.`}}),...na},run:async({args:e})=>{let t=N(e),n=ia(e),r=await B({client:n.client,site:n.site,options:{json:t,refresh:n.refresh}}),i=await xe({site:r.site,month:T(e.month),limit:k(e.limit),brandTerms:r.client?.brandTerms,...ra(e),progress:X(!t)});if(t){F(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(`
113
- `),ci(`seo export monthly ${a}${o?` --month ${JSON.stringify(o)}`:``}`)}}),zo=x({meta:{name:`report-narrative`,description:`Generate a client-ready SEO narrative from diagnosis data`},args:{...ta,...Y([`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.`},...Y([`limit`],{limit:{description:`Maximum rows per diagnostic section. Defaults to 10.`}}),"change-limit":{type:`string`,description:`Maximum saved changes to measure. Defaults to 5.`},...na},run:async({args:e})=>{let t=N(e),n=ia(e),r=await B({client:n.client,site:n.site,options:{json:t,refresh:n.refresh}}),i=await o({site:r.site,days:k(e.days),recentDays:k(e.recent),startDate:T(e[`start-date`]),endDate:T(e[`end-date`]),limit:k(e.limit),changeLimit:k(e[`change-limit`]),brandTerms:r.client?.brandTerms,...ra(e),progress:X(!t)});if(t){F(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(`
114
- `),ci(`seo export narrative ${a}`)}}),Bo=x({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=d().filter(e=>!t||e.category===t);if(N(e)){F({rules:n});return}L([`ID`,`Severity`,`Category`,`Title`],n.map(e=>[e.id,e.defaultSeverity,e.category,e.title]))}}),Vo=x({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=we(t);if(!n)throw Error(`Unknown rule: ${t}`);if(N(e)){F(n);return}I([[`Rule`,n.id],[`Title`,n.title],[`Category`,n.category],[`Severity`,n.defaultSeverity]]),process.stdout.write(`\nWhy it matters\n${n.whyItMatters}\n`),process.stdout.write(`\nHow to fix\n${n.howToFix}\n`),process.stdout.write(`\nImpact if ignored\n${n.impactIfIgnored}\n`),process.stdout.write(`\nHow to verify\n${n.howToVerify}\n`)}});function Ho(e){return JSON.stringify(e)}const Uo=x({meta:{name:`schedule`,description:`Print local cron entries for recurring SEO workflows`},subCommands:{cron:x({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 B({client:E(e),site:T(e.site),options:{json:t}}),r=n.site,i=T(e.url)??n.client?.startUrl??V(n.site),a=k(e.hour)??9,o=k(e.minute)??0,s=k(e.weekday)??n.client?.technicalWeekday??1,c=k(e.day)??n.client?.reportDay??1,l=n.client?`--project ${Ho(n.client.id)}`:`--site ${Ho(r)}`,u=T(e.urls)??n.client?.watchUrls.join(`,`),d=[{name:`technical-watch`,cron:`${o} ${a} * * ${s}`,command:[`seo technical-watch`,l,i?`--url ${Ho(i)}`:void 0,u?`--urls ${Ho(u)}`:void 0,`--json`].filter(Boolean).join(` `)},{name:`monthly-report`,cron:`${o} ${a} ${c} * *`,command:[`seo monthly-report`,l,`--json`].join(` `)}];if(t){F({site:r,lines:d});return}let f=it();I([[`Property`,r],[`Install`,`crontab -e`]]);for(let e of d)process.stdout.write(`${e.cron} ${e.command} >> ${Ho(`${f.logDir}/${e.name}.log`)} 2>&1\n`)}})}});function Wo(){return Ko(`skills`,process.env.SEO_SKILLS_DIR)}function Go(){return Ko(`evals`,process.env.SEO_EVALS_DIR)}function Ko(e,t){if(t)return nn(t);let n=y(cn(import.meta.url));for(let t=0;t<6;t+=1){let t=b(n,e);if(v(b(t,`README.md`)))return t;n=y(n)}throw new _(`INTERNAL_ERROR`,`Packaged ${e} could not be found. Reinstall \`seo\` and try again.`)}function qo(e){return Wt(b(e,`SKILL.md`),`utf8`).match(/^description:\s*(.+)$/m)?.[1]?.trim()??``}const Jo=`claude -p`;function Yo(){let e=Go();return Gt(e,{withFileTypes:!0}).filter(e=>e.isFile()&&e.name.endsWith(`.json`)).map(t=>({subject:Qt(t.name,`.json`),path:b(e,t.name)})).sort((e,t)=>e.subject<t.subject?-1:+(e.subject>t.subject))}function Xo(e,t){let n;try{n=Wt(t,`utf8`)}catch{throw new _(`INVALID_INPUT`,`Cannot read eval file for ${e}.`)}let r;try{r=JSON.parse(n)}catch{throw new _(`INVALID_INPUT`,`Eval file ${e}.json is not valid JSON.`)}if(!r||typeof r!=`object`||Array.isArray(r))throw new _(`INVALID_INPUT`,`Eval file ${e}.json must be an object.`);let i=r;if(i.subject!==e)throw new _(`INVALID_INPUT`,`Eval file ${e}.json must set subject to "${e}".`);if(!Array.isArray(i.evals)||i.evals.length===0)throw new _(`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 _(`INVALID_INPUT`,`${i}: id must be an integer.`);let o=r.id;if(a.has(o))throw new _(`INVALID_INPUT`,`${i}: duplicate id ${o}.`);if(a.add(o),typeof r.prompt!=`string`||r.prompt.trim().length===0)throw new _(`INVALID_INPUT`,`${i}: prompt must be a non-empty string.`);if(typeof r.expected_output!=`string`||r.expected_output.trim().length===0)throw new _(`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 _(`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 _(`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 Zo(){let e=b(Wo(),`seo`,`SKILL.md`);if(!v(e))throw new _(`INTERNAL_ERROR`,"The seo router skill could not be found. Reinstall `seo` or use --no-skill.");return Wt(e,`utf8`)}function Qo(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===`
115
- `||s===`\r`){r&&=(t.push(n),n=``,!1);continue}n+=s,r=!0}if(i||a)throw new _(`INVALID_INPUT`,`Unbalanced quote in agent command.`);return r&&t.push(n),t}function $o(e,t){let n=Qo(e);if(n.length===0)throw new _(`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 es(e,t){return e?`${e.trim()}\n\n---\n\nUser request:\n${t}`:t}function ts(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(`
116
+ `),I(t.inputSchema)}}),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 nt(E(e.id)??``,await zo(e.params,e[`params-file`]));if(Bo(t),P(e)){I(t.structuredContent??{});return}for(let e of t.content)process.stdout.write(`${e.text}\n`)}})}}),Ho=S({meta:{name:`monthly-report`,description:`Generate a monthly SEO report narrative`},args:{...aa,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.`}}),...oa},run:async({args:e})=>{let t=P(e),n=ca(e),r=await V({client:n.client,site:n.site,options:{json:t,refresh:n.refresh}}),i=await be({site:r.site,month:E(e.month),limit:k(e.limit),brandTerms:r.client?.brandTerms,...sa(e),progress:ia(!t)});if(t){I(i);return}process.stdout.write(`${i.markdown}\n`);let a=r.client?`--project ${JSON.stringify(r.client.id)}`:`--site ${JSON.stringify(r.site)}`,o=E(e.month);process.stdout.write(`
117
+ `),ir(`seo export monthly ${a}${o?` --month ${JSON.stringify(o)}`:``}`)}}),Uo=S({meta:{name:`report-narrative`,description:`Generate a client-ready SEO narrative from diagnosis data`},args:{...aa,...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.`},...oa},run:async({args:e})=>{let t=P(e),n=ca(e),r=await V({client:n.client,site:n.site,options:{json:t,refresh:n.refresh}}),i=await o({site:r.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:r.client?.brandTerms,...sa(e),progress:ia(!t)});if(t){I(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(`
118
+ `),ir(`seo export narrative ${a}`)}}),Wo=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=E(e.category),n=d().filter(e=>!t||e.category===t);if(P(e)){I({rules:n});return}R([`ID`,`Severity`,`Category`,`Title`],n.map(e=>[e.id,e.defaultSeverity,e.category,e.title]))}}),Go=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=E(e.rule)??``,n=Ce(t);if(!n)throw Error(`Unknown rule: ${t}`);if(P(e)){I(n);return}L([[`Rule`,n.id],[`Title`,n.title],[`Category`,n.category],[`Severity`,n.defaultSeverity]]),process.stdout.write(`\nWhy it matters\n${n.whyItMatters}\n`),process.stdout.write(`\nHow to fix\n${n.howToFix}\n`),process.stdout.write(`\nImpact if ignored\n${n.impactIfIgnored}\n`),process.stdout.write(`\nHow to verify\n${n.howToVerify}\n`)}});function Ko(e){return JSON.stringify(e)}const qo=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=P(e),n=await V({client:D(e),site:E(e.site),options:{json:t}}),r=n.site,i=E(e.url)??n.client?.startUrl??Y(n.site),a=k(e.hour)??9,o=k(e.minute)??0,s=k(e.weekday)??n.client?.technicalWeekday??1,c=k(e.day)??n.client?.reportDay??1,l=n.client?`--project ${Ko(n.client.id)}`:`--site ${Ko(r)}`,u=E(e.urls)??n.client?.watchUrls.join(`,`),d=[{name:`technical-watch`,cron:`${o} ${a} * * ${s}`,command:[`seo technical-watch`,l,i?`--url ${Ko(i)}`:void 0,u?`--urls ${Ko(u)}`:void 0,`--json`].filter(Boolean).join(` `)},{name:`monthly-report`,cron:`${o} ${a} ${c} * *`,command:[`seo monthly-report`,l,`--json`].join(` `)}];if(t){I({site:r,lines:d});return}let f=ze();L([[`Property`,r],[`Install`,`crontab -e`]]);for(let e of d)process.stdout.write(`${e.cron} ${e.command} >> ${Ko(`${f.logDir}/${e.name}.log`)} 2>&1\n`)}})}});function Jo(){return Xo(`skills`,process.env.SEO_SKILLS_DIR)}function Yo(){return Xo(`evals`,process.env.SEO_EVALS_DIR)}function Xo(e,t){if(t)return nn(t);let n=b(cn(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 Zo(e){return Wt(x(e,`SKILL.md`),`utf8`).match(/^description:\s*(.+)$/m)?.[1]?.trim()??``}const Qo=`claude -p`;function $o(){let e=Yo();return Gt(e,{withFileTypes:!0}).filter(e=>e.isFile()&&e.name.endsWith(`.json`)).map(t=>({subject:Qt(t.name,`.json`),path:x(e,t.name)})).sort((e,t)=>e.subject<t.subject?-1:+(e.subject>t.subject))}function es(e,t){let n;try{n=Wt(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 ts(){let e=x(Jo(),`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 Wt(e,`utf8`)}function ns(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===`
119
+ `||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 rs(e,t){let n=ns(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 is(e,t){return e?`${e.trim()}\n\n---\n\nUser request:\n${t}`:t}function as(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(`
116
120
  `),`</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(`
117
- `);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 ns(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 rs(e,t){let n=ns(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 is(e,t){return new Promise(n=>{let r=Date.now(),[i,...a]=e,o;try{o=on(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 as(e,t,n){let{argv:r,usesPlaceholder:i}=$o(e,t);return is(r,{input:i?void 0:t,timeoutMs:n})}async function os(e,t){let n=Date.now(),r=es(t.skillBody,e.prompt),i=await as(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:ss(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:ss(e.assertions)};if(a.length===0)return{...o,status:`errored`,error:`Agent returned no output on stdout.`,durationMs:Date.now()-n,assertions:ss(e.assertions)};if(!t.judge)return{...o,status:`unjudged`,durationMs:Date.now()-n,assertions:ss(e.assertions)};let s=0,c=``;for(let r=0;r<2;r+=1){let i=ts(e.prompt,a,e.assertions,r>0),l=await as(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:ss(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:ss(e.assertions)};let u=rs(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:ss(e.assertions)}}function ss(e){return e.map(e=>({assertion:e,verdict:`unjudged`}))}async function cs(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 ls(e){let t={passed:0,failed:0,errored:0,unjudged:0};for(let n of e)t[n.status]+=1;return t}function us(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 ds(e){let t=Yo();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 _(`INVALID_INPUT`,`Unknown eval subject: ${e}.${n?` Known subjects: ${n}.`:``}`)}return r})}function fs(e){let t=Yo().map(e=>{let t=Xo(e.subject,e.path);return{subject:e.subject,evals:t.evals.length}});if(e){F({schemaVersion:1,subjects:t});return}if(t.length===0){process.stdout.write(`No eval subjects found.
121
+ `);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 os(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 ss(e,t){let n=os(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 cs(e,t){return new Promise(n=>{let r=Date.now(),[i,...a]=e,o;try{o=on(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 ls(e,t,n){let{argv:r,usesPlaceholder:i}=rs(e,t);return cs(r,{input:i?void 0:t,timeoutMs:n})}async function us(e,t){let n=Date.now(),r=is(t.skillBody,e.prompt),i=await ls(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:ds(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:ds(e.assertions)};if(a.length===0)return{...o,status:`errored`,error:`Agent returned no output on stdout.`,durationMs:Date.now()-n,assertions:ds(e.assertions)};if(!t.judge)return{...o,status:`unjudged`,durationMs:Date.now()-n,assertions:ds(e.assertions)};let s=0,c=``;for(let r=0;r<2;r+=1){let i=as(e.prompt,a,e.assertions,r>0),l=await ls(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:ds(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:ds(e.assertions)};let u=ss(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:ds(e.assertions)}}function ds(e){return e.map(e=>({assertion:e,verdict:`unjudged`}))}async function fs(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 ps(e){let t={passed:0,failed:0,errored:0,unjudged:0};for(let n of e)t[n.status]+=1;return t}function ms(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 hs(e){let t=$o();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 gs(e){let t=$o().map(e=>{let t=es(e.subject,e.path);return{subject:e.subject,evals:t.evals.length}});if(e){I({schemaVersion:1,subjects:t});return}if(t.length===0){process.stdout.write(`No eval subjects found.
118
122
  `);return}let n=Math.max(...t.map(e=>e.subject.length));process.stdout.write(`Eval subjects
119
- `);for(let e of t){let t=e.evals===1?`eval`:`evals`;process.stdout.write(` ${e.subject.padEnd(n)} ${e.evals} ${t}\n`)}}function ps(e){return e===`passed`?`PASS`:e===`failed`?`FAIL`:e===`errored`?`ERROR`:`UNJUDGED`}function ms(e,t){t.judged?process.stdout.write(`Assertion verdicts below are judge verdicts, model judgments from the judge agent, not ground truth.
123
+ `);for(let e of t){let t=e.evals===1?`eval`:`evals`;process.stdout.write(` ${e.subject.padEnd(n)} ${e.evals} ${t}\n`)}}function _s(e){return e===`passed`?`PASS`:e===`failed`?`FAIL`:e===`errored`?`ERROR`:`UNJUDGED`}function vs(e,t){t.judged?process.stdout.write(`Assertion verdicts below are judge verdicts, model judgments from the judge agent, not ground truth.
120
124
 
121
125
  `):process.stdout.write(`Judging was skipped. Review each agent reply against the assertion checklist by hand.
122
126
 
123
- `);for(let n of e){process.stdout.write(`# ${n.subject}\n`);for(let e of n.evals){if(process.stdout.write(`\n[${ps(e.status)}] eval ${e.id} (${e.durationMs}ms)\n`),process.stdout.write(` prompt: ${gs(e.prompt,100)}\n`),e.error&&process.stdout.write(` error: ${e.error}\n`),!t.judged){process.stdout.write(` reply: ${gs(e.reply,200)}\n`),process.stdout.write(` assertions to review:
124
- `);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=ls(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=hs(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 hs(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 gs(e,t){let n=e.replace(/\s+/g,` `).trim();return n.length>t?`${n.slice(0,t-1)}…`:n}const _s=x({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:Jo,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=N(e);if(e.list===!0){fs(t);return}let n=ds(us(e)),r=k(e.id);if(r!==void 0&&n.length!==1)throw new _(`INVALID_INPUT`,`--id needs exactly one subject. Name the subject to filter.`);let i=k(e.timeout)??300;if(!(i>0))throw new _(`INVALID_INPUT`,`--timeout must be a positive number of seconds.`);let a=Math.max(1,Math.min(4,k(e.concurrency)??1)),o=T(e.agent)??Jo,s=e.judge===!1?null:T(e[`judge-agent`])??o,c=e.skill===!1?null:Zo(),l={agent:o,judge:s,skillBody:c,timeoutMs:i*1e3},u=[];for(let e of n){let n=Xo(e.subject,e.path).evals;if(r!==void 0&&(n=n.filter(e=>e.id===r),n.length===0))throw new _(`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 cs(n,a,e=>os(e,l)),o=ls(i);u.push({subject:e.subject,...o,evals:i})}let d=hs(u),f=d.failed>0||d.errored>0;t?F({schemaVersion:1,agent:o,judge:s,judged:s!==null,skillContext:c!==null,timeoutSeconds:i,concurrency:a,subjects:u,totals:d}):ms(u,{judged:s!==null,agent:o,judge:s}),f&&(process.exitCode=1)}});function vs(){let e=Wo();return Gt(e,{withFileTypes:!0}).filter(t=>t.isDirectory()&&v(b(e,t.name,`SKILL.md`))).map(t=>({name:t.name,description:qo(b(e,t.name)),path:b(e,t.name)})).sort((e,t)=>e.name<t.name?-1:+(e.name>t.name))}function ys(e){let t=vs();if(!e)return t;let n=t.find(t=>t.name===e);if(!n)throw new _(`INVALID_INPUT`,`Unknown skill: ${e}.`);return[n]}const bs=x({meta:{name:`skill`,description:`Inspect the packaged SEO skill and run its evals`},subCommands:{list:x({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=vs();if(N(e)){F({skills:t});return}L([`Skill`,`Description`],t.map(e=>[e.name,e.description]))}}),path:x({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?ys(t)[0]?.path:Wo();N(e)?F({name:t,path:n}):process.stdout.write(`${n}\n`)}}),install:x({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=Ir({quiet:t});if(t){F(n),n.status===`failed`&&(process.exitCode=1);return}if(n.status===`failed`)throw new _(`INTERNAL_ERROR`,`Skill install failed: ${n.error}`);process.stdout.write(`SEO skill installed. Agents that support skills can discover it now.
125
- `)}}),eval:_s}}),xs=x({meta:{name:`privacy`,description:`Show local config, token, cache, and log storage paths`},run:async()=>{let e=ve(),t=bt();I(e.map(e=>[e.label,`${e.path} · ${Qn(e.sizeBytes)} · ${e.mode}`])),process.stdout.write(`
126
- `),I(Object.entries(t.counts).map(([e,t])=>[e,String(t)]))}}),Ss=x({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=Be(),n=N(e);if(!e.yes&&!P({json:n}))throw new _(`INVALID_INPUT`,`Cannot prompt here. Pass --yes to confirm reset.`);if(!(e.yes||R(await xn({message:`Delete config, tokens, cache, and logs?`,initialValue:!1})))){bn(`Reset aborted.`);return}await Ie(),Jt(t.configDir,{recursive:!0,force:!0}),Jt(t.cacheDir,{recursive:!0,force:!0}),Jt(t.logDir,{recursive:!0,force:!0}),n?F({reset:!0}):process.stdout.write(`Reset complete.
127
- `)}}),Cs=x({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 ce(D(e.refresh));if(N(e)){F({sites:t});return}L([`Property`,`Permission`],t.map(e=>[e.siteUrl,e.permissionLevel??`unknown`]))}}),ws=x({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 yt({product:T(e.product),limit:k(e.limit)});if(N(e)){F({updates:t});return}L([`Start`,`End`,`Type`,`Name`,`Status`],t.map(e=>[e.start.slice(0,10),e.end?.slice(0,10)??`open`,e.type,e.name,e.status]))}}),Ts=x({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.`},"ga4-property":{type:`string`,description:`GA4 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=E(e),r=await Os(e),i=Ds(e),a=T(e.site)||n||!i&&!r.length?await B({client:n,site:T(e.site),options:{json:t}}):void 0,o=i??a?.client?.startUrl??(a?.site?V(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 Lt({url:o,projectId:a?.client?.id,site:a?.site,mode:Es(e.mode)??(r.length?`list`:void 0),urls:r,maxPages:k(e[`max-pages`]),maxDepth:k(e[`max-depth`]),concurrency:k(e.concurrency),refresh:D(e.refresh),fetchRate:qn({...e,"fetch-concurrency":e.concurrency}),include:j(e.include),exclude:j(e.exclude),ga4PropertyId:T(e[`ga4-property`])??a?.client?.ga4PropertyId,useSitemap:!O(e,`sitemap`),respectRobots:!O(e,`robots`),checkExternal:!O(e,`external`),js:!!D(e.js),limit:k(e.limit)});if(t){F(s);return}Ua(s),L([`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])),q(`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 Es(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 Ds(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 Os(e){let t=j(e.urls)??[],n=T(e[`urls-file`]);return n&&t.push(...ks(await pn(n,`utf8`))),[...new Set(t)]}function ks(e){return e.split(/[\n,]/).map(e=>e.trim()).filter(e=>e&&!e.startsWith(`#`))}function As(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 js(e){return/^[A-Za-z0-9_./:@-]+$/.test(e)?e:`'${e.replaceAll(`'`,`'\\''`)}'`}function Ms(e){try{return new URL(e).origin}catch{throw new _(`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 Ns(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:`GA4`,status:e.analytics.status,joinedPages:e.analytics.joinedPages,totalPages:e.analytics.totalPages,window:e.analytics.window,warning:e.analytics.warning})]}function Ps(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:Qe(t,{limit:5}),reviewObservations:Je(t,{limit:5})}:{status:e.status,...e.reason?{reason:e.reason}:{}}}function Fs(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 Is(e){return`topFixes`in e?{...e,topFixes:e.topFixes.slice(0,3).map(Fs),reviewObservations:e.reviewObservations.slice(0,3).map(Fs)}:e}function Ls(e){if(!(`topFixes`in e)){process.stdout.write(`\nTechnical crawl evidence: ${e.reason??e.status}\n`);return}let t=e.status===`reused`?`saved`:`new`;process.stdout.write(`\nTechnical crawl evidence (${t})\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`);for(let t of Ns(e.dataSources))process.stdout.write(`${t}\n`);if(!e.topFixes.length){process.stdout.write(`No prioritised technical fixes were found.
128
- `),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(e.searchDataJoined?`
127
+ `);for(let n of e){process.stdout.write(`# ${n.subject}\n`);for(let e of n.evals){if(process.stdout.write(`\n[${_s(e.status)}] eval ${e.id} (${e.durationMs}ms)\n`),process.stdout.write(` prompt: ${bs(e.prompt,100)}\n`),e.error&&process.stdout.write(` error: ${e.error}\n`),!t.judged){process.stdout.write(` reply: ${bs(e.reply,200)}\n`),process.stdout.write(` assertions to review:
128
+ `);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=ps(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=ys(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 ys(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 bs(e,t){let n=e.replace(/\s+/g,` `).trim();return n.length>t?`${n.slice(0,t-1)}…`:n}const xs=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:Qo,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:T(`Judge assertions with an agent.`,`Skip judging and print replies with an assertion checklist.`),skill:T(`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=P(e);if(e.list===!0){gs(t);return}let n=hs(ms(e)),r=k(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=k(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,k(e.concurrency)??1)),o=E(e.agent)??Qo,s=e.judge===!1?null:E(e[`judge-agent`])??o,c=e.skill===!1?null:ts(),l={agent:o,judge:s,skillBody:c,timeoutMs:i*1e3},u=[];for(let e of n){let n=es(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 fs(n,a,e=>us(e,l)),o=ps(i);u.push({subject:e.subject,...o,evals:i})}let d=ys(u),f=d.failed>0||d.errored>0;t?I({schemaVersion:1,agent:o,judge:s,judged:s!==null,skillContext:c!==null,timeoutSeconds:i,concurrency:a,subjects:u,totals:d}):vs(u,{judged:s!==null,agent:o,judge:s}),f&&(process.exitCode=1)}});function Ss(){let e=Jo();return Gt(e,{withFileTypes:!0}).filter(t=>t.isDirectory()&&y(x(e,t.name,`SKILL.md`))).map(t=>({name:t.name,description:Zo(x(e,t.name)),path:x(e,t.name)})).sort((e,t)=>e.name<t.name?-1:+(e.name>t.name))}function Cs(e){let t=Ss();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 ws=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=Ss();if(P(e)){I({skills:t});return}R([`Skill`,`Description`],t.map(e=>[e.name,e.description]))}}),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=E(e.name),n=t?Cs(t)[0]?.path:Jo();P(e)?I({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=P(e),n=Wr({quiet:t});if(t){I(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.
129
+ `)}}),eval:xs}}),Ts=S({meta:{name:`privacy`,description:`Show local config, token, cache, and log storage paths`},run:async()=>{let e=t(),n=re();L(e.map(e=>[e.label,`${e.path} · ${Qn(e.sizeBytes)} · ${e.mode}`])),process.stdout.write(`
130
+ `),L(Object.entries(n.counts).map(([e,t])=>[e,String(t)]))}}),Es=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=gt(),n=P(e);if(!e.yes&&!F({json:n}))throw new v(`INVALID_INPUT`,`Cannot prompt here. Pass --yes to confirm reset.`);if(!(e.yes||z(await xn({message:`Delete config, tokens, cache, and logs?`,initialValue:!1})))){bn(`Reset aborted.`);return}await _e(),Jt(t.configDir,{recursive:!0,force:!0}),Jt(t.cacheDir,{recursive:!0,force:!0}),Jt(t.logDir,{recursive:!0,force:!0}),n?I({reset:!0}):process.stdout.write(`Reset complete.
131
+ `)}}),Ds=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 ye(O(e.refresh));if(P(e)){I({sites:t});return}R([`Property`,`Permission`],t.map(e=>[e.siteUrl,e.permissionLevel??`unknown`]))}}),Os=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 bt({product:E(e.product),limit:k(e.limit)});if(P(e)){I({updates:t});return}R([`Start`,`End`,`Type`,`Name`,`Status`],t.map(e=>[e.start.slice(0,10),e.end?.slice(0,10)??`open`,e.type,e.name,e.status]))}}),ks=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.`},"ga4-property":{type:`string`,description:`GA4 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=P(e),n=D(e),r=await Ms(e),i=js(e),a=E(e.site)||n||!i&&!r.length?await V({client:n,site:E(e.site),options:{json:t}}):void 0,o=i??a?.client?.startUrl??(a?.site?Y(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 Lt({url:o,projectId:a?.client?.id,site:a?.site,mode:As(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:N({...e,"fetch-concurrency":e.concurrency}),include:j(e.include),exclude:j(e.exclude),ga4PropertyId:E(e[`ga4-property`])??a?.client?.ga4PropertyId,useSitemap:!Gn(e,`sitemap`),respectRobots:!Gn(e,`robots`),checkExternal:!Gn(e,`external`),js:!!O(e.js),limit:k(e.limit)});if(t){I(s);return}qa(s),R([`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])),q(`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 As(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 js(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 Ms(e){let t=j(e.urls)??[],n=E(e[`urls-file`]);return n&&t.push(...Ns(await pn(n,`utf8`))),[...new Set(t)]}function Ns(e){return e.split(/[\n,]/).map(e=>e.trim()).filter(e=>e&&!e.startsWith(`#`))}function Ps(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 Fs(e){return/^[A-Za-z0-9_./:@-]+$/.test(e)?e:`'${e.replaceAll(`'`,`'\\''`)}'`}function Is(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 Ls(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:`GA4`,status:e.analytics.status,joinedPages:e.analytics.joinedPages,totalPages:e.analytics.totalPages,window:e.analytics.window,warning:e.analytics.warning})]}function Rs(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:Ze(t,{limit:5}),reviewObservations:qe(t,{limit:5})}:{status:e.status,...e.reason?{reason:e.reason}:{}}}function zs(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 Bs(e){return`topFixes`in e?{...e,topFixes:e.topFixes.slice(0,3).map(zs),reviewObservations:e.reviewObservations.slice(0,3).map(zs)}:e}function Vs(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 Ls(e.dataSources))process.stdout.write(`${t}\n`);if(!e.topFixes.length){process.stdout.write(`No prioritised technical fixes were found.
132
+ `),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?`
133
+ Prioritised technical fixes
134
+ `:e.searchDataJoined?`
129
135
  Technical fixes with search value
130
136
  `:`
131
137
  Technical fixes (no Search Console data joined)
132
- `),e.searchDataJoined?L([`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)])):L([`#`,`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 Rs(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 ${js(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 ${js(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 ${js(t.projectId)}`:`--site ${js(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 ${js(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 ${js(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 zs(e){e.length&&(process.stdout.write(`
138
+ `),e.searchDataJoined?R([`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)])):R([`#`,`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 Hs(e){process.stdout.write(`
139
+ Provider-backed sections skipped
140
+ `);let t=`dataSources`in e?Ls(e.dataSources):[],n=t.length?t:[`Search Console: not connected.`,`GA4: 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 Us(e){if(!(`topFixes`in e))return`Technical crawl evidence was ${e.status}. Search Console and GA4 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 GA4 sections were skipped because they are not connected.`}function Ws(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 Gs(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 ${Fs(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 ${Fs(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 ${Fs(t.projectId)}`:`--site ${Fs(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 ${Fs(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 ${Fs(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 Ks(e){e.length&&(process.stdout.write(`
133
141
  Recommended next commands
134
- `),L([`Command`,`Why`],e.map(e=>[e.command,e.why])))}function Bs(e){process.stdout.write(`# Technical SEO report: ${e}\n\n`),process.stdout.write("Search Console is not connected for this run. The crawl below shows local technical evidence only. Run `seo start` when you want traffic, query, and ranking evidence in the same report.\n")}function Vs(e){return x({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.`},...Y([`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=N(t),r=e.printFollowups&&D(t.full),i=E(t),a=T(t.site),o=e.printFollowups?T(t.url):void 0,s=!!(i||a||!o),c=s?await B({client:i,site:a,options:{json:n,refresh:D(t.refresh)}}):void 0,l=c?.site??Ms(o??``),u=await jt({site:l,days:k(t.days),recentDays:k(t.recent),limit:k(t.limit),brandTerms:c?.client?.brandTerms,includeBrand:D(t[`include-brand`]),refresh:D(t.refresh),skipSearchData:!s}),d=e.workflowName?{...u,workflow:e.workflowName}:u,f=e.printFollowups?await Mt({site:l,searchSite:c?.site,url:o??c?.client?.startUrl??V(l),projectId:c?.client?.id,ga4PropertyId:c?.client?.ga4PropertyId,crawl:!O(t,`crawl`),refresh:D(t.refresh),maxPages:k(t[`crawl-max-pages`]),maxDepth:k(t[`crawl-max-depth`])}):void 0,p=f?Ps(f):void 0,m=e.printFollowups?Rs(d,{crawlStartUrl:o??c?.client?.startUrl??V(l),projectId:c?.client?.id,technicalBaselineStatus:f?.status,searchDataAvailable:s}):void 0;if(n){let t=e.printFollowups&&!r?As(u,e.workflowName):d;F(p||m?{...t,...e.printFollowups?{detail:r?`full`:`summary`}:{},...p?{technicalCrawl:e.printFollowups&&!r?Is(p):p}:{},...m?{nextCommands:m}:{}}:t);return}s?(process.stdout.write(`${d.output.narrative.markdown}\n\n`),Ua(d)):Bs(l),p&&Ls(p),m&&zs(m)}})}const Hs=Vs({name:`diagnose-property`,description:`Find what changed in Google Search and what to inspect next`}),Us=Vs({name:`report`,workflowName:`report`,printFollowups:!0,description:`Run the main SEO report and recommend the next best follow-up commands`}),Ws=x({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.`},...Y([`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.`}}),"ga4-property":{type:`string`,description:`GA4 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=N(e),n=await B({client:E(e),site:T(e.site),options:{json:t,refresh:D(e.refresh)}}),r=await Le({site:n.site,days:k(e.days),recentDays:k(e.recent),limit:k(e.limit),brandTerms:n.client?.brandTerms,includeBrand:D(e[`include-brand`]),ga4PropertyId:T(e[`ga4-property`])??n.client?.ga4PropertyId,verifyContent:D(e[`verify-content`]),verifyLimit:k(e[`verify-limit`]),refresh:D(e.refresh)});if(t){F(r);return}Ua(r),L([`Source`,`Category`,`Score`,`Findings`,`Template`,`GA4`,`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])),q(`Queue action details`,r.output.queue.map(e=>({label:e.target,context:`${e.category}, score ${e.score}`,action:e.action})))}}),Gs=x({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=N(e),n=await B({client:E(e),site:T(e.site),options:{json:t,refresh:D(e.refresh)}}),r=M(e.urls),i=M(e.sitemaps),a=T(e.url)??n.client?.startUrl??V(n.site),o=await $e({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:T(e.language),dailyLimit:k(e[`daily-limit`]),inspectLimit:k(e[`inspect-limit`]),maxUrls:k(e[`max-urls`]),refresh:D(e.refresh),js:D(e.js)?!0:`auto`,recoverLinks:D(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){F(o);return}Wa(o)}});function Ks(e){return e===null?`-`:e.toLocaleString(`en-GB`,{maximumFractionDigits:e>=100?0:1})}function qs(e){return[...e.winners.map(e=>({direction:`winner`,item:e})),...e.losers.map(e=>({direction:`loser`,item:e}))]}function Js(e,t){let n=qs(t);n.length&&(process.stdout.write(`\n${e}\n`),L([`Direction`,`Key`,`Clicks`,`Impr.`,`Position`],n.map(({direction:e,item:t})=>[e,t.key,Ks(t.clickDelta),Ks(t.impressionDelta),Ks(t.positionDelta)])))}function Ys(e){process.stdout.write(`
142
+ `),R([`Command`,`Why`],e.map(e=>[e.command,e.why])))}function qs(e){process.stdout.write(`# Technical SEO report: ${e}\n\n`)}function Js(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:T(`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=P(t),r=e.printFollowups&&O(t.full),i=D(t),a=E(t.site),o=e.printFollowups?E(t.url):void 0,s=!!(i||a||!o),c=s?await V({client:i,site:a,options:{json:n,refresh:O(t.refresh)}}):void 0,l=c?.site??Is(o??``),u=await jt({site:l,days:k(t.days),recentDays:k(t.recent),limit:k(t.limit),brandTerms:c?.client?.brandTerms,includeBrand:O(t[`include-brand`]),refresh:O(t.refresh),skipSearchData:!s}),d=e.workflowName?{...u,workflow:e.workflowName}:u,f=e.printFollowups?await Mt({site:l,searchSite:c?.site,url:o??c?.client?.startUrl??Y(l),projectId:c?.client?.id,ga4PropertyId:c?.client?.ga4PropertyId,crawl:!Gn(t,`crawl`),refresh:O(t.refresh),maxPages:k(t[`crawl-max-pages`]),maxDepth:k(t[`crawl-max-depth`])}):void 0,p=f?Rs(f):void 0,m=e.printFollowups?Gs(d,{crawlStartUrl:o??c?.client?.startUrl??Y(l),projectId:c?.client?.id,technicalBaselineStatus:f?.status,searchDataAvailable:s}):void 0;if(n){let t=e.printFollowups&&!r?Ps(u,e.workflowName):d,n=!s&&p?{...t,summary:Us(p),steps:[Ws(p),...t.steps]}:t;I(p||m?{...n,...e.printFollowups?{detail:r?`full`:`summary`}:{},...p?{technicalCrawl:e.printFollowups&&!r?Bs(p):p}:{},...m?{nextCommands:m}:{}}:t);return}s?(process.stdout.write(`${d.output.narrative.markdown}\n\n`),qa(d)):qs(l),p&&(Vs(p,{providerFree:!s}),s||Hs(p)),m&&Ks(m)}})}const Ys=Js({name:`diagnose-property`,description:`Find what changed in Google Search and what to inspect next`}),Xs=Js({name:`report`,workflowName:`report`,printFollowups:!0,description:`Run the main SEO report and recommend the next best follow-up commands`}),Zs=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.`}}),"ga4-property":{type:`string`,description:`GA4 property ID to use for analytics value. Defaults from the selected project.`},"verify-content":T(`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=P(e),n=await V({client:D(e),site:E(e.site),options:{json:t,refresh:O(e.refresh)}}),r=await Ie({site:n.site,days:k(e.days),recentDays:k(e.recent),limit:k(e.limit),brandTerms:n.client?.brandTerms,includeBrand:O(e[`include-brand`]),ga4PropertyId:E(e[`ga4-property`])??n.client?.ga4PropertyId,verifyContent:O(e[`verify-content`]),verifyLimit:k(e[`verify-limit`]),refresh:O(e.refresh)});if(t){I(r);return}qa(r),R([`Source`,`Category`,`Score`,`Findings`,`Template`,`GA4`,`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])),q(`Queue action details`,r.output.queue.map(e=>({label:e.target,context:`${e.category}, score ${e.score}`,action:e.action})))}}),Qs=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":T(`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=P(e),n=await V({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??Y(n.site),o=await Qe({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){I(o);return}Ja(o)}});function $s(e){return e===null?`-`:e.toLocaleString(`en-GB`,{maximumFractionDigits:e>=100?0:1})}function ec(e){return[...e.winners.map(e=>({direction:`winner`,item:e})),...e.losers.map(e=>({direction:`loser`,item:e}))]}function tc(e,t){let n=ec(t);n.length&&(process.stdout.write(`\n${e}\n`),R([`Direction`,`Key`,`Clicks`,`Impr.`,`Position`],n.map(({direction:e,item:t})=>[e,t.key,$s(t.clickDelta),$s(t.impressionDelta),$s(t.positionDelta)])))}function nc(e){process.stdout.write(`
135
143
  Segment coverage
136
- `),L([`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 Xs(e){e.length&&(process.stdout.write(`
144
+ `),R([`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 rc(e){e.length&&(process.stdout.write(`
137
145
  Template movement
138
- `),L([`Direction`,`Template`,`Pattern confidence`,`URLs`,`Clicks`,`Returned share`,`Common terms`],e.map(e=>[e.direction,e.signature,e.confidence,e.urlCount,Ks(e.clickDelta),`${Math.round(e.movementShare*100)}%`,e.commonTerms.join(`, `)||`-`])),J(`Template actions`,e.map(e=>e.summary)),J(`Template evidence scope`,[`Shares use returned directional page click movement. Pattern confidence applies only to returned matched-retained page segments.`]))}const Zs=x({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.`},...Y([`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 B({client:E(e),site:T(e.site),options:{json:t,refresh:D(e.refresh)}}),r=await tt({site:n.site,days:k(e.days),recentDays:k(e.recent),limit:k(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){F(r);return}I([[`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)]]),J(`Recommended actions`,r.actions.map(e=>`${e.title} (${e.confidence}): ${e.action}`)),J(`Postmortem findings`,r.output.insights.map(e=>e.summary)),J(`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)),Xs(r.output.templateMovement),J(`Update evidence`,r.output.update.evidence),Ys(r.output.segments),Js(`Page winners and losers`,r.output.segments.page),Js(`Query winners and losers`,r.output.segments.query),Js(`Device winners and losers`,r.output.segments.device),Js(`Country winners and losers`,r.output.segments.country),ci(`seo export update-postmortem ${n.client?`--project ${JSON.stringify(n.client.id)}`:`--site ${JSON.stringify(n.site)}`}`)}}),Qs={name:`seo`,version:We},$s=[{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`]]}],ec=[...$s,{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 GA4`],[`seo seo-to-ai-query`,`Convert searches into AI-monitor prompts`]]},{title:`Technical and data`,commands:[[`seo audit-page`,`Audit one page`],[`seo crawl`,`Crawl a site for technical SEO issues`],[`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 ga4-report`,`Run a GA4 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 reset`,`Delete local SEO data`]]}];function tc(e){process.stdout.write(`seo v${Qs.version}\n\n`),process.stdout.write(`Human-friendly SEO reports first, power tools when you need them.
146
+ `),R([`Direction`,`Template`,`Pattern confidence`,`URLs`,`Clicks`,`Returned share`,`Common terms`],e.map(e=>[e.direction,e.signature,e.confidence,e.urlCount,$s(e.clickDelta),`${Math.round(e.movementShare*100)}%`,e.commonTerms.join(`, `)||`-`])),J(`Template actions`,e.map(e=>e.summary)),J(`Template evidence scope`,[`Shares use returned directional page click movement. Pattern confidence applies only to returned matched-retained page segments.`]))}const ic=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=P(e),n=await V({client:D(e),site:E(e.site),options:{json:t,refresh:O(e.refresh)}}),r=await et({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){I(r);return}L([[`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)]]),J(`Recommended actions`,r.actions.map(e=>`${e.title} (${e.confidence}): ${e.action}`)),J(`Postmortem findings`,r.output.insights.map(e=>e.summary)),J(`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)),rc(r.output.templateMovement),J(`Update evidence`,r.output.update.evidence),nc(r.output.segments),tc(`Page winners and losers`,r.output.segments.page),tc(`Query winners and losers`,r.output.segments.query),tc(`Device winners and losers`,r.output.segments.device),tc(`Country winners and losers`,r.output.segments.country),ir(`seo export update-postmortem ${n.client?`--project ${JSON.stringify(n.client.id)}`:`--site ${JSON.stringify(n.site)}`}`)}}),ac={name:`seo`,version:Ue},oc=[{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`]]}],sc=[...oc,{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 GA4`],[`seo seo-to-ai-query`,`Convert searches into AI-monitor prompts`]]},{title:`Technical and data`,commands:[[`seo audit-page`,`Audit one page`],[`seo crawl`,`Crawl a site for technical SEO issues`],[`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 ga4-report`,`Run a GA4 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 reset`,`Delete local SEO data`]]}];function cc(e){process.stdout.write(`seo v${ac.version}\n\n`),process.stdout.write(`Run SEO audits, find what needs fixing, and ship the changes with your agent.
139
147
 
140
148
  `);for(let t of e){process.stdout.write(`${t.title}\n`);let e=Math.max(...t.commands.map(([e])=>e.length));for(let[n,r]of t.commands)process.stdout.write(` ${n.padEnd(e)} ${r}\n`);process.stdout.write(`
141
- `)}process.stdout.write("Use `seo help <command>` or `seo <command> --help` for command help.\n"),process.stdout.write("Use `seo help all` for the longer command list.\n")}const nc=x({meta:{name:`seo`,version:Qs.version,description:`Local-first SEO CLI and MCP server`},subCommands:{init:Na,llms:Ia,auth:tr,mcp:za,okf:eo,doctor:bo,"ga4-properties":xo,privacy:xs,reset:Ss,cache:nr,crawl:di,"crawl-queue":Ts,"crawl-reports":Ei,client:ai,content:li,project:oi,projects:oi,"change-log":Hi,tests:ea,"content-groups":Gi,"crawl-diff":Ba,diagnose:yo,"diagnose-property":Hs,export:Ma,"index-coverage":Ya,"index-watch":Xa,"link-recover":Za,"monthly-report":Ro,monitoring:Ja,"report-narrative":zo,rules:Bo,explain:Vo,"refresh-priorities":Ws,"redirect-trace":Qa,schedule:Uo,skill:bs,start:Zr,setup:Xr,report:Us,reports:Lo,"segment-impact":Co,"striking-distance":To,"technical-watch":Gs,"update-postmortem":Zs,sites:Cs,"gsc-query":Di,"url-inspect":Oi,"ga4-report":ki,updates:ws,"traffic-anomaly":Ni,"update-correlate":Pi,"ai-referrals":to,"ai-readiness":Li,"entity-readiness":zi,"audit-page":mo,"second-page":fo,cannibal:no,"community-intent":ro,decaying:oo,"quick-wins":uo,"page-opportunities":co,perf:vo,"internal-links":so,"ctr-underperformers":io,"query-cluster":lo,"seo-to-ai-query":po,pseo:No},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")}});Zn(Qs);const $=process.argv.slice(2);($.length===0||$.length===1&&[`help`,`--help`,`-h`].includes($[0]??``))&&(tc($s),process.exit(0)),$[0]===`help`&&$[1]===`all`&&(tc(ec),process.exit(0));const rc=$[0]===`help`&&$.length>1?[...$.slice(1),`--help`]:$;rc!==$&&(process.argv=[process.argv[0]??`node`,process.argv[1]??`seo`,...rc]);const ic=rc.some(e=>[`--help`,`-h`].includes(e)),ac=$.length===1&&[`--version`,`-v`].includes($[0]??``);if(ic||ac)await yn(nc);else try{await vn(nc,{rawArgs:rc})}catch(e){let t=Ge(e);$.includes(`--json`)?process.stdout.write(`${JSON.stringify(wt(t),null,2)}\n`):process.stderr.write(`Error: ${t.message}\n`),process.exitCode=t.exitCode}export{};
149
+ `)}process.stdout.write("Use `seo help <command>` or `seo <command> --help` for command help.\n"),process.stdout.write("Use `seo help all` for the longer command list.\n")}const lc=S({meta:{name:`seo`,version:ac.version,description:`Local-first SEO CLI and MCP server`},subCommands:{init:La,llms:Ba,auth:pr,mcp:Ua,okf:io,doctor:wo,"ga4-properties":To,privacy:Ts,reset:Es,cache:mr,crawl:_i,"crawl-queue":ks,"crawl-reports":Mi,client:pi,content:hi,project:mi,projects:mi,"change-log":Gi,tests:ra,"content-groups":Ji,"crawl-diff":Wa,diagnose:Co,"diagnose-property":Ys,export:Ia,"index-coverage":$a,"index-watch":eo,"link-recover":to,"monthly-report":Ho,monitoring:Qa,"report-narrative":Uo,rules:Wo,explain:Go,"refresh-priorities":Zs,"redirect-trace":no,schedule:qo,skill:ws,start:ai,setup:ii,report:Xs,reports:Vo,"segment-impact":Do,"striking-distance":ko,"technical-watch":Qs,"update-postmortem":ic,sites:Ds,"gsc-query":Ni,"url-inspect":Pi,"ga4-report":Fi,updates:Os,"traffic-anomaly":zi,"update-correlate":Bi,"ai-referrals":ao,"agent-readiness":ur,"ai-readiness":sr,"entity-readiness":Hi,"audit-page":vo,"second-page":go,cannibal:oo,"community-intent":so,decaying:uo,"quick-wins":ho,"page-opportunities":po,perf:So,"internal-links":fo,"ctr-underperformers":co,"query-cluster":mo,"seo-to-ai-query":_o,pseo:Lo},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")}});Zn(ac);const $=process.argv.slice(2);($.length===0||$.length===1&&[`help`,`--help`,`-h`].includes($[0]??``))&&(cc(oc),process.exit(0)),$[0]===`help`&&$[1]===`all`&&(cc(sc),process.exit(0));const uc=$[0]===`help`&&$.length>1?[...$.slice(1),`--help`]:$;uc!==$&&(process.argv=[process.argv[0]??`node`,process.argv[1]??`seo`,...uc]);const dc=uc.some(e=>[`--help`,`-h`].includes(e)),fc=$.length===1&&[`--version`,`-v`].includes($[0]??``);if(dc||fc)await yn(lc);else try{await vn(lc,{rawArgs:uc})}catch(e){let t=ut(e);$.includes(`--json`)?process.stdout.write(`${JSON.stringify(We(t),null,2)}\n`):process.stderr.write(`Error: ${t.message}\n`),process.exitCode=t.exitCode}export{};
142
150
  //# sourceMappingURL=cli.js.map