seo 0.8.2 → 0.8.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,127 +1,127 @@
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,An as r,At as i,B as a,Bn as o,Bt as s,C as c,Cn as l,Ct as u,D as d,Dn as ee,Dt as f,E as te,En as p,Et as ne,F as re,Fn as m,Ft as h,G as ie,Gn as ae,Gt as oe,H as se,Hn as ce,Ht as le,I as ue,In as de,It as fe,J as pe,Jt as me,K as he,Kn as ge,Kt as _e,L as ve,Ln as ye,Lt as be,M as xe,Mn as Se,Mt as Ce,N as we,Nn as Te,Nt as Ee,O as De,On as Oe,Ot as ke,P as Ae,Pn as je,Pt as Me,Q as Ne,Qt as Pe,R as Fe,Rn as Ie,Rt as Le,S as Re,Sn as ze,St as Be,T as Ve,Tn as He,Tt as Ue,U as We,Un as g,Ut as Ge,V as Ke,Vn as qe,Vt as Je,W as Ye,Wn as Xe,Wt as Ze,X as Qe,Xt as $e,Y as et,Yt as tt,Z as nt,Zt as rt,_ as it,_n as at,_t as ot,a as st,an as ct,at as lt,b as ut,bn as dt,bt as ft,c as pt,cn as mt,ct as ht,d as gt,dn as _t,dt as vt,en as yt,et as bt,f as xt,fn as St,ft as Ct,g as wt,gn as Tt,gt as Et,h as Dt,hn as Ot,ht as kt,i as At,in as jt,it as Mt,j as Nt,jn as Pt,jt as Ft,k as It,kn as Lt,kt as Rt,l as zt,ln as Bt,lt as Vt,m as Ht,mn as Ut,mt as Wt,nn as Gt,nt as Kt,o as qt,on as Jt,ot as Yt,p as Xt,pn as Zt,pt as Qt,q as $t,qn as en,qt as tn,rn as nn,rt as rn,sn as an,st as on,t as sn,tn as cn,tt as ln,u as un,un as dn,ut as fn,v as pn,vn as mn,vt as hn,w as gn,wn as _n,wt as vn,x as yn,xn as bn,xt as xn,y as Sn,yn as Cn,yt as wn,z as Tn,zn as En,zt as Dn}from"./dist-DkegfPAq.js";import{arch as On,homedir as kn,platform as An}from"node:os";import{basename as jn,dirname as Mn,join as _,posix as Nn,resolve as Pn,win32 as Fn}from"node:path";import{constants as In,copyFileSync as Ln,createWriteStream as Rn,existsSync as v,mkdirSync as zn,readFileSync as Bn,readdirSync as Vn,realpathSync as Hn,renameSync as Un,statSync as Wn,writeFileSync as Gn}from"node:fs";import{fileURLToPath as Kn}from"node:url";import{mkdir as qn,readFile as Jn,writeFile as Yn}from"node:fs/promises";import{isDeepStrictEqual as Xn,stripVTControlCharacters as Zn}from"node:util";import{spawn as Qn,spawnSync as $n}from"node:child_process";import{defineCommand as y,runCommand as er,runMain as tr}from"citty";import{cancel as nr,confirm as rr,intro as b,isCancel as ir,log as ar,multiselect as or,note as x,outro as S,password as C,select as sr,text as cr}from"@clack/prompts";import lr from"picocolors";import ur from"string-width";import dr from"cli-table3";import{finished as fr}from"node:stream/promises";import{applyEdits as pr,modify as mr,parse as hr,printParseErrorCode as gr}from"jsonc-parser";import _r from"update-notifier";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 vr(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 yr(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 br(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:vr(e,t)})),...[`page`,`query`,`device`,`country`].map(t=>({filename:`postmortem-segment-${t}-unmatched.csv`,headers:[...w.unmatchedSegment],rows:yr(e,t)}))]}function xr(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 Sr(e,t){return{type:`boolean`,default:!0,description:e,negativeDescription:t}}function T(e){return typeof e==`string`?e:void 0}function E(e){return typeof e==`boolean`?e:void 0}function Cr(e){let t=T(e.rendering);if(t!==void 0&&t!==`auto`&&t!==`on`&&t!==`off`)throw new g(`INVALID_INPUT`,`--rendering must be auto, on, or off.`);if(E(e.js)===!0&&t&&t!==`on`)throw new g(`INVALID_INPUT`,`Use either --js or --rendering on, not both with different modes.`);return t??(E(e.js)?`on`:`auto`)}function wr(e,t){let n=E(e[t]);if(n===!1)return!0;if(n===!0)return!1;let r=E(e[Tr(t)]);if(r===!1)return!0;if(r===!0)return!1;if(E(e[`no-${t}`])===!0||E(e[`no${Er(t)}`])===!0)return!0}function Tr(e){return e.replace(/-([a-z])/g,(e,t)=>t.toUpperCase())}function Er(e){let t=Tr(e);return t?`${t[0]?.toUpperCase()}${t.slice(1)}`:``}function D(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 O(e,t){if(e===void 0||e===``)return;let n=D(e);if(n===void 0)throw new g(`INVALID_INPUT`,`${t} must be a number.`);return n}function k(e){if(typeof e!=`string`)return;let t=e.split(`,`).map(e=>e.trim()).filter(Boolean);return t.length?t:void 0}function Dr(e){return k(e)??[]}function Or(e){let t={concurrency:D(e[`fetch-concurrency`]),intervalCap:D(e[`fetch-interval-cap`]),intervalMs:D(e[`fetch-interval-ms`])};return Object.values(t).some(e=>e!==void 0)?t:void 0}function A(e){return e.json===!0}async function kr(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 Jn(r??``,`utf8`),a=JSON.parse(i);if(!a||typeof a!=`object`||Array.isArray(a))throw Error(`JSON body must be an object.`);return a}function j(e){if(e)return e;throw new g(`INVALID_INPUT`,"No site given. Pass --site with a domain, URL or sc-domain: property. Run `seo sites` to list your Search Console properties.")}async function Ar(e,t={}){let n=j(e),r=En((await $e(t.refresh,t.account)).map(e=>e.siteUrl),n);if(!r)throw new g(`PROPERTY_NOT_FOUND`,`This Google login has no Search Console property for ${n}. Run \`seo sites\` to list the properties it can read.`);return r}function jr(e){if(e)return e;throw new g(`INVALID_INPUT`,"No Google Analytics property given. Pass --property with the numeric property id. Run `seo analytics google properties` to list them.")}function Mr(e){if(typeof e==`number`)return Number.isFinite(e)?Math.round(e):void 0;if(!e)return;let t=Number.parseInt(e,10);return Number.isFinite(t)?t:void 0}function Nr(e={}){let t=e.env??process.env,n=Mr(e.columns)??Mr(e.streamColumns)??Mr(t.COLUMNS)??80;return Math.min(200,Math.max(40,n))}function M(e={}){let t=e.env??process.env,n=e.isTTY??!!process.stdout.isTTY,r=e.color??!!(n&&t.NO_COLOR===void 0);return{columns:Nr({...e,env:t,streamColumns:e.streamColumns??process.stdout.columns}),colors:lr.createColors(r),hasColor:r,isInteractive:n&&!t.CI}}function N(e){return ur(Zn(e))}function Pr(e,t){let n=[],r=``;for(let i of e)r&&N(`${r}${i}`)>t?(n.push(r),r=i):r+=i;return r&&n.push(r),n}function Fr(e,t){if(!e)return[``];let n=e.trim().split(/\s+/u),r=[],i=``;for(let e of n){let n=N(e)>t?Pr(e,t):[e];for(let e of n)i?N(`${i} ${e}`)<=t?i+=` ${e}`:(r.push(i),i=e):i=e}return i&&r.push(i),r.length>0?r:[``]}function P(e,t){let n=Math.max(1,t);return e.replaceAll(`\r
3
+ import{$ as e,$t as t,A as n,An as r,At as i,B as a,Bn as o,Bt as s,C as c,Cn as l,Ct as u,D as d,Dn as ee,Dt as te,E as ne,En as re,Et as f,F as ie,Fn as p,Ft as m,G as ae,Gn as oe,Gt as se,H as ce,Hn as le,Ht as ue,I as de,In as fe,It as pe,J as me,Jn as he,Jt as ge,K as _e,Kn as ve,Kt as ye,L as be,Ln as xe,Lt as Se,M as Ce,Mn as we,Mt as Te,N as Ee,Nn as De,Nt as Oe,O as ke,On as Ae,Ot as je,P as Me,Pn as Ne,Pt as Pe,Q as Fe,Qt as Ie,R as Le,Rn as Re,Rt as ze,S as Be,Sn as Ve,St as He,T as Ue,Tn as We,Tt as Ge,U as Ke,Un as qe,Ut as Je,V as Ye,Vn as Xe,Vt as Ze,W as Qe,Wn as h,Wt as $e,X as et,Xt as tt,Y as nt,Yt as rt,Z as it,Zt as at,_ as ot,_n as st,_t as ct,a as lt,an as ut,at as dt,b as ft,bn as pt,bt as mt,c as ht,cn as gt,ct as _t,d as vt,dn as yt,dt as bt,en as xt,et as St,f as Ct,fn as wt,ft as Tt,g as Et,gn as Dt,gt as Ot,h as kt,hn as At,ht as jt,i as Mt,in as Nt,it as Pt,j as Ft,jn as It,jt as Lt,k as Rt,kn as zt,kt as Bt,l as Vt,ln as Ht,lt as Ut,m as Wt,mn as Gt,mt as Kt,nn as qt,nt as Jt,o as Yt,on as Xt,ot as Zt,p as Qt,pn as $t,pt as en,q as tn,qn as nn,qt as rn,rn as an,rt as on,sn,st as cn,t as ln,tn as un,tt as dn,u as fn,un as pn,ut as mn,v as hn,vn as gn,vt as _n,w as vn,wn as yn,wt as bn,x as xn,xn as Sn,xt as Cn,y as wn,yn as Tn,yt as En,z as Dn,zn as On,zt as kn}from"./dist-B8MTNp_F.js";import{arch as An,homedir as jn,platform as Mn}from"node:os";import{basename as Nn,dirname as Pn,join as g,posix as Fn,resolve as In,win32 as Ln}from"node:path";import{constants as Rn,copyFileSync as zn,createWriteStream as Bn,existsSync as _,mkdirSync as Vn,readFileSync as Hn,readdirSync as Un,realpathSync as Wn,renameSync as Gn,statSync as Kn,writeFileSync as qn}from"node:fs";import{fileURLToPath as Jn}from"node:url";import{mkdir as Yn,readFile as Xn,writeFile as Zn}from"node:fs/promises";import{isDeepStrictEqual as Qn,stripVTControlCharacters as $n}from"node:util";import{spawn as er,spawnSync as tr}from"node:child_process";import{defineCommand as v,runCommand as nr,runMain as rr}from"citty";import{cancel as ir,confirm as ar,intro as y,isCancel as or,log as sr,multiselect as cr,note as b,outro as x,password as lr,select as ur,text as dr}from"@clack/prompts";import fr from"picocolors";import pr from"string-width";import mr from"cli-table3";import{finished as hr}from"node:stream/promises";import{applyEdits as gr,modify as _r,parse as vr,printParseErrorCode as yr}from"jsonc-parser";import br from"update-notifier";const S={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 xr(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 Sr(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 Cr(e){return[{filename:`postmortem-summary.csv`,headers:[...S.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:[...S.steps],rows:e.steps.map((e,t)=>({rank:t+1,tool:e.tool,status:e.status,summary:e.summary}))},{filename:`workflow-actions.csv`,headers:[...S.actions],rows:e.actions.map((e,t)=>({rank:t+1,title:e.title,confidence:e.confidence,action:e.action}))},{filename:`postmortem-findings.csv`,headers:[...S.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:[...S.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:[...S.evidence],rows:e.output.update.evidence.map((e,t)=>({rank:t+1,evidence:e}))},{filename:`postmortem-confounders.csv`,headers:[...S.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:[...S.segment],rows:xr(e,t)})),...[`page`,`query`,`device`,`country`].map(t=>({filename:`postmortem-segment-${t}-unmatched.csv`,headers:[...S.unmatchedSegment],rows:Sr(e,t)}))]}function wr(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 Tr(e,t){return{type:`boolean`,default:!0,description:e,negativeDescription:t}}function C(e){return typeof e==`string`?e:void 0}function w(e){return typeof e==`boolean`?e:void 0}function Er(e){let t=C(e.rendering);if(t!==void 0&&t!==`auto`&&t!==`on`&&t!==`off`)throw new h(`INVALID_INPUT`,`--rendering must be auto, on, or off.`);if(w(e.js)===!0&&t&&t!==`on`)throw new h(`INVALID_INPUT`,`Use either --js or --rendering on, not both with different modes.`);return t??(w(e.js)?`on`:`auto`)}function Dr(e,t){let n=w(e[t]);if(n===!1)return!0;if(n===!0)return!1;let r=w(e[Or(t)]);if(r===!1)return!0;if(r===!0)return!1;if(w(e[`no-${t}`])===!0||w(e[`no${kr(t)}`])===!0)return!0}function Or(e){return e.replace(/-([a-z])/g,(e,t)=>t.toUpperCase())}function kr(e){let t=Or(e);return t?`${t[0]?.toUpperCase()}${t.slice(1)}`:``}function T(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 E(e,t){if(e===void 0||e===``)return;let n=T(e);if(n===void 0)throw new h(`INVALID_INPUT`,`${t} must be a number.`);return n}function D(e){if(typeof e!=`string`)return;let t=e.split(`,`).map(e=>e.trim()).filter(Boolean);return t.length?t:void 0}function Ar(e){return D(e)??[]}function jr(e){let t={concurrency:T(e[`fetch-concurrency`]),intervalCap:T(e[`fetch-interval-cap`]),intervalMs:T(e[`fetch-interval-ms`])};return Object.values(t).some(e=>e!==void 0)?t:void 0}function O(e){return e.json===!0}async function Mr(e,t){let n=C(e),r=C(t);if(n&&r)throw Error(`Use either --body or --body-file, not both.`);if(!n&&!r)return;let i=n??await Xn(r??``,`utf8`),a=JSON.parse(i);if(!a||typeof a!=`object`||Array.isArray(a))throw Error(`JSON body must be an object.`);return a}function k(e){if(e)return e;throw new h(`INVALID_INPUT`,"No site given. Pass --site with a domain, URL or sc-domain: property. Run `seo sites` to list your Search Console properties.")}async function Nr(e,t={}){let n=k(e),r=o((await at(t.refresh,t.account)).map(e=>e.siteUrl),n);if(!r)throw new h(`PROPERTY_NOT_FOUND`,`This Google login has no Search Console property for ${n}. Run \`seo sites\` to list the properties it can read.`);return r}function Pr(e){if(e)return e;throw new h(`INVALID_INPUT`,"No Google Analytics property given. Pass --property with the numeric property id. Run `seo analytics google properties` to list them.")}function Fr(e){if(typeof e==`number`)return Number.isFinite(e)?Math.round(e):void 0;if(!e)return;let t=Number.parseInt(e,10);return Number.isFinite(t)?t:void 0}function Ir(e={}){let t=e.env??process.env,n=Fr(e.columns)??Fr(e.streamColumns)??Fr(t.COLUMNS)??80;return Math.min(200,Math.max(40,n))}function A(e={}){let t=e.env??process.env,n=e.isTTY??!!process.stdout.isTTY,r=e.color??!!(n&&t.NO_COLOR===void 0);return{columns:Ir({...e,env:t,streamColumns:e.streamColumns??process.stdout.columns}),colors:fr.createColors(r),hasColor:r,isInteractive:n&&!t.CI}}function j(e){return pr($n(e))}function Lr(e,t){let n=[],r=``;for(let i of e)r&&j(`${r}${i}`)>t?(n.push(r),r=i):r+=i;return r&&n.push(r),n}function Rr(e,t){if(!e)return[``];let n=e.trim().split(/\s+/u),r=[],i=``;for(let e of n){let n=j(e)>t?Lr(e,t):[e];for(let e of n)i?j(`${i} ${e}`)<=t?i+=` ${e}`:(r.push(i),i=e):i=e}return i&&r.push(i),r.length>0?r:[``]}function M(e,t){let n=Math.max(1,t);return e.replaceAll(`\r
4
4
  `,`
5
5
  `).split(`
6
- `).flatMap(e=>Fr(e,n))}function Ir(e,t){if(N(e)<=t)return e;let n=Math.max(1,t-N(`...`)),r=``;for(let t of e){if(N(`${r}${t}`)>n)break;r+=t}return`${r}...`}const Lr={top:``,"top-mid":``,"top-left":``,"top-right":``,bottom:``,"bottom-mid":``,"bottom-left":``,"bottom-right":``,left:``,"left-mid":``,mid:``,"mid-mid":``,right:``,"right-mid":``,middle:``};function Rr(e){return e===void 0?``:String(e)}function zr(e,t){return e.map((e,n)=>{let r=t.map(e=>Rr(e[n]));return Math.min(42,Math.max(N(e),...r.map(N),4))})}function Br(e,t,n){return e.length===0||e.length>4||!e.map((e,n)=>t.every(e=>/^-?(?:\d+(?:\.\d+)?|\d+(?:\.\d+)?%)$/u.test(Rr(e[n]).trim()))).some(Boolean)?!1:zr(e,t).reduce((e,t)=>e+t+2,0)<=n.columns}function Vr(e,t,n){if(!/(?:status|state|assessment|data|allowed)/iu.test(e))return t;let r=t.trim().toLowerCase();return[`pass`,`passed`,`completed`,`allowed`,`yes`].includes(r)?n.colors.green(t):[`warn`,`warning`,`review`,`partial`].includes(r)?n.colors.yellow(t):[`fail`,`failed`,`blocked`,`no`].includes(r)?n.colors.red(t):[`unknown`,`unavailable`].includes(r)?n.colors.blue(t):t}function Hr(e,t,n){let r=zr(e,t).map(e=>e+2),i=new dr({chars:Lr,colWidths:r,head:e.map(e=>n.colors.bold(e)),style:{border:[],head:[],"padding-left":0,"padding-right":2},wordWrap:!1});for(let a of t)i.push(e.map((e,t)=>P(Vr(e,Rr(a[t]),n),Math.max(1,(r[t]??3)-2)).join(`
7
- `)));return i.toString()}function Ur(e,t,n,r){let i=r.columns-n-2;return i<24?[r.colors.dim(e),...P(t,r.columns-2).map(e=>` ${e}`)]:P(t,i).map((t,i)=>{let a=i===0?e.padEnd(n):``.padEnd(n);return`${r.colors.dim(a)}${` `.repeat(2)}${t}`})}function Wr(e,t,n,r={}){let i=Math.min(28,Math.max(...e.map(e=>N(e)),0)),a=r.rowNumbers??!1,o=String(t.length).length;return t.map((t,r)=>{let s=[];a&&s.push(n.colors.bold(String(r+1).padStart(o,`0`)));for(let[r,a]of e.entries())s.push(...Ur(a,Vr(a,Rr(t[r]),n),i,n));return s.join(`
6
+ `).flatMap(e=>Rr(e,n))}function zr(e,t){if(j(e)<=t)return e;let n=Math.max(1,t-j(`...`)),r=``;for(let t of e){if(j(`${r}${t}`)>n)break;r+=t}return`${r}...`}const Br={top:``,"top-mid":``,"top-left":``,"top-right":``,bottom:``,"bottom-mid":``,"bottom-left":``,"bottom-right":``,left:``,"left-mid":``,mid:``,"mid-mid":``,right:``,"right-mid":``,middle:``};function Vr(e){return e===void 0?``:String(e)}function Hr(e,t){return e.map((e,n)=>{let r=t.map(e=>Vr(e[n]));return Math.min(42,Math.max(j(e),...r.map(j),4))})}function Ur(e,t,n){return e.length===0||e.length>4||!e.map((e,n)=>t.every(e=>/^-?(?:\d+(?:\.\d+)?|\d+(?:\.\d+)?%)$/u.test(Vr(e[n]).trim()))).some(Boolean)?!1:Hr(e,t).reduce((e,t)=>e+t+2,0)<=n.columns}function Wr(e,t,n){if(!/(?:status|state|assessment|data|allowed)/iu.test(e))return t;let r=t.trim().toLowerCase();return[`pass`,`passed`,`completed`,`allowed`,`yes`].includes(r)?n.colors.green(t):[`warn`,`warning`,`review`,`partial`].includes(r)?n.colors.yellow(t):[`fail`,`failed`,`blocked`,`no`].includes(r)?n.colors.red(t):[`unknown`,`unavailable`].includes(r)?n.colors.blue(t):t}function Gr(e,t,n){let r=Hr(e,t).map(e=>e+2),i=new mr({chars:Br,colWidths:r,head:e.map(e=>n.colors.bold(e)),style:{border:[],head:[],"padding-left":0,"padding-right":2},wordWrap:!1});for(let a of t)i.push(e.map((e,t)=>M(Wr(e,Vr(a[t]),n),Math.max(1,(r[t]??3)-2)).join(`
7
+ `)));return i.toString()}function Kr(e,t,n,r){let i=r.columns-n-2;return i<24?[r.colors.dim(e),...M(t,r.columns-2).map(e=>` ${e}`)]:M(t,i).map((t,i)=>{let a=i===0?e.padEnd(n):``.padEnd(n);return`${r.colors.dim(a)}${` `.repeat(2)}${t}`})}function qr(e,t,n,r={}){let i=Math.min(28,Math.max(...e.map(e=>j(e)),0)),a=r.rowNumbers??!1,o=String(t.length).length;return t.map((t,r)=>{let s=[];a&&s.push(n.colors.bold(String(r+1).padStart(o,`0`)));for(let[r,a]of e.entries())s.push(...Kr(a,Wr(a,Vr(t[r]),n),i,n));return s.join(`
8
8
  `)}).join(`
9
9
 
10
- `)}function Gr(e,t,n,r={}){return t.length===0?n.colors.dim(`No results.`):r.layout===`table`||r.layout!==`records`&&Br(e,t,n)?Hr(e,t,n):Wr(e,t,n,r)}function Kr(e,t){if(e.length===0)return``;let n=Math.min(28,Math.max(...e.map(([e])=>N(e))));return e.flatMap(([e,r])=>{let i=Ur(e,r,n,t);return i.length>0&&(i[0]=(i[0]??``).replace(e,t.colors.bold(e))),i}).join(`
11
- `)}function qr(e,t){let n=[t.colors.bold(e.title)];return e.body&&n.push(...P(e.body,t.columns)),e.command&&n.push(t.colors.cyan(Ir(e.command,t.columns))),n.join(`
12
- `)}function Jr(e,t){return e===`pass`?t.colors.green:e===`warning`?t.colors.yellow:e===`fail`?t.colors.red:e===`unknown`?t.colors.blue:e===`info`?t.colors.cyan:t.colors.dim}function Yr(e){return e===`warning`?`WARN`:e===`notApplicable`?`N/A`:e.toUpperCase()}function Xr(e,t,n){let r=` `.repeat(t);return P(e,Math.max(1,n-t)).map(e=>`${r}${e}`)}function Zr(e,t){let n=Jr(e.status,t)(`[${Yr(e.status)}]`.padEnd(10)),r=Math.max(1,t.columns-10),i=P(e.title,r).map((e,r)=>`${r===0?n:` `.repeat(10)}${t.colors.bold(e)}`);i.push(...Xr(e.explanation,10,t.columns));for(let n of e.evidence??[]){let e=P(n,Math.max(1,t.columns-20));i.push(...e.map((e,n)=>`${` `.repeat(10)}${n===0?`${t.colors.dim(`Evidence`)} `:` `}${e}`))}if(e.fix){let n=P(e.fix,Math.max(1,t.columns-15));i.push(...n.map((e,n)=>`${` `.repeat(10)}${n===0?`${t.colors.cyan(t.colors.bold(`Fix`))} `:` `}${e}`))}return i.join(`
13
- `)}function Qr(e,t){return P(e.map(e=>{let n=String(e.value),r=e.status?Jr(e.status,t)(n):t.colors.bold(n);return`${e.label} ${r}`}).join(t.colors.dim(` · `)),t.columns).join(`
14
- `)}function $r(e,t){let n=Jr(e.status,t),r=[t.colors.bold(e.title)];e.target&&r.push(t.colors.dim(e.target)),r.push(``),r.push(`${n(Yr(e.status))} ${P(e.summary,Math.max(1,t.columns-6)).join(`
15
- `)}`),e.metrics?.length&&r.push(Qr(e.metrics,t));for(let n of e.sections.filter(e=>e.diagnostics.length>0))r.push(``,t.colors.bold(n.title)),r.push(n.diagnostics.map(e=>Zr(e,t)).join(`
10
+ `)}function Jr(e,t,n,r={}){return t.length===0?n.colors.dim(`No results.`):r.layout===`table`||r.layout!==`records`&&Ur(e,t,n)?Gr(e,t,n):qr(e,t,n,r)}function Yr(e,t){if(e.length===0)return``;let n=Math.min(28,Math.max(...e.map(([e])=>j(e))));return e.flatMap(([e,r])=>{let i=Kr(e,r,n,t);return i.length>0&&(i[0]=(i[0]??``).replace(e,t.colors.bold(e))),i}).join(`
11
+ `)}function Xr(e,t){let n=[t.colors.bold(e.title)];return e.body&&n.push(...M(e.body,t.columns)),e.command&&n.push(t.colors.cyan(zr(e.command,t.columns))),n.join(`
12
+ `)}function Zr(e,t){return e===`pass`?t.colors.green:e===`warning`?t.colors.yellow:e===`fail`?t.colors.red:e===`unknown`?t.colors.blue:e===`info`?t.colors.cyan:t.colors.dim}function Qr(e){return e===`warning`?`WARN`:e===`notApplicable`?`N/A`:e.toUpperCase()}function $r(e,t,n){let r=` `.repeat(t);return M(e,Math.max(1,n-t)).map(e=>`${r}${e}`)}function ei(e,t){let n=Zr(e.status,t)(`[${Qr(e.status)}]`.padEnd(10)),r=Math.max(1,t.columns-10),i=M(e.title,r).map((e,r)=>`${r===0?n:` `.repeat(10)}${t.colors.bold(e)}`);i.push(...$r(e.explanation,10,t.columns));for(let n of e.evidence??[]){let e=M(n,Math.max(1,t.columns-20));i.push(...e.map((e,n)=>`${` `.repeat(10)}${n===0?`${t.colors.dim(`Evidence`)} `:` `}${e}`))}if(e.fix){let n=M(e.fix,Math.max(1,t.columns-15));i.push(...n.map((e,n)=>`${` `.repeat(10)}${n===0?`${t.colors.cyan(t.colors.bold(`Fix`))} `:` `}${e}`))}return i.join(`
13
+ `)}function ti(e,t){return M(e.map(e=>{let n=String(e.value),r=e.status?Zr(e.status,t)(n):t.colors.bold(n);return`${e.label} ${r}`}).join(t.colors.dim(` · `)),t.columns).join(`
14
+ `)}function ni(e,t){let n=Zr(e.status,t),r=[t.colors.bold(e.title)];e.target&&r.push(t.colors.dim(e.target)),r.push(``),r.push(`${n(Qr(e.status))} ${M(e.summary,Math.max(1,t.columns-6)).join(`
15
+ `)}`),e.metrics?.length&&r.push(ti(e.metrics,t));for(let n of e.sections.filter(e=>e.diagnostics.length>0))r.push(``,t.colors.bold(n.title)),r.push(n.diagnostics.map(e=>ei(e,t)).join(`
16
16
 
17
- `));if(e.notes?.length){r.push(``,t.colors.bold(`Notes`));for(let n of e.notes){let e=P(n,Math.max(1,t.columns-2));r.push(...e.map((e,t)=>`${t===0?`- `:` `}${e}`))}}return r.join(`
18
- `)}function ei(e,t,n=`${t}s`){return e===1?t:n}function ti(e,t,n){let r=` `.repeat(t);return P(e,Math.max(1,n-t)).map(e=>`${r}${e}`)}function ni(e,t,n){return[t.colors.bold(e),...n?P(n,t.columns):[]].join(`
19
- `)}function ri(e,t){let n={pass:t.colors.green,warn:t.colors.yellow,fail:t.colors.red};return e.map(e=>{let r=[`${n[e.status](e.status.toUpperCase().padEnd(4))} ${t.colors.bold(e.label)}`];if(r.push(...ti(e.detail,6,t.columns)),e.fix){let n=P(e.fix,Math.max(1,t.columns-11));r.push(...n.map((e,n)=>`${` `.repeat(6)}${n===0?`${t.colors.bold(`Fix`)} `:` `}${e}`))}return r.join(`
17
+ `));if(e.notes?.length){r.push(``,t.colors.bold(`Notes`));for(let n of e.notes){let e=M(n,Math.max(1,t.columns-2));r.push(...e.map((e,t)=>`${t===0?`- `:` `}${e}`))}}return r.join(`
18
+ `)}function ri(e,t,n=`${t}s`){return e===1?t:n}function ii(e,t,n){let r=` `.repeat(t);return M(e,Math.max(1,n-t)).map(e=>`${r}${e}`)}function ai(e,t,n){return[t.colors.bold(e),...n?M(n,t.columns):[]].join(`
19
+ `)}function oi(e,t){let n={pass:t.colors.green,warn:t.colors.yellow,fail:t.colors.red};return e.map(e=>{let r=[`${n[e.status](e.status.toUpperCase().padEnd(4))} ${t.colors.bold(e.label)}`];if(r.push(...ii(e.detail,6,t.columns)),e.fix){let n=M(e.fix,Math.max(1,t.columns-11));r.push(...n.map((e,n)=>`${` `.repeat(6)}${n===0?`${t.colors.bold(`Fix`)} `:` `}${e}`))}return r.join(`
20
20
  `)}).join(`
21
21
 
22
- `)}function ii(e){let t=e.filter(e=>e.status===`pass`).length,n=e.filter(e=>e.status===`warn`).length,r=e.filter(e=>e.status===`fail`).length;return`${t} ${ei(t,`check`)} passed, ${n} ${ei(n,`warning`)}, ${r} failed.`}function ai(e,t,n){let r=n.columns-t-2-2;return r<24||N(e.id)>t?[...ti(e.id,2,n.columns).map(e=>n.colors.cyan(e)),...ti(e.name,4,n.columns)].join(`
23
- `):P(e.name,r).map((r,i)=>{let a=i===0?e.id.padEnd(t):``.padEnd(t);return` ${n.colors.cyan(a)}${` `.repeat(2)}${r}`}).join(`
24
- `)}function oi(e){let t=e.replaceAll(`-`,` `);return t.charAt(0).toUpperCase()+t.slice(1)}function si(e,t,n){let r=new Map;for(let t of e){let e=r.get(t.category)??[];e.push(t),r.set(t.category,e)}let i=`${e.length} ${ei(e.length,n.noun)} across ${r.size} ${ei(r.size,`category`,`categories`)}.`,a=Math.min(30,Math.max(...e.map(e=>N(e.id)),0)),o=Object.keys(n.categoryLabels??{}).filter(e=>r.has(e)),s=[...r.keys()].filter(e=>!o.includes(e));return[i,...[...o,...s].map(e=>{let i=r.get(e)??[],o=n.categoryLabels?.[e]??oi(e);return[t.colors.bold(`${o} (${i.length})`),...i.map(e=>ai(e,a,t))].join(`
22
+ `)}function si(e){let t=e.filter(e=>e.status===`pass`).length,n=e.filter(e=>e.status===`warn`).length,r=e.filter(e=>e.status===`fail`).length;return`${t} ${ri(t,`check`)} passed, ${n} ${ri(n,`warning`)}, ${r} failed.`}function ci(e,t,n){let r=n.columns-t-2-2;return r<24||j(e.id)>t?[...ii(e.id,2,n.columns).map(e=>n.colors.cyan(e)),...ii(e.name,4,n.columns)].join(`
23
+ `):M(e.name,r).map((r,i)=>{let a=i===0?e.id.padEnd(t):``.padEnd(t);return` ${n.colors.cyan(a)}${` `.repeat(2)}${r}`}).join(`
24
+ `)}function li(e){let t=e.replaceAll(`-`,` `);return t.charAt(0).toUpperCase()+t.slice(1)}function ui(e,t,n){let r=new Map;for(let t of e){let e=r.get(t.category)??[];e.push(t),r.set(t.category,e)}let i=`${e.length} ${ri(e.length,n.noun)} across ${r.size} ${ri(r.size,`category`,`categories`)}.`,a=Math.min(30,Math.max(...e.map(e=>j(e.id)),0)),o=Object.keys(n.categoryLabels??{}).filter(e=>r.has(e)),s=[...r.keys()].filter(e=>!o.includes(e));return[i,...[...o,...s].map(e=>{let i=r.get(e)??[],o=n.categoryLabels?.[e]??li(e);return[t.colors.bold(`${o} (${i.length})`),...i.map(e=>ci(e,a,t))].join(`
25
25
  `)})].join(`
26
26
 
27
- `)}function ci(e,t,n){return e.length===0?t.colors.dim(n.empty):e.map(e=>{let n=[t.colors.bold(e.title)];e.description&&n.push(...ti(e.description,2,t.columns));let r=e.meta?.filter(Boolean).join(` · `);return r&&n.push(...ti(r,2,t.columns).map(e=>t.colors.dim(e))),n.join(`
27
+ `)}function di(e,t,n){return e.length===0?t.colors.dim(n.empty):e.map(e=>{let n=[t.colors.bold(e.title)];e.description&&n.push(...ii(e.description,2,t.columns));let r=e.meta?.filter(Boolean).join(` · `);return r&&n.push(...ii(r,2,t.columns).map(e=>t.colors.dim(e))),n.join(`
28
28
  `)}).join(`
29
29
 
30
- `)}function li(e,t,n){let r=t.filter(e=>e.trim().length>0).flatMap(e=>P(e,n.columns));return[n.colors.bold(e),...r].join(`
31
- `)}function ui(e,t,n){let r=t.flatMap(e=>P(e,Math.max(1,n.columns-2)).map((e,t)=>`${t===0?`- `:` `}${e}`));return[n.colors.bold(e),...r].join(`
32
- `)}function di(e,t){return e.length===0?t.colors.dim(`No parameters.`):ci(e.map(e=>({title:e.name,description:e.description,meta:[e.type,e.required?`required`:`optional`]})),t,{empty:`No parameters.`})}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=Kr(e,M());t&&process.stdout.write(`${t}\n`)}function R(e,t){process.stdout.write(`${Gr(e,t,M())}\n`)}function fi(e){process.stdout.write(`${qr(e,M())}\n`)}function pi(e,t){process.stdout.write(`${ni(e,M(),t)}\n`)}function mi(e){process.stdout.write(`${ri(e,M())}\n`)}function hi(e){process.stdout.write(`${$r(e,M())}\n`)}function gi(e,t){process.stdout.write(`${si(e,M(),t)}\n`)}function _i(e,t){process.stdout.write(`${ci(e,M(),t)}\n`)}function vi(e,...t){process.stdout.write(`${li(e,t,M())}\n`)}function yi(e,t){process.stdout.write(`${ui(e,t,M())}\n`)}function bi(e){process.stdout.write(`${di(e,M())}\n`)}function xi(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}const Si={site:`--site`,url:`--url`,section:`--section`,date:`--date`,pages:`--pages`,dimensions:`--dimensions`,detail:`--detail`,offset:`--offset`},Ci=e=>/^[\w:/.,<>@#?=&%+-]+$/.test(String(e))?String(e):`"${e}"`,wi=(e,t)=>[`seo ${e}`,...Object.entries(t??{}).filter(([,e])=>e!==void 0).map(([e,t])=>`${Si[e]??`--${e}`} ${Ci(t??``)}`)].join(` `),Ti={account:{type:`string`,description:`Saved Google account email.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`},refresh:{type:`boolean`,default:!1,description:`Bypass the local cache and fetch fresh data.`}},Ei={"start-date":{type:`string`,description:`Optional YYYY-MM-DD.`},"end-date":{type:`string`,description:`Optional YYYY-MM-DD.`}},Di={detail:{type:`string`,description:`summary (default), rows for the next page, or full.`},offset:{type:`string`,description:`rows only: the first row.`}};function B(e){let t=T(e.detail)??`summary`;if(![`summary`,`rows`,`full`].includes(t))throw new g(`INVALID_INPUT`,`--detail is summary, rows or full.`);return t}function Oi(e,t){return Ar(t,{refresh:E(e.refresh),account:T(e.account)})}async function V(e,t){try{let n=await t(te({call:wi,account:T(e.account),refresh:E(e.refresh)}));A(e)?I(n.data):process.stdout.write(`${n.text}\n`)}catch(e){throw c(e)}}const ki=y({meta:{name:`report`,description:`The main Search Console report: overview and every section, then one section on request`},args:{site:{type:`string`,description:`Site, URL or Search Console property.`},section:{type:`string`,description:`Optional. One of ${$t.join(`, `)}.`},...Ei,...Di,...Ti},run:async({args:e})=>{let t=T(e.section);if(t&&!$t.includes(t))throw new g(`INVALID_INPUT`,`--section is one of ${$t.join(`, `)}.`);let n=T(e.site),r=t===`bing`?await Oi(e,n).catch(()=>j(n)):await Oi(e,n);await V(e,n=>pe({site:r,section:t,startDate:T(e[`start-date`]),endDate:T(e[`end-date`]),detail:B(e),offset:D(e.offset)},n))}}),Ai=y({meta:{name:`page`,description:`One URL: live page facts, Google index status and its queries`},args:{url:{type:`string`,description:`The page URL.`,required:!0},...Ei,...Di,...Ti},run:async({args:e})=>{let t=T(e.url)??``;if(!/^https?:\/\/\S+$/.test(t))throw new g(`INVALID_INPUT`,`--url must be a full http(s) URL.`);let n=await Oi(e,t);await V(e,r=>We({site:n,url:t,startDate:T(e[`start-date`]),endDate:T(e[`end-date`]),detail:B(e),offset:D(e.offset)},r))}}),ji=y({meta:{name:`compare`,description:`Equal periods before and after a date, with the pages and queries that moved`},args:{site:{type:`string`,description:`Site, URL or Search Console property.`},date:{type:`string`,description:`YYYY-MM-DD: the first day after the change.`,required:!0},pages:{type:`string`,description:`Optional URL text, such as /blog/.`},...Ti},run:async({args:e})=>{let t=await Oi(e,T(e.site));await V(e,n=>Ke({site:t,date:T(e.date)??``,pages:T(e.pages)},n))}}),Mi=y({meta:{name:`index`,description:`Pages that lost most of their clicks, checked live and with URL Inspection`},args:{site:{type:`string`,description:`Site, URL or Search Console property.`},...Ei,...Ti},run:async({args:e})=>{let t=await Oi(e,T(e.site));await V(e,n=>se({site:t,startDate:T(e[`start-date`]),endDate:T(e[`end-date`])},n))}}),Ni=y({meta:{name:`data`,description:`Raw Search Console or Bing rows beside the previous period, or Clicky landing pages`},args:{site:{type:`string`,description:`Site, URL or Search Console property.`},source:{type:`string`,description:`search_console (default), bing or clicky. Bing takes the first dimension, query or page. Clicky takes the site or its Clicky site ID.`},dimensions:{type:`string`,description:`Comma-separated: ${bt.join(`, `)}. Default query.`},page:{type:`string`,description:`Optional: only rows for pages whose URL contains this.`},query:{type:`string`,description:`Optional: only rows for queries that contain this.`},...Ei,...Di,...Ti},run:async({args:e})=>{let t=k(e.dimensions)??[`query`],n=t.filter(e=>!bt.includes(e));if(n.length)throw new g(`INVALID_INPUT`,`Unknown dimensions: ${n.join(`, `)}. Use ${bt.join(`, `)}.`);let r=T(e.source)??`search_console`;if(![`search_console`,`bing`,`clicky`].includes(r))throw new g(`INVALID_INPUT`,`--source is search_console, bing or clicky.`);if(r===`clicky`){let t=j(T(e.site));await V(e,async()=>Qe({site:t,startDate:T(e[`start-date`]),endDate:T(e[`end-date`]),detail:B(e),offset:D(e.offset)},await Ve(),{connect:"save it at https://seoskill.dev/account#keys and run `seo login`, or run `seo analytics clicky connect` to keep it on this machine only.",refresh:gn,call:e=>wi(`data`,{source:`clicky`,site:t,detail:`rows`,offset:e})}));return}if(r===`bing`){let n=j(T(e.site));await V(e,r=>nt({site:n,dimension:t[0]===`page`?`page`:`query`,startDate:T(e[`start-date`]),endDate:T(e[`end-date`]),detail:B(e),offset:D(e.offset)},r));return}let i=await Oi(e,T(e.site)),a=T(e.page),o=T(e.query);await V(e,n=>ln({site:i,dimensions:t,filters:[...a?[{dimension:`page`,operator:`contains`,expression:a}]:[],...o?[{dimension:`query`,operator:`contains`,expression:o}]:[]],startDate:T(e[`start-date`]),endDate:T(e[`end-date`]),detail:B(e),offset:D(e.offset)},n))}}),Pi=y({meta:{name:`research`,description:`Keyword, SERP, competitor and backlink data from your DataForSEO or Ahrefs key`},args:{action:{type:`positional`,required:!0,description:Ye.join(`, `)},provider:{type:`string`,description:`${ie.join(`, `)}. Default: the first one with a saved key.`},keywords:{type:`string`,description:`Comma-separated keywords.`},target:{type:`string`,description:`Domain or URL.`},country:{type:`string`,description:`Two-letter country code. Default us.`},...Di,json:Ti.json},run:async({args:e})=>{let t=T(e.action);if(!Ye.includes(t))throw new g(`INVALID_INPUT`,`The action is one of ${Ye.join(`, `)}.`);let n=await Ve(),r={dataforseo:`x-dataforseo-login`,ahrefs:`x-ahrefs-key`},i=T(e.provider)??ie.find(e=>n.has(r[e]))??`dataforseo`;if(!ie.includes(i))throw new g(`INVALID_INPUT`,`--provider is one of ${ie.join(`, `)}.`);if(!n.has(r[i]))throw new g(`AUTH_CONFIG_REQUIRED`,`No ${i} key. Save it at https://seoskill.dev/account#keys and run \`seo login\`, or run \`seo providers ${i} connect\` to keep it on this machine only.`);let a=k(e.keywords)??[],o=T(e.target),s=T(e.country);await V(e,()=>he({provider:i,action:t,keywords:a,target:o,country:s,detail:B(e),offset:D(e.offset)},n,{call:e=>wi(`research`,{action:t,provider:i,keywords:a.join(`,`)||void 0,target:o,country:s,detail:`rows`,offset:e}).replace(`--action `,``)}))}});function Fi(e){return e===`keychain`?`system keychain`:e===`file`?`private local file`:e===`environment`?Ce:e??`missing`}function Ii(e){if(e&&Mt(e))return e.trim();throw new g(`INVALID_INPUT`,`No Clicky site given. Pass --site-id with the numeric Clicky site ID.`)}async function Li(e,t){return rn({siteId:e,sitekey:t}).catch(e=>{throw new g(e instanceof Kt&&e.code===`auth`?`AUTH_REQUIRED`:`PROVIDER_UNAVAILABLE`,e instanceof Error?e.message:String(e))})}const Ri={type:`boolean`,default:!1,description:`Print machine-readable JSON.`},zi={type:`string`,description:`Numeric Clicky site ID.`},Bi=y({meta:{name:`analytics`,description:`Read analytics data from connected providers`},subCommands:{clicky:y({meta:{name:`clicky`,description:`Read Clicky, or save a Clicky site on this machine only`},subCommands:{connect:y({meta:{name:`connect`,description:`Check and save a Clicky site on this machine`},args:{"site-id":zi,json:Ri},run:async({args:e})=>{let t=A(e),n=F({json:t});n&&b(`Connect Clicky`);let r=Ii(T(e[`site-id`])??(n?z(await cr({message:`Clicky site ID`,validate:e=>Mt(e??``)?void 0:`Enter the numeric site ID from Clicky`})):void 0)),i=process.env.SEO_CLICKY_SITEKEY?.trim()||(n?z(await C({message:`Clicky sitekey`,validate:e=>lt(e??``)?void 0:`Enter the sitekey from Clicky (Preferences, Info)`})):void 0);if(!i)throw new g(`AUTH_REQUIRED`,`Run this command in a terminal or set ${Ce}.`);let a=await Li(r,i.trim()),o=await fe(r,i.trim()),s={siteId:r,domain:a,connected:!0,credentialSource:o};if(t){I(s);return}let c=`Clicky site ${r}${a?` (${a})`:``} saved in the ${Fi(o)}. Next: seo analytics clicky report --site ${r}`;n?S(c):process.stdout.write(`${c}\n`)}}),status:y({meta:{name:`status`,description:`Show a Clicky site saved on this machine`},args:{"site-id":zi,check:{type:`boolean`,default:!1,description:`Check the saved sitekey with Clicky.`},json:Ri},run:async({args:e})=>{let t=Ii(T(e[`site-id`])),n=await h(t),r=E(e.check);r&&n&&await Li(t,n.siteKey);let i={siteId:t,connected:!!n,credentialSource:n?.source,liveCheck:r?n?`passed`:`unavailable`:`not-requested`};if(A(e)){I(i);return}L([[`Site ID`,t],[`Connected`,i.connected?`yes`:`no`],[`Credential`,Fi(i.credentialSource)],[`Live check`,i.liveCheck]])}}),report:y({meta:{name:`report`,description:`Clicky landing pages, traffic sources and search engines`},args:{site:{type:`string`,description:`The site, or its numeric Clicky site ID.`},...Ei,...Di,json:Ri},run:async({args:e})=>{let t=j(T(e.site));await V(e,async()=>Qe({site:t,startDate:T(e[`start-date`]),endDate:T(e[`end-date`]),detail:B(e),offset:D(e.offset)},await Ve(),{connect:"save it at https://seoskill.dev/account#keys and run `seo login`, or run `seo analytics clicky connect` to keep it on this machine only.",refresh:gn,call:e=>`seo analytics clicky report --site ${t} --detail rows --offset ${e}`}))}}),disconnect:y({meta:{name:`disconnect`,description:`Remove a Clicky site saved on this machine`},args:{"site-id":zi,json:Ri},run:async({args:e})=>{let t=Ii(T(e[`site-id`]));await Ee(t);let n=!!process.env[Ce],r={siteId:t,savedCredentialRemoved:!0,environmentCredential:n?`active`:`missing`};A(e)?I(r):process.stdout.write(n?`Saved sitekey removed. ${Ce} is still active.\n`:`Clicky is disconnected for this site.
33
- `)}})}}),google:y({meta:{name:`google`,description:`Read Google Analytics properties and reports`},subCommands:{properties:y({meta:{name:`properties`,description:`List Google Analytics accounts and properties`},args:{json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`},account:{type:`string`,description:`Saved Google account email.`}},run:async({args:e})=>{let t=await cn(T(e.account)),n=t.flatMap(e=>e.propertySummaries.map(t=>({account:e.displayName??e.account,property:yt(t.property),displayName:t.displayName??t.property})));if(A(e)){I({accountSummaries:t,properties:n});return}R([`Property`,`Name`,`Account`],n.map(e=>[e.property,e.displayName,e.account]))}}),report:y({meta:{name:`report`,description:`Google Analytics landing pages for visits from search`},args:{property:{type:`string`,description:"Numeric Google Analytics property ID. Run `seo analytics google properties` to list them."},...Ei,...Di,json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:t})=>{let n=jr(T(t.property));await V(t,async()=>e({property:n,startDate:T(t[`start-date`]),endDate:T(t[`end-date`]),detail:B(t),offset:D(t.offset)},await Ne(await Re(),n,jt.signIn),{signIn:jt.signIn,call:e=>`seo analytics google report --property ${n} --detail rows --offset ${e}`}))}})}})}});function Vi(){return`Your browser will open. Choose your Google account and select all permission boxes for read-only access. Keep this terminal open and finish the browser steps within five minutes.`}async function Hi(){return x(Vi(),`Google sign-in`),ct()}function Ui(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 Wi(e){return e.active===`keychain`?`system keychain`:e.configured===`keychain`?`private file fallback`:`private file`}function Gi(e){return e===`shared`?`SEO Skill app`:`Local OAuth config`}function Ki(e){let t=[[`Account`,e.accountEmail]],n=new Set(e.scope.split(/\s+/u).filter(Boolean));return n.has(`https://www.googleapis.com/auth/webmasters.readonly`)?t.push([`Search Console`,`Read only`]):t.push([`Search Console`,`Not granted`]),n.has(`https://www.googleapis.com/auth/analytics.readonly`)?t.push([`Google Analytics`,`Read only`]):t.push([`Google Analytics`,`Not granted`]),t.push([`OAuth app`,Gi(e.clientSource)],[`Expires`,xr(e.expiresAt)]),t}const qi=y({meta:{name:`auth`,description:`Authentication commands`},subCommands:{login:y({meta:{name:`login`,description:`Run Google OAuth flow`},run:async()=>{if((await Jt()).activeMode===`service-account`)throw new g(`AUTH_CONFIG_REQUIRED`,`A service account is active from the environment. Unset its credential variable before running browser OAuth.`);let e=await Hi();L(Ki({accountEmail:e.account_email,clientSource:e.client_source,expiresAt:e.expires_at,scope:e.scope}))}}),logout:y({meta:{name:`logout`,description:`Delete a local Google login`},args:{account:{type:`string`,description:`Delete the login for this Google account.`},all:{type:`boolean`,default:!1,description:`Delete every saved Google login.`}},run:async({args:e})=>{let t=await Jt(),n=T(e.account);if(n&&e.all)throw new g(`INVALID_INPUT`,`Use either --account or --all, not both.`);let r=e.all?void 0:n??t.accounts.find(e=>e.active)?.accountEmail;if(await mt(r),t.activeMode===`service-account`){process.stdout.write(`${e.all?`Deleted all local Google logins.`:r?`Deleted the local login for ${r}.`:`No local Google login was found.`} Service account credentials remain available through the environment.\n`);return}process.stdout.write(`${e.all?`Deleted all local Google logins.`:r?`Deleted the local login for ${r}.`:`No local Google login was found.`}\nRevoke at https://myaccount.google.com/permissions if you also want Google to forget the grant.\n`)}}),whoami:y({meta:{name:`whoami`,description:`Show the signed-in Google account`},args:{account:{type:`string`,description:`Show this saved Google account.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=await Jt();if(t.activeMode===`service-account`){let n={mode:`service-account`,account:t.identity??null,source:Ui(t.serviceAccount.source)};if(A(e)){I(n);return}L([[`Account type`,`Service account`],[`Account`,t.identity??`Invalid configuration`],[`Credentials`,n.source],[`Access`,`Search Console and Google Analytics, read only`]]);return}let n=T(e.account),r=n?await St(n):t.tokens;if(n&&!r)throw new g(`AUTH_REQUIRED`,`Google account ${n} is not connected.`);if(!r){if(A(e)){I({mode:`oauth`,account:null});return}process.stdout.write(`Not logged in.
34
- `);return}if(A(e)){I({mode:`oauth`,account:r.account_email,scopes:r.scope.split(/\s+/u).filter(Boolean),clientSource:r.client_source,expiresAt:r.expires_at});return}L(Ki({accountEmail:r.account_email,clientSource:r.client_source,expiresAt:r.expires_at,scope:r.scope}))}}),status:y({meta:{name:`status`,description:`Show local Google auth status`},args:{account:{type:`string`,description:`Show status for this saved Google account.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=await Jt(),n=T(e.account),r=n?await St(n):t.tokens;if(n&&!r&&t.activeMode!==`service-account`)throw new g(`AUTH_REQUIRED`,`Google account ${n} is not connected.`);let i=t.activeMode===`oauth`?await dn():void 0;if(A(e)){I({authenticated:t.activeMode===`oauth`?!!r:t.serviceAccount.configured,mode:t.activeMode,identity:t.identity,account:t.activeMode===`oauth`?r?.account_email:void 0,scopes:t.activeMode===`oauth`?r?.scope:void 0,clientSource:t.activeMode===`oauth`?r?.client_source:void 0,expiresAt:t.activeMode===`oauth`?r?.expires_at:void 0,sharedConfigured:t.sharedConfigured,byoConfigured:t.byoConfigured,serviceAccount:{configured:t.serviceAccount.configured,identity:t.serviceAccount.identity,source:t.serviceAccount.source,error:t.serviceAccount.error},accounts:t.accounts,...i?{tokenStorage:i}:{}});return}if(t.activeMode===`service-account`){let e=[[`Mode`,`service account`],[`Identity`,t.identity??`unavailable`],[`Source`,Ui(t.serviceAccount.source)],[`Scopes`,`Search Console readonly, Google Analytics readonly`],[`Storage`,`credentials stay in the environment`]];t.serviceAccount.error&&e.push([`Status`,t.serviceAccount.error]),L(e);return}if(!r){let e=t.serviceAccount.error?t.serviceAccount.error:t.sharedConfigured?`Shared seo app available`:t.byoConfigured?`Local OAuth config available`:`No OAuth client configured`;process.stdout.write(`Not logged in. ${e}. Run \`seo auth login\` to connect Google.\n`);return}let a=i??await dn();L([[`Account`,r.account_email],[`Connected accounts`,String(t.accounts.length)],[`Scopes`,r.scope],[`Client`,Gi(r.client_source)],[`Expires`,xr(r.expires_at)],[`Tokens file`,Ie().tokensFile],[`Token storage`,Wi(a)],...a.reason?[[`Storage note`,a.reason]]:[],[`OAuth mode`,t.sharedConfigured?`SEO Skill app available`:t.byoConfigured?`Local OAuth config available`:`missing`],[`Revoke at`,`https://myaccount.google.com/permissions`]])}}),storage:y({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 g(`INVALID_INPUT`,`Choose either --keychain or --file, not both.`);let t=e.keychain||e.file?await Ut(e.keychain?`keychain`:`file`):await dn();if(A(e)){I(t);return}L([[`Configured storage`,t.configured],[`Active storage`,Wi(t)],...t.reason?[[`Note`,t.reason]]:[]])}}),refresh:y({meta:{name:`refresh`,description:`Refresh the Google OAuth token`},args:{account:{type:`string`,description:`Refresh this saved Google account.`}},run:async({args:e})=>{if((await Jt()).activeMode===`service-account`){process.stdout.write(`Service accounts request short-lived Google tokens automatically. No local refresh token is stored.
35
- `);return}let t=await an(T(e.account));process.stdout.write(`Refreshed. New expiry ${new Date(t.expires_at).toISOString()}.\n`)}}),accounts:y({meta:{name:`accounts`,description:`List saved Google accounts`},args:{json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:({args:e})=>{let t=_t();if(A(e)){I({accounts:t});return}if(t.length===0){process.stdout.write(`No Google accounts are connected.
36
- `);return}L(t.map(e=>[e.active?`Active account`:`Account`,e.accountEmail]))}}),use:y({meta:{name:`use`,description:`Select the default Google account`},args:{account:{type:`positional`,required:!0,description:`Saved Google account email.`}},run:({args:e})=>{let t=T(e.account);if(!t)throw new g(`INVALID_INPUT`,`Google account is required.`);Zt(t),process.stdout.write(`Default Google account: ${t}\n`)}}),"setup-client":y({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:A(e)}))throw new g(`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.");b(`Local Google OAuth client`),x([`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(`
37
- `),`Advanced`),Ot({clientId:z(await cr({message:`Google Desktop OAuth client ID`,validate:e=>e?void 0:`Client ID is required`})),clientSecret:z(await C({message:`Google Desktop OAuth client secret`,validate:e=>e?void 0:`Client secret is required`}))}),S(`Saved local OAuth config to ${Ie().oauthClientFile}`)}})}}),Ji=[`gsc`,`google-analytics`,`dataforseo`,`ahrefs`,`clicky`,`http`];function Yi(e){if(e!==void 0){if(typeof e==`string`&&Ji.includes(e))return e;throw new g(`INVALID_INPUT`,`--provider must be one of: ${Ji.join(`, `)}.`)}}const Xi=y({meta:{name:`cache`,description:`Cache helpers`},subCommands:{stats:y({meta:{name:`stats`,description:`Show local cache size and row counts`},run:async()=>{let e=nn();L([[`DB`,e.dbPath],[`Size`,xi(e.sizeBytes)],[`Cached data`,`${xi(e.logicalSizeBytes)} of ${xi(e.maxSizeBytes)} automatic limit`],[`Sites`,String(e.counts.sites??0)],[`Search Console`,String(e.counts.gsc_cache??0)],[`Google Analytics`,String(e.counts.google_analytics_cache??0)],[`DataForSEO`,String(e.counts.provider_cache??0)],[`Ahrefs`,String(e.counts.ahrefs_cache??0)],[`Clicky`,String(e.counts.clicky_cache??0)],[`HTTP`,String(e.counts.http_cache??0)]])}}),clear:y({meta:{name:`clear`,description:`Clear cached API and HTTP data`},args:{provider:{type:`string`,description:`Optional cache provider: gsc, google-analytics, dataforseo, ahrefs, clicky, or http`}},run:async({args:e})=>{let t=Gt(Yi(e.provider));process.stdout.write(`Removed ${t} cached rows.\n`)}})}});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 Ir(e,t)}function Zi(e){return e.verification?.requested?`${e.verification.verified} checked, ${e.verification.failed} failed`:`off`}function Qi(e){return{title:e.title,target:e.target,status:e.status,summary:e.summary,metrics:e.metrics,sections:(e.diagnostics??[]).map(e=>({title:e.title,diagnostics:e.items})),notes:e.notes}}function K(e){hi(Qi(e))}function q(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 J(e,t,n=5){let r=t.filter(e=>e.action.trim().length>0).slice(0,n),i=$i(e,r);i&&(process.stdout.write(`
38
- `),hi(i),t.length>r.length&&process.stdout.write(`Showing ${r.length} of ${t.length}. Use --json for full data.\n`))}function $i(e,t){if(t.length)return{title:e,status:`info`,summary:`${t.length} ${t.length===1?`action`:`actions`} shown.`,sections:[{title:`Actions`,diagnostics:t.map(e=>({status:`info`,title:e.label,explanation:e.context??`Review the evidence before making changes.`,fix:e.action}))}]}}function ea(e){process.stdout.write(`\nNext: ${e}\n`)}function Y(e,t){let n=t.filter(e=>e.trim().length>0);if(n.length){process.stdout.write(`\n${e}\n`);for(let e of n)process.stdout.write(`- ${e}\n`)}}const ta=y({meta:{name:`content`,description:`Optimize page content from real GSC queries`},subCommands:{optimize:y({meta:{name:`optimize`,description:`Build a content optimization report for one URL`},args:{site:{type:`string`},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":Sr(`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=A(e),n=await tn({site:j(T(e.site)),url:T(e.url)??``,days:D(e.days),limit:D(e.limit),minImpressions:D(e[`min-impressions`]),includeBrand:E(e[`include-brand`]),verifyContent:E(e[`verify-content`])!==!1,js:E(e.js)?!0:`auto`,refresh:E(e.refresh)});if(t){I(n);return}K({title:`Content optimization`,target:n.url,status:`info`,summary:n.summary.verdict,metrics:[{label:`Score`,value:`${n.summary.score}/100`},{label:`Primary intent`,value:n.summary.primaryIntent},{label:`Primary query`,value:n.summary.primaryQuery??`None`},{label:`Queries`,value:H(n.summary.queries)},{label:`Opportunities`,value:H(n.summary.opportunities)},{label:`Estimated lift`,value:H(n.summary.estimatedClickLift)}]}),J(`Top actions`,n.topActions.map(e=>({label:e.title,action:e.action,context:e.plainEnglish}))),n.brief.sections.length&&(process.stdout.write(`
30
+ `)}function fi(e,t,n){let r=t.filter(e=>e.trim().length>0).flatMap(e=>M(e,n.columns));return[n.colors.bold(e),...r].join(`
31
+ `)}function pi(e,t,n){let r=t.flatMap(e=>M(e,Math.max(1,n.columns-2)).map((e,t)=>`${t===0?`- `:` `}${e}`));return[n.colors.bold(e),...r].join(`
32
+ `)}function mi(e,t){return e.length===0?t.colors.dim(`No parameters.`):di(e.map(e=>({title:e.name,description:e.description,meta:[e.type,e.required?`required`:`optional`]})),t,{empty:`No parameters.`})}function N(e={}){return!!(!e.json&&process.stdin.isTTY&&process.stdout.isTTY&&!process.env.CI)}function P(e){process.stdout.write(`${JSON.stringify(e,null,2)}\n`)}function F(e){let t=Yr(e,A());t&&process.stdout.write(`${t}\n`)}function I(e,t){process.stdout.write(`${Jr(e,t,A())}\n`)}function hi(e){process.stdout.write(`${Xr(e,A())}\n`)}function gi(e,t){process.stdout.write(`${ai(e,A(),t)}\n`)}function _i(e){process.stdout.write(`${oi(e,A())}\n`)}function vi(e){process.stdout.write(`${ni(e,A())}\n`)}function yi(e,t){process.stdout.write(`${ui(e,A(),t)}\n`)}function bi(e,t){process.stdout.write(`${di(e,A(),t)}\n`)}function xi(e,...t){process.stdout.write(`${fi(e,t,A())}\n`)}function Si(e,t){process.stdout.write(`${pi(e,t,A())}\n`)}function Ci(e){process.stdout.write(`${mi(e,A())}\n`)}function wi(e){return e<1024?`${e} B`:e<1024*1024?`${(e/1024).toFixed(1)} KB`:`${(e/(1024*1024)).toFixed(1)} MB`}function L(e){return typeof e==`symbol`&&process.exit(1),e}const Ti={site:`--site`,url:`--url`,section:`--section`,date:`--date`,pages:`--pages`,dimensions:`--dimensions`,detail:`--detail`,offset:`--offset`},Ei=e=>/^[\w:/.,<>@#?=&%+-]+$/.test(String(e))?String(e):`"${e}"`,Di=(e,t)=>[`seo ${e}`,...Object.entries(t??{}).filter(([,e])=>e!==void 0).map(([e,t])=>`${Ti[e]??`--${e}`} ${Ei(t??``)}`)].join(` `),Oi={account:{type:`string`,description:`Saved Google account email.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`},refresh:{type:`boolean`,default:!1,description:`Bypass the local cache and fetch fresh data.`}},ki={"start-date":{type:`string`,description:`Optional YYYY-MM-DD.`},"end-date":{type:`string`,description:`Optional YYYY-MM-DD.`}},Ai={detail:{type:`string`,description:`summary (default), rows for the next page, or full.`},offset:{type:`string`,description:`rows only: the first row.`}};function R(e){let t=C(e.detail)??`summary`;if(![`summary`,`rows`,`full`].includes(t))throw new h(`INVALID_INPUT`,`--detail is summary, rows or full.`);return t}function ji(e,t){return Nr(t,{refresh:w(e.refresh),account:C(e.account)})}async function z(e,t){try{let n=await t(ne({call:Di,account:C(e.account),refresh:w(e.refresh)}));O(e)?P(n.data):process.stdout.write(`${n.text}\n`)}catch(e){throw c(e)}}const Mi=v({meta:{name:`report`,description:`The main Search Console report: overview and every section, then one section on request`},args:{site:{type:`string`,description:`Site, URL or Search Console property.`},section:{type:`string`,description:`Optional. One of ${tn.join(`, `)}.`},...ki,...Ai,...Oi},run:async({args:e})=>{let t=C(e.section);if(t&&!tn.includes(t))throw new h(`INVALID_INPUT`,`--section is one of ${tn.join(`, `)}.`);let n=C(e.site),r=t===`bing`?await ji(e,n).catch(()=>k(n)):await ji(e,n).catch(async r=>{if(t||!n)throw r;return await z(e,t=>me({site:k(n),startDate:C(e[`start-date`]),endDate:C(e[`end-date`])},t).catch(()=>{throw r})),null});r!==null&&await z(e,n=>nt({site:r,section:t,startDate:C(e[`start-date`]),endDate:C(e[`end-date`]),detail:R(e),offset:T(e.offset)},n))}}),Ni=v({meta:{name:`page`,description:`One URL: live page facts, Google index status and its queries`},args:{url:{type:`string`,description:`The page URL.`,required:!0},...ki,...Ai,...Oi},run:async({args:e})=>{let t=C(e.url)??``;if(!/^https?:\/\/\S+$/.test(t))throw new h(`INVALID_INPUT`,`--url must be a full http(s) URL.`);let n=await ji(e,t);await z(e,r=>Ke({site:n,url:t,startDate:C(e[`start-date`]),endDate:C(e[`end-date`]),detail:R(e),offset:T(e.offset)},r))}}),Pi=v({meta:{name:`compare`,description:`Equal periods before and after a date, with the pages and queries that moved`},args:{site:{type:`string`,description:`Site, URL or Search Console property.`},date:{type:`string`,description:`YYYY-MM-DD: the first day after the change.`,required:!0},pages:{type:`string`,description:`Optional URL text, such as /blog/.`},...Oi},run:async({args:e})=>{let t=await ji(e,C(e.site));await z(e,n=>Ye({site:t,date:C(e.date)??``,pages:C(e.pages)},n))}}),Fi=v({meta:{name:`index`,description:`Pages that lost most of their clicks, checked live and with URL Inspection`},args:{site:{type:`string`,description:`Site, URL or Search Console property.`},...ki,...Oi},run:async({args:e})=>{let t=await ji(e,C(e.site));await z(e,n=>ce({site:t,startDate:C(e[`start-date`]),endDate:C(e[`end-date`])},n))}}),Ii=v({meta:{name:`data`,description:`Raw Search Console or Bing rows beside the previous period, or Clicky landing pages`},args:{site:{type:`string`,description:`Site, URL or Search Console property.`},source:{type:`string`,description:`search_console (default), bing or clicky. Bing takes the first dimension, query or page. Clicky takes the site or its Clicky site ID.`},dimensions:{type:`string`,description:`Comma-separated: ${dn.join(`, `)}. Default query.`},page:{type:`string`,description:`Optional: only rows for pages whose URL contains this.`},query:{type:`string`,description:`Optional: only rows for queries that contain this.`},...ki,...Ai,...Oi},run:async({args:e})=>{let t=D(e.dimensions)??[`query`],n=t.filter(e=>!dn.includes(e));if(n.length)throw new h(`INVALID_INPUT`,`Unknown dimensions: ${n.join(`, `)}. Use ${dn.join(`, `)}.`);let r=C(e.source)??`search_console`;if(![`search_console`,`bing`,`clicky`].includes(r))throw new h(`INVALID_INPUT`,`--source is search_console, bing or clicky.`);if(r===`clicky`){let t=k(C(e.site));await z(e,async()=>it({site:t,startDate:C(e[`start-date`]),endDate:C(e[`end-date`]),detail:R(e),offset:T(e.offset)},await Ue(),{connect:"save it at https://seoskill.dev/account#keys and run `seo login`, or run `seo analytics clicky connect` to keep it on this machine only.",refresh:vn,call:e=>Di(`data`,{source:`clicky`,site:t,detail:`rows`,offset:e})}));return}if(r===`bing`){let n=k(C(e.site));await z(e,r=>Fe({site:n,dimension:t[0]===`page`?`page`:`query`,startDate:C(e[`start-date`]),endDate:C(e[`end-date`]),detail:R(e),offset:T(e.offset)},r));return}let i=await ji(e,C(e.site)),a=C(e.page),o=C(e.query);await z(e,n=>Jt({site:i,dimensions:t,filters:[...a?[{dimension:`page`,operator:`contains`,expression:a}]:[],...o?[{dimension:`query`,operator:`contains`,expression:o}]:[]],startDate:C(e[`start-date`]),endDate:C(e[`end-date`]),detail:R(e),offset:T(e.offset)},n))}}),Li=v({meta:{name:`research`,description:`Keyword, SERP, competitor and backlink data from your DataForSEO or Ahrefs key`},args:{action:{type:`positional`,required:!0,description:Qe.join(`, `)},provider:{type:`string`,description:`${ae.join(`, `)}. Default: the first one with a saved key.`},keywords:{type:`string`,description:`Comma-separated keywords.`},target:{type:`string`,description:`Domain or URL.`},country:{type:`string`,description:`Two-letter country code. Default us.`},...Ai,json:Oi.json},run:async({args:e})=>{let t=C(e.action);if(!Qe.includes(t))throw new h(`INVALID_INPUT`,`The action is one of ${Qe.join(`, `)}.`);let n=await Ue(),r={dataforseo:`x-dataforseo-login`,ahrefs:`x-ahrefs-key`},i=C(e.provider)??ae.find(e=>n.has(r[e]))??`dataforseo`;if(!ae.includes(i))throw new h(`INVALID_INPUT`,`--provider is one of ${ae.join(`, `)}.`);if(!n.has(r[i]))throw new h(`AUTH_CONFIG_REQUIRED`,`No ${i} key. Save it at https://seoskill.dev/account#keys and run \`seo login\`, or run \`seo providers ${i} connect\` to keep it on this machine only.`);let a=D(e.keywords)??[],o=C(e.target),s=C(e.country);await z(e,()=>_e({provider:i,action:t,keywords:a,target:o,country:s,detail:R(e),offset:T(e.offset)},n,{call:e=>Di(`research`,{action:t,provider:i,keywords:a.join(`,`)||void 0,target:o,country:s,detail:`rows`,offset:e}).replace(`--action `,``)}))}});function Ri(e){return e===`keychain`?`system keychain`:e===`file`?`private local file`:e===`environment`?Oe:e??`missing`}function zi(e){if(e&&dt(e))return e.trim();throw new h(`INVALID_INPUT`,`No Clicky site given. Pass --site-id with the numeric Clicky site ID.`)}async function Bi(e,t){return Pt({siteId:e,sitekey:t}).catch(e=>{throw new h(e instanceof on&&e.code===`auth`?`AUTH_REQUIRED`:`PROVIDER_UNAVAILABLE`,e instanceof Error?e.message:String(e))})}const Vi={type:`boolean`,default:!1,description:`Print machine-readable JSON.`},Hi={type:`string`,description:`Numeric Clicky site ID.`},Ui=v({meta:{name:`analytics`,description:`Read analytics data from connected providers`},subCommands:{clicky:v({meta:{name:`clicky`,description:`Read Clicky, or save a Clicky site on this machine only`},subCommands:{connect:v({meta:{name:`connect`,description:`Check and save a Clicky site on this machine`},args:{"site-id":Hi,json:Vi},run:async({args:e})=>{let t=O(e),n=N({json:t});n&&y(`Connect Clicky`);let r=zi(C(e[`site-id`])??(n?L(await dr({message:`Clicky site ID`,validate:e=>dt(e??``)?void 0:`Enter the numeric site ID from Clicky`})):void 0)),i=process.env.SEO_CLICKY_SITEKEY?.trim()||(n?L(await lr({message:`Clicky sitekey`,validate:e=>Zt(e??``)?void 0:`Enter the sitekey from Clicky (Preferences, Info)`})):void 0);if(!i)throw new h(`AUTH_REQUIRED`,`Run this command in a terminal or set ${Oe}.`);let a=await Bi(r,i.trim()),o=await Se(r,i.trim()),s={siteId:r,domain:a,connected:!0,credentialSource:o};if(t){P(s);return}let c=`Clicky site ${r}${a?` (${a})`:``} saved in the ${Ri(o)}. Next: seo analytics clicky report --site ${r}`;n?x(c):process.stdout.write(`${c}\n`)}}),status:v({meta:{name:`status`,description:`Show a Clicky site saved on this machine`},args:{"site-id":Hi,check:{type:`boolean`,default:!1,description:`Check the saved sitekey with Clicky.`},json:Vi},run:async({args:e})=>{let t=zi(C(e[`site-id`])),n=await pe(t),r=w(e.check);r&&n&&await Bi(t,n.siteKey);let i={siteId:t,connected:!!n,credentialSource:n?.source,liveCheck:r?n?`passed`:`unavailable`:`not-requested`};if(O(e)){P(i);return}F([[`Site ID`,t],[`Connected`,i.connected?`yes`:`no`],[`Credential`,Ri(i.credentialSource)],[`Live check`,i.liveCheck]])}}),report:v({meta:{name:`report`,description:`Clicky landing pages, traffic sources and search engines`},args:{site:{type:`string`,description:`The site, or its numeric Clicky site ID.`},...ki,...Ai,json:Vi},run:async({args:e})=>{let t=k(C(e.site));await z(e,async()=>it({site:t,startDate:C(e[`start-date`]),endDate:C(e[`end-date`]),detail:R(e),offset:T(e.offset)},await Ue(),{connect:"save it at https://seoskill.dev/account#keys and run `seo login`, or run `seo analytics clicky connect` to keep it on this machine only.",refresh:vn,call:e=>`seo analytics clicky report --site ${t} --detail rows --offset ${e}`}))}}),disconnect:v({meta:{name:`disconnect`,description:`Remove a Clicky site saved on this machine`},args:{"site-id":Hi,json:Vi},run:async({args:e})=>{let t=zi(C(e[`site-id`]));await Pe(t);let n=!!process.env[Oe],r={siteId:t,savedCredentialRemoved:!0,environmentCredential:n?`active`:`missing`};O(e)?P(r):process.stdout.write(n?`Saved sitekey removed. ${Oe} is still active.\n`:`Clicky is disconnected for this site.
33
+ `)}})}}),google:v({meta:{name:`google`,description:`Read Google Analytics properties and reports`},subCommands:{properties:v({meta:{name:`properties`,description:`List Google Analytics accounts and properties`},args:{json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`},account:{type:`string`,description:`Saved Google account email.`}},run:async({args:e})=>{let t=await qt(C(e.account)),n=t.flatMap(e=>e.propertySummaries.map(t=>({account:e.displayName??e.account,property:un(t.property),displayName:t.displayName??t.property})));if(O(e)){P({accountSummaries:t,properties:n});return}I([`Property`,`Name`,`Account`],n.map(e=>[e.property,e.displayName,e.account]))}}),report:v({meta:{name:`report`,description:`Google Analytics landing pages for visits from search`},args:{property:{type:`string`,description:"Numeric Google Analytics property ID. Run `seo analytics google properties` to list them."},...ki,...Ai,json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:t})=>{let n=Pr(C(t.property));await z(t,async()=>St({property:n,startDate:C(t[`start-date`]),endDate:C(t[`end-date`]),detail:R(t),offset:T(t.offset)},await e(await Be(),n,ut.signIn),{signIn:ut.signIn,call:e=>`seo analytics google report --property ${n} --detail rows --offset ${e}`}))}})}})}});function Wi(){return`Your browser will open. Choose your Google account and select all permission boxes for read-only access. Keep this terminal open and finish the browser steps within five minutes.`}async function Gi(){return b(Wi(),`Google sign-in`),Xt()}function Ki(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 qi(e){return e.active===`keychain`?`system keychain`:e.configured===`keychain`?`private file fallback`:`private file`}function Ji(e){return e===`shared`?`SEO Skill app`:`Local OAuth config`}function Yi(e){let t=[[`Account`,e.accountEmail]],n=new Set(e.scope.split(/\s+/u).filter(Boolean));return n.has(`https://www.googleapis.com/auth/webmasters.readonly`)?t.push([`Search Console`,`Read only`]):t.push([`Search Console`,`Not granted`]),n.has(`https://www.googleapis.com/auth/analytics.readonly`)?t.push([`Google Analytics`,`Read only`]):t.push([`Google Analytics`,`Not granted`]),t.push([`OAuth app`,Ji(e.clientSource)],[`Expires`,wr(e.expiresAt)]),t}const Xi=v({meta:{name:`auth`,description:`Authentication commands`},subCommands:{login:v({meta:{name:`login`,description:`Run Google OAuth flow`},run:async()=>{if((await sn()).activeMode===`service-account`)throw new h(`AUTH_CONFIG_REQUIRED`,`A service account is active from the environment. Unset its credential variable before running browser OAuth.`);let e=await Gi();F(Yi({accountEmail:e.account_email,clientSource:e.client_source,expiresAt:e.expires_at,scope:e.scope}))}}),logout:v({meta:{name:`logout`,description:`Delete a local Google login`},args:{account:{type:`string`,description:`Delete the login for this Google account.`},all:{type:`boolean`,default:!1,description:`Delete every saved Google login.`}},run:async({args:e})=>{let t=await sn(),n=C(e.account);if(n&&e.all)throw new h(`INVALID_INPUT`,`Use either --account or --all, not both.`);let r=e.all?void 0:n??t.accounts.find(e=>e.active)?.accountEmail;if(await Ht(r),t.activeMode===`service-account`){process.stdout.write(`${e.all?`Deleted all local Google logins.`:r?`Deleted the local login for ${r}.`:`No local Google login was found.`} Service account credentials remain available through the environment.\n`);return}process.stdout.write(`${e.all?`Deleted all local Google logins.`:r?`Deleted the local login for ${r}.`:`No local Google login was found.`}\nRevoke at https://myaccount.google.com/permissions if you also want Google to forget the grant.\n`)}}),whoami:v({meta:{name:`whoami`,description:`Show the signed-in Google account`},args:{account:{type:`string`,description:`Show this saved Google account.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=await sn();if(t.activeMode===`service-account`){let n={mode:`service-account`,account:t.identity??null,source:Ki(t.serviceAccount.source)};if(O(e)){P(n);return}F([[`Account type`,`Service account`],[`Account`,t.identity??`Invalid configuration`],[`Credentials`,n.source],[`Access`,`Search Console and Google Analytics, read only`]]);return}let n=C(e.account),r=n?await $t(n):t.tokens;if(n&&!r)throw new h(`AUTH_REQUIRED`,`Google account ${n} is not connected.`);if(!r){if(O(e)){P({mode:`oauth`,account:null});return}process.stdout.write(`Not logged in.
34
+ `);return}if(O(e)){P({mode:`oauth`,account:r.account_email,scopes:r.scope.split(/\s+/u).filter(Boolean),clientSource:r.client_source,expiresAt:r.expires_at});return}F(Yi({accountEmail:r.account_email,clientSource:r.client_source,expiresAt:r.expires_at,scope:r.scope}))}}),status:v({meta:{name:`status`,description:`Show local Google auth status`},args:{account:{type:`string`,description:`Show status for this saved Google account.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=await sn(),n=C(e.account),r=n?await $t(n):t.tokens;if(n&&!r&&t.activeMode!==`service-account`)throw new h(`AUTH_REQUIRED`,`Google account ${n} is not connected.`);let i=t.activeMode===`oauth`?await yt():void 0;if(O(e)){P({authenticated:t.activeMode===`oauth`?!!r:t.serviceAccount.configured,mode:t.activeMode,identity:t.identity,account:t.activeMode===`oauth`?r?.account_email:void 0,scopes:t.activeMode===`oauth`?r?.scope:void 0,clientSource:t.activeMode===`oauth`?r?.client_source:void 0,expiresAt:t.activeMode===`oauth`?r?.expires_at:void 0,sharedConfigured:t.sharedConfigured,byoConfigured:t.byoConfigured,serviceAccount:{configured:t.serviceAccount.configured,identity:t.serviceAccount.identity,source:t.serviceAccount.source,error:t.serviceAccount.error},accounts:t.accounts,...i?{tokenStorage:i}:{}});return}if(t.activeMode===`service-account`){let e=[[`Mode`,`service account`],[`Identity`,t.identity??`unavailable`],[`Source`,Ki(t.serviceAccount.source)],[`Scopes`,`Search Console readonly, Google Analytics readonly`],[`Storage`,`credentials stay in the environment`]];t.serviceAccount.error&&e.push([`Status`,t.serviceAccount.error]),F(e);return}if(!r){let e=t.serviceAccount.error?t.serviceAccount.error:t.sharedConfigured?`Shared seo app available`:t.byoConfigured?`Local OAuth config available`:`No OAuth client configured`;process.stdout.write(`Not logged in. ${e}. Run \`seo auth login\` to connect Google.\n`);return}let a=i??await yt();F([[`Account`,r.account_email],[`Connected accounts`,String(t.accounts.length)],[`Scopes`,r.scope],[`Client`,Ji(r.client_source)],[`Expires`,wr(r.expires_at)],[`Tokens file`,On().tokensFile],[`Token storage`,qi(a)],...a.reason?[[`Storage note`,a.reason]]:[],[`OAuth mode`,t.sharedConfigured?`SEO Skill app available`:t.byoConfigured?`Local OAuth config available`:`missing`],[`Revoke at`,`https://myaccount.google.com/permissions`]])}}),storage:v({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 h(`INVALID_INPUT`,`Choose either --keychain or --file, not both.`);let t=e.keychain||e.file?await At(e.keychain?`keychain`:`file`):await yt();if(O(e)){P(t);return}F([[`Configured storage`,t.configured],[`Active storage`,qi(t)],...t.reason?[[`Note`,t.reason]]:[]])}}),refresh:v({meta:{name:`refresh`,description:`Refresh the Google OAuth token`},args:{account:{type:`string`,description:`Refresh this saved Google account.`}},run:async({args:e})=>{if((await sn()).activeMode===`service-account`){process.stdout.write(`Service accounts request short-lived Google tokens automatically. No local refresh token is stored.
35
+ `);return}let t=await gt(C(e.account));process.stdout.write(`Refreshed. New expiry ${new Date(t.expires_at).toISOString()}.\n`)}}),accounts:v({meta:{name:`accounts`,description:`List saved Google accounts`},args:{json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:({args:e})=>{let t=wt();if(O(e)){P({accounts:t});return}if(t.length===0){process.stdout.write(`No Google accounts are connected.
36
+ `);return}F(t.map(e=>[e.active?`Active account`:`Account`,e.accountEmail]))}}),use:v({meta:{name:`use`,description:`Select the default Google account`},args:{account:{type:`positional`,required:!0,description:`Saved Google account email.`}},run:({args:e})=>{let t=C(e.account);if(!t)throw new h(`INVALID_INPUT`,`Google account is required.`);Gt(t),process.stdout.write(`Default Google account: ${t}\n`)}}),"setup-client":v({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(!N({json:O(e)}))throw new h(`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.");y(`Local Google OAuth client`),b([`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(`
37
+ `),`Advanced`),Dt({clientId:L(await dr({message:`Google Desktop OAuth client ID`,validate:e=>e?void 0:`Client ID is required`})),clientSecret:L(await lr({message:`Google Desktop OAuth client secret`,validate:e=>e?void 0:`Client secret is required`}))}),x(`Saved local OAuth config to ${On().oauthClientFile}`)}})}}),Zi=[`gsc`,`google-analytics`,`dataforseo`,`ahrefs`,`clicky`,`http`];function Qi(e){if(e!==void 0){if(typeof e==`string`&&Zi.includes(e))return e;throw new h(`INVALID_INPUT`,`--provider must be one of: ${Zi.join(`, `)}.`)}}const $i=v({meta:{name:`cache`,description:`Cache helpers`},subCommands:{stats:v({meta:{name:`stats`,description:`Show local cache size and row counts`},run:async()=>{let e=Nt();F([[`DB`,e.dbPath],[`Size`,wi(e.sizeBytes)],[`Cached data`,`${wi(e.logicalSizeBytes)} of ${wi(e.maxSizeBytes)} automatic limit`],[`Sites`,String(e.counts.sites??0)],[`Search Console`,String(e.counts.gsc_cache??0)],[`Google Analytics`,String(e.counts.google_analytics_cache??0)],[`DataForSEO`,String(e.counts.provider_cache??0)],[`Ahrefs`,String(e.counts.ahrefs_cache??0)],[`Clicky`,String(e.counts.clicky_cache??0)],[`HTTP`,String(e.counts.http_cache??0)]])}}),clear:v({meta:{name:`clear`,description:`Clear cached API and HTTP data`},args:{provider:{type:`string`,description:`Optional cache provider: gsc, google-analytics, dataforseo, ahrefs, clicky, or http`}},run:async({args:e})=>{let t=an(Qi(e.provider));process.stdout.write(`Removed ${t} cached rows.\n`)}})}});function B(e){return Math.round(e).toLocaleString(`en-GB`)}function V(e){return e.toFixed(1)}function H(e){return`${(e*100).toFixed(1)}%`}function U(e,t=72){return zr(e,t)}function ea(e){return e.verification?.requested?`${e.verification.verified} checked, ${e.verification.failed} failed`:`off`}function ta(e){return{title:e.title,target:e.target,status:e.status,summary:e.summary,metrics:e.metrics,sections:(e.diagnostics??[]).map(e=>({title:e.title,diagnostics:e.items})),notes:e.notes}}function W(e){vi(ta(e))}function G(e,t){I(e,t.slice(0,25)),t.length>25&&process.stdout.write(`Showing 25 of ${t.length}. Use --json for full data.\n`)}function K(e,t,n=5){let r=t.filter(e=>e.action.trim().length>0).slice(0,n),i=na(e,r);i&&(process.stdout.write(`
38
+ `),vi(i),t.length>r.length&&process.stdout.write(`Showing ${r.length} of ${t.length}. Use --json for full data.\n`))}function na(e,t){if(t.length)return{title:e,status:`info`,summary:`${t.length} ${t.length===1?`action`:`actions`} shown.`,sections:[{title:`Actions`,diagnostics:t.map(e=>({status:`info`,title:e.label,explanation:e.context??`Review the evidence before making changes.`,fix:e.action}))}]}}function ra(e){process.stdout.write(`\nNext: ${e}\n`)}function q(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 ia=v({meta:{name:`content`,description:`Optimize page content from real GSC queries`},subCommands:{optimize:v({meta:{name:`optimize`,description:`Build a content optimization report for one URL`},args:{site:{type:`string`},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":Tr(`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=O(e),n=await ge({site:k(C(e.site)),url:C(e.url)??``,days:T(e.days),limit:T(e.limit),minImpressions:T(e[`min-impressions`]),includeBrand:w(e[`include-brand`]),verifyContent:w(e[`verify-content`])!==!1,js:w(e.js)?!0:`auto`,refresh:w(e.refresh)});if(t){P(n);return}W({title:`Content optimization`,target:n.url,status:`info`,summary:n.summary.verdict,metrics:[{label:`Score`,value:`${n.summary.score}/100`},{label:`Primary intent`,value:n.summary.primaryIntent},{label:`Primary query`,value:n.summary.primaryQuery??`None`},{label:`Queries`,value:B(n.summary.queries)},{label:`Opportunities`,value:B(n.summary.opportunities)},{label:`Estimated lift`,value:B(n.summary.estimatedClickLift)}]}),K(`Top actions`,n.topActions.map(e=>({label:e.title,action:e.action,context:e.plainEnglish}))),n.brief.sections.length&&(process.stdout.write(`
39
39
  Suggested sections
40
- `),q([`Heading`,`Queries`],n.brief.sections.map(e=>[G(e.heading,48),G(e.queries.join(`, `),80)]))),n.intentMix.length&&(process.stdout.write(`
40
+ `),G([`Heading`,`Queries`],n.brief.sections.map(e=>[U(e.heading,48),U(e.queries.join(`, `),80)]))),n.intentMix.length&&(process.stdout.write(`
41
41
  Intent mix
42
- `),q([`Intent`,`Queries`,`Impressions`,`Clicks`],n.intentMix.map(e=>[e.intent,H(e.queries),H(e.impressions),H(e.clicks)]))),Y(`Caveats`,n.caveats)}})}});var na=class{stream;buffer=``;constructor(e){this.stream=e}async write(e){this.buffer+=e,this.buffer.length>=65536&&await this.flush()}async flush(){if(!this.buffer)return;let e=this.buffer;this.buffer=``,this.stream.write(e)||await new Promise((e,t)=>{let n=()=>{i(),e()},r=e=>{i(),t(e)},i=()=>{this.stream.off(`drain`,n),this.stream.off(`error`,r)};this.stream.once(`drain`,n),this.stream.once(`error`,r)})}};function ra(e){return` `.repeat(e)}function ia(e){return e&&typeof e==`object`&&`toJSON`in e&&typeof e.toJSON==`function`?e.toJSON():e}async function aa(e,t,n,r,i=!1){let a=ia(t);if(a===null)return await e.write(`null`),!0;if(typeof a==`string`||typeof a==`boolean`)return await e.write(JSON.stringify(a)),!0;if(typeof a==`number`)return await e.write(Number.isFinite(a)?String(a):`null`),!0;if(typeof a==`bigint`&&JSON.stringify(a),a===void 0||typeof a==`function`||typeof a==`symbol`)return i&&await e.write(`null`),i;if(!a||typeof a!=`object`)return!1;if(r.has(a))throw TypeError(`Converting circular structure to JSON`);r.add(a);try{if(Array.isArray(a)){if(a.length===0)return await e.write(`[]`),!0;await e.write(`[
42
+ `),G([`Intent`,`Queries`,`Impressions`,`Clicks`],n.intentMix.map(e=>[e.intent,B(e.queries),B(e.impressions),B(e.clicks)]))),q(`Caveats`,n.caveats)}})}});var aa=class{stream;buffer=``;constructor(e){this.stream=e}async write(e){this.buffer+=e,this.buffer.length>=65536&&await this.flush()}async flush(){if(!this.buffer)return;let e=this.buffer;this.buffer=``,this.stream.write(e)||await new Promise((e,t)=>{let n=()=>{i(),e()},r=e=>{i(),t(e)},i=()=>{this.stream.off(`drain`,n),this.stream.off(`error`,r)};this.stream.once(`drain`,n),this.stream.once(`error`,r)})}};function oa(e){return` `.repeat(e)}function sa(e){return e&&typeof e==`object`&&`toJSON`in e&&typeof e.toJSON==`function`?e.toJSON():e}async function ca(e,t,n,r,i=!1){let a=sa(t);if(a===null)return await e.write(`null`),!0;if(typeof a==`string`||typeof a==`boolean`)return await e.write(JSON.stringify(a)),!0;if(typeof a==`number`)return await e.write(Number.isFinite(a)?String(a):`null`),!0;if(typeof a==`bigint`&&JSON.stringify(a),a===void 0||typeof a==`function`||typeof a==`symbol`)return i&&await e.write(`null`),i;if(!a||typeof a!=`object`)return!1;if(r.has(a))throw TypeError(`Converting circular structure to JSON`);r.add(a);try{if(Array.isArray(a)){if(a.length===0)return await e.write(`[]`),!0;await e.write(`[
43
43
  `);for(let t=0;t<a.length;t+=1)t>0&&await e.write(`,
44
- `),await e.write(ra(n+1)),await aa(e,a[t],n+1,r,!0);return await e.write(`\n${ra(n)}]`),!0}let t=Object.entries(a).filter(([,e])=>{let t=ia(e);return![`undefined`,`function`,`symbol`].includes(typeof t)});if(t.length===0)return await e.write(`{}`),!0;await e.write(`{
44
+ `),await e.write(oa(n+1)),await ca(e,a[t],n+1,r,!0);return await e.write(`\n${oa(n)}]`),!0}let t=Object.entries(a).filter(([,e])=>{let t=sa(e);return![`undefined`,`function`,`symbol`].includes(typeof t)});if(t.length===0)return await e.write(`{}`),!0;await e.write(`{
45
45
  `);for(let i=0;i<t.length;i+=1){let[a,o]=t[i];i>0&&await e.write(`,
46
- `),await e.write(`${ra(n+1)}${JSON.stringify(a)}: `),await aa(e,o,n+1,r)}return await e.write(`\n${ra(n)}}`),!0}finally{r.delete(a)}}async function oa(e,t){let n=new na(e);await aa(n,t,0,new Set)||await n.write(`undefined`),await n.write(`
47
- `),await n.flush()}async function sa(e,t){if(!e){await oa(process.stdout,t);return}await qn(Mn(e),{recursive:!0});let n=Rn(e);try{await oa(n,t),n.end(),await fr(n)}catch(e){throw n.destroy(),e}process.stdout.write(`Wrote ${e}\n`)}function ca(e){return e.length?e.join(`, `):`none`}function la(e,t){let n=e.robotsTxt,r=(n?.botAccess??[]).filter(e=>e.purpose===t),i=t===`search`?`AI search bots`:`AI training bots`;if(!n||r.some(e=>e.allowed===null))return{status:`unknown`,title:`${i}: unknown`,explanation:`robots.txt could not be read (${n?.availability??`not checked`}).`};let a=r.filter(e=>e.allowed===!1);return{status:a.length?t===`search`?`warning`:`info`:`pass`,title:a.length?`${i}: ${a.length} of ${r.length} blocked`:`${i}: all ${r.length} allowed`,explanation:a.length?`Blocked: ${ca(a.map(e=>e.userAgent))}.`:`Checked: ${ca(r.map(e=>e.userAgent))}.`}}function ua(e){let t=e.markdownNegotiation,n=e.markdownCopies,r=t?.markdown?`Accept: text/markdown returns Markdown${t.varyAccept?` with Vary: Accept`:` without Vary: Accept`}.`:t?.availability===`available`?`Accept: text/markdown returns ${t.contentType||`no content type`}.`:`Accept: text/markdown could not be checked (${t?.availability??`not checked`}).`,i=n?` ${n.publicPagesWithLink} of ${n.publicPages} public pages link to a Markdown copy${n.privatePagesWithLink?`, and ${n.privatePagesWithLink} signed-in pages do`:``}. ${n.checked} copies checked, ${n.broken.length} broken.${n.largest?` Largest checked copy: about ${n.largest.estimatedTokens} tokens.`:``}`:``;return{status:t?.markdown||n?.publicPagesWithLink?n?.broken.length||n?.privatePagesWithLink?`warning`:`pass`:t?.availability===`available`?`info`:`unknown`,title:`Markdown copies`,explanation:`${r}${i}`}}function da(e){let t=e.llmsTxt;if(!t||t.availability===void 0)return{status:`unknown`,title:`llms.txt`,explanation:`Not checked.`};if(t.availability!==`available`)return{status:t.availability===`absent`?`info`:`unknown`,title:`llms.txt`,explanation:t.availability===`absent`?`Not found (HTTP ${t.status??`none`}).`:`Could not be read (${t.availability}).`};let n=t.links;return{status:n?.broken.length?`warning`:`pass`,title:`llms.txt`,explanation:n?`${n.found} same-site links; ${n.checked} checked, ${n.broken.length} broken.`:`Present.`}}function fa(e){if(!e)return[];let t=(e.agentResources??[]).filter(e=>e.exists).map(e=>new URL(e.url).pathname),n=[...e.robotsTxt?.contentSignals??[],...e.startPageHeaders?.contentSignal?[e.startPageHeaders.contentSignal]:[]];return[la(e,`search`),la(e,`training`),ua(e),da(e),{status:`info`,title:`Agent files`,explanation:`Found: ${ca(t)}.`},{status:`info`,title:`Content-Signal and Link header`,explanation:`Content-Signal: ${ca([...new Set(n)])}. Link header relations on the start page: ${ca(e.startPageHeaders?.linkRelations??[])}.`}]}function pa(e){return e===`high`?`fail`:`warning`}function ma(e,t=Ze(e)){let n=Ge(e),r=e.status===`failed`||e.summary.highIssues>0?`fail`:e.status===`partial`?`unknown`:e.summary.mediumIssues>0||e.summary.lowIssues>0?`warning`:`pass`,i=e.access.samples.slice(0,5).map(e=>({status:`fail`,title:e.evidence.guidance.summary,explanation:`${e.evidence.provider} returned ${e.evidence.kind} evidence for the crawler request.`,evidence:[e.url],fix:e.evidence.guidance.recommendedAction})),a=t.slice(0,10).map(e=>({status:pa(e.severity),title:`${e.title} on ${e.count} ${e.count===1?`URL`:`URLs`}`,explanation:ge(e.ruleId)?.whyItMatters??e.whyThisRanks,evidence:e.sampleUrls.slice(0,5),fix:e.howToFix})),o=n.slice(0,10).map(e=>({status:`warning`,title:`${e.title} on ${e.count} ${e.count===1?`URL`:`URLs`}`,explanation:ge(e.ruleId)?.whyItMatters??e.whyThisRanks,evidence:e.sampleUrls.slice(0,5),fix:e.howToFix}));hi({title:e.config.strategy===`health`?`Sitemap health pass`:`Site crawl`,target:e.config.url,status:r,summary:e.status===`partial`?`Partial evidence from ${e.summary.totalPages} retained pages. Review the caveats before treating missing findings as an all-clear.`:`${e.summary.totalPages} pages retained with ${e.issues.length} findings.`,metrics:[{label:`Pages`,value:e.summary.totalPages},{label:`High`,value:e.summary.highIssues,status:`fail`},{label:`Medium`,value:e.summary.mediumIssues,status:`warning`},{label:`Low`,value:e.summary.lowIssues,status:`info`},{label:`Failed fetches`,value:e.summary.failedUrls}],sections:[{title:`Access diagnostics`,diagnostics:i},{title:`Prioritised fixes`,diagnostics:a},{title:`Review before changing anything`,diagnostics:o},{title:`Agent access`,diagnostics:fa(e.ai)},...!i.length&&!a.length&&!o.length?[{title:`Checks`,diagnostics:[{status:`pass`,title:`No prioritised findings were observed`,explanation:`The evaluated crawl evidence did not produce a prioritised fix or review item.`}]}]:[]],notes:[`Strategy: ${e.config.strategy}. Data: ${e.status}. Report: ${e.id}.`,...e.warnings,...e.caveats]})}const ha={"google-analytics-property":{type:`string`,description:`Google Analytics property ID for landing-page sessions.`},"clicky-site-id":{type:`string`,description:`Clicky site ID for landing-page visits.`}};function ga(e){let t=T(e[`google-analytics-property`]),n=T(e[`clicky-site-id`]);if(t&&n)throw new g(`INVALID_INPUT`,`Pass either --google-analytics-property or --clicky-site-id, not both.`);if(t)return{provider:`google`,propertyId:t};if(n)return{provider:`clicky`,siteId:Me(n)}}function _a(e){if(e.startsWith(`http://`)||e.startsWith(`https://`))return e;if(e.startsWith(`sc-domain:`))return`https://${e.slice(10)}/`}function va(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 ya(e){return e?e===`serp-framing`?`wording gap`:e===`content-gap`?`content gap`:e===`technical-check`?`technical check`:`covered`:`-`}const ba={high:3,medium:2,low:1},xa=y({meta:{name:`crawl`,description:`Run a fast sitemap health pass or a full technical crawl`},args:{crawlUrl:{type:`positional`,required:!1,description:`Site start URL. Optional with --sitemap-url.`},url:{type:`string`,description:`Start URL to crawl. Defaults to the --site home page.`},site:{type:`string`,description:`GSC property URL for joining page metrics.`},...ha,mode:{type:`string`,description:`Crawl mode: site, page, list, or sitemap. Start with sitemap --health for large sites.`},health:{type:`boolean`,default:!1,description:`Run the uncached sitemap status and redirect pass before a full audit. No page-body analysis.`},"sitemap-url":{type:`string`,description:`Explicit sitemap URL. Selects sitemap mode when --mode is omitted.`},urls:{type:`string`,description:`Comma-separated URLs for list mode.`},"urls-file":{type:`string`,description:`File with one URL per line for list mode.`},"max-pages":{type:`string`,description:`Maximum pages to crawl, up to 10,000. Defaults to 500.`},"max-depth":{type:`string`,description:`Maximum click depth, up to 64. Defaults to 16.`},concurrency:{type:`string`,description:`Parallel page fetches, up to 16. Defaults to 8, or progressively up to 4 with --health.`},"fetch-interval-cap":{type:`string`,description:`Maximum page fetches per interval per host.`},"fetch-interval-ms":{type:`string`,description:`Fetch rate interval in milliseconds.`},refresh:{type:`boolean`,default:!1,description:`Bypass local HTTP cache and fetch fresh pages.`},include:{type:`string`,description:`Comma-separated URL patterns to include.`},exclude:{type:`string`,description:`Comma-separated URL patterns to exclude.`},"no-sitemap":{type:`boolean`,default:!1,description:`Do not seed URLs from sitemap.xml.`},"no-robots":{type:`boolean`,default:!1,description:`Do not skip URLs disallowed by robots.txt.`},"no-external":{type:`boolean`,default:!1,description:`Do not check external links. Reserved for full link checks.`},js:{type:`boolean`,default:!1,description:`Legacy alias for --rendering on.`},rendering:{type:`string`,description:`JavaScript rendering mode: auto, on, or off. Defaults to auto.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON. Alias for --format json.`},format:{type:`string`,description:`Output format: pretty, json, csv, html, markdown, or junit. JUnit requires --health.`},output:{type:`string`,description:`Write output to this path instead of stdout.`},csv:{type:`string`,description:`CSV table to render: issues or pages. Defaults to issues.`},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=A(e),n=Ta(e.format,t),r=T(e.output),i=Ea(e.csv),a=Ca(e.severity),o=Ca(e[`fail-on`]),c=await Da(e),l=wa(e),u=T(e[`sitemap-url`]),d=E(e.health),ee=Sa(e.mode);if(n===`junit`&&!d)throw Error(`--format junit requires --health.`);if((d||u)&&ee&&ee!==`sitemap`)throw Error(`--health and --sitemap-url require sitemap mode.`);if(d&&wr(e,`sitemap`))throw Error(`--health cannot be combined with --no-sitemap.`);if((d||u)&&c.length)throw Error(`--health and --sitemap-url cannot be combined with URL list input.`);let f=T(e.site),te=l??(f?_a(f):void 0)??(u?new URL(`/`,u).toString():void 0)??c[0];if(!te)throw new g(`INVALID_INPUT`,"No crawl URL given. Pass a URL, --url or --site. Run `seo sites` to list your Search Console properties.");let p=await ne({url:te,site:f,mode:ee??(d||u?`sitemap`:c.length?`list`:void 0),strategy:d?`health`:`full`,sitemapUrl:u,urls:c,maxPages:D(e[`max-pages`]),maxDepth:D(e[`max-depth`]),concurrency:D(e.concurrency),refresh:E(e.refresh),fetchRate:Or({...e,"fetch-concurrency":e.concurrency}),include:k(e.include),exclude:k(e.exclude),googleAnalyticsPropertyId:T(e[`google-analytics-property`]),analyticsConnection:ga(e),useSitemap:!wr(e,`sitemap`),respectRobots:!wr(e,`robots`),checkExternal:!wr(e,`external`),js:Cr(e)}),re=Ze(p,{severity:a}),m=o?p.issues.some(e=>ba[e.severity]>=ba[o]):!1,h=p.status===`failed`,ie={...p,topFixes:re,reviewObservations:Ge(p,{severity:a}),...o?{failOn:o,failedThreshold:m}:{}};if(n===`json`){await sa(r,ie),(h||m)&&(process.exitCode=1);return}if(n===`csv`){await ka(r,i===`pages`?Je(p):be(p)),(h||m)&&(process.exitCode=1);return}if(n===`html`){await ka(r,Le(p,re)),(h||m)&&(process.exitCode=1);return}if(n===`junit`){await ka(r,Dn(p)),(h||m)&&(process.exitCode=1);return}if(n===`markdown`){await ka(r,s(p,re)),(h||m)&&(process.exitCode=1);return}if(r){await ka(r,le(p,re)),(h||m)&&(process.exitCode=1);return}ma(p,re),o&&(process.stdout.write(`
46
+ `),await e.write(`${oa(n+1)}${JSON.stringify(a)}: `),await ca(e,o,n+1,r)}return await e.write(`\n${oa(n)}}`),!0}finally{r.delete(a)}}async function la(e,t){let n=new aa(e);await ca(n,t,0,new Set)||await n.write(`undefined`),await n.write(`
47
+ `),await n.flush()}async function ua(e,t){if(!e){await la(process.stdout,t);return}await Yn(Pn(e),{recursive:!0});let n=Bn(e);try{await la(n,t),n.end(),await hr(n)}catch(e){throw n.destroy(),e}process.stdout.write(`Wrote ${e}\n`)}function da(e){return e.length?e.join(`, `):`none`}function fa(e,t){let n=e.robotsTxt,r=(n?.botAccess??[]).filter(e=>e.purpose===t),i=t===`search`?`AI search bots`:`AI training bots`;if(!n||r.some(e=>e.allowed===null))return{status:`unknown`,title:`${i}: unknown`,explanation:`robots.txt could not be read (${n?.availability??`not checked`}).`};let a=r.filter(e=>e.allowed===!1);return{status:a.length?t===`search`?`warning`:`info`:`pass`,title:a.length?`${i}: ${a.length} of ${r.length} blocked`:`${i}: all ${r.length} allowed`,explanation:a.length?`Blocked: ${da(a.map(e=>e.userAgent))}.`:`Checked: ${da(r.map(e=>e.userAgent))}.`}}function pa(e){let t=e.markdownNegotiation,n=e.markdownCopies,r=t?.markdown?`Accept: text/markdown returns Markdown${t.varyAccept?` with Vary: Accept`:` without Vary: Accept`}.`:t?.availability===`available`?`Accept: text/markdown returns ${t.contentType||`no content type`}.`:`Accept: text/markdown could not be checked (${t?.availability??`not checked`}).`,i=n?` ${n.publicPagesWithLink} of ${n.publicPages} public pages link to a Markdown copy${n.privatePagesWithLink?`, and ${n.privatePagesWithLink} signed-in pages do`:``}. ${n.checked} copies checked, ${n.broken.length} broken.${n.largest?` Largest checked copy: about ${n.largest.estimatedTokens} tokens.`:``}`:``;return{status:t?.markdown||n?.publicPagesWithLink?n?.broken.length||n?.privatePagesWithLink?`warning`:`pass`:t?.availability===`available`?`info`:`unknown`,title:`Markdown copies`,explanation:`${r}${i}`}}function ma(e){let t=e.llmsTxt;if(!t||t.availability===void 0)return{status:`unknown`,title:`llms.txt`,explanation:`Not checked.`};if(t.availability!==`available`)return{status:t.availability===`absent`?`info`:`unknown`,title:`llms.txt`,explanation:t.availability===`absent`?`Not found (HTTP ${t.status??`none`}).`:`Could not be read (${t.availability}).`};let n=t.links;return{status:n?.broken.length?`warning`:`pass`,title:`llms.txt`,explanation:n?`${n.found} same-site links; ${n.checked} checked, ${n.broken.length} broken.`:`Present.`}}function ha(e){if(!e)return[];let t=(e.agentResources??[]).filter(e=>e.exists).map(e=>new URL(e.url).pathname),n=[...e.robotsTxt?.contentSignals??[],...e.startPageHeaders?.contentSignal?[e.startPageHeaders.contentSignal]:[]];return[fa(e,`search`),fa(e,`training`),pa(e),ma(e),{status:`info`,title:`Agent files`,explanation:`Found: ${da(t)}.`},{status:`info`,title:`Content-Signal and Link header`,explanation:`Content-Signal: ${da([...new Set(n)])}. Link header relations on the start page: ${da(e.startPageHeaders?.linkRelations??[])}.`}]}function ga(e){return e===`high`?`fail`:`warning`}function _a(e,t=se(e)){let n=$e(e),r=e.status===`failed`||e.summary.highIssues>0?`fail`:e.status===`partial`?`unknown`:e.summary.mediumIssues>0||e.summary.lowIssues>0?`warning`:`pass`,i=e.access.samples.slice(0,5).map(e=>({status:`fail`,title:e.evidence.guidance.summary,explanation:`${e.evidence.provider} returned ${e.evidence.kind} evidence for the crawler request.`,evidence:[e.url],fix:e.evidence.guidance.recommendedAction})),a=t.slice(0,10).map(e=>({status:ga(e.severity),title:`${e.title} on ${e.count} ${e.count===1?`URL`:`URLs`}`,explanation:nn(e.ruleId)?.whyItMatters??e.whyThisRanks,evidence:e.sampleUrls.slice(0,5),fix:e.howToFix})),o=n.slice(0,10).map(e=>({status:`warning`,title:`${e.title} on ${e.count} ${e.count===1?`URL`:`URLs`}`,explanation:nn(e.ruleId)?.whyItMatters??e.whyThisRanks,evidence:e.sampleUrls.slice(0,5),fix:e.howToFix}));vi({title:e.config.strategy===`health`?`Sitemap health pass`:`Site crawl`,target:e.config.url,status:r,summary:e.status===`partial`?`Partial evidence from ${e.summary.totalPages} retained pages. Review the caveats before treating missing findings as an all-clear.`:`${e.summary.totalPages} pages retained with ${e.issues.length} findings.`,metrics:[{label:`Pages`,value:e.summary.totalPages},{label:`High`,value:e.summary.highIssues,status:`fail`},{label:`Medium`,value:e.summary.mediumIssues,status:`warning`},{label:`Low`,value:e.summary.lowIssues,status:`info`},{label:`Failed fetches`,value:e.summary.failedUrls}],sections:[{title:`Access diagnostics`,diagnostics:i},{title:`Prioritised fixes`,diagnostics:a},{title:`Review before changing anything`,diagnostics:o},{title:`Agent access`,diagnostics:ha(e.ai)},...!i.length&&!a.length&&!o.length?[{title:`Checks`,diagnostics:[{status:`pass`,title:`No prioritised findings were observed`,explanation:`The evaluated crawl evidence did not produce a prioritised fix or review item.`}]}]:[]],notes:[`Strategy: ${e.config.strategy}. Data: ${e.status}. Report: ${e.id}.`,...e.warnings,...e.caveats]})}const va={"google-analytics-property":{type:`string`,description:`Google Analytics property ID for landing-page sessions.`},"clicky-site-id":{type:`string`,description:`Clicky site ID for landing-page visits.`}};function ya(e){let t=C(e[`google-analytics-property`]),n=C(e[`clicky-site-id`]);if(t&&n)throw new h(`INVALID_INPUT`,`Pass either --google-analytics-property or --clicky-site-id, not both.`);if(t)return{provider:`google`,propertyId:t};if(n)return{provider:`clicky`,siteId:m(n)}}function ba(e){if(e.startsWith(`http://`)||e.startsWith(`https://`))return e;if(e.startsWith(`sc-domain:`))return`https://${e.slice(10)}/`}function xa(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 Sa(e){return e?e===`serp-framing`?`wording gap`:e===`content-gap`?`content gap`:e===`technical-check`?`technical check`:`covered`:`-`}const Ca={high:3,medium:2,low:1},wa=v({meta:{name:`crawl`,description:`Run a fast sitemap health pass or a full technical crawl`},args:{crawlUrl:{type:`positional`,required:!1,description:`Site start URL. Optional with --sitemap-url.`},url:{type:`string`,description:`Start URL to crawl. Defaults to the --site home page.`},site:{type:`string`,description:`GSC property URL for joining page metrics.`},...va,mode:{type:`string`,description:`Crawl mode: site, page, list, or sitemap. Start with sitemap --health for large sites.`},health:{type:`boolean`,default:!1,description:`Run the uncached sitemap status and redirect pass before a full audit. No page-body analysis.`},"sitemap-url":{type:`string`,description:`Explicit sitemap URL. Selects sitemap mode when --mode is omitted.`},urls:{type:`string`,description:`Comma-separated URLs for list mode.`},"urls-file":{type:`string`,description:`File with one URL per line for list mode.`},"max-pages":{type:`string`,description:`Maximum pages to crawl, up to 10,000. Defaults to 500.`},"max-depth":{type:`string`,description:`Maximum click depth, up to 64. Defaults to 16.`},concurrency:{type:`string`,description:`Parallel page fetches, up to 16. Defaults to 8, or progressively up to 4 with --health.`},"fetch-interval-cap":{type:`string`,description:`Maximum page fetches per interval per host.`},"fetch-interval-ms":{type:`string`,description:`Fetch rate interval in milliseconds.`},refresh:{type:`boolean`,default:!1,description:`Bypass local HTTP cache and fetch fresh pages.`},include:{type:`string`,description:`Comma-separated URL patterns to include.`},exclude:{type:`string`,description:`Comma-separated URL patterns to exclude.`},"no-sitemap":{type:`boolean`,default:!1,description:`Do not seed URLs from sitemap.xml.`},"no-robots":{type:`boolean`,default:!1,description:`Do not skip URLs disallowed by robots.txt.`},"no-external":{type:`boolean`,default:!1,description:`Do not check external links. Reserved for full link checks.`},js:{type:`boolean`,default:!1,description:`Legacy alias for --rendering on.`},rendering:{type:`string`,description:`JavaScript rendering mode: auto, on, or off. Defaults to auto.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON. Alias for --format json.`},format:{type:`string`,description:`Output format: pretty, json, csv, html, markdown, or junit. JUnit requires --health.`},output:{type:`string`,description:`Write output to this path instead of stdout.`},csv:{type:`string`,description:`CSV table to render: issues or pages. Defaults to issues.`},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=O(e),n=Oa(e.format,t),r=C(e.output),i=ka(e.csv),a=Ea(e.severity),o=Ea(e[`fail-on`]),c=await Aa(e),l=Da(e),u=C(e[`sitemap-url`]),d=w(e.health),ee=Ta(e.mode);if(n===`junit`&&!d)throw Error(`--format junit requires --health.`);if((d||u)&&ee&&ee!==`sitemap`)throw Error(`--health and --sitemap-url require sitemap mode.`);if(d&&Dr(e,`sitemap`))throw Error(`--health cannot be combined with --no-sitemap.`);if((d||u)&&c.length)throw Error(`--health and --sitemap-url cannot be combined with URL list input.`);let ne=C(e.site),re=l??(ne?ba(ne):void 0)??(u?new URL(`/`,u).toString():void 0)??c[0];if(!re)throw new h(`INVALID_INPUT`,"No crawl URL given. Pass a URL, --url or --site. Run `seo sites` to list your Search Console properties.");let f=await te({url:re,site:ne,mode:ee??(d||u?`sitemap`:c.length?`list`:void 0),strategy:d?`health`:`full`,sitemapUrl:u,urls:c,maxPages:T(e[`max-pages`]),maxDepth:T(e[`max-depth`]),concurrency:T(e.concurrency),refresh:w(e.refresh),fetchRate:jr({...e,"fetch-concurrency":e.concurrency}),include:D(e.include),exclude:D(e.exclude),googleAnalyticsPropertyId:C(e[`google-analytics-property`]),analyticsConnection:ya(e),useSitemap:!Dr(e,`sitemap`),respectRobots:!Dr(e,`robots`),checkExternal:!Dr(e,`external`),js:Er(e)}),ie=se(f,{severity:a}),p=o?f.issues.some(e=>Ca[e.severity]>=Ca[o]):!1,m=f.status===`failed`,ae={...f,topFixes:ie,reviewObservations:$e(f,{severity:a}),...o?{failOn:o,failedThreshold:p}:{}};if(n===`json`){await ua(r,ae),(m||p)&&(process.exitCode=1);return}if(n===`csv`){await Ma(r,i===`pages`?ue(f):ze(f)),(m||p)&&(process.exitCode=1);return}if(n===`html`){await Ma(r,kn(f,ie)),(m||p)&&(process.exitCode=1);return}if(n===`junit`){await Ma(r,s(f)),(m||p)&&(process.exitCode=1);return}if(n===`markdown`){await Ma(r,Ze(f,ie)),(m||p)&&(process.exitCode=1);return}if(r){await Ma(r,Je(f,ie)),(m||p)&&(process.exitCode=1);return}_a(f,ie),o&&(process.stdout.write(`
48
48
  Run metadata
49
- `),L([[`Fail threshold`,o]])),(h||m)&&(process.exitCode=1)}});function Sa(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 Ca(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 wa(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 Ta(e,t){let n=T(e);if(t&&n&&n!==`json`)throw Error(`Use either --json or --format, not both.`);if(!n)return t?`json`:`pretty`;if([`pretty`,`json`,`csv`,`html`,`markdown`,`junit`].includes(n))return n;throw Error(`Format must be one of: pretty, json, csv, html, markdown, junit.`)}function Ea(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 Da(e){let t=k(e.urls)??[],n=T(e[`urls-file`]);return n&&t.push(...Oa(await Jn(n,`utf8`))),[...new Set(t)]}function Oa(e){return e.split(/[\n,]/).map(e=>e.trim()).filter(e=>e&&!e.startsWith(`#`))}async function ka(e,t){if(!e){process.stdout.write(t);return}await qn(Mn(e),{recursive:!0}),await Yn(e,t),process.stdout.write(`Wrote ${e}\n`)}const Aa=y({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.`},account:{type:`string`,description:`Saved Google account email.`},"start-date":{type:`string`,description:`Start date YYYY-MM-DD.`},"end-date":{type:`string`,description:`End date YYYY-MM-DD.`},dimensions:{type:`string`,description:`Comma-separated GSC dimensions, for example query,page.`},type:{type:`string`,description:`Search type. Defaults to web.`},limit:{type:`string`,description:`Maximum rows to request and print.`},body:{type:`string`,description:`Raw Search Analytics JSON body.`},"body-file":{type:`string`,description:`Path to a Search Analytics JSON body file.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`},refresh:{type:`boolean`,default:!1,description:`Bypass local cache and fetch fresh GSC data.`}},run:async({args:e})=>{let t=await kr(e.body,e[`body-file`])??{startDate:T(e[`start-date`]),endDate:T(e[`end-date`]),dimensions:k(e.dimensions)??[`query`,`page`],type:T(e.type)??`web`,rowLimit:D(e.limit),dataState:`final`},n=A(e),r=j(T(e.site)??T(t.siteUrl));delete t.siteUrl;let i=await rt(r,t,{refresh:E(e.refresh),accountEmail:T(e.account)}),a=D(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}K({title:`Search Console query`,target:r,status:i.rowsFetched>o.length?`unknown`:`info`,summary:`${o.length} rows returned from ${i.rowsFetched} fetched rows.`,metrics:[{label:`Rows returned`,value:o.length},{label:`Rows fetched`,value:i.rowsFetched},{label:`API calls`,value:i.calls}]}),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)]))}}),ja=y({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.`},account:{type:`string`,description:`Saved Google account email.`},url:{type:`string`,description:`URL to inspect.`},language:{type:`string`,description:`Optional inspection language code.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=A(e),n=j(T(e.site)),r=T(e.url);if(!r)throw Error(`Pass --url.`);let i=await Pe({siteUrl:n,inspectionUrl:r,accountEmail:T(e.account),languageCode:T(e.language)});if(t){I(i);return}let a=i.inspectionResult?.indexStatusResult;K({title:`URL Inspection`,target:r,status:a===void 0?`unknown`:a.verdict===`PASS`?`pass`:`warning`,summary:a===void 0?`Google did not return index status evidence for this URL.`:`${a.verdict??`Unknown`}: ${a.coverageState??`Coverage state unavailable`}.`,metrics:[{label:`Property`,value:n},{label:`Robots`,value:a?.robotsTxtState??`Unknown`},{label:`Last crawl`,value:a?.lastCrawlTime??`Unknown`},{label:`Google canonical`,value:a?.googleCanonical??`Unknown`}]})}}),Ma={days:{cli:`days`,arg:{type:`string`,description:`GSC lookback window. Defaults to 28.`}},recentDays:{cli:`recent`,arg:{type:`string`,description:`Recent anomaly window in days. Defaults to 14.`}},range:{cli:`days`,arg:{type:`string`,description:`GSC lookback window. Defaults to 28.`}},limit:{cli:`limit`,arg:{type:`string`,description:`Maximum rows to return.`}},checkLimit:{cli:`check-limit`,arg:{type:`string`,description:`Maximum candidate pages to fetch and check.`}},minImpressions:{cli:`min-impressions`,arg:{type:`string`,description:`Minimum query impressions.`}},includeBrand:{cli:`include-brand`,arg:{type:`boolean`,default:!1,description:`Include branded queries in opportunity reports.`}},verifyContent:{cli:`verify-content`,arg:{type:`boolean`,default:!1,description:`Verify top opportunities against page title, meta, and content.`}},verifyLimit:{cli:`verify-limit`,arg:{type:`string`,description:`Maximum opportunity URLs to verify.`}},js:{cli:`js`,arg:{type:`boolean`,default:!1,description:`Force JavaScript rendering for verified pages.`}},fetchConcurrency:{cli:`fetch-concurrency`,arg:{type:`string`,description:`Maximum concurrent page fetches per host, up to 16. Defaults to 4.`}},fetchIntervalCap:{cli:`fetch-interval-cap`,arg:{type:`string`,description:`Maximum page fetches per interval per host. Defaults to 4.`}},fetchIntervalMs:{cli:`fetch-interval-ms`,arg:{type:`string`,description:`Fetch rate interval in milliseconds. Defaults to 1000.`}},refresh:{cli:`refresh`,arg:{type:`boolean`,default:!1,description:`Bypass local cache and fetch fresh data.`}}};function X(e,t={}){return Object.fromEntries(e.map(e=>{let n=Ma[e];return[n.cli,{...n.arg,...t[e]}]}))}function Na(e){return e.toLocaleString(`en-GB`,{maximumFractionDigits:e>=100?0:1})}function Pa(e){if(e===null)return`n/a`;let t=Math.abs(e)>=10?e.toFixed(0):e.toFixed(1);return`${e>0?`+`:``}${t}%`}const Fa=y({meta:{name:`traffic-anomaly`,description:`Detect unusual recent GSC traffic movement`},args:{site:{type:`string`},...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=A(e),n=await Et({site:j(T(e.site)),days:D(e.days),recentDays:D(e.recent),refresh:E(e.refresh)});if(t){I(n);return}K({title:`Traffic anomaly report`,target:n.site,status:n.coverage?.status===`complete`?n.anomalies.some(e=>e.significant)?`warning`:`pass`:`unknown`,summary:n.anomalies.some(e=>e.significant)?`At least one recent metric moved beyond the report threshold.`:`No significant recent movement was found in the retained evidence.`,metrics:[{label:`Metrics checked`,value:n.anomalies.length},{label:`Significant`,value:n.anomalies.filter(e=>e.significant).length},{label:`Observed days`,value:n.coverage?.observedDays??`Unavailable`},{label:`Expected days`,value:n.coverage?.expectedDays??`Unavailable`}]}),q([`Metric`,`Direction`,`Baseline/day`,`Recent/day`,`Change`,`z`,`Significant`],n.anomalies.map(e=>[e.metric,e.direction,Na(e.baselineMean),Na(e.comparisonMean),Pa(e.percentChange),e.zScore??`n/a`,e.significant?`yes`:`no`])),n.coverage&&Y(`Evidence coverage`,[`${n.coverage.observedDays} of ${n.coverage.expectedDays} requested calendar days returned date aggregates.`,...n.coverage.caveats])}}),Ia=y({meta:{name:`update-correlate`,description:`Compare traffic movement with official Google update windows`},args:{site:{type:`string`},...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.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=A(e),n=j(T(e.site)),r=await ot({site:n,days:D(e.days),recentDays:D(e.recent),paddingDays:D(e[`padding-days`]),knownConfounders:T(e[`known-change`])?[T(e[`known-change`])??``]:void 0,refresh:E(e.refresh)});if(t){I(r);return}K({title:`Google update correlation`,target:r.site,status:`unknown`,summary:r.summary,metrics:[{label:`Classification`,value:r.classification.replaceAll(`-`,` `)},{label:`Attribution`,value:r.attribution.replaceAll(`-`,` `)},{label:`Confidence`,value:r.confidence},{label:`Updates matched`,value:r.overlappingUpdates.length},{label:`Known confounders`,value:r.confounders.length},{label:`Source`,value:r.source.name}]}),Y(`Evidence`,r.evidence),Y(`Report caveats`,r.caveats),Y(`Recommended next checks`,r.actions),q([`Metric`,`Direction`,`Baseline/day`,`Recent/day`,`Change`,`z`],r.anomalies.map(e=>[e.metric,e.direction,Na(e.baselineMean),Na(e.comparisonMean),Pa(e.percentChange),e.zScore??`n/a`])),r.overlappingUpdates.length&&(process.stdout.write(`
49
+ `),F([[`Fail threshold`,o]])),(m||p)&&(process.exitCode=1)}});function Ta(e){let t=C(e);if(t){if([`site`,`page`,`list`,`sitemap`].includes(t))return t;throw Error(`Mode must be one of: site, page, list, sitemap.`)}}function Ea(e){let t=C(e);if(t){if([`low`,`medium`,`high`].includes(t))return t;throw Error(`Severity must be one of: low, medium, high.`)}}function Da(e){let t=C(e.crawlUrl),n=C(e.url);if(t&&n&&t!==n)throw Error(`Use either a URL argument or --url, not both.`);return t??n}function Oa(e,t){let n=C(e);if(t&&n&&n!==`json`)throw Error(`Use either --json or --format, not both.`);if(!n)return t?`json`:`pretty`;if([`pretty`,`json`,`csv`,`html`,`markdown`,`junit`].includes(n))return n;throw Error(`Format must be one of: pretty, json, csv, html, markdown, junit.`)}function ka(e){let t=C(e);if(!t)return`issues`;if([`issues`,`pages`].includes(t))return t;throw Error(`CSV table must be one of: issues, pages.`)}async function Aa(e){let t=D(e.urls)??[],n=C(e[`urls-file`]);return n&&t.push(...ja(await Xn(n,`utf8`))),[...new Set(t)]}function ja(e){return e.split(/[\n,]/).map(e=>e.trim()).filter(e=>e&&!e.startsWith(`#`))}async function Ma(e,t){if(!e){process.stdout.write(t);return}await Yn(Pn(e),{recursive:!0}),await Zn(e,t),process.stdout.write(`Wrote ${e}\n`)}const Na=v({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.`},account:{type:`string`,description:`Saved Google account email.`},"start-date":{type:`string`,description:`Start date YYYY-MM-DD.`},"end-date":{type:`string`,description:`End date YYYY-MM-DD.`},dimensions:{type:`string`,description:`Comma-separated GSC dimensions, for example query,page.`},type:{type:`string`,description:`Search type. Defaults to web.`},limit:{type:`string`,description:`Maximum rows to request and print.`},body:{type:`string`,description:`Raw Search Analytics JSON body.`},"body-file":{type:`string`,description:`Path to a Search Analytics JSON body file.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`},refresh:{type:`boolean`,default:!1,description:`Bypass local cache and fetch fresh GSC data.`}},run:async({args:e})=>{let t=await Mr(e.body,e[`body-file`])??{startDate:C(e[`start-date`]),endDate:C(e[`end-date`]),dimensions:D(e.dimensions)??[`query`,`page`],type:C(e.type)??`web`,rowLimit:T(e.limit),dataState:`final`},n=O(e),r=k(C(e.site)??C(t.siteUrl));delete t.siteUrl;let i=await Ie(r,t,{refresh:w(e.refresh),accountEmail:C(e.account)}),a=T(e.limit),o=a?i.rows.slice(0,a):i.rows;if(n){P({site:r,request:t,...i,rows:o,rowsReturned:o.length});return}W({title:`Search Console query`,target:r,status:i.rowsFetched>o.length?`unknown`:`info`,summary:`${o.length} rows returned from ${i.rowsFetched} fetched rows.`,metrics:[{label:`Rows returned`,value:o.length},{label:`Rows fetched`,value:i.rowsFetched},{label:`API calls`,value:i.calls}]}),I([`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)]))}}),Pa=v({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.`},account:{type:`string`,description:`Saved Google account email.`},url:{type:`string`,description:`URL to inspect.`},language:{type:`string`,description:`Optional inspection language code.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let n=O(e),r=k(C(e.site)),i=C(e.url);if(!i)throw Error(`Pass --url.`);let a=await t({siteUrl:r,inspectionUrl:i,accountEmail:C(e.account),languageCode:C(e.language)});if(n){P(a);return}let o=a.inspectionResult?.indexStatusResult;W({title:`URL Inspection`,target:i,status:o===void 0?`unknown`:o.verdict===`PASS`?`pass`:`warning`,summary:o===void 0?`Google did not return index status evidence for this URL.`:`${o.verdict??`Unknown`}: ${o.coverageState??`Coverage state unavailable`}.`,metrics:[{label:`Property`,value:r},{label:`Robots`,value:o?.robotsTxtState??`Unknown`},{label:`Last crawl`,value:o?.lastCrawlTime??`Unknown`},{label:`Google canonical`,value:o?.googleCanonical??`Unknown`}]})}}),Fa={days:{cli:`days`,arg:{type:`string`,description:`GSC lookback window. Defaults to 28.`}},recentDays:{cli:`recent`,arg:{type:`string`,description:`Recent anomaly window in days. Defaults to 14.`}},range:{cli:`days`,arg:{type:`string`,description:`GSC lookback window. Defaults to 28.`}},limit:{cli:`limit`,arg:{type:`string`,description:`Maximum rows to return.`}},checkLimit:{cli:`check-limit`,arg:{type:`string`,description:`Maximum candidate pages to fetch and check.`}},minImpressions:{cli:`min-impressions`,arg:{type:`string`,description:`Minimum query impressions.`}},includeBrand:{cli:`include-brand`,arg:{type:`boolean`,default:!1,description:`Include branded queries in opportunity reports.`}},verifyContent:{cli:`verify-content`,arg:{type:`boolean`,default:!1,description:`Verify top opportunities against page title, meta, and content.`}},verifyLimit:{cli:`verify-limit`,arg:{type:`string`,description:`Maximum opportunity URLs to verify.`}},js:{cli:`js`,arg:{type:`boolean`,default:!1,description:`Force JavaScript rendering for verified pages.`}},fetchConcurrency:{cli:`fetch-concurrency`,arg:{type:`string`,description:`Maximum concurrent page fetches per host, up to 16. Defaults to 4.`}},fetchIntervalCap:{cli:`fetch-interval-cap`,arg:{type:`string`,description:`Maximum page fetches per interval per host. Defaults to 4.`}},fetchIntervalMs:{cli:`fetch-interval-ms`,arg:{type:`string`,description:`Fetch rate interval in milliseconds. Defaults to 1000.`}},refresh:{cli:`refresh`,arg:{type:`boolean`,default:!1,description:`Bypass local cache and fetch fresh data.`}}};function J(e,t={}){return Object.fromEntries(e.map(e=>{let n=Fa[e];return[n.cli,{...n.arg,...t[e]}]}))}function Ia(e){return e.toLocaleString(`en-GB`,{maximumFractionDigits:e>=100?0:1})}function La(e){if(e===null)return`n/a`;let t=Math.abs(e)>=10?e.toFixed(0):e.toFixed(1);return`${e>0?`+`:``}${t}%`}const Ra=v({meta:{name:`traffic-anomaly`,description:`Detect unusual recent GSC traffic movement`},args:{site:{type:`string`},...J([`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=O(e),n=await ct({site:k(C(e.site)),days:T(e.days),recentDays:T(e.recent),refresh:w(e.refresh)});if(t){P(n);return}W({title:`Traffic anomaly report`,target:n.site,status:n.coverage?.status===`complete`?n.anomalies.some(e=>e.significant)?`warning`:`pass`:`unknown`,summary:n.anomalies.some(e=>e.significant)?`At least one recent metric moved beyond the report threshold.`:`No significant recent movement was found in the retained evidence.`,metrics:[{label:`Metrics checked`,value:n.anomalies.length},{label:`Significant`,value:n.anomalies.filter(e=>e.significant).length},{label:`Observed days`,value:n.coverage?.observedDays??`Unavailable`},{label:`Expected days`,value:n.coverage?.expectedDays??`Unavailable`}]}),G([`Metric`,`Direction`,`Baseline/day`,`Recent/day`,`Change`,`z`,`Significant`],n.anomalies.map(e=>[e.metric,e.direction,Ia(e.baselineMean),Ia(e.comparisonMean),La(e.percentChange),e.zScore??`n/a`,e.significant?`yes`:`no`])),n.coverage&&q(`Evidence coverage`,[`${n.coverage.observedDays} of ${n.coverage.expectedDays} requested calendar days returned date aggregates.`,...n.coverage.caveats])}}),za=v({meta:{name:`update-correlate`,description:`Compare traffic movement with official Google update windows`},args:{site:{type:`string`},...J([`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.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=O(e),n=k(C(e.site)),r=await _n({site:n,days:T(e.days),recentDays:T(e.recent),paddingDays:T(e[`padding-days`]),knownConfounders:C(e[`known-change`])?[C(e[`known-change`])??``]:void 0,refresh:w(e.refresh)});if(t){P(r);return}W({title:`Google update correlation`,target:r.site,status:`unknown`,summary:r.summary,metrics:[{label:`Classification`,value:r.classification.replaceAll(`-`,` `)},{label:`Attribution`,value:r.attribution.replaceAll(`-`,` `)},{label:`Confidence`,value:r.confidence},{label:`Updates matched`,value:r.overlappingUpdates.length},{label:`Known confounders`,value:r.confounders.length},{label:`Source`,value:r.source.name}]}),q(`Evidence`,r.evidence),q(`Report caveats`,r.caveats),q(`Recommended next checks`,r.actions),G([`Metric`,`Direction`,`Baseline/day`,`Recent/day`,`Change`,`z`],r.anomalies.map(e=>[e.metric,e.direction,Ia(e.baselineMean),Ia(e.comparisonMean),La(e.percentChange),e.zScore??`n/a`])),r.overlappingUpdates.length&&(process.stdout.write(`
50
50
  Updates
51
- `),R([`Start`,`End`,`Type`,`Name`],r.overlappingUpdates.map(e=>[e.start.slice(0,10),e.end?.slice(0,10)??`open`,e.type,e.name]))),ea(`seo segment-impact --site ${JSON.stringify(n)} --dimension page`)}}),La={site:{type:`string`,description:`Search Console property, for example sc-domain:example.com.`}};function Ra(e){return{type:`string`,description:`Output folder. Defaults to ./seo-export/<site>-${e}-YYYY-MM-DD.`}}function za(e){return e.toLowerCase().replace(/^sc-domain:/,``).replace(/^https?:\/\//,``).replace(/[^a-z0-9._-]+/g,`-`).replace(/^-+|-+$/g,``)}function Ba(e){let t=new Date().toISOString().slice(0,10),n=za(e.site);return Pn(process.cwd(),`seo-export`,`${n}-${e.report}-${t}`)}async function Va(e,t){await qn(e,{recursive:!0});let n=[];for(let r of t){let t=Pn(e,r.filename);await Yn(t,_e(r.rows,r.headers),`utf8`),n.push(t)}return n}function Ha(e,t){process.stdout.write(`Wrote ${vn(t.length,`CSV file`)} to ${e}\n`);for(let e of t)process.stdout.write(`- ${e}\n`)}const Ua=y({meta:{name:`refresh-priorities`,description:`Export the ranked SEO priority queue, score breakdowns, grouped findings, and diagnosis detail CSVs.`},args:{...La,out:Ra(`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.`}}),...ha,"verify-content":Sr(`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=j(T(e.site)),n=await on({site:t,days:D(e.days),recentDays:D(e.recent),limit:D(e.limit),includeBrand:E(e[`include-brand`]),googleAnalyticsPropertyId:T(e[`google-analytics-property`]),analyticsConnection:ga(e),verifyContent:E(e[`verify-content`]),verifyLimit:D(e[`verify-limit`]),refresh:E(e.refresh)}),r=T(e.out)??Ba({report:`refresh-priorities`,site:t});Ha(r,await Va(r,oe(n)))}}),Wa=y({meta:{name:`export`,description:`Export full report data to CSV files`},subCommands:{"update-postmortem":y({meta:{name:`update-postmortem`,description:`Export update postmortem attribution, findings, template movement, and segment movers.`},args:{...La,out:Ra(`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.`}},run:async({args:e})=>{let t=j(T(e.site)),n=await Yt({site:t,days:D(e.days),recentDays:D(e.recent),limit:D(e.limit),includeBrand:E(e[`include-brand`]),knownConfounders:T(e[`known-change`])?[T(e[`known-change`])??``]:void 0,refresh:E(e.refresh)}),r=T(e.out)??Ba({report:`update-postmortem`,site:t});Ha(r,await Va(r,br(n)))}}),"refresh-priorities":Ua}});function Ga(e){let t=e.platform===`win32`?Fn:Nn;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 Ka(e={}){let t=e.home??kn(),n=e.platform??process.platform,r=Ga({home:t,platform:n,env:e.env??process.env}),i=n===`win32`?Fn:Nn,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 qa(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=_(e,t);if(r(n))return n}for(let e of a){let t=_(Mn(i),e);if(r(t))return t}return e}function Ja(e){return qa(e)!==e}function Ya(e={}){let t=e.targets??Ka(),n=e.hasCommand??Ja,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 Xa={insertSpaces:!0,tabSize:2,eol:`
52
- `};function Za(){return new Date().toISOString().replace(/[:.]/g,`-`)}function Qa(e,t){zn(Mn(e),{recursive:!0,mode:448});let n=`${e}.${process.pid}.${Date.now()}.tmp`;Gn(n,t,{encoding:`utf8`,mode:v(e)?Wn(e).mode&511:384}),Un(n,e)}function $a(e){let t=`${e}.bak-${Za()}`,n=t,r=2;for(;v(n);)n=`${t}-${r}`,r+=1;Ln(e,n,In.COPYFILE_EXCL)}function eo(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function to(e){if(!v(e.path))return{source:`{}
53
- `,config:{}};let t=Bn(e.path,`utf8`),n=[],r=hr(t,n,{allowTrailingComma:!0});if(n.length>0){let t=n[0],r=t?`${gr(t.error)} at offset ${t.offset}`:`unknown parse error`;throw Error(`Cannot update ${e.path}: invalid JSONC (${r}).`)}if(!eo(r))throw Error(`Cannot update ${e.path}: config root must be an object.`);if(r.mcpServers!==void 0&&!eo(r.mcpServers))throw Error(`Cannot update ${e.path}: mcpServers must be an object.`);return{source:t,config:r}}function no(e){return e.includes(`mcp`)&&e.includes(`serve`)&&e.some(e=>e===`seo`||e.startsWith(`seo@`)||e===`@seo/cli`)}function ro(e,t){return typeof e.command!=`string`||t.length!==3||t[1]!==`mcp`||t[2]!==`serve`||!eo(e.env)?!1:e.env.SEO_MCP_CONFIGURED_BY===`seo`}function io(e){if(!eo(e)||!Array.isArray(e.args))return!1;let t=e.args.filter(e=>typeof e==`string`);return no(t)||ro(e,t)}function ao(e){let t=pr(e.source,mr(e.source,[`mcpServers`,e.name??`seo`],e.value,{formattingOptions:Xa}));v(e.target.path)&&$a(e.target.path),Qa(e.target.path,t)}function oo(e,t){let n=$n(e,t,{encoding:`utf8`});return{status:n.status,stdout:n.stdout??``,stderr:n.stderr??``,error:n.error}}function so(e){let t=e.replaceAll(`\\`,`/`).toLowerCase();return t.includes(`/_npx/`)||t.includes(`/.npm/_npx/`)||t.includes(`/.cache/pnpm/dlx/`)}function co(e){if(!e||!v(e))return;let t=Hn(e);return so(t)?void 0:t}function lo(e={}){let t=co(e.cliPath??process.argv[1]);if(t)return{command:e.nodePath??process.execPath,args:[t,`mcp`,`serve`]};let n=`seo@${e.version??je}`;return(e.platform??process.platform)===`win32`?{command:`cmd`,args:[`/c`,`npx`,`-y`,n,`mcp`,`serve`]}:{command:`npx`,args:[`-y`,n,`mcp`,`serve`]}}function uo(e=!1,t=lo()){let n=Ie(),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 fo(e){return e.includes(`SEO_MCP_CONFIGURED_BY`)||/args:\s+-y\s+(?:seo(?:@\S+)?|@seo\/cli)\s+mcp\s+serve\b/.test(e)}function po(e,t,n){let r=qa(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&&!fo(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=Ie(),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 mo(e,t={}){let n={runCommand:t.runCommand??oo,reinstall:t.reinstall??!1,serverCommand:t.serverCommand??lo()};if(e.kind===`native`)return po(e,`install`,n);let{source:r,config:i}=to(e),a=i.mcpServers?.seo,o=uo(e.includeType,n.serverCommand);if(Xn(a,o)&&!n.reinstall)return{client:e.client,path:e.path,changed:!1};if(a!==void 0&&!io(a))throw Error(`Cannot install SEO MCP: ${e.path} already has an unmanaged mcpServers.seo entry.`);return ao({target:e,source:r,value:o}),{client:e.client,path:e.path,changed:!0}}function ho(e,t={}){if(e.kind===`native`)return po(e,`uninstall`,{runCommand:t.runCommand??oo,reinstall:!1,serverCommand:lo()});if(!v(e.path))return{client:e.client,path:e.path,changed:!1};let{source:n,config:r}=to(e),i=r.mcpServers;if(i?.seo===void 0)return{client:e.client,path:e.path,changed:!1};if(!io(i.seo))throw Error(`Cannot uninstall SEO MCP: ${e.path} has an unmanaged mcpServers.seo entry.`);return ao({target:e,source:n,value:void 0}),{client:e.client,path:e.path,changed:!0}}const go=`https://seoskill.dev/mcp`,_o=`seoskill`;function vo(e){return e.client!==`claude-desktop`}function yo(e){return e.includeType?{type:`http`,url:go}:{url:go}}function bo(e){return eo(e)&&e.url===`https://seoskill.dev/mcp`}function xo(e,t={}){if(!vo(e))throw Error(`${e.label} takes the hosted server from its Connectors settings, not its config file.`);if(e.kind===`native`){let n=t.runCommand??oo,r=qa(e.executable),i=n(r,[`mcp`,`get`,_o]);if(i.error?.code===`ENOENT`)throw Error("Cannot add the hosted MCP server for Codex: the `codex` command is not available on PATH.");if(i.status===0){if(i.stdout.includes(`https://seoskill.dev/mcp`))return{client:e.client,path:e.path,changed:!1};throw Error(`Cannot add the hosted MCP server: ${e.path} already has a different mcp_servers.${_o} entry.`)}let a=n(r,[`mcp`,`add`,_o,`--url`,go]);if(a.error||a.status!==0){let e=a.error?.message??a.stderr.trim()??`unknown error`;throw Error(`Cannot add the hosted MCP server for Codex: ${e}`)}return{client:e.client,path:e.path,changed:!0}}let{source:n,config:r}=to(e),i=r.mcpServers?.[_o];if(bo(i))return{client:e.client,path:e.path,changed:!1};if(i!==void 0)throw Error(`Cannot add the hosted MCP server: ${e.path} already has a different mcpServers.${_o} entry.`);return ao({target:e,source:n,value:yo(e),name:_o}),{client:e.client,path:e.path,changed:!0}}const So=e=>process.stdout.write(`${e}\n`);async function Co(){let{google:e,bing:t,keys:n}=await at();return{status:`linked`,google:e.map(e=>e.email),bing:t.length,keys:Object.keys(n).filter(e=>e!==`x-dataforseo-login`&&!e.startsWith(`x-clicky-`)),clicky:Object.keys(n).filter(e=>e.startsWith(et)).length}}function wo(e){return[`Linked to your seoskill.dev account.`,`Google: ${e.google.join(`, `)||`none`}.`,`Bing: ${e.bing?`${e.bing} linked`:`not connected (add it at ${Tt}/account)`}.`,`Keys: ${e.keys.length?e.keys.map(e=>e.replace(/^x-|-(key|password)$/g,``)).join(`, `):`none`}.`,`Clicky: ${e.clicky?`${e.clicky} site${e.clicky===1?``:`s`}`:`none`}.`,`Next: seo sites, then seo report --site <site>.`].join(`
54
- `)}const To=y({meta:{name:`login`,description:`Link this machine to your seoskill.dev account: its Google accounts, Bing and keys`},args:{json:{type:`boolean`,default:!1,description:`Print JSON.`}},run:async({args:e})=>{let t=A(e),n=async()=>{let e=await Co();t?I(e):So(wo(e))};if(process.env.SEO_TOKEN?.trim()){await n();return}if(ze()){let e=await dt();if(e===`linked`)return n();if(e===`pending`&&!F({json:t})){let e=ze();t?I({status:`pending`,...Do(e)}):So(`Waiting for approval. Open ${e?.url} and click "Link this machine", then run \`seo login\` again.`);return}}else if(mn()){await n();return}let r=ze()??await l();if(!F({json:t})){t?I({status:`pending`,...Do(r)}):So([`Open this link in the person's browser and ask them to approve it:`,r.url,`Code: ${r.code}. After they approve, run \`seo login\` again.`].join(`
55
- `));return}if(await Eo())return n();So("The login expired. Run `seo login` again."),process.exitCode=1}});async function Eo(){let e=ze()??await l();for(So(`Opening ${e.url}\nApprove code ${e.code} there. Waiting…`),await bn(e.url);ze();){await new Promise(e=>setTimeout(e,3e3));let e=await dt();if(e===`linked`)return!0;if(e===`expired`)break}return!1}const Do=e=>({url:e?.url,code:e?.code}),Oo=y({meta:{name:`logout`,description:`Unlink this machine from your seoskill.dev account`},args:{json:{type:`boolean`,default:!1,description:`Print JSON.`}},run:async({args:e})=>{let t=await Cn();A(e)?I({unlinked:t}):So(t?`This machine is no longer linked to your seoskill.dev account.`:`This machine was not linked.`)}});function ko(e={}){let t=$n(`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`}.`}}async function Ao(e){let t=async()=>{let{google:e}=await at().catch(()=>({google:[]}));return{status:`linked`,accountEmail:e.map(e=>e.email).join(`, `)||void 0}};if(process.env.SEO_TOKEN?.trim()||mn())return t();if(!e.interactive)return null;let n=z(await sr({message:`How do you want to sign in?`,options:[{value:`account`,label:`Sign in or create a free seoskill.dev account`,hint:`Recommended. Your Google accounts, Bing and keys in one place, for the CLI and the hosted MCP`},{value:`local`,label:`Sign in with Google on this machine only`},{value:`skip`,label:`Skip for now`}]}));return n===`skip`?{status:`skipped`}:n===`local`?null:await Eo()?t():(x("The link expired. Run `seo login` to try again.",`Not signed in`),{status:`skipped`})}function jo(e){let t={value:`skip`,label:`Skip for now`};return e.sharedConfigured||e.byoConfigured?[{value:`login`,label:`Connect Google`,hint:`Opens your browser for read-only Search Console and Google Analytics access`},t]:[{value:`setup`,label:`Set up Google login for local development`,hint:`This source checkout does not include the public app credentials`},t]}async function Mo(){let e=await Hi();return x(`Connected as ${e.account_email}. seo has read-only access and cannot change your site.`,`Google connected`),{status:`connected`,accountEmail:e.account_email}}async function No(e){let t=await Jt();if(t.activeMode===`service-account`)return{status:`service-account`,accountEmail:t.identity};if(!e.interactive){if(t.tokens)return{status:`already-connected`,accountEmail:t.tokens.account_email};throw new g(`AUTH_REQUIRED`,"Not signed in. Run `seo start` or `seo auth login` in a terminal.")}let n=_t();if(n.length>0){let e=z(await sr({message:`Google account to use`,options:[...n.map(e=>({value:{type:`account`,accountEmail:e.accountEmail},label:e.accountEmail,hint:e.active?`Current default`:void 0})),{value:{type:`connect`},label:`Connect another Google account`}]}));if(e.type===`account`)return Zt(e.accountEmail),{status:`already-connected`,accountEmail:e.accountEmail};if(t.sharedConfigured||t.byoConfigured)return Mo()}let r=z(await sr({message:`Connect Google now?`,options:jo({sharedConfigured:t.sharedConfigured,byoConfigured:t.byoConfigured})}));return r===`skip`?{status:`skipped`}:(r===`setup`&&Ot({clientId:z(await cr({message:`Google Desktop OAuth client ID`,validate:e=>e?void 0:`Client ID is required`})),clientSecret:z(await C({message:`Google Desktop OAuth client secret`,validate:e=>e?void 0:`Client secret is required`}))}),Mo())}async function Po(){return z(await rr({message:`Install the SEO skill so your agents know how to use seo?`,initialValue:!0}))?ko():{status:`declined`}}async function Fo(){let e=Ya();if(e.length===0)return[];let t=z(await sr({message:`Add an MCP server to your agents?`,options:[{value:`hosted`,label:`Free hosted server (recommended)`,hint:`${go}, your agent asks you to sign in with Google`},{value:`local`,label:`Local server`,hint:`Runs on this machine with the sign-in from this setup`},{value:`skip`,label:`Skip for now`}]}));if(t===`skip`)return[];let n=z(await or({message:`Which agents?`,options:e.map(e=>({value:e.client,label:e.label,hint:e.path})),initialValues:e.map(e=>e.client)})),r=e.filter(e=>n.includes(e.client)).map(e=>{let n=t===`hosted`&&vo(e)?`hosted`:`local`;try{return{...n===`hosted`?xo(e):mo(e),server:n}}catch(t){return{client:e.client,path:e.path,server:n,changed:!1,error:t instanceof Error?t.message:String(t)}}});return t===`hosted`&&r.some(e=>e.client===`claude-desktop`)&&x(`Claude Desktop got the local server. To use the hosted one there, open Settings, then Connectors, and add ${go} as a custom connector.`,`Claude Desktop`),r}const Io=[`seo sites`,`seo report --site <site>`],Lo=[`Sign in or create your free seoskill.dev account, or sign in with Google on this machine only (read-only Search Console and Google Analytics).`,`Add the free hosted MCP server (${go}) or a local one to your agents.`,`Install the SEO skill for your agents.`];function Ro(e){return e.status===`skipped`?`skipped`:e.status===`linked`?`seoskill.dev account${e.accountEmail?` (${e.accountEmail})`:``}`:e.status===`service-account`?`service account${e.accountEmail?` ${e.accountEmail}`:``}`:e.accountEmail??`connected`}function zo(e){return e.status===`installed`?`installed`:e.status===`failed`?`install failed`:`not installed`}function Bo(e){return e.length===0?`not added`:e.map(e=>e.error?`${e.client} skipped`:`${e.client} (${e.server})`).join(`, `)}function Vo(){process.stdout.write(`
56
- `),fi({title:`Help other people find SEO Skill`,body:`If SEO Skill saved you time, a GitHub star helps other people find it.`,command:`https://github.com/iannuttall/seo`})}async function Ho(e){let t=A(e);if(t||b(process.argv[2]===`setup`?`seo setup`:`seo start`),e[`dry-run`]){t?I({dryRun:!0,steps:Lo,next:Io}):(x(Lo.map((e,t)=>`${t+1}. ${e}`).join(`
57
- `),`Setup will`),x(Io.join(`
58
- `),`Then run`),S(`Dry run complete. Nothing was changed.`));return}ye();let n=F({json:t}),r=await Ao({interactive:n})??await No({interactive:n}),i=n?await Fo():[],a=n?await Po():{status:`skipped`};if(t){I({auth:r,mcp:i,skill:a,next:Io});return}L([[`Sign-in`,Ro(r)],[`MCP server`,Bo(i)],[`SEO skill`,zo(a)]]);let o=i.filter(e=>e.error);o.length&&x(o.map(e=>`${e.client}: ${e.error}`).join(`
59
- `),`MCP setup skipped`),a.error&&x(a.error,`SEO skill install failed`),x([`seo sites list your Search Console properties`,`seo report --site <site> run the main report for one of them`].join(`
60
- `),`Try next`),S(`Setup complete.`),Vo()}const Uo=y({meta:{name:`setup`,description:`Sign in with Google, then add the MCP server and skill`},args:{"dry-run":{type:`boolean`,default:!1,description:`Show what setup does without changing anything.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON. Never prompts.`}},run:async({args:e})=>Ho(e)}),Wo=y({...Uo,meta:{name:`start`,description:`Start here: sign in with Google, then add the MCP server and skill to your agents`}}),Go=y({...Wo,meta:{name:`init`,description:"Legacy alias for `seo start`."}}),Ko=y({meta:{name:`logs`,description:`Manage local log retention`},subCommands:{prune:y({meta:{name:`prune`,description:`Rotate large logs and remove old local logs`},args:{quiet:{type:`boolean`,default:!1,description:`Do not print a result.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=ut();if(A(e)){I(t);return}e.quiet||process.stdout.write(`Rotated ${t.rotated} and removed ${t.removed} log files. ${xi(t.sizeBytes)} retained.\n`)}})}}),qo=[{client:`claude-desktop`,flag:`claude-desktop`},{client:`claude-code`,flag:`claude-code`},{client:`codex`,flag:`codex`},{client:`cursor`,flag:`cursor`}];function Jo(e,t){return E(e.all)?t:qo.filter(({flag:t})=>E(e[t])).map(({client:e})=>e)}const Yo=y({meta:{name:`mcp`,description:`MCP server helpers`},subCommands:{serve:y({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-BtwI_F7s.js`);await t({test:E(e.test)})}}),install:y({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(E(e.uninstall)&&E(e.reinstall))throw new g(`INVALID_INPUT`,`Use either --uninstall or --reinstall, not both.`);let t=Ka(),n=Ya({targets:t}),r=A(e),i=Jo(e,n.map(e=>e.client));if(i.length===0){if(!F({json:r}))throw new g(`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 or({message:e.uninstall?`Remove seo from which MCP clients?`:`Install seo into which MCP clients?`,options:a.map(e=>({value:e.client,label:e.label,hint:e.path})),initialValues:n.map(e=>e.client),required:!0}))}let a=i.filter(e=>!t.some(t=>t.client===e));if(a.length>0)throw new g(`INVALID_INPUT`,`MCP setup is not supported for ${a.join(`, `)} on ${process.platform}.`);let o=t.filter(e=>i.includes(e.client)).map(t=>E(e.uninstall)?ho(t):mo(t,{reinstall:E(e.reinstall)}));if(r){I({operation:E(e.uninstall)?`uninstall`:`install`,results:o});return}for(let e of o)process.stdout.write(`${e.changed?`updated`:`skipped`} ${e.client} · ${e.path}\n`)}})}}),Xo=y({meta:{name:`index-coverage`,description:`Find crawlable pages that deserve a closer Google index check`},args:{site:{type:`string`,description:`GSC property URL, for example sc-domain:example.com.`},url:{type:`string`,description:`Start URL to crawl. Defaults to the home page.`},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=A(e),n=j(T(e.site)),r=Dr(e.sitemaps),i=await Wt({site:n,url:T(e.url),sitemaps:r.length?r:void 0,days:D(e.days),rowLimit:D(e[`row-limit`]),maxSitemapUrls:D(e[`max-sitemap-urls`]),itemsPerSection:D(e.limit),refresh:E(e.refresh)});if(t){I(i);return}K({title:`Index coverage`,target:i.site,status:i.sources.crawl.completeness===`complete`&&i.sources.searchConsole.completeness===`complete`?i.summary.crawlableCandidatesWithoutRetainedSearchVisibility>0?`warning`:`pass`:`unknown`,summary:`${i.summary.crawlableCandidatesWithoutRetainedSearchVisibility} crawlable pages need URL Inspection review.`,metrics:[{label:`Crawl report`,value:i.input.crawlReport.id},{label:`Crawl evidence`,value:i.sources.crawl.completeness},{label:`Sitemap evidence`,value:i.sources.sitemap.completeness},{label:`Search evidence`,value:i.sources.searchConsole.completeness},{label:`Search window`,value:`${i.sources.searchConsole.startDate} to ${i.sources.searchConsole.endDate}`},{label:`Search-visible pages`,value:i.summary.retainedSearchVisibleUrls},{label:`Blocked/non-indexable`,value:i.summary.blockedOrNonIndexableCrawlUrls},{label:`Sitemap only`,value:i.summary.sitemapOnlyUrls},{label:`Search Console only`,value:i.summary.searchConsoleOnlyUrls}]}),i.crawlableWithoutRetainedSearchVisibility.items.length&&(process.stdout.write(`
51
+ `),I([`Start`,`End`,`Type`,`Name`],r.overlappingUpdates.map(e=>[e.start.slice(0,10),e.end?.slice(0,10)??`open`,e.type,e.name]))),ra(`seo segment-impact --site ${JSON.stringify(n)} --dimension page`)}}),Ba={site:{type:`string`,description:`Search Console property, for example sc-domain:example.com.`}};function Va(e){return{type:`string`,description:`Output folder. Defaults to ./seo-export/<site>-${e}-YYYY-MM-DD.`}}function Ha(e){return e.toLowerCase().replace(/^sc-domain:/,``).replace(/^https?:\/\//,``).replace(/[^a-z0-9._-]+/g,`-`).replace(/^-+|-+$/g,``)}function Ua(e){let t=new Date().toISOString().slice(0,10),n=Ha(e.site);return In(process.cwd(),`seo-export`,`${n}-${e.report}-${t}`)}async function Wa(e,t){await Yn(e,{recursive:!0});let n=[];for(let r of t){let t=In(e,r.filename);await Zn(t,rn(r.rows,r.headers),`utf8`),n.push(t)}return n}function Ga(e,t){process.stdout.write(`Wrote ${Ge(t.length,`CSV file`)} to ${e}\n`);for(let e of t)process.stdout.write(`- ${e}\n`)}const Ka=v({meta:{name:`refresh-priorities`,description:`Export the ranked SEO priority queue, score breakdowns, grouped findings, and diagnosis detail CSVs.`},args:{...Ba,out:Va(`refresh-priorities`),...J([`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.`}}),...va,"verify-content":Tr(`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=k(C(e.site)),n=await _t({site:t,days:T(e.days),recentDays:T(e.recent),limit:T(e.limit),includeBrand:w(e[`include-brand`]),googleAnalyticsPropertyId:C(e[`google-analytics-property`]),analyticsConnection:ya(e),verifyContent:w(e[`verify-content`]),verifyLimit:T(e[`verify-limit`]),refresh:w(e.refresh)}),r=C(e.out)??Ua({report:`refresh-priorities`,site:t});Ga(r,await Wa(r,ye(n)))}}),qa=v({meta:{name:`export`,description:`Export full report data to CSV files`},subCommands:{"update-postmortem":v({meta:{name:`update-postmortem`,description:`Export update postmortem attribution, findings, template movement, and segment movers.`},args:{...Ba,out:Va(`update-postmortem`),...J([`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.`}},run:async({args:e})=>{let t=k(C(e.site)),n=await cn({site:t,days:T(e.days),recentDays:T(e.recent),limit:T(e.limit),includeBrand:w(e[`include-brand`]),knownConfounders:C(e[`known-change`])?[C(e[`known-change`])??``]:void 0,refresh:w(e.refresh)}),r=C(e.out)??Ua({report:`update-postmortem`,site:t});Ga(r,await Wa(r,Cr(n)))}}),"refresh-priorities":Ka}});function Ja(e){let t=e.platform===`win32`?Ln:Fn;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 Ya(e={}){let t=e.home??jn(),n=e.platform??process.platform,r=Ja({home:t,platform:n,env:e.env??process.env}),i=n===`win32`?Ln:Fn,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 Xa(e,t={}){let n=t.platform??process.platform,r=t.exists??_,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=g(e,t);if(r(n))return n}for(let e of a){let t=g(Pn(i),e);if(r(t))return t}return e}function Za(e){return Xa(e)!==e}function Qa(e={}){let t=e.targets??Ya(),n=e.hasCommand??Za,r=e.hasPath??_;return t.filter(e=>e.kind===`native`?e.commandNames.some(n):r(e.path)||e.detectionPaths.some(r)||e.commandNames.some(n))}const $a={insertSpaces:!0,tabSize:2,eol:`
52
+ `};function eo(){return new Date().toISOString().replace(/[:.]/g,`-`)}function to(e,t){Vn(Pn(e),{recursive:!0,mode:448});let n=`${e}.${process.pid}.${Date.now()}.tmp`;qn(n,t,{encoding:`utf8`,mode:_(e)?Kn(e).mode&511:384}),Gn(n,e)}function no(e){let t=`${e}.bak-${eo()}`,n=t,r=2;for(;_(n);)n=`${t}-${r}`,r+=1;zn(e,n,Rn.COPYFILE_EXCL)}function ro(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function io(e){if(!_(e.path))return{source:`{}
53
+ `,config:{}};let t=Hn(e.path,`utf8`),n=[],r=vr(t,n,{allowTrailingComma:!0});if(n.length>0){let t=n[0],r=t?`${yr(t.error)} at offset ${t.offset}`:`unknown parse error`;throw Error(`Cannot update ${e.path}: invalid JSONC (${r}).`)}if(!ro(r))throw Error(`Cannot update ${e.path}: config root must be an object.`);if(r.mcpServers!==void 0&&!ro(r.mcpServers))throw Error(`Cannot update ${e.path}: mcpServers must be an object.`);return{source:t,config:r}}function ao(e){return e.includes(`mcp`)&&e.includes(`serve`)&&e.some(e=>e===`seo`||e.startsWith(`seo@`)||e===`@seo/cli`)}function oo(e,t){return typeof e.command!=`string`||t.length!==3||t[1]!==`mcp`||t[2]!==`serve`||!ro(e.env)?!1:e.env.SEO_MCP_CONFIGURED_BY===`seo`}function so(e){if(!ro(e)||!Array.isArray(e.args))return!1;let t=e.args.filter(e=>typeof e==`string`);return ao(t)||oo(e,t)}function co(e){let t=gr(e.source,_r(e.source,[`mcpServers`,e.name??`seo`],e.value,{formattingOptions:$a}));_(e.target.path)&&no(e.target.path),to(e.target.path,t)}function lo(e,t){let n=tr(e,t,{encoding:`utf8`});return{status:n.status,stdout:n.stdout??``,stderr:n.stderr??``,error:n.error}}function uo(e){let t=e.replaceAll(`\\`,`/`).toLowerCase();return t.includes(`/_npx/`)||t.includes(`/.npm/_npx/`)||t.includes(`/.cache/pnpm/dlx/`)}function fo(e){if(!e||!_(e))return;let t=Wn(e);return uo(t)?void 0:t}function po(e={}){let t=fo(e.cliPath??process.argv[1]);if(t)return{command:e.nodePath??process.execPath,args:[t,`mcp`,`serve`]};let n=`seo@${e.version??p}`;return(e.platform??process.platform)===`win32`?{command:`cmd`,args:[`/c`,`npx`,`-y`,n,`mcp`,`serve`]}:{command:`npx`,args:[`-y`,n,`mcp`,`serve`]}}function mo(e=!1,t=po()){let n=On(),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 ho(e){return e.includes(`SEO_MCP_CONFIGURED_BY`)||/args:\s+-y\s+(?:seo(?:@\S+)?|@seo\/cli)\s+mcp\s+serve\b/.test(e)}function go(e,t,n){let r=Xa(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&&!ho(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=On(),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 _o(e,t={}){let n={runCommand:t.runCommand??lo,reinstall:t.reinstall??!1,serverCommand:t.serverCommand??po()};if(e.kind===`native`)return go(e,`install`,n);let{source:r,config:i}=io(e),a=i.mcpServers?.seo,o=mo(e.includeType,n.serverCommand);if(Qn(a,o)&&!n.reinstall)return{client:e.client,path:e.path,changed:!1};if(a!==void 0&&!so(a))throw Error(`Cannot install SEO MCP: ${e.path} already has an unmanaged mcpServers.seo entry.`);return co({target:e,source:r,value:o}),{client:e.client,path:e.path,changed:!0}}function vo(e,t={}){if(e.kind===`native`)return go(e,`uninstall`,{runCommand:t.runCommand??lo,reinstall:!1,serverCommand:po()});if(!_(e.path))return{client:e.client,path:e.path,changed:!1};let{source:n,config:r}=io(e),i=r.mcpServers;if(i?.seo===void 0)return{client:e.client,path:e.path,changed:!1};if(!so(i.seo))throw Error(`Cannot uninstall SEO MCP: ${e.path} has an unmanaged mcpServers.seo entry.`);return co({target:e,source:n,value:void 0}),{client:e.client,path:e.path,changed:!0}}const yo=`https://seoskill.dev/mcp`,bo=`seoskill`;function xo(e){return e.client!==`claude-desktop`}function So(e){return e.includeType?{type:`http`,url:yo}:{url:yo}}function Co(e){return ro(e)&&e.url===`https://seoskill.dev/mcp`}function wo(e,t={}){if(!xo(e))throw Error(`${e.label} takes the hosted server from its Connectors settings, not its config file.`);if(e.kind===`native`){let n=t.runCommand??lo,r=Xa(e.executable),i=n(r,[`mcp`,`get`,bo]);if(i.error?.code===`ENOENT`)throw Error("Cannot add the hosted MCP server for Codex: the `codex` command is not available on PATH.");if(i.status===0){if(i.stdout.includes(`https://seoskill.dev/mcp`))return{client:e.client,path:e.path,changed:!1};throw Error(`Cannot add the hosted MCP server: ${e.path} already has a different mcp_servers.${bo} entry.`)}let a=n(r,[`mcp`,`add`,bo,`--url`,yo]);if(a.error||a.status!==0){let e=a.error?.message??a.stderr.trim()??`unknown error`;throw Error(`Cannot add the hosted MCP server for Codex: ${e}`)}return{client:e.client,path:e.path,changed:!0}}let{source:n,config:r}=io(e),i=r.mcpServers?.[bo];if(Co(i))return{client:e.client,path:e.path,changed:!1};if(i!==void 0)throw Error(`Cannot add the hosted MCP server: ${e.path} already has a different mcpServers.${bo} entry.`);return co({target:e,source:n,value:So(e),name:bo}),{client:e.client,path:e.path,changed:!0}}const To=e=>process.stdout.write(`${e}\n`);async function Eo(){let{google:e,bing:t,keys:n}=await gn();return{status:`linked`,google:e.map(e=>e.email),bing:t.length,keys:Object.keys(n).filter(e=>e!==`x-dataforseo-login`&&!e.startsWith(`x-clicky-`)),clicky:Object.keys(n).filter(e=>e.startsWith(et)).length}}function Do(e){return[`Linked to your seoskill.dev account.`,`Google: ${e.google.join(`, `)||`none`}.`,`Bing: ${e.bing?`${e.bing} linked`:`not connected (add it at ${st}/account)`}.`,`Keys: ${e.keys.length?e.keys.map(e=>e.replace(/^x-|-(key|password)$/g,``)).join(`, `):`none`}.`,`Clicky: ${e.clicky?`${e.clicky} site${e.clicky===1?``:`s`}`:`none`}.`,`Next: seo sites, then seo report --site <site>.`].join(`
54
+ `)}const Oo=v({meta:{name:`login`,description:`Link this machine to your seoskill.dev account: its Google accounts, Bing and keys`},args:{json:{type:`boolean`,default:!1,description:`Print JSON.`}},run:async({args:e})=>{let t=O(e),n=async()=>{let e=await Eo();t?P(e):To(Do(e))};if(process.env.SEO_TOKEN?.trim()){await n();return}if(l()){let e=await Sn();if(e===`linked`)return n();if(e===`pending`&&!N({json:t})){let e=l();t?P({status:`pending`,...Ao(e)}):To(`Waiting for approval. Open ${e?.url} and click "Link this machine", then run \`seo login\` again.`);return}}else if(Tn()){await n();return}let r=l()??await yn();if(!N({json:t})){t?P({status:`pending`,...Ao(r)}):To([`Open this link in the person's browser and ask them to approve it:`,r.url,`Code: ${r.code}. After they approve, run \`seo login\` again.`].join(`
55
+ `));return}if(await ko())return n();To("The login expired. Run `seo login` again."),process.exitCode=1}});async function ko(){let e=l()??await yn();for(To(`Opening ${e.url}\nApprove code ${e.code} there. Waiting…`),await Ve(e.url);l();){await new Promise(e=>setTimeout(e,3e3));let e=await Sn();if(e===`linked`)return!0;if(e===`expired`)break}return!1}const Ao=e=>({url:e?.url,code:e?.code}),jo=v({meta:{name:`logout`,description:`Unlink this machine from your seoskill.dev account`},args:{json:{type:`boolean`,default:!1,description:`Print JSON.`}},run:async({args:e})=>{let t=await pt();O(e)?P({unlinked:t}):To(t?`This machine is no longer linked to your seoskill.dev account.`:`This machine was not linked.`)}});function Mo(e={}){let t=tr(`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`}.`}}async function No(e){let t=async()=>{let{google:e}=await gn().catch(()=>({google:[]}));return{status:`linked`,accountEmail:e.map(e=>e.email).join(`, `)||void 0}};if(process.env.SEO_TOKEN?.trim()||Tn())return t();if(!e.interactive)return null;let n=L(await ur({message:`How do you want to sign in?`,options:[{value:`account`,label:`Sign in or create a free seoskill.dev account`,hint:`Recommended. Your Google accounts, Bing and keys in one place, for the CLI and the hosted MCP`},{value:`local`,label:`Sign in with Google on this machine only`},{value:`skip`,label:`Skip for now`}]}));return n===`skip`?{status:`skipped`}:n===`local`?null:await ko()?t():(b("The link expired. Run `seo login` to try again.",`Not signed in`),{status:`skipped`})}function Po(e){let t={value:`skip`,label:`Skip for now`};return e.sharedConfigured||e.byoConfigured?[{value:`login`,label:`Connect Google`,hint:`Opens your browser for read-only Search Console and Google Analytics access`},t]:[{value:`setup`,label:`Set up Google login for local development`,hint:`This source checkout does not include the public app credentials`},t]}async function Fo(){let e=await Gi();return b(`Connected as ${e.account_email}. seo has read-only access and cannot change your site.`,`Google connected`),{status:`connected`,accountEmail:e.account_email}}async function Io(e){let t=await sn();if(t.activeMode===`service-account`)return{status:`service-account`,accountEmail:t.identity};if(!e.interactive){if(t.tokens)return{status:`already-connected`,accountEmail:t.tokens.account_email};throw new h(`AUTH_REQUIRED`,"Not signed in. Run `seo start` or `seo auth login` in a terminal.")}let n=wt();if(n.length>0){let e=L(await ur({message:`Google account to use`,options:[...n.map(e=>({value:{type:`account`,accountEmail:e.accountEmail},label:e.accountEmail,hint:e.active?`Current default`:void 0})),{value:{type:`connect`},label:`Connect another Google account`}]}));if(e.type===`account`)return Gt(e.accountEmail),{status:`already-connected`,accountEmail:e.accountEmail};if(t.sharedConfigured||t.byoConfigured)return Fo()}let r=L(await ur({message:`Connect Google now?`,options:Po({sharedConfigured:t.sharedConfigured,byoConfigured:t.byoConfigured})}));return r===`skip`?{status:`skipped`}:(r===`setup`&&Dt({clientId:L(await dr({message:`Google Desktop OAuth client ID`,validate:e=>e?void 0:`Client ID is required`})),clientSecret:L(await lr({message:`Google Desktop OAuth client secret`,validate:e=>e?void 0:`Client secret is required`}))}),Fo())}async function Lo(){return L(await ar({message:`Install the SEO skill so your agents know how to use seo?`,initialValue:!0}))?Mo():{status:`declined`}}async function Ro(){let e=Qa();if(e.length===0)return[];let t=L(await ur({message:`Add an MCP server to your agents?`,options:[{value:`hosted`,label:`Free hosted server (recommended)`,hint:`${yo}, your agent asks you to sign in with Google`},{value:`local`,label:`Local server`,hint:`Runs on this machine with the sign-in from this setup`},{value:`skip`,label:`Skip for now`}]}));if(t===`skip`)return[];let n=L(await cr({message:`Which agents?`,options:e.map(e=>({value:e.client,label:e.label,hint:e.path})),initialValues:e.map(e=>e.client)})),r=e.filter(e=>n.includes(e.client)).map(e=>{let n=t===`hosted`&&xo(e)?`hosted`:`local`;try{return{...n===`hosted`?wo(e):_o(e),server:n}}catch(t){return{client:e.client,path:e.path,server:n,changed:!1,error:t instanceof Error?t.message:String(t)}}});return t===`hosted`&&r.some(e=>e.client===`claude-desktop`)&&b(`Claude Desktop got the local server. To use the hosted one there, open Settings, then Connectors, and add ${yo} as a custom connector.`,`Claude Desktop`),r}const zo=[`seo sites`,`seo report --site <site>`],Bo=[`Sign in or create your free seoskill.dev account, or sign in with Google on this machine only (read-only Search Console and Google Analytics).`,`Add the free hosted MCP server (${yo}) or a local one to your agents.`,`Install the SEO skill for your agents.`];function Vo(e){return e.status===`skipped`?`skipped`:e.status===`linked`?`seoskill.dev account${e.accountEmail?` (${e.accountEmail})`:``}`:e.status===`service-account`?`service account${e.accountEmail?` ${e.accountEmail}`:``}`:e.accountEmail??`connected`}function Ho(e){return e.status===`installed`?`installed`:e.status===`failed`?`install failed`:`not installed`}function Uo(e){return e.length===0?`not added`:e.map(e=>e.error?`${e.client} skipped`:`${e.client} (${e.server})`).join(`, `)}function Wo(){process.stdout.write(`
56
+ `),hi({title:`Help other people find SEO Skill`,body:`If SEO Skill saved you time, a GitHub star helps other people find it.`,command:`https://github.com/iannuttall/seo`})}async function Go(e){let t=O(e);if(t||y(process.argv[2]===`setup`?`seo setup`:`seo start`),e[`dry-run`]){t?P({dryRun:!0,steps:Bo,next:zo}):(b(Bo.map((e,t)=>`${t+1}. ${e}`).join(`
57
+ `),`Setup will`),b(zo.join(`
58
+ `),`Then run`),x(`Dry run complete. Nothing was changed.`));return}Re();let n=N({json:t}),r=await No({interactive:n})??await Io({interactive:n}),i=n?await Ro():[],a=n?await Lo():{status:`skipped`};if(t){P({auth:r,mcp:i,skill:a,next:zo});return}F([[`Sign-in`,Vo(r)],[`MCP server`,Uo(i)],[`SEO skill`,Ho(a)]]);let o=i.filter(e=>e.error);o.length&&b(o.map(e=>`${e.client}: ${e.error}`).join(`
59
+ `),`MCP setup skipped`),a.error&&b(a.error,`SEO skill install failed`),b([`seo sites list your Search Console properties`,`seo report --site <site> run the main report for one of them`].join(`
60
+ `),`Try next`),x(`Setup complete.`),Wo()}const Ko=v({meta:{name:`setup`,description:`Sign in with Google, then add the MCP server and skill`},args:{"dry-run":{type:`boolean`,default:!1,description:`Show what setup does without changing anything.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON. Never prompts.`}},run:async({args:e})=>Go(e)}),qo=v({...Ko,meta:{name:`start`,description:`Start here: sign in with Google, then add the MCP server and skill to your agents`}}),Jo=v({...qo,meta:{name:`init`,description:"Legacy alias for `seo start`."}}),Yo=v({meta:{name:`logs`,description:`Manage local log retention`},subCommands:{prune:v({meta:{name:`prune`,description:`Rotate large logs and remove old local logs`},args:{quiet:{type:`boolean`,default:!1,description:`Do not print a result.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=ft();if(O(e)){P(t);return}e.quiet||process.stdout.write(`Rotated ${t.rotated} and removed ${t.removed} log files. ${wi(t.sizeBytes)} retained.\n`)}})}}),Xo=[{client:`claude-desktop`,flag:`claude-desktop`},{client:`claude-code`,flag:`claude-code`},{client:`codex`,flag:`codex`},{client:`cursor`,flag:`cursor`}];function Zo(e,t){return w(e.all)?t:Xo.filter(({flag:t})=>w(e[t])).map(({client:e})=>e)}const Qo=v({meta:{name:`mcp`,description:`MCP server helpers`},subCommands:{serve:v({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-Bv1nbxq_.js`);await t({test:w(e.test)})}}),install:v({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(w(e.uninstall)&&w(e.reinstall))throw new h(`INVALID_INPUT`,`Use either --uninstall or --reinstall, not both.`);let t=Ya(),n=Qa({targets:t}),r=O(e),i=Zo(e,n.map(e=>e.client));if(i.length===0){if(!N({json:r}))throw new h(`INVALID_INPUT`,`Choose an MCP client with --claude-desktop, --claude-code, --codex, --cursor, or --all.`);let a=n.length>0?n:t;i=L(await cr({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 h(`INVALID_INPUT`,`MCP setup is not supported for ${a.join(`, `)} on ${process.platform}.`);let o=t.filter(e=>i.includes(e.client)).map(t=>w(e.uninstall)?vo(t):_o(t,{reinstall:w(e.reinstall)}));if(r){P({operation:w(e.uninstall)?`uninstall`:`install`,results:o});return}for(let e of o)process.stdout.write(`${e.changed?`updated`:`skipped`} ${e.client} · ${e.path}\n`)}})}}),$o=v({meta:{name:`index-coverage`,description:`Find crawlable pages that deserve a closer Google index check`},args:{site:{type:`string`,description:`GSC property URL, for example sc-domain:example.com.`},url:{type:`string`,description:`Start URL to crawl. Defaults to the home page.`},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=O(e),n=k(C(e.site)),r=Ar(e.sitemaps),i=await jt({site:n,url:C(e.url),sitemaps:r.length?r:void 0,days:T(e.days),rowLimit:T(e[`row-limit`]),maxSitemapUrls:T(e[`max-sitemap-urls`]),itemsPerSection:T(e.limit),refresh:w(e.refresh)});if(t){P(i);return}W({title:`Index coverage`,target:i.site,status:i.sources.crawl.completeness===`complete`&&i.sources.searchConsole.completeness===`complete`?i.summary.crawlableCandidatesWithoutRetainedSearchVisibility>0?`warning`:`pass`:`unknown`,summary:`${i.summary.crawlableCandidatesWithoutRetainedSearchVisibility} crawlable pages need URL Inspection review.`,metrics:[{label:`Crawl report`,value:i.input.crawlReport.id},{label:`Crawl evidence`,value:i.sources.crawl.completeness},{label:`Sitemap evidence`,value:i.sources.sitemap.completeness},{label:`Search evidence`,value:i.sources.searchConsole.completeness},{label:`Search window`,value:`${i.sources.searchConsole.startDate} to ${i.sources.searchConsole.endDate}`},{label:`Search-visible pages`,value:i.summary.retainedSearchVisibleUrls},{label:`Blocked/non-indexable`,value:i.summary.blockedOrNonIndexableCrawlUrls},{label:`Sitemap only`,value:i.summary.sitemapOnlyUrls},{label:`Search Console only`,value:i.summary.searchConsoleOnlyUrls}]}),i.crawlableWithoutRetainedSearchVisibility.items.length&&(process.stdout.write(`
61
61
  Representative URL Inspection candidates
62
- `),R([`URL`,`Status`,`In sitemap`],i.crawlableWithoutRetainedSearchVisibility.items.map(e=>[G(e.url,80),e.status,e.inSitemap?`yes`:`no`]))),Y(`How to read this report`,i.caveats)}}),Zo=y({meta:{name:`link-recover`,description:`Find search-value URLs that are now broken, blocked, or poorly redirected`},args:{site:{type:`string`},...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=A(e),n=await Ct({site:j(T(e.site)),days:D(e.days),limit:D(e.limit),minClicks:D(e[`min-clicks`]),minImpressions:D(e[`min-impressions`]),refresh:E(e.refresh),js:E(e.js)?!0:`auto`});if(t){I(n);return}K({title:`Link recovery`,target:n.site,status:n.warnings.length>0?`unknown`:n.summary.recoverable>0?`warning`:`pass`,summary:n.summary.recoverable>0?`${H(n.summary.recoverable)} previously visible URLs need recovery review.`:`No recoverable URLs were found in the retained evidence.`,metrics:[{label:`Range`,value:`${n.range.startDate} to ${n.range.endDate}`},{label:`Checked`,value:H(n.summary.checked)},{label:`Recoverable`,value:H(n.summary.recoverable)},{label:`High severity`,value:H(n.summary.high)},{label:`Clicks at risk`,value:H(n.summary.clicksAtRisk)},{label:`Impressions at risk`,value:H(n.summary.impressionsAtRisk)}]}),n.items.length&&(q([`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)])),J(`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})))),Y(`Warnings`,n.warnings.slice(0,10))}}),Qo=y({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 Qt({url:T(e.url)??``,maxHops:D(e[`max-hops`]),refresh:E(e.refresh),js:E(e.js)?!0:`auto`});if(A(e)){I(t);return}K({title:`Redirect trace`,target:t.url,status:t.summary.issues.length>0?`warning`:t.summary.finalIndexable===void 0?`unknown`:`pass`,summary:t.summary.issues.length>0?t.summary.issues.join(`, `):`${t.summary.hops} redirect hops ended at HTTP ${t.summary.finalStatus}.`,metrics:[{label:`Final URL`,value:t.finalUrl},{label:`Final status`,value:t.summary.finalStatus},{label:`Indexable`,value:t.summary.finalIndexable===void 0?`Unknown`:t.summary.finalIndexable?`Yes`:`No`},{label:`Hops`,value:t.summary.hops}]}),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(`
62
+ `),I([`URL`,`Status`,`In sitemap`],i.crawlableWithoutRetainedSearchVisibility.items.map(e=>[U(e.url,80),e.status,e.inSitemap?`yes`:`no`]))),q(`How to read this report`,i.caveats)}}),es=v({meta:{name:`link-recover`,description:`Find search-value URLs that are now broken, blocked, or poorly redirected`},args:{site:{type:`string`},...J([`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=O(e),n=await en({site:k(C(e.site)),days:T(e.days),limit:T(e.limit),minClicks:T(e[`min-clicks`]),minImpressions:T(e[`min-impressions`]),refresh:w(e.refresh),js:w(e.js)?!0:`auto`});if(t){P(n);return}W({title:`Link recovery`,target:n.site,status:n.warnings.length>0?`unknown`:n.summary.recoverable>0?`warning`:`pass`,summary:n.summary.recoverable>0?`${B(n.summary.recoverable)} previously visible URLs need recovery review.`:`No recoverable URLs were found in the retained evidence.`,metrics:[{label:`Range`,value:`${n.range.startDate} to ${n.range.endDate}`},{label:`Checked`,value:B(n.summary.checked)},{label:`Recoverable`,value:B(n.summary.recoverable)},{label:`High severity`,value:B(n.summary.high)},{label:`Clicks at risk`,value:B(n.summary.clicksAtRisk)},{label:`Impressions at risk`,value:B(n.summary.impressionsAtRisk)}]}),n.items.length&&(G([`Severity`,`Issue`,`Clicks`,`Impr`,`URL`,`Action`],n.items.map(e=>[e.severity,e.issue,B(e.clicks),B(e.impressions),U(e.url,56),U(e.recommendation.action,72)])),K(`Top recovery actions`,n.items.map(e=>({label:U(e.url,96),context:`${e.severity}, ${B(e.clicks)} clicks at risk`,action:e.recommendation.action})))),q(`Warnings`,n.warnings.slice(0,10))}}),ts=v({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 Kt({url:C(e.url)??``,maxHops:T(e[`max-hops`]),refresh:w(e.refresh),js:w(e.js)?!0:`auto`});if(O(e)){P(t);return}W({title:`Redirect trace`,target:t.url,status:t.summary.issues.length>0?`warning`:t.summary.finalIndexable===void 0?`unknown`:`pass`,summary:t.summary.issues.length>0?t.summary.issues.join(`, `):`${t.summary.hops} redirect hops ended at HTTP ${t.summary.finalStatus}.`,metrics:[{label:`Final URL`,value:t.finalUrl},{label:`Final status`,value:t.summary.finalStatus},{label:`Indexable`,value:t.summary.finalIndexable===void 0?`Unknown`:t.summary.finalIndexable?`Yes`:`No`},{label:`Hops`,value:t.summary.hops}]}),I([`#`,`Status`,`URL`,`Next`],t.chain.map((e,t)=>[t+1,e.status,U(e.url,72),U(e.nextUrl??`-`,72)])),t.finalPage&&(process.stdout.write(`
63
63
  Final page
64
- `),L([[`Title`,t.finalPage.title??`-`],[`Canonical`,t.finalPage.canonical??`-`],[`Meta robots`,t.finalPage.metaRobots??`-`]])),Y(`Warnings`,t.warnings.slice(0,10))}}),$o=y({meta:{name:`ai-referrals`,description:`Find AI referral traffic detected in Google Analytics`},args:{property:{type:`string`,description:"Numeric Google Analytics property ID. Run `seo analytics google properties` to list them."},"start-date":{type:`string`,default:`28daysAgo`},"end-date":{type:`string`,default:`yesterday`},"max-rows":{type:`string`,description:`Maximum Google Analytics rows per query. Defaults to 100000.`},"result-limit":{type:`string`,description:`Maximum ranked landing pages returned. Defaults to 25.`},limit:{type:`string`,description:`Legacy alias for --max-rows.`},refresh:{type:`boolean`,default:!1,description:`Bypass cached Google Analytics responses.`},json:{type:`boolean`,default:!1}},run:async({args:e})=>{let n=A(e),r=await t({property:jr(T(e.property)),startDate:T(e[`start-date`]),endDate:T(e[`end-date`]),maxRows:O(e[`max-rows`],`--max-rows`),resultLimit:O(e[`result-limit`],`--result-limit`),limit:O(e.limit,`--limit`),refresh:e.refresh===!0});if(n){I(r);return}if(K({title:`AI referral report`,target:r.property,status:r.dataStatus===`complete`?`info`:`unknown`,summary:r.summary.verdict,metrics:[{label:`Evidence`,value:r.dataStatus===`complete`?`Complete`:`Partial`,status:r.dataStatus===`complete`?`pass`:`unknown`},{label:`AI sessions`,value:H(r.summary.sessions)},{label:`AI users`,value:r.summary.totalUsers===null?`Unavailable`:H(r.summary.totalUsers)},{label:`Sources`,value:H(r.summary.sources)},{label:`Landing pages`,value:H(r.summary.landingPages)},{label:`Landing-page output`,value:`${H(r.selection.landingPages.returnedRows)} returned / ${H(r.selection.landingPages.retainedRows)} retained`},{label:`Source rows`,value:H(r.dataSource.sourceDiscovery.returnedRows)}]}),r.sources.length&&(process.stdout.write(`
64
+ `),F([[`Title`,t.finalPage.title??`-`],[`Canonical`,t.finalPage.canonical??`-`],[`Meta robots`,t.finalPage.metaRobots??`-`]])),q(`Warnings`,t.warnings.slice(0,10))}}),ns=v({meta:{name:`ai-referrals`,description:`Find AI referral traffic detected in Google Analytics`},args:{property:{type:`string`,description:"Numeric Google Analytics property ID. Run `seo analytics google properties` to list them."},"start-date":{type:`string`,default:`28daysAgo`},"end-date":{type:`string`,default:`yesterday`},"max-rows":{type:`string`,description:`Maximum Google Analytics rows per query. Defaults to 100000.`},"result-limit":{type:`string`,description:`Maximum ranked landing pages returned. Defaults to 25.`},limit:{type:`string`,description:`Legacy alias for --max-rows.`},refresh:{type:`boolean`,default:!1,description:`Bypass cached Google Analytics responses.`},json:{type:`boolean`,default:!1}},run:async({args:e})=>{let t=O(e),n=await xt({property:Pr(C(e.property)),startDate:C(e[`start-date`]),endDate:C(e[`end-date`]),maxRows:E(e[`max-rows`],`--max-rows`),resultLimit:E(e[`result-limit`],`--result-limit`),limit:E(e.limit,`--limit`),refresh:e.refresh===!0});if(t){P(n);return}if(W({title:`AI referral report`,target:n.property,status:n.dataStatus===`complete`?`info`:`unknown`,summary:n.summary.verdict,metrics:[{label:`Evidence`,value:n.dataStatus===`complete`?`Complete`:`Partial`,status:n.dataStatus===`complete`?`pass`:`unknown`},{label:`AI sessions`,value:B(n.summary.sessions)},{label:`AI users`,value:n.summary.totalUsers===null?`Unavailable`:B(n.summary.totalUsers)},{label:`Sources`,value:B(n.summary.sources)},{label:`Landing pages`,value:B(n.summary.landingPages)},{label:`Landing-page output`,value:`${B(n.selection.landingPages.returnedRows)} returned / ${B(n.selection.landingPages.retainedRows)} retained`},{label:`Source rows`,value:B(n.dataSource.sourceDiscovery.returnedRows)}]}),n.sources.length&&(process.stdout.write(`
65
65
  AI sources
66
- `),q([`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(`
66
+ `),G([`Source`,`Sessions`,`Events`,`Share of AI sessions`],n.sources.map(e=>[e.label,B(e.sessions),B(e.eventCount),H(e.shareOfAiSessions)]))),n.landingPages.length&&(process.stdout.write(`
67
67
  Landing pages
68
- `),q([`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(`
68
+ `),G([`Landing page`,`Sessions`,`Events`,`Top source`],n.landingPages.map(e=>[U(e.landingPage,64),B(e.sessions),B(e.eventCount),e.topSourceDetails.label]))),n.dataSource.partialReasons.length){process.stdout.write(`
69
69
  Evidence warnings
70
- `);for(let e of r.dataSource.partialReasons)process.stdout.write(`- ${e}\n`)}process.stdout.write(`\nNote: ${r.summary.caveat}\n`)}}),es=y({meta:{name:`cannibal`,description:`Find queries with material exposure across multiple URLs`},args:{site:{type:`string`},...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=A(e),n=E(e.refresh),r=await Be({site:j(T(e.site)),days:O(e.days,`--days`),limit:O(e.limit,`--limit`),minImpressions:O(e[`min-impressions`],`--min-impressions`),brandTerms:[...k(e[`brand-terms`])??[]],includeBrand:E(e[`include-brand`]),refresh:n});if(t){I(r);return}K({title:`Cannibalisation report`,target:r.site,status:r.dataStatus===`complete`?`info`:`unknown`,summary:r.summary.verdict,metrics:[{label:`Evidence`,value:r.dataStatus},{label:`Range`,value:`${r.range.startDate} to ${r.range.endDate}`},{label:`Eligible`,value:H(r.summary.eligibleClusters)},{label:`Returned`,value:H(r.summary.returnedClusters)},{label:`Suppressed brand queries`,value:H(r.summary.suppressedQueries)},{label:`GSC completeness`,value:r.source.completeness},{label:`Brand queries`,value:E(e[`include-brand`])?`included`:`excluded`}]}),q([`Query`,`URLs`,`Property impr`,`Exposure`,`HHI`,`Review first`],r.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)])),J(`Top cannibalisation actions`,r.items.map(e=>({label:e.query,context:`${e.pages.length} material URLs; first review ${G(e.suggestedOwnerUrl,64)}`,action:e.recommendation.action}))),Y(`Report caveats`,r.caveats),Y(`Provider usage`,[r.ledgerSummary])}}),ts=y({meta:{name:`ctr-underperformers`,description:`Find high-impression queries with weak CTR for their position`},args:{site:{type:`string`},...X([`includeBrand`,`minImpressions`],{minImpressions:{description:`Minimum query impressions. Defaults to 1 per 5,000 retained impressions, from 30 to 200.`}}),json:{type:`boolean`,default:!1}},run:async({args:e})=>{let t=A(e),n=await Ue({site:j(T(e.site)),minImpressions:D(e[`min-impressions`]),includeBrand:E(e[`include-brand`])});if(t){I(n);return}K({title:`CTR underperformers`,target:n.site,status:n.dataStatus===`partial`?`unknown`:`info`,summary:n.summary.verdict,metrics:[{label:`Underperformers`,value:H(n.summary.underperformers)},{label:`Estimated click gap`,value:H(n.summary.estimatedClickShortfall)},{label:`Brand queries`,value:n.summary.brandFiltering},{label:`Evidence`,value:n.dataStatus},{label:`GSC completeness`,value:n.source.completeness}]}),Y(`Why this matters`,[`These queries already have visibility; the fastest win is usually better SERP framing, not new content.`,`Start with high-impression rows where actual CTR is far below the expected CTR for that ranking position.`]),Y(`Recommended actions`,n.recommendations),Y(`Report caveats`,n.caveats),q([`Query`,`URL`,`Pos`,`Impr`,`CTR`,`Expected`,`Gap`,`Action`],n.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)])),J(`Top CTR actions`,n.items.map(e=>({label:e.query,context:`${H(e.clickShortfall)} click gap, ${W(e.actualCtr)} CTR`,action:e.recommendation.action})))}});function ns(e){return e>0?U(e):`n/a`}const rs=y({meta:{name:`decaying`,description:`Find query/page rows losing clicks between two GSC windows`},args:{site:{type:`string`},...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=A(e),n=E(e.refresh),r=T(e.comparison),i=O(e.days,`--days`),a=O(e.limit,`--limit`),o=O(e[`min-drop-pct`],`--min-drop-pct`),s=O(e[`min-previous-clicks`],`--min-previous-clicks`),c=O(e[`min-click-loss`],`--min-click-loss`);if(r!==void 0&&r!==`previous-period`&&r!==`year-over-year`)throw new g(`INVALID_INPUT`,`--comparison must be previous-period or year-over-year.`);let l=await xn({site:j(T(e.site)),days:i,limit:a,comparison:r,brandTerms:[...k(e[`brand-terms`])??[]],includeBrand:E(e[`include-brand`]),minDropPct:o,minPreviousClicks:s,minClickLoss:c,refresh:n});if(t){I(l);return}K({title:`Decaying pages`,target:l.site,status:l.dataStatus===`complete`?l.summary.returnedRows>0?`warning`:`pass`:`unknown`,summary:l.summary.verdict,metrics:[{label:`Evidence`,value:l.dataStatus},{label:`Comparison`,value:l.comparison},{label:`Observed declines`,value:H(l.summary.returnedRows)},{label:`Eligible declines`,value:H(l.summary.eligibleRows)},{label:`Decay clusters`,value:H(l.summary.groups)},{label:`Observed click decline`,value:H(l.summary.returnedObservedRetainedQueryClickLoss)},{label:`Eligible click decline`,value:H(l.summary.observedRetainedQueryClickLoss)},{label:`Brand queries`,value:l.summary.brandFiltering},{label:`Minimum drop`,value:`${l.filters.minDropPct}%`},{label:`Minimum previous clicks`,value:H(l.filters.minPreviousClicks)},{label:`GSC completeness`,value:l.source.completeness}]}),Y(`Why this matters`,[`This report compares retained query/page rows and only counts declines observed in both windows.`,`Position, CTR, and impression movements are signals for investigation, not proof of cause.`]),Y(`Recommended actions`,l.recommendations),Y(`Report caveats`,l.caveats),Y(`Warnings`,l.warnings),l.items.length&&(l.groups.length&&(q([`Cluster`,`Rows`,`Observed loss`,`Drop`,`Sample query`,`Action`],l.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)])),J(`Top decay cluster actions`,l.groups.map(e=>({label:e.label,context:`${H(e.count)} retained rows, ${H(e.totalClickLoss)} observed click decline`,action:e.recommendation})))),q([`Query`,`Template`,`URL`,`Signals`,`Decline`,`Clicks`,`Impr`,`CTR`,`Pos`,`Action`],l.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)}`,`${ns(e.previous.position)} -> ${ns(e.current.position)}`,G(e.recommendation.action,64)])),J(`Top decay actions`,l.items.map(e=>({label:e.query,context:`${e.template.label}, ${H(e.clickLoss)} fewer observed clicks`,action:e.recommendation.action}))))}}),is=y({meta:{name:`internal-links`,description:`Find verified internal-link review candidates for one URL`},args:{url:{type:`string`,required:!0,description:`Target page that should receive internal links.`},site:{type:`string`,description:`Search Console property, when the URL alone does not pick one.`},"start-date":{type:`string`,description:`Optional YYYY-MM-DD.`},"end-date":{type:`string`,description:`Optional YYYY-MM-DD.`},refresh:{type:`boolean`,default:!1,description:`Bypass the local cache and fetch fresh data.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=A(e),n=E(e.refresh),r=T(e.url)??``,i=await kt({site:await Ar(T(e.site)??r,{refresh:n}),targetUrl:r,startDate:T(e[`start-date`]),endDate:T(e[`end-date`]),refresh:n});if(t){I(i);return}K({title:`Internal link opportunities`,target:i.targetUrl,status:i.summary.failedChecks>0||i.summary.uncheckedCandidates>0?`unknown`:`info`,summary:i.summary.verdict,metrics:[{label:`Target state`,value:i.target.verification},{label:`Preferred target`,value:i.target.preferredUrl},{label:`Target queries`,value:H(i.summary.targetQueries)},{label:`Matched sources`,value:H(i.summary.candidateSources)},{label:`Checked sources`,value:H(i.summary.checkedSources)},{label:`Attempted sources`,value:H(i.summary.attemptedSources)},{label:`Review candidates`,value:H(i.summary.returnedSources)},{label:`Technical exclusions`,value:H(i.summary.technicalExclusions)},{label:`Existing links`,value:H(i.summary.existingLinksObserved)},{label:`Failed checks`,value:H(i.summary.failedChecks)},{label:`Unchecked sources`,value:H(i.summary.uncheckedCandidates)},{label:`GSC completeness`,value:i.source.completeness},{label:`Brand queries`,value:i.summary.brandFiltering}]}),Y(`Why this matters`,[`Exact-query overlap is useful affinity evidence; precision lexical matches are review evidence only.`,`Source and target pages are checked for redirects, indexability, canonicals, and existing link placement before an action is returned.`,`Each source names the sentence where a link fits and anchor text taken from it, when a sentence names the target.`]),Y(`Recommended actions`,i.recommendations),Y(`Report caveats`,i.caveats),i.items.length&&(q([`Source URL`,`Matched impr`,`Match`,`Link evidence`,`Anchor`],i.items.map(e=>[G(e.sourceUrl,52),H(e.matchedQueryImpressions),e.bestMatchKind,e.linkEvidence.status,G(e.placement.sentences[0]?.anchors[0]??`no sentence fits`,32)])),J(`Top internal-link reviews`,i.items.map(e=>({label:G(e.sourceUrl,96),context:`${H(e.matchedQueryImpressions)} matched-query impressions; ${e.bestMatchKind}`,action:e.recommendation.action}))),Y(`Provider usage`,[i.ledgerSummary]))}}),as=y({meta:{name:`page-opportunities`,description:`Find first-party traffic growth opportunities for one URL`},args:{site:{type:`string`},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":Sr(`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=A(e),n=await me({site:j(T(e.site)),url:T(e.url)??``,days:D(e.days),limit:D(e.limit),minImpressions:D(e[`min-impressions`]),includeBrand:E(e[`include-brand`]),verifyContent:E(e[`verify-content`])!==!1,js:E(e.js)?!0:`auto`,refresh:E(e.refresh)});if(t){I(n);return}if(K({title:`Page opportunities`,target:n.url,status:n.verification.status===`failed`?`unknown`:`info`,summary:n.summary.verdict,metrics:[{label:`Title`,value:n.page?.title??`Not fetched`},{label:`Fetch`,value:va(n.page?.fetchDiagnostics)},{label:`Verification`,value:n.verification.status},{label:`Rows`,value:`${H(n.selection.returnedRows)} returned / ${H(n.selection.eligibleRows)} eligible / ${H(n.selection.sourceRows)} source`},{label:`Queries`,value:H(n.summary.queries)},{label:`Impressions`,value:H(n.summary.impressions)},{label:`Opportunities`,value:H(n.summary.opportunities)},{label:`CTR shortfall`,value:H(n.summary.estimatedCtrClickShortfall)},{label:`Focus`,value:n.summary.focus},{label:`Evidence`,value:n.dataStatus}]}),Y(`Why this matters`,[`This report starts from queries where this exact URL already has impressions, so recommendations stay tied to first-party demand.`,`Content verification separates body gaps from title/meta framing issues, which helps avoid adding copy when the page already covers the query.`]),Y(`Recommended actions`,n.recommendations),Y(`Report caveats`,n.caveats),!n.items.length){process.stdout.write(n.dataStatus===`empty`?`No GSC query rows found for this exact URL.
70
+ `);for(let e of n.dataSource.partialReasons)process.stdout.write(`- ${e}\n`)}process.stdout.write(`\nNote: ${n.summary.caveat}\n`)}}),rs=v({meta:{name:`cannibal`,description:`Find queries with material exposure across multiple URLs`},args:{site:{type:`string`},...J([`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=O(e),n=w(e.refresh),r=await u({site:k(C(e.site)),days:E(e.days,`--days`),limit:E(e.limit,`--limit`),minImpressions:E(e[`min-impressions`],`--min-impressions`),brandTerms:[...D(e[`brand-terms`])??[]],includeBrand:w(e[`include-brand`]),refresh:n});if(t){P(r);return}W({title:`Cannibalisation report`,target:r.site,status:r.dataStatus===`complete`?`info`:`unknown`,summary:r.summary.verdict,metrics:[{label:`Evidence`,value:r.dataStatus},{label:`Range`,value:`${r.range.startDate} to ${r.range.endDate}`},{label:`Eligible`,value:B(r.summary.eligibleClusters)},{label:`Returned`,value:B(r.summary.returnedClusters)},{label:`Suppressed brand queries`,value:B(r.summary.suppressedQueries)},{label:`GSC completeness`,value:r.source.completeness},{label:`Brand queries`,value:w(e[`include-brand`])?`included`:`excluded`}]}),G([`Query`,`URLs`,`Property impr`,`Exposure`,`HHI`,`Review first`],r.items.map(e=>[U(e.query,42),e.pages.length,e.propertyImpressions===void 0?`n/a`:B(e.propertyImpressions),B(e.pageExposureImpressions),e.hhi.toFixed(2),U(e.suggestedOwnerUrl,56)])),K(`Top cannibalisation actions`,r.items.map(e=>({label:e.query,context:`${e.pages.length} material URLs; first review ${U(e.suggestedOwnerUrl,64)}`,action:e.recommendation.action}))),q(`Report caveats`,r.caveats),q(`Provider usage`,[r.ledgerSummary])}}),is=v({meta:{name:`ctr-underperformers`,description:`Find high-impression queries with weak CTR for their position`},args:{site:{type:`string`},...J([`includeBrand`,`minImpressions`],{minImpressions:{description:`Minimum query impressions. Defaults to 1 per 5,000 retained impressions, from 30 to 200.`}}),json:{type:`boolean`,default:!1}},run:async({args:e})=>{let t=O(e),n=await f({site:k(C(e.site)),minImpressions:T(e[`min-impressions`]),includeBrand:w(e[`include-brand`])});if(t){P(n);return}W({title:`CTR underperformers`,target:n.site,status:n.dataStatus===`partial`?`unknown`:`info`,summary:n.summary.verdict,metrics:[{label:`Underperformers`,value:B(n.summary.underperformers)},{label:`Estimated click gap`,value:B(n.summary.estimatedClickShortfall)},{label:`Brand queries`,value:n.summary.brandFiltering},{label:`Evidence`,value:n.dataStatus},{label:`GSC completeness`,value:n.source.completeness}]}),q(`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.`]),q(`Recommended actions`,n.recommendations),q(`Report caveats`,n.caveats),G([`Query`,`URL`,`Pos`,`Impr`,`CTR`,`Expected`,`Gap`,`Action`],n.items.map(e=>[U(e.query,36),U(e.url,48),V(e.position),B(e.impressions),H(e.actualCtr),H(e.expectedCtr),B(e.clickShortfall),U(e.recommendation.action,72)])),K(`Top CTR actions`,n.items.map(e=>({label:e.query,context:`${B(e.clickShortfall)} click gap, ${H(e.actualCtr)} CTR`,action:e.recommendation.action})))}});function as(e){return e>0?V(e):`n/a`}const os=v({meta:{name:`decaying`,description:`Find query/page rows losing clicks between two GSC windows`},args:{site:{type:`string`},...J([`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=O(e),n=w(e.refresh),r=C(e.comparison),i=E(e.days,`--days`),a=E(e.limit,`--limit`),o=E(e[`min-drop-pct`],`--min-drop-pct`),s=E(e[`min-previous-clicks`],`--min-previous-clicks`),c=E(e[`min-click-loss`],`--min-click-loss`);if(r!==void 0&&r!==`previous-period`&&r!==`year-over-year`)throw new h(`INVALID_INPUT`,`--comparison must be previous-period or year-over-year.`);let l=await He({site:k(C(e.site)),days:i,limit:a,comparison:r,brandTerms:[...D(e[`brand-terms`])??[]],includeBrand:w(e[`include-brand`]),minDropPct:o,minPreviousClicks:s,minClickLoss:c,refresh:n});if(t){P(l);return}W({title:`Decaying pages`,target:l.site,status:l.dataStatus===`complete`?l.summary.returnedRows>0?`warning`:`pass`:`unknown`,summary:l.summary.verdict,metrics:[{label:`Evidence`,value:l.dataStatus},{label:`Comparison`,value:l.comparison},{label:`Observed declines`,value:B(l.summary.returnedRows)},{label:`Eligible declines`,value:B(l.summary.eligibleRows)},{label:`Decay clusters`,value:B(l.summary.groups)},{label:`Observed click decline`,value:B(l.summary.returnedObservedRetainedQueryClickLoss)},{label:`Eligible click decline`,value:B(l.summary.observedRetainedQueryClickLoss)},{label:`Brand queries`,value:l.summary.brandFiltering},{label:`Minimum drop`,value:`${l.filters.minDropPct}%`},{label:`Minimum previous clicks`,value:B(l.filters.minPreviousClicks)},{label:`GSC completeness`,value:l.source.completeness}]}),q(`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.`]),q(`Recommended actions`,l.recommendations),q(`Report caveats`,l.caveats),q(`Warnings`,l.warnings),l.items.length&&(l.groups.length&&(G([`Cluster`,`Rows`,`Observed loss`,`Drop`,`Sample query`,`Action`],l.groups.map(e=>[U(e.label,42),B(e.count),B(e.totalClickLoss),`${e.averageDropPct.toFixed(1)}%`,U(e.sampleQueries[0]??`-`,40),U(e.recommendation,72)])),K(`Top decay cluster actions`,l.groups.map(e=>({label:e.label,context:`${B(e.count)} retained rows, ${B(e.totalClickLoss)} observed click decline`,action:e.recommendation})))),G([`Query`,`Template`,`URL`,`Signals`,`Decline`,`Clicks`,`Impr`,`CTR`,`Pos`,`Action`],l.items.map(e=>[U(e.query,32),U(e.template.label,26),U(e.url,44),e.signals.join(`, `),B(e.clickLoss),`${B(e.previous.clicks)} -> ${B(e.current.clicks)}`,`${B(e.previous.impressions)} -> ${B(e.current.impressions)}`,`${H(e.previous.ctr)} -> ${H(e.current.ctr)}`,`${as(e.previous.position)} -> ${as(e.current.position)}`,U(e.recommendation.action,64)])),K(`Top decay actions`,l.items.map(e=>({label:e.query,context:`${e.template.label}, ${B(e.clickLoss)} fewer observed clicks`,action:e.recommendation.action}))))}}),ss=v({meta:{name:`internal-links`,description:`Find verified internal-link review candidates for one URL`},args:{url:{type:`string`,required:!0,description:`Target page that should receive internal links.`},site:{type:`string`,description:`Search Console property, when the URL alone does not pick one.`},"start-date":{type:`string`,description:`Optional YYYY-MM-DD.`},"end-date":{type:`string`,description:`Optional YYYY-MM-DD.`},refresh:{type:`boolean`,default:!1,description:`Bypass the local cache and fetch fresh data.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=O(e),n=w(e.refresh),r=C(e.url)??``,i=await Ot({site:await Nr(C(e.site)??r,{refresh:n}),targetUrl:r,startDate:C(e[`start-date`]),endDate:C(e[`end-date`]),refresh:n});if(t){P(i);return}W({title:`Internal link opportunities`,target:i.targetUrl,status:i.summary.failedChecks>0||i.summary.uncheckedCandidates>0?`unknown`:`info`,summary:i.summary.verdict,metrics:[{label:`Target state`,value:i.target.verification},{label:`Preferred target`,value:i.target.preferredUrl},{label:`Target queries`,value:B(i.summary.targetQueries)},{label:`Matched sources`,value:B(i.summary.candidateSources)},{label:`Checked sources`,value:B(i.summary.checkedSources)},{label:`Attempted sources`,value:B(i.summary.attemptedSources)},{label:`Review candidates`,value:B(i.summary.returnedSources)},{label:`Technical exclusions`,value:B(i.summary.technicalExclusions)},{label:`Existing links`,value:B(i.summary.existingLinksObserved)},{label:`Failed checks`,value:B(i.summary.failedChecks)},{label:`Unchecked sources`,value:B(i.summary.uncheckedCandidates)},{label:`GSC completeness`,value:i.source.completeness},{label:`Brand queries`,value:i.summary.brandFiltering}]}),q(`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.`,`Each source names the sentence where a link fits and anchor text taken from it, when a sentence names the target.`]),q(`Recommended actions`,i.recommendations),q(`Report caveats`,i.caveats),i.items.length&&(G([`Source URL`,`Matched impr`,`Match`,`Link evidence`,`Anchor`],i.items.map(e=>[U(e.sourceUrl,52),B(e.matchedQueryImpressions),e.bestMatchKind,e.linkEvidence.status,U(e.placement.sentences[0]?.anchors[0]??`no sentence fits`,32)])),K(`Top internal-link reviews`,i.items.map(e=>({label:U(e.sourceUrl,96),context:`${B(e.matchedQueryImpressions)} matched-query impressions; ${e.bestMatchKind}`,action:e.recommendation.action}))),q(`Provider usage`,[i.ledgerSummary]))}}),cs=v({meta:{name:`page-opportunities`,description:`Find first-party traffic growth opportunities for one URL`},args:{site:{type:`string`},url:{type:`string`,required:!0},...J([`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":Tr(`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=O(e),n=await rt({site:k(C(e.site)),url:C(e.url)??``,days:T(e.days),limit:T(e.limit),minImpressions:T(e[`min-impressions`]),includeBrand:w(e[`include-brand`]),verifyContent:w(e[`verify-content`])!==!1,js:w(e.js)?!0:`auto`,refresh:w(e.refresh)});if(t){P(n);return}if(W({title:`Page opportunities`,target:n.url,status:n.verification.status===`failed`?`unknown`:`info`,summary:n.summary.verdict,metrics:[{label:`Title`,value:n.page?.title??`Not fetched`},{label:`Fetch`,value:xa(n.page?.fetchDiagnostics)},{label:`Verification`,value:n.verification.status},{label:`Rows`,value:`${B(n.selection.returnedRows)} returned / ${B(n.selection.eligibleRows)} eligible / ${B(n.selection.sourceRows)} source`},{label:`Queries`,value:B(n.summary.queries)},{label:`Impressions`,value:B(n.summary.impressions)},{label:`Opportunities`,value:B(n.summary.opportunities)},{label:`CTR shortfall`,value:B(n.summary.estimatedCtrClickShortfall)},{label:`Focus`,value:n.summary.focus},{label:`Evidence`,value:n.dataStatus}]}),q(`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.`]),q(`Recommended actions`,n.recommendations),q(`Report caveats`,n.caveats),!n.items.length){process.stdout.write(n.dataStatus===`empty`?`No GSC query rows found for this exact URL.
71
71
  `:`GSC rows were found, but none met the report criteria.
72
- `);return}q([`Type`,`Query`,`Evidence`,`Pos`,`CTR`,`Expected`,`Impr`,`Shortfall`,`Action`],n.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)])),J(`Top page actions`,n.items.map(e=>({label:e.query,context:`${e.opportunityType}, ${H(e.impressions)} impressions`,action:e.recommendation})))}}),os=y({meta:{name:`query-cluster`,description:`Cluster GSC queries into repeated demand themes`},args:{site:{type:`string`},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=A(e),n=await fn({site:j(T(e.site)),scope:T(e.scope),includeBrand:E(e[`include-brand`]),minImpressions:D(e[`min-impressions`]),limit:D(e.limit)});if(t){I(n);return}K({title:`Query clusters`,target:n.site,status:`info`,summary:n.summary.verdict,metrics:[{label:`Scope`,value:n.scope??`All pages`},{label:`Clusters`,value:H(n.summary.clusters)},{label:`Queries`,value:H(n.summary.queries)},{label:`Impressions`,value:H(n.summary.impressions)},{label:`Clicks`,value:H(n.summary.clicks)},{label:`Minimum impressions`,value:H(n.summary.minImpressions)},{label:`Result limit`,value:H(n.summary.limit)},{label:`High opportunity`,value:H(n.summary.highOpportunityClusters)},{label:`Brand queries`,value:n.summary.brandFiltering}]}),Y(`Why this matters`,[`Clusters show repeated demand themes, so they are better inputs for page sections, hubs, and templates than one-off query exports.`,`Prioritise clusters with high impressions and low clicks; they usually reveal unclear intent coverage or weak SERP framing.`]),Y(`Recommended actions`,n.recommendations),Y(`Report caveats`,n.caveats),q([`Cluster`,`Intent`,`Queries`,`Impr`,`CTR`,`Expected`,`Lift`,`Action`],n.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)])),J(`Top cluster actions`,n.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??``})))}}),ss=y({meta:{name:`quick-wins`,description:`Find high-ranking low-CTR query/page opportunities`},args:{site:{type:`string`},...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=A(e),n=D(e[`verify-limit`]),r=E(e[`verify-content`])===!0||n!==void 0,i=await ft({site:j(T(e.site)),days:D(e.days),limit:D(e.limit),minImpressions:D(e[`min-impressions`]),brandTerms:[...k(e[`brand-terms`])??[]],includeBrand:E(e[`include-brand`]),verifyContent:r,verifyLimit:n,js:E(e.js)?!0:void 0,rate:Or(e),refresh:E(e.refresh)});if(t){I(i);return}K({title:`Quick wins`,target:i.site,status:i.source.possiblyTruncated?`unknown`:`info`,summary:i.summary.verdict,metrics:[{label:`Eligible rows`,value:H(i.summary.eligibleRows)},{label:`Returned rows`,value:H(i.summary.returnedRows)},{label:`Query clusters`,value:H(i.summary.repeatedQueryGroups)},{label:`Template patterns`,value:H(i.summary.templatePatterns)},{label:`CTR click shortfall`,value:H(i.summary.returnedEstimatedCtrClickShortfall)},{label:`Brand queries`,value:i.summary.brandFiltering},{label:`Evidence`,value:i.dataStatus},{label:`Verification`,value:Zi(i)}]}),Y(`Why this matters`,[`These rows have GSC average positions from 4 to 10 and observed CTR below a site-peer or versioned fallback target.`,`Page evidence is needed before treating a CTR shortfall as a title, content, or technical finding.`]),Y(`Recommended actions`,i.recommendations),Y(`Report caveats`,i.caveats),i.items.length&&(i.templateRecommendations.length&&(q([`Template`,`URLs`,`Rows`,`Shortfall`,`Impr`,`Action`],i.templateRecommendations.map(e=>[G(e.templateLabel,34),H(e.urlCount),H(e.rowCount),H(e.totalEstimatedCtrClickShortfall),H(e.totalImpressions),G(e.action,72)])),J(`Top template actions`,i.templateRecommendations.map(e=>({label:e.templateLabel,context:`${H(e.urlCount)} URLs, ${H(e.totalEstimatedCtrClickShortfall)} heuristic CTR click shortfall`,action:`${e.action} ${e.evidence}`})))),i.groups.length&&(q([`Cluster`,`URLs`,`Rows`,`Shortfall`,`Impr`,`Sample URL`,`Action`],i.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)])),J(`Top cluster actions`,i.groups.map(e=>({label:e.label,context:`${H(e.urlCount)} URLs, ${H(e.totalEstimatedCtrClickShortfall)} heuristic CTR click shortfall`,action:e.recommendation})))),q([`Query`,`Template`,`URL`,`Pos`,`Impr`,`CTR / target`,`Shortfall`,`Fetch`,`Check`,`Action`],i.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),va(e.contentVerification?.fetchDiagnostics),ya(e.contentVerification?.classification),G(e.recommendation.action,64)])),J(`Top opportunity actions`,i.items.map(e=>({label:e.query,context:`${e.template.label}, pos ${U(e.position)}, ${H(e.impressions)} impressions`,action:e.recommendation.action}))))}}),cs=y({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.`},...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=A(e),n=D(e[`verify-limit`]),r=E(e[`verify-content`])===!0||n!==void 0,i=await Vt({site:j(T(e.site)),range:D(e.days),minImpressions:D(e[`min-impressions`]),limit:T(e.limit)?Number(T(e.limit)):10,includeBrand:E(e[`include-brand`]),verifyContent:r,verifyLimit:n,js:E(e.js)?!0:void 0,rate:Or(e),refresh:E(e.refresh)});if(t){I(i);return}if(K({title:`Second-page opportunities`,target:i.site,status:i.source.possiblyTruncated||i.summary.fetchFailures>0?`unknown`:`info`,summary:i.summary.verdict,metrics:[{label:`Eligible pages`,value:H(i.summary.eligiblePages)},{label:`Returned pages`,value:H(i.summary.returnedPages)},{label:`Eligible queries`,value:H(i.summary.eligibleQueries)},{label:`Impressions`,value:H(i.summary.eligibleImpressions)},{label:`Content issues`,value:H(i.summary.contentIssues)},{label:`Technical issues`,value:H(i.summary.technicalIssues)},{label:`Fetch failures`,value:H(i.summary.fetchFailures)},{label:`Brand queries`,value:i.summary.brandFiltering},{label:`Evidence`,value:i.dataStatus},{label:`Verification`,value:Zi(i)}]}),Y(`Recommended actions`,i.recommendations),Y(`Report caveats`,i.caveats),!i.items.length){process.stdout.write(`No average-position candidates matched this report.
73
- `),process.stdout.write(`${i.ledgerSummary}\n`);return}q([`Query`,`Template`,`Pos`,`Impr`,`CTR`,`Queries`,`Fetch`,`Check`,`Action`],i.items.map(e=>[e.primaryQuery,e.template.label,e.position.toFixed(1),Math.round(e.impressions),e.ctr.toFixed(3),H(e.queryCount),va(e.fetchDiagnostics),ya(e.contentVerification?.classification),e.recommendation.action])),J(`Top second-page actions`,i.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(`${i.ledgerSummary}\n`)}}),ls=y({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.`},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=await tt({url:T(e.url)??``,site:T(e.site),js:E(e.js)?!0:`auto`,refresh:E(e.refresh)});if(A(e)){I(t);return}K({title:`Page audit`,target:t.url,status:t.issues.some(e=>e.severity===`high`)?`fail`:t.issues.length>0?`warning`:`pass`,summary:t.issues.length>0?`${t.issues.length} ${t.issues.length===1?`issue needs`:`issues need`} review.`:`No page issues were found by the available checks.`,metrics:[{label:`Final URL`,value:t.page.finalUrl},{label:`Title`,value:t.page.title??`Missing`},{label:`Meta description`,value:t.page.metaDescription??`Missing`},{label:`Words`,value:t.page.wordCount},{label:`Fetch`,value:va(t.fetchDiagnostics)}],diagnostics:[{title:`Issues`,items:t.issues.map(e=>({status:e.severity===`high`?`fail`:`warning`,title:`${e.code}: ${e.principle}`,explanation:e.detail}))}]})}});function us(e){let t=T(e)??`mobile`;if(t!==`mobile`&&t!==`desktop`)throw new g(`INVALID_INPUT`,`Invalid --strategy. Use mobile or desktop.`);return t}function ds(e){return e?e.displayValue??String(e.value??`not measured`):`not measured`}function fs(e){if(!e)return`not available`;let t=e.unit===`milliseconds`?`ms`:``;return`${e.p75}${t} (${e.rating})`}const ps=y({meta:{name:`perf`,description:`Audit lab performance and field Core Web Vitals`},subCommands:{audit:y({meta:{name:`audit`,description:`Run a Lighthouse/Core Web Vitals performance audit`},args:{url:{type:`string`,description:`URL to audit.`},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=A(e),n=T(e.url);if(!n)throw new g(`INVALID_INPUT`,`No URL given. Pass --url.`);let r=await vt({url:n,strategy:us(e.strategy),lighthouseBin:T(e[`lighthouse-bin`]),cruxApiKey:T(e[`crux-key`]),refresh:E(e.refresh),includeRaw:t&&E(e.raw)});if(t){I(r);return}K({title:`Performance report`,target:r.url,status:r.dataStatus===`partial`?`unknown`:r.grade===`good`?`pass`:`warning`,summary:r.headline,metrics:[{label:`Source`,value:r.source},{label:`Strategy`,value:r.strategy},{label:`Lab score`,value:r.score===void 0?`Unknown`:`${r.score}/100`},{label:`Lab grade`,value:r.grade},{label:`Evidence`,value:r.dataStatus},{label:`FCP`,value:ds(r.metrics.firstContentfulPaint)},{label:`LCP`,value:ds(r.metrics.largestContentfulPaint)},{label:`TBT`,value:ds(r.metrics.totalBlockingTime)},{label:`CLS`,value:ds(r.metrics.cumulativeLayoutShift)},{label:`INP`,value:ds(r.metrics.interactionToNextPaint)},{label:`Server response`,value:ds(r.metrics.serverResponseTime)},{label:`Fallback fetch`,value:ds(r.metrics.fallbackFetchDuration)},{label:`Field data`,value:r.fieldData?`${r.fieldData.scope} ${r.fieldData.formFactor.toLowerCase()} (${r.fieldData.assessment.status})`:r.fieldDataStatus.status},{label:`Field LCP p75`,value:fs(r.fieldData?.metrics.largestContentfulPaint)},{label:`Field INP p75`,value:fs(r.fieldData?.metrics.interactionToNextPaint)},{label:`Field CLS p75`,value:fs(r.fieldData?.metrics.cumulativeLayoutShift)},{label:`Field period`,value:r.fieldData?.collectionPeriod?.firstDate&&r.fieldData.collectionPeriod.lastDate?`${r.fieldData.collectionPeriod.firstDate} to ${r.fieldData.collectionPeriod.lastDate}`:`Unavailable`}]}),J(`Top actions`,r.topActions.map(e=>({label:e.title,action:e.action,context:e.plainEnglish}))),Y(`Caveats`,r.caveats)}})}}),ms=y({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 yn();if(A(e)){I(t),t.ok||(process.exitCode=1);return}pi(`SEO doctor`,ii(t.checks)),process.stdout.write(`
74
- `),mi(t.checks),process.stdout.write(`\nChecked ${t.generatedAt}\n`),t.ok||(process.exitCode=1)}}),hs=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},gs=y({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.`},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=A(e),n=hs(e.dimension),r=O(e.days,`--days`),i=O(e.compare,`--compare`),a=O(e.limit,`--limit`),o=O(e[`unmatched-limit`],`--unmatched-limit`),s=O(e[`max-rows`],`--max-rows`),c=await u({site:j(T(e.site)),dimension:n,days:r,compareDays:i,startDate:T(e[`start-date`]),endDate:T(e[`end-date`]),limit:a,unmatchedLimit:o,maxRows:s,refresh:E(e.refresh)});if(t){I(c);return}K({title:`Segment impact`,target:c.site,status:c.dataStatus===`complete`?`info`:`unknown`,summary:c.summary.verdict,metrics:[{label:`Dimension`,value:c.dimension},{label:`Evidence`,value:c.dataStatus},{label:`Before`,value:`${c.before.startDate} to ${c.before.endDate}`},{label:`After`,value:`${c.after.startDate} to ${c.after.endDate}`}]}),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(`
72
+ `);return}G([`Type`,`Query`,`Evidence`,`Pos`,`CTR`,`Expected`,`Impr`,`Shortfall`,`Action`],n.items.map(e=>[e.opportunityType,U(e.query,38),e.verification.signals.length?e.verification.signals.join(`, `):e.verification.status,e.position.toFixed(1),H(e.ctr),e.expectedCtr===void 0?`n/a`:H(e.expectedCtr),B(e.impressions),e.estimatedCtrClickShortfall===void 0?`n/a`:B(e.estimatedCtrClickShortfall),U(e.recommendation,72)])),K(`Top page actions`,n.items.map(e=>({label:e.query,context:`${e.opportunityType}, ${B(e.impressions)} impressions`,action:e.recommendation})))}}),ls=v({meta:{name:`query-cluster`,description:`Cluster GSC queries into repeated demand themes`},args:{site:{type:`string`},scope:{type:`string`},...J([`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=O(e),n=await bt({site:k(C(e.site)),scope:C(e.scope),includeBrand:w(e[`include-brand`]),minImpressions:T(e[`min-impressions`]),limit:T(e.limit)});if(t){P(n);return}W({title:`Query clusters`,target:n.site,status:`info`,summary:n.summary.verdict,metrics:[{label:`Scope`,value:n.scope??`All pages`},{label:`Clusters`,value:B(n.summary.clusters)},{label:`Queries`,value:B(n.summary.queries)},{label:`Impressions`,value:B(n.summary.impressions)},{label:`Clicks`,value:B(n.summary.clicks)},{label:`Minimum impressions`,value:B(n.summary.minImpressions)},{label:`Result limit`,value:B(n.summary.limit)},{label:`High opportunity`,value:B(n.summary.highOpportunityClusters)},{label:`Brand queries`,value:n.summary.brandFiltering}]}),q(`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.`]),q(`Recommended actions`,n.recommendations),q(`Report caveats`,n.caveats),G([`Cluster`,`Intent`,`Queries`,`Impr`,`CTR`,`Expected`,`Lift`,`Action`],n.clusters.map(e=>[U(e.label,32),e.intent,e.queries.length,B(e.totals?.impressions??0),H(e.totals?.ctr??0),H(e.benchmark?.expectedCtr??0),e.estimatedClickLift===void 0?`-`:B(e.estimatedClickLift),U(e.recommendation??``,72)])),K(`Top cluster actions`,n.clusters.map(e=>({label:e.label,context:`${B(e.totals?.impressions??0)} impressions, ${e.estimatedClickLift===void 0?`ranking opportunity`:`${B(e.estimatedClickLift)} estimated click lift`}`,action:e.recommendation??``})))}}),us=v({meta:{name:`quick-wins`,description:`Find high-ranking low-CTR query/page opportunities`},args:{site:{type:`string`},...J([`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=O(e),n=T(e[`verify-limit`]),r=w(e[`verify-content`])===!0||n!==void 0,i=await Cn({site:k(C(e.site)),days:T(e.days),limit:T(e.limit),minImpressions:T(e[`min-impressions`]),brandTerms:[...D(e[`brand-terms`])??[]],includeBrand:w(e[`include-brand`]),verifyContent:r,verifyLimit:n,js:w(e.js)?!0:void 0,rate:jr(e),refresh:w(e.refresh)});if(t){P(i);return}W({title:`Quick wins`,target:i.site,status:i.source.possiblyTruncated?`unknown`:`info`,summary:i.summary.verdict,metrics:[{label:`Eligible rows`,value:B(i.summary.eligibleRows)},{label:`Returned rows`,value:B(i.summary.returnedRows)},{label:`Query clusters`,value:B(i.summary.repeatedQueryGroups)},{label:`Template patterns`,value:B(i.summary.templatePatterns)},{label:`CTR click shortfall`,value:B(i.summary.returnedEstimatedCtrClickShortfall)},{label:`Brand queries`,value:i.summary.brandFiltering},{label:`Evidence`,value:i.dataStatus},{label:`Verification`,value:ea(i)}]}),q(`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.`]),q(`Recommended actions`,i.recommendations),q(`Report caveats`,i.caveats),i.items.length&&(i.templateRecommendations.length&&(G([`Template`,`URLs`,`Rows`,`Shortfall`,`Impr`,`Action`],i.templateRecommendations.map(e=>[U(e.templateLabel,34),B(e.urlCount),B(e.rowCount),B(e.totalEstimatedCtrClickShortfall),B(e.totalImpressions),U(e.action,72)])),K(`Top template actions`,i.templateRecommendations.map(e=>({label:e.templateLabel,context:`${B(e.urlCount)} URLs, ${B(e.totalEstimatedCtrClickShortfall)} heuristic CTR click shortfall`,action:`${e.action} ${e.evidence}`})))),i.groups.length&&(G([`Cluster`,`URLs`,`Rows`,`Shortfall`,`Impr`,`Sample URL`,`Action`],i.groups.map(e=>[U(e.label,44),B(e.urlCount),B(e.rowCount),B(e.totalEstimatedCtrClickShortfall),B(e.totalImpressions),U(e.sampleUrls[0]??`-`,46),U(e.recommendation,72)])),K(`Top cluster actions`,i.groups.map(e=>({label:e.label,context:`${B(e.urlCount)} URLs, ${B(e.totalEstimatedCtrClickShortfall)} heuristic CTR click shortfall`,action:e.recommendation})))),G([`Query`,`Template`,`URL`,`Pos`,`Impr`,`CTR / target`,`Shortfall`,`Fetch`,`Check`,`Action`],i.items.map(e=>[U(e.query,36),U(e.template.label,24),U(e.url,48),V(e.position),B(e.impressions),`${H(e.ctr)} / ${H(e.targetCtr)}`,B(e.estimatedCtrClickShortfall),xa(e.contentVerification?.fetchDiagnostics),Sa(e.contentVerification?.classification),U(e.recommendation.action,64)])),K(`Top opportunity actions`,i.items.map(e=>({label:e.query,context:`${e.template.label}, pos ${V(e.position)}, ${B(e.impressions)} impressions`,action:e.recommendation.action}))))}}),ds=v({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.`},...J([`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=O(e),n=T(e[`verify-limit`]),r=w(e[`verify-content`])===!0||n!==void 0,i=await mn({site:k(C(e.site)),range:T(e.days),minImpressions:T(e[`min-impressions`]),limit:C(e.limit)?Number(C(e.limit)):10,includeBrand:w(e[`include-brand`]),verifyContent:r,verifyLimit:n,js:w(e.js)?!0:void 0,rate:jr(e),refresh:w(e.refresh)});if(t){P(i);return}if(W({title:`Second-page opportunities`,target:i.site,status:i.source.possiblyTruncated||i.summary.fetchFailures>0?`unknown`:`info`,summary:i.summary.verdict,metrics:[{label:`Eligible pages`,value:B(i.summary.eligiblePages)},{label:`Returned pages`,value:B(i.summary.returnedPages)},{label:`Eligible queries`,value:B(i.summary.eligibleQueries)},{label:`Impressions`,value:B(i.summary.eligibleImpressions)},{label:`Content issues`,value:B(i.summary.contentIssues)},{label:`Technical issues`,value:B(i.summary.technicalIssues)},{label:`Fetch failures`,value:B(i.summary.fetchFailures)},{label:`Brand queries`,value:i.summary.brandFiltering},{label:`Evidence`,value:i.dataStatus},{label:`Verification`,value:ea(i)}]}),q(`Recommended actions`,i.recommendations),q(`Report caveats`,i.caveats),!i.items.length){process.stdout.write(`No average-position candidates matched this report.
73
+ `),process.stdout.write(`${i.ledgerSummary}\n`);return}G([`Query`,`Template`,`Pos`,`Impr`,`CTR`,`Queries`,`Fetch`,`Check`,`Action`],i.items.map(e=>[e.primaryQuery,e.template.label,e.position.toFixed(1),Math.round(e.impressions),e.ctr.toFixed(3),B(e.queryCount),xa(e.fetchDiagnostics),Sa(e.contentVerification?.classification),e.recommendation.action])),K(`Top second-page actions`,i.items.map(e=>({label:e.primaryQuery,context:`${e.template.label}, pos ${V(e.position)}, ${B(e.impressions)} impressions`,action:e.recommendation.action}))),process.stdout.write(`${i.ledgerSummary}\n`)}}),fs=v({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.`},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=await tt({url:C(e.url)??``,site:C(e.site),js:w(e.js)?!0:`auto`,refresh:w(e.refresh)});if(O(e)){P(t);return}W({title:`Page audit`,target:t.url,status:t.issues.some(e=>e.severity===`high`)?`fail`:t.issues.length>0?`warning`:`pass`,summary:t.issues.length>0?`${t.issues.length} ${t.issues.length===1?`issue needs`:`issues need`} review.`:`No page issues were found by the available checks.`,metrics:[{label:`Final URL`,value:t.page.finalUrl},{label:`Title`,value:t.page.title??`Missing`},{label:`Meta description`,value:t.page.metaDescription??`Missing`},{label:`Words`,value:t.page.wordCount},{label:`Fetch`,value:xa(t.fetchDiagnostics)}],diagnostics:[{title:`Issues`,items:t.issues.map(e=>({status:e.severity===`high`?`fail`:`warning`,title:`${e.code}: ${e.principle}`,explanation:e.detail}))}]})}});function ps(e){let t=C(e)??`mobile`;if(t!==`mobile`&&t!==`desktop`)throw new h(`INVALID_INPUT`,`Invalid --strategy. Use mobile or desktop.`);return t}function ms(e){return e?e.displayValue??String(e.value??`not measured`):`not measured`}function hs(e){if(!e)return`not available`;let t=e.unit===`milliseconds`?`ms`:``;return`${e.p75}${t} (${e.rating})`}const gs=v({meta:{name:`perf`,description:`Audit lab performance and field Core Web Vitals`},subCommands:{audit:v({meta:{name:`audit`,description:`Run a Lighthouse/Core Web Vitals performance audit`},args:{url:{type:`string`,description:`URL to audit.`},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=O(e),n=C(e.url);if(!n)throw new h(`INVALID_INPUT`,`No URL given. Pass --url.`);let r=await Tt({url:n,strategy:ps(e.strategy),lighthouseBin:C(e[`lighthouse-bin`]),cruxApiKey:C(e[`crux-key`]),refresh:w(e.refresh),includeRaw:t&&w(e.raw)});if(t){P(r);return}W({title:`Performance report`,target:r.url,status:r.dataStatus===`partial`?`unknown`:r.grade===`good`?`pass`:`warning`,summary:r.headline,metrics:[{label:`Source`,value:r.source},{label:`Strategy`,value:r.strategy},{label:`Lab score`,value:r.score===void 0?`Unknown`:`${r.score}/100`},{label:`Lab grade`,value:r.grade},{label:`Evidence`,value:r.dataStatus},{label:`FCP`,value:ms(r.metrics.firstContentfulPaint)},{label:`LCP`,value:ms(r.metrics.largestContentfulPaint)},{label:`TBT`,value:ms(r.metrics.totalBlockingTime)},{label:`CLS`,value:ms(r.metrics.cumulativeLayoutShift)},{label:`INP`,value:ms(r.metrics.interactionToNextPaint)},{label:`Server response`,value:ms(r.metrics.serverResponseTime)},{label:`Fallback fetch`,value:ms(r.metrics.fallbackFetchDuration)},{label:`Field data`,value:r.fieldData?`${r.fieldData.scope} ${r.fieldData.formFactor.toLowerCase()} (${r.fieldData.assessment.status})`:r.fieldDataStatus.status},{label:`Field LCP p75`,value:hs(r.fieldData?.metrics.largestContentfulPaint)},{label:`Field INP p75`,value:hs(r.fieldData?.metrics.interactionToNextPaint)},{label:`Field CLS p75`,value:hs(r.fieldData?.metrics.cumulativeLayoutShift)},{label:`Field period`,value:r.fieldData?.collectionPeriod?.firstDate&&r.fieldData.collectionPeriod.lastDate?`${r.fieldData.collectionPeriod.firstDate} to ${r.fieldData.collectionPeriod.lastDate}`:`Unavailable`}]}),K(`Top actions`,r.topActions.map(e=>({label:e.title,action:e.action,context:e.plainEnglish}))),q(`Caveats`,r.caveats)}})}}),_s=v({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 xn();if(O(e)){P(t),t.ok||(process.exitCode=1);return}gi(`SEO doctor`,si(t.checks)),process.stdout.write(`
74
+ `),_i(t.checks),process.stdout.write(`\nChecked ${t.generatedAt}\n`),t.ok||(process.exitCode=1)}}),vs=e=>{let t=C(e)??`page`;if(t!==`page`&&t!==`query`&&t!==`country`&&t!==`device`)throw Error(`Invalid --dimension. Use page, query, country, or device.`);return t},ys=v({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.`},dimension:{type:`string`,default:`page`,description:`Segment by page, query, country, or device.`},...J([`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=O(e),n=vs(e.dimension),r=E(e.days,`--days`),i=E(e.compare,`--compare`),a=E(e.limit,`--limit`),o=E(e[`unmatched-limit`],`--unmatched-limit`),s=E(e[`max-rows`],`--max-rows`),c=await bn({site:k(C(e.site)),dimension:n,days:r,compareDays:i,startDate:C(e[`start-date`]),endDate:C(e[`end-date`]),limit:a,unmatchedLimit:o,maxRows:s,refresh:w(e.refresh)});if(t){P(c);return}W({title:`Segment impact`,target:c.site,status:c.dataStatus===`complete`?`info`:`unknown`,summary:c.summary.verdict,metrics:[{label:`Dimension`,value:c.dimension},{label:`Evidence`,value:c.dataStatus},{label:`Before`,value:`${c.before.startDate} to ${c.before.endDate}`},{label:`After`,value:`${c.after.startDate} to ${c.after.endDate}`}]}),I([`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(`
75
75
  One-window evidence (not treated as zero)
76
- `),R([`Segment`,`Retained in`,`Clicks`,`Impressions`],c.unmatchedSegments.map(e=>[e.key,e.retainedIn,e.clicks,e.impressions]))),Y(`Warnings`,c.warnings)}});function _s(e){return`${H(e)} ${e===1?`row`:`rows`}`}const vs=y({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.`},...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=A(e),n=D(e[`verify-limit`]),r=E(e[`verify-content`])===!0||n!==void 0,i=await wn({site:j(T(e.site)),days:D(e.days),minImpressions:D(e[`min-impressions`]),limit:D(e.limit),includeBrand:E(e[`include-brand`]),verifyContent:r,verifyLimit:n,js:E(e.js)?!0:void 0,rate:Or(e),refresh:E(e.refresh)});if(t){I(i);return}if(K({title:`Striking-distance opportunities`,target:i.site,status:i.source.possiblyTruncated?`unknown`:`info`,summary:i.summary.verdict,metrics:[{label:`Eligible rows`,value:H(i.summary.eligibleRows)},{label:`Returned rows`,value:H(i.summary.returnedRows)},{label:`Template groups`,value:H(i.summary.groups)},{label:`Impressions`,value:H(i.summary.eligibleImpressions)},{label:`Brand queries`,value:i.summary.brandFiltering},{label:`Verification`,value:i.verification.requested?`Requested`:`Off`},{label:`Evidence`,value:i.dataStatus}]}),Y(`Why this matters`,[`These rows have a GSC average position above 10 and at most 20. Treat them as candidates for investigation, not guaranteed page-two rankings.`,`Groups use all eligible rows. A shared-template label is only a candidate until recurring evidence is verified across distinct URLs.`]),Y(`Recommended actions`,i.recommendations),Y(`Report caveats`,i.caveats),i.groups.length&&(q([`Group`,`Rows`,`URLs`,`Impr`,`Avg pos`,`Scope`,`Action`],i.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)])),J(`Top striking-distance group actions`,i.groups.map(e=>({label:e.label,context:`${_s(e.rowCount)}, ${H(e.totalImpressions)} impressions`,action:e.recommendation.action})))),!i.items.length){process.stdout.write(`No striking-distance opportunities matched these filters.
77
- `);return}q([`Query`,`Template`,`URL`,`Impr`,`CTR`,`Pos`,`Score`,`Fetch`,`Check`,`Action`],i.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,va(e.contentVerification?.fetchDiagnostics),ya(e.contentVerification?.classification),G(e.recommendation.action,72)])),J(`Top striking-distance actions`,i.items.map(e=>({label:e.query,context:`${e.template.label}, pos ${U(e.position)}, ${H(e.impressions)} impressions`,action:e.recommendation.action})))}});function ys(e){return e===`keychain`?`system keychain`:e===`file`?`private local file`:e??`missing`}function bs(e){return e===null?`unavailable`:`$${(e/1e6).toFixed(2)}`}function xs(e){return e.limit===null?e.used===null?`not reported`:`${e.used.toLocaleString(`en-US`)} used; no key limit`:`${(e.remaining??0).toLocaleString(`en-US`)} of ${e.limit.toLocaleString(`en-US`)} remaining`}const Ss=y({meta:{name:`ahrefs`,description:`Connect Ahrefs for optional search and link data`},subCommands:{connect:y({meta:{name:`connect`,description:`Validate and save an Ahrefs API v3 key`},args:{json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{if(!F({json:A(e)}))throw new g(`AUTH_REQUIRED`,`Run \`seo providers ahrefs connect\` in a terminal. Agents and CI can set ${ue}.`);b(`Connect Ahrefs`),x(`Paste an API v3 key from Ahrefs Account settings. The validation check and Domain Rating are free; other research can consume API units.`,`API key`);let t=z(await C({message:`Ahrefs API v3 key`,validate:e=>e?.trim()?void 0:`API key is required`})),n=await new un({apiKey:t}).limitsAndUsage(),r=await Tn(t);x(`${n.subscription} subscription. API key units: ${xs(n.apiKeyUnits)}.`,`Connection verified`),S(`Saved in the ${ys(r)}. Run seo providers ahrefs status --check to verify it again.`)}}),status:y({meta:{name:`status`,description:`Show the local Ahrefs connection`},args:{check:{type:`boolean`,default:!1,description:`Verify the API key with the free account endpoint.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=await Fe(),n=!!e.check,r=n&&t?await new un().limitsAndUsage():void 0,i={connected:!!t,apiVersion:t?3:null,credentialSource:t?.source,liveCheck:r?{status:`passed`,subscription:r.subscription,apiKeyExpiresAt:r.apiKeyExpiresAt,usageResetsAt:r.usageResetsAt,apiKeyUnits:r.apiKeyUnits,workspaceUnits:r.workspaceUnits,observedAt:r.observedAt,requestCostUnits:r.requestCostUnits}:{status:n?`unavailable`:`not-requested`}};if(A(e)){I(i);return}L([[`Connected`,i.connected?`yes`:`no`],[`API version`,i.apiVersion?`Version 3`:`not connected`],[`Credential`,ys(i.credentialSource)],[`Live check`,i.liveCheck.status===`passed`?`passed at ${i.liveCheck.observedAt}`:i.liveCheck.status===`unavailable`?`not available without credentials`:`not requested; pass --check to verify`],...r?[[`Subscription`,r.subscription],[`API key units`,xs(r.apiKeyUnits)],[`Workspace units`,xs(r.workspaceUnits)],[`Usage resets`,r.usageResetsAt],[`Key expires`,r.apiKeyExpiresAt]]:[]])}}),disconnect:y({meta:{name:`disconnect`,description:`Remove the saved Ahrefs API v3 key`},args:{json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{await ve();let t=!!process.env[ue]?.trim(),n={savedCredentialRemoved:!0,environmentCredential:t?`active`:`missing`,note:t?`The environment variable was not changed. Clear ${ue} to fully disconnect.`:`Ahrefs is disconnected.`};A(e)?I(n):process.stdout.write(`${n.note}\n`)}})}}),Cs="Run `seo providers bing connect` with a new API key.",ws=y({meta:{name:`bing`,description:`Connect Bing Webmaster Tools for Bing search data`},subCommands:{connect:y({meta:{name:`connect`,description:`Check and save a Bing Webmaster Tools API key`},args:{json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{if(!F({json:A(e)}))throw new g(`AUTH_REQUIRED`,`Run \`seo providers bing connect\` in a terminal. Agents and CI can set ${xe}.`);b(`Connect Bing Webmaster Tools`),x(`In Bing Webmaster Tools, open Settings, then API access, and copy the API key. Bing data is free.`,`API key`);let t=z(await C({message:`Bing Webmaster API key`,validate:e=>e?.trim()?void 0:`API key is required`})),n=await ce(qe({apiKey:t},{signIn:Cs})),r=await re(t);x(`${n.filter(e=>e.verified).length} verified sites.`,`Connection verified`),S(`Saved in the ${ys(r)}.`)}}),status:y({meta:{name:`status`,description:`Show the Bing Webmaster connection`},args:{json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=await Ae(),n=t?await ce(qe({apiKey:t.apiKey},{signIn:Cs})):null,r={connected:!!t,credentialSource:t?.source??null,sites:n?.filter(e=>e.verified).map(e=>e.url)??[]};if(A(e)){I(r);return}process.stdout.write(t?`Connected (${ys(t.source)}). Verified sites: ${r.sites.join(`, `)||`none`}.\n`:"Not connected. Run `seo providers bing connect`.\n")}}),disconnect:y({meta:{name:`disconnect`,description:`Remove the saved Bing Webmaster API key`},args:{json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{await we();let t=!!process.env[xe]?.trim(),n=t?`The saved key is removed. Clear ${xe} to fully disconnect.`:`Bing is disconnected.`;A(e)?I({removed:!0,environment:t}):process.stdout.write(`${n}\n`)}})}}),Ts=y({meta:{name:`dataforseo`,description:`Connect DataForSEO for optional search data`},subCommands:{connect:y({meta:{name:`connect`,description:`Validate and save DataForSEO API credentials`},args:{json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{if(!F({json:A(e)}))throw new g(`AUTH_REQUIRED`,`Run \`seo providers dataforseo connect\` in a terminal. Agents and CI can set ${d} and ${De}.`);b(`Connect DataForSEO`),x(`Use the API login and API password from DataForSEO API Access. The validation call is free; later research reports can make charged requests.`,`API credentials`);let t=z(await cr({message:`DataForSEO API login`,validate:e=>e?.trim()?void 0:`API login is required`})),n=z(await C({message:`DataForSEO API password`,validate:e=>e?void 0:`API password is required`})),r={login:t.trim(),password:n},i=await new zt({credentials:()=>r}).userData(),a=await Nt(r);x(`${i.login} is connected with an account balance of ${bs(i.balanceMicros)}.`,`Connection verified`),S(`Saved in the ${ys(a)}. Run seo providers dataforseo status --check to verify it again.`)}}),status:y({meta:{name:`status`,description:`Show the local DataForSEO connection`},args:{check:{type:`boolean`,default:!1,description:`Verify credentials with the free account endpoint.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=await n(),r=!!e.check,i=r&&t?await new zt().userData():void 0,a={connected:!!t,credentialSource:t?.source,migratedLegacyCredentials:t?.migrated??!1,liveCheck:i?{status:`passed`,account:i.login,timezone:i.timezone,balanceMicros:i.balanceMicros,observedAt:i.observedAt,requestCostMicros:i.requestCostMicros}:{status:r?`unavailable`:`not-requested`}};if(A(e)){I(a);return}L([[`Connected`,a.connected?`yes`:`no`],[`Credential`,ys(a.credentialSource)],[`Live check`,a.liveCheck.status===`passed`?`passed at ${a.liveCheck.observedAt}`:a.liveCheck.status===`unavailable`?`not available without credentials`:`not requested; pass --check to verify`],...i?[[`Account`,i.login],[`Balance`,bs(i.balanceMicros)]]:[]])}}),disconnect:y({meta:{name:`disconnect`,description:`Remove saved DataForSEO credentials`},args:{json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{await It();let t=!!process.env[d]?.trim(),n=!!process.env[De],r={savedCredentialsRemoved:!0,environmentCredentials:t&&n?`active`:t||n?`partial`:`missing`,note:t||n?`Environment variables were not changed. Clear ${d} and ${De} to fully disconnect.`:`DataForSEO is disconnected.`};A(e)?I(r):process.stdout.write(`${r.note}\n`)}})}});function Es(e){if(!e)return{};let t;try{t=JSON.parse(e)}catch{throw new g(`INVALID_INPUT`,`--account must be a JSON object.`)}if(!t||Array.isArray(t)||typeof t!=`object`)throw new g(`INVALID_INPUT`,`--account must be a JSON object.`);let n=Object.entries(t);if(n.length>20||n.some(([e,t])=>!e||e.length>64||typeof t!=`string`||t.length>4096))throw new g(`INVALID_INPUT`,`--account must contain at most 20 bounded string fields.`);return Object.fromEntries(n.map(([e,t])=>[e,t.trim()]))}function Ds(e){if(!e)return{};let t;try{t=JSON.parse(e)}catch{throw new g(`INVALID_INPUT`,`--params must be a JSON object.`)}if(!t||Array.isArray(t)||typeof t!=`object`)throw new g(`INVALID_INPUT`,`--params must be a JSON object.`);let n=Object.entries(t);if(n.length>100||n.some(([e])=>!/^[A-Za-z][A-Za-z0-9-]{0,63}$/u.test(e))||Buffer.byteLength(e)>64*1024)throw new g(`INVALID_INPUT`,`--params must be a JSON object smaller than 64 KiB.`);return Object.fromEntries(n)}async function Os(e){let t=await ke(),n=t.registry.get(e);if(n)return n;let r=t.failures.find(t=>t.id===e);throw r?new g(`INVALID_INPUT`,`Provider ${e} could not load: ${r.message}`):new g(`INVALID_INPUT`,`Provider ${e} is not installed.`)}function ks(e,t){let n=new Set(e.connection.fields.filter(e=>e.kind===`account`).map(e=>e.id)),r=Object.keys(t).find(e=>!n.has(e));if(r)throw new g(`INVALID_INPUT`,`${r} is not an account field for ${e.displayName}.`);let i=e.connection.normalizeAccount?e.connection.normalizeAccount(t):{...t};if(Object.keys(i).find(e=>!n.has(e)))throw new g(`PROVIDER_UNAVAILABLE`,`${e.displayName} returned an unknown account field.`);for(let t of e.connection.fields.filter(e=>e.kind===`account`&&e.required!==!1))if(!i[t.id]?.trim())throw new g(`INVALID_INPUT`,`${t.label} is required.`);return i}async function As(){return Dt()}const js=y({meta:{name:`providers`,description:`Connect optional data providers`},subCommands:{list:y({meta:{name:`list`,description:`List installed provider packages`},args:{json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=await As();if(A(e)){I(t);return}t.installed.length>0?R([`Installed provider`,`Package`,`Version`,`Status`],t.installed.map(e=>[e.id,e.package,e.version,e.loadStatus])):process.stdout.write(`No provider extensions are installed.
78
- `)}}),describe:y({meta:{name:`describe`,description:`Describe one installed provider for an agent`},args:{id:{type:`positional`,required:!0,description:`Installed provider id.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=await Ht(T(e.id)??``);if(A(e)){I(t);return}L([[`Provider`,t.displayName],[`Package`,`${t.package}@${t.version}`],[`Description`,t.description],[`Capabilities`,t.capabilities.map(e=>e.id).join(`, `)||`none`],[`Actions`,t.actions.map(e=>e.id).join(`, `)||`none`]])}}),install:y({meta:{name:`install`,description:`Install one provider package from npm`},args:{package:{type:`positional`,required:!0,description:`npm package name with an optional exact version.`},yes:{type:`boolean`,default:!1,description:`Approve third-party code installation without prompting.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=T(e.package)??``,n=A(e),r=E(e.yes),i=F({json:n});if(!r&&!i)throw new g(`INVALID_INPUT`,`Provider installation runs third-party code. Review the package, then pass --yes to approve it in JSON or CI mode.`);let a;try{a=await gt(t)}catch(e){throw new g(`INVALID_INPUT`,e instanceof Error?e.message:String(e))}if(!r&&(b(`Install provider package`),x([`${a.package}@${a.version}`,`Publisher: ${a.publisher??`not supplied by npm`}`,`Repository: ${a.repository??`not supplied by npm`}`,`Integrity: ${a.integrity}`].join(`
79
- `),`Package`),x(`This package can read files, use the network, and run with the same local permissions as seo. Installing it is not a security review or endorsement.`,`Third-party code`),!z(await rr({message:`Install ${a.package}@${a.version}?`,initialValue:!1})))){S(`Provider was not installed.`);return}let o;try{o=await xt(a)}catch(e){throw new g(`PROVIDER_UNAVAILABLE`,e instanceof Error?e.message:String(e))}let s={installed:o,package:a};if(n){I(s);return}S(`Installed provider ${o.id} from ${o.package}@${o.version}.`)}}),remove:y({meta:{name:`remove`,description:`Remove one installed provider extension`},args:{id:{type:`positional`,required:!0,description:`Installed provider id.`},yes:{type:`boolean`,default:!1,description:`Remove the package without prompting.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=T(e.id)??``,n=A(e),r=E(e.yes),i=F({json:n});if(!r&&!i)throw new g(`INVALID_INPUT`,`Pass --yes to remove a provider extension in JSON or CI mode.`);if(!r&&!z(await rr({message:`Remove provider ${t}?`,initialValue:!1})))return;let a=await Xt(t);if(!a)throw new g(`INVALID_INPUT`,`Provider ${t} is not installed.`);if(n){I({removed:a});return}process.stdout.write(`Removed ${t}.\n`)}}),connect:y({meta:{name:`connect`,description:`Validate and save one installed provider connection`},args:{id:{type:`positional`,required:!0,description:`Installed provider id.`},account:{type:`string`,description:`Account fields as JSON. Secret fields must use their environment variables.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=T(e.id)??``,n=A(e),r=F({json:n}),a=await Os(t),o={...it(t),...Es(T(e.account))},s={};if(r){b(`Connect ${a.displayName}`),a.connection.verificationNotice&&x(a.connection.verificationNotice,`Connection check`);for(let e of a.connection.fields){if(e.kind===`account`){let t=z(await cr({message:e.label,placeholder:e.description,initialValue:o[e.id],validate:t=>e.required!==!1&&!t?.trim()?`${e.label} is required`:void 0}));t?.trim()&&(o[e.id]=t.trim());continue}if(e.envVar&&process.env[e.envVar]?.trim())continue;let t=z(await C({message:e.label,validate:t=>e.required!==!1&&!t?.trim()?`${e.label} is required`:void 0}));t?.trim()&&(s[e.id]=t.trim())}}o=ks(a,o);let c={...s};Object.keys(c).length===0&&(c=await i({providerId:t,account:o,fields:a.connection.fields})),await f({providerId:t,account:o,credentials:c}),Object.keys(s).length>0&&await Ft({providerId:t,account:o,credentials:s}),pn(t,o);let l={provider:t,connected:!0,account:o,capabilities:(a.capabilities??[]).map(e=>e.id),actions:(a.actions??[]).map(e=>e.id)};n?I(l):S(`Connected ${a.displayName}. Run seo providers describe ${t} to see what it can do.`)}}),status:y({meta:{name:`status`,description:`Show one installed provider connection`},args:{id:{type:`positional`,required:!0,description:`Installed provider id.`},check:{type:`boolean`,default:!1,description:`Run the provider connection check.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=T(e.id)??``,n=await Os(t),r=it(t),a;try{a=await i({providerId:t,account:r,fields:n.connection.fields})}catch{a=void 0}let o=n.connection.fields.filter(e=>e.kind===`account`&&e.required!==!1).every(e=>!!r[e.id]?.trim())&&a!==void 0,s=E(e.check);if(s&&!o)throw new g(`AUTH_REQUIRED`,`Provider ${t} is not connected. Run \`seo providers connect ${t}\`.`);s&&a&&await f({providerId:t,account:r,credentials:a});let c={provider:t,installed:!0,connected:o,account:r,capabilities:(n.capabilities??[]).map(e=>e.id),actions:(n.actions??[]).map(e=>e.id),check:s?`passed`:`not-requested`};if(A(e)){I(c);return}R([`Provider`,`Connected`,`Capabilities`,`Check`],[[n.displayName,o?`yes`:`no`,c.capabilities.join(`, `),c.check]])}}),disconnect:y({meta:{name:`disconnect`,description:`Remove one saved provider connection`},args:{id:{type:`positional`,required:!0,description:`Installed provider id.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=T(e.id)??``;await Os(t),await Rt({providerId:t,account:it(t)}),wt(t);let n={provider:t,disconnected:!0,note:`Environment variables were not changed.`};A(e)?I(n):process.stdout.write(`Disconnected ${t}. ${n.note}\n`)}}),run:y({meta:{name:`run`,description:`Run one action supplied by an installed provider`},args:{id:{type:`positional`,required:!0,description:`Installed provider id.`},action:{type:`positional`,required:!0,description:`Provider action id.`},account:{type:`string`,description:`Account fields as JSON. Defaults to the saved connection.`},params:{type:`string`,description:`Action parameters as a JSON object.`},refresh:{type:`boolean`,default:!1,description:`Bypass the local provider cache.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=T(e.id)??``,n=T(e.action)??``;await Os(t),I(await Sn({providerId:t,actionId:n,account:{...it(t),...Es(T(e.account))},params:Ds(T(e.params)),refresh:E(e.refresh)}))}}),doctor:y({meta:{name:`doctor`,description:`Validate installed provider packages and entry points`},args:{json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=await As(),n={ok:t.installed.every(e=>e.loadStatus===`ready`),installed:t.installed};if(A(e)){I(n);return}if(n.installed.length===0){process.stdout.write(`No provider extensions are installed.
80
- `);return}R([`Provider`,`Package`,`Version`,`Status`],n.installed.map(e=>[e.id,e.package,e.version,e.error??e.loadStatus]))}}),ahrefs:Ss,bing:ws,dataforseo:Ts}}),Ms={"ai-search":`AI search`,crawl:`Crawling and technical checks`,diagnosis:`Diagnosis`,monitoring:`Monitoring`,opportunities:`Opportunities`,reporting:`Reporting`,setup:`Setup`,workflows:`Workflows`};function Ns(e){let t=T(e);if(t){if(!sn.includes(t))throw new g(`INVALID_INPUT`,`Unknown report category: ${t}. Choose ${sn.join(`, `)}.`);return t}}async function Ps(e,t){let n=T(e),r=T(t);if(n&&r)throw new g(`INVALID_INPUT`,`Use either --params or --params-file, not both.`);if(!n&&!r)return{};let i=n??await Jn(r??``,`utf8`),a;try{a=JSON.parse(i)}catch{throw new g(`INVALID_INPUT`,`Report parameters must be valid JSON.`)}if(!a||typeof a!=`object`||Array.isArray(a))throw new g(`INVALID_INPUT`,`Report parameters must be an object.`);return a}function Fs(e){if(!e.isError)return;let t=e.structuredContent;throw new g(t?.error?.code??`INTERNAL_ERROR`,t?.error?.message??`The report could not be completed.`)}function Is(e){if(!e||typeof e!=`object`)return[];let t=e,n=new Set(t.required??[]);return Object.entries(t.properties??{}).map(([e,t])=>{let r=Array.isArray(t.type)?t.type.join(` | `):t.type??`any`;return{name:e,type:r===`array`&&t.items?.type?`${t.items.type}[]`:t.format?`${r} (${t.format})`:r,required:n.has(e),description:t.description}})}const Ls=y({meta:{name:`reports`,description:`Discover and run the complete report catalog`},subCommands:{list:y({meta:{name:`list`,description:`List report ids available to the CLI and MCP server`},args:{category:{type:`string`,description:`Filter by category: ${sn.join(`, `)}.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=qt(Ns(e.category));if(A(e)){I({reports:t,categories:sn});return}gi(t,{noun:`report`,categoryLabels:Ms}),process.stdout.write(`
81
- `),fi({title:`Need the detail?`,body:`Describe one report to see when to use it, what it needs, and what the result can prove.`,command:`seo reports describe <id>`})}}),describe:y({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=At(T(e.id)??``);if(A(e)){I({report:t});return}pi(t.name,t.description),process.stdout.write(`
82
- `),L([[`ID`,t.id],[`Category`,t.category],[`Outcome`,t.outcome]]),process.stdout.write(`
83
- `),yi(`Use when`,t.useWhen),process.stdout.write(`
84
- `),yi(`Avoid when`,t.avoidWhen),process.stdout.write(`
85
- `),yi(`Read in order`,t.readOrder),process.stdout.write(`
86
- `),yi(`Do not claim`,t.doNotClaim),process.stdout.write(`
87
- `),vi(`Verify`,t.verify),process.stdout.write(`
88
- `),fi({title:`Implement findings`,body:t.agentWorkflow.completion,command:t.agentWorkflow.actionView.cli}),t.related.length>0&&(process.stdout.write(`
89
- `),yi(`Related reports`,t.related.map(e=>`${e.id}: ${e.reason}`))),process.stdout.write(`
76
+ `),I([`Segment`,`Retained in`,`Clicks`,`Impressions`],c.unmatchedSegments.map(e=>[e.key,e.retainedIn,e.clicks,e.impressions]))),q(`Warnings`,c.warnings)}});function bs(e){return`${B(e)} ${e===1?`row`:`rows`}`}const xs=v({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.`},...J([`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=O(e),n=T(e[`verify-limit`]),r=w(e[`verify-content`])===!0||n!==void 0,i=await mt({site:k(C(e.site)),days:T(e.days),minImpressions:T(e[`min-impressions`]),limit:T(e.limit),includeBrand:w(e[`include-brand`]),verifyContent:r,verifyLimit:n,js:w(e.js)?!0:void 0,rate:jr(e),refresh:w(e.refresh)});if(t){P(i);return}if(W({title:`Striking-distance opportunities`,target:i.site,status:i.source.possiblyTruncated?`unknown`:`info`,summary:i.summary.verdict,metrics:[{label:`Eligible rows`,value:B(i.summary.eligibleRows)},{label:`Returned rows`,value:B(i.summary.returnedRows)},{label:`Template groups`,value:B(i.summary.groups)},{label:`Impressions`,value:B(i.summary.eligibleImpressions)},{label:`Brand queries`,value:i.summary.brandFiltering},{label:`Verification`,value:i.verification.requested?`Requested`:`Off`},{label:`Evidence`,value:i.dataStatus}]}),q(`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.`]),q(`Recommended actions`,i.recommendations),q(`Report caveats`,i.caveats),i.groups.length&&(G([`Group`,`Rows`,`URLs`,`Impr`,`Avg pos`,`Scope`,`Action`],i.groups.map(e=>[U(e.label,36),B(e.rowCount),B(e.uniqueUrls),B(e.totalImpressions),V(e.impressionWeightedPosition),e.actionScope,U(e.recommendation.action,72)])),K(`Top striking-distance group actions`,i.groups.map(e=>({label:e.label,context:`${bs(e.rowCount)}, ${B(e.totalImpressions)} impressions`,action:e.recommendation.action})))),!i.items.length){process.stdout.write(`No striking-distance opportunities matched these filters.
77
+ `);return}G([`Query`,`Template`,`URL`,`Impr`,`CTR`,`Pos`,`Score`,`Fetch`,`Check`,`Action`],i.items.map(e=>[U(e.query,36),U(e.template.label,24),U(e.url,48),B(e.impressions),H(e.ctr),V(e.position),e.priority.score,xa(e.contentVerification?.fetchDiagnostics),Sa(e.contentVerification?.classification),U(e.recommendation.action,72)])),K(`Top striking-distance actions`,i.items.map(e=>({label:e.query,context:`${e.template.label}, pos ${V(e.position)}, ${B(e.impressions)} impressions`,action:e.recommendation.action})))}});function Ss(e){return e===`keychain`?`system keychain`:e===`file`?`private local file`:e??`missing`}function Cs(e){return e===null?`unavailable`:`$${(e/1e6).toFixed(2)}`}function ws(e){return e.limit===null?e.used===null?`not reported`:`${e.used.toLocaleString(`en-US`)} used; no key limit`:`${(e.remaining??0).toLocaleString(`en-US`)} of ${e.limit.toLocaleString(`en-US`)} remaining`}const Ts=v({meta:{name:`ahrefs`,description:`Connect Ahrefs for optional search and link data`},subCommands:{connect:v({meta:{name:`connect`,description:`Validate and save an Ahrefs API v3 key`},args:{json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{if(!N({json:O(e)}))throw new h(`AUTH_REQUIRED`,`Run \`seo providers ahrefs connect\` in a terminal. Agents and CI can set ${de}.`);y(`Connect Ahrefs`),b(`Paste an API v3 key from Ahrefs Account settings. The validation check and Domain Rating are free; other research can consume API units.`,`API key`);let t=L(await lr({message:`Ahrefs API v3 key`,validate:e=>e?.trim()?void 0:`API key is required`})),n=await new fn({apiKey:t}).limitsAndUsage(),r=await Dn(t);b(`${n.subscription} subscription. API key units: ${ws(n.apiKeyUnits)}.`,`Connection verified`),x(`Saved in the ${Ss(r)}. Run seo providers ahrefs status --check to verify it again.`)}}),status:v({meta:{name:`status`,description:`Show the local Ahrefs connection`},args:{check:{type:`boolean`,default:!1,description:`Verify the API key with the free account endpoint.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=await Le(),n=!!e.check,r=n&&t?await new fn().limitsAndUsage():void 0,i={connected:!!t,apiVersion:t?3:null,credentialSource:t?.source,liveCheck:r?{status:`passed`,subscription:r.subscription,apiKeyExpiresAt:r.apiKeyExpiresAt,usageResetsAt:r.usageResetsAt,apiKeyUnits:r.apiKeyUnits,workspaceUnits:r.workspaceUnits,observedAt:r.observedAt,requestCostUnits:r.requestCostUnits}:{status:n?`unavailable`:`not-requested`}};if(O(e)){P(i);return}F([[`Connected`,i.connected?`yes`:`no`],[`API version`,i.apiVersion?`Version 3`:`not connected`],[`Credential`,Ss(i.credentialSource)],[`Live check`,i.liveCheck.status===`passed`?`passed at ${i.liveCheck.observedAt}`:i.liveCheck.status===`unavailable`?`not available without credentials`:`not requested; pass --check to verify`],...r?[[`Subscription`,r.subscription],[`API key units`,ws(r.apiKeyUnits)],[`Workspace units`,ws(r.workspaceUnits)],[`Usage resets`,r.usageResetsAt],[`Key expires`,r.apiKeyExpiresAt]]:[]])}}),disconnect:v({meta:{name:`disconnect`,description:`Remove the saved Ahrefs API v3 key`},args:{json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{await be();let t=!!process.env[de]?.trim(),n={savedCredentialRemoved:!0,environmentCredential:t?`active`:`missing`,note:t?`The environment variable was not changed. Clear ${de} to fully disconnect.`:`Ahrefs is disconnected.`};O(e)?P(n):process.stdout.write(`${n.note}\n`)}})}}),Es="Run `seo providers bing connect` with a new API key.",Ds=v({meta:{name:`bing`,description:`Connect Bing Webmaster Tools for Bing search data`},subCommands:{connect:v({meta:{name:`connect`,description:`Check and save a Bing Webmaster Tools API key`},args:{json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{if(!N({json:O(e)}))throw new h(`AUTH_REQUIRED`,`Run \`seo providers bing connect\` in a terminal. Agents and CI can set ${Ce}.`);y(`Connect Bing Webmaster Tools`),b(`In Bing Webmaster Tools, open Settings, then API access, and copy the API key. Bing data is free.`,`API key`);let t=L(await lr({message:`Bing Webmaster API key`,validate:e=>e?.trim()?void 0:`API key is required`})),n=await qe(le({apiKey:t},{signIn:Es})),r=await ie(t);b(`${n.filter(e=>e.verified).length} verified sites.`,`Connection verified`),x(`Saved in the ${Ss(r)}.`)}}),status:v({meta:{name:`status`,description:`Show the Bing Webmaster connection`},args:{json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=await Me(),n=t?await qe(le({apiKey:t.apiKey},{signIn:Es})):null,r={connected:!!t,credentialSource:t?.source??null,sites:n?.filter(e=>e.verified).map(e=>e.url)??[]};if(O(e)){P(r);return}process.stdout.write(t?`Connected (${Ss(t.source)}). Verified sites: ${r.sites.join(`, `)||`none`}.\n`:"Not connected. Run `seo providers bing connect`.\n")}}),disconnect:v({meta:{name:`disconnect`,description:`Remove the saved Bing Webmaster API key`},args:{json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{await Ee();let t=!!process.env[Ce]?.trim(),n=t?`The saved key is removed. Clear ${Ce} to fully disconnect.`:`Bing is disconnected.`;O(e)?P({removed:!0,environment:t}):process.stdout.write(`${n}\n`)}})}}),Os=v({meta:{name:`dataforseo`,description:`Connect DataForSEO for optional search data`},subCommands:{connect:v({meta:{name:`connect`,description:`Validate and save DataForSEO API credentials`},args:{json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{if(!N({json:O(e)}))throw new h(`AUTH_REQUIRED`,`Run \`seo providers dataforseo connect\` in a terminal. Agents and CI can set ${d} and ${ke}.`);y(`Connect DataForSEO`),b(`Use the API login and API password from DataForSEO API Access. The validation call is free; later research reports can make charged requests.`,`API credentials`);let t=L(await dr({message:`DataForSEO API login`,validate:e=>e?.trim()?void 0:`API login is required`})),n=L(await lr({message:`DataForSEO API password`,validate:e=>e?void 0:`API password is required`})),r={login:t.trim(),password:n},i=await new Vt({credentials:()=>r}).userData(),a=await Ft(r);b(`${i.login} is connected with an account balance of ${Cs(i.balanceMicros)}.`,`Connection verified`),x(`Saved in the ${Ss(a)}. Run seo providers dataforseo status --check to verify it again.`)}}),status:v({meta:{name:`status`,description:`Show the local DataForSEO connection`},args:{check:{type:`boolean`,default:!1,description:`Verify credentials with the free account endpoint.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=await n(),r=!!e.check,i=r&&t?await new Vt().userData():void 0,a={connected:!!t,credentialSource:t?.source,migratedLegacyCredentials:t?.migrated??!1,liveCheck:i?{status:`passed`,account:i.login,timezone:i.timezone,balanceMicros:i.balanceMicros,observedAt:i.observedAt,requestCostMicros:i.requestCostMicros}:{status:r?`unavailable`:`not-requested`}};if(O(e)){P(a);return}F([[`Connected`,a.connected?`yes`:`no`],[`Credential`,Ss(a.credentialSource)],[`Live check`,a.liveCheck.status===`passed`?`passed at ${a.liveCheck.observedAt}`:a.liveCheck.status===`unavailable`?`not available without credentials`:`not requested; pass --check to verify`],...i?[[`Account`,i.login],[`Balance`,Cs(i.balanceMicros)]]:[]])}}),disconnect:v({meta:{name:`disconnect`,description:`Remove saved DataForSEO credentials`},args:{json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{await Rt();let t=!!process.env[d]?.trim(),n=!!process.env[ke],r={savedCredentialsRemoved:!0,environmentCredentials:t&&n?`active`:t||n?`partial`:`missing`,note:t||n?`Environment variables were not changed. Clear ${d} and ${ke} to fully disconnect.`:`DataForSEO is disconnected.`};O(e)?P(r):process.stdout.write(`${r.note}\n`)}})}});function ks(e){if(!e)return{};let t;try{t=JSON.parse(e)}catch{throw new h(`INVALID_INPUT`,`--account must be a JSON object.`)}if(!t||Array.isArray(t)||typeof t!=`object`)throw new h(`INVALID_INPUT`,`--account must be a JSON object.`);let n=Object.entries(t);if(n.length>20||n.some(([e,t])=>!e||e.length>64||typeof t!=`string`||t.length>4096))throw new h(`INVALID_INPUT`,`--account must contain at most 20 bounded string fields.`);return Object.fromEntries(n.map(([e,t])=>[e,t.trim()]))}function As(e){if(!e)return{};let t;try{t=JSON.parse(e)}catch{throw new h(`INVALID_INPUT`,`--params must be a JSON object.`)}if(!t||Array.isArray(t)||typeof t!=`object`)throw new h(`INVALID_INPUT`,`--params must be a JSON object.`);let n=Object.entries(t);if(n.length>100||n.some(([e])=>!/^[A-Za-z][A-Za-z0-9-]{0,63}$/u.test(e))||Buffer.byteLength(e)>64*1024)throw new h(`INVALID_INPUT`,`--params must be a JSON object smaller than 64 KiB.`);return Object.fromEntries(n)}async function js(e){let t=await Bt(),n=t.registry.get(e);if(n)return n;let r=t.failures.find(t=>t.id===e);throw r?new h(`INVALID_INPUT`,`Provider ${e} could not load: ${r.message}`):new h(`INVALID_INPUT`,`Provider ${e} is not installed.`)}function Ms(e,t){let n=new Set(e.connection.fields.filter(e=>e.kind===`account`).map(e=>e.id)),r=Object.keys(t).find(e=>!n.has(e));if(r)throw new h(`INVALID_INPUT`,`${r} is not an account field for ${e.displayName}.`);let i=e.connection.normalizeAccount?e.connection.normalizeAccount(t):{...t};if(Object.keys(i).find(e=>!n.has(e)))throw new h(`PROVIDER_UNAVAILABLE`,`${e.displayName} returned an unknown account field.`);for(let t of e.connection.fields.filter(e=>e.kind===`account`&&e.required!==!1))if(!i[t.id]?.trim())throw new h(`INVALID_INPUT`,`${t.label} is required.`);return i}async function Ns(){return kt()}const Ps=v({meta:{name:`providers`,description:`Connect optional data providers`},subCommands:{list:v({meta:{name:`list`,description:`List installed provider packages`},args:{json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=await Ns();if(O(e)){P(t);return}t.installed.length>0?I([`Installed provider`,`Package`,`Version`,`Status`],t.installed.map(e=>[e.id,e.package,e.version,e.loadStatus])):process.stdout.write(`No provider extensions are installed.
78
+ `)}}),describe:v({meta:{name:`describe`,description:`Describe one installed provider for an agent`},args:{id:{type:`positional`,required:!0,description:`Installed provider id.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=await Wt(C(e.id)??``);if(O(e)){P(t);return}F([[`Provider`,t.displayName],[`Package`,`${t.package}@${t.version}`],[`Description`,t.description],[`Capabilities`,t.capabilities.map(e=>e.id).join(`, `)||`none`],[`Actions`,t.actions.map(e=>e.id).join(`, `)||`none`]])}}),install:v({meta:{name:`install`,description:`Install one provider package from npm`},args:{package:{type:`positional`,required:!0,description:`npm package name with an optional exact version.`},yes:{type:`boolean`,default:!1,description:`Approve third-party code installation without prompting.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=C(e.package)??``,n=O(e),r=w(e.yes),i=N({json:n});if(!r&&!i)throw new h(`INVALID_INPUT`,`Provider installation runs third-party code. Review the package, then pass --yes to approve it in JSON or CI mode.`);let a;try{a=await vt(t)}catch(e){throw new h(`INVALID_INPUT`,e instanceof Error?e.message:String(e))}if(!r&&(y(`Install provider package`),b([`${a.package}@${a.version}`,`Publisher: ${a.publisher??`not supplied by npm`}`,`Repository: ${a.repository??`not supplied by npm`}`,`Integrity: ${a.integrity}`].join(`
79
+ `),`Package`),b(`This package can read files, use the network, and run with the same local permissions as seo. Installing it is not a security review or endorsement.`,`Third-party code`),!L(await ar({message:`Install ${a.package}@${a.version}?`,initialValue:!1})))){x(`Provider was not installed.`);return}let o;try{o=await Ct(a)}catch(e){throw new h(`PROVIDER_UNAVAILABLE`,e instanceof Error?e.message:String(e))}let s={installed:o,package:a};if(n){P(s);return}x(`Installed provider ${o.id} from ${o.package}@${o.version}.`)}}),remove:v({meta:{name:`remove`,description:`Remove one installed provider extension`},args:{id:{type:`positional`,required:!0,description:`Installed provider id.`},yes:{type:`boolean`,default:!1,description:`Remove the package without prompting.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=C(e.id)??``,n=O(e),r=w(e.yes),i=N({json:n});if(!r&&!i)throw new h(`INVALID_INPUT`,`Pass --yes to remove a provider extension in JSON or CI mode.`);if(!r&&!L(await ar({message:`Remove provider ${t}?`,initialValue:!1})))return;let a=await Qt(t);if(!a)throw new h(`INVALID_INPUT`,`Provider ${t} is not installed.`);if(n){P({removed:a});return}process.stdout.write(`Removed ${t}.\n`)}}),connect:v({meta:{name:`connect`,description:`Validate and save one installed provider connection`},args:{id:{type:`positional`,required:!0,description:`Installed provider id.`},account:{type:`string`,description:`Account fields as JSON. Secret fields must use their environment variables.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=C(e.id)??``,n=O(e),r=N({json:n}),i=await js(t),a={...ot(t),...ks(C(e.account))},o={};if(r){y(`Connect ${i.displayName}`),i.connection.verificationNotice&&b(i.connection.verificationNotice,`Connection check`);for(let e of i.connection.fields){if(e.kind===`account`){let t=L(await dr({message:e.label,placeholder:e.description,initialValue:a[e.id],validate:t=>e.required!==!1&&!t?.trim()?`${e.label} is required`:void 0}));t?.trim()&&(a[e.id]=t.trim());continue}if(e.envVar&&process.env[e.envVar]?.trim())continue;let t=L(await lr({message:e.label,validate:t=>e.required!==!1&&!t?.trim()?`${e.label} is required`:void 0}));t?.trim()&&(o[e.id]=t.trim())}}a=Ms(i,a);let s={...o};Object.keys(s).length===0&&(s=await Lt({providerId:t,account:a,fields:i.connection.fields})),await je({providerId:t,account:a,credentials:s}),Object.keys(o).length>0&&await Te({providerId:t,account:a,credentials:o}),hn(t,a);let c={provider:t,connected:!0,account:a,capabilities:(i.capabilities??[]).map(e=>e.id),actions:(i.actions??[]).map(e=>e.id)};n?P(c):x(`Connected ${i.displayName}. Run seo providers describe ${t} to see what it can do.`)}}),status:v({meta:{name:`status`,description:`Show one installed provider connection`},args:{id:{type:`positional`,required:!0,description:`Installed provider id.`},check:{type:`boolean`,default:!1,description:`Run the provider connection check.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=C(e.id)??``,n=await js(t),r=ot(t),i;try{i=await Lt({providerId:t,account:r,fields:n.connection.fields})}catch{i=void 0}let a=n.connection.fields.filter(e=>e.kind===`account`&&e.required!==!1).every(e=>!!r[e.id]?.trim())&&i!==void 0,o=w(e.check);if(o&&!a)throw new h(`AUTH_REQUIRED`,`Provider ${t} is not connected. Run \`seo providers connect ${t}\`.`);o&&i&&await je({providerId:t,account:r,credentials:i});let s={provider:t,installed:!0,connected:a,account:r,capabilities:(n.capabilities??[]).map(e=>e.id),actions:(n.actions??[]).map(e=>e.id),check:o?`passed`:`not-requested`};if(O(e)){P(s);return}I([`Provider`,`Connected`,`Capabilities`,`Check`],[[n.displayName,a?`yes`:`no`,s.capabilities.join(`, `),s.check]])}}),disconnect:v({meta:{name:`disconnect`,description:`Remove one saved provider connection`},args:{id:{type:`positional`,required:!0,description:`Installed provider id.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=C(e.id)??``;await js(t),await i({providerId:t,account:ot(t)}),Et(t);let n={provider:t,disconnected:!0,note:`Environment variables were not changed.`};O(e)?P(n):process.stdout.write(`Disconnected ${t}. ${n.note}\n`)}}),run:v({meta:{name:`run`,description:`Run one action supplied by an installed provider`},args:{id:{type:`positional`,required:!0,description:`Installed provider id.`},action:{type:`positional`,required:!0,description:`Provider action id.`},account:{type:`string`,description:`Account fields as JSON. Defaults to the saved connection.`},params:{type:`string`,description:`Action parameters as a JSON object.`},refresh:{type:`boolean`,default:!1,description:`Bypass the local provider cache.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=C(e.id)??``,n=C(e.action)??``;await js(t),P(await wn({providerId:t,actionId:n,account:{...ot(t),...ks(C(e.account))},params:As(C(e.params)),refresh:w(e.refresh)}))}}),doctor:v({meta:{name:`doctor`,description:`Validate installed provider packages and entry points`},args:{json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=await Ns(),n={ok:t.installed.every(e=>e.loadStatus===`ready`),installed:t.installed};if(O(e)){P(n);return}if(n.installed.length===0){process.stdout.write(`No provider extensions are installed.
80
+ `);return}I([`Provider`,`Package`,`Version`,`Status`],n.installed.map(e=>[e.id,e.package,e.version,e.error??e.loadStatus]))}}),ahrefs:Ts,bing:Ds,dataforseo:Os}}),Fs={"ai-search":`AI search`,crawl:`Crawling and technical checks`,diagnosis:`Diagnosis`,monitoring:`Monitoring`,opportunities:`Opportunities`,reporting:`Reporting`,setup:`Setup`,workflows:`Workflows`};function Is(e){let t=C(e);if(t){if(!ln.includes(t))throw new h(`INVALID_INPUT`,`Unknown report category: ${t}. Choose ${ln.join(`, `)}.`);return t}}async function Ls(e,t){let n=C(e),r=C(t);if(n&&r)throw new h(`INVALID_INPUT`,`Use either --params or --params-file, not both.`);if(!n&&!r)return{};let i=n??await Xn(r??``,`utf8`),a;try{a=JSON.parse(i)}catch{throw new h(`INVALID_INPUT`,`Report parameters must be valid JSON.`)}if(!a||typeof a!=`object`||Array.isArray(a))throw new h(`INVALID_INPUT`,`Report parameters must be an object.`);return a}function Rs(e){if(!e.isError)return;let t=e.structuredContent;throw new h(t?.error?.code??`INTERNAL_ERROR`,t?.error?.message??`The report could not be completed.`)}function zs(e){if(!e||typeof e!=`object`)return[];let t=e,n=new Set(t.required??[]);return Object.entries(t.properties??{}).map(([e,t])=>{let r=Array.isArray(t.type)?t.type.join(` | `):t.type??`any`;return{name:e,type:r===`array`&&t.items?.type?`${t.items.type}[]`:t.format?`${r} (${t.format})`:r,required:n.has(e),description:t.description}})}const Bs=v({meta:{name:`reports`,description:`Discover and run the complete report catalog`},subCommands:{list:v({meta:{name:`list`,description:`List report ids available to the CLI and MCP server`},args:{category:{type:`string`,description:`Filter by category: ${ln.join(`, `)}.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=Yt(Is(e.category));if(O(e)){P({reports:t,categories:ln});return}yi(t,{noun:`report`,categoryLabels:Fs}),process.stdout.write(`
81
+ `),hi({title:`Need the detail?`,body:`Describe one report to see when to use it, what it needs, and what the result can prove.`,command:`seo reports describe <id>`})}}),describe:v({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=Mt(C(e.id)??``);if(O(e)){P({report:t});return}gi(t.name,t.description),process.stdout.write(`
82
+ `),F([[`ID`,t.id],[`Category`,t.category],[`Outcome`,t.outcome]]),process.stdout.write(`
83
+ `),Si(`Use when`,t.useWhen),process.stdout.write(`
84
+ `),Si(`Avoid when`,t.avoidWhen),process.stdout.write(`
85
+ `),Si(`Read in order`,t.readOrder),process.stdout.write(`
86
+ `),Si(`Do not claim`,t.doNotClaim),process.stdout.write(`
87
+ `),xi(`Verify`,t.verify),process.stdout.write(`
88
+ `),hi({title:`Implement findings`,body:t.agentWorkflow.completion,command:t.agentWorkflow.actionView.cli}),t.related.length>0&&(process.stdout.write(`
89
+ `),Si(`Related reports`,t.related.map(e=>`${e.id}: ${e.reason}`))),process.stdout.write(`
90
90
  Parameters
91
- `),bi(Is(t.inputSchema)),process.stdout.write(`
92
- `),fi({title:`Need the exact schema?`,command:`seo reports describe ${t.id} --json`})}}),run:y({meta:{name:`run`,description:`Run one report with JSON parameters`},args:{id:{type:`positional`,required:!0,description:"Report id from `seo reports list`."},params:{type:`string`,description:`Report parameters as a JSON object.`},"params-file":{type:`string`,description:`Read report parameters from a JSON file.`},"actions-only":{type:`boolean`,default:!1,description:`Return findings, verification, and retained inventories without the full report body.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=await st(T(e.id)??``,await Ps(e.params,e[`params-file`]),{view:E(e[`actions-only`])?`actions`:`full`});if(Fs(t),A(e)){I(t.structuredContent??{});return}for(let e of t.content)process.stdout.write(`${e.text}\n`)}})}}),Rs=y({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=en().filter(e=>!t||e.category===t);if(A(e)){I({rules:n});return}gi(n.map(e=>({id:e.id,category:e.category,name:`${e.title} · ${e.defaultSeverity}`})),{noun:`rule`})}}),zs=y({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=ge(t);if(!n)throw Error(`Unknown rule: ${t}`);if(A(e)){I(n);return}L([[`Rule`,n.id],[`Title`,n.title],[`Category`,n.category],[`Severity`,n.defaultSeverity]]),process.stdout.write(`
93
- `),vi(`Why it matters`,n.whyItMatters),process.stdout.write(`
94
- `),vi(`How to fix`,n.howToFix),process.stdout.write(`
95
- `),vi(`Impact if ignored`,n.impactIfIgnored),process.stdout.write(`
96
- `),vi(`How to verify`,n.howToVerify)}});function Bs(){return Hs(`skills`,process.env.SEO_SKILLS_DIR)}function Vs(){return Hs(`evals`,process.env.SEO_EVALS_DIR)}function Hs(e,t){if(t)return Pn(t);let n=Mn(Kn(import.meta.url));for(let t=0;t<6;t+=1){let t=_(n,e);if(v(_(t,`README.md`)))return t;n=Mn(n)}throw new g(`INTERNAL_ERROR`,`Packaged ${e} could not be found. Reinstall \`seo\` and try again.`)}function Us(e){return Bn(_(e,`SKILL.md`),`utf8`).match(/^description:\s*(.+)$/m)?.[1]?.trim()??``}const Ws=`claude -p`;function Gs(){let e=Vs();return Vn(e,{withFileTypes:!0}).filter(e=>e.isFile()&&e.name.endsWith(`.json`)).map(t=>({subject:jn(t.name,`.json`),path:_(e,t.name)})).sort((e,t)=>e.subject<t.subject?-1:+(e.subject>t.subject))}function Ks(e,t){let n;try{n=Bn(t,`utf8`)}catch{throw new g(`INVALID_INPUT`,`Cannot read eval file for ${e}.`)}let r;try{r=JSON.parse(n)}catch{throw new g(`INVALID_INPUT`,`Eval file ${e}.json is not valid JSON.`)}if(!r||typeof r!=`object`||Array.isArray(r))throw new g(`INVALID_INPUT`,`Eval file ${e}.json must be an object.`);let i=r;if(i.subject!==e)throw new g(`INVALID_INPUT`,`Eval file ${e}.json must set subject to "${e}".`);if(!Array.isArray(i.evals)||i.evals.length===0)throw new g(`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 g(`INVALID_INPUT`,`${i}: id must be an integer.`);let o=r.id;if(a.has(o))throw new g(`INVALID_INPUT`,`${i}: duplicate id ${o}.`);if(a.add(o),typeof r.prompt!=`string`||r.prompt.trim().length===0)throw new g(`INVALID_INPUT`,`${i}: prompt must be a non-empty string.`);if(typeof r.expected_output!=`string`||r.expected_output.trim().length===0)throw new g(`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 g(`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 g(`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 qs(){let e=_(Bs(),`seo`,`SKILL.md`);if(!v(e))throw new g(`INTERNAL_ERROR`,"The seo router skill could not be found. Reinstall `seo` or use --no-skill.");return Bn(e,`utf8`)}function Js(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===`
97
- `||s===`\r`){r&&=(t.push(n),n=``,!1);continue}n+=s,r=!0}if(i||a)throw new g(`INVALID_INPUT`,`Unbalanced quote in agent command.`);return r&&t.push(n),t}function Ys(e,t){let n=Js(e);if(n.length===0)throw new g(`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 Xs(e,t){return e?`${e.trim()}\n\n---\n\nUser request:\n${t}`:t}function Zs(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(`
91
+ `),Ci(zs(t.inputSchema)),process.stdout.write(`
92
+ `),hi({title:`Need the exact schema?`,command:`seo reports describe ${t.id} --json`})}}),run:v({meta:{name:`run`,description:`Run one report with JSON parameters`},args:{id:{type:`positional`,required:!0,description:"Report id from `seo reports list`."},params:{type:`string`,description:`Report parameters as a JSON object.`},"params-file":{type:`string`,description:`Read report parameters from a JSON file.`},"actions-only":{type:`boolean`,default:!1,description:`Return findings, verification, and retained inventories without the full report body.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=await lt(C(e.id)??``,await Ls(e.params,e[`params-file`]),{view:w(e[`actions-only`])?`actions`:`full`});if(Rs(t),O(e)){P(t.structuredContent??{});return}for(let e of t.content)process.stdout.write(`${e.text}\n`)}})}}),Vs=v({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=C(e.category),n=he().filter(e=>!t||e.category===t);if(O(e)){P({rules:n});return}yi(n.map(e=>({id:e.id,category:e.category,name:`${e.title} · ${e.defaultSeverity}`})),{noun:`rule`})}}),Hs=v({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=C(e.rule)??``,n=nn(t);if(!n)throw Error(`Unknown rule: ${t}`);if(O(e)){P(n);return}F([[`Rule`,n.id],[`Title`,n.title],[`Category`,n.category],[`Severity`,n.defaultSeverity]]),process.stdout.write(`
93
+ `),xi(`Why it matters`,n.whyItMatters),process.stdout.write(`
94
+ `),xi(`How to fix`,n.howToFix),process.stdout.write(`
95
+ `),xi(`Impact if ignored`,n.impactIfIgnored),process.stdout.write(`
96
+ `),xi(`How to verify`,n.howToVerify)}});function Us(){return Gs(`skills`,process.env.SEO_SKILLS_DIR)}function Ws(){return Gs(`evals`,process.env.SEO_EVALS_DIR)}function Gs(e,t){if(t)return In(t);let n=Pn(Jn(import.meta.url));for(let t=0;t<6;t+=1){let t=g(n,e);if(_(g(t,`README.md`)))return t;n=Pn(n)}throw new h(`INTERNAL_ERROR`,`Packaged ${e} could not be found. Reinstall \`seo\` and try again.`)}function Ks(e){return Hn(g(e,`SKILL.md`),`utf8`).match(/^description:\s*(.+)$/m)?.[1]?.trim()??``}const qs=`claude -p`;function Js(){let e=Ws();return Un(e,{withFileTypes:!0}).filter(e=>e.isFile()&&e.name.endsWith(`.json`)).map(t=>({subject:Nn(t.name,`.json`),path:g(e,t.name)})).sort((e,t)=>e.subject<t.subject?-1:+(e.subject>t.subject))}function Ys(e,t){let n;try{n=Hn(t,`utf8`)}catch{throw new h(`INVALID_INPUT`,`Cannot read eval file for ${e}.`)}let r;try{r=JSON.parse(n)}catch{throw new h(`INVALID_INPUT`,`Eval file ${e}.json is not valid JSON.`)}if(!r||typeof r!=`object`||Array.isArray(r))throw new h(`INVALID_INPUT`,`Eval file ${e}.json must be an object.`);let i=r;if(i.subject!==e)throw new h(`INVALID_INPUT`,`Eval file ${e}.json must set subject to "${e}".`);if(!Array.isArray(i.evals)||i.evals.length===0)throw new h(`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 h(`INVALID_INPUT`,`${i}: id must be an integer.`);let o=r.id;if(a.has(o))throw new h(`INVALID_INPUT`,`${i}: duplicate id ${o}.`);if(a.add(o),typeof r.prompt!=`string`||r.prompt.trim().length===0)throw new h(`INVALID_INPUT`,`${i}: prompt must be a non-empty string.`);if(typeof r.expected_output!=`string`||r.expected_output.trim().length===0)throw new h(`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 h(`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 h(`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 Xs(){let e=g(Us(),`seo`,`SKILL.md`);if(!_(e))throw new h(`INTERNAL_ERROR`,"The seo router skill could not be found. Reinstall `seo` or use --no-skill.");return Hn(e,`utf8`)}function Zs(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===`
97
+ `||s===`\r`){r&&=(t.push(n),n=``,!1);continue}n+=s,r=!0}if(i||a)throw new h(`INVALID_INPUT`,`Unbalanced quote in agent command.`);return r&&t.push(n),t}function Qs(e,t){let n=Zs(e);if(n.length===0)throw new h(`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 $s(e,t){return e?`${e.trim()}\n\n---\n\nUser request:\n${t}`:t}function ec(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(`
98
98
  `),`</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(`
99
- `);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 Qs(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 $s(e,t){let n=Qs(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 ec(e,t){return new Promise(n=>{let r=Date.now(),[i,...a]=e,o;try{o=Qn(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,ee=setTimeout(()=>{u=!0,o.kill(`SIGTERM`),d=setTimeout(()=>{try{o.kill(`SIGKILL`)}catch{}},2e3),d.unref()},t.timeoutMs),f=e=>{l||(l=!0,clearTimeout(ee),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=>{f({stdout:s,stderr:c,code:null,timedOut:u,spawnError:e.message})}),o.on(`close`,e=>{f({stdout:s,stderr:c,code:e,timedOut:u})}),o.stdin&&(t.input===void 0?o.stdin.end():o.stdin.end(t.input))})}async function tc(e,t,n){let{argv:r,usesPlaceholder:i}=Ys(e,t);return ec(r,{input:i?void 0:t,timeoutMs:n})}async function nc(e,t){let n=Date.now(),r=Xs(t.skillBody,e.prompt),i=await tc(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:rc(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:rc(e.assertions)};if(a.length===0)return{...o,status:`errored`,error:`Agent returned no output on stdout.`,durationMs:Date.now()-n,assertions:rc(e.assertions)};if(!t.judge)return{...o,status:`unjudged`,durationMs:Date.now()-n,assertions:rc(e.assertions)};let s=0,c=``;for(let r=0;r<2;r+=1){let i=Zs(e.prompt,a,e.assertions,r>0),l=await tc(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:rc(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:rc(e.assertions)};let u=$s(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:rc(e.assertions)}}function rc(e){return e.map(e=>({assertion:e,verdict:`unjudged`}))}async function ic(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 ac(e){let t={passed:0,failed:0,errored:0,unjudged:0};for(let n of e)t[n.status]+=1;return t}function oc(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 sc(e){let t=Gs();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 g(`INVALID_INPUT`,`Unknown eval subject: ${e}.${n?` Known subjects: ${n}.`:``}`)}return r})}function cc(e){let t=Gs().map(e=>{let t=Ks(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.
99
+ `);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 tc(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 nc(e,t){let n=tc(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 rc(e,t){return new Promise(n=>{let r=Date.now(),[i,...a]=e,o;try{o=er(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,ee=setTimeout(()=>{u=!0,o.kill(`SIGTERM`),d=setTimeout(()=>{try{o.kill(`SIGKILL`)}catch{}},2e3),d.unref()},t.timeoutMs),te=e=>{l||(l=!0,clearTimeout(ee),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=>{te({stdout:s,stderr:c,code:null,timedOut:u,spawnError:e.message})}),o.on(`close`,e=>{te({stdout:s,stderr:c,code:e,timedOut:u})}),o.stdin&&(t.input===void 0?o.stdin.end():o.stdin.end(t.input))})}async function ic(e,t,n){let{argv:r,usesPlaceholder:i}=Qs(e,t);return rc(r,{input:i?void 0:t,timeoutMs:n})}async function ac(e,t){let n=Date.now(),r=$s(t.skillBody,e.prompt),i=await ic(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:Y(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:Y(e.assertions)};if(a.length===0)return{...o,status:`errored`,error:`Agent returned no output on stdout.`,durationMs:Date.now()-n,assertions:Y(e.assertions)};if(!t.judge)return{...o,status:`unjudged`,durationMs:Date.now()-n,assertions:Y(e.assertions)};let s=0,c=``;for(let r=0;r<2;r+=1){let i=ec(e.prompt,a,e.assertions,r>0),l=await ic(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:Y(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:Y(e.assertions)};let u=nc(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:Y(e.assertions)}}function Y(e){return e.map(e=>({assertion:e,verdict:`unjudged`}))}async function oc(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 sc(e){let t={passed:0,failed:0,errored:0,unjudged:0};for(let n of e)t[n.status]+=1;return t}function cc(e){let t=[],n=C(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 lc(e){let t=Js();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 h(`INVALID_INPUT`,`Unknown eval subject: ${e}.${n?` Known subjects: ${n}.`:``}`)}return r})}function uc(e){let t=Js().map(e=>{let t=Ys(e.subject,e.path);return{subject:e.subject,evals:t.evals.length}});if(e){P({schemaVersion:1,subjects:t});return}if(t.length===0){process.stdout.write(`No eval subjects found.
100
100
  `);return}let n=Math.max(...t.map(e=>e.subject.length));process.stdout.write(`Eval subjects
101
- `);for(let e of t){let t=e.evals===1?`eval`:`evals`;process.stdout.write(` ${e.subject.padEnd(n)} ${e.evals} ${t}\n`)}}function lc(e){return e===`passed`?`PASS`:e===`failed`?`FAIL`:e===`errored`?`ERROR`:`UNJUDGED`}function uc(e,t){t.judged?process.stdout.write(`Assertion verdicts below are judge verdicts, model judgments from the judge agent, not ground truth.
101
+ `);for(let e of t){let t=e.evals===1?`eval`:`evals`;process.stdout.write(` ${e.subject.padEnd(n)} ${e.evals} ${t}\n`)}}function dc(e){return e===`passed`?`PASS`:e===`failed`?`FAIL`:e===`errored`?`ERROR`:`UNJUDGED`}function fc(e,t){t.judged?process.stdout.write(`Assertion verdicts below are judge verdicts, model judgments from the judge agent, not ground truth.
102
102
 
103
103
  `):process.stdout.write(`Judging was skipped. Review each agent reply against the assertion checklist by hand.
104
104
 
105
- `);for(let n of e){process.stdout.write(`# ${n.subject}\n`);for(let e of n.evals){if(process.stdout.write(`\n[${lc(e.status)}] eval ${e.id} (${e.durationMs}ms)\n`),process.stdout.write(` prompt: ${fc(e.prompt,100)}\n`),e.error&&process.stdout.write(` error: ${e.error}\n`),!t.judged){process.stdout.write(` reply: ${fc(e.reply,200)}\n`),process.stdout.write(` assertions to review:
106
- `);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=ac(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=dc(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 dc(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 fc(e,t){let n=e.replace(/\s+/g,` `).trim();return n.length>t?`${n.slice(0,t-1)}…`:n}const pc=y({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:Ws,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:Sr(`Judge assertions with an agent.`,`Skip judging and print replies with an assertion checklist.`),skill:Sr(`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=A(e);if(e.list===!0){cc(t);return}let n=sc(oc(e)),r=D(e.id);if(r!==void 0&&n.length!==1)throw new g(`INVALID_INPUT`,`--id needs exactly one subject. Name the subject to filter.`);let i=D(e.timeout)??300;if(!(i>0))throw new g(`INVALID_INPUT`,`--timeout must be a positive number of seconds.`);let a=Math.max(1,Math.min(4,D(e.concurrency)??1)),o=T(e.agent)??Ws,s=e.judge===!1?null:T(e[`judge-agent`])??o,c=e.skill===!1?null:qs(),l={agent:o,judge:s,skillBody:c,timeoutMs:i*1e3},u=[];for(let e of n){let n=Ks(e.subject,e.path).evals;if(r!==void 0&&(n=n.filter(e=>e.id===r),n.length===0))throw new g(`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 ic(n,a,e=>nc(e,l)),o=ac(i);u.push({subject:e.subject,...o,evals:i})}let d=dc(u),ee=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}):uc(u,{judged:s!==null,agent:o,judge:s}),ee&&(process.exitCode=1)}});function mc(){let e=Bs();return Vn(e,{withFileTypes:!0}).filter(t=>t.isDirectory()&&v(_(e,t.name,`SKILL.md`))).map(t=>({name:t.name,description:Us(_(e,t.name)),path:_(e,t.name)})).sort((e,t)=>e.name<t.name?-1:+(e.name>t.name))}function hc(e){let t=mc();if(!e)return t;let n=t.find(t=>t.name===e);if(!n)throw new g(`INVALID_INPUT`,`Unknown skill: ${e}.`);return[n]}const gc=y({meta:{name:`skill`,description:`Inspect the packaged SEO skill and run its evals`},subCommands:{list:y({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=mc();if(A(e)){I({skills:t});return}pi(`SEO skill`,`Packaged with this CLI.`),process.stdout.write(`
107
- `);for(let e of t)L([[`Name`,e.name],[`Purpose`,`Helps agents choose and run the right SEO audit or report.`],[`Path`,e.path]]);process.stdout.write(`
108
- `),fi({title:`Add it to an agent`,body:`Run setup to install the skill and connect your first site.`,command:`seo start`})}}),path:y({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?hc(t)[0]?.path:Bs();A(e)?I({name:t,path:n}):process.stdout.write(`${n}\n`)}}),install:y({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=A(e),n=ko({quiet:t});if(t){I(n),n.status===`failed`&&(process.exitCode=1);return}if(n.status===`failed`)throw new g(`INTERNAL_ERROR`,`Skill install failed: ${n.error}`);process.stdout.write(`SEO skill installed. Agents that support skills can discover it now.
109
- `)}}),eval:pc}}),_c=y({meta:{name:`privacy`,description:`Show local config, token, cache, and log storage paths`},run:async()=>{let e=Bt(),t=nn();L(e.map(e=>[e.label,`${e.path} · ${xi(e.sizeBytes)} · ${e.mode}`])),process.stdout.write(`
110
- `),L(Object.entries(t.counts).map(([e,t])=>[e,String(t)]))}}),vc=y({meta:{name:`reset`,description:`Delete saved credentials and all local data`},args:{yes:{type:`boolean`,default:!1,description:`Confirm deletion without prompting.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=A(e);if(!e.yes&&!F({json:t}))throw new g(`INVALID_INPUT`,`Cannot prompt here. Pass --yes to confirm reset.`);if(!(e.yes||z(await rr({message:`Delete saved credentials and all local data?`,initialValue:!1})))){nr(`Reset aborted.`);return}await pt(),t?I({reset:!0}):process.stdout.write(`Reset complete.
111
- `)}}),yc=y({meta:{name:`sites`,description:`List Search Console properties available to this Google login`},args:{json:{type:`boolean`,default:!1},refresh:{type:`boolean`,default:!1},account:{type:`string`,description:`Saved Google account email.`}},run:async({args:e})=>{let t=await $e(E(e.refresh),T(e.account));if(A(e)){I({sites:t});return}R([`Property`,`Permission`],t.map(e=>[e.siteUrl,e.permissionLevel??`unknown`]))}}),bc=y({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 hn({product:T(e.product),limit:D(e.limit)});if(A(e)){I({updates:t});return}_i(t.map(e=>({title:e.name,meta:[e.type,e.status,`${e.start.slice(0,10)} to ${e.end?.slice(0,10)??`present`}`]})),{empty:`No matching Google Search updates.`})}}),xc={ci:`disabled automatically in CI or tests`,do_not_track:`disabled by DO_NOT_TRACK`,environment:`disabled by a telemetry environment variable`,local_setting:`disabled in local settings`,invalid_state:`disabled because the local state file is invalid`};function Sc(e){return{enabled:e.enabled,...e.reason?{reason:e.reason}:{},stateFile:e.stateFile,...e.state?{firstRunAt:e.state.firstRunAt,cohort:e.state.cohort,sentMilestones:e.state.sentMilestones}:{}}}function Cc(e,t){let n=Sc(e);if(t){I(n);return}L([[`Anonymous telemetry`,e.enabled?`enabled`:`disabled`],[`Reason`,e.reason?xc[e.reason]:`local setting`],[`State file`,e.stateFile],[`Install cohort`,e.state?.cohort??`not created`]]),process.stdout.write(`
105
+ `);for(let n of e){process.stdout.write(`# ${n.subject}\n`);for(let e of n.evals){if(process.stdout.write(`\n[${dc(e.status)}] eval ${e.id} (${e.durationMs}ms)\n`),process.stdout.write(` prompt: ${mc(e.prompt,100)}\n`),e.error&&process.stdout.write(` error: ${e.error}\n`),!t.judged){process.stdout.write(` reply: ${mc(e.reply,200)}\n`),process.stdout.write(` assertions to review:
106
+ `);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=sc(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=pc(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 pc(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 mc(e,t){let n=e.replace(/\s+/g,` `).trim();return n.length>t?`${n.slice(0,t-1)}…`:n}const hc=v({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:qs,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:Tr(`Judge assertions with an agent.`,`Skip judging and print replies with an assertion checklist.`),skill:Tr(`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=O(e);if(e.list===!0){uc(t);return}let n=lc(cc(e)),r=T(e.id);if(r!==void 0&&n.length!==1)throw new h(`INVALID_INPUT`,`--id needs exactly one subject. Name the subject to filter.`);let i=T(e.timeout)??300;if(!(i>0))throw new h(`INVALID_INPUT`,`--timeout must be a positive number of seconds.`);let a=Math.max(1,Math.min(4,T(e.concurrency)??1)),o=C(e.agent)??qs,s=e.judge===!1?null:C(e[`judge-agent`])??o,c=e.skill===!1?null:Xs(),l={agent:o,judge:s,skillBody:c,timeoutMs:i*1e3},u=[];for(let e of n){let n=Ys(e.subject,e.path).evals;if(r!==void 0&&(n=n.filter(e=>e.id===r),n.length===0))throw new h(`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 oc(n,a,e=>ac(e,l)),o=sc(i);u.push({subject:e.subject,...o,evals:i})}let d=pc(u),ee=d.failed>0||d.errored>0;t?P({schemaVersion:1,agent:o,judge:s,judged:s!==null,skillContext:c!==null,timeoutSeconds:i,concurrency:a,subjects:u,totals:d}):fc(u,{judged:s!==null,agent:o,judge:s}),ee&&(process.exitCode=1)}});function gc(){let e=Us();return Un(e,{withFileTypes:!0}).filter(t=>t.isDirectory()&&_(g(e,t.name,`SKILL.md`))).map(t=>({name:t.name,description:Ks(g(e,t.name)),path:g(e,t.name)})).sort((e,t)=>e.name<t.name?-1:+(e.name>t.name))}function _c(e){let t=gc();if(!e)return t;let n=t.find(t=>t.name===e);if(!n)throw new h(`INVALID_INPUT`,`Unknown skill: ${e}.`);return[n]}const vc=v({meta:{name:`skill`,description:`Inspect the packaged SEO skill and run its evals`},subCommands:{list:v({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=gc();if(O(e)){P({skills:t});return}gi(`SEO skill`,`Packaged with this CLI.`),process.stdout.write(`
107
+ `);for(let e of t)F([[`Name`,e.name],[`Purpose`,`Helps agents choose and run the right SEO audit or report.`],[`Path`,e.path]]);process.stdout.write(`
108
+ `),hi({title:`Add it to an agent`,body:`Run setup to install the skill and connect your first site.`,command:`seo start`})}}),path:v({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=C(e.name),n=t?_c(t)[0]?.path:Us();O(e)?P({name:t,path:n}):process.stdout.write(`${n}\n`)}}),install:v({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=O(e),n=Mo({quiet:t});if(t){P(n),n.status===`failed`&&(process.exitCode=1);return}if(n.status===`failed`)throw new h(`INTERNAL_ERROR`,`Skill install failed: ${n.error}`);process.stdout.write(`SEO skill installed. Agents that support skills can discover it now.
109
+ `)}}),eval:hc}}),yc=v({meta:{name:`privacy`,description:`Show local config, token, cache, and log storage paths`},run:async()=>{let e=pn(),t=Nt();F(e.map(e=>[e.label,`${e.path} · ${wi(e.sizeBytes)} · ${e.mode}`])),process.stdout.write(`
110
+ `),F(Object.entries(t.counts).map(([e,t])=>[e,String(t)]))}}),bc=v({meta:{name:`reset`,description:`Delete saved credentials and all local data`},args:{yes:{type:`boolean`,default:!1,description:`Confirm deletion without prompting.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=O(e);if(!e.yes&&!N({json:t}))throw new h(`INVALID_INPUT`,`Cannot prompt here. Pass --yes to confirm reset.`);if(!(e.yes||L(await ar({message:`Delete saved credentials and all local data?`,initialValue:!1})))){ir(`Reset aborted.`);return}await ht(),t?P({reset:!0}):process.stdout.write(`Reset complete.
111
+ `)}}),xc=v({meta:{name:`sites`,description:`List Search Console properties available to this Google login`},args:{json:{type:`boolean`,default:!1},refresh:{type:`boolean`,default:!1},account:{type:`string`,description:`Saved Google account email.`}},run:async({args:e})=>{let t=await at(w(e.refresh),C(e.account));if(O(e)){P({sites:t});return}I([`Property`,`Permission`],t.map(e=>[e.siteUrl,e.permissionLevel??`unknown`]))}}),Sc=v({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 En({product:C(e.product),limit:T(e.limit)});if(O(e)){P({updates:t});return}bi(t.map(e=>({title:e.name,meta:[e.type,e.status,`${e.start.slice(0,10)} to ${e.end?.slice(0,10)??`present`}`]})),{empty:`No matching Google Search updates.`})}}),Cc={ci:`disabled automatically in CI or tests`,do_not_track:`disabled by DO_NOT_TRACK`,environment:`disabled by a telemetry environment variable`,local_setting:`disabled in local settings`,invalid_state:`disabled because the local state file is invalid`};function wc(e){return{enabled:e.enabled,...e.reason?{reason:e.reason}:{},stateFile:e.stateFile,...e.state?{firstRunAt:e.state.firstRunAt,cohort:e.state.cohort,sentMilestones:e.state.sentMilestones}:{}}}function Tc(e,t){let n=wc(e);if(t){P(n);return}F([[`Anonymous telemetry`,e.enabled?`enabled`:`disabled`],[`Reason`,e.reason?Cc[e.reason]:`local setting`],[`State file`,e.stateFile],[`Install cohort`,e.state?.cohort??`not created`]]),process.stdout.write(`
112
112
  Only tool usage is sent. URLs, report data, Google data, and identifiers are never sent.
113
113
  `),process.stdout.write(`Details: https://seoskill.dev/telemetry
114
- `)}const wc={type:`boolean`,default:!1,description:`Print machine-readable JSON.`},Tc=y({meta:{name:`telemetry`,description:`Check or change anonymous usage telemetry`},subCommands:{status:y({meta:{name:`status`,description:`Show whether anonymous usage telemetry is enabled`},args:{json:wc},run:async({args:e})=>Cc(He(),A(e))}),enable:y({meta:{name:`enable`,description:`Enable anonymous usage telemetry in local settings`},args:{json:wc},run:async({args:e})=>{Cc(Oe(!0),A(e))}}),disable:y({meta:{name:`disable`,description:`Disable anonymous usage telemetry in local settings`},args:{json:wc},run:async({args:e})=>{Cc(Oe(!1),A(e))}})}});function Ec(e){let t=e.toLowerCase();return[`pass`,`passed`,`complete`,`completed`,`joined`].includes(t)?`pass`:[`warn`,`warning`,`partial`,`review`].includes(t)?`warning`:[`fail`,`failed`,`error`].includes(t)?`fail`:[`unknown`,`unavailable`].includes(t)?`unknown`:`info`}function Dc(e){let t=e.steps.map(e=>Ec(e.status)),n=t.includes(`fail`)?`fail`:t.includes(`warning`)?`warning`:t.includes(`unknown`)?`unknown`:`pass`;hi({title:e.workflow,target:e.site,status:n,summary:e.summary,metrics:[{label:`Completed`,value:t.filter(e=>e===`pass`).length,status:`pass`},{label:`Review`,value:t.filter(e=>e===`warning`).length,status:`warning`},{label:`Unknown`,value:t.filter(e=>e===`unknown`).length,status:`unknown`}],sections:[{title:`Evidence collection`,diagnostics:e.steps.map(e=>({status:Ec(e.status),title:e.tool,explanation:e.summary,evidence:[`Status: ${e.status}`]}))},{title:`Priority actions`,diagnostics:e.actions.map(e=>({status:`warning`,title:e.title,explanation:`Confidence: ${e.confidence}.`,fix:e.action}))}]})}const Oc=y({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. Defaults to the --site home page.`},site:{type:`string`,description:`GSC property URL for joining page metrics.`},...ha,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=A(e),n=await jc(e),r=Ac(e),i=T(e.site),a=r??(i?_a(i):void 0)??n[0];if(!a)throw new g(`INVALID_INPUT`,"No crawl URL given. Pass a URL, --url or --site. Run `seo sites` to list your Search Console properties.");let o=await ht({url:a,site:i,mode:kc(e.mode)??(n.length?`list`:void 0),urls:n,maxPages:D(e[`max-pages`]),maxDepth:D(e[`max-depth`]),concurrency:D(e.concurrency),refresh:E(e.refresh),fetchRate:Or({...e,"fetch-concurrency":e.concurrency}),include:k(e.include),exclude:k(e.exclude),googleAnalyticsPropertyId:T(e[`google-analytics-property`]),analyticsConnection:ga(e),useSitemap:!wr(e,`sitemap`),respectRobots:!wr(e,`robots`),checkExternal:!wr(e,`external`),js:!!E(e.js),limit:D(e.limit)});if(t){I(o);return}Dc(o),R([`Rank`,`Score`,`Rule`,`Severity`,`Affected`,`Target`,`Action`],o.output.queue.map((e,t)=>[t+1,e.score,e.ruleId,e.severity,e.affectedUrls,e.target,e.action])),J(`Implementation queue details`,o.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 kc(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 Ac(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 jc(e){let t=k(e.urls)??[],n=T(e[`urls-file`]);return n&&t.push(...Mc(await Jn(n,`utf8`))),[...new Set(t)]}function Mc(e){return e.split(/[\n,]/).map(e=>e.trim()).filter(e=>e&&!e.startsWith(`#`))}const Nc=y({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.`},...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.`}}),...ha,"verify-content":Sr(`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=A(e),n=await on({site:j(T(e.site)),days:D(e.days),recentDays:D(e.recent),limit:D(e.limit),includeBrand:E(e[`include-brand`]),googleAnalyticsPropertyId:T(e[`google-analytics-property`]),analyticsConnection:ga(e),verifyContent:E(e[`verify-content`]),verifyLimit:D(e[`verify-limit`]),refresh:E(e.refresh)});if(t){I(n);return}Dc(n),R([`Source`,`Category`,`Score`,`Findings`,`Template`,`Analytics`,`Target`,`Action`],n.output.queue.map(e=>[e.source,e.category,e.score,e.grouped?.count??1,e.template?.label??`-`,e.analytics?.sessions??`-`,e.target,e.action])),J(`Queue action details`,n.output.queue.map(e=>({label:e.target,context:`${e.category}, score ${e.score}`,action:e.action})))}});function Pc(e){return e===null?`-`:e.toLocaleString(`en-GB`,{maximumFractionDigits:e>=100?0:1})}function Fc(e){return[...e.winners.map(e=>({direction:`winner`,item:e})),...e.losers.map(e=>({direction:`loser`,item:e}))]}function Ic(e,t){let n=Fc(t);n.length&&(process.stdout.write(`\n${e}\n`),R([`Direction`,`Key`,`Clicks`,`Impr.`,`Position`],n.map(({direction:e,item:t})=>[e,t.key,Pc(t.clickDelta),Pc(t.impressionDelta),Pc(t.positionDelta)])))}function Lc(e){process.stdout.write(`
114
+ `)}const Ec={type:`boolean`,default:!1,description:`Print machine-readable JSON.`},Dc=v({meta:{name:`telemetry`,description:`Check or change anonymous usage telemetry`},subCommands:{status:v({meta:{name:`status`,description:`Show whether anonymous usage telemetry is enabled`},args:{json:Ec},run:async({args:e})=>Tc(re(),O(e))}),enable:v({meta:{name:`enable`,description:`Enable anonymous usage telemetry in local settings`},args:{json:Ec},run:async({args:e})=>{Tc(zt(!0),O(e))}}),disable:v({meta:{name:`disable`,description:`Disable anonymous usage telemetry in local settings`},args:{json:Ec},run:async({args:e})=>{Tc(zt(!1),O(e))}})}});function Oc(e){let t=e.toLowerCase();return[`pass`,`passed`,`complete`,`completed`,`joined`].includes(t)?`pass`:[`warn`,`warning`,`partial`,`review`].includes(t)?`warning`:[`fail`,`failed`,`error`].includes(t)?`fail`:[`unknown`,`unavailable`].includes(t)?`unknown`:`info`}function kc(e){let t=e.steps.map(e=>Oc(e.status)),n=t.includes(`fail`)?`fail`:t.includes(`warning`)?`warning`:t.includes(`unknown`)?`unknown`:`pass`;vi({title:e.workflow,target:e.site,status:n,summary:e.summary,metrics:[{label:`Completed`,value:t.filter(e=>e===`pass`).length,status:`pass`},{label:`Review`,value:t.filter(e=>e===`warning`).length,status:`warning`},{label:`Unknown`,value:t.filter(e=>e===`unknown`).length,status:`unknown`}],sections:[{title:`Evidence collection`,diagnostics:e.steps.map(e=>({status:Oc(e.status),title:e.tool,explanation:e.summary,evidence:[`Status: ${e.status}`]}))},{title:`Priority actions`,diagnostics:e.actions.map(e=>({status:`warning`,title:e.title,explanation:`Confidence: ${e.confidence}.`,fix:e.action}))}]})}const Ac=v({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. Defaults to the --site home page.`},site:{type:`string`,description:`GSC property URL for joining page metrics.`},...va,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=O(e),n=await Nc(e),r=Mc(e),i=C(e.site),a=r??(i?ba(i):void 0)??n[0];if(!a)throw new h(`INVALID_INPUT`,"No crawl URL given. Pass a URL, --url or --site. Run `seo sites` to list your Search Console properties.");let o=await Ut({url:a,site:i,mode:jc(e.mode)??(n.length?`list`:void 0),urls:n,maxPages:T(e[`max-pages`]),maxDepth:T(e[`max-depth`]),concurrency:T(e.concurrency),refresh:w(e.refresh),fetchRate:jr({...e,"fetch-concurrency":e.concurrency}),include:D(e.include),exclude:D(e.exclude),googleAnalyticsPropertyId:C(e[`google-analytics-property`]),analyticsConnection:ya(e),useSitemap:!Dr(e,`sitemap`),respectRobots:!Dr(e,`robots`),checkExternal:!Dr(e,`external`),js:!!w(e.js),limit:T(e.limit)});if(t){P(o);return}kc(o),I([`Rank`,`Score`,`Rule`,`Severity`,`Affected`,`Target`,`Action`],o.output.queue.map((e,t)=>[t+1,e.score,e.ruleId,e.severity,e.affectedUrls,e.target,e.action])),K(`Implementation queue details`,o.output.queue.map(e=>({label:`${e.ruleId} (${e.affectedUrls} URLs)`,context:`${e.severity}, score ${e.score}`,action:`${e.action} Verify with: ${e.verification.command}`})))}});function jc(e){let t=C(e);if(t){if([`site`,`page`,`list`,`sitemap`].includes(t))return t;throw Error(`Mode must be one of: site, page, list, sitemap.`)}}function Mc(e){let t=C(e.crawlUrl),n=C(e.url);if(t&&n&&t!==n)throw Error(`Use either a URL argument or --url, not both.`);return t??n}async function Nc(e){let t=D(e.urls)??[],n=C(e[`urls-file`]);return n&&t.push(...Pc(await Xn(n,`utf8`))),[...new Set(t)]}function Pc(e){return e.split(/[\n,]/).map(e=>e.trim()).filter(e=>e&&!e.startsWith(`#`))}const Fc=v({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.`},...J([`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.`}}),...va,"verify-content":Tr(`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=O(e),n=await _t({site:k(C(e.site)),days:T(e.days),recentDays:T(e.recent),limit:T(e.limit),includeBrand:w(e[`include-brand`]),googleAnalyticsPropertyId:C(e[`google-analytics-property`]),analyticsConnection:ya(e),verifyContent:w(e[`verify-content`]),verifyLimit:T(e[`verify-limit`]),refresh:w(e.refresh)});if(t){P(n);return}kc(n),I([`Source`,`Category`,`Score`,`Findings`,`Template`,`Analytics`,`Target`,`Action`],n.output.queue.map(e=>[e.source,e.category,e.score,e.grouped?.count??1,e.template?.label??`-`,e.analytics?.sessions??`-`,e.target,e.action])),K(`Queue action details`,n.output.queue.map(e=>({label:e.target,context:`${e.category}, score ${e.score}`,action:e.action})))}});function Ic(e){return e===null?`-`:e.toLocaleString(`en-GB`,{maximumFractionDigits:e>=100?0:1})}function Lc(e){return[...e.winners.map(e=>({direction:`winner`,item:e})),...e.losers.map(e=>({direction:`loser`,item:e}))]}function Rc(e,t){let n=Lc(t);n.length&&(process.stdout.write(`\n${e}\n`),I([`Direction`,`Key`,`Clicks`,`Impr.`,`Position`],n.map(({direction:e,item:t})=>[e,t.key,Ic(t.clickDelta),Ic(t.impressionDelta),Ic(t.positionDelta)])))}function zc(e){process.stdout.write(`
115
115
  Segment coverage
116
- `),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(`
116
+ `),I([`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 Bc(e){e.length&&(process.stdout.write(`
117
117
  Template movement
118
- `),R([`Direction`,`Template`,`Pattern confidence`,`URLs`,`Clicks`,`Returned share`,`Common terms`],e.map(e=>[e.direction,e.signature,e.confidence,e.urlCount,Pc(e.clickDelta),`${Math.round(e.movementShare*100)}%`,e.commonTerms.join(`, `)||`-`])),Y(`Template actions`,e.map(e=>e.summary)),Y(`Template evidence scope`,[`Shares use returned directional page click movement. Pattern confidence applies only to returned matched-retained page segments.`]))}const zc=y({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.`},...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.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=A(e),n=j(T(e.site)),r=await Yt({site:n,days:D(e.days),recentDays:D(e.recent),limit:D(e.limit),includeBrand:E(e[`include-brand`]),knownConfounders:T(e[`known-change`])?[T(e[`known-change`])??``]:void 0,refresh:E(e.refresh)});if(t){I(r);return}K({title:`Update postmortem`,target:r.site,status:`unknown`,summary:r.summary,metrics:[{label:`Attribution`,value:r.output.update.attribution.replaceAll(`-`,` `)},{label:`Confidence`,value:r.output.update.confidence},{label:`Known confounders`,value:r.output.update.confounders.length}]}),Y(`Recommended actions`,r.actions.map(e=>`${e.title} (${e.confidence}): ${e.action}`)),Y(`Postmortem findings`,r.output.insights.map(e=>e.summary)),Y(`Segment evidence warnings`,[...r.output.segments.page.warnings,...r.output.segments.query.warnings,...r.output.segments.device.warnings,...r.output.segments.country.warnings].filter((e,t,n)=>n.indexOf(e)===t)),Rc(r.output.templateMovement),Y(`Update evidence`,r.output.update.evidence),Lc(r.output.segments),Ic(`Page winners and losers`,r.output.segments.page),Ic(`Query winners and losers`,r.output.segments.query),Ic(`Device winners and losers`,r.output.segments.device),Ic(`Country winners and losers`,r.output.segments.country),ea(`seo export update-postmortem --site ${JSON.stringify(n)}`)}}),Bc=new Set([`database`,`filesystem`,`internal`,`unknown`]);function Vc(e){return e.replace(/^v/,``).split(`.`)[0]||`unknown`}function Hc(e){return de(e[0])?e[0]:void 0}function Uc(e,t){return ee(e)?`seo ${e}`:t?`seo ${t}`:`seo command`}function Wc(e){let t=m(e.error);if(!Bc.has(t.errorCategory))return;let n=Hc(e.args),r=Uc(e.report,n),i=[ae(e.error).code,t.errorCategory,t.failureReason,t.failureContext].filter(Boolean).join(` | `),a=new URL(`https://github.com/iannuttall/seo/issues/new`);return a.searchParams.set(`template`,`bug.yml`),a.searchParams.set(`title`,`bug: ${r} failed`),a.searchParams.set(`version`,e.version??je),a.searchParams.set(`runtime`,`Node ${Vc(e.nodeVersion??process.version)} on ${e.processPlatform??An()} (${e.processArch??On()})`),a.searchParams.set(`diagnostics`,i),a.toString()}const Gc={"refresh-priorities":`seo report`,"query-cluster":`seo report`,"quick-wins":`seo report --section quick-wins`,"ctr-underperformers":`seo report --section low-ctr`,"striking-distance":`seo report --section striking-distance`,"second-page":`seo report --section second-page`,decaying:`seo report --section content-decay`,cannibal:`seo report --section cannibalization`,"traffic-anomaly":`seo report --section traffic-drop`,"page-opportunities":`seo page --url <url>`,"audit-page":`seo page --url <url>`,"url-inspect":`seo page --url <url>`,"segment-impact":`seo compare --date <date>`,"update-correlate":`seo compare --date <date>`,"update-postmortem":`seo compare --date <date>`,"link-recover":`seo index`,"gsc-query":`seo data`},Kc={"agent-readiness":null,"ai-readiness":null,"community-intent":null,"entity-readiness":null,llms:null,"monthly-report":`seo report`,okf:null,pseo:null,"report-narrative":`seo report`,"seo-to-ai-query":null,"server-logs":null,links:`seo research backlinks --target <domain>`,"export knowledge":null,"export monthly":`seo report --json`,"export narrative":`seo report --json`,"export pseo":null,indexnow:null,"providers semrush":`seo providers`,"providers limits":null,"providers spend":null,"providers dataforseo limits":null,"providers dataforseo spend":null,"providers ahrefs limits":null,"change-log":null,client:null,project:null,projects:null,"content-groups":null,"crawl-diff":`seo crawl`,"crawl-reports":`seo crawl`,diagnose:`seo report`,"diagnose-property":`seo report`,"export diagnose":`seo report --json`,"index-watch":`seo page --url <url>`,monitoring:`seo crawl`,schedule:null,"technical-watch":`seo crawl`,tests:`seo compare --date <date>`},Z=`It saved history between runs. Your agent keeps its own notes now.`,qc=`Commands take --site now instead of a saved project.`,Jc=`Spend is controlled in your provider account. Set limits in DataForSEO, Ahrefs or the other provider.`,Yc={"providers limits":Jc,"providers spend":Jc,"providers dataforseo limits":Jc,"providers dataforseo spend":Jc,"providers ahrefs limits":Jc,"change-log":Z,client:qc,project:qc,projects:qc,"content-groups":Z,"crawl-diff":Z,"crawl-reports":Z,diagnose:Z,"diagnose-property":Z,"export diagnose":Z,"index-watch":Z,monitoring:Z,schedule:Z,"technical-watch":Z,tests:Z},Xc=new Set([`client`,`project`,`projects`]);function Zc(e){let t=e.findIndex(e=>e.startsWith(`-`)),n=t===-1?e:e.slice(0,t),r=+(n[0]===`help`);for(let e=3;e>=1;--e){let t=n.slice(r,r+e).join(` `);if(n.length-r>=e&&Object.hasOwn(Kc,t))return t}return null}function Qc(e){let t=Object.hasOwn(Gc,e)?Gc[e]:void 0;return t?`\`seo ${e}\` is now \`${t}\`. The old name stops working in 1.0.0.`:null}function $c(e){let t=Zc(e);if(!t)return null;let n=Kc[t],r=t.split(` `)[0]??``,i=n?`Use \`${n}\` instead.`:Xc.has(r)?"Run `seo sites` to list your Search Console properties.":r===`providers`?"Run `seo providers --help` for the provider commands.":"Run `seo help` for the commands.";return`\`seo ${t}\` was removed. ${Object.hasOwn(Yc,t)?Yc[t]:`SEO Skill now focuses on Search Console, analytics and keyword data.`} ${i}`}const el={"internal-links":[`--days`,`--limit`,`--check-limit`,`--min-impressions`,`--brand-terms`,`--include-brand`,`--no-include-brand`,`--js`,`--no-js`,`--fetch-concurrency`,`--fetch-interval-cap`,`--fetch-interval-ms`]},tl=e=>e.split(`=`)[0]??``;function nl(e){let t=e.find(e=>[`--project`,`--client`].includes(tl(e)));if(t)return`\`${tl(t)}\` was removed. ${qc} Pass --site with a domain, URL or sc-domain: property, or run \`seo sites\` to list your Search Console properties.`;let n=e[0]??``,r=Object.hasOwn(el,n)?e.find(e=>el[n]?.includes(tl(e))):void 0;if(!r)return null;let i=tl(r);return`\`${i}\` was removed from \`seo ${n}\`. It now sets its own limits, brand terms and page fetching, and shows them in the output.${i===`--days`?` Pass --start-date and --end-date for other dates.`:``}`}function rl(e){return e.replaceAll(`\\`,`/`).toLowerCase()}function il(e,t){return t===`win32`?`${e}.cmd`:e}function al(e,t=process.platform){let n=rl(e);if(!(n.includes(`/_npx/`)||n.includes(`/.npm/_npx/`)||n.includes(`/.cache/pnpm/dlx/`)||n.includes(`/.volta/`))){if(n.includes(`/.pnpm/`)||n.includes(`/pnpm/global/`))return{command:il(`pnpm`,t),args:[`add`,`--global`,`seo@latest`]};if(n.includes(`/.bun/install/global/`))return{command:il(`bun`,t),args:[`add`,`--global`,`seo@latest`]};if(n.includes(`/.config/yarn/global/`))return{command:il(`yarn`,t),args:[`global`,`add`,`seo@latest`]};if(n.includes(`/node_modules/seo/`))return{command:il(`npm`,t),args:[`install`,`--global`,`seo@latest`]}}}function ol(e,t){let n=e=>e.replace(/^v/,``).split(`-`)[0]?.split(`.`).map(e=>Number.parseInt(e,10))??[],r=n(e),i=n(t);for(let e=0;e<3;e+=1){let t=r[e]??0,n=i[e]??0;if(!Number.isFinite(t)||!Number.isFinite(n))return!1;if(n>t)return!0;if(n<t)return!1}return!1}function sl(e){return!(!e.stdinIsTty||!e.stdoutIsTty||e.env.CI||e.env.NO_UPDATE_NOTIFIER||e.env.NODE_ENV===`test`||e.env.npm_lifecycle_event||e.env.npm_execpath||e.env.TERM===`dumb`||e.argv.includes(`--json`)||e.argv.some(e=>[`--help`,`-h`,`--version`,`-v`].includes(e))||[`help`,`mcp`].includes(e.argv[0]??``))}function cl(e){if(e)try{return Hn(e)}catch{return e}}async function ll(e){let t=await sr({message:`seo ${e.update.latest} is available. You have ${e.update.current}.`,options:[{value:`update`,label:`Update now`,hint:[e.command.command,...e.command.args].join(` `)},{value:`later`,label:`Later`}],initialValue:`later`});return ir(t)?`later`:t}function ul(e){return $n(e.command,e.args,{stdio:`inherit`}).status}async function dl(e,t={}){if(!sl({argv:t.argv??process.argv.slice(2),env:t.env??process.env,stdinIsTty:t.stdinIsTty??!!process.stdin.isTTY,stdoutIsTty:t.stdoutIsTty??!!process.stdout.isTTY}))return;let n=cl(t.cliPath??process.argv[1]),r=n?al(n,t.platform??process.platform):void 0;if(!r)return;let i=(t.createNotifier??(e=>_r({pkg:e,shouldNotifyInNpmScript:!1})))(e).update;if(!(!i||!ol(i.current,i.latest))&&await(t.prompt??ll)({command:r,update:i})!==`later`)return(t.run??ul)(r)===0?(ar.success(`Updated seo to ${i.latest}. Run your command again.`),0):(ar.error(`The update failed. Your current version is unchanged.`),1)}function fl(e){if(!(!process.stderr.isTTY||process.env.CI)&&!(e.includes(`--json`)||e[0]===`mcp`))try{let{configDir:e,configFile:t}=Ie();if(!v(t))return;let n=_(e,`notices.json`),r=v(n)?JSON.parse(Bn(n,`utf8`)):[];if(r.includes(`0.4`))return;process.stderr.write(`
118
+ `),I([`Direction`,`Template`,`Pattern confidence`,`URLs`,`Clicks`,`Returned share`,`Common terms`],e.map(e=>[e.direction,e.signature,e.confidence,e.urlCount,Ic(e.clickDelta),`${Math.round(e.movementShare*100)}%`,e.commonTerms.join(`, `)||`-`])),q(`Template actions`,e.map(e=>e.summary)),q(`Template evidence scope`,[`Shares use returned directional page click movement. Pattern confidence applies only to returned matched-retained page segments.`]))}const Vc=v({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.`},...J([`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.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=O(e),n=k(C(e.site)),r=await cn({site:n,days:T(e.days),recentDays:T(e.recent),limit:T(e.limit),includeBrand:w(e[`include-brand`]),knownConfounders:C(e[`known-change`])?[C(e[`known-change`])??``]:void 0,refresh:w(e.refresh)});if(t){P(r);return}W({title:`Update postmortem`,target:r.site,status:`unknown`,summary:r.summary,metrics:[{label:`Attribution`,value:r.output.update.attribution.replaceAll(`-`,` `)},{label:`Confidence`,value:r.output.update.confidence},{label:`Known confounders`,value:r.output.update.confounders.length}]}),q(`Recommended actions`,r.actions.map(e=>`${e.title} (${e.confidence}): ${e.action}`)),q(`Postmortem findings`,r.output.insights.map(e=>e.summary)),q(`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)),Bc(r.output.templateMovement),q(`Update evidence`,r.output.update.evidence),zc(r.output.segments),Rc(`Page winners and losers`,r.output.segments.page),Rc(`Query winners and losers`,r.output.segments.query),Rc(`Device winners and losers`,r.output.segments.device),Rc(`Country winners and losers`,r.output.segments.country),ra(`seo export update-postmortem --site ${JSON.stringify(n)}`)}}),Hc=new Set([`database`,`filesystem`,`internal`,`unknown`]);function Uc(e){return e.replace(/^v/,``).split(`.`)[0]||`unknown`}function Wc(e){return xe(e[0])?e[0]:void 0}function Gc(e,t){return Ae(e)?`seo ${e}`:t?`seo ${t}`:`seo command`}function Kc(e){let t=fe(e.error);if(!Hc.has(t.errorCategory))return;let n=Wc(e.args),r=Gc(e.report,n),i=[ve(e.error).code,t.errorCategory,t.failureReason,t.failureContext].filter(Boolean).join(` | `),a=new URL(`https://github.com/iannuttall/seo/issues/new`);return a.searchParams.set(`template`,`bug.yml`),a.searchParams.set(`title`,`bug: ${r} failed`),a.searchParams.set(`version`,e.version??p),a.searchParams.set(`runtime`,`Node ${Uc(e.nodeVersion??process.version)} on ${e.processPlatform??Mn()} (${e.processArch??An()})`),a.searchParams.set(`diagnostics`,i),a.toString()}const qc={"refresh-priorities":`seo report`,"query-cluster":`seo report`,"quick-wins":`seo report --section quick-wins`,"ctr-underperformers":`seo report --section low-ctr`,"striking-distance":`seo report --section striking-distance`,"second-page":`seo report --section second-page`,decaying:`seo report --section content-decay`,cannibal:`seo report --section cannibalization`,"traffic-anomaly":`seo report --section traffic-drop`,"page-opportunities":`seo page --url <url>`,"audit-page":`seo page --url <url>`,"url-inspect":`seo page --url <url>`,"segment-impact":`seo compare --date <date>`,"update-correlate":`seo compare --date <date>`,"update-postmortem":`seo compare --date <date>`,"link-recover":`seo index`,"gsc-query":`seo data`},Jc={"agent-readiness":null,"ai-readiness":null,"community-intent":null,"entity-readiness":null,llms:null,"monthly-report":`seo report`,okf:null,pseo:null,"report-narrative":`seo report`,"seo-to-ai-query":null,"server-logs":null,links:`seo research backlinks --target <domain>`,"export knowledge":null,"export monthly":`seo report --json`,"export narrative":`seo report --json`,"export pseo":null,indexnow:null,"providers semrush":`seo providers`,"providers limits":null,"providers spend":null,"providers dataforseo limits":null,"providers dataforseo spend":null,"providers ahrefs limits":null,"change-log":null,client:null,project:null,projects:null,"content-groups":null,"crawl-diff":`seo crawl`,"crawl-reports":`seo crawl`,diagnose:`seo report`,"diagnose-property":`seo report`,"export diagnose":`seo report --json`,"index-watch":`seo page --url <url>`,monitoring:`seo crawl`,schedule:null,"technical-watch":`seo crawl`,tests:`seo compare --date <date>`},X=`It saved history between runs. Your agent keeps its own notes now.`,Yc=`Commands take --site now instead of a saved project.`,Xc=`Spend is controlled in your provider account. Set limits in DataForSEO, Ahrefs or the other provider.`,Zc={"providers limits":Xc,"providers spend":Xc,"providers dataforseo limits":Xc,"providers dataforseo spend":Xc,"providers ahrefs limits":Xc,"change-log":X,client:Yc,project:Yc,projects:Yc,"content-groups":X,"crawl-diff":X,"crawl-reports":X,diagnose:X,"diagnose-property":X,"export diagnose":X,"index-watch":X,monitoring:X,schedule:X,"technical-watch":X,tests:X},Qc=new Set([`client`,`project`,`projects`]);function $c(e){let t=e.findIndex(e=>e.startsWith(`-`)),n=t===-1?e:e.slice(0,t),r=+(n[0]===`help`);for(let e=3;e>=1;--e){let t=n.slice(r,r+e).join(` `);if(n.length-r>=e&&Object.hasOwn(Jc,t))return t}return null}function el(e){let t=Object.hasOwn(qc,e)?qc[e]:void 0;return t?`\`seo ${e}\` is now \`${t}\`. The old name stops working in 1.0.0.`:null}function tl(e){let t=$c(e);if(!t)return null;let n=Jc[t],r=t.split(` `)[0]??``,i=n?`Use \`${n}\` instead.`:Qc.has(r)?"Run `seo sites` to list your Search Console properties.":r===`providers`?"Run `seo providers --help` for the provider commands.":"Run `seo help` for the commands.";return`\`seo ${t}\` was removed. ${Object.hasOwn(Zc,t)?Zc[t]:`SEO Skill now focuses on Search Console, analytics and keyword data.`} ${i}`}const nl={"internal-links":[`--days`,`--limit`,`--check-limit`,`--min-impressions`,`--brand-terms`,`--include-brand`,`--no-include-brand`,`--js`,`--no-js`,`--fetch-concurrency`,`--fetch-interval-cap`,`--fetch-interval-ms`]},rl=e=>e.split(`=`)[0]??``;function il(e){let t=e.find(e=>[`--project`,`--client`].includes(rl(e)));if(t)return`\`${rl(t)}\` was removed. ${Yc} Pass --site with a domain, URL or sc-domain: property, or run \`seo sites\` to list your Search Console properties.`;let n=e[0]??``,r=Object.hasOwn(nl,n)?e.find(e=>nl[n]?.includes(rl(e))):void 0;if(!r)return null;let i=rl(r);return`\`${i}\` was removed from \`seo ${n}\`. It now sets its own limits, brand terms and page fetching, and shows them in the output.${i===`--days`?` Pass --start-date and --end-date for other dates.`:``}`}function al(e){return e.replaceAll(`\\`,`/`).toLowerCase()}function ol(e,t){return t===`win32`?`${e}.cmd`:e}function sl(e,t=process.platform){let n=al(e);if(!(n.includes(`/_npx/`)||n.includes(`/.npm/_npx/`)||n.includes(`/.cache/pnpm/dlx/`)||n.includes(`/.volta/`))){if(n.includes(`/.pnpm/`)||n.includes(`/pnpm/global/`))return{command:ol(`pnpm`,t),args:[`add`,`--global`,`seo@latest`]};if(n.includes(`/.bun/install/global/`))return{command:ol(`bun`,t),args:[`add`,`--global`,`seo@latest`]};if(n.includes(`/.config/yarn/global/`))return{command:ol(`yarn`,t),args:[`global`,`add`,`seo@latest`]};if(n.includes(`/node_modules/seo/`))return{command:ol(`npm`,t),args:[`install`,`--global`,`seo@latest`]}}}function cl(e,t){let n=e=>e.replace(/^v/,``).split(`-`)[0]?.split(`.`).map(e=>Number.parseInt(e,10))??[],r=n(e),i=n(t);for(let e=0;e<3;e+=1){let t=r[e]??0,n=i[e]??0;if(!Number.isFinite(t)||!Number.isFinite(n))return!1;if(n>t)return!0;if(n<t)return!1}return!1}function ll(e){return!(!e.stdinIsTty||!e.stdoutIsTty||e.env.CI||e.env.NO_UPDATE_NOTIFIER||e.env.NODE_ENV===`test`||e.env.npm_lifecycle_event||e.env.npm_execpath||e.env.TERM===`dumb`||e.argv.includes(`--json`)||e.argv.some(e=>[`--help`,`-h`,`--version`,`-v`].includes(e))||[`help`,`mcp`].includes(e.argv[0]??``))}function ul(e){if(e)try{return Wn(e)}catch{return e}}async function dl(e){let t=await ur({message:`seo ${e.update.latest} is available. You have ${e.update.current}.`,options:[{value:`update`,label:`Update now`,hint:[e.command.command,...e.command.args].join(` `)},{value:`later`,label:`Later`}],initialValue:`later`});return or(t)?`later`:t}function fl(e){return tr(e.command,e.args,{stdio:`inherit`}).status}async function pl(e,t={}){if(!ll({argv:t.argv??process.argv.slice(2),env:t.env??process.env,stdinIsTty:t.stdinIsTty??!!process.stdin.isTTY,stdoutIsTty:t.stdoutIsTty??!!process.stdout.isTTY}))return;let n=ul(t.cliPath??process.argv[1]),r=n?sl(n,t.platform??process.platform):void 0;if(!r)return;let i=(t.createNotifier??(e=>br({pkg:e,shouldNotifyInNpmScript:!1})))(e).update;if(!(!i||!cl(i.current,i.latest))&&await(t.prompt??dl)({command:r,update:i})!==`later`)return(t.run??fl)(r)===0?(sr.success(`Updated seo to ${i.latest}. Run your command again.`),0):(sr.error(`The update failed. Your current version is unchanged.`),1)}function ml(e){if(!(!process.stderr.isTTY||process.env.CI)&&!(e.includes(`--json`)||e[0]===`mcp`))try{let{configDir:e,configFile:t}=On();if(!_(t))return;let n=g(e,`notices.json`),r=_(n)?JSON.parse(Hn(n,`utf8`)):[];if(r.includes(`0.4`))return;process.stderr.write(`
119
119
  SEO Skill 0.4: eight commands replace the old reports, and projects are gone: every command takes --site (run \`seo sites\` to list yours). Old command names print what to use instead.
120
120
  Free hosted MCP server, nothing to install: https://seoskill.dev/docs/mcp
121
121
  To stay on the old version: npm i -g seo@0.2.41
122
122
  What changed: https://github.com/iannuttall/seo/blob/main/CHANGELOG.md
123
123
 
124
- `),Gn(n,JSON.stringify([...r,`0.4`]),{mode:384})}catch{}}const pl={name:`seo`,version:je},ml=[{title:`Start here`,commands:[[`seo start`,`Sign in with Google, then add the MCP server and skill`],[`seo report`,`The main report: overview and every section`],[`seo report --section quick-wins`,`One section of the report, with live page checks`]]},{title:`Commands`,commands:[[`seo page --url <url>`,`One page: live facts, index status, queries`],[`seo compare --date <date>`,`Before and after a date`],[`seo index`,`Pages that lost most of their clicks`],[`seo research <action>`,`Keyword, SERP, competitor and backlink data from your key`],[`seo data`,`Raw Search Console, Bing or Clicky rows`],[`seo crawl`,`Crawl a site and list technical issues`],[`seo internal-links`,`Pages that could link to a target page`]]},{title:`Setup`,commands:[[`seo login`,`Use your seoskill.dev account on this machine`],[`seo sites`,`List Search Console properties`],[`seo mcp install`,`Add the local MCP server to your agents`],[`seo providers`,`Connect DataForSEO or Ahrefs`],[`seo doctor`,`Check local auth and config`]]}],hl=[...ml,{title:`More commands`,commands:[[`seo crawl-queue`,`Run a crawl and rank the fix queue`],[`seo rules`,`List crawler rule ids`],[`seo explain --rule missing_title`,`Explain a crawler rule`],[`seo redirect-trace`,`Trace redirects`],[`seo perf audit`,`Run a page performance audit`],[`seo index-coverage`,`Choose pages for URL Inspection`],[`seo ai-referrals`,`AI referral traffic in Google Analytics`],[`seo analytics google report`,`Google Analytics landing pages for visits from search`],[`seo updates`,`List official Google ranking updates`],[`seo export`,`Export report data to CSV`],[`seo reports list`,`List every structured report`],[`seo skill list`,`Show the packaged SEO skill`],[`seo auth`,`Manage Google auth`],[`seo cache`,`Manage local cache`],[`seo privacy`,`Show local storage paths`],[`seo telemetry status`,`Check anonymous usage telemetry`],[`seo reset`,`Delete saved credentials and all local data`]]}];function gl(e){pi(`seo v${pl.version}`,`Your own Search Console, Google Analytics and Bing data, as facts for your agent.`),process.stdout.write(`
125
- `),gi(e.flatMap(e=>e.commands.map(([t,n])=>({category:e.title,id:t,name:n}))),{noun:`command`,categoryLabels:Object.fromEntries(e.map(e=>[e.title,e.title]))}),process.stdout.write(`
126
- `),vi(`More help`,"Use `seo help <command>` or `seo <command> --help` for command help.","Use `seo help all` for the longer command list.")}const _l=y({meta:{name:`seo`,version:pl.version,description:`Local-first SEO CLI and MCP server`},subCommands:{init:Go,analytics:Bi,auth:qi,mcp:Yo,doctor:ms,privacy:_c,telemetry:Tc,reset:vc,cache:Xi,logs:Ko,crawl:xa,"crawl-queue":Oc,content:ta,providers:js,export:Wa,"index-coverage":Xo,"link-recover":Zo,rules:Rs,explain:zs,"refresh-priorities":Nc,"redirect-trace":Qo,skill:gc,start:Wo,setup:Uo,login:To,logout:Oo,report:ki,page:Ai,compare:ji,index:Mi,data:Ni,research:Pi,reports:Ls,"segment-impact":gs,"striking-distance":vs,"update-postmortem":zc,sites:yc,"gsc-query":Aa,"url-inspect":ja,updates:bc,"traffic-anomaly":Fa,"update-correlate":Ia,"ai-referrals":$o,"audit-page":ls,"second-page":cs,cannibal:es,decaying:rs,"quick-wins":ss,"page-opportunities":as,perf:ps,"internal-links":is,"ctr-underperformers":ts,"query-cluster":os},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")}}),vl={cannibal:`cannibalisation`,crawl:`site-crawl`,"crawl-queue":`top-fixes`,decaying:`decaying-pages`,doctor:`setup-check`,explain:`explain-crawl-issue`,"link-recover":`link-recovery`,"query-cluster":`query-clusters`,report:`search-performance-overview`,rules:`crawler-rules`,"update-correlate":`update-correlation`},yl=new Set([`ai-referrals`,`audit-page`,`ctr-underperformers`,`index-coverage`,`internal-links`,`page-opportunities`,`quick-wins`,`redirect-trace`,`refresh-priorities`,`second-page`,`segment-impact`,`striking-distance`,`traffic-anomaly`,`update-postmortem`]),bl=new Set(qt().map(e=>e.id));function xl(e,t){let n=e.indexOf(t);return n>=0?e[n+1]:e.find(e=>e.startsWith(`${t}=`))?.slice(t.length+1)}function Sl(e){let[t,n,r]=e;if(t)return t in o?a(t,{section:xl(e,`--section`),action:n})??void 0:t===`reports`&&n===`run`?r&&bl.has(r)?r:void 0:t===`content`&&n===`optimize`?`content-optimization`:t===`perf`&&n===`audit`?`performance-audit`:vl[t]??(yl.has(t)?t:void 0)}const Q=process.argv.slice(2);fl(Q);const Cl=$c(Q)??nl(Q);Cl&&(process.stderr.write(`${Cl}\n`),process.exit(2));const wl=Qc(Q[0]??``);wl&&!Q.some(e=>[`--help`,`-h`].includes(e))&&process.stderr.write(`${wl}\n`);const Tl={agent:_n()},El=Q[0]===`telemetry`,Dl=Q[0]===`mcp`&&Q[1]===`serve`;!El&&!Dl&&p(Tl);const Ol=await dl(pl,{argv:Q});Ol!==void 0&&process.exit(Ol),(Q.length===0||Q.length===1&&[`help`,`--help`,`-h`].includes(Q[0]??``))&&(gl(ml),process.exit(0)),Q[0]===`help`&&Q[1]===`all`&&(gl(hl),process.exit(0));const $=Q[0]===`help`&&Q.length>1?[...Q.slice(1),`--help`]:Q;$!==Q&&(process.argv=[process.argv[0]??`node`,process.argv[1]??`seo`,...$]);const kl=$.some(e=>[`--help`,`-h`].includes(e)),Al=Q.length===1&&[`--version`,`-v`].includes(Q[0]??``),jl=kl||Al?void 0:Sl($);if(kl||Al)await tr(_l);else{jl&&Se(jl,Tl);try{await er(_l,{rawArgs:$}),jl&&r(jl,Tl),[`init`,`setup`,`start`].includes($[0]??``)&&!$.includes(`--dry-run`)&&Te(Tl)}catch(e){if(jl)Pt(jl,e,Tl);else if(!El){let t=Hc($);t&&Lt(t,e,Tl)}let t=ae(e);if(Q.includes(`--json`))process.stdout.write(`${JSON.stringify(Xe(t),null,2)}\n`);else{process.stderr.write(`Error: ${t.message}\n`);let n=Wc({args:$,error:e,report:jl});n&&process.stderr.write(`Report this bug: ${n}\n`)}process.exitCode=t.exitCode}}export{};
124
+ `),qn(n,JSON.stringify([...r,`0.4`]),{mode:384})}catch{}}const hl={name:`seo`,version:p},gl=[{title:`Start here`,commands:[[`seo start`,`Sign in with Google, then add the MCP server and skill`],[`seo report`,`The main report: overview and every section`],[`seo report --section quick-wins`,`One section of the report, with live page checks`]]},{title:`Commands`,commands:[[`seo page --url <url>`,`One page: live facts, index status, queries`],[`seo compare --date <date>`,`Before and after a date`],[`seo index`,`Pages that lost most of their clicks`],[`seo research <action>`,`Keyword, SERP, competitor and backlink data from your key`],[`seo data`,`Raw Search Console, Bing or Clicky rows`],[`seo crawl`,`Crawl a site and list technical issues`],[`seo internal-links`,`Pages that could link to a target page`]]},{title:`Setup`,commands:[[`seo login`,`Use your seoskill.dev account on this machine`],[`seo sites`,`List Search Console properties`],[`seo mcp install`,`Add the local MCP server to your agents`],[`seo providers`,`Connect DataForSEO or Ahrefs`],[`seo doctor`,`Check local auth and config`]]}],_l=[...gl,{title:`More commands`,commands:[[`seo crawl-queue`,`Run a crawl and rank the fix queue`],[`seo rules`,`List crawler rule ids`],[`seo explain --rule missing_title`,`Explain a crawler rule`],[`seo redirect-trace`,`Trace redirects`],[`seo perf audit`,`Run a page performance audit`],[`seo index-coverage`,`Choose pages for URL Inspection`],[`seo ai-referrals`,`AI referral traffic in Google Analytics`],[`seo analytics google report`,`Google Analytics landing pages for visits from search`],[`seo updates`,`List official Google ranking updates`],[`seo export`,`Export report data to CSV`],[`seo reports list`,`List every structured report`],[`seo skill list`,`Show the packaged SEO skill`],[`seo auth`,`Manage Google auth`],[`seo cache`,`Manage local cache`],[`seo privacy`,`Show local storage paths`],[`seo telemetry status`,`Check anonymous usage telemetry`],[`seo reset`,`Delete saved credentials and all local data`]]}];function vl(e){gi(`seo v${hl.version}`,`Your own Search Console, Google Analytics and Bing data, as facts for your agent.`),process.stdout.write(`
125
+ `),yi(e.flatMap(e=>e.commands.map(([t,n])=>({category:e.title,id:t,name:n}))),{noun:`command`,categoryLabels:Object.fromEntries(e.map(e=>[e.title,e.title]))}),process.stdout.write(`
126
+ `),xi(`More help`,"Use `seo help <command>` or `seo <command> --help` for command help.","Use `seo help all` for the longer command list.")}const yl=v({meta:{name:`seo`,version:hl.version,description:`Local-first SEO CLI and MCP server`},subCommands:{init:Jo,analytics:Ui,auth:Xi,mcp:Qo,doctor:_s,privacy:yc,telemetry:Dc,reset:bc,cache:$i,logs:Yo,crawl:wa,"crawl-queue":Ac,content:ia,providers:Ps,export:qa,"index-coverage":$o,"link-recover":es,rules:Vs,explain:Hs,"refresh-priorities":Fc,"redirect-trace":ts,skill:vc,start:qo,setup:Ko,login:Oo,logout:jo,report:Mi,page:Ni,compare:Pi,index:Fi,data:Ii,research:Li,reports:Bs,"segment-impact":ys,"striking-distance":xs,"update-postmortem":Vc,sites:xc,"gsc-query":Na,"url-inspect":Pa,updates:Sc,"traffic-anomaly":Ra,"update-correlate":za,"ai-referrals":ns,"audit-page":fs,"second-page":ds,cannibal:rs,decaying:os,"quick-wins":us,"page-opportunities":cs,perf:gs,"internal-links":ss,"ctr-underperformers":is,"query-cluster":ls},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")}}),bl={cannibal:`cannibalisation`,crawl:`site-crawl`,"crawl-queue":`top-fixes`,decaying:`decaying-pages`,doctor:`setup-check`,explain:`explain-crawl-issue`,"link-recover":`link-recovery`,"query-cluster":`query-clusters`,report:`search-performance-overview`,rules:`crawler-rules`,"update-correlate":`update-correlation`},xl=new Set([`ai-referrals`,`audit-page`,`ctr-underperformers`,`index-coverage`,`internal-links`,`page-opportunities`,`quick-wins`,`redirect-trace`,`refresh-priorities`,`second-page`,`segment-impact`,`striking-distance`,`traffic-anomaly`,`update-postmortem`]),Sl=new Set(Yt().map(e=>e.id));function Cl(e,t){let n=e.indexOf(t);return n>=0?e[n+1]:e.find(e=>e.startsWith(`${t}=`))?.slice(t.length+1)}function wl(e){let[t,n,r]=e;if(t)return t in Xe?a(t,{section:Cl(e,`--section`),action:n})??void 0:t===`reports`&&n===`run`?r&&Sl.has(r)?r:void 0:t===`content`&&n===`optimize`?`content-optimization`:t===`perf`&&n===`audit`?`performance-audit`:bl[t]??(xl.has(t)?t:void 0)}const Z=process.argv.slice(2);ml(Z);const Tl=tl(Z)??il(Z);Tl&&(process.stderr.write(`${Tl}\n`),process.exit(2));const El=el(Z[0]??``);El&&!Z.some(e=>[`--help`,`-h`].includes(e))&&process.stderr.write(`${El}\n`);const Dl={agent:We()},Ol=Z[0]===`telemetry`,kl=Z[0]===`mcp`&&Z[1]===`serve`;!Ol&&!kl&&ee(Dl);const Al=await pl(hl,{argv:Z});Al!==void 0&&process.exit(Al),(Z.length===0||Z.length===1&&[`help`,`--help`,`-h`].includes(Z[0]??``))&&(vl(gl),process.exit(0)),Z[0]===`help`&&Z[1]===`all`&&(vl(_l),process.exit(0));const Q=Z[0]===`help`&&Z.length>1?[...Z.slice(1),`--help`]:Z;Q!==Z&&(process.argv=[process.argv[0]??`node`,process.argv[1]??`seo`,...Q]);const jl=Q.some(e=>[`--help`,`-h`].includes(e)),Ml=Z.length===1&&[`--version`,`-v`].includes(Z[0]??``),$=jl||Ml?void 0:wl(Q);if(jl||Ml)await rr(yl);else{$&&De($,Dl);try{await nr(yl,{rawArgs:Q}),$&&It($,Dl),[`init`,`setup`,`start`].includes(Q[0]??``)&&!Q.includes(`--dry-run`)&&Ne(Dl)}catch(e){if($)we($,e,Dl);else if(!Ol){let t=Wc(Q);t&&r(t,e,Dl)}let t=ve(e);if(Z.includes(`--json`))process.stdout.write(`${JSON.stringify(oe(t),null,2)}\n`);else{process.stderr.write(`Error: ${t.message}\n`);let n=Kc({args:Q,error:e,report:$});n&&process.stderr.write(`Report this bug: ${n}\n`)}process.exitCode=t.exitCode}}export{};
127
127
  //# sourceMappingURL=cli.js.map