seo 0.15.1 → 0.15.2

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,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire as __seoCreateRequire } from 'node:module'; const require = __seoCreateRequire(import.meta.url);
3
- import{$ as e,$n as t,$t as n,A as r,An as i,At as a,B as o,Bn as s,Bt as c,C as l,Cn as u,Ct as d,D as f,Dn as ee,Dt as te,E as ne,En as p,Et as re,F as m,Fn as ie,Ft as ae,G as oe,Gn as se,Gt as ce,H as le,Hn as ue,Ht as de,I as fe,In as pe,It as me,J as he,Jn as ge,Jt as _e,K as ve,Kn as ye,Kt as be,L as xe,Ln as Se,Lt as Ce,M as we,Mn as Te,Mt as Ee,N as De,Nn as Oe,Nt as ke,O as Ae,On as je,Ot as Me,P as Ne,Pn as Pe,Pt as Fe,Q as Ie,Qn as Le,Qt as Re,R as ze,Rn as Be,Rt as Ve,S as He,Sn as Ue,St as We,T as Ge,Tn as Ke,Tt as qe,U as Je,Un as Ye,Ut as Xe,V as Ze,Vn as Qe,Vt as $e,W as et,Wn as tt,Wt as nt,X as rt,Xn as it,Xt as at,Y as ot,Yn as st,Yt as ct,Z as lt,Zn as ut,Zt as dt,_ as ft,_n as pt,_t as mt,a as ht,an as gt,ar as h,at as _t,b as vt,bn as yt,bt,c as xt,cn as St,cr as Ct,ct as wt,d as Tt,dn as Et,dt as Dt,en as Ot,er as kt,et as At,f as jt,fn as Mt,ft as Nt,g as Pt,gn as Ft,gt as It,h as Lt,hn as Rt,ht as zt,i as Bt,in as Vt,ir as Ht,it as Ut,j as Wt,jn as Gt,jt as Kt,k as qt,kn as Jt,kt as Yt,l as Xt,ln as Zt,lr as Qt,lt as $t,m as en,mn as tn,mt as nn,nn as rn,nr as an,nt as on,o as sn,on as cn,or as ln,ot as un,p as dn,pn as fn,pt as pn,q as mn,qn as hn,qt as gn,rn as _n,rr as vn,rt as yn,sn as bn,sr as xn,st as Sn,t as Cn,tn as wn,tr as Tn,tt as En,u as Dn,un as On,ut as kn,v as An,vn as jn,vt as Mn,w as Nn,wn as Pn,wt as Fn,x as In,xn as Ln,xt as Rn,y as zn,yn as Bn,yt as Vn,z as Hn,zn as Un,zt as Wn}from"./dist-DoPZlCJq.js";import{randomBytes as Gn}from"node:crypto";import{arch as Kn,homedir as qn,platform as Jn}from"node:os";import{basename as Yn,dirname as Xn,join as g,posix as Zn,relative as Qn,resolve as $n,sep as er,win32 as tr}from"node:path";import{constants as nr,copyFileSync as rr,createWriteStream as ir,existsSync as _,mkdirSync as ar,readFileSync as or,readdirSync as sr,realpathSync as cr,renameSync as lr,rmSync as ur,statSync as dr,writeFileSync as fr}from"node:fs";import{fileURLToPath as pr}from"node:url";import{cp as mr,mkdir as hr,readFile as v,readdir as gr,rename as _r,writeFile as vr}from"node:fs/promises";import{isDeepStrictEqual as yr,stripVTControlCharacters as br}from"node:util";import{spawn as xr,spawnSync as Sr}from"node:child_process";import{defineCommand as y,runCommand as Cr,runMain as wr}from"citty";import{cancel as Tr,confirm as Er,intro as Dr,isCancel as Or,log as kr,multiselect as Ar,note as b,outro as x,password as jr,select as Mr,text as Nr}from"@clack/prompts";import Pr from"picocolors";import Fr from"string-width";import Ir from"cli-table3";import{finished as Lr}from"node:stream/promises";import{applyEdits as Rr,modify as zr,parse as Br,printParseErrorCode as Vr}from"jsonc-parser";import Hr from"update-notifier";const S={summary:/* @__PURE__ */ `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 Ur(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 Wr(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 Gr(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:Ur(e,t)})),...[`page`,`query`,`device`,`country`].map(t=>({filename:`postmortem-segment-${t}-unmatched.csv`,headers:[...S.unmatchedSegment],rows:Wr(e,t)}))]}function Kr(e){return _(e)?sr(e,{withFileTypes:!0}).filter(e=>e.isFile()&&e.name.endsWith(`.log`)).map(t=>{let n=g(e,t.name),r=dr(n);return{path:n,size:r.size,modifiedAt:r.mtimeMs}}):[]}function qr(e,t){let n=Xn(e),r=Yn(e,`.log`),i=0;for(;;){let e=g(n,`${r}.${t}${i?`-${i}`:``}.log`);if(!_(e))return e;i+=1}}function Jr(e={}){let n=e.directory??t().logDir,r=e.now??Date.now(),i=e.maxFileBytes??8388608,a=e.maxTotalBytes??67108864,o=e.maxAgeMs??12096e5;ar(n,{recursive:!0,mode:448});let s=0,c=0;for(let e of Kr(n))e.size<=i||(lr(e.path,qr(e.path,r)),s+=1);for(let e of Kr(n))e.modifiedAt>=r-o||(ur(e.path,{force:!0}),c+=1);let l=Kr(n).sort((e,t)=>e.modifiedAt-t.modifiedAt||(e.path<t.path?-1:+(e.path>t.path))),u=l.reduce((e,t)=>e+t.size,0);for(let e of l){if(u<=a)break;ur(e.path,{force:!0}),u-=e.size,c+=1}return{rotated:s,removed:c,sizeBytes:u}}function Yr(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 Xr(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 Zr(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 Qr(e,t){let n=w(e[t]);if(n===!1)return!0;if(n===!0)return!1;let r=w(e[$r(t)]);if(r===!1)return!0;if(r===!0)return!1;if(w(e[`no-${t}`])===!0||w(e[`no${ei(t)}`])===!0)return!0}function $r(e){return e.replace(/-([a-z])/g,(e,t)=>t.toUpperCase())}function ei(e){let t=$r(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 ti(e){return D(e)??[]}function ni(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 ri(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 v(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 ii(e,t={}){let n=k(e),r=(await On(t.refresh,t.account)).map(e=>e.siteUrl),i=Tn(r,n);if(!i)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 i}function ai(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 oi(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 si(e={}){let t=e.env??process.env,n=oi(e.columns)??oi(e.streamColumns)??oi(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:si({...e,env:t,streamColumns:e.streamColumns??process.stdout.columns}),colors:Pr.createColors(r),hasColor:r,isInteractive:n&&!t.CI}}function j(e){return Fr(br(e))}function ci(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 li(e,t){if(!e)return[``];let n=e.trim().split(/\s+/u),r=[],i=``;for(let e of n){let n=j(e)>t?ci(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
3
+ import{$ as e,$n as t,$t as n,A as r,An as i,At as a,B as o,Bn as s,Bt as c,C as l,Cn as u,Ct as d,D as f,Dn as ee,Dt as te,E as ne,En as p,Et as re,F as m,Fn as ie,Ft as ae,G as oe,Gn as se,Gt as ce,H as le,Hn as ue,Ht as de,I as fe,In as pe,It as me,J as he,Jn as ge,Jt as _e,K as ve,Kn as ye,Kt as be,L as xe,Ln as Se,Lt as Ce,M as we,Mn as Te,Mt as Ee,N as De,Nn as Oe,Nt as ke,O as Ae,On as je,Ot as Me,P as Ne,Pn as Pe,Pt as Fe,Q as Ie,Qn as Le,Qt as Re,R as ze,Rn as Be,Rt as Ve,S as He,Sn as Ue,St as We,T as Ge,Tn as Ke,Tt as qe,U as Je,Un as Ye,Ut as Xe,V as Ze,Vn as Qe,Vt as $e,W as et,Wn as tt,Wt as nt,X as rt,Xn as it,Xt as at,Y as ot,Yn as st,Yt as ct,Z as lt,Zn as ut,Zt as dt,_ as ft,_n as pt,_t as mt,a as ht,an as gt,ar as h,at as _t,b as vt,bn as yt,bt,c as xt,cn as St,cr as Ct,ct as wt,d as Tt,dn as Et,dt as Dt,en as Ot,er as kt,et as At,f as jt,fn as Mt,ft as Nt,g as Pt,gn as Ft,gt as It,h as Lt,hn as Rt,ht as zt,i as Bt,in as Vt,ir as Ht,it as Ut,j as Wt,jn as Gt,jt as Kt,k as qt,kn as Jt,kt as Yt,l as Xt,ln as Zt,lr as Qt,lt as $t,m as en,mn as tn,mt as nn,nn as rn,nr as an,nt as on,o as sn,on as cn,or as ln,ot as un,p as dn,pn as fn,pt as pn,q as mn,qn as hn,qt as gn,rn as _n,rr as vn,rt as yn,sn as bn,sr as xn,st as Sn,t as Cn,tn as wn,tr as Tn,tt as En,u as Dn,un as On,ut as kn,v as An,vn as jn,vt as Mn,w as Nn,wn as Pn,wt as Fn,x as In,xn as Ln,xt as Rn,y as zn,yn as Bn,yt as Vn,z as Hn,zn as Un,zt as Wn}from"./dist-Dpl5hSMM.js";import{randomBytes as Gn}from"node:crypto";import{arch as Kn,homedir as qn,platform as Jn}from"node:os";import{basename as Yn,dirname as Xn,join as g,posix as Zn,relative as Qn,resolve as $n,sep as er,win32 as tr}from"node:path";import{constants as nr,copyFileSync as rr,createWriteStream as ir,existsSync as _,mkdirSync as ar,readFileSync as or,readdirSync as sr,realpathSync as cr,renameSync as lr,rmSync as ur,statSync as dr,writeFileSync as fr}from"node:fs";import{fileURLToPath as pr}from"node:url";import{cp as mr,mkdir as hr,readFile as v,readdir as gr,rename as _r,writeFile as vr}from"node:fs/promises";import{isDeepStrictEqual as yr,stripVTControlCharacters as br}from"node:util";import{spawn as xr,spawnSync as Sr}from"node:child_process";import{defineCommand as y,runCommand as Cr,runMain as wr}from"citty";import{cancel as Tr,confirm as Er,intro as Dr,isCancel as Or,log as kr,multiselect as Ar,note as b,outro as x,password as jr,select as Mr,text as Nr}from"@clack/prompts";import Pr from"picocolors";import Fr from"string-width";import Ir from"cli-table3";import{finished as Lr}from"node:stream/promises";import{applyEdits as Rr,modify as zr,parse as Br,printParseErrorCode as Vr}from"jsonc-parser";import Hr from"update-notifier";const S={summary:/* @__PURE__ */ `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 Ur(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 Wr(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 Gr(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:Ur(e,t)})),...[`page`,`query`,`device`,`country`].map(t=>({filename:`postmortem-segment-${t}-unmatched.csv`,headers:[...S.unmatchedSegment],rows:Wr(e,t)}))]}function Kr(e){return _(e)?sr(e,{withFileTypes:!0}).filter(e=>e.isFile()&&e.name.endsWith(`.log`)).map(t=>{let n=g(e,t.name),r=dr(n);return{path:n,size:r.size,modifiedAt:r.mtimeMs}}):[]}function qr(e,t){let n=Xn(e),r=Yn(e,`.log`),i=0;for(;;){let e=g(n,`${r}.${t}${i?`-${i}`:``}.log`);if(!_(e))return e;i+=1}}function Jr(e={}){let n=e.directory??t().logDir,r=e.now??Date.now(),i=e.maxFileBytes??8388608,a=e.maxTotalBytes??67108864,o=e.maxAgeMs??12096e5;ar(n,{recursive:!0,mode:448});let s=0,c=0;for(let e of Kr(n))e.size<=i||(lr(e.path,qr(e.path,r)),s+=1);for(let e of Kr(n))e.modifiedAt>=r-o||(ur(e.path,{force:!0}),c+=1);let l=Kr(n).sort((e,t)=>e.modifiedAt-t.modifiedAt||(e.path<t.path?-1:+(e.path>t.path))),u=l.reduce((e,t)=>e+t.size,0);for(let e of l){if(u<=a)break;ur(e.path,{force:!0}),u-=e.size,c+=1}return{rotated:s,removed:c,sizeBytes:u}}function Yr(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 Xr(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 Zr(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 Qr(e,t){let n=w(e[t]);if(n===!1)return!0;if(n===!0)return!1;let r=w(e[$r(t)]);if(r===!1)return!0;if(r===!0)return!1;if(w(e[`no-${t}`])===!0||w(e[`no${ei(t)}`])===!0)return!0}function $r(e){return e.replace(/-([a-z])/g,(e,t)=>t.toUpperCase())}function ei(e){let t=$r(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 ti(e){return D(e)??[]}function ni(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 ri(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 v(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 ii(e,t={}){let n=k(e),r=(await On(t.refresh,t.account)).map(e=>e.siteUrl),i=Tn(r,n);if(!i)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 i}function ai(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 oi(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 si(e={}){let t=e.env??process.env,n=oi(e.columns)??oi(e.streamColumns)??oi(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:si({...e,env:t,streamColumns:e.streamColumns??process.stdout.columns}),colors:Pr.createColors(r),hasColor:r,isInteractive:n&&!t.CI}}function j(e){return Fr(br(e))}function ci(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 li(e,t){if(!e)return[``];let n=e.trim().split(/\s+/u),r=[],i=``;for(let e of n){let n=j(e)>t?ci(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
6
  `).flatMap(e=>li(e,n))}function ui(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 di={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 fi(e){return e===void 0?``:String(e)}function pi(e,t){return e.map((e,n)=>{let r=t.map(e=>fi(e[n]));return Math.min(42,Math.max(j(e),...r.map(j),4))})}function mi(e,t,n){return e.length===0||e.length>4||!e.map((e,n)=>t.every(e=>/^-?(?:\d+(?:\.\d+)?|\d+(?:\.\d+)?%)$/u.test(fi(e[n]).trim()))).some(Boolean)?!1:pi(e,t).reduce((e,t)=>e+t+2,0)<=n.columns}function hi(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 gi(e,t,n){let r=pi(e,t).map(e=>e+2),i=new Ir({chars:di,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(hi(e,fi(a[t]),n),Math.max(1,(r[t]??3)-2)).join(`
@@ -29,7 +29,7 @@ import{$ as e,$n as t,$t as n,A as r,An as i,At as a,B as o,Bn as s,Bt as c,C as
29
29
 
30
30
  `)}function Li(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
31
  `)}function Ri(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 zi(e,t){return e.length===0?t.colors.dim(`No parameters.`):Ii(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){let t=e&&typeof e==`object`&&!Array.isArray(e)?Xt():void 0,n=t?{...e,updateNotice:t}:e;process.stdout.write(`${JSON.stringify(n,null,2)}\n`)}function F(e){let t=bi(e,A());t&&process.stdout.write(`${t}\n`)}function I(e,t){process.stdout.write(`${yi(e,t,A())}\n`)}function L(e){process.stdout.write(`${xi(e,A())}\n`)}function Bi(e,t){process.stdout.write(`${Ai(e,A(),t)}\n`)}function Vi(e){process.stdout.write(`${ji(e,A())}\n`)}function Hi(e){process.stdout.write(`${Di(e,A())}\n`)}function Ui(e,t){process.stdout.write(`${Fi(e,A(),t)}\n`)}function Wi(e,t){process.stdout.write(`${Ii(e,A(),t)}\n`)}function R(e,...t){process.stdout.write(`${Li(e,t,A())}\n`)}function Gi(e,t){process.stdout.write(`${Ri(e,t,A())}\n`)}function Ki(e){process.stdout.write(`${zi(e,A())}\n`)}function qi(e){return e<1024?`${e} B`:e<1048576?`${(e/1024).toFixed(1)} KB`:`${(e/1048576).toFixed(1)} MB`}function z(e){return typeof e==`symbol`&&process.exit(1),e}const Ji={site:`--site`,url:`--url`,section:`--section`,date:`--date`,pages:`--pages`,dimensions:`--dimensions`,detail:`--detail`,offset:`--offset`,max_urls:`--max-urls`,mark:`--mark`,issues:`--issues`,note:`--note`},Yi=e=>/^[\w:/.,<>@#?=&%+-]+$/.test(String(e))?String(e):`"${e}"`,Xi=(e,t)=>[`seo ${e}`,...Object.entries(t??{}).filter(([,e])=>e!==void 0).map(([e,t])=>`${Ji[e]??`--${e}`} ${Yi(t??``)}`)].join(` `),Zi={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.`}},Qi={"start-date":{type:`string`,description:`Optional YYYY-MM-DD.`},"end-date":{type:`string`,description:`Optional YYYY-MM-DD.`}},B={detail:{type:`string`,description:`summary (default), rows for the next page, or full.`},offset:{type:`string`,description:`rows only: the first row.`}};function V(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 $i(e,t){return ii(t,{refresh:w(e.refresh),account:C(e.account)})}async function H(e,t){try{let n=await t(r({call:Xi,account:C(e.account),refresh:w(e.refresh)}));O(e)?P(n.data):process.stdout.write(`${n.text}\n`)}catch(e){throw ne(e)}}const ea=y({meta:{name:`report`,description:"Start here. No --site: last week across your audited sites (beta, after `seo login`). --site: overview and every section. Add --section for one"},args:{site:{type:`string`,description:"Site, URL or Search Console property. Leave it out for the summary of every audited site (beta, after `seo login`)."},section:{type:`string`,description:`Optional, needs --site. One of ${[...Ie,on].join(`, `)}. issues keeps a list of what was found for the site, on this machine.`},mark:{type:`string`,description:`With --site and --issues: ${yn.join(`, `)}. Records what the person decided; runs no checks.`},issues:{type:`string`,description:`With --mark: issue ids from --section issues, comma-separated.`},note:{type:`string`,description:`Optional, with --mark: why.`},...Qi,...B,...Zi},run:async({args:t})=>{let n=C(t.section),r=[...Ie,on];if(n&&!r.includes(n))throw new h(`INVALID_INPUT`,`--section is one of ${r.join(`, `)}.`);let i=C(t.site),a=C(t.mark),o=C(t.issues);if(a||o){if(a&&!yn.includes(a))throw new h(`INVALID_INPUT`,`--mark is one of ${yn.join(`, `)}.`);let e=await $i(t,k(i));await H(t,r=>At({site:e,section:n,mark:a,issues:o?.split(`,`),note:C(t.note)},r));return}if(!i&&!n&&Te()){await H(t,e=>et(e));return}let s=n===`bing`?await $i(t,i).catch(()=>k(i)):await $i(t,i).catch(async r=>{if(n||!i)throw r;return await H(t,n=>e({site:k(i),startDate:C(t[`start-date`]),endDate:C(t[`end-date`])},n).catch(()=>{throw r})),null});s!==null&&await H(t,e=>At({site:s,section:n,startDate:C(t[`start-date`]),endDate:C(t[`end-date`]),detail:V(t),offset:T(t.offset)},e))}}),ta=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},...Qi,...B,...Zi},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 $i(e,t);await H(e,r=>he({site:n,url:t,startDate:C(e[`start-date`]),endDate:C(e[`end-date`]),detail:V(e),offset:T(e.offset)},r))}}),na=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/.`},...Zi},run:async({args:e})=>{let t=await $i(e,C(e.site));await H(e,n=>ve({site:t,date:C(e.date)??``,pages:C(e.pages)},n))}}),ra=y({meta:{name:`index`,description:`Google indexing reasons for known URLs, checked live, and pages that lost most of their clicks`},args:{site:{type:`string`,description:`Site, URL or Search Console property.`},"max-urls":{type:`string`,description:`How many URLs to ask Google about, 100 by default. Ask for more when you want a fuller picture; it stops at the URL Inspection quota left today.`},...Qi,...B,...Zi},run:async({args:e})=>{let t=await $i(e,C(e.site)),n=T(e[`max-urls`]);if(n!==void 0&&(!Number.isInteger(n)||n<1||n>2e3))throw new h(`INVALID_INPUT`,`--max-urls is a whole number from 1 to ${_t}.`);await H(e,r=>mn({site:t,startDate:C(e[`start-date`]),endDate:C(e[`end-date`]),detail:V(e),offset:T(e.offset),maxUrls:n},r))}}),ia=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: ${Nt.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.`},...Qi,...B,...Zi},run:async({args:e})=>{let t=D(e.dimensions)??[`query`],n=t.filter(e=>!Nt.includes(e));if(n.length)throw new h(`INVALID_INPUT`,`Unknown dimensions: ${n.join(`, `)}. Use ${Nt.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 H(e,async()=>Sn({site:t,startDate:C(e[`start-date`]),endDate:C(e[`end-date`]),detail:V(e),offset:T(e.offset)},await qt(),{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:Ae,call:e=>Xi(`data`,{source:`clicky`,site:t,detail:`rows`,offset:e})}));return}if(r===`bing`){let n=k(C(e.site));await H(e,r=>wt({site:n,dimension:t[0]===`page`?`page`:`query`,startDate:C(e[`start-date`]),endDate:C(e[`end-date`]),detail:V(e),offset:T(e.offset)},r));return}let i=await $i(e,C(e.site)),a=C(e.page),o=C(e.query);await H(e,n=>pn({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:V(e),offset:T(e.offset)},n))}}),aa=y({meta:{name:`research`,description:`Keyword, SERP, competitor, backlink and AI answer data from your DataForSEO or Ahrefs key`},args:{action:{type:`positional`,required:!0,description:ot.join(`, `)},provider:{type:`string`,description:`${rt.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.`},...B,account:Zi.account,json:Zi.json},run:async({args:e})=>{let t=C(e.action);if(!ot.includes(t))throw new h(`INVALID_INPUT`,`The action is one of ${ot.join(`, `)}.`);let n=await qt(),r={dataforseo:`x-dataforseo-login`,ahrefs:`x-ahrefs-key`},i=C(e.provider)??(t===`ai_answers`?`dataforseo`:rt.find(e=>n.has(r[e])))??`dataforseo`;if(!rt.includes(i))throw new h(`INVALID_INPUT`,`--provider is one of ${rt.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);if(t===`ai_answers`){if(i!==`dataforseo`)throw new h(`INVALID_INPUT`,`ai_answers uses dataforseo.`);let r=a.length?o??``:await $i(e,o);await H(e,i=>Dt({site:r,country:s??`us`,detail:V(e),offset:T(e.offset)??0,questions:a},n,i,e=>Xi(`research`,{action:t,target:o,keywords:a.join(`,`)||void 0,country:s,detail:`rows`,offset:e}).replace(`--action `,``)))}else await H(e,()=>lt({provider:i,action:t,keywords:a,target:o,country:s,detail:V(e),offset:T(e.offset)},n,{call:e=>Xi(`research`,{action:t,provider:i,keywords:a.join(`,`)||void 0,target:o,country:s,detail:`rows`,offset:e}).replace(`--action `,``)}))}}),oa=y({meta:{name:`lint`,description:`Check copy for patterns common in AI-written text, weak copy and title and description length`},args:{paths:{type:`positional`,required:!0,description:`Files, folders or globs: .md, .mdx, .html, .astro or .txt. Up to 100 files.`},preset:{type:`string`,description:`${zt.join(`, `)}. Default blog for Markdown and text, landing for HTML and Astro. meta reads one title or description per line.`},format:{type:`string`,description:`${nn.join(`, `)}. Default: from each file name.`},"fail-on":{type:`string`,description:`error (default) or warning: exit with code 1 when a finding at that level or above is found.`},ignore:{type:`string`,description:`Rule ids or review lenses to skip, comma-separated.`},"ignore-phrase":{type:`string`,description:`A phrase to allow, such as a product name a rule keeps matching. Repeat it for more.`},json:Zi.json},run:async({args:e})=>{let t=C(e.preset);if(t&&!zt.includes(t))throw new h(`INVALID_INPUT`,`--preset is one of ${zt.join(`, `)}.`);let n=C(e.format);if(n&&!nn.includes(n))throw new h(`INVALID_INPUT`,`--format is one of ${nn.join(`, `)}.`);let r=C(e[`fail-on`])??`error`;if(r!==`error`&&r!==`warning`)throw new h(`INVALID_INPUT`,`--fail-on is error or warning.`);let i=(e._??[]).filter(e=>typeof e==`string`),a=[].concat(e[`ignore-phrase`]??[]).filter(e=>typeof e==`string`);try{let o=await En({paths:i,preset:t,format:n,ignoreRules:D(e.ignore),ignorePhrases:a},e=>l(e));O(e)?P(o.data):process.stdout.write(`${o.text}\n`);let{errors:s,warnings:c}=o.data;(s>0||r===`warning`&&c>0)&&(process.exitCode=1)}catch(e){throw ne(e)}}});function sa(e){return e===`keychain`?`system keychain`:e===`file`?`private local file`:e===`environment`?be:e??`missing`}function ca(e){if(e&&Mn(e))return e.trim();throw new h(`INVALID_INPUT`,`No Clicky site given. Pass --site-id with the numeric Clicky site ID.`)}async function la(e,t){return mt({siteId:e,sitekey:t}).catch(e=>{throw new h(e instanceof It&&e.code===`auth`?`AUTH_REQUIRED`:`PROVIDER_UNAVAILABLE`,e instanceof Error?e.message:String(e))})}const ua={type:`boolean`,default:!1,description:`Print machine-readable JSON.`},da={type:`string`,description:`Numeric Clicky site ID.`},fa=y({meta:{name:`connect`,description:`Check and save a Clicky site on this machine`},args:{"site-id":da,json:ua},run:async({args:e})=>{let t=O(e),n=N({json:t});n&&Dr(`Connect Clicky`);let r=ca(C(e[`site-id`])??(n?z(await Nr({message:`Clicky site ID`,validate:e=>Mn(e??``)?void 0:`Enter the numeric site ID from Clicky`})):void 0)),i=process.env.SEO_CLICKY_SITEKEY?.trim()||(n?z(await jr({message:`Clicky sitekey`,validate:e=>Vn(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 ${be}.`);let a=await la(r,i.trim()),o=await at(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 ${sa(o)}. Next: seo analytics clicky report --site ${r}`;n?x(c):process.stdout.write(`${c}\n`)}}),pa=y({meta:{name:`status`,description:`Show a Clicky site saved on this machine`},args:{"site-id":da,check:{type:`boolean`,default:!1,description:`Check the saved sitekey with Clicky.`},json:ua},run:async({args:e})=>{let t=ca(C(e[`site-id`])),n=await ct(t),r=w(e.check);r&&n&&await la(t,n.siteKey);let i={siteId:t,connected:!!n,credentialSource:n?.source,liveCheck:r?n?`passed`:`unavailable`:`not-requested`};O(e)?P(i):F([[`Site ID`,t],[`Connected`,i.connected?`yes`:`no`],[`Credential`,sa(i.credentialSource)],[`Live check`,i.liveCheck]])}}),ma=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.`},...Qi,...B,json:ua},run:async({args:e})=>{let t=k(C(e.site));await H(e,async()=>Sn({site:t,startDate:C(e[`start-date`]),endDate:C(e[`end-date`]),detail:V(e),offset:T(e.offset)},await qt(),{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:Ae,call:e=>`seo analytics clicky report --site ${t} --detail rows --offset ${e}`}))}}),ha=y({meta:{name:`disconnect`,description:`Remove a Clicky site saved on this machine`},args:{"site-id":da,json:ua},run:async({args:e})=>{let t=ca(C(e[`site-id`]));await gn(t);let n=!!process.env[be],r={siteId:t,savedCredentialRemoved:!0,environmentCredential:n?`active`:`missing`};O(e)?P(r):process.stdout.write(n?`Saved sitekey removed. ${be} is still active.\n`:`Clicky is disconnected for this site.
32
+ `)}function zi(e,t){return e.length===0?t.colors.dim(`No parameters.`):Ii(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){let t=e&&typeof e==`object`&&!Array.isArray(e)?Xt():void 0,n=t?{...e,updateNotice:t}:e;process.stdout.write(`${JSON.stringify(n,null,2)}\n`)}function F(e){let t=bi(e,A());t&&process.stdout.write(`${t}\n`)}function I(e,t){process.stdout.write(`${yi(e,t,A())}\n`)}function L(e){process.stdout.write(`${xi(e,A())}\n`)}function Bi(e,t){process.stdout.write(`${Ai(e,A(),t)}\n`)}function Vi(e){process.stdout.write(`${ji(e,A())}\n`)}function Hi(e){process.stdout.write(`${Di(e,A())}\n`)}function Ui(e,t){process.stdout.write(`${Fi(e,A(),t)}\n`)}function Wi(e,t){process.stdout.write(`${Ii(e,A(),t)}\n`)}function R(e,...t){process.stdout.write(`${Li(e,t,A())}\n`)}function Gi(e,t){process.stdout.write(`${Ri(e,t,A())}\n`)}function Ki(e){process.stdout.write(`${zi(e,A())}\n`)}function qi(e){return e<1024?`${e} B`:e<1048576?`${(e/1024).toFixed(1)} KB`:`${(e/1048576).toFixed(1)} MB`}function z(e){return typeof e==`symbol`&&process.exit(1),e}const Ji={site:`--site`,url:`--url`,section:`--section`,date:`--date`,pages:`--pages`,dimensions:`--dimensions`,detail:`--detail`,offset:`--offset`,max_urls:`--max-urls`,mark:`--mark`,issues:`--issues`,note:`--note`},Yi=e=>/^[\w:/.,<>@#?=&%+-]+$/.test(String(e))?String(e):`"${e}"`,Xi=(e,t)=>[`seo ${e}`,...Object.entries(t??{}).filter(([,e])=>e!==void 0).map(([e,t])=>`${Ji[e]??`--${e}`} ${Yi(t??``)}`)].join(` `),Zi={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.`}},Qi={"start-date":{type:`string`,description:`Optional YYYY-MM-DD.`},"end-date":{type:`string`,description:`Optional YYYY-MM-DD.`}},B={detail:{type:`string`,description:`summary (default), rows for the next page, or full.`},offset:{type:`string`,description:`rows only: the first row.`}};function V(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 $i(e,t){return ii(t,{refresh:w(e.refresh),account:C(e.account)})}async function H(e,t){try{let n=await t(r({call:Xi,account:C(e.account),refresh:w(e.refresh)}));O(e)?P(n.data):process.stdout.write(`${n.text}\n`)}catch(e){throw ne(e)}}const ea=y({meta:{name:`report`,description:"Start here. No --site: last week across your audited sites (beta, after `seo login`). --site: overview and every section. Add --section for one"},args:{site:{type:`string`,description:"Site, URL or Search Console property. Leave it out for the summary of every audited site (beta, after `seo login`)."},section:{type:`string`,description:`Optional, needs --site. One of ${[...Ie,on].join(`, `)}. issues keeps a list of what was found for the site, on this machine.`},mark:{type:`string`,description:`With --site and --issues: ${yn.join(`, `)}. Records what the person decided; runs no checks.`},issues:{type:`string`,description:`With --mark: issue ids from --section issues, comma-separated.`},note:{type:`string`,description:`Optional, with --mark: why.`},...Qi,...B,...Zi},run:async({args:t})=>{let n=C(t.section),r=[...Ie,on];if(n&&!r.includes(n))throw new h(`INVALID_INPUT`,`--section is one of ${r.join(`, `)}.`);let i=C(t.site),a=C(t.mark),o=C(t.issues);if(a||o){if(a&&!yn.includes(a))throw new h(`INVALID_INPUT`,`--mark is one of ${yn.join(`, `)}.`);let e=await $i(t,k(i));await H(t,r=>At({site:e,section:n,mark:a,issues:o?.split(`,`),note:C(t.note)},r));return}if(!i&&!n&&Te()){await H(t,e=>et(e));return}let s=n===`bing`?await $i(t,i).catch(()=>k(i)):await $i(t,i).catch(async r=>{if(n||!i)throw r;return await H(t,n=>e({site:k(i),startDate:C(t[`start-date`]),endDate:C(t[`end-date`])},n).catch(()=>{throw r})),null});s!==null&&await H(t,e=>At({site:s,section:n,startDate:C(t[`start-date`]),endDate:C(t[`end-date`]),detail:V(t),offset:T(t.offset)},e))}}),ta=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},...Qi,...B,...Zi},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 $i(e,t);await H(e,r=>he({site:n,url:t,startDate:C(e[`start-date`]),endDate:C(e[`end-date`]),detail:V(e),offset:T(e.offset)},r))}}),na=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/.`},...Zi},run:async({args:e})=>{let t=await $i(e,C(e.site));await H(e,n=>ve({site:t,date:C(e.date)??``,pages:C(e.pages)},n))}}),ra=y({meta:{name:`index`,description:`Google indexing reasons for known URLs, checked live, and pages that lost most of their clicks`},args:{site:{type:`string`,description:`Site, URL or Search Console property.`},"max-urls":{type:`string`,description:`How many URLs to ask Google about, 100 by default. Ask for more when you want a fuller picture; it stops at the URL Inspection quota left today.`},...Qi,...B,...Zi},run:async({args:e})=>{let t=await $i(e,C(e.site)),n=T(e[`max-urls`]);if(n!==void 0&&(!Number.isInteger(n)||n<1||n>2e3))throw new h(`INVALID_INPUT`,`--max-urls is a whole number from 1 to ${_t}.`);await H(e,r=>mn({site:t,startDate:C(e[`start-date`]),endDate:C(e[`end-date`]),detail:V(e),offset:T(e.offset),maxUrls:n},r))}}),ia=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: ${Nt.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.`},...Qi,...B,...Zi},run:async({args:e})=>{let t=D(e.dimensions)??[`query`],n=t.filter(e=>!Nt.includes(e));if(n.length)throw new h(`INVALID_INPUT`,`Unknown dimensions: ${n.join(`, `)}. Use ${Nt.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 H(e,async()=>Sn({site:t,startDate:C(e[`start-date`]),endDate:C(e[`end-date`]),detail:V(e),offset:T(e.offset)},await qt(),{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:Ae,call:e=>Xi(`data`,{source:`clicky`,site:t,detail:`rows`,offset:e})}));return}if(r===`bing`){let n=k(C(e.site));await H(e,r=>wt({site:n,dimension:t[0]===`page`?`page`:`query`,startDate:C(e[`start-date`]),endDate:C(e[`end-date`]),detail:V(e),offset:T(e.offset)},r));return}let i=await $i(e,C(e.site)),a=C(e.page),o=C(e.query);await H(e,n=>pn({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:V(e),offset:T(e.offset)},n))}}),aa=y({meta:{name:`research`,description:`Keyword, SERP, competitor, backlink and AI answer data from your DataForSEO or Ahrefs key`},args:{action:{type:`positional`,required:!0,description:ot.join(`, `)},provider:{type:`string`,description:`${rt.join(`, `)}. Default: the first one with a saved key.`},keywords:{type:`string`,description:`Comma-separated keywords.`},tasks:{type:`string`,description:`ai_answers only: comma-separated tasks an earlier run returned as not ready.`},target:{type:`string`,description:`Domain or URL.`},country:{type:`string`,description:`Two-letter country code. Default us.`},...B,account:Zi.account,json:Zi.json},run:async({args:e})=>{let t=C(e.action);if(!ot.includes(t))throw new h(`INVALID_INPUT`,`The action is one of ${ot.join(`, `)}.`);let n=await qt(),r={dataforseo:`x-dataforseo-login`,ahrefs:`x-ahrefs-key`},i=C(e.provider)??(t===`ai_answers`?`dataforseo`:rt.find(e=>n.has(r[e])))??`dataforseo`;if(!rt.includes(i))throw new h(`INVALID_INPUT`,`--provider is one of ${rt.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);if(t===`ai_answers`){if(i!==`dataforseo`)throw new h(`INVALID_INPUT`,`ai_answers uses dataforseo.`);let r=D(e.tasks)??[],c=a.length||r.length?o??``:await $i(e,o);await H(e,i=>Dt({site:c,country:s??`us`,detail:V(e),offset:T(e.offset)??0,questions:a,tasks:r,waitSeconds:240},n,i,e=>Xi(`research`,{action:t,target:o,keywords:a.join(`,`)||void 0,country:s,detail:`rows`,offset:e}).replace(`--action `,``)))}else await H(e,()=>lt({provider:i,action:t,keywords:a,target:o,country:s,detail:V(e),offset:T(e.offset)},n,{call:e=>Xi(`research`,{action:t,provider:i,keywords:a.join(`,`)||void 0,target:o,country:s,detail:`rows`,offset:e}).replace(`--action `,``)}))}}),oa=y({meta:{name:`lint`,description:`Check copy for patterns common in AI-written text, weak copy and title and description length`},args:{paths:{type:`positional`,required:!0,description:`Files, folders or globs: .md, .mdx, .html, .astro or .txt. Up to 100 files.`},preset:{type:`string`,description:`${zt.join(`, `)}. Default blog for Markdown and text, landing for HTML and Astro. meta reads one title or description per line.`},format:{type:`string`,description:`${nn.join(`, `)}. Default: from each file name.`},"fail-on":{type:`string`,description:`error (default) or warning: exit with code 1 when a finding at that level or above is found.`},ignore:{type:`string`,description:`Rule ids or review lenses to skip, comma-separated.`},"ignore-phrase":{type:`string`,description:`A phrase to allow, such as a product name a rule keeps matching. Repeat it for more.`},json:Zi.json},run:async({args:e})=>{let t=C(e.preset);if(t&&!zt.includes(t))throw new h(`INVALID_INPUT`,`--preset is one of ${zt.join(`, `)}.`);let n=C(e.format);if(n&&!nn.includes(n))throw new h(`INVALID_INPUT`,`--format is one of ${nn.join(`, `)}.`);let r=C(e[`fail-on`])??`error`;if(r!==`error`&&r!==`warning`)throw new h(`INVALID_INPUT`,`--fail-on is error or warning.`);let i=(e._??[]).filter(e=>typeof e==`string`),a=[].concat(e[`ignore-phrase`]??[]).filter(e=>typeof e==`string`);try{let o=await En({paths:i,preset:t,format:n,ignoreRules:D(e.ignore),ignorePhrases:a},e=>l(e));O(e)?P(o.data):process.stdout.write(`${o.text}\n`);let{errors:s,warnings:c}=o.data;(s>0||r===`warning`&&c>0)&&(process.exitCode=1)}catch(e){throw ne(e)}}});function sa(e){return e===`keychain`?`system keychain`:e===`file`?`private local file`:e===`environment`?be:e??`missing`}function ca(e){if(e&&Mn(e))return e.trim();throw new h(`INVALID_INPUT`,`No Clicky site given. Pass --site-id with the numeric Clicky site ID.`)}async function la(e,t){return mt({siteId:e,sitekey:t}).catch(e=>{throw new h(e instanceof It&&e.code===`auth`?`AUTH_REQUIRED`:`PROVIDER_UNAVAILABLE`,e instanceof Error?e.message:String(e))})}const ua={type:`boolean`,default:!1,description:`Print machine-readable JSON.`},da={type:`string`,description:`Numeric Clicky site ID.`},fa=y({meta:{name:`connect`,description:`Check and save a Clicky site on this machine`},args:{"site-id":da,json:ua},run:async({args:e})=>{let t=O(e),n=N({json:t});n&&Dr(`Connect Clicky`);let r=ca(C(e[`site-id`])??(n?z(await Nr({message:`Clicky site ID`,validate:e=>Mn(e??``)?void 0:`Enter the numeric site ID from Clicky`})):void 0)),i=process.env.SEO_CLICKY_SITEKEY?.trim()||(n?z(await jr({message:`Clicky sitekey`,validate:e=>Vn(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 ${be}.`);let a=await la(r,i.trim()),o=await at(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 ${sa(o)}. Next: seo analytics clicky report --site ${r}`;n?x(c):process.stdout.write(`${c}\n`)}}),pa=y({meta:{name:`status`,description:`Show a Clicky site saved on this machine`},args:{"site-id":da,check:{type:`boolean`,default:!1,description:`Check the saved sitekey with Clicky.`},json:ua},run:async({args:e})=>{let t=ca(C(e[`site-id`])),n=await ct(t),r=w(e.check);r&&n&&await la(t,n.siteKey);let i={siteId:t,connected:!!n,credentialSource:n?.source,liveCheck:r?n?`passed`:`unavailable`:`not-requested`};O(e)?P(i):F([[`Site ID`,t],[`Connected`,i.connected?`yes`:`no`],[`Credential`,sa(i.credentialSource)],[`Live check`,i.liveCheck]])}}),ma=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.`},...Qi,...B,json:ua},run:async({args:e})=>{let t=k(C(e.site));await H(e,async()=>Sn({site:t,startDate:C(e[`start-date`]),endDate:C(e[`end-date`]),detail:V(e),offset:T(e.offset)},await qt(),{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:Ae,call:e=>`seo analytics clicky report --site ${t} --detail rows --offset ${e}`}))}}),ha=y({meta:{name:`disconnect`,description:`Remove a Clicky site saved on this machine`},args:{"site-id":da,json:ua},run:async({args:e})=>{let t=ca(C(e[`site-id`]));await gn(t);let n=!!process.env[be],r={siteId:t,savedCredentialRemoved:!0,environmentCredential:n?`active`:`missing`};O(e)?P(r):process.stdout.write(n?`Saved sitekey removed. ${be} is still active.\n`:`Clicky is disconnected for this site.
33
33
  `)}}),ga=y({meta:{name:`clicky`,description:`Read Clicky, or save a Clicky site on this machine only`},subCommands:{connect:fa,status:pa,report:ma,disconnect:ha}}),_a=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 Rt(C(e.account)),n=t.flatMap(e=>e.propertySummaries.map(t=>({account:e.displayName??e.account,property:tn(t.property),displayName:t.displayName??t.property})));O(e)?P({accountSummaries:t,properties:n}):I([`Property`,`Name`,`Account`],n.map(e=>[e.property,e.displayName,e.account]))}}),va=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."},...Qi,...B,json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=ai(C(e.property));await H(e,async()=>kn({property:t,startDate:C(e[`start-date`]),endDate:C(e[`end-date`]),detail:V(e),offset:T(e.offset)},await $t(await Ge(),t,jn.signIn),{signIn:jn.signIn,call:e=>`seo analytics google report --property ${t} --detail rows --offset ${e}`}))}}),ya=y({meta:{name:`google`,description:`Read Google Analytics properties and reports`},subCommands:{properties:_a,report:va}}),ba=y({meta:{name:`analytics`,description:`Read analytics data from connected providers`},subCommands:{clicky:ga,google:ya}});function xa(){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 Sa(){return b(xa(),`Google sign-in`),Bn()}function Ca(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 wa(e){return e.active===`keychain`?`system keychain`:e.configured===`keychain`?`private file fallback`:`private file`}function Ta(e){return e===`shared`?`SEO Skill app`:`Local OAuth config`}function Ea(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`,Ta(e.clientSource)],[`Expires`,Yr(e.expiresAt)]),t}const Da=y({meta:{name:`auth`,description:`Authentication commands`},subCommands:{login:y({meta:{name:`login`,description:`Run Google OAuth flow`},run:async()=>{if((await yt()).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 Sa();F(Ea({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 yt(),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;await Ue(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`):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 yt();if(t.activeMode===`service-account`){let n={mode:`service-account`,account:t.identity??null,source:Ca(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 p(n):t.tokens;if(n&&!r)throw new h(`AUTH_REQUIRED`,`Google account ${n} is not connected.`);if(r)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}):F(Ea({accountEmail:r.account_email,clientSource:r.client_source,expiresAt:r.expires_at,scope:r.scope}));else{if(O(e)){P({mode:`oauth`,account:null});return}process.stdout.write(`Not logged in.
34
34
  `)}}}),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 n=await yt(),r=C(e.account),i=r?await p(r):n.tokens;if(r&&!i&&n.activeMode!==`service-account`)throw new h(`AUTH_REQUIRED`,`Google account ${r} is not connected.`);let a=n.activeMode===`oauth`?await Pn():void 0;if(O(e)){P({authenticated:n.activeMode===`oauth`?!!i:n.serviceAccount.configured,mode:n.activeMode,identity:n.identity,account:n.activeMode===`oauth`?i?.account_email:void 0,scopes:n.activeMode===`oauth`?i?.scope:void 0,clientSource:n.activeMode===`oauth`?i?.client_source:void 0,expiresAt:n.activeMode===`oauth`?i?.expires_at:void 0,sharedConfigured:n.sharedConfigured,byoConfigured:n.byoConfigured,serviceAccount:{configured:n.serviceAccount.configured,identity:n.serviceAccount.identity,source:n.serviceAccount.source,error:n.serviceAccount.error},accounts:n.accounts,...a?{tokenStorage:a}:{}});return}if(n.activeMode===`service-account`){let e=[[`Mode`,`service account`],[`Identity`,n.identity??`unavailable`],[`Source`,Ca(n.serviceAccount.source)],[`Scopes`,`Search Console readonly, Google Analytics readonly`],[`Storage`,`credentials stay in the environment`]];n.serviceAccount.error&&e.push([`Status`,n.serviceAccount.error]),F(e);return}if(!i){let e=n.serviceAccount.error?n.serviceAccount.error:n.sharedConfigured?`Shared seo app available`:n.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 o=a??await Pn();F([[`Account`,i.account_email],[`Connected accounts`,String(n.accounts.length)],[`Scopes`,i.scope],[`Client`,Ta(i.client_source)],[`Expires`,Yr(i.expires_at)],[`Tokens file`,t().tokensFile],[`Token storage`,wa(o)],...o.reason?[[`Storage note`,o.reason]]:[],[`OAuth mode`,n.sharedConfigured?`SEO Skill app available`:n.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 h(`INVALID_INPUT`,`Choose either --keychain or --file, not both.`);let t=e.keychain||e.file?await je(e.keychain?`keychain`:`file`):await Pn();O(e)?P(t):F([[`Configured storage`,t.configured],[`Active storage`,wa(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 yt()).activeMode===`service-account`){process.stdout.write(`Service accounts request short-lived Google tokens automatically. No local refresh token is stored.
35
35
  `);return}let t=await Ln(C(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=Ke();O(e)?P({accounts:t}):t.length===0?process.stdout.write(`No Google accounts are connected.
@@ -58,7 +58,7 @@ Updates
58
58
  `),`Setup will`),b(ys.join(`
59
59
  `),`Then run`),x(`Dry run complete. Nothing was changed.`));return}Le();let n=N({json:t}),r=await ps({interactive:n})??await gs({interactive:n}),i=n?await vs():[],a=n?await _s():{status:`skipped`};if(t){P({auth:r,mcp:i,skill:a,next:ys});return}F([[`Sign-in`,xs(r)],[`MCP server`,Cs(i)],[`SEO skill`,Ss(a)]]);let o=i.filter(e=>e.error);o.length&&b(o.map(e=>`${e.client}: ${e.error}`).join(`
60
60
  `),`MCP setup skipped`),a.error&&b(a.error,`SEO skill install failed`),b(`seo sites list your Search Console properties
61
- seo report --site <site> run the main report for one of them`,`Try next`),x(`Setup complete.`),ws()}const Es=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})=>Ts(e)}),Ds=y({...Es,meta:{name:`start`,description:`Start here: sign in with Google, then add the MCP server and skill to your agents`}}),Os=y({...Ds,meta:{name:`init`,description:"Legacy alias for `seo start`."}}),ks=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=Jr();O(e)?P(t):e.quiet||process.stdout.write(`Rotated ${t.rotated} and removed ${t.removed} log files. ${qi(t.sizeBytes)} retained.\n`)}})}}),As=[{client:`claude-desktop`,flag:`claude-desktop`},{client:`claude-code`,flag:`claude-code`},{client:`codex`,flag:`codex`},{client:`cursor`,flag:`cursor`}];function js(e,t){return w(e.all)?t:As.filter(({flag:t})=>w(e[t])).map(({client:e})=>e)}const Ms=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-BnpsvtXl.js`);await t({test:w(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(w(e.uninstall)&&w(e.reinstall))throw new h(`INVALID_INPUT`,`Use either --uninstall or --reinstall, not both.`);let t=Ao(),n=No({targets:t}),r=O(e),i=js(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=z(await Ar({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)?Qo(t):Zo(t,{reinstall:w(e.reinstall)}));if(r)P({operation:w(e.uninstall)?`uninstall`:`install`,results:o});else for(let e of o)process.stdout.write(`${e.changed?`updated`:`skipped`} ${e.client} · ${e.path}\n`)}})}}),Ns=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=O(e),n=k(C(e.site)),r=ti(e.sitemaps),i=await Me({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)});t?P(i):(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(`
61
+ seo report --site <site> run the main report for one of them`,`Try next`),x(`Setup complete.`),ws()}const Es=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})=>Ts(e)}),Ds=y({...Es,meta:{name:`start`,description:`Start here: sign in with Google, then add the MCP server and skill to your agents`}}),Os=y({...Ds,meta:{name:`init`,description:"Legacy alias for `seo start`."}}),ks=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=Jr();O(e)?P(t):e.quiet||process.stdout.write(`Rotated ${t.rotated} and removed ${t.removed} log files. ${qi(t.sizeBytes)} retained.\n`)}})}}),As=[{client:`claude-desktop`,flag:`claude-desktop`},{client:`claude-code`,flag:`claude-code`},{client:`codex`,flag:`codex`},{client:`cursor`,flag:`cursor`}];function js(e,t){return w(e.all)?t:As.filter(({flag:t})=>w(e[t])).map(({client:e})=>e)}const Ms=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-DOJoZZ9W.js`);await t({test:w(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(w(e.uninstall)&&w(e.reinstall))throw new h(`INVALID_INPUT`,`Use either --uninstall or --reinstall, not both.`);let t=Ao(),n=No({targets:t}),r=O(e),i=js(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=z(await Ar({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)?Qo(t):Zo(t,{reinstall:w(e.reinstall)}));if(r)P({operation:w(e.uninstall)?`uninstall`:`install`,results:o});else for(let e of o)process.stdout.write(`${e.changed?`updated`:`skipped`} ${e.client} · ${e.path}\n`)}})}}),Ns=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=O(e),n=k(C(e.site)),r=ti(e.sitemaps),i=await Me({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)});t?P(i):(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(`
62
62
  Representative URL Inspection candidates
63
63
  `),I([`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))}}),Ps=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=O(e),n=await re({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`});t?P(n):(K({title:`Link recovery`,target:n.site,status:n.warnings.length>0?`unknown`:n.summary.recoverable>0?`warning`:`pass`,summary:n.summary.recoverable>0?`${U(n.summary.recoverable)} previously visible URLs need recovery review.`:`No recoverable URLs were found in the retained evidence.`,metrics:[{label:`Range`,value:`${n.range.startDate} to ${n.range.endDate}`},{label:`Checked`,value:U(n.summary.checked)},{label:`Recoverable`,value:U(n.summary.recoverable)},{label:`High severity`,value:U(n.summary.high)},{label:`Clicks at risk`,value:U(n.summary.clicksAtRisk)},{label:`Impressions at risk`,value:U(n.summary.impressionsAtRisk)}]}),n.items.length&&(q([`Severity`,`Issue`,`Clicks`,`Impr`,`URL`,`Action`],n.items.map(e=>[e.severity,e.issue,U(e.clicks),U(e.impressions),G(e.url,56),G(e.recommendation.action,72)])),J(`Top recovery actions`,n.items.map(e=>({label:G(e.url,96),context:`${e.severity}, ${U(e.clicks)} clicks at risk`,action:e.recommendation.action})))),Y(`Warnings`,n.warnings.slice(0,10)))}}),Fs=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 te({url:C(e.url)??``,maxHops:T(e[`max-hops`]),refresh:w(e.refresh),js:w(e.js)?!0:`auto`});O(e)?P(t):(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}]}),I([`#`,`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(`
64
64
  Final page