seo 0.2.37 → 0.2.38

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,34 +1,34 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire as __seoCreateRequire } from 'node:module'; const require = __seoCreateRequire(import.meta.url);
3
- import{$ as e,$n as t,$r as n,$t as r,A as i,An as a,Ar as o,At as s,B as c,Bn as l,Br as u,Bt as d,C as f,Cn as p,Cr as m,Ct as h,D as g,Dn as _,Dr as v,Dt as y,E as b,En as ee,Er as x,Et as te,F as ne,Fn as re,Fr as S,Ft as C,G as ie,Gn as ae,Gr as oe,Gt as se,H as ce,Hn as le,Hr as ue,Ht as de,I as fe,In as pe,Ir as me,It as he,J as ge,Jn as _e,Jr as ve,Jt as ye,K as be,Kn as xe,Kr as Se,Kt as Ce,L as we,Ln as Te,Lr as Ee,Lt as De,M as Oe,Mn as ke,Mr as Ae,Mt as je,N as Me,Nn as Ne,Nr as Pe,Nt as Fe,O as Ie,On as Le,Or as Re,Ot as ze,P as Be,Pn as Ve,Pr as He,Pt as Ue,Q as We,Qn as Ge,Qr as Ke,Qt as qe,R as Je,Rn as Ye,Rr as Xe,Rt as Ze,S as Qe,Sn as $e,Sr as et,St as tt,T as nt,Tn as rt,Tr as it,Tt as at,U as ot,Un as st,Ur as ct,Ut as lt,V as ut,Vn as dt,Vr as ft,Vt as pt,W as mt,Wn as ht,Wr as gt,Wt as _t,X as vt,Xn as yt,Xr as bt,Xt as xt,Y as St,Yn as Ct,Yr as wt,Yt as Tt,Z as Et,Zn as Dt,Zr as Ot,Zt as kt,_ as At,_n as jt,_r as Mt,_t as Nt,a as Pt,ai as Ft,an as It,ar as Lt,at as Rt,b as zt,bn as Bt,br as Vt,bt as Ht,ci as w,cn as Ut,cr as Wt,ct as Gt,d as Kt,dn as qt,dr as Jt,dt as Yt,ei as Xt,en as Zt,er as Qt,et as $t,f as en,fn as tn,fr as nn,ft as rn,g as an,gn as on,gr as sn,gt as cn,h as ln,hn as un,hr as dn,ht as fn,i as pn,ii as mn,in as hn,ir as gn,it as _n,j as vn,jn as yn,jr as bn,jt as xn,k as Sn,kn as Cn,kr as wn,kt as Tn,li as En,ln as Dn,lr as On,lt as kn,m as An,mn as jn,mr as Mn,mt as Nn,n as Pn,ni as Fn,nn as In,nr as Ln,nt as Rn,o as zn,oi as Bn,on as Vn,or as Hn,ot as Un,p as Wn,pn as Gn,pr as Kn,pt as qn,q as Jn,qn as Yn,qr as Xn,qt as Zn,ri as Qn,rn as $n,rr as er,rt as tr,s as nr,si as rr,sn as ir,sr as ar,st as or,t as sr,ti as cr,tn as lr,tr as ur,tt as dr,u as fr,ui as pr,un as mr,ur as hr,ut as gr,v as _r,vn as vr,vr as yr,vt as br,w as xr,wn as Sr,wr as Cr,wt as wr,x as Tr,xn as Er,xr as Dr,xt as Or,y as kr,yn as Ar,yr as jr,yt as Mr,z as Nr,zn as Pr,zr as Fr,zt as Ir}from"./dist-DcFi5w-G.js";import{createHash as Lr,randomUUID as Rr}from"node:crypto";import{constants as zr,copyFileSync as Br,createWriteStream as Vr,existsSync as Hr,mkdirSync as Ur,readFileSync as Wr,readdirSync as Gr,realpathSync as Kr,renameSync as qr,statSync as Jr,writeFileSync as Yr}from"node:fs";import{basename as Xr,dirname as Zr,extname as Qr,isAbsolute as $r,join as T,posix as ei,relative as ti,resolve as ni,win32 as ri}from"node:path";import{arch as ii,homedir as ai,platform as oi}from"node:os";import{lstat as si,mkdir as ci,mkdtemp as li,readFile as ui,readdir as di,rename as fi,rm as pi,stat as mi,unlink as hi,writeFile as gi}from"node:fs/promises";import{isDeepStrictEqual as _i,stripVTControlCharacters as vi}from"node:util";import{fileURLToPath as yi}from"node:url";import{spawn as bi,spawnSync as xi}from"node:child_process";import{defineCommand as E,runCommand as Si,runMain as Ci}from"citty";import{cancel as wi,confirm as Ti,intro as Ei,isCancel as Di,log as Oi,multiselect as ki,note as D,outro as Ai,password as ji,select as Mi,text as Ni}from"@clack/prompts";import Pi from"picocolors";import Fi from"string-width";import Ii from"cli-table3";import{applyEdits as Li,modify as Ri,parse as zi,printParseErrorCode as Bi}from"jsonc-parser";import{finished as Vi}from"node:stream/promises";import Hi from"update-notifier";function O(e){return e.replaceAll(`&`,`&amp;`).replaceAll(`<`,`&lt;`).replaceAll(`>`,`&gt;`).replaceAll(`"`,`&quot;`).replaceAll(`'`,`&#39;`)}function Ui(e){try{let t=new URL(e);return t.protocol===`http:`||t.protocol===`https:`?t.toString():void 0}catch{return}}function Wi(e){let t=Ui(e),n=O(e);return t?`<a href="${O(t)}" rel="noreferrer">${n}</a>`:n}function Gi(e){return e.length===0?`<p class="empty">No items to report.</p>`:`<ul>${e.map(e=>`<li>${O(e)}</li>`).join(``)}</ul>`}function Ki(e){return e.sections.map(e=>`<section class="panel">
4
- <h2>${O(e.title)}</h2>
5
- ${Gi(e.bullets)}
6
- </section>`).join(``)}function qi(e){return e.map(e=>`<section class="panel">
7
- <h2>${O(e.title)}</h2>
8
- ${e.summary?`<p>${O(e.summary)}</p>`:``}
9
- ${Gi(e.items)}
10
- </section>`).join(``)}function Ji(e){return e.priorities.length===0?`<p class="empty">No evidence-backed priorities were found.</p>`:`<ol class="priorities">${e.priorities.map(e=>`<li>
3
+ import{$ as e,$n as t,$r as n,$t as r,A as i,An as a,Ar as o,At as s,B as c,Bn as l,Br as u,Bt as d,C as f,Cn as p,Cr as m,Ct as h,D as g,Dn as _,Dr as v,Dt as y,E as b,En as ee,Er as x,Et as te,F as ne,Fn as re,Fr as ie,Ft as S,G as ae,Gn as oe,Gr as se,Gt as ce,H as le,Hn as ue,Hr as de,Ht as fe,I as pe,In as me,Ir as he,It as ge,J as _e,Jn as ve,Jr as ye,Jt as be,K as xe,Kn as Se,Kr as Ce,Kt as we,L as Te,Ln as Ee,Lr as De,Lt as Oe,M as ke,Mn as Ae,Mr as je,Mt as Me,N as Ne,Nn as Pe,Nr as Fe,Nt as Ie,O as Le,On as Re,Or as ze,Ot as Be,P as Ve,Pn as He,Pr as Ue,Pt as We,Q as Ge,Qn as Ke,Qr as qe,Qt as Je,R as Ye,Rn as Xe,Rr as Ze,Rt as Qe,S as $e,Sn as et,Sr as tt,St as nt,T as rt,Tn as it,Tr as at,Tt as ot,U as st,Un as ct,Ur as lt,Ut as ut,V as dt,Vn as ft,Vr as pt,Vt as mt,W as ht,Wn as gt,Wr as _t,Wt as vt,X as yt,Xn as bt,Xr as xt,Xt as St,Y as Ct,Yn as wt,Yr as Tt,Yt as Et,Z as Dt,Zn as Ot,Zr as kt,Zt as At,_ as jt,_n as Mt,_r as Nt,_t as Pt,a as Ft,ai as It,an as Lt,ar as Rt,at as zt,b as Bt,bn as Vt,br as Ht,bt as Ut,ci as C,cn as Wt,cr as Gt,ct as Kt,d as qt,dn as Jt,dr as Yt,dt as Xt,ei as Zt,en as Qt,er as $t,et as en,f as tn,fn as nn,fr as rn,ft as an,g as on,gn as sn,gr as cn,gt as ln,h as un,hn as dn,hr as fn,ht as pn,i as mn,ii as hn,in as gn,ir as _n,it as vn,j as yn,jn as bn,jr as xn,jt as Sn,k as Cn,kn as wn,kr as Tn,kt as En,li as Dn,ln as On,lr as kn,lt as An,m as jn,mn as Mn,mr as Nn,mt as Pn,n as Fn,ni as In,nn as Ln,nr as Rn,nt as zn,o as Bn,oi as Vn,on as Hn,or as Un,ot as Wn,p as Gn,pn as Kn,pr as qn,pt as Jn,q as Yn,qn as Xn,qr as Zn,qt as Qn,ri as $n,rn as er,rr as tr,rt as nr,s as rr,si as ir,sn as ar,sr as or,st as sr,t as cr,ti as lr,tn as ur,tr as dr,tt as fr,u as pr,ui as mr,un as hr,ur as gr,ut as _r,v as vr,vn as yr,vr as br,vt as xr,w as Sr,wn as Cr,wr,wt as Tr,x as Er,xn as Dr,xr as Or,xt as kr,y as Ar,yn as jr,yr as Mr,yt as Nr,z as Pr,zn as Fr,zr as Ir,zt as Lr}from"./dist-CI75G5N6.js";import{createHash as Rr,randomUUID as zr}from"node:crypto";import{constants as Br,copyFileSync as Vr,createWriteStream as Hr,existsSync as Ur,mkdirSync as Wr,readFileSync as Gr,readdirSync as Kr,realpathSync as qr,renameSync as Jr,statSync as Yr,writeFileSync as Xr}from"node:fs";import{basename as Zr,dirname as Qr,extname as $r,isAbsolute as ei,join as w,posix as ti,relative as ni,resolve as ri,win32 as ii}from"node:path";import{arch as ai,homedir as oi,platform as si}from"node:os";import{lstat as ci,mkdir as li,mkdtemp as ui,readFile as di,readdir as fi,rename as pi,rm as mi,stat as hi,unlink as gi,writeFile as _i}from"node:fs/promises";import{isDeepStrictEqual as vi,stripVTControlCharacters as yi}from"node:util";import{fileURLToPath as bi}from"node:url";import{spawn as xi,spawnSync as Si}from"node:child_process";import{defineCommand as T,runCommand as Ci,runMain as wi}from"citty";import{cancel as Ti,confirm as Ei,intro as Di,isCancel as Oi,log as ki,multiselect as Ai,note as E,outro as ji,password as Mi,select as Ni,text as Pi}from"@clack/prompts";import Fi from"picocolors";import Ii from"string-width";import Li from"cli-table3";import{applyEdits as Ri,modify as zi,parse as Bi,printParseErrorCode as Vi}from"jsonc-parser";import{finished as Hi}from"node:stream/promises";import Ui from"update-notifier";function D(e){return e.replaceAll(`&`,`&amp;`).replaceAll(`<`,`&lt;`).replaceAll(`>`,`&gt;`).replaceAll(`"`,`&quot;`).replaceAll(`'`,`&#39;`)}function Wi(e){try{let t=new URL(e);return t.protocol===`http:`||t.protocol===`https:`?t.toString():void 0}catch{return}}function Gi(e){let t=Wi(e),n=D(e);return t?`<a href="${D(t)}" rel="noreferrer">${n}</a>`:n}function Ki(e){return e.length===0?`<p class="empty">No items to report.</p>`:`<ul>${e.map(e=>`<li>${D(e)}</li>`).join(``)}</ul>`}function qi(e){return e.sections.map(e=>`<section class="panel">
4
+ <h2>${D(e.title)}</h2>
5
+ ${Ki(e.bullets)}
6
+ </section>`).join(``)}function Ji(e){return e.map(e=>`<section class="panel">
7
+ <h2>${D(e.title)}</h2>
8
+ ${e.summary?`<p>${D(e.summary)}</p>`:``}
9
+ ${Ki(e.items)}
10
+ </section>`).join(``)}function Yi(e){return e.priorities.length===0?`<p class="empty">No evidence-backed priorities were found.</p>`:`<ol class="priorities">${e.priorities.map(e=>`<li>
11
11
  <div class="priority-heading">
12
- <strong>${O(e.title)}</strong>
13
- <span class="confidence">${O(e.confidence)} confidence</span>
12
+ <strong>${D(e.title)}</strong>
13
+ <span class="confidence">${D(e.confidence)} confidence</span>
14
14
  </div>
15
- <p>${O(e.action)}</p>
16
- </li>`).join(``)}</ol>`}function Yi(e){let t=e.diagnosis.skippedSections??[],n=e.changeMeasurementAttempts;return`<section class="panel analyst-only">
15
+ <p>${D(e.action)}</p>
16
+ </li>`).join(``)}</ol>`}function Xi(e){let t=e.diagnosis.skippedSections??[],n=e.changeMeasurementAttempts;return`<section class="panel analyst-only">
17
17
  <h2>Evidence coverage</h2>
18
18
  <dl class="facts">
19
- <div><dt>Data status</dt><dd>${O(e.dataStatus)}</dd></div>
20
- <div><dt>Generated</dt><dd>${O(e.generatedAt)}</dd></div>
19
+ <div><dt>Data status</dt><dd>${D(e.dataStatus)}</dd></div>
20
+ <div><dt>Generated</dt><dd>${D(e.generatedAt)}</dd></div>
21
21
  <div><dt>Saved changes checked</dt><dd>${n.length}</dd></div>
22
22
  <div><dt>Unavailable sections</dt><dd>${t.length}</dd></div>
23
23
  </dl>
24
- ${t.length?`<h3>Unavailable sections</h3>${Gi(t.map(e=>`reason`in e&&typeof e.reason==`string`?`${e.section}: ${e.reason}`:e.section))}`:``}
25
- </section>`}function Xi(e){let t=e.report,n=e.view??`client`,r=e.title??`SEO report`,i=t.dataStatus===`complete`?``:` open`,a=n===`analyst`?Yi(t):``;return`<!doctype html>
24
+ ${t.length?`<h3>Unavailable sections</h3>${Ki(t.map(e=>`reason`in e&&typeof e.reason==`string`?`${e.section}: ${e.reason}`:e.section))}`:``}
25
+ </section>`}function Zi(e){let t=e.report,n=e.view??`client`,r=e.title??`SEO report`,i=t.dataStatus===`complete`?``:` open`,a=n===`analyst`?Xi(t):``;return`<!doctype html>
26
26
  <html lang="en">
27
27
  <head>
28
28
  <meta charset="utf-8">
29
29
  <meta name="viewport" content="width=device-width, initial-scale=1">
30
30
  <meta name="robots" content="noindex,nofollow">
31
- <title>${O(r)}: ${O(t.site)}</title>
31
+ <title>${D(r)}: ${D(t.site)}</title>
32
32
  <style>
33
33
  :root { color-scheme: light; font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; color: #17211b; background: #f2f4ef; }
34
34
  * { box-sizing: border-box; }
@@ -69,221 +69,221 @@ import{$ as e,$n as t,$r as n,$t as r,A as i,An as a,Ar as o,At as s,B as c,Bn a
69
69
  <body>
70
70
  <main>
71
71
  <header>
72
- <p class="eyebrow">${O(r)}</p>
73
- <h1>${Wi(t.site)}</h1>
74
- <p class="headline">${O(t.headline)}</p>
72
+ <p class="eyebrow">${D(r)}</p>
73
+ <h1>${Gi(t.site)}</h1>
74
+ <p class="headline">${D(t.headline)}</p>
75
75
  <div class="meta">
76
- <span>${O(t.period.startDate)} to ${O(t.period.endDate)}</span>
77
- <span>Generated ${O(t.generatedAt.slice(0,10))}</span>
78
- <span>${O(n)} view</span>
76
+ <span>${D(t.period.startDate)} to ${D(t.period.endDate)}</span>
77
+ <span>Generated ${D(t.generatedAt.slice(0,10))}</span>
78
+ <span>${D(n)} view</span>
79
79
  </div>
80
- <span class="status">${O(t.dataStatus)} data</span>
80
+ <span class="status">${D(t.dataStatus)} data</span>
81
81
  </header>
82
82
 
83
83
  <section class="panel">
84
84
  <h2>Priorities</h2>
85
- ${Ji(t)}
85
+ ${Yi(t)}
86
86
  </section>
87
87
 
88
- ${Ki(t)}
89
- ${qi(e.additionalSections??[])}
88
+ ${qi(t)}
89
+ ${Ji(e.additionalSections??[])}
90
90
  ${a}
91
91
 
92
92
  <section class="panel">
93
93
  <details${i}>
94
94
  <summary>Evidence notes and limitations</summary>
95
- ${Gi(t.caveats)}
95
+ ${Ki(t.caveats)}
96
96
  </details>
97
97
  </section>
98
98
 
99
99
  <footer>Generated locally from observed report evidence.</footer>
100
100
  </main>
101
101
  </body>
102
- </html>`}const Zi={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 Qi(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 $i(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 ea(e){return[{filename:`postmortem-summary.csv`,headers:[...Zi.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:[...Zi.steps],rows:e.steps.map((e,t)=>({rank:t+1,tool:e.tool,status:e.status,summary:e.summary}))},{filename:`workflow-actions.csv`,headers:[...Zi.actions],rows:e.actions.map((e,t)=>({rank:t+1,title:e.title,confidence:e.confidence,action:e.action}))},{filename:`postmortem-findings.csv`,headers:[...Zi.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:[...Zi.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:[...Zi.evidence],rows:e.output.update.evidence.map((e,t)=>({rank:t+1,evidence:e}))},{filename:`postmortem-confounders.csv`,headers:[...Zi.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:[...Zi.segment],rows:Qi(e,t)})),...[`page`,`query`,`device`,`country`].map(t=>({filename:`postmortem-segment-${t}-unmatched.csv`,headers:[...Zi.unmatchedSegment],rows:$i(e,t)}))]}function ta(e){try{let t=new URL(e);return t.protocol!==`http:`&&t.protocol!==`https:`?void 0:t.hostname.toLowerCase().replace(/\.$/,``)||void 0}catch{return}}function na(e){if(!e.startsWith(`sc-domain:`))return;let t=e.slice(10).trim().toLowerCase();if(!(!t||/[/:]/.test(t)))return t.replace(/\.$/,``)||void 0}function ra(e,t){let n=na(e);return n?t===n||t.endsWith(`.${n}`)?`domain`:void 0:ta(e)===t?`hostname`:void 0}function ia(e,t){return t.flatMap(t=>{let n=t.stream.webStreamData?.defaultUri;if(!n)return[];let r=ta(n);if(!r)return[];let i=ra(e,r);return i?[{...t,defaultUri:n,hostname:r,match:i}]:[]})}function aa(e,t){let n=e.stream.displayName??e.defaultUri,r=e.match===`domain`?`is inside`:`matches`;return`Matched Google Analytics web stream ${n} (${e.defaultUri}): its hostname ${r} ${t}.`}const oa=`Search Console export`,sa=new Set([`top queries`,`query`,`queries`]),ca=new Set([`top pages`,`page`,`pages`,`url`]),la=[`clicks`,`url clicks`],ua=[`impressions`],da=[`ctr`,`url ctr`,`site ctr`],fa=[`position`,`average position`,`avg position`,`avg. position`],pa=[`Query and page tables are separate aggregates from a Search Console export; no query-to-page mapping exists and none was created.`,`Search Console exports are partial: anonymised queries are withheld and export row caps apply, so missing rows are not zeros.`];function ma(e){let t=e??5e3;if(!Number.isSafeInteger(t)||t<1||t>1e5)throw new w(`INVALID_INPUT`,`Search Console export row limit must be between 1 and 100000.`);return t}async function ha(e){let t=ni(e),n=await mi(t).catch(()=>void 0);if(!n)throw new w(`INVALID_INPUT`,`Search Console export path was not found: ${t}`);if(n.isFile())return[t];if(!n.isDirectory())throw new w(`INVALID_INPUT`,`Search Console export path must be a CSV file or a directory: ${t}`);let r=(await di(t,{withFileTypes:!0})).filter(e=>{if(!e.isFile())return!1;let t=Qr(e.name).toLowerCase();return t===`.csv`||t===`.tsv`}).map(e=>T(t,e.name)).sort(Sa);if(r.length===0)throw new w(`INVALID_INPUT`,`Search Console export directory contains no CSV files: ${t}`);if(r.length>20)throw new w(`INVALID_INPUT`,`Search Console export directories can contain at most 20 CSV files.`);return r}function ga(e){return e.replace(/^\uFEFF/u,``).trim().toLowerCase()}function _a(e,t){for(let n of t){let t=e.indexOf(n);if(t>0)return t}return null}function va(e){let t=(e??``).trim();if(!t||!/^\d+(?:,\d{3})*$/u.test(t))return null;let n=Number(t.replaceAll(`,`,``));return Number.isSafeInteger(n)?n:null}function ya(e){let t=(e??``).trim();if(!t)return null;if(t.endsWith(`%`)){let e=Number(t.slice(0,-1).trim());if(!Number.isFinite(e))return null;let n=e/100;return n>=0&&n<=1?n:null}let n=Number(t);return Number.isFinite(n)&&n>=0&&n<=1?n:null}function ba(e){let t=(e??``).trim();if(!t)return null;let n=Number(t);return Number.isFinite(n)&&n>0?n:null}function xa(e){let t;try{t=new URL(e)}catch{return null}return t.protocol===`http:`||t.protocol===`https:`?e:null}function Sa(e,t){return e<t?-1:+(e>t)}function Ca(e,t){return e.impressions===t.impressions?e.clicks>t.clicks:e.impressions>t.impressions}function wa(e,t){return t.clicks-e.clicks||t.impressions-e.impressions||Sa(e.query,t.query)}function Ta(e,t){return t.impressions-e.impressions||t.clicks-e.clicks||Sa(e.url,t.url)}function Ea(e,t,n){let r=[...e.byKey.values()].sort(t),i=e.capped||r.length>n;return{rows:r.slice(0,n),totalRows:r.length,capped:i}}async function Da(e){let t=ma(e.rowLimit),n=await ha(e.path),r=[],i=[],a={byKey:new Map,suppliedRows:0,capped:!1},o={byKey:new Map,suppliedRows:0,capped:!1};for(let e of n){let n=await Mt({file:e,format:`csv`,label:oa}),s=Lr(`sha256`),c={bytesRead:0,hash:s},l=sn({path:n.path,byteLimit:dn,progress:c,label:oa}),u=await l.next(),d=(Array.isArray(u.value)?u.value:[]).map((e,t)=>(t===0?e.replace(/^\uFEFF/u,``):e).trim()),f=d.map(ga),p=f[0]??``,m=sa.has(p)?`queries`:ca.has(p)?`pages`:`unrecognized`,h=m===`unrecognized`?d.length===0?`The file has no header row.`:`The first header "${d[0]}" is not a recognized Search Console query or page column.`:void 0,g=_a(f,la),_=_a(f,ua),v=_a(f,da),y=_a(f,fa),b=[...g===null?[`Clicks`]:[],..._===null?[`Impressions`]:[]],ee=h??(b.length?`The recognized ${m} table is missing required ${b.join(` and `)} columns.`:void 0),x=m!==`unrecognized`&&ee===void 0,te=0,ne=0,re=0,S=0,C=0,ie=m===`queries`?a:o;for await(let e of l){if(te+=1,!x||ie.suppliedRows>=t)continue;if(ne+=1,ie.suppliedRows+=1,e.length!==d.length){S+=1;continue}let n=va(e[g??-1]),r=va(e[_??-1]);if(n===null||r===null){S+=1;continue}let i=v===null?``:(e[v]??``).trim(),s=y===null?``:(e[y]??``).trim(),c=v===null?null:ya(i),l=y===null?null:ba(s);if(i&&c===null||s&&l===null){S+=1;continue}let u={clicks:n,impressions:r,ctr:c,position:l},f=(e[0]??``).trim();if(m===`queries`){if(!f){S+=1;continue}let e=a.byKey.get(f);if(e){C+=1,Ca(u,e)&&a.byKey.set(f,{query:f,...u});continue}re+=1,a.byKey.set(f,{query:f,...u});continue}let p=xa(f);if(!p){S+=1;continue}let h=o.byKey.get(p);if(h){C+=1,Ca(u,h)&&o.byKey.set(p,{url:p,...u});continue}re+=1,o.byKey.set(p,{url:p,...u})}let ae=x&&te>ne;ae&&(m===`queries`?a.capped=!0:o.capped=!0,i.push(`Only ${ne} of ${te} rows in ${n.path} were processed within the shared ${m} row limit of ${t}.`)),ee&&i.push(`Skipped ${n.path}: ${ee??`the file was not recognized.`}`),S>0&&i.push(`${S} row${S===1?``:`s`} in ${n.path} ${S===1?`was`:`were`} invalid and skipped.`),r.push({path:n.path,table:m,sha256:s.digest(`hex`),fileBytes:n.fileBytes,bytesRead:c.bytesRead,encoding:c.encoding??`utf-8`,delimiter:c.delimiter??null,includedFields:[...d].sort(Sa),fileRows:te,suppliedRows:ne,validRows:re,invalidRows:S,duplicateRows:C,capped:ae,rowLimit:t,...ee===void 0?{}:{reason:ee}})}return r.every(e=>e.table===`unrecognized`)&&i.push(`No file matched the Search Console query or page table headers, so no rows were imported.`),{source:`search-console-export`,exportedAt:e.exportedAt?.toISOString()??null,importedAt:(e.now??new Date).toISOString(),files:r,queries:Ea(a,wa,t),pages:Ea(o,Ta,t),warnings:i,caveats:[...pa,e.exportedAt?`exportedAt was supplied by the caller and is not verified from the file; importedAt records when the local files were loaded.`:`The export time is unavailable unless supplied separately; importedAt records when the local files were loaded, not when Search Console produced them.`]}}function Oa(e){let t=e??100;if(!Number.isSafeInteger(t)||t<1)throw new w(`INVALID_INPUT`,`Unreached-page limit must be a positive integer.`);return t}function ka(e){let t;try{t=new URL(e)}catch{return null}return`${t.pathname!==`/`&&t.pathname.endsWith(`/`)?t.pathname.slice(0,-1):t.pathname}${t.search}`}function Aa(e,t){return e<t?-1:+(e>t)}function ja(e,t){return t.impressions-e.impressions||t.clicks-e.clicks||Aa(e.url,t.url)}function Ma(e){let t=Oa(e.limit),n=new Set;for(let t of e.crawledUrls){let e=ka(t);e!==null&&n.add(e)}let r=new Set,i=0,a=[];for(let t of e.pages){let e;try{e=new URL(t.url)}catch{continue}r.add(e.origin);let o=ka(t.url);o!==null&&n.has(o)?i+=1:a.push(t)}a.sort(ja);let o=[...r].sort(Aa);return{joinBasis:`path`,crawlOrigin:e.crawlOrigin,exportOrigins:o,originMismatch:o.some(t=>t!==e.crawlOrigin),matchedPages:i,unreachedPages:a.slice(0,t),unreachedCount:a.length,capped:a.length>t}}function Na(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 Pa(e,t){return{type:`boolean`,default:!0,description:e,negativeDescription:t}}function k(e){return typeof e==`string`?e:void 0}function A(e){let t=k(e.project),n=k(e.client);if(t&&n&&t!==n)throw Error(`Use either --project or --client, not both.`);return t??n}function j(e){return typeof e==`boolean`?e:void 0}function Fa(e){let t=k(e.rendering);if(t!==void 0&&t!==`auto`&&t!==`on`&&t!==`off`)throw new w(`INVALID_INPUT`,`--rendering must be auto, on, or off.`);if(j(e.js)===!0&&t&&t!==`on`)throw new w(`INVALID_INPUT`,`Use either --js or --rendering on, not both with different modes.`);return t??(j(e.js)?`on`:`auto`)}function Ia(e,t){let n=j(e[t]);if(n===!1)return!0;if(n===!0)return!1;let r=j(e[La(t)]);if(r===!1)return!0;if(r===!0)return!1;if(j(e[`no-${t}`])===!0||j(e[`no${Ra(t)}`])===!0)return!0}function La(e){return e.replace(/-([a-z])/g,(e,t)=>t.toUpperCase())}function Ra(e){let t=La(e);return t?`${t[0]?.toUpperCase()}${t.slice(1)}`:``}function M(e){if(typeof e==`number`)return e;if(typeof e!=`string`||!e.trim())return;let t=Number(e);return Number.isFinite(t)?t:void 0}function N(e,t){if(e===void 0||e===``)return;let n=M(e);if(n===void 0)throw new w(`INVALID_INPUT`,`${t} must be a number.`);return n}function P(e){if(typeof e!=`string`)return;let t=e.split(`,`).map(e=>e.trim()).filter(Boolean);return t.length?t:void 0}function F(e){return P(e)??[]}function za(e){let t={concurrency:M(e[`fetch-concurrency`]),intervalCap:M(e[`fetch-interval-cap`]),intervalMs:M(e[`fetch-interval-ms`])};return Object.values(t).some(e=>e!==void 0)?t:void 0}function Ba(e){let t={concurrency:N(e[`fetch-concurrency`],`--fetch-concurrency`),intervalCap:N(e[`fetch-interval-cap`],`--fetch-interval-cap`),intervalMs:N(e[`fetch-interval-ms`],`--fetch-interval-ms`)};return Object.values(t).some(e=>e!==void 0)?t:void 0}function I(e){return e.json===!0}async function Va(e,t){let n=k(e),r=k(t);if(n&&r)throw Error(`Use either --body or --body-file, not both.`);if(!n&&!r)return;let i=n??await ui(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 Ha(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 Ua(e={}){let t=e.env??process.env,n=Ha(e.columns)??Ha(e.streamColumns)??Ha(t.COLUMNS)??80;return Math.min(200,Math.max(40,n))}function Wa(e={}){let t=e.env??process.env,n=e.isTTY??!!process.stdout.isTTY,r=e.color??!!(n&&t.NO_COLOR===void 0);return{columns:Ua({...e,env:t,streamColumns:e.streamColumns??process.stdout.columns}),colors:Pi.createColors(r),hasColor:r,isInteractive:n&&!t.CI}}function Ga(e){return Fi(vi(e))}function Ka(e,t){let n=[],r=``;for(let i of e)r&&Ga(`${r}${i}`)>t?(n.push(r),r=i):r+=i;return r&&n.push(r),n}function qa(e,t){if(!e)return[``];let n=e.trim().split(/\s+/u),r=[],i=``;for(let e of n){let n=Ga(e)>t?Ka(e,t):[e];for(let e of n)i?Ga(`${i} ${e}`)<=t?i+=` ${e}`:(r.push(i),i=e):i=e}return i&&r.push(i),r.length>0?r:[``]}function L(e,t){let n=Math.max(1,t);return e.replaceAll(`\r
102
+ </html>`}const Qi={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 $i(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 ea(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 ta(e){return[{filename:`postmortem-summary.csv`,headers:[...Qi.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:[...Qi.steps],rows:e.steps.map((e,t)=>({rank:t+1,tool:e.tool,status:e.status,summary:e.summary}))},{filename:`workflow-actions.csv`,headers:[...Qi.actions],rows:e.actions.map((e,t)=>({rank:t+1,title:e.title,confidence:e.confidence,action:e.action}))},{filename:`postmortem-findings.csv`,headers:[...Qi.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:[...Qi.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:[...Qi.evidence],rows:e.output.update.evidence.map((e,t)=>({rank:t+1,evidence:e}))},{filename:`postmortem-confounders.csv`,headers:[...Qi.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:[...Qi.segment],rows:$i(e,t)})),...[`page`,`query`,`device`,`country`].map(t=>({filename:`postmortem-segment-${t}-unmatched.csv`,headers:[...Qi.unmatchedSegment],rows:ea(e,t)}))]}function na(e){try{let t=new URL(e);return t.protocol!==`http:`&&t.protocol!==`https:`?void 0:t.hostname.toLowerCase().replace(/\.$/,``)||void 0}catch{return}}function ra(e){if(!e.startsWith(`sc-domain:`))return;let t=e.slice(10).trim().toLowerCase();if(!(!t||/[/:]/.test(t)))return t.replace(/\.$/,``)||void 0}function ia(e,t){let n=ra(e);return n?t===n||t.endsWith(`.${n}`)?`domain`:void 0:na(e)===t?`hostname`:void 0}function aa(e,t){return t.flatMap(t=>{let n=t.stream.webStreamData?.defaultUri;if(!n)return[];let r=na(n);if(!r)return[];let i=ia(e,r);return i?[{...t,defaultUri:n,hostname:r,match:i}]:[]})}function oa(e,t){let n=e.stream.displayName??e.defaultUri,r=e.match===`domain`?`is inside`:`matches`;return`Matched Google Analytics web stream ${n} (${e.defaultUri}): its hostname ${r} ${t}.`}const sa=`Search Console export`,ca=new Set([`top queries`,`query`,`queries`]),la=new Set([`top pages`,`page`,`pages`,`url`]),ua=[`clicks`,`url clicks`],da=[`impressions`],fa=[`ctr`,`url ctr`,`site ctr`],pa=[`position`,`average position`,`avg position`,`avg. position`],ma=[`Query and page tables are separate aggregates from a Search Console export; no query-to-page mapping exists and none was created.`,`Search Console exports are partial: anonymised queries are withheld and export row caps apply, so missing rows are not zeros.`];function ha(e){let t=e??5e3;if(!Number.isSafeInteger(t)||t<1||t>1e5)throw new C(`INVALID_INPUT`,`Search Console export row limit must be between 1 and 100000.`);return t}async function ga(e){let t=ri(e),n=await hi(t).catch(()=>void 0);if(!n)throw new C(`INVALID_INPUT`,`Search Console export path was not found: ${t}`);if(n.isFile())return[t];if(!n.isDirectory())throw new C(`INVALID_INPUT`,`Search Console export path must be a CSV file or a directory: ${t}`);let r=(await fi(t,{withFileTypes:!0})).filter(e=>{if(!e.isFile())return!1;let t=$r(e.name).toLowerCase();return t===`.csv`||t===`.tsv`}).map(e=>w(t,e.name)).sort(Ca);if(r.length===0)throw new C(`INVALID_INPUT`,`Search Console export directory contains no CSV files: ${t}`);if(r.length>20)throw new C(`INVALID_INPUT`,`Search Console export directories can contain at most 20 CSV files.`);return r}function _a(e){return e.replace(/^\uFEFF/u,``).trim().toLowerCase()}function va(e,t){for(let n of t){let t=e.indexOf(n);if(t>0)return t}return null}function ya(e){let t=(e??``).trim();if(!t||!/^\d+(?:,\d{3})*$/u.test(t))return null;let n=Number(t.replaceAll(`,`,``));return Number.isSafeInteger(n)?n:null}function ba(e){let t=(e??``).trim();if(!t)return null;if(t.endsWith(`%`)){let e=Number(t.slice(0,-1).trim());if(!Number.isFinite(e))return null;let n=e/100;return n>=0&&n<=1?n:null}let n=Number(t);return Number.isFinite(n)&&n>=0&&n<=1?n:null}function xa(e){let t=(e??``).trim();if(!t)return null;let n=Number(t);return Number.isFinite(n)&&n>0?n:null}function Sa(e){let t;try{t=new URL(e)}catch{return null}return t.protocol===`http:`||t.protocol===`https:`?e:null}function Ca(e,t){return e<t?-1:+(e>t)}function wa(e,t){return e.impressions===t.impressions?e.clicks>t.clicks:e.impressions>t.impressions}function Ta(e,t){return t.clicks-e.clicks||t.impressions-e.impressions||Ca(e.query,t.query)}function Ea(e,t){return t.impressions-e.impressions||t.clicks-e.clicks||Ca(e.url,t.url)}function Da(e,t,n){let r=[...e.byKey.values()].sort(t),i=e.capped||r.length>n;return{rows:r.slice(0,n),totalRows:r.length,capped:i}}async function Oa(e){let t=ha(e.rowLimit),n=await ga(e.path),r=[],i=[],a={byKey:new Map,suppliedRows:0,capped:!1},o={byKey:new Map,suppliedRows:0,capped:!1};for(let e of n){let n=await Nt({file:e,format:`csv`,label:sa}),s=Rr(`sha256`),c={bytesRead:0,hash:s},l=cn({path:n.path,byteLimit:fn,progress:c,label:sa}),u=await l.next(),d=(Array.isArray(u.value)?u.value:[]).map((e,t)=>(t===0?e.replace(/^\uFEFF/u,``):e).trim()),f=d.map(_a),p=f[0]??``,m=ca.has(p)?`queries`:la.has(p)?`pages`:`unrecognized`,h=m===`unrecognized`?d.length===0?`The file has no header row.`:`The first header "${d[0]}" is not a recognized Search Console query or page column.`:void 0,g=va(f,ua),_=va(f,da),v=va(f,fa),y=va(f,pa),b=[...g===null?[`Clicks`]:[],..._===null?[`Impressions`]:[]],ee=h??(b.length?`The recognized ${m} table is missing required ${b.join(` and `)} columns.`:void 0),x=m!==`unrecognized`&&ee===void 0,te=0,ne=0,re=0,ie=0,S=0,ae=m===`queries`?a:o;for await(let e of l){if(te+=1,!x||ae.suppliedRows>=t)continue;if(ne+=1,ae.suppliedRows+=1,e.length!==d.length){ie+=1;continue}let n=ya(e[g??-1]),r=ya(e[_??-1]);if(n===null||r===null){ie+=1;continue}let i=v===null?``:(e[v]??``).trim(),s=y===null?``:(e[y]??``).trim(),c=v===null?null:ba(i),l=y===null?null:xa(s);if(i&&c===null||s&&l===null){ie+=1;continue}let u={clicks:n,impressions:r,ctr:c,position:l},f=(e[0]??``).trim();if(m===`queries`){if(!f){ie+=1;continue}let e=a.byKey.get(f);if(e){S+=1,wa(u,e)&&a.byKey.set(f,{query:f,...u});continue}re+=1,a.byKey.set(f,{query:f,...u});continue}let p=Sa(f);if(!p){ie+=1;continue}let h=o.byKey.get(p);if(h){S+=1,wa(u,h)&&o.byKey.set(p,{url:p,...u});continue}re+=1,o.byKey.set(p,{url:p,...u})}let oe=x&&te>ne;oe&&(m===`queries`?a.capped=!0:o.capped=!0,i.push(`Only ${ne} of ${te} rows in ${n.path} were processed within the shared ${m} row limit of ${t}.`)),ee&&i.push(`Skipped ${n.path}: ${ee??`the file was not recognized.`}`),ie>0&&i.push(`${ie} row${ie===1?``:`s`} in ${n.path} ${ie===1?`was`:`were`} invalid and skipped.`),r.push({path:n.path,table:m,sha256:s.digest(`hex`),fileBytes:n.fileBytes,bytesRead:c.bytesRead,encoding:c.encoding??`utf-8`,delimiter:c.delimiter??null,includedFields:[...d].sort(Ca),fileRows:te,suppliedRows:ne,validRows:re,invalidRows:ie,duplicateRows:S,capped:oe,rowLimit:t,...ee===void 0?{}:{reason:ee}})}return r.every(e=>e.table===`unrecognized`)&&i.push(`No file matched the Search Console query or page table headers, so no rows were imported.`),{source:`search-console-export`,exportedAt:e.exportedAt?.toISOString()??null,importedAt:(e.now??new Date).toISOString(),files:r,queries:Da(a,Ta,t),pages:Da(o,Ea,t),warnings:i,caveats:[...ma,e.exportedAt?`exportedAt was supplied by the caller and is not verified from the file; importedAt records when the local files were loaded.`:`The export time is unavailable unless supplied separately; importedAt records when the local files were loaded, not when Search Console produced them.`]}}function ka(e){let t=e??100;if(!Number.isSafeInteger(t)||t<1)throw new C(`INVALID_INPUT`,`Unreached-page limit must be a positive integer.`);return t}function Aa(e){let t;try{t=new URL(e)}catch{return null}return`${t.pathname!==`/`&&t.pathname.endsWith(`/`)?t.pathname.slice(0,-1):t.pathname}${t.search}`}function ja(e,t){return e<t?-1:+(e>t)}function Ma(e,t){return t.impressions-e.impressions||t.clicks-e.clicks||ja(e.url,t.url)}function Na(e){let t=ka(e.limit),n=new Set;for(let t of e.crawledUrls){let e=Aa(t);e!==null&&n.add(e)}let r=new Set,i=0,a=[];for(let t of e.pages){let e;try{e=new URL(t.url)}catch{continue}r.add(e.origin);let o=Aa(t.url);o!==null&&n.has(o)?i+=1:a.push(t)}a.sort(Ma);let o=[...r].sort(ja);return{joinBasis:`path`,crawlOrigin:e.crawlOrigin,exportOrigins:o,originMismatch:o.some(t=>t!==e.crawlOrigin),matchedPages:i,unreachedPages:a.slice(0,t),unreachedCount:a.length,capped:a.length>t}}function Pa(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 Fa(e,t){return{type:`boolean`,default:!0,description:e,negativeDescription:t}}function O(e){return typeof e==`string`?e:void 0}function k(e){let t=O(e.project),n=O(e.client);if(t&&n&&t!==n)throw Error(`Use either --project or --client, not both.`);return t??n}function A(e){return typeof e==`boolean`?e:void 0}function Ia(e){let t=O(e.rendering);if(t!==void 0&&t!==`auto`&&t!==`on`&&t!==`off`)throw new C(`INVALID_INPUT`,`--rendering must be auto, on, or off.`);if(A(e.js)===!0&&t&&t!==`on`)throw new C(`INVALID_INPUT`,`Use either --js or --rendering on, not both with different modes.`);return t??(A(e.js)?`on`:`auto`)}function La(e,t){let n=A(e[t]);if(n===!1)return!0;if(n===!0)return!1;let r=A(e[Ra(t)]);if(r===!1)return!0;if(r===!0)return!1;if(A(e[`no-${t}`])===!0||A(e[`no${za(t)}`])===!0)return!0}function Ra(e){return e.replace(/-([a-z])/g,(e,t)=>t.toUpperCase())}function za(e){let t=Ra(e);return t?`${t[0]?.toUpperCase()}${t.slice(1)}`:``}function j(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 M(e,t){if(e===void 0||e===``)return;let n=j(e);if(n===void 0)throw new C(`INVALID_INPUT`,`${t} must be a number.`);return n}function N(e){if(typeof e!=`string`)return;let t=e.split(`,`).map(e=>e.trim()).filter(Boolean);return t.length?t:void 0}function P(e){return N(e)??[]}function Ba(e){let t={concurrency:j(e[`fetch-concurrency`]),intervalCap:j(e[`fetch-interval-cap`]),intervalMs:j(e[`fetch-interval-ms`])};return Object.values(t).some(e=>e!==void 0)?t:void 0}function Va(e){let t={concurrency:M(e[`fetch-concurrency`],`--fetch-concurrency`),intervalCap:M(e[`fetch-interval-cap`],`--fetch-interval-cap`),intervalMs:M(e[`fetch-interval-ms`],`--fetch-interval-ms`)};return Object.values(t).some(e=>e!==void 0)?t:void 0}function F(e){return e.json===!0}async function Ha(e,t){let n=O(e),r=O(t);if(n&&r)throw Error(`Use either --body or --body-file, not both.`);if(!n&&!r)return;let i=n??await di(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 Ua(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 Wa(e={}){let t=e.env??process.env,n=Ua(e.columns)??Ua(e.streamColumns)??Ua(t.COLUMNS)??80;return Math.min(200,Math.max(40,n))}function Ga(e={}){let t=e.env??process.env,n=e.isTTY??!!process.stdout.isTTY,r=e.color??!!(n&&t.NO_COLOR===void 0);return{columns:Wa({...e,env:t,streamColumns:e.streamColumns??process.stdout.columns}),colors:Fi.createColors(r),hasColor:r,isInteractive:n&&!t.CI}}function Ka(e){return Ii(yi(e))}function qa(e,t){let n=[],r=``;for(let i of e)r&&Ka(`${r}${i}`)>t?(n.push(r),r=i):r+=i;return r&&n.push(r),n}function Ja(e,t){if(!e)return[``];let n=e.trim().split(/\s+/u),r=[],i=``;for(let e of n){let n=Ka(e)>t?qa(e,t):[e];for(let e of n)i?Ka(`${i} ${e}`)<=t?i+=` ${e}`:(r.push(i),i=e):i=e}return i&&r.push(i),r.length>0?r:[``]}function I(e,t){let n=Math.max(1,t);return e.replaceAll(`\r
103
103
  `,`
104
104
  `).split(`
105
- `).flatMap(e=>qa(e,n))}function Ja(e,t){if(Ga(e)<=t)return e;let n=Math.max(1,t-Ga(`...`)),r=``;for(let t of e){if(Ga(`${r}${t}`)>n)break;r+=t}return`${r}...`}const Ya={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 Xa(e){return e===void 0?``:String(e)}function Za(e,t){return e.map((e,n)=>{let r=t.map(e=>Xa(e[n]));return Math.min(42,Math.max(Ga(e),...r.map(Ga),4))})}function Qa(e,t,n){return e.length===0||e.length>4||!e.map((e,n)=>t.every(e=>/^-?(?:\d+(?:\.\d+)?|\d+(?:\.\d+)?%)$/u.test(Xa(e[n]).trim()))).some(Boolean)?!1:Za(e,t).reduce((e,t)=>e+t+2,0)<=n.columns}function $a(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 eo(e,t,n){let r=Za(e,t).map(e=>e+2),i=new Ii({chars:Ya,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)=>L($a(e,Xa(a[t]),n),Math.max(1,(r[t]??3)-2)).join(`
106
- `)));return i.toString()}function to(e,t,n,r){let i=r.columns-n-2;return i<24?[r.colors.dim(e),...L(t,r.columns-2).map(e=>` ${e}`)]:L(t,i).map((t,i)=>{let a=i===0?e.padEnd(n):``.padEnd(n);return`${r.colors.dim(a)}${` `.repeat(2)}${t}`})}function no(e,t,n,r={}){let i=Math.min(28,Math.max(...e.map(e=>Ga(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(...to(a,$a(a,Xa(t[r]),n),i,n));return s.join(`
105
+ `).flatMap(e=>Ja(e,n))}function Ya(e,t){if(Ka(e)<=t)return e;let n=Math.max(1,t-Ka(`...`)),r=``;for(let t of e){if(Ka(`${r}${t}`)>n)break;r+=t}return`${r}...`}const Xa={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 Za(e){return e===void 0?``:String(e)}function Qa(e,t){return e.map((e,n)=>{let r=t.map(e=>Za(e[n]));return Math.min(42,Math.max(Ka(e),...r.map(Ka),4))})}function $a(e,t,n){return e.length===0||e.length>4||!e.map((e,n)=>t.every(e=>/^-?(?:\d+(?:\.\d+)?|\d+(?:\.\d+)?%)$/u.test(Za(e[n]).trim()))).some(Boolean)?!1:Qa(e,t).reduce((e,t)=>e+t+2,0)<=n.columns}function eo(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 to(e,t,n){let r=Qa(e,t).map(e=>e+2),i=new Li({chars:Xa,colWidths:r,head:e.map(e=>n.colors.bold(e)),style:{border:[],head:[],"padding-left":0,"padding-right":2},wordWrap:!1});for(let a of t)i.push(e.map((e,t)=>I(eo(e,Za(a[t]),n),Math.max(1,(r[t]??3)-2)).join(`
106
+ `)));return i.toString()}function no(e,t,n,r){let i=r.columns-n-2;return i<24?[r.colors.dim(e),...I(t,r.columns-2).map(e=>` ${e}`)]:I(t,i).map((t,i)=>{let a=i===0?e.padEnd(n):``.padEnd(n);return`${r.colors.dim(a)}${` `.repeat(2)}${t}`})}function ro(e,t,n,r={}){let i=Math.min(28,Math.max(...e.map(e=>Ka(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(...no(a,eo(a,Za(t[r]),n),i,n));return s.join(`
107
107
  `)}).join(`
108
108
 
109
- `)}function ro(e,t,n,r={}){return t.length===0?n.colors.dim(`No results.`):r.layout===`table`||r.layout!==`records`&&Qa(e,t,n)?eo(e,t,n):no(e,t,n,r)}function io(e,t){if(e.length===0)return``;let n=Math.min(28,Math.max(...e.map(([e])=>Ga(e))));return e.flatMap(([e,r])=>{let i=to(e,r,n,t);return i.length>0&&(i[0]=(i[0]??``).replace(e,t.colors.bold(e))),i}).join(`
110
- `)}function ao(e,t){let n=[t.colors.bold(e.title)];return e.body&&n.push(...L(e.body,t.columns)),e.command&&n.push(t.colors.cyan(Ja(e.command,t.columns))),n.join(`
111
- `)}function oo(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 so(e){return e===`warning`?`WARN`:e===`notApplicable`?`N/A`:e.toUpperCase()}function co(e,t,n){let r=` `.repeat(t);return L(e,Math.max(1,n-t)).map(e=>`${r}${e}`)}function lo(e,t){let n=oo(e.status,t)(`[${so(e.status)}]`.padEnd(10)),r=Math.max(1,t.columns-10),i=L(e.title,r).map((e,r)=>`${r===0?n:` `.repeat(10)}${t.colors.bold(e)}`);i.push(...co(e.explanation,10,t.columns));for(let n of e.evidence??[]){let e=L(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=L(e.fix,Math.max(1,t.columns-15));i.push(...n.map((e,n)=>`${` `.repeat(10)}${n===0?`${t.colors.cyan(t.colors.bold(`Fix`))} `:` `}${e}`))}return i.join(`
112
- `)}function uo(e,t){return L(e.map(e=>{let n=String(e.value),r=e.status?oo(e.status,t)(n):t.colors.bold(n);return`${e.label} ${r}`}).join(t.colors.dim(` · `)),t.columns).join(`
113
- `)}function fo(e,t){let n=oo(e.status,t),r=[t.colors.bold(e.title)];e.target&&r.push(t.colors.dim(e.target)),r.push(``),r.push(`${n(so(e.status))} ${L(e.summary,Math.max(1,t.columns-6)).join(`
114
- `)}`),e.metrics?.length&&r.push(uo(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=>lo(e,t)).join(`
109
+ `)}function io(e,t,n,r={}){return t.length===0?n.colors.dim(`No results.`):r.layout===`table`||r.layout!==`records`&&$a(e,t,n)?to(e,t,n):ro(e,t,n,r)}function ao(e,t){if(e.length===0)return``;let n=Math.min(28,Math.max(...e.map(([e])=>Ka(e))));return e.flatMap(([e,r])=>{let i=no(e,r,n,t);return i.length>0&&(i[0]=(i[0]??``).replace(e,t.colors.bold(e))),i}).join(`
110
+ `)}function oo(e,t){let n=[t.colors.bold(e.title)];return e.body&&n.push(...I(e.body,t.columns)),e.command&&n.push(t.colors.cyan(Ya(e.command,t.columns))),n.join(`
111
+ `)}function so(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 co(e){return e===`warning`?`WARN`:e===`notApplicable`?`N/A`:e.toUpperCase()}function lo(e,t,n){let r=` `.repeat(t);return I(e,Math.max(1,n-t)).map(e=>`${r}${e}`)}function uo(e,t){let n=so(e.status,t)(`[${co(e.status)}]`.padEnd(10)),r=Math.max(1,t.columns-10),i=I(e.title,r).map((e,r)=>`${r===0?n:` `.repeat(10)}${t.colors.bold(e)}`);i.push(...lo(e.explanation,10,t.columns));for(let n of e.evidence??[]){let e=I(n,Math.max(1,t.columns-20));i.push(...e.map((e,n)=>`${` `.repeat(10)}${n===0?`${t.colors.dim(`Evidence`)} `:` `}${e}`))}if(e.fix){let n=I(e.fix,Math.max(1,t.columns-15));i.push(...n.map((e,n)=>`${` `.repeat(10)}${n===0?`${t.colors.cyan(t.colors.bold(`Fix`))} `:` `}${e}`))}return i.join(`
112
+ `)}function fo(e,t){return I(e.map(e=>{let n=String(e.value),r=e.status?so(e.status,t)(n):t.colors.bold(n);return`${e.label} ${r}`}).join(t.colors.dim(` · `)),t.columns).join(`
113
+ `)}function po(e,t){let n=so(e.status,t),r=[t.colors.bold(e.title)];e.target&&r.push(t.colors.dim(e.target)),r.push(``),r.push(`${n(co(e.status))} ${I(e.summary,Math.max(1,t.columns-6)).join(`
114
+ `)}`),e.metrics?.length&&r.push(fo(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=>uo(e,t)).join(`
115
115
 
116
- `));if(e.notes?.length){r.push(``,t.colors.bold(`Notes`));for(let n of e.notes){let e=L(n,Math.max(1,t.columns-2));r.push(...e.map((e,t)=>`${t===0?`- `:` `}${e}`))}}return r.join(`
117
- `)}function po(e,t,n=`${t}s`){return e===1?t:n}function mo(e,t,n){let r=` `.repeat(t);return L(e,Math.max(1,n-t)).map(e=>`${r}${e}`)}function ho(e,t,n){return[t.colors.bold(e),...n?L(n,t.columns):[]].join(`
118
- `)}function go(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(...mo(e.detail,6,t.columns)),e.fix){let n=L(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(`
116
+ `));if(e.notes?.length){r.push(``,t.colors.bold(`Notes`));for(let n of e.notes){let e=I(n,Math.max(1,t.columns-2));r.push(...e.map((e,t)=>`${t===0?`- `:` `}${e}`))}}return r.join(`
117
+ `)}function mo(e,t,n=`${t}s`){return e===1?t:n}function ho(e,t,n){let r=` `.repeat(t);return I(e,Math.max(1,n-t)).map(e=>`${r}${e}`)}function go(e,t,n){return[t.colors.bold(e),...n?I(n,t.columns):[]].join(`
118
+ `)}function _o(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(...ho(e.detail,6,t.columns)),e.fix){let n=I(e.fix,Math.max(1,t.columns-11));r.push(...n.map((e,n)=>`${` `.repeat(6)}${n===0?`${t.colors.bold(`Fix`)} `:` `}${e}`))}return r.join(`
119
119
  `)}).join(`
120
120
 
121
- `)}function _o(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} ${po(t,`check`)} passed, ${n} ${po(n,`warning`)}, ${r} failed.`}function vo(e,t,n){let r=n.columns-t-2-2;return r<24||Ga(e.id)>t?[...mo(e.id,2,n.columns).map(e=>n.colors.cyan(e)),...mo(e.name,4,n.columns)].join(`
122
- `):L(e.name,r).map((r,i)=>{let a=i===0?e.id.padEnd(t):``.padEnd(t);return` ${n.colors.cyan(a)}${` `.repeat(2)}${r}`}).join(`
123
- `)}function yo(e){let t=e.replaceAll(`-`,` `);return t.charAt(0).toUpperCase()+t.slice(1)}function bo(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} ${po(e.length,n.noun)} across ${r.size} ${po(r.size,`category`,`categories`)}.`,a=Math.min(30,Math.max(...e.map(e=>Ga(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]??yo(e);return[t.colors.bold(`${o} (${i.length})`),...i.map(e=>vo(e,a,t))].join(`
121
+ `)}function vo(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} ${mo(t,`check`)} passed, ${n} ${mo(n,`warning`)}, ${r} failed.`}function yo(e,t,n){let r=n.columns-t-2-2;return r<24||Ka(e.id)>t?[...ho(e.id,2,n.columns).map(e=>n.colors.cyan(e)),...ho(e.name,4,n.columns)].join(`
122
+ `):I(e.name,r).map((r,i)=>{let a=i===0?e.id.padEnd(t):``.padEnd(t);return` ${n.colors.cyan(a)}${` `.repeat(2)}${r}`}).join(`
123
+ `)}function bo(e){let t=e.replaceAll(`-`,` `);return t.charAt(0).toUpperCase()+t.slice(1)}function xo(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} ${mo(e.length,n.noun)} across ${r.size} ${mo(r.size,`category`,`categories`)}.`,a=Math.min(30,Math.max(...e.map(e=>Ka(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]??bo(e);return[t.colors.bold(`${o} (${i.length})`),...i.map(e=>yo(e,a,t))].join(`
124
124
  `)})].join(`
125
125
 
126
- `)}function xo(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(...mo(e.description,2,t.columns));let r=e.meta?.filter(Boolean).join(` · `);return r&&n.push(...mo(r,2,t.columns).map(e=>t.colors.dim(e))),n.join(`
126
+ `)}function So(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(...ho(e.description,2,t.columns));let r=e.meta?.filter(Boolean).join(` · `);return r&&n.push(...ho(r,2,t.columns).map(e=>t.colors.dim(e))),n.join(`
127
127
  `)}).join(`
128
128
 
129
- `)}function So(e,t,n){let r=t.filter(e=>e.trim().length>0).flatMap(e=>L(e,n.columns));return[n.colors.bold(e),...r].join(`
130
- `)}function Co(e,t,n){let r=t.flatMap(e=>L(e,Math.max(1,n.columns-2)).map((e,t)=>`${t===0?`- `:` `}${e}`));return[n.colors.bold(e),...r].join(`
131
- `)}function wo(e,t){return e.length===0?t.colors.dim(`No parameters.`):xo(e.map(e=>({title:e.name,description:e.description,meta:[e.type,e.required?`required`:`optional`]})),t,{empty:`No parameters.`})}function R(e={}){return!!(!e.json&&process.stdin.isTTY&&process.stdout.isTTY&&!process.env.CI)}function z(e){process.stdout.write(`${JSON.stringify(e,null,2)}\n`)}function B(e){let t=io(e,Wa());t&&process.stdout.write(`${t}\n`)}function V(e,t){process.stdout.write(`${ro(e,t,Wa())}\n`)}function To(e){process.stdout.write(`${ao(e,Wa())}\n`)}function Eo(e,t){process.stdout.write(`${ho(e,Wa(),t)}\n`)}function Do(e){process.stdout.write(`${go(e,Wa())}\n`)}function Oo(e){process.stdout.write(`${fo(e,Wa())}\n`)}function ko(e,t){process.stdout.write(`${bo(e,Wa(),t)}\n`)}function Ao(e,t){process.stdout.write(`${xo(e,Wa(),t)}\n`)}function jo(e,...t){process.stdout.write(`${So(e,t,Wa())}\n`)}function Mo(e,t){process.stdout.write(`${Co(e,t,Wa())}\n`)}function No(e){process.stdout.write(`${wo(e,Wa())}\n`)}function Po(e){return e<1024?`${e} B`:e<1024*1024?`${(e/1024).toFixed(1)} KB`:`${(e/(1024*1024)).toFixed(1)} MB`}function H(e){return typeof e==`symbol`&&process.exit(1),e}function Fo(e){wn(e?.googleAccounts)}function Io(e,t){let n=t.trim().toLowerCase();return!n||e.some(e=>e.toLowerCase().includes(n))}async function Lo(e){let t=e.choices;if(e.choices.length>8){let n=H(await Ni({message:e.searchMessage,placeholder:`Type part of the name or URL, or leave blank`}));if(t=e.choices.filter(t=>Io(e.searchValues(t),n)),!t.length)throw new w(`INVALID_INPUT`,e.emptyMessage)}return H(await Mi({message:e.message,options:t.map(t=>{let n=e.hint?.(t);return{value:t,label:e.label(t),...n?{hint:n}:{}}})}))}async function Ro(e){if(e.site)return e.site;let t=Xn(),n=!!t.defaultSite&&(t.clients.some(e=>e.siteUrl===t.defaultSite)||t.sites.some(e=>e.siteUrl===t.defaultSite));if(e.options?.allowDefault!==!1&&t.defaultSite&&n)return Fo(t.clients.find(e=>e.isDefault&&e.siteUrl===t.defaultSite)),t.defaultSite;if(e.options?.json||!R())throw new w(`INVALID_INPUT`,t.defaultSite?`Saved default site ${t.defaultSite} does not match any saved project. Pass --site or --project, or run \`seo start\` to save a new default.`:"No site selected. Pass --site, use --project on supported commands, or run `seo start` in a terminal.");let r=await m(e.options?.refresh,e.options?.account);if(!r.length)throw new w(`PROPERTY_NOT_FOUND`,`No Search Console properties found for this Google login.`);return r.length===1?r[0]?.siteUrl??``:(await Lo({message:`Choose a Search Console property`,searchMessage:`Search Search Console properties`,emptyMessage:`No Search Console properties matched that search.`,choices:r,label:e=>e.siteUrl,hint:e=>e.permissionLevel,searchValues:e=>[e.siteUrl,e.permissionLevel??``]})).siteUrl}async function U(e){if(e.client&&e.project&&e.client!==e.project)throw new w(`INVALID_INPUT`,`Use either --project or --client, not both.`);let t=e.project??e.client;if(t){let e=vt(t);if(!e)throw new w(`INVALID_INPUT`,`Project not found: ${t}`);return Fo(e),{client:e,site:e.siteUrl}}let n=vt();return!e.site&&n?(Fo(n),{client:n,site:n.siteUrl}):{site:await Ro({site:e.site,options:e.options})}}async function W(e){if(e.client&&e.project&&e.client!==e.project)throw new w(`INVALID_INPUT`,`Use either --project or --client, not both.`);let t=e.project??e.client;if(t){let e=vt(t);if(!e)throw new w(`INVALID_INPUT`,`Project not found: ${t}`);return Fo(e),e}let n=We();if(!n.length)return;let r=vt();if(r)return Fo(r),r;if(e.options?.json||!R())return;let i=await Lo({message:`Choose a project`,searchMessage:`Search projects`,emptyMessage:`No projects matched that search.`,choices:n,label:e=>e.name,hint:e=>e.siteUrl,searchValues:e=>[e.id,e.name,e.siteUrl]});return Fo(i),i}async function zo(e){if(e.property)return e.property;let t=Xn();if(t.analytics.google.defaultPropertyId)return t.analytics.google.defaultPropertyId;if(e.options?.json||!R())throw new w(`INVALID_INPUT`,`No Google Analytics property selected. Pass --property or run this command in a terminal to choose one.`);let n=(await ar(e.options?.account)).flatMap(e=>e.propertySummaries.map(t=>({property:Lt(t.property),displayName:t.displayName??t.property,account:e.displayName??e.account})));if(!n.length)throw new w(`PROPERTY_NOT_FOUND`,`No Google Analytics properties found for this Google login.`);return n.length===1?n[0]?.property??``:(await Lo({message:`Choose a Google Analytics property`,searchMessage:`Search Google Analytics properties`,emptyMessage:`No Google Analytics properties matched that search.`,choices:n,label:e=>`${e.displayName} (${e.property})`,hint:e=>e.account,searchValues:e=>[e.property,e.displayName,e.account]})).property}async function Bo(e,t={}){let n=k(e[`report-id`]);if(n){let e=a(n);if(!e)throw Error(`No saved crawl report found for ${n}.`);return e}let r=A(e),i=k(e.site),o=r||i?await U({client:r,site:i,options:t}):void 0,s=Le(o?.site);if(!s)throw Error(o?.site?`No saved crawl report found for ${o.site}. Run \`seo crawl --project ${o.client?.id??o.site} --save\` first.`:"No saved crawl report found. Run `seo crawl <url> --save` first, or pass --report-id.");return s}function Vo(e){let t=t=>e.checks.filter(e=>e.status===t).length,n=t(`fail`),r=t(`warning`),i=t(`unknown`),a=t(`pass`),o=n?`fail`:r?`warning`:i?`unknown`:`pass`;Oo({title:`AI search evidence`,target:e.url,status:o,summary:e.headline,metrics:[{label:`Passed`,value:a,status:`pass`},{label:`Review`,value:r,status:`warning`},{label:`Failed`,value:n,status:`fail`},{label:`Unknown`,value:i,status:`unknown`}],sections:e.sections.map(e=>({title:e.title,diagnostics:e.checks.map(e=>({status:e.status,title:e.title,explanation:e.plainEnglish,fix:e.status===`pass`?void 0:e.action,evidence:(e.urls??[]).slice(0,5)}))})),notes:[`Data: ${e.dataStatus}. Report: ${e.reportId}.`,...e.caveats]})}const Ho=E({meta:{name:`ai-readiness`,description:`Review AI-search technical evidence and optional observations`},args:{"report-id":{type:`string`,description:`Saved crawl report id to analyze.`},site:{type:`string`,description:`GSC property URL for selecting the latest saved crawl.`},client:{type:`string`,description:`Legacy alias for --project.`},project:{type:`string`,description:`Saved project id or name.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=I(e),n=re(await Bo(e,{json:t}));if(t){z(n);return}Vo(n)}});function Uo(e){let t=k(e.agentReadinessUrl),n=k(e.url);if(t&&n&&t!==n)throw Error(`Use either the positional URL or --url, not both.`);return t??n}function Wo(e){let t=e.summary.failed>0?`fail`:e.summary.warnings>0?`warning`:e.summary.unknown>0?`unknown`:`pass`;Oo({title:`AI agent readiness`,target:e.url,status:t,summary:e.headline,metrics:[{label:`Passed`,value:e.summary.passed,status:`pass`},{label:`Review`,value:e.summary.warnings,status:`warning`},{label:`Failed`,value:e.summary.failed,status:`fail`},{label:`Unknown`,value:e.summary.unknown,status:`unknown`}],sections:e.sections.map(e=>({title:e.title,diagnostics:e.checks.filter(e=>![`info`,`notApplicable`].includes(e.status)).map(e=>({status:e.status,title:e.title,explanation:e.plainEnglish,fix:e.status===`pass`?void 0:e.action,evidence:(e.urls??[]).slice(0,5)}))})),notes:[`Profile: ${e.profile}. Data: ${e.dataStatus}. Report: ${e.reportId}.`,...e.caveats]})}const Go=E({meta:{name:`agent-readiness`,description:`Check content-site access, Markdown alternatives, discovery, and identity for AI agents`},args:{agentReadinessUrl:{type:`positional`,required:!1,description:`Public site URL to check.`},url:{type:`string`,description:`Public site URL to check.`},"report-id":{type:`string`,description:`Saved crawl report id to analyze without fetching again.`},site:{type:`string`,description:`GSC property URL for selecting the latest saved crawl.`},client:{type:`string`,description:`Legacy alias for --project.`},project:{type:`string`,description:`Saved project id or name.`},"max-pages":{type:`string`,description:`Maximum public HTML pages to crawl.`},"fetch-interval-cap":{type:`string`,description:`Maximum page fetches per interval per host.`},"fetch-interval-ms":{type:`string`,description:`Fetch rate interval in milliseconds.`},refresh:{type:`boolean`,default:!1,description:`Bypass local HTTP cache and fetch fresh pages.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=I(e),n=Uo(e),r=k(e[`report-id`]);if(n&&r)throw Error(`Use either a URL or --report-id, not both.`);let i=Te(n?await(async()=>{let r=A(e),i=k(e.site),a=r||i?await U({client:r,site:i,options:{json:t}}):void 0;return rt({url:n,projectId:a?.client?.id,site:a?.site,maxPages:M(e[`max-pages`]),fetchRate:za(e),refresh:j(e.refresh),checkAgentDiscovery:!0,useSitemap:!0,checkExternal:!1})})():await Bo(e,{json:t}));if(t){z(i);return}Wo(i)}}),Ko=`@seoskill/clicky-provider`;function qo(e){return e===`keychain`?`system keychain`:e===`file`?`private local file`:e===`provider-store`?`provider credential store`:e??`missing`}async function Jo(e){let t=await le(),n=t.registry.get(`clicky`);if(n)return n;let r=t.failures.find(e=>e.id===`clicky`);if(r)throw new w(`PROVIDER_UNAVAILABLE`,`The Clicky provider package could not load: ${r.message}`);if(!R({json:e.json}))throw new w(`PROVIDER_UNAVAILABLE`,`Clicky is now an optional provider package. Run \`seo providers install ${Ko} --yes\`, then run this command again.`);let i;try{i=await b(Ko)}catch(e){throw new w(`PROVIDER_UNAVAILABLE`,e instanceof Error?e.message:String(e))}if(D([`${i.package}@${i.version}`,`Publisher: ${i.publisher??`not supplied by npm`}`,`Repository: ${i.repository??`not supplied by npm`}`,`Integrity: ${i.integrity}`].join(`
132
- `),`Clicky package`),D(`This package can read files, use the network, and run with the same local permissions as seo.`,`Local permissions`),!H(await Ti({message:`Install the Clicky provider package?`,initialValue:!0})))throw new w(`PROVIDER_UNAVAILABLE`,`The Clicky provider package is required for this command.`);await g(i),t=await le();let a=t.registry.get(`clicky`);if(!a)throw new w(`PROVIDER_UNAVAILABLE`,"The Clicky provider package was installed but could not load. Run `seo providers doctor`.");return a}async function Yo(e){if(e.siteId)return e.siteId;let t=Jn(await W({client:e.project,options:{json:e.json}}));if(t?.provider===`clicky`)return t.siteId;if(t?.provider===`extension`&&t.providerId===`clicky`&&t.account.siteId)return t.account.siteId;throw new w(`INVALID_INPUT`,`No Clicky site is selected. Pass --site-id or use a project connected to Clicky.`)}async function Xo(e,t){let n=await Ct(t);if(n)return{siteKey:n.siteKey,source:n.source};try{let n=await ht({providerId:`clicky`,account:{siteId:t},fields:e.connection.fields});return n.sitekey?{siteKey:n.sitekey,source:`provider-store`}:{}}catch{return{}}}const Zo=E({meta:{name:`connect`,description:`Validate and save a Clicky sitekey`},args:{"site-id":{type:`string`,description:`Numeric Clicky site ID.`},project:{type:`string`,description:`Saved project id or name.`},client:{type:`string`,description:`Legacy alias for --project.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let n=I(e),r=await Jo({json:n}),i=R({json:n}),a=A(e),o=a?await W({project:a,options:{json:n}}):void 0;i&&Ei(`Connect Clicky`);let s=Jn(o),c=s?.provider===`clicky`?s.siteId:s?.provider===`extension`&&s.providerId===`clicky`?s.account.siteId:void 0,l=k(e[`site-id`])??c??(i?H(await Ni({message:`Clicky site ID`,validate:e=>/^\d{1,30}$/u.test(e?.trim()??``)?void 0:`Enter the numeric site ID from Clicky`})):void 0);if(!l)throw new w(`INVALID_INPUT`,`Pass --site-id when the selected project is not already connected to Clicky.`);let u=await Xo(r,l),d=u.siteKey??(i?H(await ji({message:`Clicky sitekey`,validate:e=>/^[A-Za-z0-9]{12,64}$/u.test(e?.trim()??``)?void 0:`Enter the sitekey from Clicky`})):void 0);if(!d)throw new w(`AUTH_REQUIRED`,`Run this command in a terminal or set ${xe}.`);await ee({providerId:`clicky`,account:{siteId:l},credentials:{sitekey:d}});let f=u.source??await yt(l,d);await ae({providerId:`clicky`,account:{siteId:l},credentials:{sitekey:d}}),t(`clicky`,{siteId:l});let p=o?dr(o.id,{provider:`extension`,providerId:`clicky`,account:{siteId:l}}):void 0,m={siteId:l,connected:!0,credentialSource:f,project:p?{id:p.id,name:p.name}:void 0};if(n){z(m);return}let h=p?`Connection verified and attached to ${p.name}. Saved in the ${qo(f)}.`:`Connection verified. Saved in the ${qo(f)}.`;i?Ai(h):process.stdout.write(`${h}\n`)}}),Qo=E({meta:{name:`status`,description:`Show the local Clicky connection`},args:{"site-id":{type:`string`,description:`Numeric Clicky site ID.`},project:{type:`string`,description:`Saved project id or name.`},client:{type:`string`,description:`Legacy alias for --project.`},check:{type:`boolean`,default:!1,description:`Verify the saved sitekey with Clicky.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=I(e),n=await Jo({json:t}),r=await Yo({siteId:k(e[`site-id`]),project:A(e),json:t}),i=await Xo(n,r),a=j(e.check);a&&i.siteKey&&await ee({providerId:`clicky`,account:{siteId:r},credentials:{sitekey:i.siteKey}});let o={siteId:r,connected:!!i.siteKey,credentialSource:i.source,liveCheck:a?i.siteKey?`passed`:`unavailable`:`not-requested`};if(t){z(o);return}B([[`Site ID`,r],[`Connected`,o.connected?`yes`:`no`],[`Credential`,qo(o.credentialSource)],[`Live check`,o.liveCheck]])}}),$o=E({meta:{name:`report`,description:`Run a bounded Clicky analytics report`},args:{"site-id":{type:`string`,description:`Numeric Clicky site ID.`},project:{type:`string`,description:`Saved project id or name.`},client:{type:`string`,description:`Legacy alias for --project.`},type:{type:`string`,default:`pages-entrance`,description:`Clicky report type. Defaults to pages-entrance.`},"start-date":{type:`string`,required:!0,description:`First site calendar date in YYYY-MM-DD format.`},"end-date":{type:`string`,required:!0,description:`Last site calendar date in YYYY-MM-DD format.`},limit:{type:`string`,default:`100`,description:`Maximum retained rows. Defaults to 100 and caps at 5000.`},refresh:{type:`boolean`,default:!1,description:`Bypass the local Clicky cache.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=I(e),n=await Jo({json:t}),r=await Yo({siteId:k(e[`site-id`]),project:A(e),json:t}),i=await Xo(n,r),a=await vn({providerId:`clicky`,actionId:`report`,account:{siteId:r},...i.siteKey?{credentials:{sitekey:i.siteKey}}:{},params:{type:k(e.type)??`pages-entrance`,startDate:k(e[`start-date`])??``,endDate:k(e[`end-date`])??``,limit:M(e.limit)??100},refresh:j(e.refresh)});if(!a.data||typeof a.data!=`object`)throw new w(`PROVIDER_UNAVAILABLE`,`The Clicky provider returned an invalid report.`);let o={...a.data,cache:a.cache};if(t){z(o);return}B([[`Site ID`,o.siteId],[`Report`,o.type],[`Rows`,String(o.returnedRows)],[`Cache`,o.cache]]),V([`Value`,`Title`,`URL`],o.rows.slice(0,25).map(e=>[String(e.value??``),String(e.title??``),String(e.url??``)]))}}),es=E({meta:{name:`disconnect`,description:`Remove a saved Clicky sitekey`},args:{"site-id":{type:`string`,description:`Numeric Clicky site ID.`},project:{type:`string`,description:`Saved project id or name.`},client:{type:`string`,description:`Legacy alias for --project.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=I(e);await Jo({json:t});let n=await Yo({siteId:k(e[`site-id`]),project:A(e),json:t});await Yn(n),await st({providerId:`clicky`,account:{siteId:n}});let r=!!process.env[xe];t?z({siteId:n,savedCredentialRemoved:!0,environmentCredential:r?`active`:`missing`}):process.stdout.write(r?`Saved sitekey removed. ${xe} is still active.\n`:`Clicky is disconnected for this site.
133
- `)}}),ts=E({meta:{name:`clicky`,description:`Connect Clicky and read bounded analytics reports`},subCommands:{connect:Zo,status:Qo,report:$o,detach:E({meta:{name:`detach`,description:`Remove Clicky from one project without deleting its sitekey`},args:{project:{type:`string`,required:!0,description:`Saved project id or name.`},client:{type:`string`,description:`Legacy alias for --project.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:t})=>{let n=I(t),r=await W({project:A(t),options:{json:n}}),i=Jn(r),a=i?.provider===`extension`&&i.providerId===`clicky`?`extension:clicky`:i?.provider===`clicky`?`clicky`:void 0;if(!r||!a)throw new w(`INVALID_INPUT`,`The selected project is not attached to Clicky.`);let o=e(r.id,a),s={project:{id:o.id,name:o.name},detached:!0,selectedAnalytics:o.analytics.selected,savedSitekey:!0};n?z(s):process.stdout.write(`Clicky detached from ${o.name}. The saved sitekey was kept.\n`)}}),disconnect:es}}),ns=E({meta:{name:`properties`,description:`List Google Analytics accounts and properties`},args:{json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`},save:{type:`string`,description:`Save this numeric Google Analytics property ID as the default.`},account:{type:`string`,description:`Saved Google account email.`}},run:async({args:e})=>{let t=await ar(k(e.account)),n=t.flatMap(e=>e.propertySummaries.map(t=>({account:e.displayName??e.account,property:Lt(t.property),displayName:t.displayName??t.property}))),r=k(e.save);if(r){let e=Xn();e.analytics.google.defaultPropertyId=r,Ot(e)}if(I(e)){z({accountSummaries:t,properties:n,saved:r});return}r&&process.stdout.write(`Saved default Google Analytics property ${r}.\n`),V([`Property`,`Name`,`Account`],n.map(e=>[e.property,e.displayName,e.account]))}});function G(e){return Math.round(e).toLocaleString(`en-GB`)}function rs(e){return e.toFixed(1)}function is(e){return`${(e*100).toFixed(1)}%`}function K(e,t=72){return Ja(e,t)}function as(e){return e.verification?.requested?`${e.verification.verified} checked, ${e.verification.failed} failed`:`off`}function os(e){return{title:e.title,target:e.target,status:e.status,summary:e.summary,metrics:e.metrics,sections:(e.diagnostics??[]).map(e=>({title:e.title,diagnostics:e.items})),notes:e.notes}}function q(e){Oo(os(e))}function J(e,t){V(e,t.slice(0,25)),t.length>25&&process.stdout.write(`Showing 25 of ${t.length}. Use --json for full data.\n`)}function Y(e,t,n=5){let r=t.filter(e=>e.action.trim().length>0).slice(0,n),i=ss(e,r);i&&(process.stdout.write(`
134
- `),Oo(i),t.length>r.length&&process.stdout.write(`Showing ${r.length} of ${t.length}. Use --json for full data.\n`))}function ss(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 cs(e){process.stdout.write(`\nNext: ${e}\n`)}function X(e,t){let n=t.filter(e=>e.trim().length>0);if(n.length){process.stdout.write(`\n${e}\n`);for(let e of n)process.stdout.write(`- ${e}\n`)}}async function ls(e,t={resolveClient:W,resolveGoogleAnalyticsProperty:zo}){if(e.property)return t.resolveGoogleAnalyticsProperty({property:e.property,options:e.options});let n=await t.resolveClient({client:e.project,options:e.options});return t.resolveGoogleAnalyticsProperty({property:Et(n),options:e.options})}const us=E({meta:{name:`analytics`,description:`Read analytics data from connected providers`},subCommands:{clicky:ts,google:E({meta:{name:`google`,description:`Read Google Analytics properties and reports`},subCommands:{properties:ns,report:E({meta:{name:`report`,description:`Run a Google Analytics report`},args:{property:{type:`string`,description:`Google Analytics property ID. If omitted in a terminal, choose one.`},client:{type:`string`,description:`Legacy alias for --project.`},project:{type:`string`,description:`Saved project id or name with an optional Google Analytics property.`},account:{type:`string`,description:`Saved Google account email.`},"start-date":{type:`string`,default:`28daysAgo`},"end-date":{type:`string`,default:`yesterday`},dimensions:{type:`string`,default:`landingPage`},metrics:{type:`string`,default:`sessions,totalUsers,eventCount`},limit:{type:`string`,default:`25`},body:{type:`string`},"body-file":{type:`string`},refresh:{type:`boolean`,default:!1,description:`Bypass the local Google Analytics cache.`},json:{type:`boolean`,default:!1}},run:async({args:e})=>{let t=I(e),n=await ls({property:k(e.property),project:A(e),options:{json:t,account:k(e.account)}}),r=await On(n,await Va(e.body,e[`body-file`])??{dateRanges:[{startDate:k(e[`start-date`]),endDate:k(e[`end-date`])}],dimensions:(P(e.dimensions)??[]).map(e=>({name:e})),metrics:(P(e.metrics)??[]).map(e=>({name:e})),limit:k(e.limit)},{refresh:j(e.refresh),accountEmail:k(e.account)});if(t){z(r);return}let i=Hn(r);if(q({title:`Google Analytics report`,target:n,status:`info`,summary:`${r.rowCount??i.length} rows returned for the requested dimensions and metrics.`,metrics:[{label:`Rows`,value:r.rowCount??i.length},{label:`Displayed`,value:Math.min(i.length,25)}]}),i.length){let e=Object.keys(i[0]??{});V(e,i.slice(0,25).map(t=>e.map(e=>t[e]??``)))}}})}})}});function ds(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 fs(e){return e.active===`keychain`?`system keychain`:e.configured===`keychain`?`private file fallback`:`private file`}function ps(e){return e===`shared`?`SEO Skill app`:`Local OAuth config`}function ms(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`,ps(e.clientSource)],[`Expires`,Na(e.expiresAt)]),t}const hs=E({meta:{name:`auth`,description:`Authentication commands`},subCommands:{login:E({meta:{name:`login`,description:`Run Google OAuth flow`},run:async()=>{if((await v()).activeMode===`service-account`)throw new w(`AUTH_CONFIG_REQUIRED`,`A service account is active from the environment. Unset its credential variable before running browser OAuth.`);let e=await x();B(ms({accountEmail:e.account_email,clientSource:e.client_source,expiresAt:e.expires_at,scope:e.scope}))}}),logout:E({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 v(),n=k(e.account);if(n&&e.all)throw new w(`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 ct(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:E({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 v();if(t.activeMode===`service-account`){let n={mode:`service-account`,account:t.identity??null,source:ds(t.serviceAccount.source)};if(I(e)){z(n);return}B([[`Account type`,`Service account`],[`Account`,t.identity??`Invalid configuration`],[`Credentials`,n.source],[`Access`,`Search Console and Google Analytics, read only`]]);return}let n=k(e.account),r=n?await ve(n):t.tokens;if(n&&!r)throw new w(`AUTH_REQUIRED`,`Google account ${n} is not connected.`);if(!r){if(I(e)){z({mode:`oauth`,account:null});return}process.stdout.write(`Not logged in.
135
- `);return}if(I(e)){z({mode:`oauth`,account:r.account_email,scopes:r.scope.split(/\s+/u).filter(Boolean),clientSource:r.client_source,expiresAt:r.expires_at});return}B(ms({accountEmail:r.account_email,clientSource:r.client_source,expiresAt:r.expires_at,scope:r.scope}))}}),status:E({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 v(),n=k(e.account),r=n?await ve(n):t.tokens;if(n&&!r&&t.activeMode!==`service-account`)throw new w(`AUTH_REQUIRED`,`Google account ${n} is not connected.`);let i=t.activeMode===`oauth`?await oe():void 0;if(I(e)){z({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`,ds(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]),B(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 oe();B([[`Account`,r.account_email],[`Connected accounts`,String(t.accounts.length)],[`Scopes`,r.scope],[`Client`,ps(r.client_source)],[`Expires`,Na(r.expires_at)],[`Tokens file`,rr().tokensFile],[`Token storage`,fs(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:E({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 w(`INVALID_INPUT`,`Choose either --keychain or --file, not both.`);let t=e.keychain||e.file?await bt(e.keychain?`keychain`:`file`):await oe();if(I(e)){z(t);return}B([[`Configured storage`,t.configured],[`Active storage`,fs(t)],...t.reason?[[`Note`,t.reason]]:[]])}}),refresh:E({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 v()).activeMode===`service-account`){process.stdout.write(`Service accounts request short-lived Google tokens automatically. No local refresh token is stored.
136
- `);return}let t=await Re(k(e.account));process.stdout.write(`Refreshed. New expiry ${new Date(t.expires_at).toISOString()}.\n`)}}),accounts:E({meta:{name:`accounts`,description:`List saved Google accounts`},args:{json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:({args:e})=>{let t=Se();if(I(e)){z({accounts:t});return}if(t.length===0){process.stdout.write(`No Google accounts are connected.
137
- `);return}B(t.map(e=>[e.active?`Active account`:`Account`,e.accountEmail]))}}),use:E({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=k(e.account);if(!t)throw new w(`INVALID_INPUT`,`Google account is required.`);wt(t),process.stdout.write(`Default Google account: ${t}\n`)}}),"setup-client":E({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(!R({json:I(e)}))throw new w(`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.");Ei(`Local Google OAuth client`),D([`This is the advanced path.`,`Most users should use the shared seo Google app.`,`Your actual sensitive data is the local refresh token, not the desktop app client secret.`].join(`
138
- `),`Advanced`),Ke({clientId:H(await Ni({message:`Google Desktop OAuth client ID`,validate:e=>e?void 0:`Client ID is required`})),clientSecret:H(await ji({message:`Google Desktop OAuth client secret`,validate:e=>e?void 0:`Client secret is required`}))}),Ai(`Saved local OAuth config to ${rr().oauthClientFile}`)}})}}),gs=[`gsc`,`google-analytics`,`semrush`,`dataforseo`,`ahrefs`,`serpbase`,`clicky`,`http`];function _s(e){if(e!==void 0){if(typeof e==`string`&&gs.includes(e))return e;throw new w(`INVALID_INPUT`,`--provider must be one of: ${gs.join(`, `)}.`)}}const vs=E({meta:{name:`cache`,description:`Cache helpers`},subCommands:{stats:E({meta:{name:`stats`,description:`Show local cache size and row counts`},run:async()=>{let e=Ft();B([[`DB`,e.dbPath],[`Size`,Po(e.sizeBytes)],[`Cached data`,`${Po(e.logicalSizeBytes)} of ${Po(e.maxSizeBytes)} automatic limit`],[`Sites`,String(e.counts.sites??0)],[`Search Console`,String(e.counts.gsc_cache??0)],[`Google Analytics`,String(e.counts.google_analytics_cache??0)],[`Semrush`,String(e.counts.semrush_cache??0)],[`DataForSEO`,String(e.counts.provider_cache??0)],[`Ahrefs`,String(e.counts.ahrefs_cache??0)],[`SerpBase`,String(e.counts.serpbase_cache??0)],[`Clicky`,String(e.counts.clicky_cache??0)],[`HTTP`,String(e.counts.http_cache??0)]])}}),clear:E({meta:{name:`clear`,description:`Clear cached API and HTTP data`},args:{provider:{type:`string`,description:`Optional cache provider: gsc, google-analytics, semrush, dataforseo, ahrefs, serpbase, clicky, or http`}},run:async({args:e})=>{let t=mn(_s(e.provider));process.stdout.write(`Removed ${t} cached rows.\n`)}})}});async function ys(e,t={}){return(await U({client:e.client,project:e.project,site:e.site,options:t})).site}function bs(e){if(e.startsWith(`http://`)||e.startsWith(`https://`))return e;if(e.startsWith(`sc-domain:`))return`https://${e.slice(10)}/`}function xs(e){return e.replace(/^sc-domain:/,``).replace(/^https?:\/\//,``)}function Ss(e){return e.toLowerCase().replace(/^https?:\/\//,``).replace(/^sc-domain:/,``).replace(/[^a-z0-9]+/g,`-`).replace(/^-+|-+$/g,``).slice(0,60)}function Cs(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 ws(e){return e?e===`serp-framing`?`wording gap`:e===`content-gap`?`content gap`:e===`technical-check`?`technical check`:`covered`:`-`}const Ts=`__create_project__`,Es=`__skip_project__`;function Ds(e){return[{value:Ts,label:`Create a new project`,hint:`Save a separate site and its data sources`},...e.map(e=>({value:e.id,label:`Update ${e.name}`,hint:`${e.id}, ${e.siteUrl}`})),{value:Es,label:`Continue without a project profile`,hint:`Pass --site or --url when you run reports`}]}function Os(e,t){let n=Ss(e);if(!n)throw Error(`Project id could not be derived.`);let r=new Set(t.map(e=>e.id.toLowerCase()));if(!r.has(n.toLowerCase()))return n;let i=2;for(;r.has(`${n}-${i}`.toLowerCase());)i+=1;return`${n}-${i}`}async function ks(e){if(e.skipProfile)return{mode:`skip`};if(e.requestedId){let t=e.requestedId.toLowerCase(),n=Ss(e.requestedId).toLowerCase(),r=e.clients.find(e=>e.id.toLowerCase()===n||e.name.toLowerCase()===t);return r?{mode:`update`,client:r}:{mode:`create`,requestedId:e.requestedId}}if(!e.interactive)return{mode:`create`};let t=Ds(e.clients),n=H(await(e.prompt?e.prompt(t):Mi({message:`Create a project or update an existing one?`,options:t})));if(n===Ts)return{mode:`create`};if(n===Es)return{mode:`skip`};let r=e.clients.find(e=>e.id===n);if(!r)throw Error(`Project not found: ${n}`);return{mode:`update`,client:r}}function As(e){let t=e.platform===`win32`?ri:ei;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 js(e={}){let t=e.home??ai(),n=e.platform??process.platform,r=As({home:t,platform:n,env:e.env??process.env}),i=n===`win32`?ri:ei,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 Ms(e,t={}){let n=t.platform??process.platform,r=t.exists??Hr,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=T(e,t);if(r(n))return n}for(let e of a){let t=T(Zr(i),e);if(r(t))return t}return e}function Ns(e){return Ms(e)!==e}function Ps(e={}){let t=e.targets??js(),n=e.hasCommand??Ns,r=e.hasPath??Hr;return t.filter(e=>e.kind===`native`?e.commandNames.some(n):r(e.path)||e.detectionPaths.some(r)||e.commandNames.some(n))}const Fs={insertSpaces:!0,tabSize:2,eol:`
139
- `};function Is(){return new Date().toISOString().replace(/[:.]/g,`-`)}function Ls(e,t){Ur(Zr(e),{recursive:!0,mode:448});let n=`${e}.${process.pid}.${Date.now()}.tmp`;Yr(n,t,{encoding:`utf8`,mode:Hr(e)?Jr(e).mode&511:384}),qr(n,e)}function Rs(e){let t=`${e}.bak-${Is()}`,n=t,r=2;for(;Hr(n);)n=`${t}-${r}`,r+=1;Br(e,n,zr.COPYFILE_EXCL)}function zs(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function Bs(e){if(!Hr(e.path))return{source:`{}
140
- `,config:{}};let t=Wr(e.path,`utf8`),n=[],r=zi(t,n,{allowTrailingComma:!0});if(n.length>0){let t=n[0],r=t?`${Bi(t.error)} at offset ${t.offset}`:`unknown parse error`;throw Error(`Cannot update ${e.path}: invalid JSONC (${r}).`)}if(!zs(r))throw Error(`Cannot update ${e.path}: config root must be an object.`);if(r.mcpServers!==void 0&&!zs(r.mcpServers))throw Error(`Cannot update ${e.path}: mcpServers must be an object.`);return{source:t,config:r}}function Vs(e){return e.includes(`mcp`)&&e.includes(`serve`)&&e.some(e=>e===`seo`||e.startsWith(`seo@`)||e===`@seo/cli`)}function Hs(e,t){return typeof e.command!=`string`||t.length!==3||t[1]!==`mcp`||t[2]!==`serve`||!zs(e.env)?!1:e.env.SEO_MCP_CONFIGURED_BY===`seo`}function Us(e){if(!zs(e)||!Array.isArray(e.args))return!1;let t=e.args.filter(e=>typeof e==`string`);return Vs(t)||Hs(e,t)}function Ws(e){let t=Li(e.source,Ri(e.source,[`mcpServers`,`seo`],e.value,{formattingOptions:Fs}));Hr(e.target.path)&&Rs(e.target.path),Ls(e.target.path,t)}function Gs(e,t){let n=xi(e,t,{encoding:`utf8`});return{status:n.status,stdout:n.stdout??``,stderr:n.stderr??``,error:n.error}}function Ks(e){let t=e.replaceAll(`\\`,`/`).toLowerCase();return t.includes(`/_npx/`)||t.includes(`/.npm/_npx/`)||t.includes(`/.cache/pnpm/dlx/`)}function qs(e){if(!e||!Hr(e))return;let t=Kr(e);return Ks(t)?void 0:t}function Js(e={}){let t=qs(e.cliPath??process.argv[1]);if(t)return{command:e.nodePath??process.execPath,args:[t,`mcp`,`serve`]};let n=`seo@${e.version??Fr}`;return(e.platform??process.platform)===`win32`?{command:`cmd`,args:[`/c`,`npx`,`-y`,n,`mcp`,`serve`]}:{command:`npx`,args:[`-y`,n,`mcp`,`serve`]}}function Ys(e=!1,t=Js()){let n=rr(),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 Xs(e){return e.includes(`SEO_MCP_CONFIGURED_BY`)||/args:\s+-y\s+(?:seo(?:@\S+)?|@seo\/cli)\s+mcp\s+serve\b/.test(e)}function Zs(e,t,n){let r=Ms(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&&!Xs(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=rr(),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 Qs(e,t={}){let n={runCommand:t.runCommand??Gs,reinstall:t.reinstall??!1,serverCommand:t.serverCommand??Js()};if(e.kind===`native`)return Zs(e,`install`,n);let{source:r,config:i}=Bs(e),a=i.mcpServers?.seo,o=Ys(e.includeType,n.serverCommand);if(_i(a,o)&&!n.reinstall)return{client:e.client,path:e.path,changed:!1};if(a!==void 0&&!Us(a))throw Error(`Cannot install SEO MCP: ${e.path} already has an unmanaged mcpServers.seo entry.`);return Ws({target:e,source:r,value:o}),{client:e.client,path:e.path,changed:!0}}function $s(e,t={}){if(e.kind===`native`)return Zs(e,`uninstall`,{runCommand:t.runCommand??Gs,reinstall:!1,serverCommand:Js()});if(!Hr(e.path))return{client:e.client,path:e.path,changed:!1};let{source:n,config:r}=Bs(e),i=r.mcpServers;if(i?.seo===void 0)return{client:e.client,path:e.path,changed:!1};if(!Us(i.seo))throw Error(`Cannot uninstall SEO MCP: ${e.path} has an unmanaged mcpServers.seo entry.`);return Ws({target:e,source:n,value:void 0}),{client:e.client,path:e.path,changed:!0}}function ec(e={}){let t=xi(`npx`,[`-y`,`skills`,`add`,`iannuttall/seo`],{stdio:e.quiet?`ignore`:`inherit`});return t.error?{status:`failed`,error:t.error.message}:t.status===0?{status:`installed`}:{status:`failed`,error:`npx skills add exited with status ${t.status??`unknown`}.`}}function tc(e,t=[]){let n=[{value:`google`,label:`Google Analytics`,hint:`Use a property available to your Google login`},...t.filter(e=>e.capabilities.some(e=>e.id===`landing-page-visits`)&&e.id!==`google`).map(e=>({value:`extension:${e.id}`,label:e.displayName,hint:`Installed provider from ${e.package}`}))];return e?[{value:`keep`,label:`Keep ${Qn(e)}`,hint:`Leave this project connection unchanged`},...n,{value:`remove`,label:`Remove traffic analytics`}]:[...n,{value:`skip`,label:`Skip traffic analytics`}]}async function nc(e,t={},n=!0){if(!e.capabilities.some(e=>e.id===`landing-page-visits`))throw new w(`INVALID_INPUT`,`Provider ${e.id} does not supply landing-page visits.`);let r={...t},i={};for(let t of e.connection.fields.filter(e=>e.kind===`account`)){if(r[t.id]?.trim())continue;if(!n){if(t.required!==!1)throw new w(`INVALID_INPUT`,`${t.label} is required.`);continue}let e=H(await Ni({message:t.label,validate:e=>t.required!==!1&&!e?.trim()?`${t.label} is required`:void 0})).trim();e&&(r[t.id]=e)}let a=e.connection.normalizeAccount?e.connection.normalizeAccount(r):r;try{Object.assign(i,await ht({providerId:e.id,account:a,fields:e.connection.fields}))}catch{}if(e.id===`clicky`&&!i.sitekey){let e=await Ct(a.siteId??``);e&&(i.sitekey=e.siteKey)}for(let t of e.connection.fields.filter(e=>e.kind===`secret`)){let r=t.envVar?process.env[t.envVar]?.trim():void 0;if(r&&(i[t.id]=r),i[t.id])continue;if(!n)throw new w(`AUTH_REQUIRED`,t.envVar?`Set ${t.envVar} to connect ${e.displayName}.`:`${t.label} is required to connect ${e.displayName}.`);let a=H(await ji({message:t.label,validate:e=>t.required!==!1&&!e?.trim()?`${t.label} is required`:void 0})).trim();a&&(i[t.id]=a)}return e.connection.verificationNotice&&D(e.connection.verificationNotice,`Connection check`),await ee({providerId:e.id,account:a,credentials:i}),Object.keys(i).length>0&&await ae({providerId:e.id,account:a,credentials:i}),{provider:`extension`,extension:{providerId:e.id,account:a}}}function rc(e){let t=e.canSkip?[{value:`skip`,label:`Skip for now`}]:[];return e.sharedConfigured||e.byoConfigured?[{value:`login`,label:`Connect Google`,hint:`Opens your browser for read-only Search Console and Google Analytics access`},...t]:[{value:`setup`,label:`Set up Google login for local development`,hint:`This source checkout does not include the public app credentials`},...t]}async function ic(e,t){let n=[],r=!0,i=0;async function a(){for(;i<e.length;){let a=e[i];if(i+=1,a)try{let e=await Wt(a.property,t);n.push(...e.filter(e=>e.webStreamData).map(e=>({account:a.account,property:a.property,propertyName:a.label,stream:e})))}catch{r=!1}}}return await Promise.all(Array.from({length:Math.min(e.length,4)},()=>a())),{candidates:n,complete:r}}async function ac(e){let t=await v();if(t.activeMode===`service-account`)return{status:`service-account`,accountEmail:t.identity};let n=typeof e[`search-console-account`]==`string`?e[`search-console-account`]:void 0,r=Se();if(n){let e=r.find(e=>e.accountEmail.toLowerCase()===n.toLowerCase());if(!e)throw new w(`AUTH_REQUIRED`,`Google account ${n} is not connected. Run \`seo auth login\` first.`);return{status:`already-connected`,accountEmail:e.accountEmail}}if(e[`skip-auth`])return{status:`skipped`};if(!R({json:e.json===!0})){if(t.tokens)return{status:`already-connected`,accountEmail:t.tokens.account_email};throw new w(`AUTH_REQUIRED`,"Not logged in. Run `seo auth login`, or pass --skip-auth to save a project profile without connecting Google.")}if(r.length>0){let n=H(await Mi({message:`Google account for Search Console`,options:[...r.map(e=>({value:{type:`account`,accountEmail:e.accountEmail},label:e.accountEmail,hint:e.active?`Current default`:void 0})),{value:{type:`connect`},label:`Connect another Google account`},...typeof e.site==`string`?[{value:{type:`skip`},label:`Skip for now`}]:[]]}));if(n.type===`account`)return wt(n.accountEmail),{status:`already-connected`,accountEmail:n.accountEmail};if(n.type===`skip`)return{status:`skipped`};if(t.sharedConfigured||t.byoConfigured){let e=await x();return D(`Connected as ${e.account_email}. seo has read-only access and cannot change your site.`,`Google connected`),{status:`connected`,accountEmail:e.account_email}}}let i=H(await Mi({message:`Connect Google now?`,options:rc({sharedConfigured:t.sharedConfigured,byoConfigured:t.byoConfigured,canSkip:typeof e.site==`string`&&e.site.length>0})}));if(i===`skip`)return{status:`skipped`};i===`setup`&&Ke({clientId:H(await Ni({message:`Google Desktop OAuth client ID`,validate:e=>e?void 0:`Client ID is required`})),clientSecret:H(await ji({message:`Google Desktop OAuth client secret`,validate:e=>e?void 0:`Client secret is required`}))});let a=await x();return D(`Connected as ${a.account_email}. seo has read-only access and cannot change your site.`,`Google connected`),{status:`connected`,accountEmail:a.account_email}}async function oc(e){if(e.property)return{propertyId:e.property,accountEmail:e.accountEmail,selection:`explicit`,reason:`Set with --google-analytics-property.`};if(!(e.interactive??R()))return;let t=(await ar(e.accountEmail).catch(()=>[])).flatMap(e=>e.propertySummaries.map(t=>({property:Lt(t.property),label:t.displayName??t.property,account:e.displayName??e.account})));if(!t.length)return;let{candidates:n,complete:r}=await ic(t,e.accountEmail),i=r?ia(e.site,n):[],a=new Map;for(let e of i)a.set(e.property,e);let o=[...a.values()];if(o.length===1){let t=o[0];return t?{propertyId:t.property,accountEmail:e.accountEmail,selection:`matched`,reason:aa(t,e.site)}:void 0}let s=o.length?o.map(e=>({property:e.property,label:e.propertyName,account:e.account,match:e})):t,c=H(await Mi({message:o.length?`Several Google Analytics properties match this site. Which property should seo use?`:r?`No Google Analytics web stream clearly matches this site. Attach a property?`:`Some Google Analytics web streams could not be read. Attach a property?`,options:[{value:``,label:`Skip Google Analytics for now`},...s.map(t=>({value:t,label:`${t.label} (${t.property})`,hint:t.match?aa(t.match,e.site):t.account}))]}));if(c)return{propertyId:c.property,accountEmail:e.accountEmail,selection:`manual`,reason:c.match?aa(c.match,e.site):r?`Selected ${c.label} during setup. Its web stream did not clearly match ${e.site}.`:`Selected ${c.label} during setup. seo could not read every Google Analytics web stream, so it did not guess a match.`}}async function sc(e){if(e.googleProperty&&e.clickySiteId)throw new w(`INVALID_INPUT`,`Pass either --google-analytics-property or --clicky-site-id, not both.`);let t=e.interactive??R();if(e.clickySiteId){let n=(await l()).registry.get(`clicky`);if(!n)throw new w(`PROVIDER_UNAVAILABLE`,"The Clicky provider package is not installed. Run `seo providers install @seoskill/clicky-provider`.");return nc(n,{siteId:e.clickySiteId},t)}if(e.googleProperty){let n=await cc({requested:e.googleAccount,interactive:t}),r=await oc({property:e.googleProperty,accountEmail:n,site:e.site,interactive:t});return r?{provider:`google`,google:r}:void 0}if(!t)return;let n=await l(),r=H(await Mi({message:`Traffic analytics`,options:tc(e.current,n.registry.list())}));if(r===`keep`)return;if(r===`remove`)return{provider:`none`};if(r===`skip`)return;if(r.startsWith(`extension:`)){let e=r.slice(10),t=n.registry.get(e);if(!t)throw new w(`INVALID_INPUT`,`Provider ${e} is installed but could not be loaded. Run \`seo providers doctor\`.`);return nc(t)}let i=await oc({accountEmail:await cc({interactive:t}),site:e.site,interactive:t});return i||D(`No Google Analytics property was available for this Google login. You can connect one later.`,`Traffic analytics skipped`),i?{provider:`google`,google:i}:void 0}async function cc(e){let t=await v();if(t.activeMode===`service-account`)return t.identity;let n=Se();if(e.requested){let t=n.find(t=>t.accountEmail.toLowerCase()===e.requested?.toLowerCase());if(!t)throw new w(`AUTH_REQUIRED`,`Google account ${e.requested} is not connected. Run \`seo auth login\` first.`);return t.accountEmail}if(!e.interactive)return n.find(e=>e.active)?.accountEmail;if(n.length===0)return(await x()).account_email;let r=H(await Mi({message:`Google account for Google Analytics`,options:[...n.map(e=>({value:{type:`account`,accountEmail:e.accountEmail},label:e.accountEmail,hint:e.active?`Current default`:void 0})),{value:{type:`connect`},label:`Connect another Google account`}]}));if(r.type===`account`)return r.accountEmail;let i=await x();return D(`Connected as ${i.account_email}. seo has read-only access and cannot change your site.`,`Google connected`),i.account_email}async function lc(e){return e[`skip-skill`]||!R({json:e.json===!0})?{status:`skipped`}:H(await Ti({message:`Install the SEO skill so coding agents know how to run reports?`,initialValue:!0}))?ec():{status:`declined`}}async function uc(e){if(e[`skip-mcp`]||!R({json:e.json===!0}))return[];let t=Ps();if(t.length===0||!H(await Ti({message:`Install seo as an MCP server too?`,initialValue:!0})))return[];let n=H(await ki({message:`Which MCP clients?`,options:t.map(e=>({value:e.client,label:e.label,hint:e.path})),initialValues:t.map(e=>e.client)}));return t.filter(e=>n.includes(e.client)).map(e=>{try{return Qs(e)}catch(t){return{client:e.client,path:e.path,changed:!1,error:t instanceof Error?t.message:String(t)}}})}function dc(e){return e.status===`installed`?`installed`:e.status===`failed`?`install failed`:`not installed`}function fc(e){return/^[A-Za-z0-9_./:@-]+$/.test(e)?e:`'${e.replaceAll(`'`,`'\\''`)}'`}function pc(){process.stdout.write(`
141
- `),To({title:`Help other people find SEO Skill`,body:`If SEO Skill saved you time, a GitHub star helps other people find it.`,command:`https://github.com/iannuttall/seo`})}function mc(e){let t=e.filter(e=>e.error).length,n=e.length-t;return e.length===0?`not installed`:t===0?`${n} installed`:n===0?`${t} skipped`:`${n} installed, ${t} skipped`}function hc(e){let t=e.filter(e=>!!e.error);if(t.length!==0)return t.map(e=>`${e.client}: ${e.error}`).join(`
142
- `)}async function gc(e){let t=I(e);if(t||Ei(process.argv[2]===`start`?`seo start`:`seo setup`),e[`dry-run`]){let e=[`seo auth login`,`seo start`,`seo report --project acme`,`seo refresh-priorities --project acme --verify-content`,`seo technical-watch --project acme`];t?z({dryRun:!0,next:e}):(D(e.join(`
143
- `),`This setup will guide you through`),Ai(`Dry run complete.`));return}Bn();let n=R({json:t});n&&D(`A project profile remembers one site and its report defaults so future commands stay short.`,`Project profiles`);let r=We(),i=await ks({clients:r,interactive:n,requestedId:k(e.id),skipProfile:j(e[`skip-profile`])}),a=i.mode===`update`?i.client:void 0,o=k(e.site)??a?.siteUrl,s={site:o,options:{allowDefault:i.mode===`update`,json:t,refresh:j(e.refresh)}},c=n?void 0:await Ro(s),l=await ac(o?{...e,site:o}:e),u=l.status,d=c??await Ro({...s,options:{...s.options,account:l.accountEmail}}),f=xs(d);if(i.mode===`skip`){let n=await uc(e),r=await lc(e),i=`--site ${fc(d)}`,a=[`seo report ${i}`,`seo refresh-priorities ${i} --verify-content`,`seo technical-watch ${i}`],o={site:d,auth:u,mcp:n,skill:r,next:a};if(t){z(o);return}B([[`Project profile`,`not saved`],[`GSC property`,d],[`Auth`,u],[`MCP`,mc(n)],[`SEO skill`,dc(r)]]);let s=hc(n);s&&D(s,`MCP setup skipped`),r.error&&D(r.error,`SEO skill install failed`),D(a.join(`
144
- `),`Try next`),Ai(`Setup complete.`),pc();return}let p=k(e.name)??(n?H(await Ni({message:`Project name`,placeholder:a?.name??f,defaultValue:a?.name??f})):a?.name??f),m=a?.id??(i.mode===`create`&&i.requestedId?Ss(i.requestedId):Os(p,r)),h=a?.startUrl??bs(d)??``,g=k(e.url)??(n?H(await Ni({message:`Website URL to crawl`,placeholder:h||`https://example.com`,defaultValue:h})):h||void 0),_=e.urls===void 0?a?.watchUrls??[]:F(e.urls),v=await sc({googleProperty:k(e[`google-analytics-property`]),googleAccount:k(e[`google-analytics-account`]),clickySiteId:k(e[`clicky-site-id`]),current:Jn(a),site:d,interactive:n}),y=v?.provider===`google`?v.google:void 0,b=v?.provider===`extension`?v.extension:void 0,ee=a?.brandTerms??ur({id:m,name:p,siteUrl:d}),x=e.brand===void 0?ee:F(e.brand),te=M(e[`report-day`])??a?.reportDay??1,ne=M(e.weekday)??a?.technicalWeekday??1,re=j(e.default)??a?.isDefault??!0,S={name:p,siteUrl:d,startUrl:g,watchUrls:_,brandTerms:x,analytics:v===void 0?void 0:v.provider===`google`?{...a?.analytics,selected:`google`,google:{propertyId:v.google.propertyId}}:v.provider===`extension`?{...a?.analytics,selected:`extension:${v.extension.providerId}`,extensions:{...a?.analytics.extensions,[v.extension.providerId]:{account:v.extension.account}}}:{},googleAccounts:{...a?.googleAccounts,...l.accountEmail?{searchConsole:l.accountEmail}:{},...y?.accountEmail?{googleAnalytics:y.accountEmail}:{}},reportDay:te,technicalWeekday:ne,isDefault:re},C=a?_n(a.id,S):$t({id:m,...S}),ie=await uc(e),ae=await lc(e),oe=[`seo report --project ${C.id}`,`seo refresh-priorities --project ${C.id} --verify-content`,`seo technical-watch --project ${C.id}`],se={client:C,site:d,auth:u,googleAnalytics:y,extension:b,mcp:ie,skill:ae,next:oe};if(t){z(se);return}let ce=Jn(C);B([[`Project profile`,`${C.name} (${C.id})`],[`GSC property`,C.siteUrl],[`Search Console account`,C.googleAccounts?.searchConsole??`default Google account`],[`Crawl URL`,C.startUrl??`not set`],[`Watch URLs`,String(C.watchUrls.length)],[`Brand terms`,C.brandTerms.join(`, `)||`not set`],[`Traffic analytics`,ce?Qn(ce):`not connected (optional)`],...y?[[`Google Analytics selection`,y.reason]]:[],...C.googleAccounts?.googleAnalytics?[[`Google Analytics account`,C.googleAccounts.googleAnalytics]]:[],[`Auth`,u],[`MCP`,mc(ie)],[`SEO skill`,dc(ae)]]);let le=hc(ie);le&&D(le,`MCP setup skipped`),ae.error&&D(ae.error,`SEO skill install failed`),D(oe.join(`
145
- `),`Try next`),Ai(`Setup complete.`),pc()}const _c=E({meta:{name:`setup`,description:`Guided setup for auth, one project profile, MCP, and next commands`},args:{id:{type:`string`,description:`Short stable project id.`},name:{type:`string`,description:`Human project name.`},site:{type:`string`,description:`GSC property URL, for example sc-domain:example.com.`},"search-console-account":{type:`string`,description:`Saved Google account email for Search Console.`},url:{type:`string`,description:`Default technical crawl start URL.`},urls:{type:`string`,description:`Comma-separated URLs to watch with URL Inspection.`},"google-analytics-property":{type:`string`,description:`Optional Google Analytics property ID.`},"google-analytics-account":{type:`string`,description:`Saved Google account email for Google Analytics.`},"clicky-site-id":{type:`string`,description:`Optional Clicky site ID. Set SEO_CLICKY_SITEKEY outside a terminal.`},brand:{type:`string`,description:`Comma-separated branded query terms to exclude by default.`},"report-day":{type:`string`,description:`Preferred monthly report day. Defaults to 1.`},weekday:{type:`string`,description:`Preferred technical-watch weekday. Defaults to Monday.`},default:{type:`boolean`,description:`Make this the default project.`},"skip-profile":{type:`boolean`,default:!1,description:`Do not save a project profile during setup.`},"skip-auth":{type:`boolean`,default:!1,description:`Skip Google sign-in during setup.`},"skip-mcp":{type:`boolean`,default:!1,description:`Skip MCP install prompts.`},"skip-skill":{type:`boolean`,default:!1,description:`Skip the SEO skill install prompt.`},refresh:{type:`boolean`,default:!1,description:`Refresh GSC property discovery.`},"dry-run":{type:`boolean`,default:!1,description:`Show what setup does without changing files.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>gc(e)}),vc=E({..._c,meta:{name:`start`,description:`Start here: connect first-party data, save a profile, and get the first report commands`}}),yc=E({..._c,meta:{name:`setup`,description:`Guided setup for one saved project profile`}}),bc=E({meta:{name:`brand`,description:`Detect and manage project brand query terms`},subCommands:{detect:E({meta:{name:`detect`,description:`Suggest branded query terms from GSC navigational queries`},args:{client:{type:`string`,description:`Legacy alias for --project.`},project:{type:`string`,description:`Saved project id or name. Defaults to default.`},site:{type:`string`,description:`GSC property URL, for example sc-domain:example.com.`},days:{type:`string`,description:`Detection window length in days. Defaults to 28.`},limit:{type:`string`,description:`Maximum candidate terms. Defaults to 10.`},"min-impressions":{type:`string`,description:`Minimum query impressions. Defaults to 10.`},save:{type:`boolean`,default:!1,description:`Save suggested terms to the selected project.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`},refresh:{type:`boolean`,default:!1,description:`Bypass local GSC cache.`}},run:async({args:e})=>{let t=I(e),n=vt(A(e)),r=await Ln({site:await Ro({site:k(e.site)??n?.siteUrl,options:{json:t,refresh:j(e.refresh)}}),id:n?.id,name:n?.name,days:M(e.days),limit:M(e.limit),minImpressions:M(e[`min-impressions`]),refresh:j(e.refresh)}),i=j(e.save)&&n?$t({id:n.id,name:n.name,siteUrl:n.siteUrl,startUrl:n.startUrl,watchUrls:n.watchUrls,analytics:n.analytics,brandTerms:r.suggestedTerms,reportDay:n.reportDay,technicalWeekday:n.technicalWeekday,isDefault:n.isDefault}):void 0;if(j(e.save)&&!n)throw Error(`Pass --project to save detected brand terms.`);if(t){z({...r,saved:i});return}B([[`Property`,r.site],[`Derived terms`,r.derivedTerms.join(`, `)],[`Suggested terms`,r.suggestedTerms.join(`, `)],[`Saved`,i?i.id:`no`]]),V([`Term`,`Score`,`Evidence`],r.candidates.map(e=>[e.term,e.score,e.evidence.map(e=>`${e.query} (${Math.round(e.clicks)} clicks)`).join(`, `)]))}})}}),xc={project:{type:`string`,description:`Saved project id or name.`},client:{type:`string`,description:`Legacy alias for --project.`}},Sc={type:`boolean`,default:!1,description:`Print machine-readable JSON.`};async function Cc(e){let t=await W({project:A(e),options:{json:I(e)}});if(!t)throw new w(`INVALID_INPUT`,`Select a saved project with --project or make one the default.`);return t}async function wc(e){let t=k(e.keyword),n=k(e.keywords)?.split(`,`).map(e=>e.trim()).filter(Boolean),r=k(e.file),i=[...t?[t]:[],...n??[]];if(r){let e=await mi(r).catch(()=>void 0);if(!e?.isFile()||e.size>1e5)throw new w(`INVALID_INPUT`,`Keyword files must be regular files no larger than 100 KB.`);i.push(...(await ui(r,`utf8`)).split(/\r?\n/u).map(e=>e.trim()).filter(Boolean))}if(i.length===0)throw new w(`INVALID_INPUT`,`Pass --keyword, comma-separated --keywords, or a newline-delimited --file.`);return i}const Tc=E({meta:{name:`add`,description:`Add or update keywords in a saved set`},args:{...xc,set:{type:`string`,description:`Keyword set id or name.`,required:!0},keyword:{type:`string`,description:`One keyword.`},keywords:{type:`string`,description:`Comma-separated keywords.`},file:{type:`string`,description:`Newline-delimited keyword file.`},tags:{type:`string`,description:`Comma-separated tags applied to every keyword.`},"target-url":{type:`string`,description:`Existing target page for every keyword.`},"proposed-url":{type:`string`,description:`Proposed page for every keyword.`},json:Sc},run:async({args:e})=>{let t=k(e[`target-url`]),n=k(e[`proposed-url`]);if(t&&n)throw new w(`INVALID_INPUT`,`Use either --target-url or --proposed-url.`);let r=br({projectId:(await Cc(e)).id,idOrName:k(e.set)??``,items:(await wc(e)).map(r=>({keyword:r,tags:F(e.tags),...t||n?{page:{kind:t?`target`:`proposed`,url:t??n??``}}:{}}))});I(e)?z(r):B([[`Set`,r.setId],[`Added`,String(r.added)],[`Updated`,String(r.updated)],[`Keywords`,String(r.keywordCount)]])}}),Ec=E({meta:{name:`create`,description:`Create an empty saved keyword set`},args:{...xc,name:{type:`string`,description:`Keyword set name.`,required:!0},country:{type:`string`,description:`Two-letter country code.`,required:!0},language:{type:`string`,description:`Search language code.`,required:!0},"search-engine":{type:`string`,default:`google`,description:`Search engine: google or bing.`},"location-code":{type:`string`,description:`Optional provider location code.`},location:{type:`string`,description:`Optional canonical location name.`},device:{type:`string`,description:`Optional device: desktop or mobile.`},provider:{type:`string`,description:`Optional preferred provider.`},"source-report":{type:`string`,description:`Report that produced the initial research.`},json:Sc},run:async({args:e})=>{let t=M(e[`location-code`]);if(t!==void 0&&!Number.isSafeInteger(t))throw new w(`INVALID_INPUT`,`--location-code must be an integer.`);let n=await Cc(e),r=tt({projectId:n.id,name:k(e.name)??``,market:{searchEngine:k(e[`search-engine`]),countryCode:k(e.country),languageCode:k(e.language),...t!==void 0||k(e.location)?{location:{code:t,name:k(e.location)}}:{},device:k(e.device)},provider:k(e.provider),sourceReport:k(e[`source-report`])});I(e)?z(r):B([[`Created`,r.name],[`Project`,n.name],[`Keywords`,`0`],[`Id`,r.id]])}}),Dc=E({meta:{name:`delete`,description:`Delete a saved keyword set`},args:{...xc,set:{type:`string`,description:`Keyword set id or name.`,required:!0},yes:{type:`boolean`,default:!1,description:`Confirm permanent deletion.`},json:Sc},run:async({args:e})=>{if(!j(e.yes))throw new w(`INVALID_INPUT`,`Pass --yes to delete the keyword set.`);let t=await Cc(e),n=k(e.set)??``;if(!h({projectId:t.id,idOrName:n}))throw new w(`INVALID_INPUT`,`Keyword set not found: ${n}`);let r={deleted:!0,projectId:t.id,set:n};I(e)?z(r):process.stdout.write(`Deleted keyword set ${n}.\n`)}}),Oc=E({meta:{name:`export`,description:`Export a bounded saved keyword set`},args:{...xc,set:{type:`string`,description:`Keyword set id or name.`,required:!0},format:{type:`string`,default:`csv`,description:`Output format: csv or json.`},output:{type:`string`,description:`Output file, or - for stdout.`,required:!0},tag:{type:`string`,description:`Only export keywords with this tag.`},limit:{type:`string`,default:`1000`,description:`Rows to export, from 1 to 1000.`},offset:{type:`string`,default:`0`,description:`Rows to skip.`}},run:async({args:e})=>{let t=k(e.format);if(t!==`csv`&&t!==`json`)throw new w(`INVALID_INPUT`,`--format must be csv or json.`);let n=M(e.limit),r=M(e.offset);if(!Number.isSafeInteger(n)||!Number.isSafeInteger(r))throw new w(`INVALID_INPUT`,`--limit and --offset must be integers.`);let i=wr({projectId:(await Cc(e)).id,idOrName:k(e.set)??``,tag:k(e.tag),limit:n,offset:r}),a=t===`json`?`${JSON.stringify(i,null,2)}\n`:kc(i),o=k(e.output)??``;if(o===`-`)process.stdout.write(a);else{try{await gi(o,a,{encoding:`utf8`,flag:`wx`,mode:384})}catch(e){throw e.code===`EEXIST`?new w(`INVALID_INPUT`,`Refusing to overwrite the existing file at ${o}.`):e}process.stdout.write(`Wrote ${i.items.length} keywords to ${o}.\n`)}}});function kc(e){return p(e.items.map(e=>{let t=e.latestMetric?.metric;return{keyword:e.keyword,normalized_keyword:e.normalizedKeyword,tags:e.tags.join(`; `),page_kind:e.page?.kind,page_url:e.page?.url,metric_provider:e.latestMetric?.provider,metric_observed_at:e.latestMetric?.observedAt,monthly_search_volume:Ac(t?.monthlySearchVolume),cpc_usd:Ac(t?.cpcUsd),paid_competition:Ac(t?.paidCompetition),keyword_difficulty:Ac(t?.keywordDifficulty),intent:t?.intent.state===`observed`?t.intent.value:null,result_count:Ac(t?.resultCount)}}),[`keyword`,`normalized_keyword`,`tags`,`page_kind`,`page_url`,`metric_provider`,`metric_observed_at`,`monthly_search_volume`,`cpc_usd`,`paid_competition`,`keyword_difficulty`,`intent`,`result_count`])}function Ac(e){return e?.state===`observed`?e.value:null}function jc(e){if(e.length===0){process.stdout.write(`No keyword sets saved for this project.
146
- `);return}V([`Name`,`Keywords`,`Market`,`Provider`,`Updated`],e.map(e=>[e.name,e.keywordCount,`${e.market.countryCode}/${e.market.languageCode}`,e.provider??`automatic`,e.updatedAt]))}function Mc(e){if(B([[`Name`,e.set.name],[`Project`,e.set.projectId],[`Keywords`,String(e.set.keywordCount)],[`Market`,`${e.set.market.searchEngine} ${e.set.market.countryCode}/${e.set.market.languageCode}`],[`Provider`,e.set.provider??`automatic`],[`Last refreshed`,e.set.lastRefreshedAt??`never`]]),e.items.length===0){process.stdout.write(`
129
+ `)}function Co(e,t,n){let r=t.filter(e=>e.trim().length>0).flatMap(e=>I(e,n.columns));return[n.colors.bold(e),...r].join(`
130
+ `)}function wo(e,t,n){let r=t.flatMap(e=>I(e,Math.max(1,n.columns-2)).map((e,t)=>`${t===0?`- `:` `}${e}`));return[n.colors.bold(e),...r].join(`
131
+ `)}function To(e,t){return e.length===0?t.colors.dim(`No parameters.`):So(e.map(e=>({title:e.name,description:e.description,meta:[e.type,e.required?`required`:`optional`]})),t,{empty:`No parameters.`})}function L(e={}){return!!(!e.json&&process.stdin.isTTY&&process.stdout.isTTY&&!process.env.CI)}function R(e){process.stdout.write(`${JSON.stringify(e,null,2)}\n`)}function z(e){let t=ao(e,Ga());t&&process.stdout.write(`${t}\n`)}function B(e,t){process.stdout.write(`${io(e,t,Ga())}\n`)}function Eo(e){process.stdout.write(`${oo(e,Ga())}\n`)}function Do(e,t){process.stdout.write(`${go(e,Ga(),t)}\n`)}function Oo(e){process.stdout.write(`${_o(e,Ga())}\n`)}function ko(e){process.stdout.write(`${po(e,Ga())}\n`)}function Ao(e,t){process.stdout.write(`${xo(e,Ga(),t)}\n`)}function jo(e,t){process.stdout.write(`${So(e,Ga(),t)}\n`)}function Mo(e,...t){process.stdout.write(`${Co(e,t,Ga())}\n`)}function No(e,t){process.stdout.write(`${wo(e,t,Ga())}\n`)}function Po(e){process.stdout.write(`${To(e,Ga())}\n`)}function Fo(e){return e<1024?`${e} B`:e<1024*1024?`${(e/1024).toFixed(1)} KB`:`${(e/(1024*1024)).toFixed(1)} MB`}function V(e){return typeof e==`symbol`&&process.exit(1),e}function Io(e){Tn(e?.googleAccounts)}function Lo(e,t){let n=t.trim().toLowerCase();return!n||e.some(e=>e.toLowerCase().includes(n))}async function Ro(e){let t=e.choices;if(e.choices.length>8){let n=V(await Pi({message:e.searchMessage,placeholder:`Type part of the name or URL, or leave blank`}));if(t=e.choices.filter(t=>Lo(e.searchValues(t),n)),!t.length)throw new C(`INVALID_INPUT`,e.emptyMessage)}return V(await Ni({message:e.message,options:t.map(t=>{let n=e.hint?.(t);return{value:t,label:e.label(t),...n?{hint:n}:{}}})}))}async function zo(e){if(e.site)return e.site;let t=Zn(),n=!!t.defaultSite&&(t.clients.some(e=>e.siteUrl===t.defaultSite)||t.sites.some(e=>e.siteUrl===t.defaultSite));if(e.options?.allowDefault!==!1&&t.defaultSite&&n)return Io(t.clients.find(e=>e.isDefault&&e.siteUrl===t.defaultSite)),t.defaultSite;if(e.options?.json||!L())throw new C(`INVALID_INPUT`,t.defaultSite?`Saved default site ${t.defaultSite} does not match any saved project. Pass --site or --project, or run \`seo start\` to save a new default.`:"No site selected. Pass --site, use --project on supported commands, or run `seo start` in a terminal.");let r=await m(e.options?.refresh,e.options?.account);if(!r.length)throw new C(`PROPERTY_NOT_FOUND`,`No Search Console properties found for this Google login.`);return r.length===1?r[0]?.siteUrl??``:(await Ro({message:`Choose a Search Console property`,searchMessage:`Search Search Console properties`,emptyMessage:`No Search Console properties matched that search.`,choices:r,label:e=>e.siteUrl,hint:e=>e.permissionLevel,searchValues:e=>[e.siteUrl,e.permissionLevel??``]})).siteUrl}async function H(e){if(e.client&&e.project&&e.client!==e.project)throw new C(`INVALID_INPUT`,`Use either --project or --client, not both.`);let t=e.project??e.client;if(t){let e=yt(t);if(!e)throw new C(`INVALID_INPUT`,`Project not found: ${t}`);return Io(e),{client:e,site:e.siteUrl}}let n=yt();return!e.site&&n?(Io(n),{client:n,site:n.siteUrl}):{site:await zo({site:e.site,options:e.options})}}async function U(e){if(e.client&&e.project&&e.client!==e.project)throw new C(`INVALID_INPUT`,`Use either --project or --client, not both.`);let t=e.project??e.client;if(t){let e=yt(t);if(!e)throw new C(`INVALID_INPUT`,`Project not found: ${t}`);return Io(e),e}let n=Ge();if(!n.length)return;let r=yt();if(r)return Io(r),r;if(e.options?.json||!L())return;let i=await Ro({message:`Choose a project`,searchMessage:`Search projects`,emptyMessage:`No projects matched that search.`,choices:n,label:e=>e.name,hint:e=>e.siteUrl,searchValues:e=>[e.id,e.name,e.siteUrl]});return Io(i),i}async function Bo(e){if(e.property)return e.property;let t=Zn();if(t.analytics.google.defaultPropertyId)return t.analytics.google.defaultPropertyId;if(e.options?.json||!L())throw new C(`INVALID_INPUT`,`No Google Analytics property selected. Pass --property or run this command in a terminal to choose one.`);let n=(await or(e.options?.account)).flatMap(e=>e.propertySummaries.map(t=>({property:Rt(t.property),displayName:t.displayName??t.property,account:e.displayName??e.account})));if(!n.length)throw new C(`PROPERTY_NOT_FOUND`,`No Google Analytics properties found for this Google login.`);return n.length===1?n[0]?.property??``:(await Ro({message:`Choose a Google Analytics property`,searchMessage:`Search Google Analytics properties`,emptyMessage:`No Google Analytics properties matched that search.`,choices:n,label:e=>`${e.displayName} (${e.property})`,hint:e=>e.account,searchValues:e=>[e.property,e.displayName,e.account]})).property}async function Vo(e,t={}){let n=O(e[`report-id`]);if(n){let e=a(n);if(!e)throw Error(`No saved crawl report found for ${n}.`);return e}let r=k(e),i=O(e.site),o=r||i?await H({client:r,site:i,options:t}):void 0,s=Re(o?.site);if(!s)throw Error(o?.site?`No saved crawl report found for ${o.site}. Run \`seo crawl --project ${o.client?.id??o.site} --save\` first.`:"No saved crawl report found. Run `seo crawl <url> --save` first, or pass --report-id.");return s}function Ho(e){let t=t=>e.checks.filter(e=>e.status===t).length,n=t(`fail`),r=t(`warning`),i=t(`unknown`),a=t(`pass`),o=n?`fail`:r?`warning`:i?`unknown`:`pass`;ko({title:`AI search evidence`,target:e.url,status:o,summary:e.headline,metrics:[{label:`Passed`,value:a,status:`pass`},{label:`Review`,value:r,status:`warning`},{label:`Failed`,value:n,status:`fail`},{label:`Unknown`,value:i,status:`unknown`}],sections:e.sections.map(e=>({title:e.title,diagnostics:e.checks.map(e=>({status:e.status,title:e.title,explanation:e.plainEnglish,fix:e.status===`pass`?void 0:e.action,evidence:(e.urls??[]).slice(0,5)}))})),notes:[`Data: ${e.dataStatus}. Report: ${e.reportId}.`,...e.caveats]})}const Uo=T({meta:{name:`ai-readiness`,description:`Review AI-search technical evidence and optional observations`},args:{"report-id":{type:`string`,description:`Saved crawl report id to analyze.`},site:{type:`string`,description:`GSC property URL for selecting the latest saved crawl.`},client:{type:`string`,description:`Legacy alias for --project.`},project:{type:`string`,description:`Saved project id or name.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=F(e),n=re(await Vo(e,{json:t}));if(t){R(n);return}Ho(n)}});function Wo(e){let t=O(e.agentReadinessUrl),n=O(e.url);if(t&&n&&t!==n)throw Error(`Use either the positional URL or --url, not both.`);return t??n}function Go(e){let t=e.summary.failed>0?`fail`:e.summary.warnings>0?`warning`:e.summary.unknown>0?`unknown`:`pass`;ko({title:`AI agent readiness`,target:e.url,status:t,summary:e.headline,metrics:[{label:`Passed`,value:e.summary.passed,status:`pass`},{label:`Review`,value:e.summary.warnings,status:`warning`},{label:`Failed`,value:e.summary.failed,status:`fail`},{label:`Unknown`,value:e.summary.unknown,status:`unknown`}],sections:e.sections.map(e=>({title:e.title,diagnostics:e.checks.filter(e=>![`info`,`notApplicable`].includes(e.status)).map(e=>({status:e.status,title:e.title,explanation:e.plainEnglish,fix:e.status===`pass`?void 0:e.action,evidence:(e.urls??[]).slice(0,5)}))})),notes:[`Profile: ${e.profile}. Data: ${e.dataStatus}. Report: ${e.reportId}.`,...e.caveats]})}const Ko=T({meta:{name:`agent-readiness`,description:`Check content-site access, Markdown alternatives, discovery, and identity for AI agents`},args:{agentReadinessUrl:{type:`positional`,required:!1,description:`Public site URL to check.`},url:{type:`string`,description:`Public site URL to check.`},"report-id":{type:`string`,description:`Saved crawl report id to analyze without fetching again.`},site:{type:`string`,description:`GSC property URL for selecting the latest saved crawl.`},client:{type:`string`,description:`Legacy alias for --project.`},project:{type:`string`,description:`Saved project id or name.`},"max-pages":{type:`string`,description:`Maximum public HTML pages to crawl.`},"fetch-interval-cap":{type:`string`,description:`Maximum page fetches per interval per host.`},"fetch-interval-ms":{type:`string`,description:`Fetch rate interval in milliseconds.`},refresh:{type:`boolean`,default:!1,description:`Bypass local HTTP cache and fetch fresh pages.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=F(e),n=Wo(e),r=O(e[`report-id`]);if(n&&r)throw Error(`Use either a URL or --report-id, not both.`);let i=Ee(n?await(async()=>{let r=k(e),i=O(e.site),a=r||i?await H({client:r,site:i,options:{json:t}}):void 0;return it({url:n,projectId:a?.client?.id,site:a?.site,maxPages:j(e[`max-pages`]),fetchRate:Ba(e),refresh:A(e.refresh),checkAgentDiscovery:!0,useSitemap:!0,checkExternal:!1})})():await Vo(e,{json:t}));if(t){R(i);return}Go(i)}}),qo=`@seoskill/clicky-provider`;function Jo(e){return e===`keychain`?`system keychain`:e===`file`?`private local file`:e===`provider-store`?`provider credential store`:e??`missing`}async function Yo(e){let t=await ue(),n=t.registry.get(`clicky`);if(n)return n;let r=t.failures.find(e=>e.id===`clicky`);if(r)throw new C(`PROVIDER_UNAVAILABLE`,`The Clicky provider package could not load: ${r.message}`);if(!L({json:e.json}))throw new C(`PROVIDER_UNAVAILABLE`,`Clicky is now an optional provider package. Run \`seo providers install ${qo} --yes\`, then run this command again.`);let i;try{i=await b(qo)}catch(e){throw new C(`PROVIDER_UNAVAILABLE`,e instanceof Error?e.message:String(e))}if(E([`${i.package}@${i.version}`,`Publisher: ${i.publisher??`not supplied by npm`}`,`Repository: ${i.repository??`not supplied by npm`}`,`Integrity: ${i.integrity}`].join(`
132
+ `),`Clicky package`),E(`This package can read files, use the network, and run with the same local permissions as seo.`,`Local permissions`),!V(await Ei({message:`Install the Clicky provider package?`,initialValue:!0})))throw new C(`PROVIDER_UNAVAILABLE`,`The Clicky provider package is required for this command.`);await g(i),t=await ue();let a=t.registry.get(`clicky`);if(!a)throw new C(`PROVIDER_UNAVAILABLE`,"The Clicky provider package was installed but could not load. Run `seo providers doctor`.");return a}async function Xo(e){if(e.siteId)return e.siteId;let t=Yn(await U({client:e.project,options:{json:e.json}}));if(t?.provider===`clicky`)return t.siteId;if(t?.provider===`extension`&&t.providerId===`clicky`&&t.account.siteId)return t.account.siteId;throw new C(`INVALID_INPUT`,`No Clicky site is selected. Pass --site-id or use a project connected to Clicky.`)}async function Zo(e,t){let n=await wt(t);if(n)return{siteKey:n.siteKey,source:n.source};try{let n=await gt({providerId:`clicky`,account:{siteId:t},fields:e.connection.fields});return n.sitekey?{siteKey:n.sitekey,source:`provider-store`}:{}}catch{return{}}}const Qo=T({meta:{name:`connect`,description:`Validate and save a Clicky sitekey`},args:{"site-id":{type:`string`,description:`Numeric Clicky site ID.`},project:{type:`string`,description:`Saved project id or name.`},client:{type:`string`,description:`Legacy alias for --project.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let n=F(e),r=await Yo({json:n}),i=L({json:n}),a=k(e),o=a?await U({project:a,options:{json:n}}):void 0;i&&Di(`Connect Clicky`);let s=Yn(o),c=s?.provider===`clicky`?s.siteId:s?.provider===`extension`&&s.providerId===`clicky`?s.account.siteId:void 0,l=O(e[`site-id`])??c??(i?V(await Pi({message:`Clicky site ID`,validate:e=>/^\d{1,30}$/u.test(e?.trim()??``)?void 0:`Enter the numeric site ID from Clicky`})):void 0);if(!l)throw new C(`INVALID_INPUT`,`Pass --site-id when the selected project is not already connected to Clicky.`);let u=await Zo(r,l),d=u.siteKey??(i?V(await Mi({message:`Clicky sitekey`,validate:e=>/^[A-Za-z0-9]{12,64}$/u.test(e?.trim()??``)?void 0:`Enter the sitekey from Clicky`})):void 0);if(!d)throw new C(`AUTH_REQUIRED`,`Run this command in a terminal or set ${Se}.`);await ee({providerId:`clicky`,account:{siteId:l},credentials:{sitekey:d}});let f=u.source??await bt(l,d);await oe({providerId:`clicky`,account:{siteId:l},credentials:{sitekey:d}}),t(`clicky`,{siteId:l});let p=o?fr(o.id,{provider:`extension`,providerId:`clicky`,account:{siteId:l}}):void 0,m={siteId:l,connected:!0,credentialSource:f,project:p?{id:p.id,name:p.name}:void 0};if(n){R(m);return}let h=p?`Connection verified and attached to ${p.name}. Saved in the ${Jo(f)}.`:`Connection verified. Saved in the ${Jo(f)}.`;i?ji(h):process.stdout.write(`${h}\n`)}}),$o=T({meta:{name:`status`,description:`Show the local Clicky connection`},args:{"site-id":{type:`string`,description:`Numeric Clicky site ID.`},project:{type:`string`,description:`Saved project id or name.`},client:{type:`string`,description:`Legacy alias for --project.`},check:{type:`boolean`,default:!1,description:`Verify the saved sitekey with Clicky.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=F(e),n=await Yo({json:t}),r=await Xo({siteId:O(e[`site-id`]),project:k(e),json:t}),i=await Zo(n,r),a=A(e.check);a&&i.siteKey&&await ee({providerId:`clicky`,account:{siteId:r},credentials:{sitekey:i.siteKey}});let o={siteId:r,connected:!!i.siteKey,credentialSource:i.source,liveCheck:a?i.siteKey?`passed`:`unavailable`:`not-requested`};if(t){R(o);return}z([[`Site ID`,r],[`Connected`,o.connected?`yes`:`no`],[`Credential`,Jo(o.credentialSource)],[`Live check`,o.liveCheck]])}}),es=T({meta:{name:`report`,description:`Run a bounded Clicky analytics report`},args:{"site-id":{type:`string`,description:`Numeric Clicky site ID.`},project:{type:`string`,description:`Saved project id or name.`},client:{type:`string`,description:`Legacy alias for --project.`},type:{type:`string`,default:`pages-entrance`,description:`Clicky report type. Defaults to pages-entrance.`},"start-date":{type:`string`,required:!0,description:`First site calendar date in YYYY-MM-DD format.`},"end-date":{type:`string`,required:!0,description:`Last site calendar date in YYYY-MM-DD format.`},limit:{type:`string`,default:`100`,description:`Maximum retained rows. Defaults to 100 and caps at 5000.`},refresh:{type:`boolean`,default:!1,description:`Bypass the local Clicky cache.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=F(e),n=await Yo({json:t}),r=await Xo({siteId:O(e[`site-id`]),project:k(e),json:t}),i=await Zo(n,r),a=await yn({providerId:`clicky`,actionId:`report`,account:{siteId:r},...i.siteKey?{credentials:{sitekey:i.siteKey}}:{},params:{type:O(e.type)??`pages-entrance`,startDate:O(e[`start-date`])??``,endDate:O(e[`end-date`])??``,limit:j(e.limit)??100},refresh:A(e.refresh)});if(!a.data||typeof a.data!=`object`)throw new C(`PROVIDER_UNAVAILABLE`,`The Clicky provider returned an invalid report.`);let o={...a.data,cache:a.cache};if(t){R(o);return}z([[`Site ID`,o.siteId],[`Report`,o.type],[`Rows`,String(o.returnedRows)],[`Cache`,o.cache]]),B([`Value`,`Title`,`URL`],o.rows.slice(0,25).map(e=>[String(e.value??``),String(e.title??``),String(e.url??``)]))}}),ts=T({meta:{name:`disconnect`,description:`Remove a saved Clicky sitekey`},args:{"site-id":{type:`string`,description:`Numeric Clicky site ID.`},project:{type:`string`,description:`Saved project id or name.`},client:{type:`string`,description:`Legacy alias for --project.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=F(e);await Yo({json:t});let n=await Xo({siteId:O(e[`site-id`]),project:k(e),json:t});await Xn(n),await ct({providerId:`clicky`,account:{siteId:n}});let r=!!process.env[Se];t?R({siteId:n,savedCredentialRemoved:!0,environmentCredential:r?`active`:`missing`}):process.stdout.write(r?`Saved sitekey removed. ${Se} is still active.\n`:`Clicky is disconnected for this site.
133
+ `)}}),ns=T({meta:{name:`clicky`,description:`Connect Clicky and read bounded analytics reports`},subCommands:{connect:Qo,status:$o,report:es,detach:T({meta:{name:`detach`,description:`Remove Clicky from one project without deleting its sitekey`},args:{project:{type:`string`,required:!0,description:`Saved project id or name.`},client:{type:`string`,description:`Legacy alias for --project.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:t})=>{let n=F(t),r=await U({project:k(t),options:{json:n}}),i=Yn(r),a=i?.provider===`extension`&&i.providerId===`clicky`?`extension:clicky`:i?.provider===`clicky`?`clicky`:void 0;if(!r||!a)throw new C(`INVALID_INPUT`,`The selected project is not attached to Clicky.`);let o=e(r.id,a),s={project:{id:o.id,name:o.name},detached:!0,selectedAnalytics:o.analytics.selected,savedSitekey:!0};n?R(s):process.stdout.write(`Clicky detached from ${o.name}. The saved sitekey was kept.\n`)}}),disconnect:ts}}),rs=T({meta:{name:`properties`,description:`List Google Analytics accounts and properties`},args:{json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`},save:{type:`string`,description:`Save this numeric Google Analytics property ID as the default.`},account:{type:`string`,description:`Saved Google account email.`}},run:async({args:e})=>{let t=await or(O(e.account)),n=t.flatMap(e=>e.propertySummaries.map(t=>({account:e.displayName??e.account,property:Rt(t.property),displayName:t.displayName??t.property}))),r=O(e.save);if(r){let e=Zn();e.analytics.google.defaultPropertyId=r,kt(e)}if(F(e)){R({accountSummaries:t,properties:n,saved:r});return}r&&process.stdout.write(`Saved default Google Analytics property ${r}.\n`),B([`Property`,`Name`,`Account`],n.map(e=>[e.property,e.displayName,e.account]))}});function W(e){return Math.round(e).toLocaleString(`en-GB`)}function is(e){return e.toFixed(1)}function as(e){return`${(e*100).toFixed(1)}%`}function G(e,t=72){return Ya(e,t)}function os(e){return e.verification?.requested?`${e.verification.verified} checked, ${e.verification.failed} failed`:`off`}function ss(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){ko(ss(e))}function q(e,t){B(e,t.slice(0,25)),t.length>25&&process.stdout.write(`Showing 25 of ${t.length}. Use --json for full data.\n`)}function J(e,t,n=5){let r=t.filter(e=>e.action.trim().length>0).slice(0,n),i=cs(e,r);i&&(process.stdout.write(`
134
+ `),ko(i),t.length>r.length&&process.stdout.write(`Showing ${r.length} of ${t.length}. Use --json for full data.\n`))}function cs(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 ls(e){process.stdout.write(`\nNext: ${e}\n`)}function Y(e,t){let n=t.filter(e=>e.trim().length>0);if(n.length){process.stdout.write(`\n${e}\n`);for(let e of n)process.stdout.write(`- ${e}\n`)}}async function us(e,t={resolveClient:U,resolveGoogleAnalyticsProperty:Bo}){if(e.property)return t.resolveGoogleAnalyticsProperty({property:e.property,options:e.options});let n=await t.resolveClient({client:e.project,options:e.options});return t.resolveGoogleAnalyticsProperty({property:Dt(n),options:e.options})}const ds=T({meta:{name:`analytics`,description:`Read analytics data from connected providers`},subCommands:{clicky:ns,google:T({meta:{name:`google`,description:`Read Google Analytics properties and reports`},subCommands:{properties:rs,report:T({meta:{name:`report`,description:`Run a Google Analytics report`},args:{property:{type:`string`,description:`Google Analytics property ID. If omitted in a terminal, choose one.`},client:{type:`string`,description:`Legacy alias for --project.`},project:{type:`string`,description:`Saved project id or name with an optional Google Analytics property.`},account:{type:`string`,description:`Saved Google account email.`},"start-date":{type:`string`,default:`28daysAgo`},"end-date":{type:`string`,default:`yesterday`},dimensions:{type:`string`,default:`landingPage`},metrics:{type:`string`,default:`sessions,totalUsers,eventCount`},limit:{type:`string`,default:`25`},body:{type:`string`},"body-file":{type:`string`},refresh:{type:`boolean`,default:!1,description:`Bypass the local Google Analytics cache.`},json:{type:`boolean`,default:!1}},run:async({args:e})=>{let t=F(e),n=await us({property:O(e.property),project:k(e),options:{json:t,account:O(e.account)}}),r=await kn(n,await Ha(e.body,e[`body-file`])??{dateRanges:[{startDate:O(e[`start-date`]),endDate:O(e[`end-date`])}],dimensions:(N(e.dimensions)??[]).map(e=>({name:e})),metrics:(N(e.metrics)??[]).map(e=>({name:e})),limit:O(e.limit)},{refresh:A(e.refresh),accountEmail:O(e.account)});if(t){R(r);return}let i=Un(r);if(K({title:`Google Analytics report`,target:n,status:`info`,summary:`${r.rowCount??i.length} rows returned for the requested dimensions and metrics.`,metrics:[{label:`Rows`,value:r.rowCount??i.length},{label:`Displayed`,value:Math.min(i.length,25)}]}),i.length){let e=Object.keys(i[0]??{});B(e,i.slice(0,25).map(t=>e.map(e=>t[e]??``)))}}})}})}});function fs(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 ps(e){return e.active===`keychain`?`system keychain`:e.configured===`keychain`?`private file fallback`:`private file`}function ms(e){return e===`shared`?`SEO Skill app`:`Local OAuth config`}function hs(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`,ms(e.clientSource)],[`Expires`,Pa(e.expiresAt)]),t}const gs=T({meta:{name:`auth`,description:`Authentication commands`},subCommands:{login:T({meta:{name:`login`,description:`Run Google OAuth flow`},run:async()=>{if((await v()).activeMode===`service-account`)throw new C(`AUTH_CONFIG_REQUIRED`,`A service account is active from the environment. Unset its credential variable before running browser OAuth.`);let e=await x();z(hs({accountEmail:e.account_email,clientSource:e.client_source,expiresAt:e.expires_at,scope:e.scope}))}}),logout:T({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 v(),n=O(e.account);if(n&&e.all)throw new C(`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 lt(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:T({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 v();if(t.activeMode===`service-account`){let n={mode:`service-account`,account:t.identity??null,source:fs(t.serviceAccount.source)};if(F(e)){R(n);return}z([[`Account type`,`Service account`],[`Account`,t.identity??`Invalid configuration`],[`Credentials`,n.source],[`Access`,`Search Console and Google Analytics, read only`]]);return}let n=O(e.account),r=n?await ye(n):t.tokens;if(n&&!r)throw new C(`AUTH_REQUIRED`,`Google account ${n} is not connected.`);if(!r){if(F(e)){R({mode:`oauth`,account:null});return}process.stdout.write(`Not logged in.
135
+ `);return}if(F(e)){R({mode:`oauth`,account:r.account_email,scopes:r.scope.split(/\s+/u).filter(Boolean),clientSource:r.client_source,expiresAt:r.expires_at});return}z(hs({accountEmail:r.account_email,clientSource:r.client_source,expiresAt:r.expires_at,scope:r.scope}))}}),status:T({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 v(),n=O(e.account),r=n?await ye(n):t.tokens;if(n&&!r&&t.activeMode!==`service-account`)throw new C(`AUTH_REQUIRED`,`Google account ${n} is not connected.`);let i=t.activeMode===`oauth`?await se():void 0;if(F(e)){R({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`,fs(t.serviceAccount.source)],[`Scopes`,`Search Console readonly, Google Analytics readonly`],[`Storage`,`credentials stay in the environment`]];t.serviceAccount.error&&e.push([`Status`,t.serviceAccount.error]),z(e);return}if(!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 se();z([[`Account`,r.account_email],[`Connected accounts`,String(t.accounts.length)],[`Scopes`,r.scope],[`Client`,ms(r.client_source)],[`Expires`,Pa(r.expires_at)],[`Tokens file`,ir().tokensFile],[`Token storage`,ps(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:T({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 C(`INVALID_INPUT`,`Choose either --keychain or --file, not both.`);let t=e.keychain||e.file?await xt(e.keychain?`keychain`:`file`):await se();if(F(e)){R(t);return}z([[`Configured storage`,t.configured],[`Active storage`,ps(t)],...t.reason?[[`Note`,t.reason]]:[]])}}),refresh:T({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 v()).activeMode===`service-account`){process.stdout.write(`Service accounts request short-lived Google tokens automatically. No local refresh token is stored.
136
+ `);return}let t=await ze(O(e.account));process.stdout.write(`Refreshed. New expiry ${new Date(t.expires_at).toISOString()}.\n`)}}),accounts:T({meta:{name:`accounts`,description:`List saved Google accounts`},args:{json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:({args:e})=>{let t=Ce();if(F(e)){R({accounts:t});return}if(t.length===0){process.stdout.write(`No Google accounts are connected.
137
+ `);return}z(t.map(e=>[e.active?`Active account`:`Account`,e.accountEmail]))}}),use:T({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=O(e.account);if(!t)throw new C(`INVALID_INPUT`,`Google account is required.`);Tt(t),process.stdout.write(`Default Google account: ${t}\n`)}}),"setup-client":T({meta:{name:`setup-client`,description:`Save your own Google Desktop OAuth client`},args:{json:{type:`boolean`,default:!1,description:`Return a structured error instead of prompting.`}},run:async({args:e})=>{if(!L({json:F(e)}))throw new C(`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.");Di(`Local Google OAuth client`),E([`This is the advanced path.`,`Most users should use the shared seo Google app.`,`Your actual sensitive data is the local refresh token, not the desktop app client secret.`].join(`
138
+ `),`Advanced`),qe({clientId:V(await Pi({message:`Google Desktop OAuth client ID`,validate:e=>e?void 0:`Client ID is required`})),clientSecret:V(await Mi({message:`Google Desktop OAuth client secret`,validate:e=>e?void 0:`Client secret is required`}))}),ji(`Saved local OAuth config to ${ir().oauthClientFile}`)}})}}),_s=[`gsc`,`google-analytics`,`semrush`,`dataforseo`,`ahrefs`,`serpbase`,`clicky`,`http`];function vs(e){if(e!==void 0){if(typeof e==`string`&&_s.includes(e))return e;throw new C(`INVALID_INPUT`,`--provider must be one of: ${_s.join(`, `)}.`)}}const ys=T({meta:{name:`cache`,description:`Cache helpers`},subCommands:{stats:T({meta:{name:`stats`,description:`Show local cache size and row counts`},run:async()=>{let e=It();z([[`DB`,e.dbPath],[`Size`,Fo(e.sizeBytes)],[`Cached data`,`${Fo(e.logicalSizeBytes)} of ${Fo(e.maxSizeBytes)} automatic limit`],[`Sites`,String(e.counts.sites??0)],[`Search Console`,String(e.counts.gsc_cache??0)],[`Google Analytics`,String(e.counts.google_analytics_cache??0)],[`Semrush`,String(e.counts.semrush_cache??0)],[`DataForSEO`,String(e.counts.provider_cache??0)],[`Ahrefs`,String(e.counts.ahrefs_cache??0)],[`SerpBase`,String(e.counts.serpbase_cache??0)],[`Clicky`,String(e.counts.clicky_cache??0)],[`HTTP`,String(e.counts.http_cache??0)]])}}),clear:T({meta:{name:`clear`,description:`Clear cached API and HTTP data`},args:{provider:{type:`string`,description:`Optional cache provider: gsc, google-analytics, semrush, dataforseo, ahrefs, serpbase, clicky, or http`}},run:async({args:e})=>{let t=hn(vs(e.provider));process.stdout.write(`Removed ${t} cached rows.\n`)}})}});async function bs(e,t={}){return(await H({client:e.client,project:e.project,site:e.site,options:t})).site}function xs(e){if(e.startsWith(`http://`)||e.startsWith(`https://`))return e;if(e.startsWith(`sc-domain:`))return`https://${e.slice(10)}/`}function Ss(e){return e.replace(/^sc-domain:/,``).replace(/^https?:\/\//,``)}function Cs(e){return e.toLowerCase().replace(/^https?:\/\//,``).replace(/^sc-domain:/,``).replace(/[^a-z0-9]+/g,`-`).replace(/^-+|-+$/g,``).slice(0,60)}function ws(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 Ts(e){return e?e===`serp-framing`?`wording gap`:e===`content-gap`?`content gap`:e===`technical-check`?`technical check`:`covered`:`-`}const Es=`__create_project__`,Ds=`__skip_project__`;function Os(e){return[{value:Es,label:`Create a new project`,hint:`Save a separate site and its data sources`},...e.map(e=>({value:e.id,label:`Update ${e.name}`,hint:`${e.id}, ${e.siteUrl}`})),{value:Ds,label:`Continue without a project profile`,hint:`Pass --site or --url when you run reports`}]}function ks(e,t){let n=Cs(e);if(!n)throw Error(`Project id could not be derived.`);let r=new Set(t.map(e=>e.id.toLowerCase()));if(!r.has(n.toLowerCase()))return n;let i=2;for(;r.has(`${n}-${i}`.toLowerCase());)i+=1;return`${n}-${i}`}async function As(e){if(e.skipProfile)return{mode:`skip`};if(e.requestedId){let t=e.requestedId.toLowerCase(),n=Cs(e.requestedId).toLowerCase(),r=e.clients.find(e=>e.id.toLowerCase()===n||e.name.toLowerCase()===t);return r?{mode:`update`,client:r}:{mode:`create`,requestedId:e.requestedId}}if(!e.interactive)return{mode:`create`};let t=Os(e.clients),n=V(await(e.prompt?e.prompt(t):Ni({message:`Create a project or update an existing one?`,options:t})));if(n===Es)return{mode:`create`};if(n===Ds)return{mode:`skip`};let r=e.clients.find(e=>e.id===n);if(!r)throw Error(`Project not found: ${n}`);return{mode:`update`,client:r}}function js(e){let t=e.platform===`win32`?ii:ti;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 Ms(e={}){let t=e.home??oi(),n=e.platform??process.platform,r=js({home:t,platform:n,env:e.env??process.env}),i=n===`win32`?ii:ti,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 Ns(e,t={}){let n=t.platform??process.platform,r=t.exists??Ur,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=w(e,t);if(r(n))return n}for(let e of a){let t=w(Qr(i),e);if(r(t))return t}return e}function Ps(e){return Ns(e)!==e}function Fs(e={}){let t=e.targets??Ms(),n=e.hasCommand??Ps,r=e.hasPath??Ur;return t.filter(e=>e.kind===`native`?e.commandNames.some(n):r(e.path)||e.detectionPaths.some(r)||e.commandNames.some(n))}const Is={insertSpaces:!0,tabSize:2,eol:`
139
+ `};function Ls(){return new Date().toISOString().replace(/[:.]/g,`-`)}function Rs(e,t){Wr(Qr(e),{recursive:!0,mode:448});let n=`${e}.${process.pid}.${Date.now()}.tmp`;Xr(n,t,{encoding:`utf8`,mode:Ur(e)?Yr(e).mode&511:384}),Jr(n,e)}function zs(e){let t=`${e}.bak-${Ls()}`,n=t,r=2;for(;Ur(n);)n=`${t}-${r}`,r+=1;Vr(e,n,Br.COPYFILE_EXCL)}function Bs(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function Vs(e){if(!Ur(e.path))return{source:`{}
140
+ `,config:{}};let t=Gr(e.path,`utf8`),n=[],r=Bi(t,n,{allowTrailingComma:!0});if(n.length>0){let t=n[0],r=t?`${Vi(t.error)} at offset ${t.offset}`:`unknown parse error`;throw Error(`Cannot update ${e.path}: invalid JSONC (${r}).`)}if(!Bs(r))throw Error(`Cannot update ${e.path}: config root must be an object.`);if(r.mcpServers!==void 0&&!Bs(r.mcpServers))throw Error(`Cannot update ${e.path}: mcpServers must be an object.`);return{source:t,config:r}}function Hs(e){return e.includes(`mcp`)&&e.includes(`serve`)&&e.some(e=>e===`seo`||e.startsWith(`seo@`)||e===`@seo/cli`)}function Us(e,t){return typeof e.command!=`string`||t.length!==3||t[1]!==`mcp`||t[2]!==`serve`||!Bs(e.env)?!1:e.env.SEO_MCP_CONFIGURED_BY===`seo`}function Ws(e){if(!Bs(e)||!Array.isArray(e.args))return!1;let t=e.args.filter(e=>typeof e==`string`);return Hs(t)||Us(e,t)}function Gs(e){let t=Ri(e.source,zi(e.source,[`mcpServers`,`seo`],e.value,{formattingOptions:Is}));Ur(e.target.path)&&zs(e.target.path),Rs(e.target.path,t)}function Ks(e,t){let n=Si(e,t,{encoding:`utf8`});return{status:n.status,stdout:n.stdout??``,stderr:n.stderr??``,error:n.error}}function qs(e){let t=e.replaceAll(`\\`,`/`).toLowerCase();return t.includes(`/_npx/`)||t.includes(`/.npm/_npx/`)||t.includes(`/.cache/pnpm/dlx/`)}function Js(e){if(!e||!Ur(e))return;let t=qr(e);return qs(t)?void 0:t}function Ys(e={}){let t=Js(e.cliPath??process.argv[1]);if(t)return{command:e.nodePath??process.execPath,args:[t,`mcp`,`serve`]};let n=`seo@${e.version??Ir}`;return(e.platform??process.platform)===`win32`?{command:`cmd`,args:[`/c`,`npx`,`-y`,n,`mcp`,`serve`]}:{command:`npx`,args:[`-y`,n,`mcp`,`serve`]}}function Xs(e=!1,t=Ys()){let n=ir(),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 Zs(e){return e.includes(`SEO_MCP_CONFIGURED_BY`)||/args:\s+-y\s+(?:seo(?:@\S+)?|@seo\/cli)\s+mcp\s+serve\b/.test(e)}function Qs(e,t,n){let r=Ns(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&&!Zs(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=ir(),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 $s(e,t={}){let n={runCommand:t.runCommand??Ks,reinstall:t.reinstall??!1,serverCommand:t.serverCommand??Ys()};if(e.kind===`native`)return Qs(e,`install`,n);let{source:r,config:i}=Vs(e),a=i.mcpServers?.seo,o=Xs(e.includeType,n.serverCommand);if(vi(a,o)&&!n.reinstall)return{client:e.client,path:e.path,changed:!1};if(a!==void 0&&!Ws(a))throw Error(`Cannot install SEO MCP: ${e.path} already has an unmanaged mcpServers.seo entry.`);return Gs({target:e,source:r,value:o}),{client:e.client,path:e.path,changed:!0}}function ec(e,t={}){if(e.kind===`native`)return Qs(e,`uninstall`,{runCommand:t.runCommand??Ks,reinstall:!1,serverCommand:Ys()});if(!Ur(e.path))return{client:e.client,path:e.path,changed:!1};let{source:n,config:r}=Vs(e),i=r.mcpServers;if(i?.seo===void 0)return{client:e.client,path:e.path,changed:!1};if(!Ws(i.seo))throw Error(`Cannot uninstall SEO MCP: ${e.path} has an unmanaged mcpServers.seo entry.`);return Gs({target:e,source:n,value:void 0}),{client:e.client,path:e.path,changed:!0}}function tc(e={}){let t=Si(`npx`,[`-y`,`skills`,`add`,`iannuttall/seo`],{stdio:e.quiet?`ignore`:`inherit`});return t.error?{status:`failed`,error:t.error.message}:t.status===0?{status:`installed`}:{status:`failed`,error:`npx skills add exited with status ${t.status??`unknown`}.`}}function nc(e,t=[]){let n=[{value:`google`,label:`Google Analytics`,hint:`Use a property available to your Google login`},...t.filter(e=>e.capabilities.some(e=>e.id===`landing-page-visits`)&&e.id!==`google`).map(e=>({value:`extension:${e.id}`,label:e.displayName,hint:`Installed provider from ${e.package}`}))];return e?[{value:`keep`,label:`Keep ${$n(e)}`,hint:`Leave this project connection unchanged`},...n,{value:`remove`,label:`Remove traffic analytics`}]:[...n,{value:`skip`,label:`Skip traffic analytics`}]}async function rc(e,t={},n=!0){if(!e.capabilities.some(e=>e.id===`landing-page-visits`))throw new C(`INVALID_INPUT`,`Provider ${e.id} does not supply landing-page visits.`);let r={...t},i={};for(let t of e.connection.fields.filter(e=>e.kind===`account`)){if(r[t.id]?.trim())continue;if(!n){if(t.required!==!1)throw new C(`INVALID_INPUT`,`${t.label} is required.`);continue}let e=V(await Pi({message:t.label,validate:e=>t.required!==!1&&!e?.trim()?`${t.label} is required`:void 0})).trim();e&&(r[t.id]=e)}let a=e.connection.normalizeAccount?e.connection.normalizeAccount(r):r;try{Object.assign(i,await gt({providerId:e.id,account:a,fields:e.connection.fields}))}catch{}if(e.id===`clicky`&&!i.sitekey){let e=await wt(a.siteId??``);e&&(i.sitekey=e.siteKey)}for(let t of e.connection.fields.filter(e=>e.kind===`secret`)){let r=t.envVar?process.env[t.envVar]?.trim():void 0;if(r&&(i[t.id]=r),i[t.id])continue;if(!n)throw new C(`AUTH_REQUIRED`,t.envVar?`Set ${t.envVar} to connect ${e.displayName}.`:`${t.label} is required to connect ${e.displayName}.`);let a=V(await Mi({message:t.label,validate:e=>t.required!==!1&&!e?.trim()?`${t.label} is required`:void 0})).trim();a&&(i[t.id]=a)}return e.connection.verificationNotice&&E(e.connection.verificationNotice,`Connection check`),await ee({providerId:e.id,account:a,credentials:i}),Object.keys(i).length>0&&await oe({providerId:e.id,account:a,credentials:i}),{provider:`extension`,extension:{providerId:e.id,account:a}}}function ic(e){let t=e.canSkip?[{value:`skip`,label:`Skip for now`}]:[];return e.sharedConfigured||e.byoConfigured?[{value:`login`,label:`Connect Google`,hint:`Opens your browser for read-only Search Console and Google Analytics access`},...t]:[{value:`setup`,label:`Set up Google login for local development`,hint:`This source checkout does not include the public app credentials`},...t]}async function ac(e,t){let n=[],r=!0,i=0;async function a(){for(;i<e.length;){let a=e[i];if(i+=1,a)try{let e=await Gt(a.property,t);n.push(...e.filter(e=>e.webStreamData).map(e=>({account:a.account,property:a.property,propertyName:a.label,stream:e})))}catch{r=!1}}}return await Promise.all(Array.from({length:Math.min(e.length,4)},()=>a())),{candidates:n,complete:r}}async function oc(e){let t=await v();if(t.activeMode===`service-account`)return{status:`service-account`,accountEmail:t.identity};let n=typeof e[`search-console-account`]==`string`?e[`search-console-account`]:void 0,r=Ce();if(n){let e=r.find(e=>e.accountEmail.toLowerCase()===n.toLowerCase());if(!e)throw new C(`AUTH_REQUIRED`,`Google account ${n} is not connected. Run \`seo auth login\` first.`);return{status:`already-connected`,accountEmail:e.accountEmail}}if(e[`skip-auth`])return{status:`skipped`};if(!L({json:e.json===!0})){if(t.tokens)return{status:`already-connected`,accountEmail:t.tokens.account_email};throw new C(`AUTH_REQUIRED`,"Not logged in. Run `seo auth login`, or pass --skip-auth to save a project profile without connecting Google.")}if(r.length>0){let n=V(await Ni({message:`Google account for Search Console`,options:[...r.map(e=>({value:{type:`account`,accountEmail:e.accountEmail},label:e.accountEmail,hint:e.active?`Current default`:void 0})),{value:{type:`connect`},label:`Connect another Google account`},...typeof e.site==`string`?[{value:{type:`skip`},label:`Skip for now`}]:[]]}));if(n.type===`account`)return Tt(n.accountEmail),{status:`already-connected`,accountEmail:n.accountEmail};if(n.type===`skip`)return{status:`skipped`};if(t.sharedConfigured||t.byoConfigured){let e=await x();return E(`Connected as ${e.account_email}. seo has read-only access and cannot change your site.`,`Google connected`),{status:`connected`,accountEmail:e.account_email}}}let i=V(await Ni({message:`Connect Google now?`,options:ic({sharedConfigured:t.sharedConfigured,byoConfigured:t.byoConfigured,canSkip:typeof e.site==`string`&&e.site.length>0})}));if(i===`skip`)return{status:`skipped`};i===`setup`&&qe({clientId:V(await Pi({message:`Google Desktop OAuth client ID`,validate:e=>e?void 0:`Client ID is required`})),clientSecret:V(await Mi({message:`Google Desktop OAuth client secret`,validate:e=>e?void 0:`Client secret is required`}))});let a=await x();return E(`Connected as ${a.account_email}. seo has read-only access and cannot change your site.`,`Google connected`),{status:`connected`,accountEmail:a.account_email}}async function sc(e){if(e.property)return{propertyId:e.property,accountEmail:e.accountEmail,selection:`explicit`,reason:`Set with --google-analytics-property.`};if(!(e.interactive??L()))return;let t=(await or(e.accountEmail).catch(()=>[])).flatMap(e=>e.propertySummaries.map(t=>({property:Rt(t.property),label:t.displayName??t.property,account:e.displayName??e.account})));if(!t.length)return;let{candidates:n,complete:r}=await ac(t,e.accountEmail),i=r?aa(e.site,n):[],a=new Map;for(let e of i)a.set(e.property,e);let o=[...a.values()];if(o.length===1){let t=o[0];return t?{propertyId:t.property,accountEmail:e.accountEmail,selection:`matched`,reason:oa(t,e.site)}:void 0}let s=o.length?o.map(e=>({property:e.property,label:e.propertyName,account:e.account,match:e})):t,c=V(await Ni({message:o.length?`Several Google Analytics properties match this site. Which property should seo use?`:r?`No Google Analytics web stream clearly matches this site. Attach a property?`:`Some Google Analytics web streams could not be read. Attach a property?`,options:[{value:``,label:`Skip Google Analytics for now`},...s.map(t=>({value:t,label:`${t.label} (${t.property})`,hint:t.match?oa(t.match,e.site):t.account}))]}));if(c)return{propertyId:c.property,accountEmail:e.accountEmail,selection:`manual`,reason:c.match?oa(c.match,e.site):r?`Selected ${c.label} during setup. Its web stream did not clearly match ${e.site}.`:`Selected ${c.label} during setup. seo could not read every Google Analytics web stream, so it did not guess a match.`}}async function cc(e){if(e.googleProperty&&e.clickySiteId)throw new C(`INVALID_INPUT`,`Pass either --google-analytics-property or --clicky-site-id, not both.`);let t=e.interactive??L();if(e.clickySiteId){let n=(await l()).registry.get(`clicky`);if(!n)throw new C(`PROVIDER_UNAVAILABLE`,"The Clicky provider package is not installed. Run `seo providers install @seoskill/clicky-provider`.");return rc(n,{siteId:e.clickySiteId},t)}if(e.googleProperty){let n=await lc({requested:e.googleAccount,interactive:t}),r=await sc({property:e.googleProperty,accountEmail:n,site:e.site,interactive:t});return r?{provider:`google`,google:r}:void 0}if(!t)return;let n=await l(),r=V(await Ni({message:`Traffic analytics`,options:nc(e.current,n.registry.list())}));if(r===`keep`)return;if(r===`remove`)return{provider:`none`};if(r===`skip`)return;if(r.startsWith(`extension:`)){let e=r.slice(10),t=n.registry.get(e);if(!t)throw new C(`INVALID_INPUT`,`Provider ${e} is installed but could not be loaded. Run \`seo providers doctor\`.`);return rc(t)}let i=await sc({accountEmail:await lc({interactive:t}),site:e.site,interactive:t});return i||E(`No Google Analytics property was available for this Google login. You can connect one later.`,`Traffic analytics skipped`),i?{provider:`google`,google:i}:void 0}async function lc(e){let t=await v();if(t.activeMode===`service-account`)return t.identity;let n=Ce();if(e.requested){let t=n.find(t=>t.accountEmail.toLowerCase()===e.requested?.toLowerCase());if(!t)throw new C(`AUTH_REQUIRED`,`Google account ${e.requested} is not connected. Run \`seo auth login\` first.`);return t.accountEmail}if(!e.interactive)return n.find(e=>e.active)?.accountEmail;if(n.length===0)return(await x()).account_email;let r=V(await Ni({message:`Google account for Google Analytics`,options:[...n.map(e=>({value:{type:`account`,accountEmail:e.accountEmail},label:e.accountEmail,hint:e.active?`Current default`:void 0})),{value:{type:`connect`},label:`Connect another Google account`}]}));if(r.type===`account`)return r.accountEmail;let i=await x();return E(`Connected as ${i.account_email}. seo has read-only access and cannot change your site.`,`Google connected`),i.account_email}async function uc(e){return e[`skip-skill`]||!L({json:e.json===!0})?{status:`skipped`}:V(await Ei({message:`Install the SEO skill so coding agents know how to run reports?`,initialValue:!0}))?tc():{status:`declined`}}async function dc(e){if(e[`skip-mcp`]||!L({json:e.json===!0}))return[];let t=Fs();if(t.length===0||!V(await Ei({message:`Install seo as an MCP server too?`,initialValue:!0})))return[];let n=V(await Ai({message:`Which MCP clients?`,options:t.map(e=>({value:e.client,label:e.label,hint:e.path})),initialValues:t.map(e=>e.client)}));return t.filter(e=>n.includes(e.client)).map(e=>{try{return $s(e)}catch(t){return{client:e.client,path:e.path,changed:!1,error:t instanceof Error?t.message:String(t)}}})}function fc(e){return e.status===`installed`?`installed`:e.status===`failed`?`install failed`:`not installed`}function pc(e){return/^[A-Za-z0-9_./:@-]+$/.test(e)?e:`'${e.replaceAll(`'`,`'\\''`)}'`}function mc(){process.stdout.write(`
141
+ `),Eo({title:`Help other people find SEO Skill`,body:`If SEO Skill saved you time, a GitHub star helps other people find it.`,command:`https://github.com/iannuttall/seo`})}function hc(e){let t=e.filter(e=>e.error).length,n=e.length-t;return e.length===0?`not installed`:t===0?`${n} installed`:n===0?`${t} skipped`:`${n} installed, ${t} skipped`}function gc(e){let t=e.filter(e=>!!e.error);if(t.length!==0)return t.map(e=>`${e.client}: ${e.error}`).join(`
142
+ `)}async function _c(e){let t=F(e);if(t||Di(process.argv[2]===`start`?`seo start`:`seo setup`),e[`dry-run`]){let e=[`seo auth login`,`seo start`,`seo report --project acme`,`seo refresh-priorities --project acme --verify-content`,`seo technical-watch --project acme`];t?R({dryRun:!0,next:e}):(E(e.join(`
143
+ `),`This setup will guide you through`),ji(`Dry run complete.`));return}Vn();let n=L({json:t});n&&E(`A project profile remembers one site and its report defaults so future commands stay short.`,`Project profiles`);let r=Ge(),i=await As({clients:r,interactive:n,requestedId:O(e.id),skipProfile:A(e[`skip-profile`])}),a=i.mode===`update`?i.client:void 0,o=O(e.site)??a?.siteUrl,s={site:o,options:{allowDefault:i.mode===`update`,json:t,refresh:A(e.refresh)}},c=n?void 0:await zo(s),l=await oc(o?{...e,site:o}:e),u=l.status,d=c??await zo({...s,options:{...s.options,account:l.accountEmail}}),f=Ss(d);if(i.mode===`skip`){let n=await dc(e),r=await uc(e),i=`--site ${pc(d)}`,a=[`seo report ${i}`,`seo refresh-priorities ${i} --verify-content`,`seo technical-watch ${i}`],o={site:d,auth:u,mcp:n,skill:r,next:a};if(t){R(o);return}z([[`Project profile`,`not saved`],[`GSC property`,d],[`Auth`,u],[`MCP`,hc(n)],[`SEO skill`,fc(r)]]);let s=gc(n);s&&E(s,`MCP setup skipped`),r.error&&E(r.error,`SEO skill install failed`),E(a.join(`
144
+ `),`Try next`),ji(`Setup complete.`),mc();return}let p=O(e.name)??(n?V(await Pi({message:`Project name`,placeholder:a?.name??f,defaultValue:a?.name??f})):a?.name??f),m=a?.id??(i.mode===`create`&&i.requestedId?Cs(i.requestedId):ks(p,r)),h=a?.startUrl??xs(d)??``,g=O(e.url)??(n?V(await Pi({message:`Website URL to crawl`,placeholder:h||`https://example.com`,defaultValue:h})):h||void 0),_=e.urls===void 0?a?.watchUrls??[]:P(e.urls),v=await cc({googleProperty:O(e[`google-analytics-property`]),googleAccount:O(e[`google-analytics-account`]),clickySiteId:O(e[`clicky-site-id`]),current:Yn(a),site:d,interactive:n}),y=v?.provider===`google`?v.google:void 0,b=v?.provider===`extension`?v.extension:void 0,ee=a?.brandTerms??dr({id:m,name:p,siteUrl:d}),x=e.brand===void 0?ee:P(e.brand),te=j(e[`report-day`])??a?.reportDay??1,ne=j(e.weekday)??a?.technicalWeekday??1,re=A(e.default)??a?.isDefault??!0,ie={name:p,siteUrl:d,startUrl:g,watchUrls:_,brandTerms:x,analytics:v===void 0?void 0:v.provider===`google`?{...a?.analytics,selected:`google`,google:{propertyId:v.google.propertyId}}:v.provider===`extension`?{...a?.analytics,selected:`extension:${v.extension.providerId}`,extensions:{...a?.analytics.extensions,[v.extension.providerId]:{account:v.extension.account}}}:{},googleAccounts:{...a?.googleAccounts,...l.accountEmail?{searchConsole:l.accountEmail}:{},...y?.accountEmail?{googleAnalytics:y.accountEmail}:{}},reportDay:te,technicalWeekday:ne,isDefault:re},S=a?vn(a.id,ie):en({id:m,...ie}),ae=await dc(e),oe=await uc(e),se=[`seo report --project ${S.id}`,`seo refresh-priorities --project ${S.id} --verify-content`,`seo technical-watch --project ${S.id}`],ce={client:S,site:d,auth:u,googleAnalytics:y,extension:b,mcp:ae,skill:oe,next:se};if(t){R(ce);return}let le=Yn(S);z([[`Project profile`,`${S.name} (${S.id})`],[`GSC property`,S.siteUrl],[`Search Console account`,S.googleAccounts?.searchConsole??`default Google account`],[`Crawl URL`,S.startUrl??`not set`],[`Watch URLs`,String(S.watchUrls.length)],[`Brand terms`,S.brandTerms.join(`, `)||`not set`],[`Traffic analytics`,le?$n(le):`not connected (optional)`],...y?[[`Google Analytics selection`,y.reason]]:[],...S.googleAccounts?.googleAnalytics?[[`Google Analytics account`,S.googleAccounts.googleAnalytics]]:[],[`Auth`,u],[`MCP`,hc(ae)],[`SEO skill`,fc(oe)]]);let ue=gc(ae);ue&&E(ue,`MCP setup skipped`),oe.error&&E(oe.error,`SEO skill install failed`),E(se.join(`
145
+ `),`Try next`),ji(`Setup complete.`),mc()}const vc=T({meta:{name:`setup`,description:`Guided setup for auth, one project profile, MCP, and next commands`},args:{id:{type:`string`,description:`Short stable project id.`},name:{type:`string`,description:`Human project name.`},site:{type:`string`,description:`GSC property URL, for example sc-domain:example.com.`},"search-console-account":{type:`string`,description:`Saved Google account email for Search Console.`},url:{type:`string`,description:`Default technical crawl start URL.`},urls:{type:`string`,description:`Comma-separated URLs to watch with URL Inspection.`},"google-analytics-property":{type:`string`,description:`Optional Google Analytics property ID.`},"google-analytics-account":{type:`string`,description:`Saved Google account email for Google Analytics.`},"clicky-site-id":{type:`string`,description:`Optional Clicky site ID. Set SEO_CLICKY_SITEKEY outside a terminal.`},brand:{type:`string`,description:`Comma-separated branded query terms to exclude by default.`},"report-day":{type:`string`,description:`Preferred monthly report day. Defaults to 1.`},weekday:{type:`string`,description:`Preferred technical-watch weekday. Defaults to Monday.`},default:{type:`boolean`,description:`Make this the default project.`},"skip-profile":{type:`boolean`,default:!1,description:`Do not save a project profile during setup.`},"skip-auth":{type:`boolean`,default:!1,description:`Skip Google sign-in during setup.`},"skip-mcp":{type:`boolean`,default:!1,description:`Skip MCP install prompts.`},"skip-skill":{type:`boolean`,default:!1,description:`Skip the SEO skill install prompt.`},refresh:{type:`boolean`,default:!1,description:`Refresh GSC property discovery.`},"dry-run":{type:`boolean`,default:!1,description:`Show what setup does without changing files.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>_c(e)}),yc=T({...vc,meta:{name:`start`,description:`Start here: connect first-party data, save a profile, and get the first report commands`}}),bc=T({...vc,meta:{name:`setup`,description:`Guided setup for one saved project profile`}}),xc=T({meta:{name:`brand`,description:`Detect and manage project brand query terms`},subCommands:{detect:T({meta:{name:`detect`,description:`Suggest branded query terms from GSC navigational queries`},args:{client:{type:`string`,description:`Legacy alias for --project.`},project:{type:`string`,description:`Saved project id or name. Defaults to default.`},site:{type:`string`,description:`GSC property URL, for example sc-domain:example.com.`},days:{type:`string`,description:`Detection window length in days. Defaults to 28.`},limit:{type:`string`,description:`Maximum candidate terms. Defaults to 10.`},"min-impressions":{type:`string`,description:`Minimum query impressions. Defaults to 10.`},save:{type:`boolean`,default:!1,description:`Save suggested terms to the selected project.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`},refresh:{type:`boolean`,default:!1,description:`Bypass local GSC cache.`}},run:async({args:e})=>{let t=F(e),n=yt(k(e)),r=await Rn({site:await zo({site:O(e.site)??n?.siteUrl,options:{json:t,refresh:A(e.refresh)}}),id:n?.id,name:n?.name,days:j(e.days),limit:j(e.limit),minImpressions:j(e[`min-impressions`]),refresh:A(e.refresh)}),i=A(e.save)&&n?en({id:n.id,name:n.name,siteUrl:n.siteUrl,startUrl:n.startUrl,watchUrls:n.watchUrls,analytics:n.analytics,brandTerms:r.suggestedTerms,reportDay:n.reportDay,technicalWeekday:n.technicalWeekday,isDefault:n.isDefault}):void 0;if(A(e.save)&&!n)throw Error(`Pass --project to save detected brand terms.`);if(t){R({...r,saved:i});return}z([[`Property`,r.site],[`Derived terms`,r.derivedTerms.join(`, `)],[`Suggested terms`,r.suggestedTerms.join(`, `)],[`Saved`,i?i.id:`no`]]),B([`Term`,`Score`,`Evidence`],r.candidates.map(e=>[e.term,e.score,e.evidence.map(e=>`${e.query} (${Math.round(e.clicks)} clicks)`).join(`, `)]))}})}}),Sc={project:{type:`string`,description:`Saved project id or name.`},client:{type:`string`,description:`Legacy alias for --project.`}},Cc={type:`boolean`,default:!1,description:`Print machine-readable JSON.`};async function wc(e){let t=await U({project:k(e),options:{json:F(e)}});if(!t)throw new C(`INVALID_INPUT`,`Select a saved project with --project or make one the default.`);return t}async function Tc(e){let t=O(e.keyword),n=O(e.keywords)?.split(`,`).map(e=>e.trim()).filter(Boolean),r=O(e.file),i=[...t?[t]:[],...n??[]];if(r){let e=await hi(r).catch(()=>void 0);if(!e?.isFile()||e.size>1e5)throw new C(`INVALID_INPUT`,`Keyword files must be regular files no larger than 100 KB.`);i.push(...(await di(r,`utf8`)).split(/\r?\n/u).map(e=>e.trim()).filter(Boolean))}if(i.length===0)throw new C(`INVALID_INPUT`,`Pass --keyword, comma-separated --keywords, or a newline-delimited --file.`);return i}const Ec=T({meta:{name:`add`,description:`Add or update keywords in a saved set`},args:{...Sc,set:{type:`string`,description:`Keyword set id or name.`,required:!0},keyword:{type:`string`,description:`One keyword.`},keywords:{type:`string`,description:`Comma-separated keywords.`},file:{type:`string`,description:`Newline-delimited keyword file.`},tags:{type:`string`,description:`Comma-separated tags applied to every keyword.`},"target-url":{type:`string`,description:`Existing target page for every keyword.`},"proposed-url":{type:`string`,description:`Proposed page for every keyword.`},json:Cc},run:async({args:e})=>{let t=O(e[`target-url`]),n=O(e[`proposed-url`]);if(t&&n)throw new C(`INVALID_INPUT`,`Use either --target-url or --proposed-url.`);let r=xr({projectId:(await wc(e)).id,idOrName:O(e.set)??``,items:(await Tc(e)).map(r=>({keyword:r,tags:P(e.tags),...t||n?{page:{kind:t?`target`:`proposed`,url:t??n??``}}:{}}))});F(e)?R(r):z([[`Set`,r.setId],[`Added`,String(r.added)],[`Updated`,String(r.updated)],[`Keywords`,String(r.keywordCount)]])}}),Dc=T({meta:{name:`create`,description:`Create an empty saved keyword set`},args:{...Sc,name:{type:`string`,description:`Keyword set name.`,required:!0},country:{type:`string`,description:`Two-letter country code.`,required:!0},language:{type:`string`,description:`Search language code.`,required:!0},"search-engine":{type:`string`,default:`google`,description:`Search engine: google or bing.`},"location-code":{type:`string`,description:`Optional provider location code.`},location:{type:`string`,description:`Optional canonical location name.`},device:{type:`string`,description:`Optional device: desktop or mobile.`},provider:{type:`string`,description:`Optional preferred provider.`},"source-report":{type:`string`,description:`Report that produced the initial research.`},json:Cc},run:async({args:e})=>{let t=j(e[`location-code`]);if(t!==void 0&&!Number.isSafeInteger(t))throw new C(`INVALID_INPUT`,`--location-code must be an integer.`);let n=await wc(e),r=nt({projectId:n.id,name:O(e.name)??``,market:{searchEngine:O(e[`search-engine`]),countryCode:O(e.country),languageCode:O(e.language),...t!==void 0||O(e.location)?{location:{code:t,name:O(e.location)}}:{},device:O(e.device)},provider:O(e.provider),sourceReport:O(e[`source-report`])});F(e)?R(r):z([[`Created`,r.name],[`Project`,n.name],[`Keywords`,`0`],[`Id`,r.id]])}}),Oc=T({meta:{name:`delete`,description:`Delete a saved keyword set`},args:{...Sc,set:{type:`string`,description:`Keyword set id or name.`,required:!0},yes:{type:`boolean`,default:!1,description:`Confirm permanent deletion.`},json:Cc},run:async({args:e})=>{if(!A(e.yes))throw new C(`INVALID_INPUT`,`Pass --yes to delete the keyword set.`);let t=await wc(e),n=O(e.set)??``;if(!h({projectId:t.id,idOrName:n}))throw new C(`INVALID_INPUT`,`Keyword set not found: ${n}`);let r={deleted:!0,projectId:t.id,set:n};F(e)?R(r):process.stdout.write(`Deleted keyword set ${n}.\n`)}}),kc=T({meta:{name:`export`,description:`Export a bounded saved keyword set`},args:{...Sc,set:{type:`string`,description:`Keyword set id or name.`,required:!0},format:{type:`string`,default:`csv`,description:`Output format: csv or json.`},output:{type:`string`,description:`Output file, or - for stdout.`,required:!0},tag:{type:`string`,description:`Only export keywords with this tag.`},limit:{type:`string`,default:`1000`,description:`Rows to export, from 1 to 1000.`},offset:{type:`string`,default:`0`,description:`Rows to skip.`}},run:async({args:e})=>{let t=O(e.format);if(t!==`csv`&&t!==`json`)throw new C(`INVALID_INPUT`,`--format must be csv or json.`);let n=j(e.limit),r=j(e.offset);if(!Number.isSafeInteger(n)||!Number.isSafeInteger(r))throw new C(`INVALID_INPUT`,`--limit and --offset must be integers.`);let i=Tr({projectId:(await wc(e)).id,idOrName:O(e.set)??``,tag:O(e.tag),limit:n,offset:r}),a=t===`json`?`${JSON.stringify(i,null,2)}\n`:Ac(i),o=O(e.output)??``;if(o===`-`)process.stdout.write(a);else{try{await _i(o,a,{encoding:`utf8`,flag:`wx`,mode:384})}catch(e){throw e.code===`EEXIST`?new C(`INVALID_INPUT`,`Refusing to overwrite the existing file at ${o}.`):e}process.stdout.write(`Wrote ${i.items.length} keywords to ${o}.\n`)}}});function Ac(e){return p(e.items.map(e=>{let t=e.latestMetric?.metric;return{keyword:e.keyword,normalized_keyword:e.normalizedKeyword,tags:e.tags.join(`; `),page_kind:e.page?.kind,page_url:e.page?.url,metric_provider:e.latestMetric?.provider,metric_observed_at:e.latestMetric?.observedAt,monthly_search_volume:jc(t?.monthlySearchVolume),cpc_usd:jc(t?.cpcUsd),paid_competition:jc(t?.paidCompetition),keyword_difficulty:jc(t?.keywordDifficulty),intent:t?.intent.state===`observed`?t.intent.value:null,result_count:jc(t?.resultCount)}}),[`keyword`,`normalized_keyword`,`tags`,`page_kind`,`page_url`,`metric_provider`,`metric_observed_at`,`monthly_search_volume`,`cpc_usd`,`paid_competition`,`keyword_difficulty`,`intent`,`result_count`])}function jc(e){return e?.state===`observed`?e.value:null}function Mc(e){if(e.length===0){process.stdout.write(`No keyword sets saved for this project.
146
+ `);return}B([`Name`,`Keywords`,`Market`,`Provider`,`Updated`],e.map(e=>[e.name,e.keywordCount,`${e.market.countryCode}/${e.market.languageCode}`,e.provider??`automatic`,e.updatedAt]))}function Nc(e){if(z([[`Name`,e.set.name],[`Project`,e.set.projectId],[`Keywords`,String(e.set.keywordCount)],[`Market`,`${e.set.market.searchEngine} ${e.set.market.countryCode}/${e.set.market.languageCode}`],[`Provider`,e.set.provider??`automatic`],[`Last refreshed`,e.set.lastRefreshedAt??`never`]]),e.items.length===0){process.stdout.write(`
147
147
  No keywords matched this view.
148
148
  `);return}process.stdout.write(`
149
- `),V([`Keyword`,`Tags`,`Volume`,`Page`],e.items.map(e=>[e.keyword,e.tags.join(`, `)||`-`,e.latestMetric?.metric.monthlySearchVolume.state===`observed`?e.latestMetric.metric.monthlySearchVolume.value:`-`,e.page?.url??`-`])),e.pagination.nextOffset!==null&&process.stdout.write(`\nMore rows are available. Continue with --offset ${e.pagination.nextOffset}.\n`)}const Nc=E({meta:{name:`list`,description:`List saved keyword sets for a project`},args:{...xc,json:Sc},run:async({args:e})=>{let t=await Cc(e),n=at({projectId:t.id});I(e)?z({projectId:t.id,sets:n}):jc(n)}}),Pc=E({meta:{name:`refresh`,description:`Preview or refresh saved keyword metrics`},args:{...xc,set:{type:`string`,description:`Keyword set id or name.`,required:!0},provider:{type:`string`,description:`Optional keyword provider.`},limit:{type:`string`,default:`1000`,description:`Keywords to refresh, from 1 to 1000.`},offset:{type:`string`,default:`0`,description:`Saved keyword offset, from 0 to 100000.`},yes:{type:`boolean`,default:!1,description:`Run the paid refresh after estimating its cost.`},json:Sc},run:async({args:e})=>{let t=M(e.limit),n=M(e.offset);if(!Number.isSafeInteger(t))throw new w(`INVALID_INPUT`,`--limit must be an integer.`);if(!Number.isSafeInteger(n))throw new w(`INVALID_INPUT`,`--offset must be an integer.`);let r=await Nt({projectId:(await Cc(e)).id,idOrName:k(e.set)??``,provider:k(e.provider),limit:t,offset:n,execute:j(e.yes)});if(I(e)){z(r);return}B([[`Set`,r.set.name],[`Mode`,r.mode],[`Keywords`,String(r.selection.selectedKeywords)],[`Offset`,String(r.selection.offset)],[`Requests`,String(r.cost.requestCount)],[`Estimated cost`,r.cost.estimatedMicros===null?`unavailable`:`$${(r.cost.estimatedMicros/1e6).toFixed(4)}`],...r.execution?[[`Saved snapshots`,String(r.execution.savedSnapshots)],[`Partial batches`,String(r.execution.partialBatches)],[`Failed batches`,String(r.execution.failedBatches)],[`Actual cost`,r.execution.actualMicros===null?`unavailable`:`$${(r.execution.actualMicros/1e6).toFixed(4)}`]]:[]]),r.mode===`preview`&&process.stdout.write(`\nRun again with --yes to refresh these ${r.selection.selectedKeywords} keywords.\n`)}}),Fc=E({meta:{name:`remove`,description:`Remove keywords from a saved set`},args:{...xc,set:{type:`string`,description:`Keyword set id or name.`,required:!0},keyword:{type:`string`,description:`One keyword.`},keywords:{type:`string`,description:`Comma-separated keywords.`},file:{type:`string`,description:`Newline-delimited keyword file.`},json:Sc},run:async({args:e})=>{let t=Mr({projectId:(await Cc(e)).id,idOrName:k(e.set)??``,keywords:await wc(e)});I(e)?z(t):B([[`Set`,t.setId],[`Removed`,String(t.removed)],[`Keywords`,String(t.keywordCount)]])}}),Ic=E({meta:{name:`keyword-sets`,description:`Manage project keyword research sets`},subCommands:{create:Ec,list:Nc,show:E({meta:{name:`show`,description:`Show a bounded saved keyword set view`},args:{...xc,set:{type:`string`,description:`Keyword set id or name.`,required:!0},tag:{type:`string`,description:`Only return keywords with this tag.`},limit:{type:`string`,default:`100`,description:`Rows to return, from 1 to 1000.`},offset:{type:`string`,default:`0`,description:`Rows to skip.`},json:Sc},run:async({args:e})=>{let t=M(e.limit),n=M(e.offset);if(!Number.isSafeInteger(t)||!Number.isSafeInteger(n))throw new w(`INVALID_INPUT`,`--limit and --offset must be integers.`);let r=wr({projectId:(await Cc(e)).id,idOrName:k(e.set)??``,tag:k(e.tag),limit:t,offset:n});I(e)?z(r):Mc(r)}}),add:Tc,remove:Fc,refresh:Pc,export:Oc,delete:Dc}});function Lc(e){Ao(e.map(e=>({title:`${e.name}${e.isDefault?` (default)`:``}`,description:e.siteUrl,meta:[e.id,e.startUrl?`crawl ${e.startUrl}`:``,e.watchUrls.length?`${e.watchUrls.length} watched URL${e.watchUrls.length===1?``:`s`}`:``,e.brandTerms.length?`${e.brandTerms.length} brand term${e.brandTerms.length===1?``:`s`}`:``]})),{empty:`No saved projects.`})}function Rc(e){B([[`ID`,e.id],[`Name`,e.name],[`GSC property`,e.siteUrl],[`Search Console account`,e.googleAccounts?.searchConsole??`default Google account`],[`Crawl URL`,e.startUrl??`not set`],[`Watch URLs`,e.watchUrls.join(`, `)||`not set`],[`Brand terms`,e.brandTerms.join(`, `)||`not set`],[`Google Analytics property`,e.analytics.google?.propertyId??`not set`],[`Google Analytics account`,e.googleAccounts?.googleAnalytics??`default Google account`],[`Clicky site`,e.analytics.clicky?.siteId??`not set`],[`Selected analytics`,e.analytics.selected??`not set`],[`Report day`,e.reportDay?String(e.reportDay):`not set`],[`Technical weekday`,e.technicalWeekday===void 0?`not set`:String(e.technicalWeekday)],[`Default`,e.isDefault?`yes`:`no`]])}const zc=E({meta:{name:`add`,description:`Create or update a project profile`},args:{id:{type:`string`,description:`Short stable project id, for example acme.`},name:{type:`string`,description:`Human project name.`},site:{type:`string`,description:`GSC property URL, for example sc-domain:example.com.`},"search-console-account":{type:`string`,description:`Saved Google account email for Search Console.`},url:{type:`string`,description:`Default technical crawl start URL.`},urls:{type:`string`,description:`Comma-separated URLs to watch with URL Inspection.`},"google-analytics-property":{type:`string`,description:`Optional Google Analytics property ID for this project.`},"google-analytics-account":{type:`string`,description:`Saved Google account email for Google Analytics.`},"clicky-site-id":{type:`string`,description:`Optional legacy Clicky site ID. New connections use seo start.`},brand:{type:`string`,description:`Comma-separated branded query terms to exclude by default.`},"report-day":{type:`string`,description:`Preferred monthly report day, 1-31.`},weekday:{type:`string`,description:`Preferred technical-watch weekday, 0-7.`},default:{type:`boolean`,description:`Make this the default project.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=I(e),n=k(e.id),r=n?vt(n)??vt(Ss(n)):void 0,i=k(e[`google-analytics-property`]),a=k(e[`google-analytics-account`]),o=k(e[`search-console-account`]),s=k(e[`clicky-site-id`]);if(i&&s)throw new w(`INVALID_INPUT`,`Pass either --google-analytics-property or --clicky-site-id, not both.`);if(a&&!i&&!r?.analytics.google?.propertyId)throw new w(`INVALID_INPUT`,`--google-analytics-account needs a Google Analytics property.`);let c=await Ro({site:k(e.site)??r?.siteUrl,options:{json:t,account:o}}),l={name:k(e.name),siteUrl:c,startUrl:k(e.url),watchUrls:e.urls===void 0?void 0:F(e.urls),brandTerms:e.brand===void 0?void 0:F(e.brand),analytics:i||a?{...r?.analytics,selected:`google`,google:{propertyId:i??r?.analytics.google?.propertyId??``}}:s?{...r?.analytics,selected:`clicky`,clicky:{siteId:s}}:void 0,googleAccounts:o||a?{...r?.googleAccounts,...o?{searchConsole:o}:{},...a?{googleAnalytics:a}:{}}:void 0,reportDay:M(e[`report-day`]),technicalWeekday:M(e.weekday),isDefault:j(e.default)},u=r?_n(r.id,l):$t({id:n,...l});if(t){z(u);return}Rc(u)}}),Bc=E({meta:{name:`default`,description:`Set the default project`},args:{id:{type:`string`,required:!0,description:`Project id or name.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=tr(k(e.id)??``);if(I(e)){z(t);return}process.stdout.write(`Default project set to ${t.id}.\n`)}}),Vc=E({meta:{name:`delete`,description:`Delete a project profile`},args:{id:{type:`string`,required:!0,description:`Project id or name.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=k(e.id)??``,n=St(t);if(I(e)){z({id:t,deleted:n});return}process.stdout.write(`${n?`Deleted`:`Not found`} ${t}.\n`)}}),Hc=E({meta:{name:`client`,description:`Manage saved project profiles`},subCommands:{setup:yc,brand:bc,"keyword-sets":Ic,list:E({meta:{name:`list`,description:`List saved project profiles`},args:{json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=We();if(I(e)){z({clients:t});return}Lc(t)}}),add:zc,show:E({meta:{name:`show`,description:`Show one project profile`},args:{id:{type:`string`,description:`Project id or name. Defaults to the default project.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=vt(k(e.id));if(!t)throw Error(`Project not found.`);if(I(e)){z(t);return}Rc(t)}}),default:Bc,delete:Vc}}),Uc=E({...Hc,meta:{name:`projects`,description:`Manage saved project profiles`}}),Wc=E({meta:{name:`content`,description:`Optimize page content from real GSC queries`},subCommands:{optimize:E({meta:{name:`optimize`,description:`Build a content optimization report for one URL`},args:{site:{type:`string`},client:{type:`string`,description:`Legacy alias for --project.`},project:{type:`string`,description:`Saved project id or name.`},url:{type:`string`,required:!0},days:{type:`string`,description:`GSC lookback days. Defaults to 28.`},limit:{type:`string`,description:`Maximum GSC queries to inspect. Defaults to 25.`},"min-impressions":{type:`string`,description:`Minimum query impressions. Defaults to 10.`},"include-brand":{type:`boolean`,default:!1,description:`Include branded queries.`},"verify-content":Pa(`Fetch the page for content checks. Defaults to true.`,`Skip fetching the page for content checks.`),js:{type:`boolean`,default:!1,description:`Force JavaScript rendering for page extraction.`},refresh:{type:`boolean`,default:!1,description:`Bypass local GSC and HTTP cache.`},json:{type:`boolean`,default:!1}},run:async({args:e})=>{let t=I(e),n=await U({client:A(e),site:k(e.site),options:{json:t,refresh:j(e.refresh)}}),r=await Ye({site:n.site,url:k(e.url)??``,days:M(e.days),limit:M(e.limit),minImpressions:M(e[`min-impressions`]),brandTerms:n.client?.brandTerms,includeBrand:j(e[`include-brand`]),verifyContent:j(e[`verify-content`])!==!1,js:j(e.js)?!0:`auto`,refresh:j(e.refresh)});if(t){z(r);return}q({title:`Content optimization`,target:r.url,status:`info`,summary:r.summary.verdict,metrics:[{label:`Score`,value:`${r.summary.score}/100`},{label:`Primary intent`,value:r.summary.primaryIntent},{label:`Primary query`,value:r.summary.primaryQuery??`None`},{label:`Queries`,value:G(r.summary.queries)},{label:`Opportunities`,value:G(r.summary.opportunities)},{label:`Estimated lift`,value:G(r.summary.estimatedClickLift)}]}),Y(`Top actions`,r.topActions.map(e=>({label:e.title,action:e.action,context:e.plainEnglish}))),r.brief.sections.length&&(process.stdout.write(`
149
+ `),B([`Keyword`,`Tags`,`Volume`,`Page`],e.items.map(e=>[e.keyword,e.tags.join(`, `)||`-`,e.latestMetric?.metric.monthlySearchVolume.state===`observed`?e.latestMetric.metric.monthlySearchVolume.value:`-`,e.page?.url??`-`])),e.pagination.nextOffset!==null&&process.stdout.write(`\nMore rows are available. Continue with --offset ${e.pagination.nextOffset}.\n`)}const Pc=T({meta:{name:`list`,description:`List saved keyword sets for a project`},args:{...Sc,json:Cc},run:async({args:e})=>{let t=await wc(e),n=ot({projectId:t.id});F(e)?R({projectId:t.id,sets:n}):Mc(n)}}),Fc=T({meta:{name:`refresh`,description:`Preview or refresh saved keyword metrics`},args:{...Sc,set:{type:`string`,description:`Keyword set id or name.`,required:!0},provider:{type:`string`,description:`Optional keyword provider.`},limit:{type:`string`,default:`1000`,description:`Keywords to refresh, from 1 to 1000.`},offset:{type:`string`,default:`0`,description:`Saved keyword offset, from 0 to 100000.`},yes:{type:`boolean`,default:!1,description:`Run the paid refresh after estimating its cost.`},json:Cc},run:async({args:e})=>{let t=j(e.limit),n=j(e.offset);if(!Number.isSafeInteger(t))throw new C(`INVALID_INPUT`,`--limit must be an integer.`);if(!Number.isSafeInteger(n))throw new C(`INVALID_INPUT`,`--offset must be an integer.`);let r=await Pt({projectId:(await wc(e)).id,idOrName:O(e.set)??``,provider:O(e.provider),limit:t,offset:n,execute:A(e.yes)});if(F(e)){R(r);return}z([[`Set`,r.set.name],[`Mode`,r.mode],[`Keywords`,String(r.selection.selectedKeywords)],[`Offset`,String(r.selection.offset)],[`Requests`,String(r.cost.requestCount)],[`Estimated cost`,r.cost.estimatedMicros===null?`unavailable`:`$${(r.cost.estimatedMicros/1e6).toFixed(4)}`],...r.execution?[[`Saved snapshots`,String(r.execution.savedSnapshots)],[`Partial batches`,String(r.execution.partialBatches)],[`Failed batches`,String(r.execution.failedBatches)],[`Actual cost`,r.execution.actualMicros===null?`unavailable`:`$${(r.execution.actualMicros/1e6).toFixed(4)}`]]:[]]),r.mode===`preview`&&process.stdout.write(`\nRun again with --yes to refresh these ${r.selection.selectedKeywords} keywords.\n`)}}),Ic=T({meta:{name:`remove`,description:`Remove keywords from a saved set`},args:{...Sc,set:{type:`string`,description:`Keyword set id or name.`,required:!0},keyword:{type:`string`,description:`One keyword.`},keywords:{type:`string`,description:`Comma-separated keywords.`},file:{type:`string`,description:`Newline-delimited keyword file.`},json:Cc},run:async({args:e})=>{let t=Nr({projectId:(await wc(e)).id,idOrName:O(e.set)??``,keywords:await Tc(e)});F(e)?R(t):z([[`Set`,t.setId],[`Removed`,String(t.removed)],[`Keywords`,String(t.keywordCount)]])}}),Lc=T({meta:{name:`keyword-sets`,description:`Manage project keyword research sets`},subCommands:{create:Dc,list:Pc,show:T({meta:{name:`show`,description:`Show a bounded saved keyword set view`},args:{...Sc,set:{type:`string`,description:`Keyword set id or name.`,required:!0},tag:{type:`string`,description:`Only return keywords with this tag.`},limit:{type:`string`,default:`100`,description:`Rows to return, from 1 to 1000.`},offset:{type:`string`,default:`0`,description:`Rows to skip.`},json:Cc},run:async({args:e})=>{let t=j(e.limit),n=j(e.offset);if(!Number.isSafeInteger(t)||!Number.isSafeInteger(n))throw new C(`INVALID_INPUT`,`--limit and --offset must be integers.`);let r=Tr({projectId:(await wc(e)).id,idOrName:O(e.set)??``,tag:O(e.tag),limit:t,offset:n});F(e)?R(r):Nc(r)}}),add:Ec,remove:Ic,refresh:Fc,export:kc,delete:Oc}});function Rc(e){jo(e.map(e=>({title:`${e.name}${e.isDefault?` (default)`:``}`,description:e.siteUrl,meta:[e.id,e.startUrl?`crawl ${e.startUrl}`:``,e.watchUrls.length?`${e.watchUrls.length} watched URL${e.watchUrls.length===1?``:`s`}`:``,e.brandTerms.length?`${e.brandTerms.length} brand term${e.brandTerms.length===1?``:`s`}`:``]})),{empty:`No saved projects.`})}function zc(e){z([[`ID`,e.id],[`Name`,e.name],[`GSC property`,e.siteUrl],[`Search Console account`,e.googleAccounts?.searchConsole??`default Google account`],[`Crawl URL`,e.startUrl??`not set`],[`Watch URLs`,e.watchUrls.join(`, `)||`not set`],[`Brand terms`,e.brandTerms.join(`, `)||`not set`],[`Google Analytics property`,e.analytics.google?.propertyId??`not set`],[`Google Analytics account`,e.googleAccounts?.googleAnalytics??`default Google account`],[`Clicky site`,e.analytics.clicky?.siteId??`not set`],[`Selected analytics`,e.analytics.selected??`not set`],[`Report day`,e.reportDay?String(e.reportDay):`not set`],[`Technical weekday`,e.technicalWeekday===void 0?`not set`:String(e.technicalWeekday)],[`Default`,e.isDefault?`yes`:`no`]])}const Bc=T({meta:{name:`add`,description:`Create or update a project profile`},args:{id:{type:`string`,description:`Short stable project id, for example acme.`},name:{type:`string`,description:`Human project name.`},site:{type:`string`,description:`GSC property URL, for example sc-domain:example.com.`},"search-console-account":{type:`string`,description:`Saved Google account email for Search Console.`},url:{type:`string`,description:`Default technical crawl start URL.`},urls:{type:`string`,description:`Comma-separated URLs to watch with URL Inspection.`},"google-analytics-property":{type:`string`,description:`Optional Google Analytics property ID for this project.`},"google-analytics-account":{type:`string`,description:`Saved Google account email for Google Analytics.`},"clicky-site-id":{type:`string`,description:`Optional legacy Clicky site ID. New connections use seo start.`},brand:{type:`string`,description:`Comma-separated branded query terms to exclude by default.`},"report-day":{type:`string`,description:`Preferred monthly report day, 1-31.`},weekday:{type:`string`,description:`Preferred technical-watch weekday, 0-7.`},default:{type:`boolean`,description:`Make this the default project.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=F(e),n=O(e.id),r=n?yt(n)??yt(Cs(n)):void 0,i=O(e[`google-analytics-property`]),a=O(e[`google-analytics-account`]),o=O(e[`search-console-account`]),s=O(e[`clicky-site-id`]);if(i&&s)throw new C(`INVALID_INPUT`,`Pass either --google-analytics-property or --clicky-site-id, not both.`);if(a&&!i&&!r?.analytics.google?.propertyId)throw new C(`INVALID_INPUT`,`--google-analytics-account needs a Google Analytics property.`);let c=await zo({site:O(e.site)??r?.siteUrl,options:{json:t,account:o}}),l={name:O(e.name),siteUrl:c,startUrl:O(e.url),watchUrls:e.urls===void 0?void 0:P(e.urls),brandTerms:e.brand===void 0?void 0:P(e.brand),analytics:i||a?{...r?.analytics,selected:`google`,google:{propertyId:i??r?.analytics.google?.propertyId??``}}:s?{...r?.analytics,selected:`clicky`,clicky:{siteId:s}}:void 0,googleAccounts:o||a?{...r?.googleAccounts,...o?{searchConsole:o}:{},...a?{googleAnalytics:a}:{}}:void 0,reportDay:j(e[`report-day`]),technicalWeekday:j(e.weekday),isDefault:A(e.default)},u=r?vn(r.id,l):en({id:n,...l});if(t){R(u);return}zc(u)}}),Vc=T({meta:{name:`default`,description:`Set the default project`},args:{id:{type:`string`,required:!0,description:`Project id or name.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=nr(O(e.id)??``);if(F(e)){R(t);return}process.stdout.write(`Default project set to ${t.id}.\n`)}}),Hc=T({meta:{name:`delete`,description:`Delete a project profile`},args:{id:{type:`string`,required:!0,description:`Project id or name.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=O(e.id)??``,n=Ct(t);if(F(e)){R({id:t,deleted:n});return}process.stdout.write(`${n?`Deleted`:`Not found`} ${t}.\n`)}}),Uc=T({meta:{name:`client`,description:`Manage saved project profiles`},subCommands:{setup:bc,brand:xc,"keyword-sets":Lc,list:T({meta:{name:`list`,description:`List saved project profiles`},args:{json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=Ge();if(F(e)){R({clients:t});return}Rc(t)}}),add:Bc,show:T({meta:{name:`show`,description:`Show one project profile`},args:{id:{type:`string`,description:`Project id or name. Defaults to the default project.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=yt(O(e.id));if(!t)throw Error(`Project not found.`);if(F(e)){R(t);return}zc(t)}}),default:Vc,delete:Hc}}),Wc=T({...Uc,meta:{name:`projects`,description:`Manage saved project profiles`}}),Gc=T({meta:{name:`content`,description:`Optimize page content from real GSC queries`},subCommands:{optimize:T({meta:{name:`optimize`,description:`Build a content optimization report for one URL`},args:{site:{type:`string`},client:{type:`string`,description:`Legacy alias for --project.`},project:{type:`string`,description:`Saved project id or name.`},url:{type:`string`,required:!0},days:{type:`string`,description:`GSC lookback days. Defaults to 28.`},limit:{type:`string`,description:`Maximum GSC queries to inspect. Defaults to 25.`},"min-impressions":{type:`string`,description:`Minimum query impressions. Defaults to 10.`},"include-brand":{type:`boolean`,default:!1,description:`Include branded queries.`},"verify-content":Fa(`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=F(e),n=await H({client:k(e),site:O(e.site),options:{json:t,refresh:A(e.refresh)}}),r=await Xe({site:n.site,url:O(e.url)??``,days:j(e.days),limit:j(e.limit),minImpressions:j(e[`min-impressions`]),brandTerms:n.client?.brandTerms,includeBrand:A(e[`include-brand`]),verifyContent:A(e[`verify-content`])!==!1,js:A(e.js)?!0:`auto`,refresh:A(e.refresh)});if(t){R(r);return}K({title:`Content optimization`,target:r.url,status:`info`,summary:r.summary.verdict,metrics:[{label:`Score`,value:`${r.summary.score}/100`},{label:`Primary intent`,value:r.summary.primaryIntent},{label:`Primary query`,value:r.summary.primaryQuery??`None`},{label:`Queries`,value:W(r.summary.queries)},{label:`Opportunities`,value:W(r.summary.opportunities)},{label:`Estimated lift`,value:W(r.summary.estimatedClickLift)}]}),J(`Top actions`,r.topActions.map(e=>({label:e.title,action:e.action,context:e.plainEnglish}))),r.brief.sections.length&&(process.stdout.write(`
150
150
  Suggested sections
151
- `),J([`Heading`,`Queries`],r.brief.sections.map(e=>[K(e.heading,48),K(e.queries.join(`, `),80)]))),r.intentMix.length&&(process.stdout.write(`
151
+ `),q([`Heading`,`Queries`],r.brief.sections.map(e=>[G(e.heading,48),G(e.queries.join(`, `),80)]))),r.intentMix.length&&(process.stdout.write(`
152
152
  Intent mix
153
- `),J([`Intent`,`Queries`,`Impressions`,`Clicks`],r.intentMix.map(e=>[e.intent,G(e.queries),G(e.impressions),G(e.clicks)]))),X(`Caveats`,r.caveats)}})}});var Gc=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 Kc(e){return` `.repeat(e)}function qc(e){return e&&typeof e==`object`&&`toJSON`in e&&typeof e.toJSON==`function`?e.toJSON():e}async function Jc(e,t,n,r,i=!1){let a=qc(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(`[
153
+ `),q([`Intent`,`Queries`,`Impressions`,`Clicks`],r.intentMix.map(e=>[e.intent,W(e.queries),W(e.impressions),W(e.clicks)]))),Y(`Caveats`,r.caveats)}})}});var Kc=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 qc(e){return` `.repeat(e)}function Jc(e){return e&&typeof e==`object`&&`toJSON`in e&&typeof e.toJSON==`function`?e.toJSON():e}async function Yc(e,t,n,r,i=!1){let a=Jc(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(`[
154
154
  `);for(let t=0;t<a.length;t+=1)t>0&&await e.write(`,
155
- `),await e.write(Kc(n+1)),await Jc(e,a[t],n+1,r,!0);return await e.write(`\n${Kc(n)}]`),!0}let t=Object.entries(a).filter(([,e])=>{let t=qc(e);return![`undefined`,`function`,`symbol`].includes(typeof t)});if(t.length===0)return await e.write(`{}`),!0;await e.write(`{
155
+ `),await e.write(qc(n+1)),await Yc(e,a[t],n+1,r,!0);return await e.write(`\n${qc(n)}]`),!0}let t=Object.entries(a).filter(([,e])=>{let t=Jc(e);return![`undefined`,`function`,`symbol`].includes(typeof t)});if(t.length===0)return await e.write(`{}`),!0;await e.write(`{
156
156
  `);for(let i=0;i<t.length;i+=1){let[a,o]=t[i];i>0&&await e.write(`,
157
- `),await e.write(`${Kc(n+1)}${JSON.stringify(a)}: `),await Jc(e,o,n+1,r)}return await e.write(`\n${Kc(n)}}`),!0}finally{r.delete(a)}}async function Yc(e,t){let n=new Gc(e);await Jc(n,t,0,new Set)||await n.write(`undefined`),await n.write(`
158
- `),await n.flush()}async function Xc(e,t){if(!e){await Yc(process.stdout,t);return}await ci(Zr(e),{recursive:!0});let n=Vr(e);try{await Yc(n,t),n.end(),await Vi(n)}catch(e){throw n.destroy(),e}process.stdout.write(`Wrote ${e}\n`)}function Zc(e){return e===`high`?`fail`:`warning`}function Qc(e,t=vr(e)){let n=jt(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:Zc(e.severity),title:`${e.title} on ${e.count} ${e.count===1?`URL`:`URLs`}`,explanation:cr(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:cr(e.ruleId)?.whyItMatters??e.whyThisRanks,evidence:e.sampleUrls.slice(0,5),fix:e.howToFix}));Oo({title:e.config.strategy===`health`?`Sitemap health pass`:`Site crawl`,target:e.config.url,status:r,summary:e.status===`partial`?`Partial evidence from ${e.summary.totalPages} retained pages. Review the caveats before treating missing findings as an all-clear.`:`${e.summary.totalPages} pages retained with ${e.issues.length} findings.`,metrics:[{label:`Pages`,value:e.summary.totalPages},{label:`High`,value:e.summary.highIssues,status:`fail`},{label:`Medium`,value:e.summary.mediumIssues,status:`warning`},{label:`Low`,value:e.summary.lowIssues,status:`info`},{label:`Failed fetches`,value:e.summary.failedUrls}],sections:[{title:`Access diagnostics`,diagnostics:i},{title:`Prioritised fixes`,diagnostics:a},{title:`Review before changing anything`,diagnostics:o},...!i.length&&!a.length&&!o.length?[{title:`Checks`,diagnostics:[{status:`pass`,title:`No prioritised findings were observed`,explanation:`The evaluated crawl evidence did not produce a prioritised fix or review item.`}]}]:[]],notes:[`Strategy: ${e.config.strategy}. Data: ${e.status}. Report: ${e.id}.`,...e.warnings,...e.caveats]})}const $c={"google-analytics-property":{type:`string`,description:`Google Analytics property ID for landing-page sessions. Defaults from --project when saved.`},"clicky-site-id":{type:`string`,description:`Clicky site ID for landing-page visits. Defaults from --project when saved.`}};function el(e,t){let n=k(e[`google-analytics-property`]),r=k(e[`clicky-site-id`]);if(n&&r)throw new w(`INVALID_INPUT`,`Pass either --google-analytics-property or --clicky-site-id, not both.`);return n?{provider:`google`,propertyId:n}:r?{provider:`clicky`,siteId:_e(r)}:t}const tl={high:3,medium:2,low:1},nl=E({meta:{name:`crawl`,description:`Run a fast sitemap health pass or a full technical crawl`},args:{crawlUrl:{type:`positional`,required:!1,description:`Site start URL. Optional with --sitemap-url.`},url:{type:`string`,description:`Start URL to crawl. Optional when --project has a saved crawl URL.`},site:{type:`string`,description:`GSC property URL for joining page metrics.`},client:{type:`string`,description:`Legacy alias for --project.`},project:{type:`string`,description:`Saved project id or name.`},...$c,mode:{type:`string`,description:`Crawl mode: site, page, list, or sitemap. Start with sitemap --health for large sites.`},health:{type:`boolean`,default:!1,description:`Run the uncached sitemap status and redirect pass before a full audit. No page-body analysis.`},"sitemap-url":{type:`string`,description:`Explicit sitemap URL. Selects sitemap mode when --mode is omitted.`},urls:{type:`string`,description:`Comma-separated URLs for list mode.`},"urls-file":{type:`string`,description:`File with one URL per line for list mode.`},"max-pages":{type:`string`,description:`Maximum pages to crawl, up to 10,000. Defaults to 500.`},"max-depth":{type:`string`,description:`Maximum click depth, up to 64. Defaults to 16.`},concurrency:{type:`string`,description:`Parallel page fetches, up to 16. Defaults to 8, or progressively up to 4 with --health.`},"fetch-interval-cap":{type:`string`,description:`Maximum page fetches per interval per host.`},"fetch-interval-ms":{type:`string`,description:`Fetch rate interval in milliseconds.`},refresh:{type:`boolean`,default:!1,description:`Bypass local HTTP cache and fetch fresh pages.`},include:{type:`string`,description:`Comma-separated URL patterns to include.`},exclude:{type:`string`,description:`Comma-separated URL patterns to exclude.`},"no-sitemap":{type:`boolean`,default:!1,description:`Do not seed URLs from sitemap.xml.`},"no-robots":{type:`boolean`,default:!1,description:`Do not skip URLs disallowed by robots.txt.`},"no-external":{type:`boolean`,default:!1,description:`Do not check external links. Reserved for full link checks.`},js:{type:`boolean`,default:!1,description:`Legacy alias for --rendering on.`},rendering:{type:`string`,description:`JavaScript rendering mode: auto, on, or off. Defaults to auto.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON. Alias for --format json.`},format:{type:`string`,description:`Output format: pretty, json, csv, html, markdown, or junit. JUnit requires --health.`},output:{type:`string`,description:`Write output to this path instead of stdout.`},csv:{type:`string`,description:`CSV table to render: issues or pages. Defaults to issues.`},save:{type:`boolean`,default:!1,description:`Save the crawl report locally.`},severity:{type:`string`,description:`Only show top fixes at this severity.`},"fail-on":{type:`string`,description:`Exit non-zero when issues exist at this severity or above.`}},run:async({args:e})=>{let t=I(e),n=ol(e.format,t),r=k(e.output),i=sl(e.csv),a=il(e.severity),o=il(e[`fail-on`]),s=A(e),c=await cl(e),l=al(e),u=k(e[`sitemap-url`]),d=j(e.health),f=rl(e.mode);if(n===`junit`&&!d)throw Error(`--format junit requires --health.`);if((d||u)&&f&&f!==`sitemap`)throw Error(`--health and --sitemap-url require sitemap mode.`);if(d&&Ia(e,`sitemap`))throw Error(`--health cannot be combined with --no-sitemap.`);if((d||u)&&c.length)throw Error(`--health and --sitemap-url cannot be combined with URL list input.`);let p=k(e.site)||s||!l&&!c.length&&!u?await U({client:s,site:k(e.site),options:{json:t}}):void 0,m=l??p?.client?.startUrl??(p?.site?bs(p.site):void 0)??(u?new URL(`/`,u).toString():void 0)??c[0];if(!m)throw Error(`No crawl URL selected. Pass a URL, --url, or use --project with a saved crawl URL.`);let h=await rt({url:m,projectId:p?.client?.id,site:p?.site,mode:f??(d||u?`sitemap`:c.length?`list`:void 0),strategy:d?`health`:`full`,sitemapUrl:u,urls:c,maxPages:M(e[`max-pages`]),maxDepth:M(e[`max-depth`]),concurrency:M(e.concurrency),refresh:j(e.refresh),fetchRate:za({...e,"fetch-concurrency":e.concurrency}),include:P(e.include),exclude:P(e.exclude),googleAnalyticsPropertyId:k(e[`google-analytics-property`]),analyticsConnection:el(e,Jn(p?.client)),useSitemap:!Ia(e,`sitemap`),respectRobots:!Ia(e,`robots`),checkExternal:!Ia(e,`external`),js:Fa(e)}),g=j(e.save)?yn(h):void 0,_=vr(h,{severity:a}),v=o?h.issues.some(e=>tl[e.severity]>=tl[o]):!1,y=h.status===`failed`,b={...h,topFixes:_,reviewObservations:jt(h,{severity:a}),...o?{failOn:o,failedThreshold:v}:{},...g?{saved:g}:{}};if(n===`json`){await Xc(r,b),(y||v)&&(process.exitCode=1);return}if(n===`csv`){await ul(r,i===`pages`?un(h):qt(h)),(y||v)&&(process.exitCode=1);return}if(n===`html`){await ul(r,tn(h,_)),(y||v)&&(process.exitCode=1);return}if(n===`junit`){await ul(r,Gn(h)),(y||v)&&(process.exitCode=1);return}if(n===`markdown`){await ul(r,jn(h,_)),(y||v)&&(process.exitCode=1);return}if(r){await ul(r,on(h,_)),(y||v)&&(process.exitCode=1);return}Qc(h,_),(g||o)&&(process.stdout.write(`
157
+ `),await e.write(`${qc(n+1)}${JSON.stringify(a)}: `),await Yc(e,o,n+1,r)}return await e.write(`\n${qc(n)}}`),!0}finally{r.delete(a)}}async function Xc(e,t){let n=new Kc(e);await Yc(n,t,0,new Set)||await n.write(`undefined`),await n.write(`
158
+ `),await n.flush()}async function Zc(e,t){if(!e){await Xc(process.stdout,t);return}await li(Qr(e),{recursive:!0});let n=Hr(e);try{await Xc(n,t),n.end(),await Hi(n)}catch(e){throw n.destroy(),e}process.stdout.write(`Wrote ${e}\n`)}function Qc(e){return e===`high`?`fail`:`warning`}function $c(e,t=yr(e)){let n=Mt(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:Qc(e.severity),title:`${e.title} on ${e.count} ${e.count===1?`URL`:`URLs`}`,explanation:lr(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:lr(e.ruleId)?.whyItMatters??e.whyThisRanks,evidence:e.sampleUrls.slice(0,5),fix:e.howToFix}));ko({title:e.config.strategy===`health`?`Sitemap health pass`:`Site crawl`,target:e.config.url,status:r,summary:e.status===`partial`?`Partial evidence from ${e.summary.totalPages} retained pages. Review the caveats before treating missing findings as an all-clear.`:`${e.summary.totalPages} pages retained with ${e.issues.length} findings.`,metrics:[{label:`Pages`,value:e.summary.totalPages},{label:`High`,value:e.summary.highIssues,status:`fail`},{label:`Medium`,value:e.summary.mediumIssues,status:`warning`},{label:`Low`,value:e.summary.lowIssues,status:`info`},{label:`Failed fetches`,value:e.summary.failedUrls}],sections:[{title:`Access diagnostics`,diagnostics:i},{title:`Prioritised fixes`,diagnostics:a},{title:`Review before changing anything`,diagnostics:o},...!i.length&&!a.length&&!o.length?[{title:`Checks`,diagnostics:[{status:`pass`,title:`No prioritised findings were observed`,explanation:`The evaluated crawl evidence did not produce a prioritised fix or review item.`}]}]:[]],notes:[`Strategy: ${e.config.strategy}. Data: ${e.status}. Report: ${e.id}.`,...e.warnings,...e.caveats]})}const el={"google-analytics-property":{type:`string`,description:`Google Analytics property ID for landing-page sessions. Defaults from --project when saved.`},"clicky-site-id":{type:`string`,description:`Clicky site ID for landing-page visits. Defaults from --project when saved.`}};function tl(e,t){let n=O(e[`google-analytics-property`]),r=O(e[`clicky-site-id`]);if(n&&r)throw new C(`INVALID_INPUT`,`Pass either --google-analytics-property or --clicky-site-id, not both.`);return n?{provider:`google`,propertyId:n}:r?{provider:`clicky`,siteId:ve(r)}:t}const nl={high:3,medium:2,low:1},rl=T({meta:{name:`crawl`,description:`Run a fast sitemap health pass or a full technical crawl`},args:{crawlUrl:{type:`positional`,required:!1,description:`Site start URL. Optional with --sitemap-url.`},url:{type:`string`,description:`Start URL to crawl. Optional when --project has a saved crawl URL.`},site:{type:`string`,description:`GSC property URL for joining page metrics.`},client:{type:`string`,description:`Legacy alias for --project.`},project:{type:`string`,description:`Saved project id or name.`},...el,mode:{type:`string`,description:`Crawl mode: site, page, list, or sitemap. Start with sitemap --health for large sites.`},health:{type:`boolean`,default:!1,description:`Run the uncached sitemap status and redirect pass before a full audit. No page-body analysis.`},"sitemap-url":{type:`string`,description:`Explicit sitemap URL. Selects sitemap mode when --mode is omitted.`},urls:{type:`string`,description:`Comma-separated URLs for list mode.`},"urls-file":{type:`string`,description:`File with one URL per line for list mode.`},"max-pages":{type:`string`,description:`Maximum pages to crawl, up to 10,000. Defaults to 500.`},"max-depth":{type:`string`,description:`Maximum click depth, up to 64. Defaults to 16.`},concurrency:{type:`string`,description:`Parallel page fetches, up to 16. Defaults to 8, or progressively up to 4 with --health.`},"fetch-interval-cap":{type:`string`,description:`Maximum page fetches per interval per host.`},"fetch-interval-ms":{type:`string`,description:`Fetch rate interval in milliseconds.`},refresh:{type:`boolean`,default:!1,description:`Bypass local HTTP cache and fetch fresh pages.`},include:{type:`string`,description:`Comma-separated URL patterns to include.`},exclude:{type:`string`,description:`Comma-separated URL patterns to exclude.`},"no-sitemap":{type:`boolean`,default:!1,description:`Do not seed URLs from sitemap.xml.`},"no-robots":{type:`boolean`,default:!1,description:`Do not skip URLs disallowed by robots.txt.`},"no-external":{type:`boolean`,default:!1,description:`Do not check external links. Reserved for full link checks.`},js:{type:`boolean`,default:!1,description:`Legacy alias for --rendering on.`},rendering:{type:`string`,description:`JavaScript rendering mode: auto, on, or off. Defaults to auto.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON. Alias for --format json.`},format:{type:`string`,description:`Output format: pretty, json, csv, html, markdown, or junit. JUnit requires --health.`},output:{type:`string`,description:`Write output to this path instead of stdout.`},csv:{type:`string`,description:`CSV table to render: issues or pages. Defaults to issues.`},save:{type:`boolean`,default:!1,description:`Save the crawl report locally.`},severity:{type:`string`,description:`Only show top fixes at this severity.`},"fail-on":{type:`string`,description:`Exit non-zero when issues exist at this severity or above.`}},run:async({args:e})=>{let t=F(e),n=sl(e.format,t),r=O(e.output),i=cl(e.csv),a=al(e.severity),o=al(e[`fail-on`]),s=k(e),c=await ll(e),l=ol(e),u=O(e[`sitemap-url`]),d=A(e.health),f=il(e.mode);if(n===`junit`&&!d)throw Error(`--format junit requires --health.`);if((d||u)&&f&&f!==`sitemap`)throw Error(`--health and --sitemap-url require sitemap mode.`);if(d&&La(e,`sitemap`))throw Error(`--health cannot be combined with --no-sitemap.`);if((d||u)&&c.length)throw Error(`--health and --sitemap-url cannot be combined with URL list input.`);let p=O(e.site)||s||!l&&!c.length&&!u?await H({client:s,site:O(e.site),options:{json:t}}):void 0,m=l??p?.client?.startUrl??(p?.site?xs(p.site):void 0)??(u?new URL(`/`,u).toString():void 0)??c[0];if(!m)throw Error(`No crawl URL selected. Pass a URL, --url, or use --project with a saved crawl URL.`);let h=await it({url:m,projectId:p?.client?.id,site:p?.site,mode:f??(d||u?`sitemap`:c.length?`list`:void 0),strategy:d?`health`:`full`,sitemapUrl:u,urls:c,maxPages:j(e[`max-pages`]),maxDepth:j(e[`max-depth`]),concurrency:j(e.concurrency),refresh:A(e.refresh),fetchRate:Ba({...e,"fetch-concurrency":e.concurrency}),include:N(e.include),exclude:N(e.exclude),googleAnalyticsPropertyId:O(e[`google-analytics-property`]),analyticsConnection:tl(e,Yn(p?.client)),useSitemap:!La(e,`sitemap`),respectRobots:!La(e,`robots`),checkExternal:!La(e,`external`),js:Ia(e)}),g=A(e.save)?bn(h):void 0,_=yr(h,{severity:a}),v=o?h.issues.some(e=>nl[e.severity]>=nl[o]):!1,y=h.status===`failed`,b={...h,topFixes:_,reviewObservations:Mt(h,{severity:a}),...o?{failOn:o,failedThreshold:v}:{},...g?{saved:g}:{}};if(n===`json`){await Zc(r,b),(y||v)&&(process.exitCode=1);return}if(n===`csv`){await dl(r,i===`pages`?dn(h):Jt(h)),(y||v)&&(process.exitCode=1);return}if(n===`html`){await dl(r,nn(h,_)),(y||v)&&(process.exitCode=1);return}if(n===`junit`){await dl(r,Kn(h)),(y||v)&&(process.exitCode=1);return}if(n===`markdown`){await dl(r,Mn(h,_)),(y||v)&&(process.exitCode=1);return}if(r){await dl(r,sn(h,_)),(y||v)&&(process.exitCode=1);return}$c(h,_),(g||o)&&(process.stdout.write(`
159
159
  Run metadata
160
- `),B([[`Saved report`,g?.id??`no`],[`Fail threshold`,o??`off`]])),(y||v)&&(process.exitCode=1)}});function rl(e){let t=k(e);if(t){if([`site`,`page`,`list`,`sitemap`].includes(t))return t;throw Error(`Mode must be one of: site, page, list, sitemap.`)}}function il(e){let t=k(e);if(t){if([`low`,`medium`,`high`].includes(t))return t;throw Error(`Severity must be one of: low, medium, high.`)}}function al(e){let t=k(e.crawlUrl),n=k(e.url);if(t&&n&&t!==n)throw Error(`Use either a URL argument or --url, not both.`);return t??n}function ol(e,t){let n=k(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 sl(e){let t=k(e);if(!t)return`issues`;if([`issues`,`pages`].includes(t))return t;throw Error(`CSV table must be one of: issues, pages.`)}async function cl(e){let t=P(e.urls)??[],n=k(e[`urls-file`]);return n&&t.push(...ll(await ui(n,`utf8`))),[...new Set(t)]}function ll(e){return e.split(/[\n,]/).map(e=>e.trim()).filter(e=>e&&!e.startsWith(`#`))}async function ul(e,t){if(!e){process.stdout.write(t);return}await ci(Zr(e),{recursive:!0}),await gi(e,t),process.stdout.write(`Wrote ${e}\n`)}async function dl(e,t){let n=A(e),r=k(e.site);if(!(!n&&!r))return(await U({client:n,site:r,options:{json:t}})).site}function fl(e){Ao(e.map(e=>({title:e.id,description:e.site??e.url,meta:[e.status,`${e.totalPages} page${e.totalPages===1?``:`s`}`,`${e.issueCount} issue${e.issueCount===1?``:`s`}`,e.createdAt]})),{empty:`No saved crawl reports.`})}function pl(e){Qc(e)}function ml(e){q({title:`Crawl report comparison`,target:e.after.url,status:e.comparability.status!==`comparable`||e.completeness.status!==`complete`?`unknown`:e.summary.newStatusErrors>0||e.summary.issueGroupsWorse>0?`warning`:`pass`,summary:e.headline,metrics:[{label:`Before`,value:`${e.before.id} (${e.before.generatedAt})`},{label:`After`,value:`${e.after.id} (${e.after.generatedAt})`},{label:`Comparability`,value:e.comparability.status},{label:`Completeness`,value:e.completeness.status},{label:`Pages`,value:`${e.after.summary.totalPages} (${e.summary.pageDelta>=0?`+`:``}${e.summary.pageDelta})`},{label:`Issues`,value:`${e.after.summary.highIssues+e.after.summary.mediumIssues+e.after.summary.lowIssues} (${e.summary.issueDelta>=0?`+`:``}${e.summary.issueDelta})`},{label:`New status errors`,value:e.summary.newStatusErrors},{label:`Fixed status errors`,value:e.summary.fixedStatusErrors},{label:`Issue groups worse`,value:e.summary.issueGroupsWorse},{label:`Issue groups better`,value:e.summary.issueGroupsBetter},{label:`Changed pages`,value:e.summary.changedPages},{label:`Added pages`,value:e.summary.addedPages},{label:`Removed pages`,value:e.summary.removedPages}]}),Y(`Top actions`,e.topActions.map(e=>({label:e.title,context:e.plainEnglish,action:e.action})),e.topActions.length),e.pageChanges.length&&(process.stdout.write(`
160
+ `),z([[`Saved report`,g?.id??`no`],[`Fail threshold`,o??`off`]])),(y||v)&&(process.exitCode=1)}});function il(e){let t=O(e);if(t){if([`site`,`page`,`list`,`sitemap`].includes(t))return t;throw Error(`Mode must be one of: site, page, list, sitemap.`)}}function al(e){let t=O(e);if(t){if([`low`,`medium`,`high`].includes(t))return t;throw Error(`Severity must be one of: low, medium, high.`)}}function ol(e){let t=O(e.crawlUrl),n=O(e.url);if(t&&n&&t!==n)throw Error(`Use either a URL argument or --url, not both.`);return t??n}function sl(e,t){let n=O(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 cl(e){let t=O(e);if(!t)return`issues`;if([`issues`,`pages`].includes(t))return t;throw Error(`CSV table must be one of: issues, pages.`)}async function ll(e){let t=N(e.urls)??[],n=O(e[`urls-file`]);return n&&t.push(...ul(await di(n,`utf8`))),[...new Set(t)]}function ul(e){return e.split(/[\n,]/).map(e=>e.trim()).filter(e=>e&&!e.startsWith(`#`))}async function dl(e,t){if(!e){process.stdout.write(t);return}await li(Qr(e),{recursive:!0}),await _i(e,t),process.stdout.write(`Wrote ${e}\n`)}async function fl(e,t){let n=k(e),r=O(e.site);if(!(!n&&!r))return(await H({client:n,site:r,options:{json:t}})).site}function pl(e){jo(e.map(e=>({title:e.id,description:e.site??e.url,meta:[e.status,`${e.totalPages} page${e.totalPages===1?``:`s`}`,`${e.issueCount} issue${e.issueCount===1?``:`s`}`,e.createdAt]})),{empty:`No saved crawl reports.`})}function ml(e){$c(e)}function hl(e){K({title:`Crawl report comparison`,target:e.after.url,status:e.comparability.status!==`comparable`||e.completeness.status!==`complete`?`unknown`:e.summary.newStatusErrors>0||e.summary.issueGroupsWorse>0?`warning`:`pass`,summary:e.headline,metrics:[{label:`Before`,value:`${e.before.id} (${e.before.generatedAt})`},{label:`After`,value:`${e.after.id} (${e.after.generatedAt})`},{label:`Comparability`,value:e.comparability.status},{label:`Completeness`,value:e.completeness.status},{label:`Pages`,value:`${e.after.summary.totalPages} (${e.summary.pageDelta>=0?`+`:``}${e.summary.pageDelta})`},{label:`Issues`,value:`${e.after.summary.highIssues+e.after.summary.mediumIssues+e.after.summary.lowIssues} (${e.summary.issueDelta>=0?`+`:``}${e.summary.issueDelta})`},{label:`New status errors`,value:e.summary.newStatusErrors},{label:`Fixed status errors`,value:e.summary.fixedStatusErrors},{label:`Issue groups worse`,value:e.summary.issueGroupsWorse},{label:`Issue groups better`,value:e.summary.issueGroupsBetter},{label:`Changed pages`,value:e.summary.changedPages},{label:`Added pages`,value:e.summary.addedPages},{label:`Removed pages`,value:e.summary.removedPages}]}),J(`Top actions`,e.topActions.map(e=>({label:e.title,context:e.plainEnglish,action:e.action})),e.topActions.length),e.pageChanges.length&&(process.stdout.write(`
161
161
  Changed pages
162
- `),V([`Kind`,`Changes`,`URL`],e.pageChanges.slice(0,15).map(e=>[e.kind,e.changes.join(`, `),K(e.url,80)])),e.pageChanges.length>15&&process.stdout.write(`Showing 15 of ${e.pageChanges.length}. Use --json for all changes.\n`)),e.issueChanges.length&&(process.stdout.write(`
162
+ `),B([`Kind`,`Changes`,`URL`],e.pageChanges.slice(0,15).map(e=>[e.kind,e.changes.join(`, `),G(e.url,80)])),e.pageChanges.length>15&&process.stdout.write(`Showing 15 of ${e.pageChanges.length}. Use --json for all changes.\n`)),e.issueChanges.length&&(process.stdout.write(`
163
163
  Issue movement
164
- `),V([`Rule`,`Before`,`After`,`Delta`],e.issueChanges.slice(0,10).map(e=>[e.ruleId,String(e.before),String(e.after),`${e.delta>=0?`+`:``}${e.delta}`]))),X(`Caveats`,e.caveats)}function hl(e,t){if(t.value===`latest`)return e.find(e=>e.id!==t.skipId);if(t.skipId){let n=e.findIndex(e=>e.id===t.skipId);return(n>=0?e.slice(n+1):e).find(e=>e.id!==t.skipId)}return e[1]}function gl(e){if(e.value!==`latest`&&e.value!==`previous`)return a(e.value);let t=hl(Cn({site:e.site,limit:20}),{value:e.value,skipId:e.skipId});return t?a(t.id):void 0}const _l=E({meta:{name:`crawl-reports`,description:`List, show, or delete saved crawl reports`},args:{site:{type:`string`,description:`GSC property URL for filtering reports.`},client:{type:`string`,description:`Legacy alias for --project.`},project:{type:`string`,description:`Saved project id or name.`},id:{type:`string`,description:`Saved crawl report id to show.`},latest:{type:`boolean`,default:!1,description:`Show the latest saved crawl report.`},delete:{type:`string`,description:`Delete a saved crawl report by id.`},rerun:{type:`string`,description:`Rerun a saved crawl report by id, or pass latest.`},compare:{type:`string`,description:`Compare a saved report id, latest, or previous.`},against:{type:`string`,description:`Baseline report id. Defaults to previous.`},limit:{type:`string`,description:`Maximum reports to list. Defaults to 20.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=I(e),n=await dl(e,t),r=k(e.delete),i=k(e.rerun),o=k(e.compare),s=k(e.id);if(r){let e=_(r);if(t){z({id:r,deleted:e});return}process.stdout.write(e?`Deleted crawl report ${r}.\n`:`No saved crawl report found for ${r}.\n`);return}if(i){let e=i===`latest`?Le(n):a(i);if(!e)throw Error(i===`latest`?`No saved crawl reports found.`:`No saved crawl report found for ${i}.`);let r=await rt({...e.config,projectId:e.projectId,site:e.site,googleAnalyticsPropertyId:e.googleAnalyticsPropertyId}),o=yn(r);if(t){z({...r,rerunOf:e.id,saved:o,topFixes:vr(r),reviewObservations:jt(r)});return}process.stdout.write(`Reran crawl report ${e.id}.\n\n`),pl(r);return}if(o){let r=gl({value:o,site:n});if(!r)throw Error(`No saved crawl report found for ${o}.`);let i=k(e.against)??`previous`,a=gl({value:i,site:r.site??n,skipId:r.id});if(!a)throw Error(`No baseline crawl report found for ${i}. Save at least two reports or pass --against <id>.`);let s=mr({before:a,after:r});if(t){z(s);return}ml(s);return}if(s||j(e.latest)){let e=s?a(s):Le(n);if(!e)throw Error(s?`No saved crawl report found for ${s}.`:`No saved crawl reports found.`);if(t){z(e);return}pl(e);return}let c=Cn({site:n,limit:M(e.limit)});if(t){z({reports:c});return}fl(c)}}),vl=E({meta:{name:`gsc-query`,description:`Run a raw Search Console Search Analytics query`},args:{site:{type:`string`,description:`GSC property URL, for example sc-domain:example.com.`},client:{type:`string`,description:`Legacy alias for --project.`},project:{type:`string`,description:`Saved project id or name.`},account:{type:`string`,description:`Saved Google account email.`},"start-date":{type:`string`,description:`Start date YYYY-MM-DD.`},"end-date":{type:`string`,description:`End date YYYY-MM-DD.`},dimensions:{type:`string`,description:`Comma-separated GSC dimensions, for example query,page.`},type:{type:`string`,description:`Search type. Defaults to web.`},limit:{type:`string`,description:`Maximum rows to request and print.`},body:{type:`string`,description:`Raw Search Analytics JSON body.`},"body-file":{type:`string`,description:`Path to a Search Analytics JSON body file.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`},refresh:{type:`boolean`,default:!1,description:`Bypass local cache and fetch fresh GSC data.`}},run:async({args:e})=>{let t=await Va(e.body,e[`body-file`])??{startDate:k(e[`start-date`]),endDate:k(e[`end-date`]),dimensions:P(e.dimensions)??[`query`,`page`],type:k(e.type)??`web`,rowLimit:M(e.limit),dataState:`final`},n=I(e),r=await ys({client:A(e),site:k(e.site)??k(t.siteUrl)},{json:n,refresh:j(e.refresh),account:k(e.account)});delete t.siteUrl;let i=await Cr(r,t,{refresh:j(e.refresh),accountEmail:k(e.account)}),a=M(e.limit),o=a?i.rows.slice(0,a):i.rows;if(n){z({site:r,request:t,...i,rows:o,rowsReturned:o.length});return}q({title:`Search Console query`,target:r,status:i.rowsFetched>o.length?`unknown`:`info`,summary:`${o.length} rows returned from ${i.rowsFetched} fetched rows.`,metrics:[{label:`Rows returned`,value:o.length},{label:`Rows fetched`,value:i.rowsFetched},{label:`API calls`,value:i.calls}]}),V([`Keys`,`Clicks`,`Impr`,`CTR`,`Pos`],o.slice(0,25).map(e=>[e.keys.join(` | `),Math.round(e.clicks),Math.round(e.impressions),e.ctr.toFixed(3),e.position.toFixed(1)]))}}),yl=E({meta:{name:`url-inspect`,description:`Inspect a URL with the Search Console URL Inspection API`},args:{site:{type:`string`,description:`GSC property URL, for example sc-domain:example.com.`},client:{type:`string`,description:`Legacy alias for --project.`},project:{type:`string`,description:`Saved project id or name.`},account:{type:`string`,description:`Saved Google account email.`},url:{type:`string`,description:`URL to inspect.`},language:{type:`string`,description:`Optional inspection language code.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=I(e),n=await ys({client:A(e),site:k(e.site)},{json:t,account:k(e.account)}),r=k(e.url);if(!r)throw Error(`Pass --url.`);let i=await it({siteUrl:n,inspectionUrl:r,accountEmail:k(e.account),languageCode:k(e.language)});if(t){z(i);return}let a=i.inspectionResult?.indexStatusResult;q({title:`URL Inspection`,target:r,status:a===void 0?`unknown`:a.verdict===`PASS`?`pass`:`warning`,summary:a===void 0?`Google did not return index status evidence for this URL.`:`${a.verdict??`Unknown`}: ${a.coverageState??`Coverage state unavailable`}.`,metrics:[{label:`Property`,value:n},{label:`Robots`,value:a?.robotsTxtState??`Unknown`},{label:`Last crawl`,value:a?.lastCrawlTime??`Unknown`},{label:`Google canonical`,value:a?.googleCanonical??`Unknown`}]})}}),bl={days:{cli:`days`,arg:{type:`string`,description:`GSC lookback window. Defaults to 28.`}},recentDays:{cli:`recent`,arg:{type:`string`,description:`Recent anomaly window in days. Defaults to 14.`}},range:{cli:`days`,arg:{type:`string`,description:`GSC lookback window. Defaults to 28.`}},limit:{cli:`limit`,arg:{type:`string`,description:`Maximum rows to return.`}},checkLimit:{cli:`check-limit`,arg:{type:`string`,description:`Maximum candidate pages to fetch and check.`}},minImpressions:{cli:`min-impressions`,arg:{type:`string`,description:`Minimum query impressions.`}},includeBrand:{cli:`include-brand`,arg:{type:`boolean`,default:!1,description:`Include branded queries in opportunity reports.`}},verifyContent:{cli:`verify-content`,arg:{type:`boolean`,default:!1,description:`Verify top opportunities against page title, meta, and content.`}},verifyLimit:{cli:`verify-limit`,arg:{type:`string`,description:`Maximum opportunity URLs to verify.`}},js:{cli:`js`,arg:{type:`boolean`,default:!1,description:`Force JavaScript rendering for verified pages.`}},fetchConcurrency:{cli:`fetch-concurrency`,arg:{type:`string`,description:`Maximum concurrent page fetches per host, up to 16. Defaults to 4.`}},fetchIntervalCap:{cli:`fetch-interval-cap`,arg:{type:`string`,description:`Maximum page fetches per interval per host. Defaults to 4.`}},fetchIntervalMs:{cli:`fetch-interval-ms`,arg:{type:`string`,description:`Fetch rate interval in milliseconds. Defaults to 1000.`}},refresh:{cli:`refresh`,arg:{type:`boolean`,default:!1,description:`Bypass local cache and fetch fresh data.`}}};function Z(e,t={}){return Object.fromEntries(e.map(e=>{let n=bl[e];return[n.cli,{...n.arg,...t[e]}]}))}function xl(e){return e.toLocaleString(`en-GB`,{maximumFractionDigits:e>=100?0:1})}function Sl(e){if(e===null)return`n/a`;let t=Math.abs(e)>=10?e.toFixed(0):e.toFixed(1);return`${e>0?`+`:``}${t}%`}const Cl=E({meta:{name:`traffic-anomaly`,description:`Detect unusual recent GSC traffic movement`},args:{site:{type:`string`},project:{type:`string`,description:`Saved project id or name.`},client:{type:`string`,description:`Legacy alias for --project.`},...Z([`days`,`recentDays`,`refresh`],{days:{description:`Baseline window length in days. Defaults to 90.`},recentDays:{description:`Recent anomaly window in days. Defaults to 7.`},refresh:{description:`Bypass local cache and fetch fresh GSC data.`}}),json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=I(e),n=await _t({site:await ys({client:A(e),site:k(e.site)},{json:t,refresh:j(e.refresh)}),days:M(e.days),recentDays:M(e.recent),refresh:j(e.refresh)});if(t){z(n);return}q({title:`Traffic anomaly report`,target:n.site,status:n.coverage?.status===`complete`?n.anomalies.some(e=>e.significant)?`warning`:`pass`:`unknown`,summary:n.anomalies.some(e=>e.significant)?`At least one recent metric moved beyond the report threshold.`:`No significant recent movement was found in the retained evidence.`,metrics:[{label:`Metrics checked`,value:n.anomalies.length},{label:`Significant`,value:n.anomalies.filter(e=>e.significant).length},{label:`Observed days`,value:n.coverage?.observedDays??`Unavailable`},{label:`Expected days`,value:n.coverage?.expectedDays??`Unavailable`}]}),J([`Metric`,`Direction`,`Baseline/day`,`Recent/day`,`Change`,`z`,`Significant`],n.anomalies.map(e=>[e.metric,e.direction,xl(e.baselineMean),xl(e.comparisonMean),Sl(e.percentChange),e.zScore??`n/a`,e.significant?`yes`:`no`])),n.coverage&&X(`Evidence coverage`,[`${n.coverage.observedDays} of ${n.coverage.expectedDays} requested calendar days returned date aggregates.`,...n.coverage.caveats])}}),wl=E({meta:{name:`update-correlate`,description:`Compare traffic movement with official Google update windows`},args:{site:{type:`string`},project:{type:`string`,description:`Saved project id or name.`},client:{type:`string`,description:`Legacy alias for --project.`},...Z([`days`,`recentDays`,`refresh`],{days:{description:`Baseline window length in days. Defaults to 90.`},recentDays:{description:`Recent anomaly window in days. Defaults to 7.`},refresh:{description:`Bypass local cache and fetch fresh GSC data.`}}),"padding-days":{type:`string`,description:`Days around update windows to count as overlap.`},"known-change":{type:`string`,description:`Manual site-side change to treat as a confounder, for example pruning pages or blocking traffic.`},"ignore-change-log":{type:`boolean`,default:!1,description:`Do not use saved change-log entries as confounders.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=I(e),n=A(e),r=await ys({client:n,site:k(e.site)},{json:t,refresh:j(e.refresh)}),i=await se({site:r,days:M(e.days),recentDays:M(e.recent),paddingDays:M(e[`padding-days`]),knownConfounders:k(e[`known-change`])?[k(e[`known-change`])??``]:void 0,includeChangeLog:!j(e[`ignore-change-log`]),refresh:j(e.refresh)});if(t){z(i);return}q({title:`Google update correlation`,target:i.site,status:`unknown`,summary:i.summary,metrics:[{label:`Classification`,value:i.classification.replaceAll(`-`,` `)},{label:`Attribution`,value:i.attribution.replaceAll(`-`,` `)},{label:`Confidence`,value:i.confidence},{label:`Updates matched`,value:i.overlappingUpdates.length},{label:`Known confounders`,value:i.confounders.length},{label:`Source`,value:i.source.name}]}),X(`Evidence`,i.evidence),X(`Report caveats`,i.caveats),X(`Recommended next checks`,i.actions),J([`Metric`,`Direction`,`Baseline/day`,`Recent/day`,`Change`,`z`],i.anomalies.map(e=>[e.metric,e.direction,xl(e.baselineMean),xl(e.comparisonMean),Sl(e.percentChange),e.zScore??`n/a`])),i.overlappingUpdates.length&&(process.stdout.write(`
164
+ `),B([`Rule`,`Before`,`After`,`Delta`],e.issueChanges.slice(0,10).map(e=>[e.ruleId,String(e.before),String(e.after),`${e.delta>=0?`+`:``}${e.delta}`]))),Y(`Caveats`,e.caveats)}function gl(e,t){if(t.value===`latest`)return e.find(e=>e.id!==t.skipId);if(t.skipId){let n=e.findIndex(e=>e.id===t.skipId);return(n>=0?e.slice(n+1):e).find(e=>e.id!==t.skipId)}return e[1]}function _l(e){if(e.value!==`latest`&&e.value!==`previous`)return a(e.value);let t=gl(wn({site:e.site,limit:20}),{value:e.value,skipId:e.skipId});return t?a(t.id):void 0}const vl=T({meta:{name:`crawl-reports`,description:`List, show, or delete saved crawl reports`},args:{site:{type:`string`,description:`GSC property URL for filtering reports.`},client:{type:`string`,description:`Legacy alias for --project.`},project:{type:`string`,description:`Saved project id or name.`},id:{type:`string`,description:`Saved crawl report id to show.`},latest:{type:`boolean`,default:!1,description:`Show the latest saved crawl report.`},delete:{type:`string`,description:`Delete a saved crawl report by id.`},rerun:{type:`string`,description:`Rerun a saved crawl report by id, or pass latest.`},compare:{type:`string`,description:`Compare a saved report id, latest, or previous.`},against:{type:`string`,description:`Baseline report id. Defaults to previous.`},limit:{type:`string`,description:`Maximum reports to list. Defaults to 20.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=F(e),n=await fl(e,t),r=O(e.delete),i=O(e.rerun),o=O(e.compare),s=O(e.id);if(r){let e=_(r);if(t){R({id:r,deleted:e});return}process.stdout.write(e?`Deleted crawl report ${r}.\n`:`No saved crawl report found for ${r}.\n`);return}if(i){let e=i===`latest`?Re(n):a(i);if(!e)throw Error(i===`latest`?`No saved crawl reports found.`:`No saved crawl report found for ${i}.`);let r=await it({...e.config,projectId:e.projectId,site:e.site,googleAnalyticsPropertyId:e.googleAnalyticsPropertyId}),o=bn(r);if(t){R({...r,rerunOf:e.id,saved:o,topFixes:yr(r),reviewObservations:Mt(r)});return}process.stdout.write(`Reran crawl report ${e.id}.\n\n`),ml(r);return}if(o){let r=_l({value:o,site:n});if(!r)throw Error(`No saved crawl report found for ${o}.`);let i=O(e.against)??`previous`,a=_l({value:i,site:r.site??n,skipId:r.id});if(!a)throw Error(`No baseline crawl report found for ${i}. Save at least two reports or pass --against <id>.`);let s=hr({before:a,after:r});if(t){R(s);return}hl(s);return}if(s||A(e.latest)){let e=s?a(s):Re(n);if(!e)throw Error(s?`No saved crawl report found for ${s}.`:`No saved crawl reports found.`);if(t){R(e);return}ml(e);return}let c=wn({site:n,limit:j(e.limit)});if(t){R({reports:c});return}pl(c)}}),yl=T({meta:{name:`gsc-query`,description:`Run a raw Search Console Search Analytics query`},args:{site:{type:`string`,description:`GSC property URL, for example sc-domain:example.com.`},client:{type:`string`,description:`Legacy alias for --project.`},project:{type:`string`,description:`Saved project id or name.`},account:{type:`string`,description:`Saved Google account email.`},"start-date":{type:`string`,description:`Start date YYYY-MM-DD.`},"end-date":{type:`string`,description:`End date YYYY-MM-DD.`},dimensions:{type:`string`,description:`Comma-separated GSC dimensions, for example query,page.`},type:{type:`string`,description:`Search type. Defaults to web.`},limit:{type:`string`,description:`Maximum rows to request and print.`},body:{type:`string`,description:`Raw Search Analytics JSON body.`},"body-file":{type:`string`,description:`Path to a Search Analytics JSON body file.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`},refresh:{type:`boolean`,default:!1,description:`Bypass local cache and fetch fresh GSC data.`}},run:async({args:e})=>{let t=await Ha(e.body,e[`body-file`])??{startDate:O(e[`start-date`]),endDate:O(e[`end-date`]),dimensions:N(e.dimensions)??[`query`,`page`],type:O(e.type)??`web`,rowLimit:j(e.limit),dataState:`final`},n=F(e),r=await bs({client:k(e),site:O(e.site)??O(t.siteUrl)},{json:n,refresh:A(e.refresh),account:O(e.account)});delete t.siteUrl;let i=await wr(r,t,{refresh:A(e.refresh),accountEmail:O(e.account)}),a=j(e.limit),o=a?i.rows.slice(0,a):i.rows;if(n){R({site:r,request:t,...i,rows:o,rowsReturned:o.length});return}K({title:`Search Console query`,target:r,status:i.rowsFetched>o.length?`unknown`:`info`,summary:`${o.length} rows returned from ${i.rowsFetched} fetched rows.`,metrics:[{label:`Rows returned`,value:o.length},{label:`Rows fetched`,value:i.rowsFetched},{label:`API calls`,value:i.calls}]}),B([`Keys`,`Clicks`,`Impr`,`CTR`,`Pos`],o.slice(0,25).map(e=>[e.keys.join(` | `),Math.round(e.clicks),Math.round(e.impressions),e.ctr.toFixed(3),e.position.toFixed(1)]))}}),bl=T({meta:{name:`url-inspect`,description:`Inspect a URL with the Search Console URL Inspection API`},args:{site:{type:`string`,description:`GSC property URL, for example sc-domain:example.com.`},client:{type:`string`,description:`Legacy alias for --project.`},project:{type:`string`,description:`Saved project id or name.`},account:{type:`string`,description:`Saved Google account email.`},url:{type:`string`,description:`URL to inspect.`},language:{type:`string`,description:`Optional inspection language code.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=F(e),n=await bs({client:k(e),site:O(e.site)},{json:t,account:O(e.account)}),r=O(e.url);if(!r)throw Error(`Pass --url.`);let i=await at({siteUrl:n,inspectionUrl:r,accountEmail:O(e.account),languageCode:O(e.language)});if(t){R(i);return}let a=i.inspectionResult?.indexStatusResult;K({title:`URL Inspection`,target:r,status:a===void 0?`unknown`:a.verdict===`PASS`?`pass`:`warning`,summary:a===void 0?`Google did not return index status evidence for this URL.`:`${a.verdict??`Unknown`}: ${a.coverageState??`Coverage state unavailable`}.`,metrics:[{label:`Property`,value:n},{label:`Robots`,value:a?.robotsTxtState??`Unknown`},{label:`Last crawl`,value:a?.lastCrawlTime??`Unknown`},{label:`Google canonical`,value:a?.googleCanonical??`Unknown`}]})}}),xl={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=xl[e];return[n.cli,{...n.arg,...t[e]}]}))}function Sl(e){return e.toLocaleString(`en-GB`,{maximumFractionDigits:e>=100?0:1})}function Cl(e){if(e===null)return`n/a`;let t=Math.abs(e)>=10?e.toFixed(0):e.toFixed(1);return`${e>0?`+`:``}${t}%`}const wl=T({meta:{name:`traffic-anomaly`,description:`Detect unusual recent GSC traffic movement`},args:{site:{type:`string`},project:{type:`string`,description:`Saved project id or name.`},client:{type:`string`,description:`Legacy alias for --project.`},...X([`days`,`recentDays`,`refresh`],{days:{description:`Baseline window length in days. Defaults to 90.`},recentDays:{description:`Recent anomaly window in days. Defaults to 7.`},refresh:{description:`Bypass local cache and fetch fresh GSC data.`}}),json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=F(e),n=await vt({site:await bs({client:k(e),site:O(e.site)},{json:t,refresh:A(e.refresh)}),days:j(e.days),recentDays:j(e.recent),refresh:A(e.refresh)});if(t){R(n);return}K({title:`Traffic anomaly report`,target:n.site,status:n.coverage?.status===`complete`?n.anomalies.some(e=>e.significant)?`warning`:`pass`:`unknown`,summary:n.anomalies.some(e=>e.significant)?`At least one recent metric moved beyond the report threshold.`:`No significant recent movement was found in the retained evidence.`,metrics:[{label:`Metrics checked`,value:n.anomalies.length},{label:`Significant`,value:n.anomalies.filter(e=>e.significant).length},{label:`Observed days`,value:n.coverage?.observedDays??`Unavailable`},{label:`Expected days`,value:n.coverage?.expectedDays??`Unavailable`}]}),q([`Metric`,`Direction`,`Baseline/day`,`Recent/day`,`Change`,`z`,`Significant`],n.anomalies.map(e=>[e.metric,e.direction,Sl(e.baselineMean),Sl(e.comparisonMean),Cl(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])}}),Tl=T({meta:{name:`update-correlate`,description:`Compare traffic movement with official Google update windows`},args:{site:{type:`string`},project:{type:`string`,description:`Saved project id or name.`},client:{type:`string`,description:`Legacy alias for --project.`},...X([`days`,`recentDays`,`refresh`],{days:{description:`Baseline window length in days. Defaults to 90.`},recentDays:{description:`Recent anomaly window in days. Defaults to 7.`},refresh:{description:`Bypass local cache and fetch fresh GSC data.`}}),"padding-days":{type:`string`,description:`Days around update windows to count as overlap.`},"known-change":{type:`string`,description:`Manual site-side change to treat as a confounder, for example pruning pages or blocking traffic.`},"ignore-change-log":{type:`boolean`,default:!1,description:`Do not use saved change-log entries as confounders.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=F(e),n=k(e),r=await bs({client:n,site:O(e.site)},{json:t,refresh:A(e.refresh)}),i=await ce({site:r,days:j(e.days),recentDays:j(e.recent),paddingDays:j(e[`padding-days`]),knownConfounders:O(e[`known-change`])?[O(e[`known-change`])??``]:void 0,includeChangeLog:!A(e[`ignore-change-log`]),refresh:A(e.refresh)});if(t){R(i);return}K({title:`Google update correlation`,target:i.site,status:`unknown`,summary:i.summary,metrics:[{label:`Classification`,value:i.classification.replaceAll(`-`,` `)},{label:`Attribution`,value:i.attribution.replaceAll(`-`,` `)},{label:`Confidence`,value:i.confidence},{label:`Updates matched`,value:i.overlappingUpdates.length},{label:`Known confounders`,value:i.confounders.length},{label:`Source`,value:i.source.name}]}),Y(`Evidence`,i.evidence),Y(`Report caveats`,i.caveats),Y(`Recommended next checks`,i.actions),q([`Metric`,`Direction`,`Baseline/day`,`Recent/day`,`Change`,`z`],i.anomalies.map(e=>[e.metric,e.direction,Sl(e.baselineMean),Sl(e.comparisonMean),Cl(e.percentChange),e.zScore??`n/a`])),i.overlappingUpdates.length&&(process.stdout.write(`
165
165
  Updates
166
- `),V([`Start`,`End`,`Type`,`Name`],i.overlappingUpdates.map(e=>[e.start.slice(0,10),e.end?.slice(0,10)??`open`,e.type,e.name]))),cs(`seo segment-impact ${n?`--project ${JSON.stringify(n)}`:`--site ${JSON.stringify(r)}`} --dimension page`)}});function Tl(e){Oo({title:`Entity evidence`,target:e.url,status:e.dataStatus===`partial`?`unknown`:`info`,summary:e.headline,metrics:[{label:`Pages`,value:`${e.evaluatedPages}/${e.crawlPages}`},{label:`sameAs`,value:e.entities.sameAs.length},{label:`Social`,value:e.entities.socialProfiles.length},{label:`Authors`,value:e.entities.authors.length}],sections:[{title:`Observed entity signals`,diagnostics:e.checks.map(e=>({status:e.status,title:e.title,explanation:e.plainEnglish,fix:e.action,evidence:(e.urls??[]).slice(0,5)}))}],notes:[`Data: ${e.dataStatus}. Report: ${e.reportId}.`,...e.caveats]})}const El=E({meta:{name:`entity-readiness`,description:`Check schema, sameAs, social, author, and naming signals from a saved crawl`},args:{"report-id":{type:`string`,description:`Saved crawl report id to analyze.`},site:{type:`string`,description:`GSC property URL for selecting the latest saved crawl.`},client:{type:`string`,description:`Legacy alias for --project.`},project:{type:`string`,description:`Saved project id or name.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=I(e),n=pe(await Bo(e,{json:t}));if(t){z(n);return}Tl(n)}});function Dl(){return new Date().toISOString().slice(0,10)}function Ol(e){let t=k(e)??`page`;if(t!==`site`&&t!==`page`&&t!==`query`&&t!==`group`)throw Error(`Invalid --scope. Use site, page, query, or group.`);return t}const kl=E({meta:{name:`change-log`,description:`Annotate SEO changes and measure before/after impact`},subCommands:{list:E({meta:{name:`list`,description:`List recorded SEO changes`},args:{site:{type:`string`},limit:{type:`string`},json:{type:`boolean`,default:!1}},run:async({args:e})=>{let t=Zn({site:k(e.site),limit:M(e.limit)});if(I(e)){z({changes:t});return}V([`ID`,`Date`,`Site`,`Scope`,`Target`,`Title`],t.map(e=>[e.id,e.changedAt,e.site,e.scope,e.target,e.title]))}}),add:E({meta:{name:`add`,description:`Record an SEO change`},args:{site:{type:`string`},scope:{type:`string`,default:`page`},target:{type:`string`,required:!0},title:{type:`string`,required:!0},description:{type:`string`},date:{type:`string`},json:{type:`boolean`,default:!1}},run:async({args:e})=>{let t=I(e),n=ye({site:await Ro({site:k(e.site),options:{json:t}}),scope:Ol(e.scope),target:k(e.target)??``,title:k(e.title)??``,description:k(e.description),changedAt:k(e.date)??Dl()});if(t){z(n);return}B([[`ID`,n.id],[`Date`,n.changedAt],[`Site`,n.site],[`Scope`,n.scope],[`Target`,n.target],[`Title`,n.title]])}}),delete:E({meta:{name:`delete`,description:`Delete a recorded SEO change`},args:{id:{type:`string`,required:!0},json:{type:`boolean`,default:!1}},run:async({args:e})=>{let t=k(e.id)??``,n=Ce(t);if(I(e)){z({id:t,deleted:n});return}process.stdout.write(`${n?`Deleted`:`Not found`} ${t}.\n`)}}),measure:E({meta:{name:`measure`,description:`Measure a change with equal finalized before/after windows`},args:{id:{type:`string`},site:{type:`string`},scope:{type:`string`},target:{type:`string`},title:{type:`string`},date:{type:`string`},before:{type:`string`},after:{type:`string`},json:{type:`boolean`,default:!1},refresh:{type:`boolean`,default:!1}},run:async({args:e})=>{let t=I(e),n=k(e.id);if(!n&&(!k(e.site)||!k(e.scope)||!k(e.target)||!k(e.date)))throw Error(`Pass --id, or pass --site, --scope, --target, and --date for an ad hoc measurement.`);let r=await Ir({id:n,site:n?void 0:await Ro({site:k(e.site),options:{json:t}}),scope:n?void 0:Ol(e.scope),target:n?void 0:k(e.target),title:k(e.title),changedAt:n?void 0:k(e.date),beforeDays:N(e.before,`--before`),afterDays:N(e.after,`--after`),refresh:j(e.refresh)});if(t){z(r);return}q({title:`Change measurement`,target:r.change.title,status:r.dataStatus===`complete`?`info`:`unknown`,summary:r.note,metrics:[{label:`Verdict`,value:r.verdict},{label:`Confidence`,value:r.confidence},{label:`Before`,value:`${r.before.startDate} to ${r.before.endDate}`},{label:`After`,value:`${r.after.startDate} to ${r.after.endDate}`},{label:`Window`,value:`${r.window.effectiveDays}/${r.window.requestedDays} finalized days`}]}),V([`Metric`,`Before`,`After`,`Delta`],[[`Clicks`,r.before.metrics?.clicks??`unavailable`,r.after.metrics?.clicks??`unavailable`,r.delta.clicks===null?`unavailable`:r.delta.clickPct===null?`${r.delta.clicks}`:`${r.delta.clicks} (${r.delta.clickPct}%)`],[`Impressions`,r.before.metrics?.impressions??`unavailable`,r.after.metrics?.impressions??`unavailable`,r.delta.impressions===null?`unavailable`:r.delta.impressionPct===null?`${r.delta.impressions}`:`${r.delta.impressions} (${r.delta.impressionPct}%)`],[`CTR`,r.before.metrics?.ctr??`unavailable`,r.after.metrics?.ctr??`unavailable`,r.delta.ctr??`unavailable`],[`Position`,r.before.metrics?.position??`unavailable`,r.after.metrics?.position??`unavailable`,r.delta.position??`unavailable`]]),X(`Warnings`,r.warnings),X(`Caveats`,r.caveats)}})}});function Al(e){let t=k(e)??`page`;if(t!==`page`&&t!==`query`)throw Error(`Invalid --dimension. Use page or query.`);return t}function jl(e){let t=k(e)??`contains`;if(t!==`equals`&&t!==`contains`&&t!==`regex`)throw Error(`Invalid --match. Use equals, contains, or regex.`);return t}const Ml=E({meta:{name:`content-groups`,description:`Create and manage reusable page/query groups`},subCommands:{list:E({meta:{name:`list`,description:`List saved content groups`},args:{site:{type:`string`},json:{type:`boolean`,default:!1}},run:async({args:e})=>{let t=de(k(e.site));if(I(e)){z({groups:t});return}V([`ID`,`Site`,`Name`,`Dimension`,`Match`,`Pattern`],t.map(e=>[e.id,e.site,e.name,e.dimension,e.matchType,e.pattern]))}}),add:E({meta:{name:`add`,description:`Create a reusable page or query group`},args:{site:{type:`string`},name:{type:`string`,required:!0},dimension:{type:`string`,default:`page`},match:{type:`string`,default:`contains`},pattern:{type:`string`,required:!0},json:{type:`boolean`,default:!1}},run:async({args:e})=>{let t=I(e),n=d({site:await Ro({site:k(e.site),options:{json:t}}),name:k(e.name)??``,dimension:Al(e.dimension),matchType:jl(e.match),pattern:k(e.pattern)??``});if(t){z(n);return}B([[`ID`,n.id],[`Site`,n.site],[`Name`,n.name],[`Filter`,`${n.dimension} ${n.matchType} ${n.pattern}`]])}}),delete:E({meta:{name:`delete`,description:`Delete a content group`},args:{id:{type:`string`,required:!0},json:{type:`boolean`,default:!1}},run:async({args:e})=>{let t=k(e.id)??``,n=pt(t);if(I(e)){z({id:t,deleted:n});return}process.stdout.write(`${n?`Deleted`:`Not found`} ${t}.\n`)}})}});function Nl(){return new Date().toISOString().slice(0,10)}function Pl(e){let t=k(e)??`page`;if(t!==`site`&&t!==`page`&&t!==`query`&&t!==`group`)throw Error(`Invalid --scope. Use site, page, query, or group.`);return t}function Fl(e){return e?Pl(e):void 0}function Il(e){return e===null?`n/a`:`${e.toFixed(1)}%`}function Ll(e){return e==null?`unavailable`:G(e)}function Rl(e,t){return e===null?`unavailable`:`${G(e)} (${Il(t)})`}function zl(e,t=1){return e==null?`unavailable`:e.toFixed(t)}function Bl(e){q({title:`SEO test report`,target:e.change.title,status:e.dataStatus===`complete`?`info`:`unknown`,summary:e.note,metrics:[{label:`Verdict`,value:e.verdict},{label:`Confidence`,value:e.confidence},{label:`Before`,value:`${e.before.startDate} to ${e.before.endDate}`},{label:`After`,value:`${e.after.startDate} to ${e.after.endDate}`},{label:`Window`,value:`${e.window.effectiveDays}/${e.window.requestedDays} finalized days`}]}),V([`Metric`,`Before`,`After`,`Delta`],[[`GSC clicks`,Ll(e.before.metrics?.clicks),Ll(e.after.metrics?.clicks),Rl(e.delta.clicks,e.delta.clickPct)],[`GSC impressions`,Ll(e.before.metrics?.impressions),Ll(e.after.metrics?.impressions),Rl(e.delta.impressions,e.delta.impressionPct)],[`GSC CTR`,e.before.metrics?`${(e.before.metrics.ctr*100).toFixed(1)}%`:`unavailable`,e.after.metrics?`${(e.after.metrics.ctr*100).toFixed(1)}%`:`unavailable`,e.delta.ctr===null?`unavailable`:`${(e.delta.ctr*100).toFixed(1)} pts`],[`GSC position`,zl(e.before.metrics?.position),zl(e.after.metrics?.position),zl(e.delta.position)]]),e.analytics&&(process.stdout.write(`
166
+ `),B([`Start`,`End`,`Type`,`Name`],i.overlappingUpdates.map(e=>[e.start.slice(0,10),e.end?.slice(0,10)??`open`,e.type,e.name]))),ls(`seo segment-impact ${n?`--project ${JSON.stringify(n)}`:`--site ${JSON.stringify(r)}`} --dimension page`)}});function El(e){ko({title:`Entity evidence`,target:e.url,status:e.dataStatus===`partial`?`unknown`:`info`,summary:e.headline,metrics:[{label:`Pages`,value:`${e.evaluatedPages}/${e.crawlPages}`},{label:`sameAs`,value:e.entities.sameAs.length},{label:`Social`,value:e.entities.socialProfiles.length},{label:`Authors`,value:e.entities.authors.length}],sections:[{title:`Observed entity signals`,diagnostics:e.checks.map(e=>({status:e.status,title:e.title,explanation:e.plainEnglish,fix:e.action,evidence:(e.urls??[]).slice(0,5)}))}],notes:[`Data: ${e.dataStatus}. Report: ${e.reportId}.`,...e.caveats]})}const Dl=T({meta:{name:`entity-readiness`,description:`Check schema, sameAs, social, author, and naming signals from a saved crawl`},args:{"report-id":{type:`string`,description:`Saved crawl report id to analyze.`},site:{type:`string`,description:`GSC property URL for selecting the latest saved crawl.`},client:{type:`string`,description:`Legacy alias for --project.`},project:{type:`string`,description:`Saved project id or name.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=F(e),n=me(await Vo(e,{json:t}));if(t){R(n);return}El(n)}});function Ol(){return new Date().toISOString().slice(0,10)}function kl(e){let t=O(e)??`page`;if(t!==`site`&&t!==`page`&&t!==`query`&&t!==`group`)throw Error(`Invalid --scope. Use site, page, query, or group.`);return t}const Al=T({meta:{name:`change-log`,description:`Annotate SEO changes and measure before/after impact`},subCommands:{list:T({meta:{name:`list`,description:`List recorded SEO changes`},args:{site:{type:`string`},limit:{type:`string`},json:{type:`boolean`,default:!1}},run:async({args:e})=>{let t=Qn({site:O(e.site),limit:j(e.limit)});if(F(e)){R({changes:t});return}B([`ID`,`Date`,`Site`,`Scope`,`Target`,`Title`],t.map(e=>[e.id,e.changedAt,e.site,e.scope,e.target,e.title]))}}),add:T({meta:{name:`add`,description:`Record an SEO change`},args:{site:{type:`string`},scope:{type:`string`,default:`page`},target:{type:`string`,required:!0},title:{type:`string`,required:!0},description:{type:`string`},date:{type:`string`},json:{type:`boolean`,default:!1}},run:async({args:e})=>{let t=F(e),n=be({site:await zo({site:O(e.site),options:{json:t}}),scope:kl(e.scope),target:O(e.target)??``,title:O(e.title)??``,description:O(e.description),changedAt:O(e.date)??Ol()});if(t){R(n);return}z([[`ID`,n.id],[`Date`,n.changedAt],[`Site`,n.site],[`Scope`,n.scope],[`Target`,n.target],[`Title`,n.title]])}}),delete:T({meta:{name:`delete`,description:`Delete a recorded SEO change`},args:{id:{type:`string`,required:!0},json:{type:`boolean`,default:!1}},run:async({args:e})=>{let t=O(e.id)??``,n=we(t);if(F(e)){R({id:t,deleted:n});return}process.stdout.write(`${n?`Deleted`:`Not found`} ${t}.\n`)}}),measure:T({meta:{name:`measure`,description:`Measure a change with equal finalized before/after windows`},args:{id:{type:`string`},site:{type:`string`},scope:{type:`string`},target:{type:`string`},title:{type:`string`},date:{type:`string`},before:{type:`string`},after:{type:`string`},json:{type:`boolean`,default:!1},refresh:{type:`boolean`,default:!1}},run:async({args:e})=>{let t=F(e),n=O(e.id);if(!n&&(!O(e.site)||!O(e.scope)||!O(e.target)||!O(e.date)))throw Error(`Pass --id, or pass --site, --scope, --target, and --date for an ad hoc measurement.`);let r=await Lr({id:n,site:n?void 0:await zo({site:O(e.site),options:{json:t}}),scope:n?void 0:kl(e.scope),target:n?void 0:O(e.target),title:O(e.title),changedAt:n?void 0:O(e.date),beforeDays:M(e.before,`--before`),afterDays:M(e.after,`--after`),refresh:A(e.refresh)});if(t){R(r);return}K({title:`Change measurement`,target:r.change.title,status:r.dataStatus===`complete`?`info`:`unknown`,summary:r.note,metrics:[{label:`Verdict`,value:r.verdict},{label:`Confidence`,value:r.confidence},{label:`Before`,value:`${r.before.startDate} to ${r.before.endDate}`},{label:`After`,value:`${r.after.startDate} to ${r.after.endDate}`},{label:`Window`,value:`${r.window.effectiveDays}/${r.window.requestedDays} finalized days`}]}),B([`Metric`,`Before`,`After`,`Delta`],[[`Clicks`,r.before.metrics?.clicks??`unavailable`,r.after.metrics?.clicks??`unavailable`,r.delta.clicks===null?`unavailable`:r.delta.clickPct===null?`${r.delta.clicks}`:`${r.delta.clicks} (${r.delta.clickPct}%)`],[`Impressions`,r.before.metrics?.impressions??`unavailable`,r.after.metrics?.impressions??`unavailable`,r.delta.impressions===null?`unavailable`:r.delta.impressionPct===null?`${r.delta.impressions}`:`${r.delta.impressions} (${r.delta.impressionPct}%)`],[`CTR`,r.before.metrics?.ctr??`unavailable`,r.after.metrics?.ctr??`unavailable`,r.delta.ctr??`unavailable`],[`Position`,r.before.metrics?.position??`unavailable`,r.after.metrics?.position??`unavailable`,r.delta.position??`unavailable`]]),Y(`Warnings`,r.warnings),Y(`Caveats`,r.caveats)}})}});function jl(e){let t=O(e)??`page`;if(t!==`page`&&t!==`query`)throw Error(`Invalid --dimension. Use page or query.`);return t}function Ml(e){let t=O(e)??`contains`;if(t!==`equals`&&t!==`contains`&&t!==`regex`)throw Error(`Invalid --match. Use equals, contains, or regex.`);return t}const Nl=T({meta:{name:`content-groups`,description:`Create and manage reusable page/query groups`},subCommands:{list:T({meta:{name:`list`,description:`List saved content groups`},args:{site:{type:`string`},json:{type:`boolean`,default:!1}},run:async({args:e})=>{let t=fe(O(e.site));if(F(e)){R({groups:t});return}B([`ID`,`Site`,`Name`,`Dimension`,`Match`,`Pattern`],t.map(e=>[e.id,e.site,e.name,e.dimension,e.matchType,e.pattern]))}}),add:T({meta:{name:`add`,description:`Create a reusable page or query group`},args:{site:{type:`string`},name:{type:`string`,required:!0},dimension:{type:`string`,default:`page`},match:{type:`string`,default:`contains`},pattern:{type:`string`,required:!0},json:{type:`boolean`,default:!1}},run:async({args:e})=>{let t=F(e),n=d({site:await zo({site:O(e.site),options:{json:t}}),name:O(e.name)??``,dimension:jl(e.dimension),matchType:Ml(e.match),pattern:O(e.pattern)??``});if(t){R(n);return}z([[`ID`,n.id],[`Site`,n.site],[`Name`,n.name],[`Filter`,`${n.dimension} ${n.matchType} ${n.pattern}`]])}}),delete:T({meta:{name:`delete`,description:`Delete a content group`},args:{id:{type:`string`,required:!0},json:{type:`boolean`,default:!1}},run:async({args:e})=>{let t=O(e.id)??``,n=mt(t);if(F(e)){R({id:t,deleted:n});return}process.stdout.write(`${n?`Deleted`:`Not found`} ${t}.\n`)}})}});function Pl(){return new Date().toISOString().slice(0,10)}function Fl(e){let t=O(e)??`page`;if(t!==`site`&&t!==`page`&&t!==`query`&&t!==`group`)throw Error(`Invalid --scope. Use site, page, query, or group.`);return t}function Il(e){return e?Fl(e):void 0}function Ll(e){return e===null?`n/a`:`${e.toFixed(1)}%`}function Rl(e){return e==null?`unavailable`:W(e)}function zl(e,t){return e===null?`unavailable`:`${W(e)} (${Ll(t)})`}function Bl(e,t=1){return e==null?`unavailable`:e.toFixed(t)}function Vl(e){K({title:`SEO test report`,target:e.change.title,status:e.dataStatus===`complete`?`info`:`unknown`,summary:e.note,metrics:[{label:`Verdict`,value:e.verdict},{label:`Confidence`,value:e.confidence},{label:`Before`,value:`${e.before.startDate} to ${e.before.endDate}`},{label:`After`,value:`${e.after.startDate} to ${e.after.endDate}`},{label:`Window`,value:`${e.window.effectiveDays}/${e.window.requestedDays} finalized days`}]}),B([`Metric`,`Before`,`After`,`Delta`],[[`GSC clicks`,Rl(e.before.metrics?.clicks),Rl(e.after.metrics?.clicks),zl(e.delta.clicks,e.delta.clickPct)],[`GSC impressions`,Rl(e.before.metrics?.impressions),Rl(e.after.metrics?.impressions),zl(e.delta.impressions,e.delta.impressionPct)],[`GSC CTR`,e.before.metrics?`${(e.before.metrics.ctr*100).toFixed(1)}%`:`unavailable`,e.after.metrics?`${(e.after.metrics.ctr*100).toFixed(1)}%`:`unavailable`,e.delta.ctr===null?`unavailable`:`${(e.delta.ctr*100).toFixed(1)} pts`],[`GSC position`,Bl(e.before.metrics?.position),Bl(e.after.metrics?.position),Bl(e.delta.position)]]),e.analytics&&(process.stdout.write(`
167
167
  Google Analytics impact
168
- `),V([`Metric`,`Before`,`After`,`Delta`],[[`Sessions`,G(e.analytics.before.metrics.sessions),G(e.analytics.after.metrics.sessions),`${G(e.analytics.delta.sessions)} (${Il(e.analytics.delta.sessionPct)})`],[`Conversions`,G(e.analytics.before.metrics.conversions),G(e.analytics.after.metrics.conversions),`${G(e.analytics.delta.conversions)} (${Il(e.analytics.delta.conversionPct)})`],[`Revenue`,e.analytics.before.metrics.totalRevenue.toFixed(2),e.analytics.after.metrics.totalRevenue.toFixed(2),`${e.analytics.delta.totalRevenue.toFixed(2)} (${Il(e.analytics.delta.revenuePct)})`]])),e.control&&(process.stdout.write(`
168
+ `),B([`Metric`,`Before`,`After`,`Delta`],[[`Sessions`,W(e.analytics.before.metrics.sessions),W(e.analytics.after.metrics.sessions),`${W(e.analytics.delta.sessions)} (${Ll(e.analytics.delta.sessionPct)})`],[`Conversions`,W(e.analytics.before.metrics.conversions),W(e.analytics.after.metrics.conversions),`${W(e.analytics.delta.conversions)} (${Ll(e.analytics.delta.conversionPct)})`],[`Revenue`,e.analytics.before.metrics.totalRevenue.toFixed(2),e.analytics.after.metrics.totalRevenue.toFixed(2),`${e.analytics.delta.totalRevenue.toFixed(2)} (${Ll(e.analytics.delta.revenuePct)})`]])),e.control&&(process.stdout.write(`
169
169
  Control comparison
170
- `),B([[`Control`,e.control.change.title],[`Adjusted clicks`,e.control.adjusted.clickDelta===null?`n/a`:G(e.control.adjusted.clickDelta)],[`Adjusted click %`,e.control.adjusted.clickPctPoints===null?`n/a`:`${e.control.adjusted.clickPctPoints.toFixed(1)} pts`],[`Note`,e.control.note]])),X(`Warnings`,e.warnings),X(`Caveats`,e.caveats)}const Vl=E({meta:{name:`tests`,description:`Create and report local SEO tests from Search Console and Google Analytics data`},subCommands:{list:E({meta:{name:`list`,description:`List saved SEO tests`},args:{site:{type:`string`,description:`GSC property URL.`},client:{type:`string`,description:`Legacy alias for --project.`},project:{type:`string`,description:`Saved project id or name.`},limit:{type:`string`,description:`Maximum tests to list. Defaults to 20.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=I(e),n=A(e),r=Zn({site:(n||k(e.site)?await U({client:n,site:k(e.site),options:{json:t}}):void 0)?.site,limit:M(e.limit)});if(t){z({tests:r});return}V([`ID`,`Date`,`Scope`,`Target`,`Title`],r.map(e=>[e.id,e.changedAt,e.scope,e.target,e.title]))}}),create:E({meta:{name:`create`,description:`Create a local SEO test`},args:{site:{type:`string`,description:`GSC property URL.`},client:{type:`string`,description:`Legacy alias for --project.`},project:{type:`string`,description:`Saved project id or name.`},scope:{type:`string`,default:`page`,description:`Change scope: site, page, query, or group.`},target:{type:`string`,required:!0,description:`Changed URL, query, group pattern, or site marker.`},title:{type:`string`,required:!0,description:`Plain-English name for the test.`},description:{type:`string`,description:`Optional note about what changed.`},date:{type:`string`,description:`Change date in YYYY-MM-DD. Defaults to today.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=I(e),n=await U({client:A(e),site:k(e.site),options:{json:t}}),r=ye({site:n.site,scope:Pl(e.scope),target:k(e.target)??``,title:k(e.title)??``,description:k(e.description),changedAt:k(e.date)??Nl()});if(t){z(r);return}B([[`ID`,r.id],[`Date`,r.changedAt],[`Project`,n.client?.name??n.site],[`Scope`,r.scope],[`Target`,r.target],[`Title`,r.title]])}}),report:E({meta:{name:`report`,description:`Report an SEO test with equal finalized before/after windows`},args:{id:{type:`string`,description:`Saved SEO test id.`},site:{type:`string`,description:`GSC property URL.`},client:{type:`string`,description:`Legacy alias for --project.`},project:{type:`string`,description:`Saved project id or name.`},scope:{type:`string`,description:`Ad-hoc change scope: site, page, query, or group.`},target:{type:`string`,description:`Ad-hoc changed URL, query, group pattern, or site marker.`},title:{type:`string`,description:`Ad-hoc test title.`},date:{type:`string`,description:`Ad-hoc change date in YYYY-MM-DD.`},property:{type:`string`,description:`Google Analytics property ID.`},"control-scope":{type:`string`,description:`Control scope: site, page, query, or group.`},"control-target":{type:`string`,description:`Control URL, query, group pattern, or site marker.`},"control-title":{type:`string`,description:`Plain-English name for the control group.`},before:{type:`string`,description:`Days before the change date. Defaults to 28.`},after:{type:`string`,description:`Days after the change date. Defaults to 28.`},refresh:{type:`boolean`,default:!1,description:`Bypass the local Search Console and Google Analytics cache.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=I(e),n=k(e.id),r=!n||A(e)||k(e.site)?await U({client:A(e),site:k(e.site),options:{json:t,refresh:j(e.refresh)}}):void 0,i=await Ir({id:n,site:n?void 0:r?.site,scope:n?void 0:Fl(e.scope),target:n?void 0:k(e.target),title:k(e.title),changedAt:n?void 0:k(e.date),googleAnalyticsPropertyId:k(e.property)??r?.client?.analytics.google?.propertyId,controlScope:Fl(e[`control-scope`]),controlTarget:k(e[`control-target`]),controlTitle:k(e[`control-title`]),beforeDays:N(e.before,`--before`),afterDays:N(e.after,`--after`),refresh:j(e.refresh)});if(t){z(i);return}Bl(i)}})}});function Hl(e){if(!e)return;let t=Date.now();return e=>{let n=((Date.now()-t)/1e3).toFixed(1);process.stderr.write(`[seo ${n}s] ${e}\n`)}}const Ul={site:{type:`string`,description:`GSC property URL, for example sc-domain:example.com.`},client:{type:`string`,description:`Legacy alias for --project.`},project:{type:`string`,description:`Saved project id or name.`}},Wl={...Z([`includeBrand`,`verifyContent`,`verifyLimit`,`js`,`fetchConcurrency`,`fetchIntervalCap`,`fetchIntervalMs`,`refresh`]),json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}};function Gl(e){return{includeBrand:j(e[`include-brand`]),verifyContent:j(e[`verify-content`]),verifyLimit:M(e[`verify-limit`])??3,js:j(e.js)?!0:void 0,rate:za(e),refresh:j(e.refresh)}}function Kl(e){return{client:A(e),site:k(e.site),refresh:j(e.refresh)}}const{json:ql,...Jl}=Wl,Yl=Jl,Xl={client:{type:`string`,description:`Legacy alias for --project.`},project:{type:`string`,description:`Saved project id or name. Defaults to the configured default.`},site:{type:`string`,description:`GSC property URL/id when not using a saved project.`}};function Zl(e){return{type:`string`,description:`Output folder. Defaults to ./seo-export/<project>-${e}-YYYY-MM-DD.`}}function Ql(e){return e.toLowerCase().replace(/^sc-domain:/,``).replace(/^https?:\/\//,``).replace(/[^a-z0-9._-]+/g,`-`).replace(/^-+|-+$/g,``)}function $l(e){let t=new Date().toISOString().slice(0,10),n=Ql(e.clientId??e.site);return ni(process.cwd(),`seo-export`,`${n}-${e.report}-${t}`)}async function eu(e,t){await ci(e,{recursive:!0});let n=[];for(let r of t){let t=ni(e,r.filename);await gi(t,p(r.rows,r.headers),`utf8`),n.push(t)}return n}function tu(e,t){process.stdout.write(`Wrote ${Dn(t.length,`CSV file`)} to ${e}\n`);for(let e of t)process.stdout.write(`- ${e}\n`)}const nu=E({meta:{name:`diagnose`,description:`Export diagnosis tables: priorities, anomalies, movement, decay, cannibalisation, striking-distance, and quick wins.`},args:{...Xl,out:Zl(`diagnose`),days:{type:`string`,description:`GSC lookback window. Defaults to 90.`},recent:{type:`string`,description:`Recent anomaly window in days.`},limit:{type:`string`,description:`Maximum rows per movement/opportunity table.`},"include-brand":{type:`boolean`,default:!1,description:`Include branded queries in opportunity reports.`},"verify-content":{type:`boolean`,default:!1,description:`Verify top opportunities against page title, meta, and content.`},"verify-limit":{type:`string`,description:`Maximum opportunity URLs to verify.`},js:{type:`boolean`,default:!1,description:`Force JavaScript rendering for verified pages.`},"fetch-concurrency":{type:`string`,description:`Maximum concurrent page fetches per host. Defaults to 4.`},"fetch-interval-cap":{type:`string`,description:`Maximum page fetches per interval per host. Defaults to 4.`},"fetch-interval-ms":{type:`string`,description:`Fetch rate interval in milliseconds. Defaults to 1000.`},refresh:{type:`boolean`,default:!1,description:`Bypass local cache and fetch fresh data.`}},run:async({args:e})=>{let t=await U({client:A(e),site:k(e.site)}),n=await lt({site:t.site,days:M(e.days),recentDays:M(e.recent),limit:M(e.limit),brandTerms:t.client?.brandTerms,includeBrand:j(e[`include-brand`]),verifyContent:j(e[`verify-content`]),verifyLimit:M(e[`verify-limit`]),js:j(e.js)?!0:`auto`,rate:za(e),refresh:j(e.refresh),progress:Hl(!0)}),r=k(e.out)??$l({report:`diagnose`,clientId:t.client?.id,site:t.site});tu(r,await eu(r,Ar(n)))}}),ru=`.seo-okf.json`,iu=`${JSON.stringify({generator:`seo`,format:`okf`,version:1})}\n`;function au(e,t){let n=ni(e,t),r=ti(ni(e),n);if(!r||r.startsWith(`..`)||$r(r))throw new w(`INVALID_INPUT`,`Unsafe OKF file path: ${t}`);return n}async function ou(e,t){for(let n of t){let t=au(e,n.path);await ci(Zr(t),{recursive:!0}),await gi(t,n.content)}await gi(T(e,ru),iu)}async function su(e){let t=[],n=0;async function r(e,i=``){let a=await di(e,{withFileTypes:!0});for(let o of a){let a=i?`${i}/${o.name}`:o.name,s=T(e,o.name);if(o.isDirectory())await r(s,a);else if(o.isFile()&&o.name.endsWith(`.md`)){if(t.length>=5006)throw new w(`INVALID_INPUT`,`OKF validation accepts at most ${hn} Markdown files.`);let e=await si(s);if(e.size>2e6)throw new w(`INVALID_INPUT`,`OKF file exceeds ${It} bytes: ${a}`);if(n+=e.size,n>67108864)throw new w(`INVALID_INPUT`,`OKF Markdown files exceed ${Vn} bytes in total.`);t.push({path:a,content:await ui(s,`utf8`)})}}}return await r(e),t.sort((e,t)=>e.path<t.path?-1:+(e.path>t.path))}async function cu(e){try{let t=await si(e);if(t.isSymbolicLink()||!t.isDirectory())throw new w(`INVALID_INPUT`,`OKF output must be a real directory: ${e}`);if(!(await di(e)).length)return!0;try{if(await ui(T(e,ru),`utf8`)===iu)return!0}catch{}throw new w(`INVALID_INPUT`,`Refusing to replace non-empty unmanaged directory: ${e}`)}catch(e){if(e.code===`ENOENT`)return!1;throw e}}async function lu(e,t){if(!Ut(t,{profile:`seo-export`}).valid)throw new w(`INVALID_INPUT`,`Generated bundle did not pass the SEO export profile and was not written.`);let n=ni(e),r=Zr(n);await ci(r,{recursive:!0});let i=await cu(n),a=await li(T(r,`.${Xr(n)}-staging-`)),o=T(r,`.${Xr(n)}-backup-${Rr()}`),s=!1;try{await ou(a,t);let e=Ut(await su(a),{profile:`seo-export`});if(!e.valid)throw new w(`INTERNAL_ERROR`,`Staged OKF files failed validation after writing.`);return i&&(await fi(n,o),s=!0),await fi(a,n),s&&await pi(o,{recursive:!0,force:!0}),e}catch(e){throw await pi(a,{recursive:!0,force:!0}),s&&(await pi(n,{recursive:!0,force:!0}),await fi(o,n)),e}}async function uu(e,t){if(!e){process.stdout.write(t);return}await ci(Zr(e),{recursive:!0}),await gi(e,t),process.stdout.write(`Wrote ${e}\n`)}function du(e){let t=k(e)??`okf`;if([`okf`,`markdown`,`json`].includes(t))return t;throw Error(`Format must be one of: okf, markdown, json.`)}function fu(e){return{reportId:e.id,url:e.config.url,generatedAt:e.generatedAt,crawl:{summary:e.summary,caveats:e.caveats,warnings:e.warnings,pages:e.pages.map(e=>({url:e.finalUrl,title:e.title,metaDescription:e.metaDescription,status:e.status,indexable:e.indexable,wordCount:e.wordCount,schemaTypes:e.schemaTypes,sameAs:e.schemaSameAs,internalInlinks:e.internalInlinkCount,contentSample:e.contentSample}))},aiReadiness:re(e),entityReadiness:pe(e),llmsTxt:ke(e)}}function pu(e){let t=fu(e);return`${[`# Site Knowledge Export`,``,`Source: ${e.config.url}`,`Crawl report: ${e.id}`,`Generated: ${t.generatedAt}`,``,`## Summary`,``,`- Documents: ${e.summary.totalPages}${e.requestEvidenceStatus===`available`?` from ${e.requests.length} URL requests`:e.requestEvidenceStatus===`partial`?` from ${e.requests.length} observed URL requests (request evidence is partial)`:` (request evidence unavailable for this legacy report)`}`,`- Indexable pages: ${e.summary.indexablePages}`,`- Crawl findings: ${e.issues.length} (${e.summary.highIssues} high, ${e.summary.mediumIssues} medium, ${e.summary.lowIssues} low)`,`- AI-search evidence: ${t.aiReadiness.dataStatus} (${t.aiReadiness.assessment})`,`- Entity evidence: ${t.entityReadiness.dataStatus} (${t.entityReadiness.assessment})`,``,`## Top AI Readiness Actions`,``,...t.aiReadiness.topActions.length?t.aiReadiness.topActions.map(e=>`- ${e.title}: ${e.action}`):[`- No AI-search technical actions in this crawl.`],``,`## Entity Signals`,``,`- sameAs profiles: ${t.entityReadiness.entities.sameAs.length}`,`- Social profiles: ${t.entityReadiness.entities.socialProfiles.length}`,`- Authors: ${t.entityReadiness.entities.authors.length}`,``,`## Important Pages`,``,...e.pages.filter(e=>e.indexable&&e.status<400).slice(0,100).map(e=>`- [${e.title??e.finalUrl}](${e.finalUrl}) - ${e.metaDescription??e.contentSample??`No summary available.`}`),``,`# Citations`,``,`- [Crawl start URL](${e.config.url})`,``].join(`
171
- `)}\n`}const mu=E({meta:{name:`knowledge`,description:`Export site knowledge from a saved crawl as OKF, Markdown, or JSON`},args:{"report-id":{type:`string`,description:`Saved crawl report id to export.`},site:{type:`string`,description:`GSC property URL for selecting the latest saved crawl.`},client:{type:`string`,description:`Legacy alias for --project.`},project:{type:`string`,description:`Saved project id or name.`},format:{type:`string`,description:`Output format: okf, markdown, or json. Defaults to okf.`},output:{type:`string`,description:`Output directory for OKF, or output file for Markdown/JSON.`},"max-concepts":{type:`string`,description:`Maximum OKF page concepts. Defaults to 500.`},json:{type:`boolean`,default:!1,description:`Print command metadata as JSON.`}},run:async({args:e})=>{let t=I(e),n=du(e.format),r=n===`okf`?$n(N(e[`max-concepts`],`--max-concepts`)):void 0,i=await Bo(e,{json:t}),a=k(e.output);if(n===`okf`){let e=In(i,{maxConcepts:r}),o=a??`./okf`,s=await lu(o,e.files);if(t){z({format:n,output:o,bundle:e,validation:s});return}process.stdout.write(`Wrote OKF bundle to ${o}\n\n`),B([[`Files`,String(e.files.length)],[`Concepts`,String(e.conceptCount)],[`Page concepts`,String(e.pageConceptCount)],[`Valid`,s.valid?`yes`:`no`]]);return}if(n===`json`){await uu(a,`${JSON.stringify(fu(i),null,2)}\n`);return}await uu(a,pu(i))}}),hu=E({meta:{name:`monthly`,description:`Export monthly report tables plus the underlying diagnosis detail CSVs.`},args:{...Xl,out:Zl(`monthly`),month:{type:`string`,description:`Report month as YYYY-MM. Defaults to the latest month with final GSC data.`},...Z([`limit`],{limit:{description:`Maximum rows per diagnostic section. Defaults to 10.`}}),...Yl},run:async({args:e})=>{let t=await U({client:A(e),site:k(e.site),options:{refresh:j(e.refresh)}}),n=await Ht({site:t.site,month:k(e.month),limit:M(e.limit),brandTerms:t.client?.brandTerms,...Gl(e),progress:Hl(!0)}),r=k(e.out)??$l({report:`monthly`,clientId:t.client?.id,site:t.site});tu(r,await eu(r,Bt(n)))}}),gu=E({meta:{name:`narrative`,description:`Export report narrative tables plus the underlying diagnosis detail CSVs.`},args:{...Xl,out:Zl(`narrative`),...Z([`days`,`recentDays`],{days:{description:`Diagnosis window length in days. Defaults to 90.`}}),"start-date":{type:`string`,description:`Optional report start date as YYYY-MM-DD.`},"end-date":{type:`string`,description:`Optional report end date as YYYY-MM-DD.`},...Z([`limit`],{limit:{description:`Maximum rows per diagnostic section. Defaults to 10.`}}),"change-limit":{type:`string`,description:`Maximum saved changes to measure. Defaults to 5.`},...Yl},run:async({args:e})=>{let t=await U({client:A(e),site:k(e.site),options:{refresh:j(e.refresh)}}),n=await Or({site:t.site,days:M(e.days),recentDays:M(e.recent),startDate:k(e[`start-date`]),endDate:k(e[`end-date`]),limit:M(e.limit),changeLimit:M(e[`change-limit`]),brandTerms:t.client?.brandTerms,...Gl(e),progress:Hl(!0)}),r=k(e.out)??$l({report:`narrative`,clientId:t.client?.id,site:t.site});tu(r,await eu(r,Bt(n)))}});function _u(e){return y({days:N(e.days,`--days`),sitemaps:P(e.sitemap),maxSitemapUrls:N(e[`max-sitemap-urls`],`--max-sitemap-urls`),templateLimit:N(e.limit,`--limit`),minimumTemplateUrls:N(e[`minimum-template-urls`],`--minimum-template-urls`),minimumTemplateShare:N(e[`minimum-template-share`],`--minimum-template-share`),minimumTemplateImpressions:N(e[`minimum-template-impressions`],`--minimum-template-impressions`),crawlSamples:N(e[`crawl-samples`],`--crawl-samples`),inspectSamples:N(e[`inspect-samples`],`--inspect-samples`),brandTerms:P(e[`brand-terms`]),includeBrand:j(e[`include-brand`]),js:j(e.js)?!0:`auto`,refresh:j(e.refresh),rate:Ba(e)})}const vu=E({meta:{name:`pseo`,description:`Export pSEO audit tables: templates, demand patterns, top queries, sample coverage, crawl samples, and inspection samples.`},args:{...Xl,out:Zl(`pseo`),days:{type:`string`,description:`GSC lookback window. Defaults to 28.`},limit:{type:`string`,description:`Maximum pSEO templates to export.`},sitemap:{type:`string`,description:`Comma-separated sitemap URLs. Can be passed once.`},"crawl-samples":{type:`string`,description:`Number of representative URLs to fetch per template. Defaults to 0.`},"inspect-samples":{type:`string`,description:`Number of representative URLs to inspect in GSC per template. Defaults to 0.`},"max-sitemap-urls":{type:`string`,description:`Maximum discovered URLs per sitemap. Defaults to 50000.`},"minimum-template-urls":{type:`string`,description:`Minimum repeated URLs required for a template. Defaults to 3.`},"minimum-template-share":{type:`string`,description:`Minimum share of discovered URLs from 0 to 1. Defaults to 0.`},"minimum-template-impressions":{type:`string`,description:`Minimum retained page impressions for a template. Defaults to 0.`},"brand-terms":{type:`string`,description:`Comma-separated brand terms to exclude from query evidence.`},"include-brand":{type:`boolean`,default:!1,description:`Include branded queries in pSEO demand analysis.`},js:{type:`boolean`,default:!1,description:`Force JavaScript rendering for crawled samples.`},"fetch-concurrency":{type:`string`,description:`Maximum concurrent page fetches per host. Defaults to 4.`},"fetch-interval-cap":{type:`string`,description:`Maximum page fetches per interval per host. Defaults to 4.`},"fetch-interval-ms":{type:`string`,description:`Fetch rate interval in milliseconds. Defaults to 1000.`},refresh:{type:`boolean`,default:!1,description:`Bypass local cache and fetch fresh data.`}},run:async({args:e})=>{let t=_u(e),n=await U({client:A(e),site:k(e.site)}),r=await ze({site:n.site,...t,brandTerms:t.brandTerms??n.client?.brandTerms,progress:Hl(!0)}),i=k(e.out)??$l({report:`pseo`,clientId:n.client?.id,site:n.site});tu(i,await eu(i,Er(r)))}}),yu=E({meta:{name:`refresh-priorities`,description:`Export the ranked SEO priority queue, score breakdowns, grouped findings, and diagnosis detail CSVs.`},args:{...Xl,out:Zl(`refresh-priorities`),...Z([`days`,`recentDays`,`limit`,`includeBrand`,`refresh`],{days:{description:`Diagnosis window length in days. Defaults to 90.`},limit:{description:`Maximum queue items to export. Defaults to 25.`}}),...$c,"verify-content":Pa(`Verify top opportunities against page title, meta, and content. Defaults to true.`,`Skip page content verification.`),"verify-limit":{type:`string`,description:`Maximum opportunity URLs to verify. Defaults to 5.`}},run:async({args:e})=>{let t=await U({client:A(e),site:k(e.site),options:{refresh:j(e.refresh)}}),n=await rn({site:t.site,days:M(e.days),recentDays:M(e.recent),limit:M(e.limit),brandTerms:t.client?.brandTerms,includeBrand:j(e[`include-brand`]),googleAnalyticsPropertyId:k(e[`google-analytics-property`])??t.client?.analytics.google?.propertyId,analyticsConnection:el(e,Jn(t.client)),verifyContent:j(e[`verify-content`]),verifyLimit:M(e[`verify-limit`]),refresh:j(e.refresh)}),r=k(e.out)??$l({report:`refresh-priorities`,clientId:t.client?.id,site:t.site});tu(r,await eu(r,$e(n)))}}),bu=E({meta:{name:`export`,description:`Export full report data to CSV files`},subCommands:{diagnose:nu,knowledge:mu,narrative:gu,monthly:hu,"update-postmortem":E({meta:{name:`update-postmortem`,description:`Export update postmortem attribution, findings, template movement, and segment movers.`},args:{...Xl,out:Zl(`update-postmortem`),...Z([`days`,`recentDays`,`limit`,`includeBrand`,`refresh`],{days:{description:`Diagnosis window length in days. Defaults to 90.`},limit:{description:`Maximum winners/losers per segment. Defaults to 20.`}}),"known-change":{type:`string`,description:`Manual site-side change to treat as a confounder, for example pruning pages or blocking traffic.`},"ignore-change-log":{type:`boolean`,default:!1,description:`Do not use saved change-log entries as confounders.`}},run:async({args:e})=>{let t=await U({client:A(e),site:k(e.site),options:{refresh:j(e.refresh)}}),n=await gr({site:t.site,days:M(e.days),recentDays:M(e.recent),limit:M(e.limit),brandTerms:t.client?.brandTerms,includeBrand:j(e[`include-brand`]),knownConfounders:k(e[`known-change`])?[k(e[`known-change`])??``]:void 0,includeChangeLog:!j(e[`ignore-change-log`]),refresh:j(e.refresh)}),r=k(e.out)??$l({report:`update-postmortem`,clientId:t.client?.id,site:t.site});tu(r,await eu(r,ea(n)))}}),"refresh-priorities":yu,pseo:vu}}),xu={project:{type:`string`,description:`Saved project id or name.`},client:{type:`string`,description:`Legacy alias for --project.`},site:{type:`string`,description:`Site URL.`}};async function Su(e){let t=k(e.site);if(t)return t;let n=await W({project:A(e),options:{json:I(e)}});if(!n)throw new w(`INVALID_INPUT`,`Pass --site or select a saved project.`);return n.siteUrl}async function Cu(e){let t=k(e.url),n=P(e.urls)??[],r=k(e.file);if(!t&&n.length===0&&!r)throw new w(`INVALID_INPUT`,`Pass --url, comma-separated --urls, or a newline-delimited --file.`);let i=[...t?[t]:[],...n];if(r){let e=await mi(r).catch(()=>void 0);if(!e)throw new w(`INVALID_INPUT`,`IndexNow URL file not found: ${r}`);if(!e.isFile()||e.size>21e5)throw new w(`INVALID_INPUT`,`IndexNow URL files must be regular files no larger than 2.1 MB.`);i.push(...(await ui(r,`utf8`)).split(/\r?\n/).map(e=>e.trim()).filter(Boolean))}return i}const wu=E({meta:{name:`indexnow`,description:`Set up and submit changed URLs with IndexNow`},subCommands:{setup:E({meta:{name:`setup`,description:`Generate an IndexNow key file for one site`},args:{...xu,output:{type:`string`,description:`Public asset directory for the generated key file.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=k(e.output);if(!t)throw new w(`INVALID_INPUT`,`Pass --output with the public asset directory that deploys at the site root.`);let n=ut({site:await Su(e)}),r=ni(t),i=T(r,`${n.key}.txt`);await ci(r,{recursive:!0});try{await gi(i,`${n.key}\n`,{encoding:`utf8`,flag:`wx`,mode:384})}catch(e){throw e.code===`EEXIST`?new w(`INVALID_INPUT`,`Refusing to overwrite the existing file at ${i}.`):e}let a;try{a=await ie(n)}catch(e){throw await hi(i).catch(()=>void 0),e}let o={configured:!0,host:n.host,keyLocation:n.keyLocation,keyFile:i,credentialSource:a,nextStep:`Deploy ${i}, then run seo indexnow verify --site ${new URL(n.keyLocation).origin}`};I(e)?z(o):B([[`Host`,o.host],[`Key file`,o.keyFile],[`Public URL`,o.keyLocation],[`Saved in`,o.credentialSource],[`Next`,o.nextStep]])}}),status:E({meta:{name:`status`,description:`Show saved IndexNow hosts`},args:{json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=await ce(),n={configuredSites:t.length,sites:t};I(e)?z(n):V([`Host`,`Key location`,`Created`],t.map(e=>[e.host,e.keyLocation,e.createdAt]))}}),verify:E({meta:{name:`verify`,description:`Check the public IndexNow key file`},args:{...xu,json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=await mt({site:await Su(e)}),n={...await c({record:t.record}),credentialSource:t.source};I(e)?z(n):B([[`Verified`,n.verified?`yes`:`no`],[`HTTP status`,String(n.status)],[`Key location`,n.keyLocation],[`Credential`,n.credentialSource]]),n.verified||(process.exitCode=4)}}),submit:E({meta:{name:`submit`,description:`Notify IndexNow about changed URLs`},args:{...xu,url:{type:`string`,description:`One changed URL.`},urls:{type:`string`,description:`Comma-separated changed URLs.`},file:{type:`string`,description:`Newline-delimited changed URL file.`},"dry-run":{type:`boolean`,default:!1,description:`Validate without notifying IndexNow.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=await mt({site:await Su(e)}),n=await Nr({record:t.record,urls:await Cu(e),dryRun:j(e[`dry-run`])}),r={...n,credentialSource:t.source};if(I(e)){z(r);return}B([[`Host`,n.host],[`Status`,n.status],[`URLs`,String(n.submittedUrls)],[`Dry run`,n.dryRun?`yes`:`no`],[`Key location`,n.keyLocation]]);for(let e of n.caveats)process.stdout.write(`\n${e}\n`)}}),remove:E({meta:{name:`remove`,description:`Remove a saved IndexNow key`},args:{...xu,json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=await ot(await Su(e)),n={removed:t,note:`The deployed key file was not removed.`};I(e)?z(n):process.stdout.write(`${t?`Saved key removed.`:`No saved key found.`} ${n.note}\n`)}})}}),Tu=E({...vc,meta:{name:`init`,description:"Legacy alias for `seo start`."}}),Eu=E({meta:{name:`links`,description:`Review bounded referring-link evidence from Ahrefs, DataForSEO, Bing or a file`},args:{project:{type:`string`,description:`Saved project id or name.`},client:{type:`string`,description:`Legacy alias for --project.`},site:{type:`string`,description:`Verified Bing Webmaster site URL.`},provider:{type:`string`,description:`Live link source: ahrefs, dataforseo or bing.`},target:{type:`string`,description:`Domain or absolute page URL for Ahrefs or DataForSEO.`},scope:{type:`string`,description:`Live provider target scope: domain or page.`},"include-subdomains":{type:`boolean`,default:!0,description:`Include subdomains for a live provider domain target.`},"search-site":{type:`string`,description:`Search Console property used to add target-page search evidence.`},file:{type:`string`,description:`Local CSV, JSON, JSONL, or NDJSON link export.`},format:{type:`string`,description:`Import format override: csv, json, or jsonl.`},"row-limit":{type:`string`,description:`Maximum source rows. Defaults: 100 live provider, 500 Bing, 10000 files.`},"target-limit":{type:`string`,description:`Maximum Bing target pages to inspect. Defaults to 20.`},"detail-pages":{type:`string`,description:`Maximum Bing result pages per target. Defaults to 1.`},limit:{type:`string`,description:`Maximum link rows returned. Defaults to 100.`},days:{type:`string`,description:`Search Console lookback window for linked target pages. Defaults to 90.`},refresh:{type:`boolean`,default:!1,description:`Bypass local provider and Search Console caches.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=I(e),n=k(e.file),r=k(e.format),i=k(e.provider);if(i&&![`ahrefs`,`dataforseo`,`bing`].includes(i))throw new w(`INVALID_INPUT`,`--provider must be ahrefs, dataforseo or bing.`);if(n&&i)throw new w(`INVALID_INPUT`,`Do not pass --provider with a local link file.`);let a=k(e.scope);if(a&&a!==`domain`&&a!==`page`)throw new w(`INVALID_INPUT`,`--scope must be domain or page.`);if(r&&![`csv`,`json`,`jsonl`].includes(r))throw new w(`INVALID_INPUT`,`--format must be csv, json, or jsonl.`);let o=N(e[`row-limit`],`--row-limit`),s=k(e.target),c=k(e.site);if(n&&(c||s))throw new w(`INVALID_INPUT`,`Pass one link source: --file, --site for Bing, or --target for Ahrefs or DataForSEO.`);if(c&&s)throw new w(`INVALID_INPUT`,`Pass --site for Bing or --target for a live research provider, not both.`);if(s&&i===`bing`)throw new w(`INVALID_INPUT`,`Use --site with Bing or --target with Ahrefs or DataForSEO.`);if(c&&[`ahrefs`,`dataforseo`].includes(i??``))throw new w(`INVALID_INPUT`,`Use --target with Ahrefs or DataForSEO, or --site with Bing.`);let l=A(e),u=l?await W({project:l,options:{json:t}}):void 0;!n&&!u&&!s&&(u=await W({options:{json:t}}));let d;if(n)d=await Be({file:n,format:r,rowLimit:o});else if(i===`ahrefs`||i===`dataforseo`||s){let t=s??u?.startUrl??u?.siteUrl.replace(/^sc-domain:/u,``);if(!t)throw new w(`INVALID_INPUT`,`Pass --target or use a saved project with a crawl URL or Search Console property.`);let n={target:t,scope:a,includeSubdomains:j(e[`include-subdomains`]),rowLimit:o,refresh:j(e.refresh),context:{projectId:u?.id,reportId:`link-evidence`,reportRunId:Rr()}};d=i===`ahrefs`?await Je(n):await ne(n)}else{let t=c??ge(u);if(!t)throw new w(`INVALID_INPUT`,`Pass --site, connect Bing to a saved project, or import a link file with --file.`);d=await we({site:t,rowLimit:o,targetLimit:N(e[`target-limit`],`--target-limit`),detailPagesPerTarget:N(e[`detail-pages`],`--detail-pages`)})}let f=k(e[`search-site`])??u?.siteUrl,p=u||f||d.externalProvider?await fe({evidence:d,searchConsoleSite:f,crawlSite:u?.siteUrl??f??d.externalProvider?.summary.data.target,days:N(e.days,`--days`),refresh:j(e.refresh)}):void 0,m=Me({evidence:d,limit:N(e.limit,`--limit`),targetContext:p});if(t){z(m);return}let h=m.providerEvidence?[m.providerEvidence.summary.cost.actualMicros,m.providerEvidence.backlinks.cost.actualMicros]:[],g=h.every(e=>e!==null)?h.reduce((e,t)=>e+t,0):null,_=m.providerEvidence?[m.providerEvidence.summary.cost.native?.actualUnits,m.providerEvidence.backlinks.cost.native?.actualUnits]:[],v=_.every(e=>e!=null)?_.reduce((e,t)=>e+t,0):null,y=m.providerEvidence?[m.providerEvidence.summary.cache.status,m.providerEvidence.backlinks.cache.status].every(e=>e===`hit`):!1;q({title:`Inbound link evidence`,target:m.provenance.provider,status:m.dataStatus===`complete`?`info`:`unknown`,summary:`${G(m.summary.observedLinks)} retained links from ${G(m.summary.referringDomains)} referring domains.`,metrics:[{label:`Evidence`,value:m.dataStatus},{label:`Observed links`,value:G(m.summary.observedLinks)},{label:`Referring domains`,value:G(m.summary.referringDomains)},{label:`Target pages`,value:G(m.summary.targetPages)},{label:`Returned rows`,value:`${G(m.selection.returnedRows)} of ${G(m.selection.availableRows)}`},...m.providerSummary?.backlinks.state===`observed`?[{label:`Provider backlinks`,value:G(m.providerSummary.backlinks.value)}]:[],...m.providerSummary?.referringDomains.state===`observed`?[{label:`Provider domains`,value:G(m.providerSummary.referringDomains.value)}]:[],...m.providerEvidence?[{label:`Provider cost`,value:g===null?v===null?`Unknown`:`${G(v)} API units${y?` (cached)`:``}`:`$${(g/1e6).toFixed(4)}${y?` (cached)`:``}`}]:[],...m.providerSummary?.brokenBacklinks.state===`observed`&&m.providerSummary.brokenBacklinks.value>0?[{label:`Provider broken links`,value:G(m.providerSummary.brokenBacklinks.value)}]:[]]}),m.findings.length&&J([`Priority`,`Finding`,`Target`],m.findings.map(e=>[e.priority,e.code,K(e.targetUrl,72)])),m.links.length&&J([`Source`,`Target`,`Anchor`],m.links.map(e=>[K(e.sourceUrl,58),K(e.targetUrl,58),K(e.anchorText??``,36)])),X(`Warnings`,m.warnings),X(`Report caveats`,m.caveats)}});async function Du(e,t){if(!e){process.stdout.write(t);return}await ci(Zr(e),{recursive:!0}),await gi(e,t),process.stdout.write(`Wrote ${e}\n`)}const Ou={url:{type:`string`,description:`Public site or subpath URL to crawl.`},"report-id":{type:`string`,description:`Saved crawl report id to use.`},site:{type:`string`,description:`GSC property URL for selecting the latest saved crawl.`},client:{type:`string`,description:`Legacy alias for --project.`},project:{type:`string`,description:`Saved project id or name.`},"max-pages":{type:`string`,description:`Maximum public HTML pages to crawl.`},"fetch-interval-cap":{type:`string`,description:`Maximum page fetches per interval per host.`},"fetch-interval-ms":{type:`string`,description:`Fetch rate interval in milliseconds.`},refresh:{type:`boolean`,default:!1,description:`Bypass local HTTP cache and fetch fresh pages.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}};async function ku(e,t){let n=k(e.url),r=k(e[`report-id`]);if(n&&r)throw Error(`Use either --url or --report-id, not both.`);if(!n)return Bo(e,{json:t.json});let i=A(e),a=k(e.site),o=i||a?await U({client:i,site:a,options:{json:t.json}}):void 0;return rt({url:n,projectId:o?.client?.id,site:o?.site,maxPages:M(e[`max-pages`]),fetchRate:za(e),refresh:j(e.refresh),useSitemap:!0,checkExternal:!1})}const Au=E({meta:{name:`llms`,description:`Check or generate an optional llms.txt file`},subCommands:{audit:E({meta:{name:`audit`,description:`Check an optional llms.txt file body and links`},args:Ou,run:async({args:e})=>{let t=I(e),n=await Ne(await ku(e,{json:t}));if(t){z(n);return}q({title:`llms.txt audit`,target:n.url,status:n.issues.length>0?`warning`:n.dataStatus===`complete`?`pass`:`info`,summary:n.headline,metrics:[{label:`SEO impact`,value:n.googleSearchImpact},{label:`Optional`,value:n.optional?`Yes`:`No`},{label:`Found`,value:n.exists?`Yes`:`No`},{label:`URL`,value:n.llmsTxtUrl},{label:`HTTP status`,value:n.status?String(n.status):`Unavailable`}],diagnostics:[{title:`Issues`,items:n.issues.map(e=>({status:e.severity===`high`?`fail`:`warning`,title:e.title,explanation:e.plainEnglish,fix:e.action}))}]}),X(`Caveats`,n.caveats),X(`Recommended pages`,n.recommendedPages.slice(0,10).map(e=>`${e.section}: ${e.url}`))}}),generate:E({meta:{name:`generate`,description:`Generate a valid llms.txt v2 draft from crawl data`},args:{...Ou,output:{type:`string`,description:`Write llms.txt to this path instead of stdout.`},"max-urls":{type:`string`,description:`Maximum URLs to include. Defaults to 250.`},"token-budget":{type:`string`,description:`Approximate token budget. Defaults to 12000.`},exclude:{type:`string`,description:`Comma-separated URL patterns to exclude.`},title:{type:`string`,description:`Override the llms.txt title.`},description:{type:`string`,description:`Override the llms.txt description.`}},run:async({args:e})=>{let t=I(e),n=Ve(await ku(e,{json:t}),{maxUrls:M(e[`max-urls`]),tokenBudget:M(e[`token-budget`]),maxBytes:t?65536:void 0,exclude:P(e.exclude),title:k(e.title),description:k(e.description)});if(t){z(n);return}await Du(k(e.output),n.content),k(e.output)&&B([[`URLs`,String(n.includedUrls)],[`Estimated tokens`,String(n.estimatedTokens)],[`Truncated`,n.limits.truncated?n.limits.reasons.join(`, `):`No`]])}})}}),ju=E({meta:{name:`logs`,description:`Manage local log retention`},subCommands:{prune:E({meta:{name:`prune`,description:`Rotate large logs and remove old local logs`},args:{quiet:{type:`boolean`,default:!1,description:`Do not print a result.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=Oe();if(I(e)){z(t);return}e.quiet||process.stdout.write(`Rotated ${t.rotated} and removed ${t.removed} log files. ${Po(t.sizeBytes)} retained.\n`)}})}}),Mu=[{client:`claude-desktop`,flag:`claude-desktop`},{client:`claude-code`,flag:`claude-code`},{client:`codex`,flag:`codex`},{client:`cursor`,flag:`cursor`}];function Nu(e,t){return j(e.all)?t:Mu.filter(({flag:t})=>j(e[t])).map(({client:e})=>e)}const Pu=E({meta:{name:`mcp`,description:`MCP server helpers`},subCommands:{serve:E({meta:{name:`serve`,description:`Run the SEO MCP server`},args:{test:{type:`boolean`,default:!1,description:`Start in test mode`}},run:async({args:e})=>{let{startMcpServer:t}=await import(`./dist-LvTv0NK4.js`);await t({test:j(e.test)})}}),install:E({meta:{name:`install`,description:`Install or remove SEO MCP config for local clients`},args:{uninstall:{type:`boolean`,default:!1,description:`Remove the SEO MCP config instead of adding it`},reinstall:{type:`boolean`,default:!1,description:`Rewrite an existing managed SEO MCP config`},"claude-desktop":{type:`boolean`,default:!1,description:`Update Claude Desktop`},"claude-code":{type:`boolean`,default:!1,description:`Update Claude Code`},codex:{type:`boolean`,default:!1,description:`Update Codex CLI, app, and IDE extension`},cursor:{type:`boolean`,default:!1,description:`Update Cursor`},all:{type:`boolean`,default:!1,description:`Update every detected MCP client`},json:{type:`boolean`,default:!1,description:`Print structured JSON output`}},run:async({args:e})=>{if(j(e.uninstall)&&j(e.reinstall))throw new w(`INVALID_INPUT`,`Use either --uninstall or --reinstall, not both.`);let t=js(),n=Ps({targets:t}),r=I(e),i=Nu(e,n.map(e=>e.client));if(i.length===0){if(!R({json:r}))throw new w(`INVALID_INPUT`,`Choose an MCP client with --claude-desktop, --claude-code, --codex, --cursor, or --all.`);let a=n.length>0?n:t;i=H(await ki({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 w(`INVALID_INPUT`,`MCP setup is not supported for ${a.join(`, `)} on ${process.platform}.`);let o=t.filter(e=>i.includes(e.client)).map(t=>j(e.uninstall)?$s(t):Qs(t,{reinstall:j(e.reinstall)}));if(r){z({operation:j(e.uninstall)?`uninstall`:`install`,results:o});return}for(let e of o)process.stdout.write(`${e.changed?`updated`:`skipped`} ${e.client} · ${e.path}\n`)}})}}),Fu=E({meta:{name:`crawl-diff`,description:`Crawl a site and diff technical/page changes vs last run`},args:{url:{type:`string`,required:!0,description:`Start URL to crawl.`},site:{type:`string`,description:`Optional GSC property to associate with the crawl.`},limit:{type:`string`,description:`Maximum pages to crawl. Defaults to 50.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`},refresh:{type:`boolean`,default:!1,description:`Bypass local HTTP cache.`},js:{type:`boolean`,default:!1,description:`Force JavaScript rendering when Playwright is installed.`}},run:async({args:e})=>{let t=await De({startUrl:k(e.url)??``,site:k(e.site),limit:M(e.limit),refresh:j(e.refresh),js:j(e.js)?!0:`auto`});if(I(e)){z(t);return}if(q({title:`Crawl history comparison`,target:t.run.startUrl,status:t.previousRun===void 0?`unknown`:t.summary.newErrors>0||t.summary.indexabilityFlips>0?`warning`:`pass`,summary:t.previousRun===void 0?`No previous crawl was available for comparison.`:`${t.summary.changed} changed, ${t.summary.added} added, and ${t.summary.removed} removed URLs.`,metrics:[{label:`Run`,value:t.run.id},{label:`Crawled`,value:t.summary.crawled},{label:`Previous run`,value:t.previousRun?.id??`None`},{label:`Added`,value:t.summary.added},{label:`Removed`,value:t.summary.removed},{label:`Changed`,value:t.summary.changed},{label:`New errors`,value:t.summary.newErrors},{label:`Indexability flips`,value:t.summary.indexabilityFlips},{label:`High priority`,value:t.summary.highPriorityRecommendations}]}),t.recommendations.length&&(process.stdout.write(`
170
+ `),z([[`Control`,e.control.change.title],[`Adjusted clicks`,e.control.adjusted.clickDelta===null?`n/a`:W(e.control.adjusted.clickDelta)],[`Adjusted click %`,e.control.adjusted.clickPctPoints===null?`n/a`:`${e.control.adjusted.clickPctPoints.toFixed(1)} pts`],[`Note`,e.control.note]])),Y(`Warnings`,e.warnings),Y(`Caveats`,e.caveats)}const Hl=T({meta:{name:`tests`,description:`Create and report local SEO tests from Search Console and Google Analytics data`},subCommands:{list:T({meta:{name:`list`,description:`List saved SEO tests`},args:{site:{type:`string`,description:`GSC property URL.`},client:{type:`string`,description:`Legacy alias for --project.`},project:{type:`string`,description:`Saved project id or name.`},limit:{type:`string`,description:`Maximum tests to list. Defaults to 20.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=F(e),n=k(e),r=Qn({site:(n||O(e.site)?await H({client:n,site:O(e.site),options:{json:t}}):void 0)?.site,limit:j(e.limit)});if(t){R({tests:r});return}B([`ID`,`Date`,`Scope`,`Target`,`Title`],r.map(e=>[e.id,e.changedAt,e.scope,e.target,e.title]))}}),create:T({meta:{name:`create`,description:`Create a local SEO test`},args:{site:{type:`string`,description:`GSC property URL.`},client:{type:`string`,description:`Legacy alias for --project.`},project:{type:`string`,description:`Saved project id or name.`},scope:{type:`string`,default:`page`,description:`Change scope: site, page, query, or group.`},target:{type:`string`,required:!0,description:`Changed URL, query, group pattern, or site marker.`},title:{type:`string`,required:!0,description:`Plain-English name for the test.`},description:{type:`string`,description:`Optional note about what changed.`},date:{type:`string`,description:`Change date in YYYY-MM-DD. Defaults to today.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=F(e),n=await H({client:k(e),site:O(e.site),options:{json:t}}),r=be({site:n.site,scope:Fl(e.scope),target:O(e.target)??``,title:O(e.title)??``,description:O(e.description),changedAt:O(e.date)??Pl()});if(t){R(r);return}z([[`ID`,r.id],[`Date`,r.changedAt],[`Project`,n.client?.name??n.site],[`Scope`,r.scope],[`Target`,r.target],[`Title`,r.title]])}}),report:T({meta:{name:`report`,description:`Report an SEO test with equal finalized before/after windows`},args:{id:{type:`string`,description:`Saved SEO test id.`},site:{type:`string`,description:`GSC property URL.`},client:{type:`string`,description:`Legacy alias for --project.`},project:{type:`string`,description:`Saved project id or name.`},scope:{type:`string`,description:`Ad-hoc change scope: site, page, query, or group.`},target:{type:`string`,description:`Ad-hoc changed URL, query, group pattern, or site marker.`},title:{type:`string`,description:`Ad-hoc test title.`},date:{type:`string`,description:`Ad-hoc change date in YYYY-MM-DD.`},property:{type:`string`,description:`Google Analytics property ID.`},"control-scope":{type:`string`,description:`Control scope: site, page, query, or group.`},"control-target":{type:`string`,description:`Control URL, query, group pattern, or site marker.`},"control-title":{type:`string`,description:`Plain-English name for the control group.`},before:{type:`string`,description:`Days before the change date. Defaults to 28.`},after:{type:`string`,description:`Days after the change date. Defaults to 28.`},refresh:{type:`boolean`,default:!1,description:`Bypass the local Search Console and Google Analytics cache.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=F(e),n=O(e.id),r=!n||k(e)||O(e.site)?await H({client:k(e),site:O(e.site),options:{json:t,refresh:A(e.refresh)}}):void 0,i=await Lr({id:n,site:n?void 0:r?.site,scope:n?void 0:Il(e.scope),target:n?void 0:O(e.target),title:O(e.title),changedAt:n?void 0:O(e.date),googleAnalyticsPropertyId:O(e.property)??r?.client?.analytics.google?.propertyId,controlScope:Il(e[`control-scope`]),controlTarget:O(e[`control-target`]),controlTitle:O(e[`control-title`]),beforeDays:M(e.before,`--before`),afterDays:M(e.after,`--after`),refresh:A(e.refresh)});if(t){R(i);return}Vl(i)}})}});function Ul(e){if(!e)return;let t=Date.now();return e=>{let n=((Date.now()-t)/1e3).toFixed(1);process.stderr.write(`[seo ${n}s] ${e}\n`)}}const Wl={site:{type:`string`,description:`GSC property URL, for example sc-domain:example.com.`},client:{type:`string`,description:`Legacy alias for --project.`},project:{type:`string`,description:`Saved project id or name.`}},Gl={...X([`includeBrand`,`verifyContent`,`verifyLimit`,`js`,`fetchConcurrency`,`fetchIntervalCap`,`fetchIntervalMs`,`refresh`]),json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}};function Kl(e){return{includeBrand:A(e[`include-brand`]),verifyContent:A(e[`verify-content`]),verifyLimit:j(e[`verify-limit`])??3,js:A(e.js)?!0:void 0,rate:Ba(e),refresh:A(e.refresh)}}function ql(e){return{client:k(e),site:O(e.site),refresh:A(e.refresh)}}const{json:Jl,...Yl}=Gl,Xl=Yl,Zl={client:{type:`string`,description:`Legacy alias for --project.`},project:{type:`string`,description:`Saved project id or name. Defaults to the configured default.`},site:{type:`string`,description:`GSC property URL/id when not using a saved project.`}};function Ql(e){return{type:`string`,description:`Output folder. Defaults to ./seo-export/<project>-${e}-YYYY-MM-DD.`}}function $l(e){return e.toLowerCase().replace(/^sc-domain:/,``).replace(/^https?:\/\//,``).replace(/[^a-z0-9._-]+/g,`-`).replace(/^-+|-+$/g,``)}function eu(e){let t=new Date().toISOString().slice(0,10),n=$l(e.clientId??e.site);return ri(process.cwd(),`seo-export`,`${n}-${e.report}-${t}`)}async function tu(e,t){await li(e,{recursive:!0});let n=[];for(let r of t){let t=ri(e,r.filename);await _i(t,p(r.rows,r.headers),`utf8`),n.push(t)}return n}function nu(e,t){process.stdout.write(`Wrote ${On(t.length,`CSV file`)} to ${e}\n`);for(let e of t)process.stdout.write(`- ${e}\n`)}const ru=T({meta:{name:`diagnose`,description:`Export diagnosis tables: priorities, anomalies, movement, decay, cannibalisation, striking-distance, and quick wins.`},args:{...Zl,out:Ql(`diagnose`),days:{type:`string`,description:`GSC lookback window. Defaults to 90.`},recent:{type:`string`,description:`Recent anomaly window in days.`},limit:{type:`string`,description:`Maximum rows per movement/opportunity table.`},"include-brand":{type:`boolean`,default:!1,description:`Include branded queries in opportunity reports.`},"verify-content":{type:`boolean`,default:!1,description:`Verify top opportunities against page title, meta, and content.`},"verify-limit":{type:`string`,description:`Maximum opportunity URLs to verify.`},js:{type:`boolean`,default:!1,description:`Force JavaScript rendering for verified pages.`},"fetch-concurrency":{type:`string`,description:`Maximum concurrent page fetches per host. Defaults to 4.`},"fetch-interval-cap":{type:`string`,description:`Maximum page fetches per interval per host. Defaults to 4.`},"fetch-interval-ms":{type:`string`,description:`Fetch rate interval in milliseconds. Defaults to 1000.`},refresh:{type:`boolean`,default:!1,description:`Bypass local cache and fetch fresh data.`}},run:async({args:e})=>{let t=await H({client:k(e),site:O(e.site)}),n=await ut({site:t.site,days:j(e.days),recentDays:j(e.recent),limit:j(e.limit),brandTerms:t.client?.brandTerms,includeBrand:A(e[`include-brand`]),verifyContent:A(e[`verify-content`]),verifyLimit:j(e[`verify-limit`]),js:A(e.js)?!0:`auto`,rate:Ba(e),refresh:A(e.refresh),progress:Ul(!0)}),r=O(e.out)??eu({report:`diagnose`,clientId:t.client?.id,site:t.site});nu(r,await tu(r,jr(n)))}}),iu=`.seo-okf.json`,au=`${JSON.stringify({generator:`seo`,format:`okf`,version:1})}\n`;function ou(e,t){let n=ri(e,t),r=ni(ri(e),n);if(!r||r.startsWith(`..`)||ei(r))throw new C(`INVALID_INPUT`,`Unsafe OKF file path: ${t}`);return n}async function su(e,t){for(let n of t){let t=ou(e,n.path);await li(Qr(t),{recursive:!0}),await _i(t,n.content)}await _i(w(e,iu),au)}async function cu(e){let t=[],n=0;async function r(e,i=``){let a=await fi(e,{withFileTypes:!0});for(let o of a){let a=i?`${i}/${o.name}`:o.name,s=w(e,o.name);if(o.isDirectory())await r(s,a);else if(o.isFile()&&o.name.endsWith(`.md`)){if(t.length>=5006)throw new C(`INVALID_INPUT`,`OKF validation accepts at most ${gn} Markdown files.`);let e=await ci(s);if(e.size>2e6)throw new C(`INVALID_INPUT`,`OKF file exceeds ${Lt} bytes: ${a}`);if(n+=e.size,n>67108864)throw new C(`INVALID_INPUT`,`OKF Markdown files exceed ${Hn} bytes in total.`);t.push({path:a,content:await di(s,`utf8`)})}}}return await r(e),t.sort((e,t)=>e.path<t.path?-1:+(e.path>t.path))}async function lu(e){try{let t=await ci(e);if(t.isSymbolicLink()||!t.isDirectory())throw new C(`INVALID_INPUT`,`OKF output must be a real directory: ${e}`);if(!(await fi(e)).length)return!0;try{if(await di(w(e,iu),`utf8`)===au)return!0}catch{}throw new C(`INVALID_INPUT`,`Refusing to replace non-empty unmanaged directory: ${e}`)}catch(e){if(e.code===`ENOENT`)return!1;throw e}}async function uu(e,t){if(!Wt(t,{profile:`seo-export`}).valid)throw new C(`INVALID_INPUT`,`Generated bundle did not pass the SEO export profile and was not written.`);let n=ri(e),r=Qr(n);await li(r,{recursive:!0});let i=await lu(n),a=await ui(w(r,`.${Zr(n)}-staging-`)),o=w(r,`.${Zr(n)}-backup-${zr()}`),s=!1;try{await su(a,t);let e=Wt(await cu(a),{profile:`seo-export`});if(!e.valid)throw new C(`INTERNAL_ERROR`,`Staged OKF files failed validation after writing.`);return i&&(await pi(n,o),s=!0),await pi(a,n),s&&await mi(o,{recursive:!0,force:!0}),e}catch(e){throw await mi(a,{recursive:!0,force:!0}),s&&(await mi(n,{recursive:!0,force:!0}),await pi(o,n)),e}}async function du(e,t){if(!e){process.stdout.write(t);return}await li(Qr(e),{recursive:!0}),await _i(e,t),process.stdout.write(`Wrote ${e}\n`)}function fu(e){let t=O(e)??`okf`;if([`okf`,`markdown`,`json`].includes(t))return t;throw Error(`Format must be one of: okf, markdown, json.`)}function pu(e){return{reportId:e.id,url:e.config.url,generatedAt:e.generatedAt,crawl:{summary:e.summary,caveats:e.caveats,warnings:e.warnings,pages:e.pages.map(e=>({url:e.finalUrl,title:e.title,metaDescription:e.metaDescription,status:e.status,indexable:e.indexable,wordCount:e.wordCount,schemaTypes:e.schemaTypes,sameAs:e.schemaSameAs,internalInlinks:e.internalInlinkCount,contentSample:e.contentSample}))},aiReadiness:re(e),entityReadiness:me(e),llmsTxt:Ae(e)}}function mu(e){let t=pu(e);return`${[`# Site Knowledge Export`,``,`Source: ${e.config.url}`,`Crawl report: ${e.id}`,`Generated: ${t.generatedAt}`,``,`## Summary`,``,`- Documents: ${e.summary.totalPages}${e.requestEvidenceStatus===`available`?` from ${e.requests.length} URL requests`:e.requestEvidenceStatus===`partial`?` from ${e.requests.length} observed URL requests (request evidence is partial)`:` (request evidence unavailable for this legacy report)`}`,`- Indexable pages: ${e.summary.indexablePages}`,`- Crawl findings: ${e.issues.length} (${e.summary.highIssues} high, ${e.summary.mediumIssues} medium, ${e.summary.lowIssues} low)`,`- AI-search evidence: ${t.aiReadiness.dataStatus} (${t.aiReadiness.assessment})`,`- Entity evidence: ${t.entityReadiness.dataStatus} (${t.entityReadiness.assessment})`,``,`## Top AI Readiness Actions`,``,...t.aiReadiness.topActions.length?t.aiReadiness.topActions.map(e=>`- ${e.title}: ${e.action}`):[`- No AI-search technical actions in this crawl.`],``,`## Entity Signals`,``,`- sameAs profiles: ${t.entityReadiness.entities.sameAs.length}`,`- Social profiles: ${t.entityReadiness.entities.socialProfiles.length}`,`- Authors: ${t.entityReadiness.entities.authors.length}`,``,`## Important Pages`,``,...e.pages.filter(e=>e.indexable&&e.status<400).slice(0,100).map(e=>`- [${e.title??e.finalUrl}](${e.finalUrl}) - ${e.metaDescription??e.contentSample??`No summary available.`}`),``,`# Citations`,``,`- [Crawl start URL](${e.config.url})`,``].join(`
171
+ `)}\n`}const hu=T({meta:{name:`knowledge`,description:`Export site knowledge from a saved crawl as OKF, Markdown, or JSON`},args:{"report-id":{type:`string`,description:`Saved crawl report id to export.`},site:{type:`string`,description:`GSC property URL for selecting the latest saved crawl.`},client:{type:`string`,description:`Legacy alias for --project.`},project:{type:`string`,description:`Saved project id or name.`},format:{type:`string`,description:`Output format: okf, markdown, or json. Defaults to okf.`},output:{type:`string`,description:`Output directory for OKF, or output file for Markdown/JSON.`},"max-concepts":{type:`string`,description:`Maximum OKF page concepts. Defaults to 500.`},json:{type:`boolean`,default:!1,description:`Print command metadata as JSON.`}},run:async({args:e})=>{let t=F(e),n=fu(e.format),r=n===`okf`?er(M(e[`max-concepts`],`--max-concepts`)):void 0,i=await Vo(e,{json:t}),a=O(e.output);if(n===`okf`){let e=Ln(i,{maxConcepts:r}),o=a??`./okf`,s=await uu(o,e.files);if(t){R({format:n,output:o,bundle:e,validation:s});return}process.stdout.write(`Wrote OKF bundle to ${o}\n\n`),z([[`Files`,String(e.files.length)],[`Concepts`,String(e.conceptCount)],[`Page concepts`,String(e.pageConceptCount)],[`Valid`,s.valid?`yes`:`no`]]);return}if(n===`json`){await du(a,`${JSON.stringify(pu(i),null,2)}\n`);return}await du(a,mu(i))}}),gu=T({meta:{name:`monthly`,description:`Export monthly report tables plus the underlying diagnosis detail CSVs.`},args:{...Zl,out:Ql(`monthly`),month:{type:`string`,description:`Report month as YYYY-MM. Defaults to the latest month with final GSC data.`},...X([`limit`],{limit:{description:`Maximum rows per diagnostic section. Defaults to 10.`}}),...Xl},run:async({args:e})=>{let t=await H({client:k(e),site:O(e.site),options:{refresh:A(e.refresh)}}),n=await Ut({site:t.site,month:O(e.month),limit:j(e.limit),brandTerms:t.client?.brandTerms,...Kl(e),progress:Ul(!0)}),r=O(e.out)??eu({report:`monthly`,clientId:t.client?.id,site:t.site});nu(r,await tu(r,Vt(n)))}}),_u=T({meta:{name:`narrative`,description:`Export report narrative tables plus the underlying diagnosis detail CSVs.`},args:{...Zl,out:Ql(`narrative`),...X([`days`,`recentDays`],{days:{description:`Diagnosis window length in days. Defaults to 90.`}}),"start-date":{type:`string`,description:`Optional report start date as YYYY-MM-DD.`},"end-date":{type:`string`,description:`Optional report end date as YYYY-MM-DD.`},...X([`limit`],{limit:{description:`Maximum rows per diagnostic section. Defaults to 10.`}}),"change-limit":{type:`string`,description:`Maximum saved changes to measure. Defaults to 5.`},...Xl},run:async({args:e})=>{let t=await H({client:k(e),site:O(e.site),options:{refresh:A(e.refresh)}}),n=await kr({site:t.site,days:j(e.days),recentDays:j(e.recent),startDate:O(e[`start-date`]),endDate:O(e[`end-date`]),limit:j(e.limit),changeLimit:j(e[`change-limit`]),brandTerms:t.client?.brandTerms,...Kl(e),progress:Ul(!0)}),r=O(e.out)??eu({report:`narrative`,clientId:t.client?.id,site:t.site});nu(r,await tu(r,Vt(n)))}});function vu(e){return y({days:M(e.days,`--days`),sitemaps:N(e.sitemap),maxSitemapUrls:M(e[`max-sitemap-urls`],`--max-sitemap-urls`),templateLimit:M(e.limit,`--limit`),minimumTemplateUrls:M(e[`minimum-template-urls`],`--minimum-template-urls`),minimumTemplateShare:M(e[`minimum-template-share`],`--minimum-template-share`),minimumTemplateImpressions:M(e[`minimum-template-impressions`],`--minimum-template-impressions`),crawlSamples:M(e[`crawl-samples`],`--crawl-samples`),inspectSamples:M(e[`inspect-samples`],`--inspect-samples`),brandTerms:N(e[`brand-terms`]),includeBrand:A(e[`include-brand`]),js:A(e.js)?!0:`auto`,refresh:A(e.refresh),rate:Va(e)})}const yu=T({meta:{name:`pseo`,description:`Export pSEO audit tables: templates, demand patterns, top queries, sample coverage, crawl samples, and inspection samples.`},args:{...Zl,out:Ql(`pseo`),days:{type:`string`,description:`GSC lookback window. Defaults to 28.`},limit:{type:`string`,description:`Maximum pSEO templates to export.`},sitemap:{type:`string`,description:`Comma-separated sitemap URLs. Can be passed once.`},"crawl-samples":{type:`string`,description:`Number of representative URLs to fetch per template. Defaults to 0.`},"inspect-samples":{type:`string`,description:`Number of representative URLs to inspect in GSC per template. Defaults to 0.`},"max-sitemap-urls":{type:`string`,description:`Maximum discovered URLs per sitemap. Defaults to 50000.`},"minimum-template-urls":{type:`string`,description:`Minimum repeated URLs required for a template. Defaults to 3.`},"minimum-template-share":{type:`string`,description:`Minimum share of discovered URLs from 0 to 1. Defaults to 0.`},"minimum-template-impressions":{type:`string`,description:`Minimum retained page impressions for a template. Defaults to 0.`},"brand-terms":{type:`string`,description:`Comma-separated brand terms to exclude from query evidence.`},"include-brand":{type:`boolean`,default:!1,description:`Include branded queries in pSEO demand analysis.`},js:{type:`boolean`,default:!1,description:`Force JavaScript rendering for crawled samples.`},"fetch-concurrency":{type:`string`,description:`Maximum concurrent page fetches per host. Defaults to 4.`},"fetch-interval-cap":{type:`string`,description:`Maximum page fetches per interval per host. Defaults to 4.`},"fetch-interval-ms":{type:`string`,description:`Fetch rate interval in milliseconds. Defaults to 1000.`},refresh:{type:`boolean`,default:!1,description:`Bypass local cache and fetch fresh data.`}},run:async({args:e})=>{let t=vu(e),n=await H({client:k(e),site:O(e.site)}),r=await Be({site:n.site,...t,brandTerms:t.brandTerms??n.client?.brandTerms,progress:Ul(!0)}),i=O(e.out)??eu({report:`pseo`,clientId:n.client?.id,site:n.site});nu(i,await tu(i,Dr(r)))}}),bu=T({meta:{name:`refresh-priorities`,description:`Export the ranked SEO priority queue, score breakdowns, grouped findings, and diagnosis detail CSVs.`},args:{...Zl,out:Ql(`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.`}}),...el,"verify-content":Fa(`Verify top opportunities against page title, meta, and content. Defaults to true.`,`Skip page content verification.`),"verify-limit":{type:`string`,description:`Maximum opportunity URLs to verify. Defaults to 5.`}},run:async({args:e})=>{let t=await H({client:k(e),site:O(e.site),options:{refresh:A(e.refresh)}}),n=await an({site:t.site,days:j(e.days),recentDays:j(e.recent),limit:j(e.limit),brandTerms:t.client?.brandTerms,includeBrand:A(e[`include-brand`]),googleAnalyticsPropertyId:O(e[`google-analytics-property`])??t.client?.analytics.google?.propertyId,analyticsConnection:tl(e,Yn(t.client)),verifyContent:A(e[`verify-content`]),verifyLimit:j(e[`verify-limit`]),refresh:A(e.refresh)}),r=O(e.out)??eu({report:`refresh-priorities`,clientId:t.client?.id,site:t.site});nu(r,await tu(r,et(n)))}}),xu=T({meta:{name:`export`,description:`Export full report data to CSV files`},subCommands:{diagnose:ru,knowledge:hu,narrative:_u,monthly:gu,"update-postmortem":T({meta:{name:`update-postmortem`,description:`Export update postmortem attribution, findings, template movement, and segment movers.`},args:{...Zl,out:Ql(`update-postmortem`),...X([`days`,`recentDays`,`limit`,`includeBrand`,`refresh`],{days:{description:`Diagnosis window length in days. Defaults to 90.`},limit:{description:`Maximum winners/losers per segment. Defaults to 20.`}}),"known-change":{type:`string`,description:`Manual site-side change to treat as a confounder, for example pruning pages or blocking traffic.`},"ignore-change-log":{type:`boolean`,default:!1,description:`Do not use saved change-log entries as confounders.`}},run:async({args:e})=>{let t=await H({client:k(e),site:O(e.site),options:{refresh:A(e.refresh)}}),n=await _r({site:t.site,days:j(e.days),recentDays:j(e.recent),limit:j(e.limit),brandTerms:t.client?.brandTerms,includeBrand:A(e[`include-brand`]),knownConfounders:O(e[`known-change`])?[O(e[`known-change`])??``]:void 0,includeChangeLog:!A(e[`ignore-change-log`]),refresh:A(e.refresh)}),r=O(e.out)??eu({report:`update-postmortem`,clientId:t.client?.id,site:t.site});nu(r,await tu(r,ta(n)))}}),"refresh-priorities":bu,pseo:yu}}),Su={project:{type:`string`,description:`Saved project id or name.`},client:{type:`string`,description:`Legacy alias for --project.`},site:{type:`string`,description:`Site URL.`}};async function Cu(e){let t=O(e.site);if(t)return t;let n=await U({project:k(e),options:{json:F(e)}});if(!n)throw new C(`INVALID_INPUT`,`Pass --site or select a saved project.`);return n.siteUrl}async function wu(e){let t=O(e.url),n=N(e.urls)??[],r=O(e.file);if(!t&&n.length===0&&!r)throw new C(`INVALID_INPUT`,`Pass --url, comma-separated --urls, or a newline-delimited --file.`);let i=[...t?[t]:[],...n];if(r){let e=await hi(r).catch(()=>void 0);if(!e)throw new C(`INVALID_INPUT`,`IndexNow URL file not found: ${r}`);if(!e.isFile()||e.size>21e5)throw new C(`INVALID_INPUT`,`IndexNow URL files must be regular files no larger than 2.1 MB.`);i.push(...(await di(r,`utf8`)).split(/\r?\n/).map(e=>e.trim()).filter(Boolean))}return i}const Tu=T({meta:{name:`indexnow`,description:`Set up and submit changed URLs with IndexNow`},subCommands:{setup:T({meta:{name:`setup`,description:`Generate an IndexNow key file for one site`},args:{...Su,output:{type:`string`,description:`Public asset directory for the generated key file.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=O(e.output);if(!t)throw new C(`INVALID_INPUT`,`Pass --output with the public asset directory that deploys at the site root.`);let n=dt({site:await Cu(e)}),r=ri(t),i=w(r,`${n.key}.txt`);await li(r,{recursive:!0});try{await _i(i,`${n.key}\n`,{encoding:`utf8`,flag:`wx`,mode:384})}catch(e){throw e.code===`EEXIST`?new C(`INVALID_INPUT`,`Refusing to overwrite the existing file at ${i}.`):e}let a;try{a=await ae(n)}catch(e){throw await gi(i).catch(()=>void 0),e}let o={configured:!0,host:n.host,keyLocation:n.keyLocation,keyFile:i,credentialSource:a,nextStep:`Deploy ${i}, then run seo indexnow verify --site ${new URL(n.keyLocation).origin}`};F(e)?R(o):z([[`Host`,o.host],[`Key file`,o.keyFile],[`Public URL`,o.keyLocation],[`Saved in`,o.credentialSource],[`Next`,o.nextStep]])}}),status:T({meta:{name:`status`,description:`Show saved IndexNow hosts`},args:{json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=await le(),n={configuredSites:t.length,sites:t};F(e)?R(n):B([`Host`,`Key location`,`Created`],t.map(e=>[e.host,e.keyLocation,e.createdAt]))}}),verify:T({meta:{name:`verify`,description:`Check the public IndexNow key file`},args:{...Su,json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=await ht({site:await Cu(e)}),n={...await c({record:t.record}),credentialSource:t.source};F(e)?R(n):z([[`Verified`,n.verified?`yes`:`no`],[`HTTP status`,String(n.status)],[`Key location`,n.keyLocation],[`Credential`,n.credentialSource]]),n.verified||(process.exitCode=4)}}),submit:T({meta:{name:`submit`,description:`Notify IndexNow about changed URLs`},args:{...Su,url:{type:`string`,description:`One changed URL.`},urls:{type:`string`,description:`Comma-separated changed URLs.`},file:{type:`string`,description:`Newline-delimited changed URL file.`},"dry-run":{type:`boolean`,default:!1,description:`Validate without notifying IndexNow.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=await ht({site:await Cu(e)}),n=await Pr({record:t.record,urls:await wu(e),dryRun:A(e[`dry-run`])}),r={...n,credentialSource:t.source};if(F(e)){R(r);return}z([[`Host`,n.host],[`Status`,n.status],[`URLs`,String(n.submittedUrls)],[`Dry run`,n.dryRun?`yes`:`no`],[`Key location`,n.keyLocation]]);for(let e of n.caveats)process.stdout.write(`\n${e}\n`)}}),remove:T({meta:{name:`remove`,description:`Remove a saved IndexNow key`},args:{...Su,json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=await st(await Cu(e)),n={removed:t,note:`The deployed key file was not removed.`};F(e)?R(n):process.stdout.write(`${t?`Saved key removed.`:`No saved key found.`} ${n.note}\n`)}})}}),Eu=T({...yc,meta:{name:`init`,description:"Legacy alias for `seo start`."}}),Du=T({meta:{name:`links`,description:`Review bounded referring-link evidence from Ahrefs, DataForSEO, Bing or a file`},args:{project:{type:`string`,description:`Saved project id or name.`},client:{type:`string`,description:`Legacy alias for --project.`},site:{type:`string`,description:`Verified Bing Webmaster site URL.`},provider:{type:`string`,description:`Live link source: ahrefs, dataforseo or bing.`},target:{type:`string`,description:`Domain or absolute page URL for Ahrefs or DataForSEO.`},scope:{type:`string`,description:`Live provider target scope: domain or page.`},"include-subdomains":{type:`boolean`,default:!0,description:`Include subdomains for a live provider domain target.`},"search-site":{type:`string`,description:`Search Console property used to add target-page search evidence.`},file:{type:`string`,description:`Local CSV, JSON, JSONL, or NDJSON link export.`},format:{type:`string`,description:`Import format override: csv, json, or jsonl.`},"row-limit":{type:`string`,description:`Maximum source rows. Defaults: 100 live provider, 500 Bing, 10000 files.`},"target-limit":{type:`string`,description:`Maximum Bing target pages to inspect. Defaults to 20.`},"detail-pages":{type:`string`,description:`Maximum Bing result pages per target. Defaults to 1.`},limit:{type:`string`,description:`Maximum link rows returned. Defaults to 100.`},days:{type:`string`,description:`Search Console lookback window for linked target pages. Defaults to 90.`},refresh:{type:`boolean`,default:!1,description:`Bypass local provider and Search Console caches.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=F(e),n=O(e.file),r=O(e.format),i=O(e.provider);if(i&&![`ahrefs`,`dataforseo`,`bing`].includes(i))throw new C(`INVALID_INPUT`,`--provider must be ahrefs, dataforseo or bing.`);if(n&&i)throw new C(`INVALID_INPUT`,`Do not pass --provider with a local link file.`);let a=O(e.scope);if(a&&a!==`domain`&&a!==`page`)throw new C(`INVALID_INPUT`,`--scope must be domain or page.`);if(r&&![`csv`,`json`,`jsonl`].includes(r))throw new C(`INVALID_INPUT`,`--format must be csv, json, or jsonl.`);let o=M(e[`row-limit`],`--row-limit`),s=O(e.target),c=O(e.site);if(n&&(c||s))throw new C(`INVALID_INPUT`,`Pass one link source: --file, --site for Bing, or --target for Ahrefs or DataForSEO.`);if(c&&s)throw new C(`INVALID_INPUT`,`Pass --site for Bing or --target for a live research provider, not both.`);if(s&&i===`bing`)throw new C(`INVALID_INPUT`,`Use --site with Bing or --target with Ahrefs or DataForSEO.`);if(c&&[`ahrefs`,`dataforseo`].includes(i??``))throw new C(`INVALID_INPUT`,`Use --target with Ahrefs or DataForSEO, or --site with Bing.`);let l=k(e),u=l?await U({project:l,options:{json:t}}):void 0;!n&&!u&&!s&&(u=await U({options:{json:t}}));let d;if(n)d=await Ve({file:n,format:r,rowLimit:o});else if(i===`ahrefs`||i===`dataforseo`||s){let t=s??u?.startUrl??u?.siteUrl.replace(/^sc-domain:/u,``);if(!t)throw new C(`INVALID_INPUT`,`Pass --target or use a saved project with a crawl URL or Search Console property.`);let n={target:t,scope:a,includeSubdomains:A(e[`include-subdomains`]),rowLimit:o,refresh:A(e.refresh),context:{projectId:u?.id,reportId:`link-evidence`,reportRunId:zr()}};d=i===`ahrefs`?await Ye(n):await ne(n)}else{let t=c??_e(u);if(!t)throw new C(`INVALID_INPUT`,`Pass --site, connect Bing to a saved project, or import a link file with --file.`);d=await Te({site:t,rowLimit:o,targetLimit:M(e[`target-limit`],`--target-limit`),detailPagesPerTarget:M(e[`detail-pages`],`--detail-pages`)})}let f=O(e[`search-site`])??u?.siteUrl,p=u||f||d.externalProvider?await pe({evidence:d,searchConsoleSite:f,crawlSite:u?.siteUrl??f??d.externalProvider?.summary.data.target,days:M(e.days,`--days`),refresh:A(e.refresh)}):void 0,m=Ne({evidence:d,limit:M(e.limit,`--limit`),targetContext:p});if(t){R(m);return}let h=m.providerEvidence?[m.providerEvidence.summary.cost.actualMicros,m.providerEvidence.backlinks.cost.actualMicros]:[],g=h.every(e=>e!==null)?h.reduce((e,t)=>e+t,0):null,_=m.providerEvidence?[m.providerEvidence.summary.cost.native?.actualUnits,m.providerEvidence.backlinks.cost.native?.actualUnits]:[],v=_.every(e=>e!=null)?_.reduce((e,t)=>e+t,0):null,y=m.providerEvidence?[m.providerEvidence.summary.cache.status,m.providerEvidence.backlinks.cache.status].every(e=>e===`hit`):!1;K({title:`Inbound link evidence`,target:m.provenance.provider,status:m.dataStatus===`complete`?`info`:`unknown`,summary:`${W(m.summary.observedLinks)} retained links from ${W(m.summary.referringDomains)} referring domains.`,metrics:[{label:`Evidence`,value:m.dataStatus},{label:`Observed links`,value:W(m.summary.observedLinks)},{label:`Referring domains`,value:W(m.summary.referringDomains)},{label:`Target pages`,value:W(m.summary.targetPages)},{label:`Returned rows`,value:`${W(m.selection.returnedRows)} of ${W(m.selection.availableRows)}`},...m.providerSummary?.backlinks.state===`observed`?[{label:`Provider backlinks`,value:W(m.providerSummary.backlinks.value)}]:[],...m.providerSummary?.referringDomains.state===`observed`?[{label:`Provider domains`,value:W(m.providerSummary.referringDomains.value)}]:[],...m.providerEvidence?[{label:`Provider cost`,value:g===null?v===null?`Unknown`:`${W(v)} API units${y?` (cached)`:``}`:`$${(g/1e6).toFixed(4)}${y?` (cached)`:``}`}]:[],...m.providerSummary?.brokenBacklinks.state===`observed`&&m.providerSummary.brokenBacklinks.value>0?[{label:`Provider broken links`,value:W(m.providerSummary.brokenBacklinks.value)}]:[]]}),m.findings.length&&q([`Priority`,`Finding`,`Target`],m.findings.map(e=>[e.priority,e.code,G(e.targetUrl,72)])),m.links.length&&q([`Source`,`Target`,`Anchor`],m.links.map(e=>[G(e.sourceUrl,58),G(e.targetUrl,58),G(e.anchorText??``,36)])),Y(`Warnings`,m.warnings),Y(`Report caveats`,m.caveats)}});async function Ou(e,t){if(!e){process.stdout.write(t);return}await li(Qr(e),{recursive:!0}),await _i(e,t),process.stdout.write(`Wrote ${e}\n`)}const ku={url:{type:`string`,description:`Public site or subpath URL to crawl.`},"report-id":{type:`string`,description:`Saved crawl report id to use.`},site:{type:`string`,description:`GSC property URL for selecting the latest saved crawl.`},client:{type:`string`,description:`Legacy alias for --project.`},project:{type:`string`,description:`Saved project id or name.`},"max-pages":{type:`string`,description:`Maximum public HTML pages to crawl.`},"fetch-interval-cap":{type:`string`,description:`Maximum page fetches per interval per host.`},"fetch-interval-ms":{type:`string`,description:`Fetch rate interval in milliseconds.`},refresh:{type:`boolean`,default:!1,description:`Bypass local HTTP cache and fetch fresh pages.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}};async function Au(e,t){let n=O(e.url),r=O(e[`report-id`]);if(n&&r)throw Error(`Use either --url or --report-id, not both.`);if(!n)return Vo(e,{json:t.json});let i=k(e),a=O(e.site),o=i||a?await H({client:i,site:a,options:{json:t.json}}):void 0;return it({url:n,projectId:o?.client?.id,site:o?.site,maxPages:j(e[`max-pages`]),fetchRate:Ba(e),refresh:A(e.refresh),useSitemap:!0,checkExternal:!1})}const ju=T({meta:{name:`llms`,description:`Check or generate an optional llms.txt file`},subCommands:{audit:T({meta:{name:`audit`,description:`Check an optional llms.txt file body and links`},args:ku,run:async({args:e})=>{let t=F(e),n=await Pe(await Au(e,{json:t}));if(t){R(n);return}K({title:`llms.txt audit`,target:n.url,status:n.issues.length>0?`warning`:n.dataStatus===`complete`?`pass`:`info`,summary:n.headline,metrics:[{label:`SEO impact`,value:n.googleSearchImpact},{label:`Optional`,value:n.optional?`Yes`:`No`},{label:`Found`,value:n.exists?`Yes`:`No`},{label:`URL`,value:n.llmsTxtUrl},{label:`HTTP status`,value:n.status?String(n.status):`Unavailable`}],diagnostics:[{title:`Issues`,items:n.issues.map(e=>({status:e.severity===`high`?`fail`:`warning`,title:e.title,explanation:e.plainEnglish,fix:e.action}))}]}),Y(`Caveats`,n.caveats),Y(`Recommended pages`,n.recommendedPages.slice(0,10).map(e=>`${e.section}: ${e.url}`))}}),generate:T({meta:{name:`generate`,description:`Generate a valid llms.txt v2 draft from crawl data`},args:{...ku,output:{type:`string`,description:`Write llms.txt to this path instead of stdout.`},"max-urls":{type:`string`,description:`Maximum URLs to include. Defaults to 250.`},"token-budget":{type:`string`,description:`Approximate token budget. Defaults to 12000.`},exclude:{type:`string`,description:`Comma-separated URL patterns to exclude.`},title:{type:`string`,description:`Override the llms.txt title.`},description:{type:`string`,description:`Override the llms.txt description.`}},run:async({args:e})=>{let t=F(e),n=He(await Au(e,{json:t}),{maxUrls:j(e[`max-urls`]),tokenBudget:j(e[`token-budget`]),maxBytes:t?65536:void 0,exclude:N(e.exclude),title:O(e.title),description:O(e.description)});if(t){R(n);return}await Ou(O(e.output),n.content),O(e.output)&&z([[`URLs`,String(n.includedUrls)],[`Estimated tokens`,String(n.estimatedTokens)],[`Truncated`,n.limits.truncated?n.limits.reasons.join(`, `):`No`]])}})}}),Mu=T({meta:{name:`logs`,description:`Manage local log retention`},subCommands:{prune:T({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=ke();if(F(e)){R(t);return}e.quiet||process.stdout.write(`Rotated ${t.rotated} and removed ${t.removed} log files. ${Fo(t.sizeBytes)} retained.\n`)}})}}),Nu=[{client:`claude-desktop`,flag:`claude-desktop`},{client:`claude-code`,flag:`claude-code`},{client:`codex`,flag:`codex`},{client:`cursor`,flag:`cursor`}];function Pu(e,t){return A(e.all)?t:Nu.filter(({flag:t})=>A(e[t])).map(({client:e})=>e)}const Fu=T({meta:{name:`mcp`,description:`MCP server helpers`},subCommands:{serve:T({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-RmyuHlmM.js`);await t({test:A(e.test)})}}),install:T({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(A(e.uninstall)&&A(e.reinstall))throw new C(`INVALID_INPUT`,`Use either --uninstall or --reinstall, not both.`);let t=Ms(),n=Fs({targets:t}),r=F(e),i=Pu(e,n.map(e=>e.client));if(i.length===0){if(!L({json:r}))throw new C(`INVALID_INPUT`,`Choose an MCP client with --claude-desktop, --claude-code, --codex, --cursor, or --all.`);let a=n.length>0?n:t;i=V(await Ai({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 C(`INVALID_INPUT`,`MCP setup is not supported for ${a.join(`, `)} on ${process.platform}.`);let o=t.filter(e=>i.includes(e.client)).map(t=>A(e.uninstall)?ec(t):$s(t,{reinstall:A(e.reinstall)}));if(r){R({operation:A(e.uninstall)?`uninstall`:`install`,results:o});return}for(let e of o)process.stdout.write(`${e.changed?`updated`:`skipped`} ${e.client} · ${e.path}\n`)}})}}),Iu=T({meta:{name:`crawl-diff`,description:`Crawl a site and diff technical/page changes vs last run`},args:{url:{type:`string`,required:!0,description:`Start URL to crawl.`},site:{type:`string`,description:`Optional GSC property to associate with the crawl.`},limit:{type:`string`,description:`Maximum pages to crawl. Defaults to 50.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`},refresh:{type:`boolean`,default:!1,description:`Bypass local HTTP cache.`},js:{type:`boolean`,default:!1,description:`Force JavaScript rendering when Playwright is installed.`}},run:async({args:e})=>{let t=await Oe({startUrl:O(e.url)??``,site:O(e.site),limit:j(e.limit),refresh:A(e.refresh),js:A(e.js)?!0:`auto`});if(F(e)){R(t);return}if(K({title:`Crawl history comparison`,target:t.run.startUrl,status:t.previousRun===void 0?`unknown`:t.summary.newErrors>0||t.summary.indexabilityFlips>0?`warning`:`pass`,summary:t.previousRun===void 0?`No previous crawl was available for comparison.`:`${t.summary.changed} changed, ${t.summary.added} added, and ${t.summary.removed} removed URLs.`,metrics:[{label:`Run`,value:t.run.id},{label:`Crawled`,value:t.summary.crawled},{label:`Previous run`,value:t.previousRun?.id??`None`},{label:`Added`,value:t.summary.added},{label:`Removed`,value:t.summary.removed},{label:`Changed`,value:t.summary.changed},{label:`New errors`,value:t.summary.newErrors},{label:`Indexability flips`,value:t.summary.indexabilityFlips},{label:`High priority`,value:t.summary.highPriorityRecommendations}]}),t.recommendations.length&&(process.stdout.write(`
172
172
  Recommended actions
173
- `),V([`Severity`,`Category`,`URL`,`Action`],t.recommendations.slice(0,10).map(e=>[e.severity,e.category,K(e.url,56),K(e.action,72)])),Y(`Top crawl actions`,t.recommendations.map(e=>({label:e.category,context:`${e.severity}, ${K(e.url,96)}`,action:e.action})))),t.items.length&&(process.stdout.write(`
173
+ `),B([`Severity`,`Category`,`URL`,`Action`],t.recommendations.slice(0,10).map(e=>[e.severity,e.category,G(e.url,56),G(e.action,72)])),J(`Top crawl actions`,t.recommendations.map(e=>({label:e.category,context:`${e.severity}, ${G(e.url,96)}`,action:e.action})))),t.items.length&&(process.stdout.write(`
174
174
  Changed URLs
175
- `),V([`Kind`,`URL`,`Changes`,`Recommendation`],t.items.slice(0,50).map(e=>[e.kind,K(e.url,56),e.changes.join(`, `),e.recommendation?.title??`-`]))),t.warnings.length){process.stdout.write(`
175
+ `),B([`Kind`,`URL`,`Changes`,`Recommendation`],t.items.slice(0,50).map(e=>[e.kind,G(e.url,56),e.changes.join(`, `),e.recommendation?.title??`-`]))),t.warnings.length){process.stdout.write(`
176
176
  Warnings
177
- `);for(let e of t.warnings.slice(0,10))process.stdout.write(`- ${e}\n`)}}});function Iu(){return{site:{type:`string`,description:`GSC property URL, for example sc-domain:example.com.`},client:{type:`string`,description:`Legacy alias for --project.`},project:{type:`string`,description:`Saved project id or name.`},url:{type:`string`,description:`Start URL to crawl. Defaults from the project or GSC property.`},urls:{type:`string`,description:`Comma-separated URLs to inspect with URL Inspection.`},sitemaps:{type:`string`,description:`Comma-separated XML sitemap URLs for quota-aware index monitoring.`},properties:{type:`string`,description:`Comma-separated GSC properties for sitemap index monitoring.`},limit:{type:`string`,description:`Maximum pages to crawl. Defaults to 50.`},"daily-limit":{type:`string`,description:`URL Inspection daily limit per property. Defaults to 2000.`},"inspect-limit":{type:`string`,description:`Maximum sitemap URLs to inspect in this run. Defaults to 100.`},"max-urls":{type:`string`,description:`Maximum sitemap URLs to load for monitoring. Defaults to 50000.`},language:{type:`string`,description:`Optional URL Inspection language code.`},"recover-links":Pa(`Check search-value GSC pages for broken, blocked, or poorly redirected URLs.`,`Skip link recovery checks.`),"recover-days":{type:`string`,description:`GSC lookback window for link recovery. Defaults to 90.`},"recover-limit":{type:`string`,description:`Maximum search-value pages to check for link recovery. Defaults to 10.`},"recover-min-clicks":{type:`string`,description:`Minimum clicks for link recovery candidates. Defaults to 1.`},"recover-min-impressions":{type:`string`,description:`Minimum impressions for link recovery candidates. Defaults to 100.`},refresh:{type:`boolean`,default:!1,description:`Bypass local HTTP/GSC cache where supported.`},js:{type:`boolean`,default:!1,description:`Force JavaScript rendering when Playwright is installed.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}}}function Lu(){return{site:{type:`string`,description:`GSC property URL, for example sc-domain:example.com.`},client:{type:`string`,description:`Legacy alias for --project.`},project:{type:`string`,description:`Saved project id or name.`},"stale-days":{type:`string`,description:`Mark checks stale after this many days. Defaults to 8.`},refresh:{type:`boolean`,default:!1,description:`Refresh property selection data when prompting.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}}}function Ru(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 zu(e){let t=e.steps.map(e=>Ru(e.status)),n=t.includes(`fail`)?`fail`:t.includes(`warning`)?`warning`:t.includes(`unknown`)?`unknown`:`pass`;Oo({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:Ru(e.status),title:e.tool,explanation:e.summary,evidence:[`Status: ${e.status}`]}))},{title:`Priority actions`,diagnostics:e.actions.map(e=>({status:`warning`,title:e.title,explanation:`Confidence: ${e.confidence}.`,fix:e.action}))}]})}function Bu(e){if(zu(e),e.output.crawl?.recommendations.length&&(process.stdout.write(`
177
+ `);for(let e of t.warnings.slice(0,10))process.stdout.write(`- ${e}\n`)}}});function Lu(){return{site:{type:`string`,description:`GSC property URL, for example sc-domain:example.com.`},client:{type:`string`,description:`Legacy alias for --project.`},project:{type:`string`,description:`Saved project id or name.`},url:{type:`string`,description:`Start URL to crawl. Defaults from the project or GSC property.`},urls:{type:`string`,description:`Comma-separated URLs to inspect with URL Inspection.`},sitemaps:{type:`string`,description:`Comma-separated XML sitemap URLs for quota-aware index monitoring.`},properties:{type:`string`,description:`Comma-separated GSC properties for sitemap index monitoring.`},limit:{type:`string`,description:`Maximum pages to crawl. Defaults to 50.`},"daily-limit":{type:`string`,description:`URL Inspection daily limit per property. Defaults to 2000.`},"inspect-limit":{type:`string`,description:`Maximum sitemap URLs to inspect in this run. Defaults to 100.`},"max-urls":{type:`string`,description:`Maximum sitemap URLs to load for monitoring. Defaults to 50000.`},language:{type:`string`,description:`Optional URL Inspection language code.`},"recover-links":Fa(`Check search-value GSC pages for broken, blocked, or poorly redirected URLs.`,`Skip link recovery checks.`),"recover-days":{type:`string`,description:`GSC lookback window for link recovery. Defaults to 90.`},"recover-limit":{type:`string`,description:`Maximum search-value pages to check for link recovery. Defaults to 10.`},"recover-min-clicks":{type:`string`,description:`Minimum clicks for link recovery candidates. Defaults to 1.`},"recover-min-impressions":{type:`string`,description:`Minimum impressions for link recovery candidates. Defaults to 100.`},refresh:{type:`boolean`,default:!1,description:`Bypass local HTTP/GSC cache where supported.`},js:{type:`boolean`,default:!1,description:`Force JavaScript rendering when Playwright is installed.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}}}function Ru(){return{site:{type:`string`,description:`GSC property URL, for example sc-domain:example.com.`},client:{type:`string`,description:`Legacy alias for --project.`},project:{type:`string`,description:`Saved project id or name.`},"stale-days":{type:`string`,description:`Mark checks stale after this many days. Defaults to 8.`},refresh:{type:`boolean`,default:!1,description:`Refresh property selection data when prompting.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}}}function zu(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 Bu(e){let t=e.steps.map(e=>zu(e.status)),n=t.includes(`fail`)?`fail`:t.includes(`warning`)?`warning`:t.includes(`unknown`)?`unknown`:`pass`;ko({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:zu(e.status),title:e.tool,explanation:e.summary,evidence:[`Status: ${e.status}`]}))},{title:`Priority actions`,diagnostics:e.actions.map(e=>({status:`warning`,title:e.title,explanation:`Confidence: ${e.confidence}.`,fix:e.action}))}]})}function Vu(e){if(Bu(e),e.output.crawl?.recommendations.length&&(process.stdout.write(`
178
178
  Crawl actions
179
- `),V([`Severity`,`URL`,`Action`],e.output.crawl.recommendations.slice(0,10).map(e=>[e.severity,K(e.url,56),K(e.action,72)])),Y(`Top crawl actions`,e.output.crawl.recommendations.map(e=>({label:e.severity,context:K(e.url,96),action:e.action})))),e.output.index?.items.length){let t=e.output.index.items.filter(e=>e.currentIssue||e.changed||e.inspectionStatus!==`succeeded`).slice(0,25);t.length&&(process.stdout.write(`
179
+ `),B([`Severity`,`URL`,`Action`],e.output.crawl.recommendations.slice(0,10).map(e=>[e.severity,G(e.url,56),G(e.action,72)])),J(`Top crawl actions`,e.output.crawl.recommendations.map(e=>({label:e.severity,context:G(e.url,96),action:e.action})))),e.output.index?.items.length){let t=e.output.index.items.filter(e=>e.currentIssue||e.changed||e.inspectionStatus!==`succeeded`).slice(0,25);t.length&&(process.stdout.write(`
180
180
  Index reviews, changes, and failed checks
181
- `),V([`Check`,`Change`,`Index state`,`Evidence`,`URL`],t.map(e=>[e.inspectionStatus,e.changeKind,e.indexStatus,K((e.errorCode??e.issueCodes.join(`, `))||`none`,40),K(e.url,64)])))}e.output.recovery?.items.length&&(process.stdout.write(`
181
+ `),B([`Check`,`Change`,`Index state`,`Evidence`,`URL`],t.map(e=>[e.inspectionStatus,e.changeKind,e.indexStatus,G((e.errorCode??e.issueCodes.join(`, `))||`none`,40),G(e.url,64)])))}e.output.recovery?.items.length&&(process.stdout.write(`
182
182
  Recoverable URLs
183
- `),V([`Severity`,`Issue`,`Clicks`,`Impr`,`URL`,`Action`],e.output.recovery.items.slice(0,10).map(e=>[e.severity,e.issue,G(e.clicks),G(e.impressions),K(e.url,56),K(e.recommendation.action,72)])),Y(`Top recovery actions`,e.output.recovery.items.map(e=>({label:K(e.url,96),context:`${e.severity}, ${G(e.clicks)} clicks at risk`,action:e.recommendation.action}))))}function Vu(e){q({title:`Technical monitoring status`,target:e.site,status:e.health===`clear`?`pass`:e.health===`attention`?`warning`:`unknown`,summary:e.summary.attention>0?`${e.summary.attention} checks need attention.`:e.summary.stale>0||e.summary.notRun>0?`${e.summary.stale} checks are stale and ${e.summary.notRun} have not run.`:`No monitoring checks currently need attention.`,metrics:[{label:`Health`,value:e.health},{label:`Attention`,value:e.summary.attention},{label:`Stale`,value:e.summary.stale},{label:`Not run`,value:e.summary.notRun}]}),V([`Check`,`Status`,`Last run`,`Summary`],e.checks.map(e=>[e.name,e.status,e.lastRunAt??`-`,e.summary]));let t=e.checks.filter(e=>e.action);t.length&&(process.stdout.write(`
183
+ `),B([`Severity`,`Issue`,`Clicks`,`Impr`,`URL`,`Action`],e.output.recovery.items.slice(0,10).map(e=>[e.severity,e.issue,W(e.clicks),W(e.impressions),G(e.url,56),G(e.recommendation.action,72)])),J(`Top recovery actions`,e.output.recovery.items.map(e=>({label:G(e.url,96),context:`${e.severity}, ${W(e.clicks)} clicks at risk`,action:e.recommendation.action}))))}function Hu(e){K({title:`Technical monitoring status`,target:e.site,status:e.health===`clear`?`pass`:e.health===`attention`?`warning`:`unknown`,summary:e.summary.attention>0?`${e.summary.attention} checks need attention.`:e.summary.stale>0||e.summary.notRun>0?`${e.summary.stale} checks are stale and ${e.summary.notRun} have not run.`:`No monitoring checks currently need attention.`,metrics:[{label:`Health`,value:e.health},{label:`Attention`,value:e.summary.attention},{label:`Stale`,value:e.summary.stale},{label:`Not run`,value:e.summary.notRun}]}),B([`Check`,`Status`,`Last run`,`Summary`],e.checks.map(e=>[e.name,e.status,e.lastRunAt??`-`,e.summary]));let t=e.checks.filter(e=>e.action);t.length&&(process.stdout.write(`
184
184
  Next actions
185
- `),V([`Check`,`Action`],t.map(e=>[e.name,K(e.action??``,96)])),Y(`Next action details`,t.map(e=>({label:e.name,action:e.action??``}))))}function Hu(e,t){let n=k(t);return n?`--${e} ${JSON.stringify(n)}`:void 0}function Uu(e,t){return j(t)?`--${e}`:void 0}function Wu(e){return j(e)===!1?`--recover-links=false`:void 0}const Gu=E({meta:{name:`monitoring`,description:`Run and review recurring technical SEO monitoring`},subCommands:{run:E({meta:{name:`run`,description:`Run crawl, index, and link recovery monitoring for a property`},args:Iu(),run:async({args:e})=>{let t=I(e),n=await U({client:A(e),site:k(e.site),options:{json:t,refresh:j(e.refresh)}}),r=F(e.urls),i=F(e.sitemaps),a=k(e.url)??n.client?.startUrl??bs(n.site),o=await Yt({site:n.site,startUrl:a,urls:r.length?r:n.client?.watchUrls,sitemaps:i.length?i:void 0,properties:F(e.properties).length?F(e.properties):void 0,limit:M(e.limit),languageCode:k(e.language),dailyLimit:M(e[`daily-limit`]),inspectLimit:M(e[`inspect-limit`]),maxUrls:M(e[`max-urls`]),refresh:j(e.refresh),js:j(e.js)?!0:`auto`,recoverLinks:j(e[`recover-links`]),recoverDays:M(e[`recover-days`]),recoverLimit:M(e[`recover-limit`]),recoverMinClicks:M(e[`recover-min-clicks`]),recoverMinImpressions:M(e[`recover-min-impressions`])});if(t){z(o);return}Bu(o)}}),status:E({meta:{name:`status`,description:`Show the latest saved monitoring state for a property`},args:Lu(),run:async({args:e})=>{let t=I(e),n=s({site:(await U({client:A(e),site:k(e.site),options:{json:t,refresh:j(e.refresh)}})).site,staleAfterDays:M(e[`stale-days`])});if(t){z(n);return}Vu(n)}}),cron:E({meta:{name:`cron`,description:`Print a local cron line for recurring monitoring`},args:{...Iu(),hour:{type:`string`,default:`9`,description:`Hour in local cron time. Defaults to 9.`},minute:{type:`string`,default:`0`,description:`Minute in local cron time. Defaults to 0.`},weekday:{type:`string`,description:`Weekly monitoring day. Defaults to client setting, then Monday.`}},run:async({args:e})=>{let t=I(e),n=await U({client:A(e),site:k(e.site),options:{json:t,refresh:j(e.refresh)}}),r=M(e.hour)??9,i=M(e.minute)??0,a=M(e.weekday)??n.client?.technicalWeekday??1,o=[`seo monitoring run`,n.client?`--project ${JSON.stringify(n.client.id)}`:`--site ${JSON.stringify(n.site)}`,Hu(`url`,e.url),Hu(`urls`,e.urls),Hu(`sitemaps`,e.sitemaps),Hu(`properties`,e.properties),Hu(`limit`,e.limit),Hu(`daily-limit`,e[`daily-limit`]),Hu(`inspect-limit`,e[`inspect-limit`]),Hu(`max-urls`,e[`max-urls`]),Hu(`language`,e.language),Hu(`recover-days`,e[`recover-days`]),Hu(`recover-limit`,e[`recover-limit`]),Hu(`recover-min-clicks`,e[`recover-min-clicks`]),Hu(`recover-min-impressions`,e[`recover-min-impressions`]),Wu(e[`recover-links`]),Uu(`refresh`,e.refresh),Uu(`js`,e.js),`--json`].filter(Boolean).join(` `),s={name:`monitoring`,cron:`${i} ${r} * * ${a}`,command:o};if(t){z({site:n.site,line:s});return}B([[`Property`,n.site],[`Install`,`crontab -e`]]),process.stdout.write(`${s.cron} ${s.command}\n`)}})}}),Ku=E({meta:{name:`index-coverage`,description:`Find crawlable pages that deserve a closer Google index check`},args:{project:{type:`string`,description:`Saved project id or name.`},client:{type:`string`,description:`Legacy alias for --project.`},site:{type:`string`,description:`GSC property URL, for example sc-domain:example.com.`},"crawl-report-id":{type:`string`,description:`Saved crawl report id. Defaults to the latest for the site.`},sitemaps:{type:`string`,description:`Comma-separated XML sitemap URLs. Defaults to declarations captured by the crawl.`},days:{type:`string`,description:`Finalized Search Console date range. Defaults to 90 days.`},"row-limit":{type:`string`,description:`Maximum Search Console page rows. Defaults to 100000.`},"max-sitemap-urls":{type:`string`,description:`Maximum sitemap inventory URLs. Defaults to 100000.`},limit:{type:`string`,description:`Maximum URLs returned in each evidence group. Defaults to 100.`},refresh:{type:`boolean`,default:!1,description:`Bypass the local Search Console cache.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=I(e),n=await U({client:A(e),site:k(e.site),options:{json:t,refresh:j(e.refresh)}}),r=F(e.sitemaps),i=await he({site:n.site,crawlReportId:k(e[`crawl-report-id`]),sitemaps:r.length?r:void 0,days:M(e.days),rowLimit:M(e[`row-limit`]),maxSitemapUrls:M(e[`max-sitemap-urls`]),itemsPerSection:M(e.limit),refresh:j(e.refresh)});if(t){z(i);return}q({title:`Index coverage`,target:i.site,status:i.sources.crawl.completeness===`complete`&&i.sources.searchConsole.completeness===`complete`?i.summary.crawlableCandidatesWithoutRetainedSearchVisibility>0?`warning`:`pass`:`unknown`,summary:`${i.summary.crawlableCandidatesWithoutRetainedSearchVisibility} crawlable pages need URL Inspection review.`,metrics:[{label:`Crawl report`,value:i.input.crawlReport.id},{label:`Crawl evidence`,value:i.sources.crawl.completeness},{label:`Sitemap evidence`,value:i.sources.sitemap.completeness},{label:`Search evidence`,value:i.sources.searchConsole.completeness},{label:`Search window`,value:`${i.sources.searchConsole.startDate} to ${i.sources.searchConsole.endDate}`},{label:`Search-visible pages`,value:i.summary.retainedSearchVisibleUrls},{label:`Blocked/non-indexable`,value:i.summary.blockedOrNonIndexableCrawlUrls},{label:`Sitemap only`,value:i.summary.sitemapOnlyUrls},{label:`Search Console only`,value:i.summary.searchConsoleOnlyUrls}]}),i.crawlableWithoutRetainedSearchVisibility.items.length&&(process.stdout.write(`
185
+ `),B([`Check`,`Action`],t.map(e=>[e.name,G(e.action??``,96)])),J(`Next action details`,t.map(e=>({label:e.name,action:e.action??``}))))}function Uu(e,t){let n=O(t);return n?`--${e} ${JSON.stringify(n)}`:void 0}function Wu(e,t){return A(t)?`--${e}`:void 0}function Gu(e){return A(e)===!1?`--recover-links=false`:void 0}const Ku=T({meta:{name:`monitoring`,description:`Run and review recurring technical SEO monitoring`},subCommands:{run:T({meta:{name:`run`,description:`Run crawl, index, and link recovery monitoring for a property`},args:Lu(),run:async({args:e})=>{let t=F(e),n=await H({client:k(e),site:O(e.site),options:{json:t,refresh:A(e.refresh)}}),r=P(e.urls),i=P(e.sitemaps),a=O(e.url)??n.client?.startUrl??xs(n.site),o=await Xt({site:n.site,startUrl:a,urls:r.length?r:n.client?.watchUrls,sitemaps:i.length?i:void 0,properties:P(e.properties).length?P(e.properties):void 0,limit:j(e.limit),languageCode:O(e.language),dailyLimit:j(e[`daily-limit`]),inspectLimit:j(e[`inspect-limit`]),maxUrls:j(e[`max-urls`]),refresh:A(e.refresh),js:A(e.js)?!0:`auto`,recoverLinks:A(e[`recover-links`]),recoverDays:j(e[`recover-days`]),recoverLimit:j(e[`recover-limit`]),recoverMinClicks:j(e[`recover-min-clicks`]),recoverMinImpressions:j(e[`recover-min-impressions`])});if(t){R(o);return}Vu(o)}}),status:T({meta:{name:`status`,description:`Show the latest saved monitoring state for a property`},args:Ru(),run:async({args:e})=>{let t=F(e),n=s({site:(await H({client:k(e),site:O(e.site),options:{json:t,refresh:A(e.refresh)}})).site,staleAfterDays:j(e[`stale-days`])});if(t){R(n);return}Hu(n)}}),cron:T({meta:{name:`cron`,description:`Print a local cron line for recurring monitoring`},args:{...Lu(),hour:{type:`string`,default:`9`,description:`Hour in local cron time. Defaults to 9.`},minute:{type:`string`,default:`0`,description:`Minute in local cron time. Defaults to 0.`},weekday:{type:`string`,description:`Weekly monitoring day. Defaults to client setting, then Monday.`}},run:async({args:e})=>{let t=F(e),n=await H({client:k(e),site:O(e.site),options:{json:t,refresh:A(e.refresh)}}),r=j(e.hour)??9,i=j(e.minute)??0,a=j(e.weekday)??n.client?.technicalWeekday??1,o=[`seo monitoring run`,n.client?`--project ${JSON.stringify(n.client.id)}`:`--site ${JSON.stringify(n.site)}`,Uu(`url`,e.url),Uu(`urls`,e.urls),Uu(`sitemaps`,e.sitemaps),Uu(`properties`,e.properties),Uu(`limit`,e.limit),Uu(`daily-limit`,e[`daily-limit`]),Uu(`inspect-limit`,e[`inspect-limit`]),Uu(`max-urls`,e[`max-urls`]),Uu(`language`,e.language),Uu(`recover-days`,e[`recover-days`]),Uu(`recover-limit`,e[`recover-limit`]),Uu(`recover-min-clicks`,e[`recover-min-clicks`]),Uu(`recover-min-impressions`,e[`recover-min-impressions`]),Gu(e[`recover-links`]),Wu(`refresh`,e.refresh),Wu(`js`,e.js),`--json`].filter(Boolean).join(` `),s={name:`monitoring`,cron:`${i} ${r} * * ${a}`,command:o};if(t){R({site:n.site,line:s});return}z([[`Property`,n.site],[`Install`,`crontab -e`]]),process.stdout.write(`${s.cron} ${s.command}\n`)}})}}),qu=T({meta:{name:`index-coverage`,description:`Find crawlable pages that deserve a closer Google index check`},args:{project:{type:`string`,description:`Saved project id or name.`},client:{type:`string`,description:`Legacy alias for --project.`},site:{type:`string`,description:`GSC property URL, for example sc-domain:example.com.`},"crawl-report-id":{type:`string`,description:`Saved crawl report id. Defaults to the latest for the site.`},sitemaps:{type:`string`,description:`Comma-separated XML sitemap URLs. Defaults to declarations captured by the crawl.`},days:{type:`string`,description:`Finalized Search Console date range. Defaults to 90 days.`},"row-limit":{type:`string`,description:`Maximum Search Console page rows. Defaults to 100000.`},"max-sitemap-urls":{type:`string`,description:`Maximum sitemap inventory URLs. Defaults to 100000.`},limit:{type:`string`,description:`Maximum URLs returned in each evidence group. Defaults to 100.`},refresh:{type:`boolean`,default:!1,description:`Bypass the local Search Console cache.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=F(e),n=await H({client:k(e),site:O(e.site),options:{json:t,refresh:A(e.refresh)}}),r=P(e.sitemaps),i=await ge({site:n.site,crawlReportId:O(e[`crawl-report-id`]),sitemaps:r.length?r:void 0,days:j(e.days),rowLimit:j(e[`row-limit`]),maxSitemapUrls:j(e[`max-sitemap-urls`]),itemsPerSection:j(e.limit),refresh:A(e.refresh)});if(t){R(i);return}K({title:`Index coverage`,target:i.site,status:i.sources.crawl.completeness===`complete`&&i.sources.searchConsole.completeness===`complete`?i.summary.crawlableCandidatesWithoutRetainedSearchVisibility>0?`warning`:`pass`:`unknown`,summary:`${i.summary.crawlableCandidatesWithoutRetainedSearchVisibility} crawlable pages need URL Inspection review.`,metrics:[{label:`Crawl report`,value:i.input.crawlReport.id},{label:`Crawl evidence`,value:i.sources.crawl.completeness},{label:`Sitemap evidence`,value:i.sources.sitemap.completeness},{label:`Search evidence`,value:i.sources.searchConsole.completeness},{label:`Search window`,value:`${i.sources.searchConsole.startDate} to ${i.sources.searchConsole.endDate}`},{label:`Search-visible pages`,value:i.summary.retainedSearchVisibleUrls},{label:`Blocked/non-indexable`,value:i.summary.blockedOrNonIndexableCrawlUrls},{label:`Sitemap only`,value:i.summary.sitemapOnlyUrls},{label:`Search Console only`,value:i.summary.searchConsoleOnlyUrls}]}),i.crawlableWithoutRetainedSearchVisibility.items.length&&(process.stdout.write(`
186
186
  Representative URL Inspection candidates
187
- `),V([`URL`,`Status`,`In sitemap`],i.crawlableWithoutRetainedSearchVisibility.items.map(e=>[K(e.url,80),e.status,e.inSitemap?`yes`:`no`]))),X(`How to read this report`,i.caveats)}}),qu=E({meta:{name:`index-watch`,description:`Inspect URLs with GSC and alert on index status changes`},args:{site:{type:`string`,description:`GSC property URL, for example sc-domain:example.com.`},urls:{type:`string`,description:`Comma-separated URLs to inspect.`},sitemaps:{type:`string`,description:`Comma-separated XML sitemap URLs to plan quota-aware monitoring.`},plan:{type:`boolean`,default:!1,description:`Plan sitemap URL allocation across GSC properties without inspecting URLs.`},properties:{type:`string`,description:`Comma-separated GSC properties to use for planning. Defaults to account properties.`},"daily-limit":{type:`string`,description:`URL Inspection daily limit per property. Defaults to 2000.`},"target-days":{type:`string`,description:`Target number of days to inspect all monitored URLs. Defaults to 1.`},"max-urls":{type:`string`,description:`Maximum sitemap URLs to load for planning. Defaults to 50000.`},"inspect-limit":{type:`string`,description:`Maximum URLs to inspect in this run from sitemap mode. Defaults to 100.`},language:{type:`string`,description:`Optional URL Inspection language code.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=I(e),n=F(e.urls),r=await Ro({site:k(e.site),options:{json:t}}),i=F(e.sitemaps);if(j(e.plan)){if(!i.length)throw Error(`Pass at least one sitemap with --sitemaps.`);let n=await C({site:r,sitemaps:i,properties:F(e.properties).length?F(e.properties):void 0,dailyLimit:M(e[`daily-limit`]),targetCycleDays:M(e[`target-days`]),maxUrls:M(e[`max-urls`])});if(t){z(n);return}q({title:`Index monitoring plan`,target:n.site,status:n.warnings.length>0?`warning`:`info`,summary:`${n.summary.urlCount} sitemap URLs can be checked in an estimated ${Dn(n.summary.estimatedCycleDays,`day`)}.`,metrics:[{label:`Sitemap URLs`,value:n.summary.urlCount},{label:`Properties`,value:n.summary.properties},{label:`Daily capacity`,value:n.summary.dailyCapacity},{label:`Target cycle`,value:Dn(n.summary.targetCycleDays,`day`)},{label:`Suggested properties`,value:n.summary.suggestedProperties}]}),n.properties.length&&V([`Property`,`URLs`,`Cycle`,`Sample URL`],n.properties.map(e=>[e.property,e.urlCount,Dn(e.cycleDays,`day`),e.sampleUrls[0]??`-`])),n.suggestions.length&&(process.stdout.write(`
187
+ `),B([`URL`,`Status`,`In sitemap`],i.crawlableWithoutRetainedSearchVisibility.items.map(e=>[G(e.url,80),e.status,e.inSitemap?`yes`:`no`]))),Y(`How to read this report`,i.caveats)}}),Ju=T({meta:{name:`index-watch`,description:`Inspect URLs with GSC and alert on index status changes`},args:{site:{type:`string`,description:`GSC property URL, for example sc-domain:example.com.`},urls:{type:`string`,description:`Comma-separated URLs to inspect.`},sitemaps:{type:`string`,description:`Comma-separated XML sitemap URLs to plan quota-aware monitoring.`},plan:{type:`boolean`,default:!1,description:`Plan sitemap URL allocation across GSC properties without inspecting URLs.`},properties:{type:`string`,description:`Comma-separated GSC properties to use for planning. Defaults to account properties.`},"daily-limit":{type:`string`,description:`URL Inspection daily limit per property. Defaults to 2000.`},"target-days":{type:`string`,description:`Target number of days to inspect all monitored URLs. Defaults to 1.`},"max-urls":{type:`string`,description:`Maximum sitemap URLs to load for planning. Defaults to 50000.`},"inspect-limit":{type:`string`,description:`Maximum URLs to inspect in this run from sitemap mode. Defaults to 100.`},language:{type:`string`,description:`Optional URL Inspection language code.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=F(e),n=P(e.urls),r=await zo({site:O(e.site),options:{json:t}}),i=P(e.sitemaps);if(A(e.plan)){if(!i.length)throw Error(`Pass at least one sitemap with --sitemaps.`);let n=await S({site:r,sitemaps:i,properties:P(e.properties).length?P(e.properties):void 0,dailyLimit:j(e[`daily-limit`]),targetCycleDays:j(e[`target-days`]),maxUrls:j(e[`max-urls`])});if(t){R(n);return}K({title:`Index monitoring plan`,target:n.site,status:n.warnings.length>0?`warning`:`info`,summary:`${n.summary.urlCount} sitemap URLs can be checked in an estimated ${On(n.summary.estimatedCycleDays,`day`)}.`,metrics:[{label:`Sitemap URLs`,value:n.summary.urlCount},{label:`Properties`,value:n.summary.properties},{label:`Daily capacity`,value:n.summary.dailyCapacity},{label:`Target cycle`,value:On(n.summary.targetCycleDays,`day`)},{label:`Suggested properties`,value:n.summary.suggestedProperties}]}),n.properties.length&&B([`Property`,`URLs`,`Cycle`,`Sample URL`],n.properties.map(e=>[e.property,e.urlCount,On(e.cycleDays,`day`),e.sampleUrls[0]??`-`])),n.suggestions.length&&(process.stdout.write(`
188
188
  Suggested URL-prefix properties
189
- `),V([`Property`,`URLs`,`Current property`,`Cycle`,`Reason`],n.suggestions.map(e=>[e.property,e.urlCount,e.currentProperty,Dn(e.estimatedCycleDays,`day`),e.reason]))),X(`Warnings`,n.warnings.slice(0,10));return}if(i.length){let n=await Fe({site:r,sitemaps:i,properties:F(e.properties).length?F(e.properties):void 0,dailyLimit:M(e[`daily-limit`]),inspectLimit:M(e[`inspect-limit`]),maxUrls:M(e[`max-urls`]),languageCode:k(e.language)});if(t){z(n);return}q({title:`Index monitoring run`,target:n.site,status:n.summary.failed>0||n.summary.quotaBlocked>0?`unknown`:n.summary.alerts>0||n.summary.regressions>0?`warning`:n.summary.attempted>0?`pass`:`info`,summary:`${n.summary.inspected} URLs inspected, with ${n.summary.alerts} new alerts and ${n.summary.regressions} regressions.`,metrics:[{label:`Inventory URLs`,value:n.summary.inventoryUrls},{label:`Selected`,value:n.summary.selected},{label:`Inspected`,value:n.summary.inspected},{label:`Failed`,value:n.summary.failed},{label:`Quota blocked`,value:n.summary.quotaBlocked},{label:`Current reviews`,value:n.summary.currentIssues},{label:`Regressions`,value:n.summary.regressions},{label:`Recoveries`,value:n.summary.recoveries},{label:`New alerts`,value:n.summary.alerts}]}),n.properties.length&&V([`Property`,`Inventory`,`Selected`,`Inspected`,`Failed`,`Current`,`Alerts`],n.properties.map(e=>[e.property,e.inventoryUrls,e.selectedUrls,e.inspected,e.failed+e.quotaBlocked,e.currentIssues,e.alerts]));let a=n.items.filter(e=>e.currentIssue||e.changed||e.inspectionStatus!==`succeeded`).slice(0,50);a.length&&(process.stdout.write(`
189
+ `),B([`Property`,`URLs`,`Current property`,`Cycle`,`Reason`],n.suggestions.map(e=>[e.property,e.urlCount,e.currentProperty,On(e.estimatedCycleDays,`day`),e.reason]))),Y(`Warnings`,n.warnings.slice(0,10));return}if(i.length){let n=await Ie({site:r,sitemaps:i,properties:P(e.properties).length?P(e.properties):void 0,dailyLimit:j(e[`daily-limit`]),inspectLimit:j(e[`inspect-limit`]),maxUrls:j(e[`max-urls`]),languageCode:O(e.language)});if(t){R(n);return}K({title:`Index monitoring run`,target:n.site,status:n.summary.failed>0||n.summary.quotaBlocked>0?`unknown`:n.summary.alerts>0||n.summary.regressions>0?`warning`:n.summary.attempted>0?`pass`:`info`,summary:`${n.summary.inspected} URLs inspected, with ${n.summary.alerts} new alerts and ${n.summary.regressions} regressions.`,metrics:[{label:`Inventory URLs`,value:n.summary.inventoryUrls},{label:`Selected`,value:n.summary.selected},{label:`Inspected`,value:n.summary.inspected},{label:`Failed`,value:n.summary.failed},{label:`Quota blocked`,value:n.summary.quotaBlocked},{label:`Current reviews`,value:n.summary.currentIssues},{label:`Regressions`,value:n.summary.regressions},{label:`Recoveries`,value:n.summary.recoveries},{label:`New alerts`,value:n.summary.alerts}]}),n.properties.length&&B([`Property`,`Inventory`,`Selected`,`Inspected`,`Failed`,`Current`,`Alerts`],n.properties.map(e=>[e.property,e.inventoryUrls,e.selectedUrls,e.inspected,e.failed+e.quotaBlocked,e.currentIssues,e.alerts]));let a=n.items.filter(e=>e.currentIssue||e.changed||e.inspectionStatus!==`succeeded`).slice(0,50);a.length&&(process.stdout.write(`
190
190
  Index reviews, changes, and failed checks
191
- `),V([`Check`,`Change`,`Index state`,`Evidence`,`URL`],a.map(e=>[e.inspectionStatus,e.changeKind,e.indexStatus,(e.errorCode??e.issueCodes.join(`, `))||`none`,e.url]))),X(`Warnings`,n.warnings.slice(0,10));return}if(!n.length)throw Error(`Pass at least one URL with --urls.`);let a=await Ue({site:r,urls:n,languageCode:k(e.language),dailyLimit:M(e[`daily-limit`])});if(t){z(a);return}q({title:`Index watch`,target:a.site,status:a.summary.failed>0||a.summary.quotaBlocked>0?`unknown`:a.summary.alerts>0||a.summary.regressions>0?`warning`:a.summary.attempted>0?`pass`:`info`,summary:`${a.summary.inspected} URLs inspected, with ${a.summary.alerts} new alerts.`,metrics:[{label:`Property`,value:a.source.property},{label:`Attempted`,value:a.summary.attempted},{label:`Inspected`,value:a.summary.inspected},{label:`Failed`,value:a.summary.failed},{label:`Quota blocked`,value:a.summary.quotaBlocked},{label:`Current reviews`,value:a.summary.currentIssues},{label:`Regressions`,value:a.summary.regressions},{label:`Recoveries`,value:a.summary.recoveries},{label:`New alerts`,value:a.summary.alerts}]}),V([`Check`,`Change`,`Index state`,`Evidence`,`URL`],a.items.map(e=>[e.inspectionStatus,e.changeKind,e.indexStatus,(e.errorCode??e.issueCodes.join(`, `))||`none`,e.url]))}}),Ju=E({meta:{name:`link-recover`,description:`Find search-value URLs that are now broken, blocked, or poorly redirected`},args:{site:{type:`string`},project:{type:`string`,description:`Saved project id or name.`},client:{type:`string`,description:`Legacy alias for --project.`},...Z([`days`,`limit`,`minImpressions`,`refresh`,`js`],{days:{description:`GSC lookback window. Defaults to 90.`},limit:{description:`Maximum GSC pages to check. Defaults to 50.`},minImpressions:{description:`Minimum impressions to check a page. Defaults to 100.`},refresh:{description:`Bypass local GSC and HTTP caches.`},js:{description:`Force JavaScript rendering for final page extraction.`}}),"min-clicks":{type:`string`,description:`Minimum clicks to check a page. Defaults to 1.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=I(e),n=await xn({site:await ys({client:A(e),site:k(e.site)},{json:t}),days:M(e.days),limit:M(e.limit),minClicks:M(e[`min-clicks`]),minImpressions:M(e[`min-impressions`]),refresh:j(e.refresh),js:j(e.js)?!0:`auto`});if(t){z(n);return}q({title:`Link recovery`,target:n.site,status:n.warnings.length>0?`unknown`:n.summary.recoverable>0?`warning`:`pass`,summary:n.summary.recoverable>0?`${G(n.summary.recoverable)} previously visible URLs need recovery review.`:`No recoverable URLs were found in the retained evidence.`,metrics:[{label:`Range`,value:`${n.range.startDate} to ${n.range.endDate}`},{label:`Checked`,value:G(n.summary.checked)},{label:`Recoverable`,value:G(n.summary.recoverable)},{label:`High severity`,value:G(n.summary.high)},{label:`Clicks at risk`,value:G(n.summary.clicksAtRisk)},{label:`Impressions at risk`,value:G(n.summary.impressionsAtRisk)}]}),n.items.length&&(J([`Severity`,`Issue`,`Clicks`,`Impr`,`URL`,`Action`],n.items.map(e=>[e.severity,e.issue,G(e.clicks),G(e.impressions),K(e.url,56),K(e.recommendation.action,72)])),Y(`Top recovery actions`,n.items.map(e=>({label:K(e.url,96),context:`${e.severity}, ${G(e.clicks)} clicks at risk`,action:e.recommendation.action})))),X(`Warnings`,n.warnings.slice(0,10))}}),Yu=E({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 je({url:k(e.url)??``,maxHops:M(e[`max-hops`]),refresh:j(e.refresh),js:j(e.js)?!0:`auto`});if(I(e)){z(t);return}q({title:`Redirect trace`,target:t.url,status:t.summary.issues.length>0?`warning`:t.summary.finalIndexable===void 0?`unknown`:`pass`,summary:t.summary.issues.length>0?t.summary.issues.join(`, `):`${t.summary.hops} redirect hops ended at HTTP ${t.summary.finalStatus}.`,metrics:[{label:`Final URL`,value:t.finalUrl},{label:`Final status`,value:t.summary.finalStatus},{label:`Indexable`,value:t.summary.finalIndexable===void 0?`Unknown`:t.summary.finalIndexable?`Yes`:`No`},{label:`Hops`,value:t.summary.hops}]}),V([`#`,`Status`,`URL`,`Next`],t.chain.map((e,t)=>[t+1,e.status,K(e.url,72),K(e.nextUrl??`-`,72)])),t.finalPage&&(process.stdout.write(`
191
+ `),B([`Check`,`Change`,`Index state`,`Evidence`,`URL`],a.map(e=>[e.inspectionStatus,e.changeKind,e.indexStatus,(e.errorCode??e.issueCodes.join(`, `))||`none`,e.url]))),Y(`Warnings`,n.warnings.slice(0,10));return}if(!n.length)throw Error(`Pass at least one URL with --urls.`);let a=await We({site:r,urls:n,languageCode:O(e.language),dailyLimit:j(e[`daily-limit`])});if(t){R(a);return}K({title:`Index watch`,target:a.site,status:a.summary.failed>0||a.summary.quotaBlocked>0?`unknown`:a.summary.alerts>0||a.summary.regressions>0?`warning`:a.summary.attempted>0?`pass`:`info`,summary:`${a.summary.inspected} URLs inspected, with ${a.summary.alerts} new alerts.`,metrics:[{label:`Property`,value:a.source.property},{label:`Attempted`,value:a.summary.attempted},{label:`Inspected`,value:a.summary.inspected},{label:`Failed`,value:a.summary.failed},{label:`Quota blocked`,value:a.summary.quotaBlocked},{label:`Current reviews`,value:a.summary.currentIssues},{label:`Regressions`,value:a.summary.regressions},{label:`Recoveries`,value:a.summary.recoveries},{label:`New alerts`,value:a.summary.alerts}]}),B([`Check`,`Change`,`Index state`,`Evidence`,`URL`],a.items.map(e=>[e.inspectionStatus,e.changeKind,e.indexStatus,(e.errorCode??e.issueCodes.join(`, `))||`none`,e.url]))}}),Yu=T({meta:{name:`link-recover`,description:`Find search-value URLs that are now broken, blocked, or poorly redirected`},args:{site:{type:`string`},project:{type:`string`,description:`Saved project id or name.`},client:{type:`string`,description:`Legacy alias for --project.`},...X([`days`,`limit`,`minImpressions`,`refresh`,`js`],{days:{description:`GSC lookback window. Defaults to 90.`},limit:{description:`Maximum GSC pages to check. Defaults to 50.`},minImpressions:{description:`Minimum impressions to check a page. Defaults to 100.`},refresh:{description:`Bypass local GSC and HTTP caches.`},js:{description:`Force JavaScript rendering for final page extraction.`}}),"min-clicks":{type:`string`,description:`Minimum clicks to check a page. Defaults to 1.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=F(e),n=await Sn({site:await bs({client:k(e),site:O(e.site)},{json:t}),days:j(e.days),limit:j(e.limit),minClicks:j(e[`min-clicks`]),minImpressions:j(e[`min-impressions`]),refresh:A(e.refresh),js:A(e.js)?!0:`auto`});if(t){R(n);return}K({title:`Link recovery`,target:n.site,status:n.warnings.length>0?`unknown`:n.summary.recoverable>0?`warning`:`pass`,summary:n.summary.recoverable>0?`${W(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:W(n.summary.checked)},{label:`Recoverable`,value:W(n.summary.recoverable)},{label:`High severity`,value:W(n.summary.high)},{label:`Clicks at risk`,value:W(n.summary.clicksAtRisk)},{label:`Impressions at risk`,value:W(n.summary.impressionsAtRisk)}]}),n.items.length&&(q([`Severity`,`Issue`,`Clicks`,`Impr`,`URL`,`Action`],n.items.map(e=>[e.severity,e.issue,W(e.clicks),W(e.impressions),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}, ${W(e.clicks)} clicks at risk`,action:e.recommendation.action})))),Y(`Warnings`,n.warnings.slice(0,10))}}),Xu=T({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 Me({url:O(e.url)??``,maxHops:j(e[`max-hops`]),refresh:A(e.refresh),js:A(e.js)?!0:`auto`});if(F(e)){R(t);return}K({title:`Redirect trace`,target:t.url,status:t.summary.issues.length>0?`warning`:t.summary.finalIndexable===void 0?`unknown`:`pass`,summary:t.summary.issues.length>0?t.summary.issues.join(`, `):`${t.summary.hops} redirect hops ended at HTTP ${t.summary.finalStatus}.`,metrics:[{label:`Final URL`,value:t.finalUrl},{label:`Final status`,value:t.summary.finalStatus},{label:`Indexable`,value:t.summary.finalIndexable===void 0?`Unknown`:t.summary.finalIndexable?`Yes`:`No`},{label:`Hops`,value:t.summary.hops}]}),B([`#`,`Status`,`URL`,`Next`],t.chain.map((e,t)=>[t+1,e.status,G(e.url,72),G(e.nextUrl??`-`,72)])),t.finalPage&&(process.stdout.write(`
192
192
  Final page
193
- `),B([[`Title`,t.finalPage.title??`-`],[`Canonical`,t.finalPage.canonical??`-`],[`Meta robots`,t.finalPage.metaRobots??`-`]])),X(`Warnings`,t.warnings.slice(0,10))}});function Xu(e,t){process.stdout.write(`OKF validation for ${e}\n\n`),B([[`Version`,t.formatVersion??`not declared`],[`Compatibility`,t.compatibility],[`Profile`,t.profile],[`Valid`,t.valid?`yes`:`no`],[`Files`,String(t.files)],[`Concepts`,String(t.concepts)],[`Errors`,String(t.issueCounts.errors)],[`Warnings`,String(t.issueCounts.warnings)],[`With sources`,String(t.provenance.sources)],[`Generated`,String(t.generation.generated)],[`Human reviewed`,String(t.trust.humanReviewed)],[`Machine confirmed`,String(t.trust.machineConfirmed)],[`Unverified`,String(t.trust.unverified)],[`Draft`,String(t.lifecycle.draft)],[`Stable`,String(t.lifecycle.stable)],[`Deprecated`,String(t.lifecycle.deprecated)],[`Fresh`,String(t.freshness.fresh)],[`Stale`,String(t.freshness.stale)],[`Freshness unspecified`,String(t.freshness.unspecified)],[`Attested computations`,String(t.attestation.concepts)],[`Complete attestation contracts`,String(t.attestation.completeContracts)],[`Incomplete attestation contracts`,String(t.attestation.incompleteContracts)],[`Inline computations`,String(t.attestation.inlineComputations)],[`File computations`,String(t.attestation.fileComputations)]]),t.issues.length&&(process.stdout.write(`
193
+ `),z([[`Title`,t.finalPage.title??`-`],[`Canonical`,t.finalPage.canonical??`-`],[`Meta robots`,t.finalPage.metaRobots??`-`]])),Y(`Warnings`,t.warnings.slice(0,10))}});function Zu(e,t){process.stdout.write(`OKF validation for ${e}\n\n`),z([[`Version`,t.formatVersion??`not declared`],[`Compatibility`,t.compatibility],[`Profile`,t.profile],[`Valid`,t.valid?`yes`:`no`],[`Files`,String(t.files)],[`Concepts`,String(t.concepts)],[`Errors`,String(t.issueCounts.errors)],[`Warnings`,String(t.issueCounts.warnings)],[`With sources`,String(t.provenance.sources)],[`Generated`,String(t.generation.generated)],[`Human reviewed`,String(t.trust.humanReviewed)],[`Machine confirmed`,String(t.trust.machineConfirmed)],[`Unverified`,String(t.trust.unverified)],[`Draft`,String(t.lifecycle.draft)],[`Stable`,String(t.lifecycle.stable)],[`Deprecated`,String(t.lifecycle.deprecated)],[`Fresh`,String(t.freshness.fresh)],[`Stale`,String(t.freshness.stale)],[`Freshness unspecified`,String(t.freshness.unspecified)],[`Attested computations`,String(t.attestation.concepts)],[`Complete attestation contracts`,String(t.attestation.completeContracts)],[`Incomplete attestation contracts`,String(t.attestation.incompleteContracts)],[`Inline computations`,String(t.attestation.inlineComputations)],[`File computations`,String(t.attestation.fileComputations)]]),t.issues.length&&(process.stdout.write(`
194
194
  Issues
195
- `),V([`Severity`,`Path`,`Message`],t.issues.slice(0,25).map(e=>[e.severity,e.path,e.message]))),t.issuesTruncated&&process.stdout.write(`\n${t.omittedIssues} more validation issues were omitted.\n`)}function Zu(e){let t=k(e)??`okf`;if(t===`okf`||t===`seo-export`)return t;throw new w(`INVALID_INPUT`,`Profile must be okf or seo-export.`)}const Qu=E({meta:{name:`okf`,description:`Export, validate, and explain OKF site knowledge bundles`},subCommands:{export:E({meta:{name:`export`,description:`Export a saved crawl as an OKF bundle`},args:{"report-id":{type:`string`,description:`Saved crawl report id to export.`},site:{type:`string`,description:`GSC property URL for selecting the latest saved crawl.`},client:{type:`string`,description:`Legacy alias for --project.`},project:{type:`string`,description:`Saved project id or name.`},output:{type:`string`,description:`Output directory. Defaults to ./okf.`},"max-concepts":{type:`string`,description:`Maximum page concepts to export. Defaults to 500.`},title:{type:`string`,description:`Override the root bundle title.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=I(e),n=$n(N(e[`max-concepts`],`--max-concepts`)),r=In(await Bo(e,{json:t}),{maxConcepts:n,title:k(e.title)}),i=k(e.output)??`./okf`,a=await lu(i,r.files);if(t){z({output:i,bundle:r,validation:a});return}process.stdout.write(`Wrote OKF bundle to ${i}\n\n`),B([[`Files`,String(r.files.length)],[`Concepts`,String(r.conceptCount)],[`Page concepts`,String(r.pageConceptCount)],[`Valid`,a.valid?`yes`:`no`]])}}),validate:E({meta:{name:`validate`,description:`Validate an OKF bundle directory`},args:{path:{type:`positional`,required:!0,description:`OKF bundle directory.`},profile:{type:`string`,description:`Validation profile: okf or seo-export. Defaults to okf.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=k(e.path);if(!t)throw Error(`Pass an OKF bundle directory.`);let n=Ut(await su(t),{profile:Zu(e.profile)});if(n.valid||(process.exitCode=1),I(e)){z(n);return}Xu(t,n)}}),explain:E({meta:{name:`explain`,description:`Explain OKF validation results in plain English`},args:{path:{type:`positional`,required:!0,description:`OKF bundle directory.`},profile:{type:`string`,description:`Validation profile: okf or seo-export. Defaults to okf.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=k(e.path);if(!t)throw Error(`Pass an OKF bundle directory.`);let n=ir(Ut(await su(t),{profile:Zu(e.profile)}));if(n.valid||(process.exitCode=1),I(e)){z(n);return}if(process.stdout.write(`${n.summary}\n\n`),B([[`Version`,n.formatVersion??`not declared`],[`Compatibility`,n.compatibility],[`Profile`,n.profile],[`Valid`,n.valid?`yes`:`no`],[`Files`,String(n.files)],[`Concepts`,String(n.concepts)],[`Errors`,String(n.errors)],[`Warnings`,String(n.warnings)],[`With sources`,String(n.provenance.sources)],[`Generated`,String(n.generation.generated)],[`Human reviewed`,String(n.trust.humanReviewed)],[`Machine confirmed`,String(n.trust.machineConfirmed)],[`Unverified`,String(n.trust.unverified)],[`Draft`,String(n.lifecycle.draft)],[`Stable`,String(n.lifecycle.stable)],[`Deprecated`,String(n.lifecycle.deprecated)],[`Fresh`,String(n.freshness.fresh)],[`Stale`,String(n.freshness.stale)],[`Freshness unspecified`,String(n.freshness.unspecified)],[`Attested computations`,String(n.attestation.concepts)],[`Complete attestation contracts`,String(n.attestation.completeContracts)],[`Incomplete attestation contracts`,String(n.attestation.incompleteContracts)],[`Inline computations`,String(n.attestation.inlineComputations)],[`File computations`,String(n.attestation.fileComputations)]]),n.nextActions.length){process.stdout.write(`
195
+ `),B([`Severity`,`Path`,`Message`],t.issues.slice(0,25).map(e=>[e.severity,e.path,e.message]))),t.issuesTruncated&&process.stdout.write(`\n${t.omittedIssues} more validation issues were omitted.\n`)}function Qu(e){let t=O(e)??`okf`;if(t===`okf`||t===`seo-export`)return t;throw new C(`INVALID_INPUT`,`Profile must be okf or seo-export.`)}const $u=T({meta:{name:`okf`,description:`Export, validate, and explain OKF site knowledge bundles`},subCommands:{export:T({meta:{name:`export`,description:`Export a saved crawl as an OKF bundle`},args:{"report-id":{type:`string`,description:`Saved crawl report id to export.`},site:{type:`string`,description:`GSC property URL for selecting the latest saved crawl.`},client:{type:`string`,description:`Legacy alias for --project.`},project:{type:`string`,description:`Saved project id or name.`},output:{type:`string`,description:`Output directory. Defaults to ./okf.`},"max-concepts":{type:`string`,description:`Maximum page concepts to export. Defaults to 500.`},title:{type:`string`,description:`Override the root bundle title.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=F(e),n=er(M(e[`max-concepts`],`--max-concepts`)),r=Ln(await Vo(e,{json:t}),{maxConcepts:n,title:O(e.title)}),i=O(e.output)??`./okf`,a=await uu(i,r.files);if(t){R({output:i,bundle:r,validation:a});return}process.stdout.write(`Wrote OKF bundle to ${i}\n\n`),z([[`Files`,String(r.files.length)],[`Concepts`,String(r.conceptCount)],[`Page concepts`,String(r.pageConceptCount)],[`Valid`,a.valid?`yes`:`no`]])}}),validate:T({meta:{name:`validate`,description:`Validate an OKF bundle directory`},args:{path:{type:`positional`,required:!0,description:`OKF bundle directory.`},profile:{type:`string`,description:`Validation profile: okf or seo-export. Defaults to okf.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=O(e.path);if(!t)throw Error(`Pass an OKF bundle directory.`);let n=Wt(await cu(t),{profile:Qu(e.profile)});if(n.valid||(process.exitCode=1),F(e)){R(n);return}Zu(t,n)}}),explain:T({meta:{name:`explain`,description:`Explain OKF validation results in plain English`},args:{path:{type:`positional`,required:!0,description:`OKF bundle directory.`},profile:{type:`string`,description:`Validation profile: okf or seo-export. Defaults to okf.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=O(e.path);if(!t)throw Error(`Pass an OKF bundle directory.`);let n=ar(Wt(await cu(t),{profile:Qu(e.profile)}));if(n.valid||(process.exitCode=1),F(e)){R(n);return}if(process.stdout.write(`${n.summary}\n\n`),z([[`Version`,n.formatVersion??`not declared`],[`Compatibility`,n.compatibility],[`Profile`,n.profile],[`Valid`,n.valid?`yes`:`no`],[`Files`,String(n.files)],[`Concepts`,String(n.concepts)],[`Errors`,String(n.errors)],[`Warnings`,String(n.warnings)],[`With sources`,String(n.provenance.sources)],[`Generated`,String(n.generation.generated)],[`Human reviewed`,String(n.trust.humanReviewed)],[`Machine confirmed`,String(n.trust.machineConfirmed)],[`Unverified`,String(n.trust.unverified)],[`Draft`,String(n.lifecycle.draft)],[`Stable`,String(n.lifecycle.stable)],[`Deprecated`,String(n.lifecycle.deprecated)],[`Fresh`,String(n.freshness.fresh)],[`Stale`,String(n.freshness.stale)],[`Freshness unspecified`,String(n.freshness.unspecified)],[`Attested computations`,String(n.attestation.concepts)],[`Complete attestation contracts`,String(n.attestation.completeContracts)],[`Incomplete attestation contracts`,String(n.attestation.incompleteContracts)],[`Inline computations`,String(n.attestation.inlineComputations)],[`File computations`,String(n.attestation.fileComputations)]]),n.nextActions.length){process.stdout.write(`
196
196
  Next actions
197
- `);for(let e of n.nextActions)process.stdout.write(`- ${e}\n`)}}})}}),$u=E({meta:{name:`ai-referrals`,description:`Find AI referral traffic detected in Google Analytics`},args:{property:{type:`string`,description:`Google Analytics property ID. If omitted in a terminal, choose one.`},client:{type:`string`,description:`Legacy alias for --project.`},project:{type:`string`,description:`Saved project id or name with an optional Google Analytics property.`},"start-date":{type:`string`,default:`28daysAgo`},"end-date":{type:`string`,default:`yesterday`},"max-rows":{type:`string`,description:`Maximum Google Analytics rows per query. Defaults to 100000.`},"result-limit":{type:`string`,description:`Maximum ranked landing pages returned. Defaults to 25.`},limit:{type:`string`,description:`Legacy alias for --max-rows.`},refresh:{type:`boolean`,default:!1,description:`Bypass cached Google Analytics responses.`},json:{type:`boolean`,default:!1}},run:async({args:e})=>{let t=I(e),n=await W({client:A(e),options:{json:t}}),r=await gn({property:await zo({property:k(e.property)??n?.analytics.google?.propertyId,options:{json:t}}),startDate:k(e[`start-date`]),endDate:k(e[`end-date`]),maxRows:N(e[`max-rows`],`--max-rows`),resultLimit:N(e[`result-limit`],`--result-limit`),limit:N(e.limit,`--limit`),refresh:e.refresh===!0});if(t){z(r);return}if(q({title:`AI referral report`,target:r.property,status:r.dataStatus===`complete`?`info`:`unknown`,summary:r.summary.verdict,metrics:[{label:`Evidence`,value:r.dataStatus===`complete`?`Complete`:`Partial`,status:r.dataStatus===`complete`?`pass`:`unknown`},{label:`AI sessions`,value:G(r.summary.sessions)},{label:`AI users`,value:r.summary.totalUsers===null?`Unavailable`:G(r.summary.totalUsers)},{label:`Sources`,value:G(r.summary.sources)},{label:`Landing pages`,value:G(r.summary.landingPages)},{label:`Landing-page output`,value:`${G(r.selection.landingPages.returnedRows)} returned / ${G(r.selection.landingPages.retainedRows)} retained`},{label:`Source rows`,value:G(r.dataSource.sourceDiscovery.returnedRows)}]}),r.sources.length&&(process.stdout.write(`
197
+ `);for(let e of n.nextActions)process.stdout.write(`- ${e}\n`)}}})}}),ed=T({meta:{name:`ai-referrals`,description:`Find AI referral traffic detected in Google Analytics`},args:{property:{type:`string`,description:`Google Analytics property ID. If omitted in a terminal, choose one.`},client:{type:`string`,description:`Legacy alias for --project.`},project:{type:`string`,description:`Saved project id or name with an optional Google Analytics property.`},"start-date":{type:`string`,default:`28daysAgo`},"end-date":{type:`string`,default:`yesterday`},"max-rows":{type:`string`,description:`Maximum Google Analytics rows per query. Defaults to 100000.`},"result-limit":{type:`string`,description:`Maximum ranked landing pages returned. Defaults to 25.`},limit:{type:`string`,description:`Legacy alias for --max-rows.`},refresh:{type:`boolean`,default:!1,description:`Bypass cached Google Analytics responses.`},json:{type:`boolean`,default:!1}},run:async({args:e})=>{let t=F(e),n=await U({client:k(e),options:{json:t}}),r=await _n({property:await Bo({property:O(e.property)??n?.analytics.google?.propertyId,options:{json:t}}),startDate:O(e[`start-date`]),endDate:O(e[`end-date`]),maxRows:M(e[`max-rows`],`--max-rows`),resultLimit:M(e[`result-limit`],`--result-limit`),limit:M(e.limit,`--limit`),refresh:e.refresh===!0});if(t){R(r);return}if(K({title:`AI referral report`,target:r.property,status:r.dataStatus===`complete`?`info`:`unknown`,summary:r.summary.verdict,metrics:[{label:`Evidence`,value:r.dataStatus===`complete`?`Complete`:`Partial`,status:r.dataStatus===`complete`?`pass`:`unknown`},{label:`AI sessions`,value:W(r.summary.sessions)},{label:`AI users`,value:r.summary.totalUsers===null?`Unavailable`:W(r.summary.totalUsers)},{label:`Sources`,value:W(r.summary.sources)},{label:`Landing pages`,value:W(r.summary.landingPages)},{label:`Landing-page output`,value:`${W(r.selection.landingPages.returnedRows)} returned / ${W(r.selection.landingPages.retainedRows)} retained`},{label:`Source rows`,value:W(r.dataSource.sourceDiscovery.returnedRows)}]}),r.sources.length&&(process.stdout.write(`
198
198
  AI sources
199
- `),J([`Source`,`Sessions`,`Events`,`Share of AI sessions`],r.sources.map(e=>[e.label,G(e.sessions),G(e.eventCount),is(e.shareOfAiSessions)]))),r.landingPages.length&&(process.stdout.write(`
199
+ `),q([`Source`,`Sessions`,`Events`,`Share of AI sessions`],r.sources.map(e=>[e.label,W(e.sessions),W(e.eventCount),as(e.shareOfAiSessions)]))),r.landingPages.length&&(process.stdout.write(`
200
200
  Landing pages
201
- `),J([`Landing page`,`Sessions`,`Events`,`Top source`],r.landingPages.map(e=>[K(e.landingPage,64),G(e.sessions),G(e.eventCount),e.topSourceDetails.label]))),r.dataSource.partialReasons.length){process.stdout.write(`
201
+ `),q([`Landing page`,`Sessions`,`Events`,`Top source`],r.landingPages.map(e=>[G(e.landingPage,64),W(e.sessions),W(e.eventCount),e.topSourceDetails.label]))),r.dataSource.partialReasons.length){process.stdout.write(`
202
202
  Evidence warnings
203
- `);for(let e of r.dataSource.partialReasons)process.stdout.write(`- ${e}\n`)}process.stdout.write(`\nNote: ${r.summary.caveat}\n`)}}),ed=E({meta:{name:`cannibal`,description:`Find queries with material exposure across multiple URLs`},args:{site:{type:`string`},project:{type:`string`,description:`Saved project id or name.`},client:{type:`string`,description:`Legacy alias for --project.`},...Z([`days`,`limit`,`includeBrand`,`minImpressions`,`refresh`],{minImpressions:{description:`Minimum query impressions. Defaults to 50.`}}),"brand-terms":{type:`string`,description:`Comma-separated brand terms to exclude.`},json:{type:`boolean`,default:!1}},run:async({args:e})=>{let t=I(e),n=j(e.refresh),i=await U({client:A(e),site:k(e.site),options:{json:t,refresh:n}}),a=await r({site:i.site,days:N(e.days,`--days`),limit:N(e.limit,`--limit`),minImpressions:N(e[`min-impressions`],`--min-impressions`),brandTerms:[...i.client?.brandTerms??[],...P(e[`brand-terms`])??[]],includeBrand:j(e[`include-brand`]),refresh:n});if(t){z(a);return}q({title:`Cannibalisation report`,target:a.site,status:a.dataStatus===`complete`?`info`:`unknown`,summary:a.summary.verdict,metrics:[{label:`Evidence`,value:a.dataStatus},{label:`Range`,value:`${a.range.startDate} to ${a.range.endDate}`},{label:`Eligible`,value:G(a.summary.eligibleClusters)},{label:`Returned`,value:G(a.summary.returnedClusters)},{label:`Suppressed brand queries`,value:G(a.summary.suppressedQueries)},{label:`GSC completeness`,value:a.source.completeness},{label:`Brand queries`,value:j(e[`include-brand`])?`included`:`excluded`}]}),J([`Query`,`URLs`,`Property impr`,`Exposure`,`HHI`,`Review first`],a.items.map(e=>[K(e.query,42),e.pages.length,e.propertyImpressions===void 0?`n/a`:G(e.propertyImpressions),G(e.pageExposureImpressions),e.hhi.toFixed(2),K(e.suggestedOwnerUrl,56)])),Y(`Top cannibalisation actions`,a.items.map(e=>({label:e.query,context:`${e.pages.length} material URLs; first review ${K(e.suggestedOwnerUrl,64)}`,action:e.recommendation.action}))),X(`Report caveats`,a.caveats),X(`Provider usage`,[a.ledgerSummary])}}),td=E({meta:{name:`community-intent`,description:`Find GSC queries with forum, review, and comparison intent`},args:{site:{type:`string`},project:{type:`string`,description:`Saved project id or name.`},client:{type:`string`,description:`Legacy alias for --project.`},"start-date":{type:`string`,description:`Exact GSC start date (YYYY-MM-DD). Use with --end-date.`},"end-date":{type:`string`,description:`Exact GSC end date (YYYY-MM-DD). Use with --start-date.`},"max-rows":{type:`string`,description:`Maximum retained GSC query rows. Defaults to 50000.`},"brand-terms":{type:`string`,description:`Comma-separated brand terms to exclude.`},...Z([`days`,`limit`,`minImpressions`,`includeBrand`,`refresh`],{days:{description:`GSC lookback within the current 16-month retention.`},limit:{description:`Maximum intent queries to print. Defaults to 25.`},minImpressions:{description:`Minimum query impressions. Defaults to 20.`},includeBrand:{description:`Include branded queries in community-intent reports.`},refresh:{description:`Bypass local GSC cache.`}}),json:{type:`boolean`,default:!1}},run:async({args:e})=>{let t=I(e),n=await U({client:A(e),site:k(e.site),options:{json:t,refresh:j(e.refresh)}}),r=await Qt({site:n.site,days:N(e.days,`--days`),startDate:k(e[`start-date`]),endDate:k(e[`end-date`]),limit:N(e.limit,`--limit`),minImpressions:N(e[`min-impressions`],`--min-impressions`),maxRows:N(e[`max-rows`],`--max-rows`),brandTerms:[...n.client?.brandTerms??[],...P(e[`brand-terms`])??[]],includeBrand:j(e[`include-brand`]),refresh:j(e.refresh)});if(t){z(r);return}q({title:`Community intent report`,target:r.site,status:r.dataStatus===`available`||r.dataStatus===`empty`?`info`:`unknown`,summary:r.summary.verdict,metrics:[{label:`Evidence`,value:r.dataStatus},{label:`Classified queries`,value:G(r.summary.classifiedQueries)},{label:`Returned queries`,value:G(r.summary.returnedQueries)},{label:`Impressions`,value:G(r.summary.returnedImpressions)},{label:`Clicks`,value:G(r.summary.returnedClicks)},{label:`GSC completeness`,value:r.source.completeness}]}),J([`Intent`,`Query`,`Impr`,`Clicks`,`Confidence`,`Action`],r.items.map(e=>[e.intent,K(e.query,42),G(e.impressions),G(e.clicks),e.confidence,K(e.action,76)])),Y(`Top community-intent actions`,r.items.map(e=>({label:e.query,context:`${e.intent}, ${G(e.impressions)} impressions`,action:e.action}))),X(`Report caveats`,r.caveats),X(`Warnings`,r.warnings)}}),nd=E({meta:{name:`ctr-underperformers`,description:`Find high-impression queries with weak CTR for their position`},args:{site:{type:`string`},project:{type:`string`,description:`Saved project id or name.`},client:{type:`string`,description:`Legacy alias for --project.`},...Z([`includeBrand`,`minImpressions`],{minImpressions:{description:`Minimum query impressions. Defaults to 200.`}}),json:{type:`boolean`,default:!1}},run:async({args:e})=>{let t=I(e),n=await U({client:A(e),site:k(e.site),options:{json:t}}),r=await lr({site:n.site,minImpressions:M(e[`min-impressions`]),brandTerms:n.client?.brandTerms,includeBrand:j(e[`include-brand`])});if(t){z(r);return}q({title:`CTR underperformers`,target:r.site,status:r.dataStatus===`partial`?`unknown`:`info`,summary:r.summary.verdict,metrics:[{label:`Underperformers`,value:G(r.summary.underperformers)},{label:`Estimated click gap`,value:G(r.summary.estimatedClickShortfall)},{label:`Brand queries`,value:r.summary.brandFiltering},{label:`Evidence`,value:r.dataStatus},{label:`GSC completeness`,value:r.source.completeness}]}),X(`Why this matters`,[`These queries already have visibility; the fastest win is usually better SERP framing, not new content.`,`Start with high-impression rows where actual CTR is far below the expected CTR for that ranking position.`]),X(`Recommended actions`,r.recommendations),X(`Report caveats`,r.caveats),J([`Query`,`URL`,`Pos`,`Impr`,`CTR`,`Expected`,`Gap`,`Action`],r.items.map(e=>[K(e.query,36),K(e.url,48),rs(e.position),G(e.impressions),is(e.actualCtr),is(e.expectedCtr),G(e.clickShortfall),K(e.recommendation.action,72)])),Y(`Top CTR actions`,r.items.map(e=>({label:e.query,context:`${G(e.clickShortfall)} click gap, ${is(e.actualCtr)} CTR`,action:e.recommendation.action})))}});function rd(e){return e>0?rs(e):`n/a`}const id=E({meta:{name:`decaying`,description:`Find query/page rows losing clicks between two GSC windows`},args:{site:{type:`string`},project:{type:`string`,description:`Saved project id or name.`},client:{type:`string`,description:`Legacy alias for --project.`},...Z([`days`,`limit`,`includeBrand`,`refresh`]),comparison:{type:`string`,description:`Comparison: previous-period or year-over-year.`},"brand-terms":{type:`string`,description:`Comma-separated brand terms to exclude.`},"min-drop-pct":{type:`string`,description:`Minimum click drop percentage. Defaults to 20.`},"min-previous-clicks":{type:`string`,description:`Minimum previous-window clicks for a query/page row. Defaults to 2.`},"min-click-loss":{type:`string`,description:`Minimum absolute click loss. Defaults to 1.`},json:{type:`boolean`,default:!1}},run:async({args:e})=>{let t=I(e),n=j(e.refresh),r=k(e.comparison),i=N(e.days,`--days`),a=N(e.limit,`--limit`),o=N(e[`min-drop-pct`],`--min-drop-pct`),s=N(e[`min-previous-clicks`],`--min-previous-clicks`),c=N(e[`min-click-loss`],`--min-click-loss`);if(r!==void 0&&r!==`previous-period`&&r!==`year-over-year`)throw new w(`INVALID_INPUT`,`--comparison must be previous-period or year-over-year.`);let l=await U({client:A(e),site:k(e.site),options:{json:t,refresh:n}}),u=await qe({site:l.site,days:i,limit:a,comparison:r,brandTerms:[...l.client?.brandTerms??[],...P(e[`brand-terms`])??[]],includeBrand:j(e[`include-brand`]),minDropPct:o,minPreviousClicks:s,minClickLoss:c,refresh:n});if(t){z(u);return}q({title:`Decaying pages`,target:u.site,status:u.dataStatus===`complete`?u.summary.returnedRows>0?`warning`:`pass`:`unknown`,summary:u.summary.verdict,metrics:[{label:`Evidence`,value:u.dataStatus},{label:`Comparison`,value:u.comparison},{label:`Observed declines`,value:G(u.summary.returnedRows)},{label:`Eligible declines`,value:G(u.summary.eligibleRows)},{label:`Decay clusters`,value:G(u.summary.groups)},{label:`Observed click decline`,value:G(u.summary.returnedObservedRetainedQueryClickLoss)},{label:`Eligible click decline`,value:G(u.summary.observedRetainedQueryClickLoss)},{label:`Brand queries`,value:u.summary.brandFiltering},{label:`Minimum drop`,value:`${u.filters.minDropPct}%`},{label:`Minimum previous clicks`,value:G(u.filters.minPreviousClicks)},{label:`GSC completeness`,value:u.source.completeness}]}),X(`Why this matters`,[`This report compares retained query/page rows and only counts declines observed in both windows.`,`Position, CTR, and impression movements are signals for investigation, not proof of cause.`]),X(`Recommended actions`,u.recommendations),X(`Report caveats`,u.caveats),X(`Warnings`,u.warnings),u.items.length&&(u.groups.length&&(J([`Cluster`,`Rows`,`Observed loss`,`Drop`,`Sample query`,`Action`],u.groups.map(e=>[K(e.label,42),G(e.count),G(e.totalClickLoss),`${e.averageDropPct.toFixed(1)}%`,K(e.sampleQueries[0]??`-`,40),K(e.recommendation,72)])),Y(`Top decay cluster actions`,u.groups.map(e=>({label:e.label,context:`${G(e.count)} retained rows, ${G(e.totalClickLoss)} observed click decline`,action:e.recommendation})))),J([`Query`,`Template`,`URL`,`Signals`,`Decline`,`Clicks`,`Impr`,`CTR`,`Pos`,`Action`],u.items.map(e=>[K(e.query,32),K(e.template.label,26),K(e.url,44),e.signals.join(`, `),G(e.clickLoss),`${G(e.previous.clicks)} -> ${G(e.current.clicks)}`,`${G(e.previous.impressions)} -> ${G(e.current.impressions)}`,`${is(e.previous.ctr)} -> ${is(e.current.ctr)}`,`${rd(e.previous.position)} -> ${rd(e.current.position)}`,K(e.recommendation.action,64)])),Y(`Top decay actions`,u.items.map(e=>({label:e.query,context:`${e.template.label}, ${G(e.clickLoss)} fewer observed clicks`,action:e.recommendation.action}))))}}),ad=E({meta:{name:`internal-links`,description:`Find verified internal-link review candidates for one URL`},args:{site:{type:`string`,description:`GSC property URL, for example sc-domain:example.com.`},project:{type:`string`,description:`Saved project id or name.`},client:{type:`string`,description:`Legacy alias for --project.`},url:{type:`string`,required:!0,description:`Target page that should receive relevant internal links.`},...Z([`days`,`limit`,`checkLimit`,`minImpressions`,`includeBrand`,`js`,`fetchConcurrency`,`fetchIntervalCap`,`fetchIntervalMs`,`refresh`]),"brand-terms":{type:`string`,description:`Comma-separated brand terms to exclude.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=I(e),n=j(e.refresh),r=N(e.days,`--days`),i=N(e.limit,`--limit`),a=N(e[`check-limit`],`--check-limit`),o=N(e[`min-impressions`],`--min-impressions`),s=Ba(e),c=await U({client:A(e),site:k(e.site),options:{json:t,refresh:n}}),l=await Ze({site:c.site,targetUrl:k(e.url)??``,days:r,limit:i,checkLimit:a,minImpressions:o,brandTerms:[...c.client?.brandTerms??[],...P(e[`brand-terms`])??[]],includeBrand:j(e[`include-brand`]),js:j(e.js)?!0:void 0,rate:s,refresh:n});if(t){z(l);return}q({title:`Internal link opportunities`,target:l.targetUrl,status:l.summary.failedChecks>0||l.summary.uncheckedCandidates>0?`unknown`:`info`,summary:l.summary.verdict,metrics:[{label:`Target state`,value:l.target.verification},{label:`Preferred target`,value:l.target.preferredUrl},{label:`Target queries`,value:G(l.summary.targetQueries)},{label:`Matched sources`,value:G(l.summary.candidateSources)},{label:`Checked sources`,value:G(l.summary.checkedSources)},{label:`Attempted sources`,value:G(l.summary.attemptedSources)},{label:`Review candidates`,value:G(l.summary.returnedSources)},{label:`Technical exclusions`,value:G(l.summary.technicalExclusions)},{label:`Existing links`,value:G(l.summary.existingLinksObserved)},{label:`Failed checks`,value:G(l.summary.failedChecks)},{label:`Unchecked sources`,value:G(l.summary.uncheckedCandidates)},{label:`GSC completeness`,value:l.source.completeness},{label:`Brand queries`,value:l.summary.brandFiltering}]}),X(`Why this matters`,[`Exact-query overlap is useful affinity evidence; precision lexical matches are review evidence only.`,`Source and target pages are checked for redirects, indexability, canonicals, and existing link placement before an action is returned.`]),X(`Recommended actions`,l.recommendations),X(`Report caveats`,l.caveats),l.items.length&&(J([`Source URL`,`Matched impr`,`Match`,`Link evidence`,`Action`],l.items.map(e=>[K(e.sourceUrl,52),G(e.matchedQueryImpressions),e.bestMatchKind,e.linkEvidence.status,e.actionType])),Y(`Top internal-link reviews`,l.items.map(e=>({label:K(e.sourceUrl,96),context:`${G(e.matchedQueryImpressions)} matched-query impressions; ${e.bestMatchKind}`,action:e.recommendation.action}))),X(`Provider usage`,[l.ledgerSummary]))}}),od=E({meta:{name:`page-opportunities`,description:`Find first-party traffic growth opportunities for one URL`},args:{site:{type:`string`},project:{type:`string`,description:`Saved project id or name.`},client:{type:`string`,description:`Legacy alias for --project.`},url:{type:`string`,required:!0},...Z([`days`,`limit`,`minImpressions`,`includeBrand`,`js`,`refresh`],{limit:{description:`Maximum page queries to inspect. Defaults to 25.`},minImpressions:{description:`Minimum query impressions. Defaults to 10.`},includeBrand:{description:`Include branded queries in page opportunity reports.`},js:{description:`Force JavaScript rendering for page extraction.`},refresh:{description:`Bypass local GSC and HTTP cache.`}}),"verify-content":Pa(`Fetch the page for title/H1/body checks. Defaults to true.`,`Skip fetching the page for title/H1/body checks.`),json:{type:`boolean`,default:!1}},run:async({args:e})=>{let t=I(e),n=await U({client:A(e),site:k(e.site),options:{json:t,refresh:j(e.refresh)}}),r=await Pr({site:n.site,url:k(e.url)??``,days:M(e.days),limit:M(e.limit),minImpressions:M(e[`min-impressions`]),brandTerms:n.client?.brandTerms,includeBrand:j(e[`include-brand`]),verifyContent:j(e[`verify-content`])!==!1,js:j(e.js)?!0:`auto`,refresh:j(e.refresh)});if(t){z(r);return}if(q({title:`Page opportunities`,target:r.url,status:r.verification.status===`failed`?`unknown`:`info`,summary:r.summary.verdict,metrics:[{label:`Title`,value:r.page?.title??`Not fetched`},{label:`Fetch`,value:Cs(r.page?.fetchDiagnostics)},{label:`Verification`,value:r.verification.status},{label:`Rows`,value:`${G(r.selection.returnedRows)} returned / ${G(r.selection.eligibleRows)} eligible / ${G(r.selection.sourceRows)} source`},{label:`Queries`,value:G(r.summary.queries)},{label:`Impressions`,value:G(r.summary.impressions)},{label:`Opportunities`,value:G(r.summary.opportunities)},{label:`CTR shortfall`,value:G(r.summary.estimatedCtrClickShortfall)},{label:`Focus`,value:r.summary.focus},{label:`Evidence`,value:r.dataStatus}]}),X(`Why this matters`,[`This report starts from queries where this exact URL already has impressions, so recommendations stay tied to first-party demand.`,`Content verification separates body gaps from title/meta framing issues, which helps avoid adding copy when the page already covers the query.`]),X(`Recommended actions`,r.recommendations),X(`Report caveats`,r.caveats),!r.items.length){process.stdout.write(r.dataStatus===`empty`?`No GSC query rows found for this exact URL.
203
+ `);for(let e of r.dataSource.partialReasons)process.stdout.write(`- ${e}\n`)}process.stdout.write(`\nNote: ${r.summary.caveat}\n`)}}),td=T({meta:{name:`cannibal`,description:`Find queries with material exposure across multiple URLs`},args:{site:{type:`string`},project:{type:`string`,description:`Saved project id or name.`},client:{type:`string`,description:`Legacy alias for --project.`},...X([`days`,`limit`,`includeBrand`,`minImpressions`,`refresh`],{minImpressions:{description:`Minimum query impressions. Defaults to 50.`}}),"brand-terms":{type:`string`,description:`Comma-separated brand terms to exclude.`},json:{type:`boolean`,default:!1}},run:async({args:e})=>{let t=F(e),n=A(e.refresh),i=await H({client:k(e),site:O(e.site),options:{json:t,refresh:n}}),a=await r({site:i.site,days:M(e.days,`--days`),limit:M(e.limit,`--limit`),minImpressions:M(e[`min-impressions`],`--min-impressions`),brandTerms:[...i.client?.brandTerms??[],...N(e[`brand-terms`])??[]],includeBrand:A(e[`include-brand`]),refresh:n});if(t){R(a);return}K({title:`Cannibalisation report`,target:a.site,status:a.dataStatus===`complete`?`info`:`unknown`,summary:a.summary.verdict,metrics:[{label:`Evidence`,value:a.dataStatus},{label:`Range`,value:`${a.range.startDate} to ${a.range.endDate}`},{label:`Eligible`,value:W(a.summary.eligibleClusters)},{label:`Returned`,value:W(a.summary.returnedClusters)},{label:`Suppressed brand queries`,value:W(a.summary.suppressedQueries)},{label:`GSC completeness`,value:a.source.completeness},{label:`Brand queries`,value:A(e[`include-brand`])?`included`:`excluded`}]}),q([`Query`,`URLs`,`Property impr`,`Exposure`,`HHI`,`Review first`],a.items.map(e=>[G(e.query,42),e.pages.length,e.propertyImpressions===void 0?`n/a`:W(e.propertyImpressions),W(e.pageExposureImpressions),e.hhi.toFixed(2),G(e.suggestedOwnerUrl,56)])),J(`Top cannibalisation actions`,a.items.map(e=>({label:e.query,context:`${e.pages.length} material URLs; first review ${G(e.suggestedOwnerUrl,64)}`,action:e.recommendation.action}))),Y(`Report caveats`,a.caveats),Y(`Provider usage`,[a.ledgerSummary])}}),nd=T({meta:{name:`community-intent`,description:`Find GSC queries with forum, review, and comparison intent`},args:{site:{type:`string`},project:{type:`string`,description:`Saved project id or name.`},client:{type:`string`,description:`Legacy alias for --project.`},"start-date":{type:`string`,description:`Exact GSC start date (YYYY-MM-DD). Use with --end-date.`},"end-date":{type:`string`,description:`Exact GSC end date (YYYY-MM-DD). Use with --start-date.`},"max-rows":{type:`string`,description:`Maximum retained GSC query rows. Defaults to 50000.`},"brand-terms":{type:`string`,description:`Comma-separated brand terms to exclude.`},...X([`days`,`limit`,`minImpressions`,`includeBrand`,`refresh`],{days:{description:`GSC lookback within the current 16-month retention.`},limit:{description:`Maximum intent queries to print. Defaults to 25.`},minImpressions:{description:`Minimum query impressions. Defaults to 20.`},includeBrand:{description:`Include branded queries in community-intent reports.`},refresh:{description:`Bypass local GSC cache.`}}),json:{type:`boolean`,default:!1}},run:async({args:e})=>{let t=F(e),n=await H({client:k(e),site:O(e.site),options:{json:t,refresh:A(e.refresh)}}),r=await $t({site:n.site,days:M(e.days,`--days`),startDate:O(e[`start-date`]),endDate:O(e[`end-date`]),limit:M(e.limit,`--limit`),minImpressions:M(e[`min-impressions`],`--min-impressions`),maxRows:M(e[`max-rows`],`--max-rows`),brandTerms:[...n.client?.brandTerms??[],...N(e[`brand-terms`])??[]],includeBrand:A(e[`include-brand`]),refresh:A(e.refresh)});if(t){R(r);return}K({title:`Community intent report`,target:r.site,status:r.dataStatus===`available`||r.dataStatus===`empty`?`info`:`unknown`,summary:r.summary.verdict,metrics:[{label:`Evidence`,value:r.dataStatus},{label:`Classified queries`,value:W(r.summary.classifiedQueries)},{label:`Returned queries`,value:W(r.summary.returnedQueries)},{label:`Impressions`,value:W(r.summary.returnedImpressions)},{label:`Clicks`,value:W(r.summary.returnedClicks)},{label:`GSC completeness`,value:r.source.completeness}]}),q([`Intent`,`Query`,`Impr`,`Clicks`,`Confidence`,`Action`],r.items.map(e=>[e.intent,G(e.query,42),W(e.impressions),W(e.clicks),e.confidence,G(e.action,76)])),J(`Top community-intent actions`,r.items.map(e=>({label:e.query,context:`${e.intent}, ${W(e.impressions)} impressions`,action:e.action}))),Y(`Report caveats`,r.caveats),Y(`Warnings`,r.warnings)}}),rd=T({meta:{name:`ctr-underperformers`,description:`Find high-impression queries with weak CTR for their position`},args:{site:{type:`string`},project:{type:`string`,description:`Saved project id or name.`},client:{type:`string`,description:`Legacy alias for --project.`},...X([`includeBrand`,`minImpressions`],{minImpressions:{description:`Minimum query impressions. Defaults to 200.`}}),json:{type:`boolean`,default:!1}},run:async({args:e})=>{let t=F(e),n=await H({client:k(e),site:O(e.site),options:{json:t}}),r=await ur({site:n.site,minImpressions:j(e[`min-impressions`]),brandTerms:n.client?.brandTerms,includeBrand:A(e[`include-brand`])});if(t){R(r);return}K({title:`CTR underperformers`,target:r.site,status:r.dataStatus===`partial`?`unknown`:`info`,summary:r.summary.verdict,metrics:[{label:`Underperformers`,value:W(r.summary.underperformers)},{label:`Estimated click gap`,value:W(r.summary.estimatedClickShortfall)},{label:`Brand queries`,value:r.summary.brandFiltering},{label:`Evidence`,value:r.dataStatus},{label:`GSC completeness`,value:r.source.completeness}]}),Y(`Why this matters`,[`These queries already have visibility; the fastest win is usually better SERP framing, not new content.`,`Start with high-impression rows where actual CTR is far below the expected CTR for that ranking position.`]),Y(`Recommended actions`,r.recommendations),Y(`Report caveats`,r.caveats),q([`Query`,`URL`,`Pos`,`Impr`,`CTR`,`Expected`,`Gap`,`Action`],r.items.map(e=>[G(e.query,36),G(e.url,48),is(e.position),W(e.impressions),as(e.actualCtr),as(e.expectedCtr),W(e.clickShortfall),G(e.recommendation.action,72)])),J(`Top CTR actions`,r.items.map(e=>({label:e.query,context:`${W(e.clickShortfall)} click gap, ${as(e.actualCtr)} CTR`,action:e.recommendation.action})))}});function id(e){return e>0?is(e):`n/a`}const ad=T({meta:{name:`decaying`,description:`Find query/page rows losing clicks between two GSC windows`},args:{site:{type:`string`},project:{type:`string`,description:`Saved project id or name.`},client:{type:`string`,description:`Legacy alias for --project.`},...X([`days`,`limit`,`includeBrand`,`refresh`]),comparison:{type:`string`,description:`Comparison: previous-period or year-over-year.`},"brand-terms":{type:`string`,description:`Comma-separated brand terms to exclude.`},"min-drop-pct":{type:`string`,description:`Minimum click drop percentage. Defaults to 20.`},"min-previous-clicks":{type:`string`,description:`Minimum previous-window clicks for a query/page row. Defaults to 2.`},"min-click-loss":{type:`string`,description:`Minimum absolute click loss. Defaults to 1.`},json:{type:`boolean`,default:!1}},run:async({args:e})=>{let t=F(e),n=A(e.refresh),r=O(e.comparison),i=M(e.days,`--days`),a=M(e.limit,`--limit`),o=M(e[`min-drop-pct`],`--min-drop-pct`),s=M(e[`min-previous-clicks`],`--min-previous-clicks`),c=M(e[`min-click-loss`],`--min-click-loss`);if(r!==void 0&&r!==`previous-period`&&r!==`year-over-year`)throw new C(`INVALID_INPUT`,`--comparison must be previous-period or year-over-year.`);let l=await H({client:k(e),site:O(e.site),options:{json:t,refresh:n}}),u=await Je({site:l.site,days:i,limit:a,comparison:r,brandTerms:[...l.client?.brandTerms??[],...N(e[`brand-terms`])??[]],includeBrand:A(e[`include-brand`]),minDropPct:o,minPreviousClicks:s,minClickLoss:c,refresh:n});if(t){R(u);return}K({title:`Decaying pages`,target:u.site,status:u.dataStatus===`complete`?u.summary.returnedRows>0?`warning`:`pass`:`unknown`,summary:u.summary.verdict,metrics:[{label:`Evidence`,value:u.dataStatus},{label:`Comparison`,value:u.comparison},{label:`Observed declines`,value:W(u.summary.returnedRows)},{label:`Eligible declines`,value:W(u.summary.eligibleRows)},{label:`Decay clusters`,value:W(u.summary.groups)},{label:`Observed click decline`,value:W(u.summary.returnedObservedRetainedQueryClickLoss)},{label:`Eligible click decline`,value:W(u.summary.observedRetainedQueryClickLoss)},{label:`Brand queries`,value:u.summary.brandFiltering},{label:`Minimum drop`,value:`${u.filters.minDropPct}%`},{label:`Minimum previous clicks`,value:W(u.filters.minPreviousClicks)},{label:`GSC completeness`,value:u.source.completeness}]}),Y(`Why this matters`,[`This report compares retained query/page rows and only counts declines observed in both windows.`,`Position, CTR, and impression movements are signals for investigation, not proof of cause.`]),Y(`Recommended actions`,u.recommendations),Y(`Report caveats`,u.caveats),Y(`Warnings`,u.warnings),u.items.length&&(u.groups.length&&(q([`Cluster`,`Rows`,`Observed loss`,`Drop`,`Sample query`,`Action`],u.groups.map(e=>[G(e.label,42),W(e.count),W(e.totalClickLoss),`${e.averageDropPct.toFixed(1)}%`,G(e.sampleQueries[0]??`-`,40),G(e.recommendation,72)])),J(`Top decay cluster actions`,u.groups.map(e=>({label:e.label,context:`${W(e.count)} retained rows, ${W(e.totalClickLoss)} observed click decline`,action:e.recommendation})))),q([`Query`,`Template`,`URL`,`Signals`,`Decline`,`Clicks`,`Impr`,`CTR`,`Pos`,`Action`],u.items.map(e=>[G(e.query,32),G(e.template.label,26),G(e.url,44),e.signals.join(`, `),W(e.clickLoss),`${W(e.previous.clicks)} -> ${W(e.current.clicks)}`,`${W(e.previous.impressions)} -> ${W(e.current.impressions)}`,`${as(e.previous.ctr)} -> ${as(e.current.ctr)}`,`${id(e.previous.position)} -> ${id(e.current.position)}`,G(e.recommendation.action,64)])),J(`Top decay actions`,u.items.map(e=>({label:e.query,context:`${e.template.label}, ${W(e.clickLoss)} fewer observed clicks`,action:e.recommendation.action}))))}}),od=T({meta:{name:`internal-links`,description:`Find verified internal-link review candidates for one URL`},args:{site:{type:`string`,description:`GSC property URL, for example sc-domain:example.com.`},project:{type:`string`,description:`Saved project id or name.`},client:{type:`string`,description:`Legacy alias for --project.`},url:{type:`string`,required:!0,description:`Target page that should receive relevant internal links.`},...X([`days`,`limit`,`checkLimit`,`minImpressions`,`includeBrand`,`js`,`fetchConcurrency`,`fetchIntervalCap`,`fetchIntervalMs`,`refresh`]),"brand-terms":{type:`string`,description:`Comma-separated brand terms to exclude.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=F(e),n=A(e.refresh),r=M(e.days,`--days`),i=M(e.limit,`--limit`),a=M(e[`check-limit`],`--check-limit`),o=M(e[`min-impressions`],`--min-impressions`),s=Va(e),c=await H({client:k(e),site:O(e.site),options:{json:t,refresh:n}}),l=await Qe({site:c.site,targetUrl:O(e.url)??``,days:r,limit:i,checkLimit:a,minImpressions:o,brandTerms:[...c.client?.brandTerms??[],...N(e[`brand-terms`])??[]],includeBrand:A(e[`include-brand`]),js:A(e.js)?!0:void 0,rate:s,refresh:n});if(t){R(l);return}K({title:`Internal link opportunities`,target:l.targetUrl,status:l.summary.failedChecks>0||l.summary.uncheckedCandidates>0?`unknown`:`info`,summary:l.summary.verdict,metrics:[{label:`Target state`,value:l.target.verification},{label:`Preferred target`,value:l.target.preferredUrl},{label:`Target queries`,value:W(l.summary.targetQueries)},{label:`Matched sources`,value:W(l.summary.candidateSources)},{label:`Checked sources`,value:W(l.summary.checkedSources)},{label:`Attempted sources`,value:W(l.summary.attemptedSources)},{label:`Review candidates`,value:W(l.summary.returnedSources)},{label:`Technical exclusions`,value:W(l.summary.technicalExclusions)},{label:`Existing links`,value:W(l.summary.existingLinksObserved)},{label:`Failed checks`,value:W(l.summary.failedChecks)},{label:`Unchecked sources`,value:W(l.summary.uncheckedCandidates)},{label:`GSC completeness`,value:l.source.completeness},{label:`Brand queries`,value:l.summary.brandFiltering}]}),Y(`Why this matters`,[`Exact-query overlap is useful affinity evidence; precision lexical matches are review evidence only.`,`Source and target pages are checked for redirects, indexability, canonicals, and existing link placement before an action is returned.`]),Y(`Recommended actions`,l.recommendations),Y(`Report caveats`,l.caveats),l.items.length&&(q([`Source URL`,`Matched impr`,`Match`,`Link evidence`,`Action`],l.items.map(e=>[G(e.sourceUrl,52),W(e.matchedQueryImpressions),e.bestMatchKind,e.linkEvidence.status,e.actionType])),J(`Top internal-link reviews`,l.items.map(e=>({label:G(e.sourceUrl,96),context:`${W(e.matchedQueryImpressions)} matched-query impressions; ${e.bestMatchKind}`,action:e.recommendation.action}))),Y(`Provider usage`,[l.ledgerSummary]))}}),sd=T({meta:{name:`page-opportunities`,description:`Find first-party traffic growth opportunities for one URL`},args:{site:{type:`string`},project:{type:`string`,description:`Saved project id or name.`},client:{type:`string`,description:`Legacy alias for --project.`},url:{type:`string`,required:!0},...X([`days`,`limit`,`minImpressions`,`includeBrand`,`js`,`refresh`],{limit:{description:`Maximum page queries to inspect. Defaults to 25.`},minImpressions:{description:`Minimum query impressions. Defaults to 10.`},includeBrand:{description:`Include branded queries in page opportunity reports.`},js:{description:`Force JavaScript rendering for page extraction.`},refresh:{description:`Bypass local GSC and HTTP cache.`}}),"verify-content":Fa(`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=F(e),n=await H({client:k(e),site:O(e.site),options:{json:t,refresh:A(e.refresh)}}),r=await Fr({site:n.site,url:O(e.url)??``,days:j(e.days),limit:j(e.limit),minImpressions:j(e[`min-impressions`]),brandTerms:n.client?.brandTerms,includeBrand:A(e[`include-brand`]),verifyContent:A(e[`verify-content`])!==!1,js:A(e.js)?!0:`auto`,refresh:A(e.refresh)});if(t){R(r);return}if(K({title:`Page opportunities`,target:r.url,status:r.verification.status===`failed`?`unknown`:`info`,summary:r.summary.verdict,metrics:[{label:`Title`,value:r.page?.title??`Not fetched`},{label:`Fetch`,value:ws(r.page?.fetchDiagnostics)},{label:`Verification`,value:r.verification.status},{label:`Rows`,value:`${W(r.selection.returnedRows)} returned / ${W(r.selection.eligibleRows)} eligible / ${W(r.selection.sourceRows)} source`},{label:`Queries`,value:W(r.summary.queries)},{label:`Impressions`,value:W(r.summary.impressions)},{label:`Opportunities`,value:W(r.summary.opportunities)},{label:`CTR shortfall`,value:W(r.summary.estimatedCtrClickShortfall)},{label:`Focus`,value:r.summary.focus},{label:`Evidence`,value:r.dataStatus}]}),Y(`Why this matters`,[`This report starts from queries where this exact URL already has impressions, so recommendations stay tied to first-party demand.`,`Content verification separates body gaps from title/meta framing issues, which helps avoid adding copy when the page already covers the query.`]),Y(`Recommended actions`,r.recommendations),Y(`Report caveats`,r.caveats),!r.items.length){process.stdout.write(r.dataStatus===`empty`?`No GSC query rows found for this exact URL.
204
204
  `:`GSC rows were found, but none met the report criteria.
205
- `);return}J([`Type`,`Query`,`Evidence`,`Pos`,`CTR`,`Expected`,`Impr`,`Shortfall`,`Action`],r.items.map(e=>[e.opportunityType,K(e.query,38),e.verification.signals.length?e.verification.signals.join(`, `):e.verification.status,e.position.toFixed(1),is(e.ctr),e.expectedCtr===void 0?`n/a`:is(e.expectedCtr),G(e.impressions),e.estimatedCtrClickShortfall===void 0?`n/a`:G(e.estimatedCtrClickShortfall),K(e.recommendation,72)])),Y(`Top page actions`,r.items.map(e=>({label:e.query,context:`${e.opportunityType}, ${G(e.impressions)} impressions`,action:e.recommendation})))}}),sd=E({meta:{name:`query-cluster`,description:`Cluster GSC queries into repeated demand themes`},args:{site:{type:`string`},project:{type:`string`,description:`Saved project id or name.`},client:{type:`string`,description:`Legacy alias for --project.`},scope:{type:`string`},...Z([`includeBrand`,`minImpressions`,`limit`],{includeBrand:{description:`Include branded queries in query clustering.`},minImpressions:{description:`Minimum impressions per query. Defaults to 25.`},limit:{description:`Maximum clusters to return. Defaults to 25; max 100.`}}),json:{type:`boolean`,default:!1}},run:async({args:e})=>{let t=I(e),n=await U({client:A(e),site:k(e.site),options:{json:t}}),r=await te({site:n.site,scope:k(e.scope),brand:n.client?.brandTerms?.[0],brandTerms:n.client?.brandTerms,includeBrand:j(e[`include-brand`]),minImpressions:M(e[`min-impressions`]),limit:M(e.limit)});if(t){z(r);return}q({title:`Query clusters`,target:r.site,status:`info`,summary:r.summary.verdict,metrics:[{label:`Scope`,value:r.scope??`All pages`},{label:`Clusters`,value:G(r.summary.clusters)},{label:`Queries`,value:G(r.summary.queries)},{label:`Impressions`,value:G(r.summary.impressions)},{label:`Clicks`,value:G(r.summary.clicks)},{label:`Minimum impressions`,value:G(r.summary.minImpressions)},{label:`Result limit`,value:G(r.summary.limit)},{label:`High opportunity`,value:G(r.summary.highOpportunityClusters)},{label:`Brand queries`,value:r.summary.brandFiltering}]}),X(`Why this matters`,[`Clusters show repeated demand themes, so they are better inputs for page sections, hubs, and templates than one-off query exports.`,`Prioritise clusters with high impressions and low clicks; they usually reveal unclear intent coverage or weak SERP framing.`]),X(`Recommended actions`,r.recommendations),X(`Report caveats`,r.caveats),J([`Cluster`,`Intent`,`Queries`,`Impr`,`CTR`,`Expected`,`Lift`,`Action`],r.clusters.map(e=>[K(e.label,32),e.intent,e.queries.length,G(e.totals?.impressions??0),is(e.totals?.ctr??0),is(e.benchmark?.expectedCtr??0),e.estimatedClickLift===void 0?`-`:G(e.estimatedClickLift),K(e.recommendation??``,72)])),Y(`Top cluster actions`,r.clusters.map(e=>({label:e.label,context:`${G(e.totals?.impressions??0)} impressions, ${e.estimatedClickLift===void 0?`ranking opportunity`:`${G(e.estimatedClickLift)} estimated click lift`}`,action:e.recommendation??``})))}}),cd=E({meta:{name:`quick-wins`,description:`Find high-ranking low-CTR query/page opportunities`},args:{site:{type:`string`},project:{type:`string`,description:`Saved project id or name.`},client:{type:`string`,description:`Legacy alias for --project.`},...Z([`days`,`limit`,`includeBrand`,`minImpressions`,`verifyContent`,`verifyLimit`,`js`,`fetchConcurrency`,`fetchIntervalCap`,`fetchIntervalMs`,`refresh`]),"brand-terms":{type:`string`,description:`Comma-separated brand terms to exclude.`},json:{type:`boolean`,default:!1}},run:async({args:e})=>{let t=I(e),n=M(e[`verify-limit`]),r=j(e[`verify-content`])===!0||n!==void 0,i=await U({client:A(e),site:k(e.site),options:{json:t}}),a=await kt({site:i.site,days:M(e.days),limit:M(e.limit),minImpressions:M(e[`min-impressions`]),brandTerms:[...i.client?.brandTerms??[],...P(e[`brand-terms`])??[]],includeBrand:j(e[`include-brand`]),verifyContent:r,verifyLimit:n,js:j(e.js)?!0:void 0,rate:za(e),refresh:j(e.refresh)});if(t){z(a);return}q({title:`Quick wins`,target:a.site,status:a.source.possiblyTruncated?`unknown`:`info`,summary:a.summary.verdict,metrics:[{label:`Eligible rows`,value:G(a.summary.eligibleRows)},{label:`Returned rows`,value:G(a.summary.returnedRows)},{label:`Query clusters`,value:G(a.summary.repeatedQueryGroups)},{label:`Template patterns`,value:G(a.summary.templatePatterns)},{label:`CTR click shortfall`,value:G(a.summary.returnedEstimatedCtrClickShortfall)},{label:`Brand queries`,value:a.summary.brandFiltering},{label:`Evidence`,value:a.dataStatus},{label:`Verification`,value:as(a)}]}),X(`Why this matters`,[`These rows have GSC average positions from 4 to 10 and observed CTR below a site-peer or versioned fallback target.`,`Page evidence is needed before treating a CTR shortfall as a title, content, or technical finding.`]),X(`Recommended actions`,a.recommendations),X(`Report caveats`,a.caveats),a.items.length&&(a.templateRecommendations.length&&(J([`Template`,`URLs`,`Rows`,`Shortfall`,`Impr`,`Action`],a.templateRecommendations.map(e=>[K(e.templateLabel,34),G(e.urlCount),G(e.rowCount),G(e.totalEstimatedCtrClickShortfall),G(e.totalImpressions),K(e.action,72)])),Y(`Top template actions`,a.templateRecommendations.map(e=>({label:e.templateLabel,context:`${G(e.urlCount)} URLs, ${G(e.totalEstimatedCtrClickShortfall)} heuristic CTR click shortfall`,action:`${e.action} ${e.evidence}`})))),a.groups.length&&(J([`Cluster`,`URLs`,`Rows`,`Shortfall`,`Impr`,`Sample URL`,`Action`],a.groups.map(e=>[K(e.label,44),G(e.urlCount),G(e.rowCount),G(e.totalEstimatedCtrClickShortfall),G(e.totalImpressions),K(e.sampleUrls[0]??`-`,46),K(e.recommendation,72)])),Y(`Top cluster actions`,a.groups.map(e=>({label:e.label,context:`${G(e.urlCount)} URLs, ${G(e.totalEstimatedCtrClickShortfall)} heuristic CTR click shortfall`,action:e.recommendation})))),J([`Query`,`Template`,`URL`,`Pos`,`Impr`,`CTR / target`,`Shortfall`,`Fetch`,`Check`,`Action`],a.items.map(e=>[K(e.query,36),K(e.template.label,24),K(e.url,48),rs(e.position),G(e.impressions),`${is(e.ctr)} / ${is(e.targetCtr)}`,G(e.estimatedCtrClickShortfall),Cs(e.contentVerification?.fetchDiagnostics),ws(e.contentVerification?.classification),K(e.recommendation.action,64)])),Y(`Top opportunity actions`,a.items.map(e=>({label:e.query,context:`${e.template.label}, pos ${rs(e.position)}, ${G(e.impressions)} impressions`,action:e.recommendation.action}))))}}),ld=E({meta:{name:`second-page`,description:`Find URLs averaging positions above 10 through 20 in GSC`},args:{site:{type:`string`,description:`GSC property URL, for example sc-domain:example.com.`},project:{type:`string`,description:`Saved project id or name.`},client:{type:`string`,description:`Legacy alias for --project.`},...Z([`range`,`minImpressions`,`limit`,`includeBrand`,`verifyContent`,`verifyLimit`,`js`,`fetchConcurrency`,`fetchIntervalCap`,`fetchIntervalMs`,`refresh`]),json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=I(e),n=M(e[`verify-limit`]),r=j(e[`verify-content`])===!0||n!==void 0,i=await U({client:A(e),site:k(e.site),options:{json:t,refresh:j(e.refresh)}}),a=await cn({site:i.site,range:M(e.days),minImpressions:M(e[`min-impressions`]),limit:k(e.limit)?Number(k(e.limit)):10,brandTerms:i.client?.brandTerms,includeBrand:j(e[`include-brand`]),verifyContent:r,verifyLimit:n,js:j(e.js)?!0:void 0,rate:za(e),refresh:j(e.refresh)});if(t){z(a);return}if(q({title:`Second-page opportunities`,target:a.site,status:a.source.possiblyTruncated||a.summary.fetchFailures>0?`unknown`:`info`,summary:a.summary.verdict,metrics:[{label:`Eligible pages`,value:G(a.summary.eligiblePages)},{label:`Returned pages`,value:G(a.summary.returnedPages)},{label:`Eligible queries`,value:G(a.summary.eligibleQueries)},{label:`Impressions`,value:G(a.summary.eligibleImpressions)},{label:`Content issues`,value:G(a.summary.contentIssues)},{label:`Technical issues`,value:G(a.summary.technicalIssues)},{label:`Fetch failures`,value:G(a.summary.fetchFailures)},{label:`Brand queries`,value:a.summary.brandFiltering},{label:`Evidence`,value:a.dataStatus},{label:`Verification`,value:as(a)}]}),X(`Recommended actions`,a.recommendations),X(`Report caveats`,a.caveats),!a.items.length){process.stdout.write(`No average-position candidates matched this report.
206
- `),process.stdout.write(`${a.ledgerSummary}\n`);return}J([`Query`,`Template`,`Pos`,`Impr`,`CTR`,`Queries`,`Fetch`,`Check`,`Action`],a.items.map(e=>[e.primaryQuery,e.template.label,e.position.toFixed(1),Math.round(e.impressions),e.ctr.toFixed(3),G(e.queryCount),Cs(e.fetchDiagnostics),ws(e.contentVerification?.classification),e.recommendation.action])),Y(`Top second-page actions`,a.items.map(e=>({label:e.primaryQuery,context:`${e.template.label}, pos ${rs(e.position)}, ${G(e.impressions)} impressions`,action:e.recommendation.action}))),process.stdout.write(`${a.ledgerSummary}\n`)}}),ud=E({meta:{name:`seo-to-ai-query`,description:`Convert GSC search queries into AI-style monitoring prompts`},args:{site:{type:`string`},project:{type:`string`,description:`Saved project id or name.`},client:{type:`string`,description:`Legacy alias for --project.`},"start-date":{type:`string`,description:`Exact GSC start date (YYYY-MM-DD). Use with --end-date.`},"end-date":{type:`string`,description:`Exact GSC end date (YYYY-MM-DD). Use with --start-date.`},"max-rows":{type:`string`,description:`Maximum retained GSC query rows. Defaults to 50000.`},"brand-terms":{type:`string`,description:`Comma-separated brand terms to exclude.`},...Z([`days`,`limit`,`minImpressions`,`includeBrand`,`refresh`],{days:{description:`GSC lookback within the current 16-month retention.`},limit:{description:`Maximum source queries to convert. Defaults to 25.`},minImpressions:{description:`Minimum query impressions. Defaults to 20.`},includeBrand:{description:`Include branded queries in prompt generation.`},refresh:{description:`Bypass local GSC cache.`}}),json:{type:`boolean`,default:!1}},run:async({args:e})=>{let t=I(e),n=await U({client:A(e),site:k(e.site),options:{json:t,refresh:j(e.refresh)}}),r=await fn({site:n.site,days:N(e.days,`--days`),startDate:k(e[`start-date`]),endDate:k(e[`end-date`]),limit:N(e.limit,`--limit`),minImpressions:N(e[`min-impressions`],`--min-impressions`),maxRows:N(e[`max-rows`],`--max-rows`),brandTerms:[...n.client?.brandTerms??[],...P(e[`brand-terms`])??[]],includeBrand:j(e[`include-brand`]),refresh:j(e.refresh)});if(t){z(r);return}q({title:`SEO to AI query prompts`,target:r.site,status:r.dataStatus===`available`||r.dataStatus===`empty`?`info`:`unknown`,summary:r.summary.verdict,metrics:[{label:`Evidence`,value:r.dataStatus},{label:`Eligible queries`,value:G(r.summary.eligibleQueries)},{label:`Returned queries`,value:G(r.summary.returnedQueries)},{label:`Prompts`,value:G(r.summary.prompts)},{label:`GSC completeness`,value:r.source.completeness}]}),J([`Query`,`Impr`,`Prompt`],r.items.flatMap(e=>e.prompts.map(t=>[K(e.query,42),G(e.impressions),K(t,86)]))),X(`Report caveats`,r.caveats),X(`Warnings`,r.warnings)}}),dd=E({meta:{name:`audit-page`,description:`Fetch one page and audit title, metadata, content, links, and schema`},args:{url:{type:`string`,required:!0,description:`URL to audit.`},site:{type:`string`,description:`Optional GSC property for page search metrics, for example sc-domain:example.com.`},client:{type:`string`,description:`Legacy alias for --project.`},project:{type:`string`,description:`Saved project id or name.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`},js:{type:`boolean`,default:!1,description:`Force JavaScript rendering for extraction.`},refresh:{type:`boolean`,default:!1,description:`Bypass local HTTP cache.`}},run:async({args:e})=>{let t=A(e),n=k(e.site),r=t?await W({project:t,options:{json:I(e)}}):void 0,i=t||n?void 0:await W({options:{json:I(e)}}),a=await er({url:k(e.url)??``,site:r?.siteUrl??n??i?.siteUrl,js:j(e.js)?!0:`auto`,refresh:j(e.refresh)});if(I(e)){z(a);return}q({title:`Page audit`,target:a.url,status:a.issues.some(e=>e.severity===`high`)?`fail`:a.issues.length>0?`warning`:`pass`,summary:a.issues.length>0?`${a.issues.length} ${a.issues.length===1?`issue needs`:`issues need`} review.`:`No page issues were found by the available checks.`,metrics:[{label:`Final URL`,value:a.page.finalUrl},{label:`Title`,value:a.page.title??`Missing`},{label:`Meta description`,value:a.page.metaDescription??`Missing`},{label:`Words`,value:a.page.wordCount},{label:`Fetch`,value:Cs(a.fetchDiagnostics)}],diagnostics:[{title:`Issues`,items:a.issues.map(e=>({status:e.severity===`high`?`fail`:`warning`,title:`${e.code}: ${e.principle}`,explanation:e.detail}))}]})}});function fd(e){let t=k(e)??`mobile`;if(t!==`mobile`&&t!==`desktop`)throw new w(`INVALID_INPUT`,`Invalid --strategy. Use mobile or desktop.`);return t}function pd(e){return e?e.displayValue??String(e.value??`not measured`):`not measured`}function md(e){if(!e)return`not available`;let t=e.unit===`milliseconds`?`ms`:``;return`${e.p75}${t} (${e.rating})`}const hd=E({meta:{name:`perf`,description:`Audit lab performance and field Core Web Vitals`},subCommands:{audit:E({meta:{name:`audit`,description:`Run a Lighthouse/Core Web Vitals performance audit`},args:{url:{type:`string`,description:`URL to audit. Defaults to the selected project crawl URL.`},client:{type:`string`,description:`Legacy alias for --project.`},project:{type:`string`,description:`Saved project id or name.`},strategy:{type:`string`,default:`mobile`,description:`Audit strategy: mobile or desktop.`},"lighthouse-bin":{type:`string`,description:`Custom Lighthouse binary path. Defaults to the bundled version.`},"crux-key":{type:`string`,description:`CrUX API key. Prefer SEO_CRUX_API_KEY to avoid shell history.`},raw:{type:`boolean`,default:!1,description:`Include the full Lighthouse JSON in JSON output.`},refresh:{type:`boolean`,default:!1,description:`Bypass the local performance report cache.`},json:{type:`boolean`,default:!1}},run:async({args:e})=>{let t=I(e),n=await W({client:A(e),options:{json:t}}),r=k(e.url)??n?.startUrl;if(!r)throw Error(`No URL selected. Pass --url or use --project with a crawl URL.`);let i=await Tn({url:r,strategy:fd(e.strategy),lighthouseBin:k(e[`lighthouse-bin`]),cruxApiKey:k(e[`crux-key`]),refresh:j(e.refresh),includeRaw:t&&j(e.raw)});if(t){z(i);return}q({title:`Performance report`,target:i.url,status:i.dataStatus===`partial`?`unknown`:i.grade===`good`?`pass`:`warning`,summary:i.headline,metrics:[{label:`Source`,value:i.source},{label:`Strategy`,value:i.strategy},{label:`Lab score`,value:i.score===void 0?`Unknown`:`${i.score}/100`},{label:`Lab grade`,value:i.grade},{label:`Evidence`,value:i.dataStatus},{label:`FCP`,value:pd(i.metrics.firstContentfulPaint)},{label:`LCP`,value:pd(i.metrics.largestContentfulPaint)},{label:`TBT`,value:pd(i.metrics.totalBlockingTime)},{label:`CLS`,value:pd(i.metrics.cumulativeLayoutShift)},{label:`INP`,value:pd(i.metrics.interactionToNextPaint)},{label:`Server response`,value:pd(i.metrics.serverResponseTime)},{label:`Fallback fetch`,value:pd(i.metrics.fallbackFetchDuration)},{label:`Field data`,value:i.fieldData?`${i.fieldData.scope} ${i.fieldData.formFactor.toLowerCase()} (${i.fieldData.assessment.status})`:i.fieldDataStatus.status},{label:`Field LCP p75`,value:md(i.fieldData?.metrics.largestContentfulPaint)},{label:`Field INP p75`,value:md(i.fieldData?.metrics.interactionToNextPaint)},{label:`Field CLS p75`,value:md(i.fieldData?.metrics.cumulativeLayoutShift)},{label:`Field period`,value:i.fieldData?.collectionPeriod?.firstDate&&i.fieldData.collectionPeriod.lastDate?`${i.fieldData.collectionPeriod.firstDate} to ${i.fieldData.collectionPeriod.lastDate}`:`Unavailable`}]}),Y(`Top actions`,i.topActions.map(e=>({label:e.title,action:e.action,context:e.plainEnglish}))),X(`Caveats`,i.caveats)}})}}),gd=E({meta:{name:`diagnose`,description:`Find where Google Search performance changed`},args:{site:{type:`string`,description:`GSC property URL, for example sc-domain:example.com.`},client:{type:`string`,description:`Legacy alias for --project.`},project:{type:`string`,description:`Saved project id or name.`},...Z([`days`,`recentDays`,`limit`,`includeBrand`,`refresh`],{days:{description:`Baseline window length in days. Defaults to 90.`},limit:{description:`Maximum rows per diagnostic section. Defaults to 10.`}}),json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=I(e),n=await U({client:A(e),site:k(e.site),options:{json:t,refresh:j(e.refresh)}}),r=await lt({site:n.site,days:M(e.days),recentDays:M(e.recent),limit:M(e.limit),brandTerms:n.client?.brandTerms,includeBrand:j(e[`include-brand`]),refresh:j(e.refresh),progress:Hl(!t)});if(t){z(r);return}q({title:`SEO diagnosis`,target:r.site,status:r.dataStatus===`complete`?r.priorities.length>0?`warning`:`pass`:`unknown`,summary:r.priorities.length>0?`${r.priorities.length} priorities need review.`:`No priorities were returned by the available evidence.`,metrics:[{label:`Evidence`,value:r.dataStatus},{label:`Update overlap`,value:r.summary.updateAttribution.replaceAll(`-`,` `)},{label:`Significant anomalies`,value:r.summary.significantAnomalies},{label:`Update matches`,value:r.summary.updateMatches},{label:`Decay items`,value:r.summary.decayItems},{label:`Cannibal items`,value:r.summary.cannibalItems},{label:`Striking distance`,value:r.summary.strikingDistanceItems},{label:`Quick wins`,value:r.summary.quickWinItems}]}),V([`Priority`,`Confidence`,`Reason`,`Action`],r.priorities.map(e=>[e.label,e.confidence,e.reason,e.action])),r.skippedSections?.length&&V([`Skipped section`,`Reason`],r.skippedSections.map(e=>[e.section,e.reason])),Y(`Priority action details`,r.priorities.map(e=>({label:e.label,context:e.confidence,action:e.action})))}}),_d=E({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 be();if(I(e)){z(t),t.ok||(process.exitCode=1);return}Eo(`SEO doctor`,_o(t.checks)),process.stdout.write(`
207
- `),Do(t.checks),process.stdout.write(`\nChecked ${t.generatedAt}\n`),t.ok||(process.exitCode=1)}}),vd=e=>{let t=k(e)??`page`;if(t!==`page`&&t!==`query`&&t!==`country`&&t!==`device`)throw Error(`Invalid --dimension. Use page, query, country, or device.`);return t},yd=E({meta:{name:`segment-impact`,description:`Compare GSC movement by page, query, device, or country`},args:{site:{type:`string`,description:`GSC property URL, for example sc-domain:example.com.`},client:{type:`string`,description:`Legacy alias for --project.`},project:{type:`string`,description:`Saved project id or name.`},dimension:{type:`string`,default:`page`,description:`Segment by page, query, country, or device.`},...Z([`days`,`limit`,`refresh`],{days:{description:`Recent window length from 1 to 240 days. Defaults to 28.`},limit:{description:`Maximum segment rows to print. Defaults to 25.`},refresh:{description:`Bypass local cache and fetch fresh GSC data.`}}),compare:{type:`string`,description:`Legacy 1-240 day comparison length. Must equal the current window.`},"start-date":{type:`string`,description:`Exact current-window start date in YYYY-MM-DD format.`},"end-date":{type:`string`,description:`Exact current-window end date in YYYY-MM-DD format.`},"max-rows":{type:`string`,description:`Maximum retained GSC rows per window. Defaults to 100000.`},"unmatched-limit":{type:`string`,description:`Maximum one-window evidence rows to return. Defaults to 25.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=I(e),n=vd(e.dimension),r=N(e.days,`--days`),i=N(e.compare,`--compare`),a=N(e.limit,`--limit`),o=N(e[`unmatched-limit`],`--unmatched-limit`),s=N(e[`max-rows`],`--max-rows`),c=await Zt({site:(await U({client:A(e),site:k(e.site),options:{json:t,refresh:j(e.refresh)}})).site,dimension:n,days:r,compareDays:i,startDate:k(e[`start-date`]),endDate:k(e[`end-date`]),limit:a,unmatchedLimit:o,maxRows:s,refresh:j(e.refresh)});if(t){z(c);return}q({title:`Segment impact`,target:c.site,status:c.dataStatus===`complete`?`info`:`unknown`,summary:c.summary.verdict,metrics:[{label:`Dimension`,value:c.dimension},{label:`Evidence`,value:c.dataStatus},{label:`Before`,value:`${c.before.startDate} to ${c.before.endDate}`},{label:`After`,value:`${c.after.startDate} to ${c.after.endDate}`}]}),V([`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(`
205
+ `);return}q([`Type`,`Query`,`Evidence`,`Pos`,`CTR`,`Expected`,`Impr`,`Shortfall`,`Action`],r.items.map(e=>[e.opportunityType,G(e.query,38),e.verification.signals.length?e.verification.signals.join(`, `):e.verification.status,e.position.toFixed(1),as(e.ctr),e.expectedCtr===void 0?`n/a`:as(e.expectedCtr),W(e.impressions),e.estimatedCtrClickShortfall===void 0?`n/a`:W(e.estimatedCtrClickShortfall),G(e.recommendation,72)])),J(`Top page actions`,r.items.map(e=>({label:e.query,context:`${e.opportunityType}, ${W(e.impressions)} impressions`,action:e.recommendation})))}}),cd=T({meta:{name:`query-cluster`,description:`Cluster GSC queries into repeated demand themes`},args:{site:{type:`string`},project:{type:`string`,description:`Saved project id or name.`},client:{type:`string`,description:`Legacy alias for --project.`},scope:{type:`string`},...X([`includeBrand`,`minImpressions`,`limit`],{includeBrand:{description:`Include branded queries in query clustering.`},minImpressions:{description:`Minimum impressions per query. Defaults to 25.`},limit:{description:`Maximum clusters to return. Defaults to 25; max 100.`}}),json:{type:`boolean`,default:!1}},run:async({args:e})=>{let t=F(e),n=await H({client:k(e),site:O(e.site),options:{json:t}}),r=await te({site:n.site,scope:O(e.scope),brand:n.client?.brandTerms?.[0],brandTerms:n.client?.brandTerms,includeBrand:A(e[`include-brand`]),minImpressions:j(e[`min-impressions`]),limit:j(e.limit)});if(t){R(r);return}K({title:`Query clusters`,target:r.site,status:`info`,summary:r.summary.verdict,metrics:[{label:`Scope`,value:r.scope??`All pages`},{label:`Clusters`,value:W(r.summary.clusters)},{label:`Queries`,value:W(r.summary.queries)},{label:`Impressions`,value:W(r.summary.impressions)},{label:`Clicks`,value:W(r.summary.clicks)},{label:`Minimum impressions`,value:W(r.summary.minImpressions)},{label:`Result limit`,value:W(r.summary.limit)},{label:`High opportunity`,value:W(r.summary.highOpportunityClusters)},{label:`Brand queries`,value:r.summary.brandFiltering}]}),Y(`Why this matters`,[`Clusters show repeated demand themes, so they are better inputs for page sections, hubs, and templates than one-off query exports.`,`Prioritise clusters with high impressions and low clicks; they usually reveal unclear intent coverage or weak SERP framing.`]),Y(`Recommended actions`,r.recommendations),Y(`Report caveats`,r.caveats),q([`Cluster`,`Intent`,`Queries`,`Impr`,`CTR`,`Expected`,`Lift`,`Action`],r.clusters.map(e=>[G(e.label,32),e.intent,e.queries.length,W(e.totals?.impressions??0),as(e.totals?.ctr??0),as(e.benchmark?.expectedCtr??0),e.estimatedClickLift===void 0?`-`:W(e.estimatedClickLift),G(e.recommendation??``,72)])),J(`Top cluster actions`,r.clusters.map(e=>({label:e.label,context:`${W(e.totals?.impressions??0)} impressions, ${e.estimatedClickLift===void 0?`ranking opportunity`:`${W(e.estimatedClickLift)} estimated click lift`}`,action:e.recommendation??``})))}}),ld=T({meta:{name:`quick-wins`,description:`Find high-ranking low-CTR query/page opportunities`},args:{site:{type:`string`},project:{type:`string`,description:`Saved project id or name.`},client:{type:`string`,description:`Legacy alias for --project.`},...X([`days`,`limit`,`includeBrand`,`minImpressions`,`verifyContent`,`verifyLimit`,`js`,`fetchConcurrency`,`fetchIntervalCap`,`fetchIntervalMs`,`refresh`]),"brand-terms":{type:`string`,description:`Comma-separated brand terms to exclude.`},json:{type:`boolean`,default:!1}},run:async({args:e})=>{let t=F(e),n=j(e[`verify-limit`]),r=A(e[`verify-content`])===!0||n!==void 0,i=await H({client:k(e),site:O(e.site),options:{json:t}}),a=await At({site:i.site,days:j(e.days),limit:j(e.limit),minImpressions:j(e[`min-impressions`]),brandTerms:[...i.client?.brandTerms??[],...N(e[`brand-terms`])??[]],includeBrand:A(e[`include-brand`]),verifyContent:r,verifyLimit:n,js:A(e.js)?!0:void 0,rate:Ba(e),refresh:A(e.refresh)});if(t){R(a);return}K({title:`Quick wins`,target:a.site,status:a.source.possiblyTruncated?`unknown`:`info`,summary:a.summary.verdict,metrics:[{label:`Eligible rows`,value:W(a.summary.eligibleRows)},{label:`Returned rows`,value:W(a.summary.returnedRows)},{label:`Query clusters`,value:W(a.summary.repeatedQueryGroups)},{label:`Template patterns`,value:W(a.summary.templatePatterns)},{label:`CTR click shortfall`,value:W(a.summary.returnedEstimatedCtrClickShortfall)},{label:`Brand queries`,value:a.summary.brandFiltering},{label:`Evidence`,value:a.dataStatus},{label:`Verification`,value:os(a)}]}),Y(`Why this matters`,[`These rows have GSC average positions from 4 to 10 and observed CTR below a site-peer or versioned fallback target.`,`Page evidence is needed before treating a CTR shortfall as a title, content, or technical finding.`]),Y(`Recommended actions`,a.recommendations),Y(`Report caveats`,a.caveats),a.items.length&&(a.templateRecommendations.length&&(q([`Template`,`URLs`,`Rows`,`Shortfall`,`Impr`,`Action`],a.templateRecommendations.map(e=>[G(e.templateLabel,34),W(e.urlCount),W(e.rowCount),W(e.totalEstimatedCtrClickShortfall),W(e.totalImpressions),G(e.action,72)])),J(`Top template actions`,a.templateRecommendations.map(e=>({label:e.templateLabel,context:`${W(e.urlCount)} URLs, ${W(e.totalEstimatedCtrClickShortfall)} heuristic CTR click shortfall`,action:`${e.action} ${e.evidence}`})))),a.groups.length&&(q([`Cluster`,`URLs`,`Rows`,`Shortfall`,`Impr`,`Sample URL`,`Action`],a.groups.map(e=>[G(e.label,44),W(e.urlCount),W(e.rowCount),W(e.totalEstimatedCtrClickShortfall),W(e.totalImpressions),G(e.sampleUrls[0]??`-`,46),G(e.recommendation,72)])),J(`Top cluster actions`,a.groups.map(e=>({label:e.label,context:`${W(e.urlCount)} URLs, ${W(e.totalEstimatedCtrClickShortfall)} heuristic CTR click shortfall`,action:e.recommendation})))),q([`Query`,`Template`,`URL`,`Pos`,`Impr`,`CTR / target`,`Shortfall`,`Fetch`,`Check`,`Action`],a.items.map(e=>[G(e.query,36),G(e.template.label,24),G(e.url,48),is(e.position),W(e.impressions),`${as(e.ctr)} / ${as(e.targetCtr)}`,W(e.estimatedCtrClickShortfall),ws(e.contentVerification?.fetchDiagnostics),Ts(e.contentVerification?.classification),G(e.recommendation.action,64)])),J(`Top opportunity actions`,a.items.map(e=>({label:e.query,context:`${e.template.label}, pos ${is(e.position)}, ${W(e.impressions)} impressions`,action:e.recommendation.action}))))}}),ud=T({meta:{name:`second-page`,description:`Find URLs averaging positions above 10 through 20 in GSC`},args:{site:{type:`string`,description:`GSC property URL, for example sc-domain:example.com.`},project:{type:`string`,description:`Saved project id or name.`},client:{type:`string`,description:`Legacy alias for --project.`},...X([`range`,`minImpressions`,`limit`,`includeBrand`,`verifyContent`,`verifyLimit`,`js`,`fetchConcurrency`,`fetchIntervalCap`,`fetchIntervalMs`,`refresh`]),json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=F(e),n=j(e[`verify-limit`]),r=A(e[`verify-content`])===!0||n!==void 0,i=await H({client:k(e),site:O(e.site),options:{json:t,refresh:A(e.refresh)}}),a=await ln({site:i.site,range:j(e.days),minImpressions:j(e[`min-impressions`]),limit:O(e.limit)?Number(O(e.limit)):10,brandTerms:i.client?.brandTerms,includeBrand:A(e[`include-brand`]),verifyContent:r,verifyLimit:n,js:A(e.js)?!0:void 0,rate:Ba(e),refresh:A(e.refresh)});if(t){R(a);return}if(K({title:`Second-page opportunities`,target:a.site,status:a.source.possiblyTruncated||a.summary.fetchFailures>0?`unknown`:`info`,summary:a.summary.verdict,metrics:[{label:`Eligible pages`,value:W(a.summary.eligiblePages)},{label:`Returned pages`,value:W(a.summary.returnedPages)},{label:`Eligible queries`,value:W(a.summary.eligibleQueries)},{label:`Impressions`,value:W(a.summary.eligibleImpressions)},{label:`Content issues`,value:W(a.summary.contentIssues)},{label:`Technical issues`,value:W(a.summary.technicalIssues)},{label:`Fetch failures`,value:W(a.summary.fetchFailures)},{label:`Brand queries`,value:a.summary.brandFiltering},{label:`Evidence`,value:a.dataStatus},{label:`Verification`,value:os(a)}]}),Y(`Recommended actions`,a.recommendations),Y(`Report caveats`,a.caveats),!a.items.length){process.stdout.write(`No average-position candidates matched this report.
206
+ `),process.stdout.write(`${a.ledgerSummary}\n`);return}q([`Query`,`Template`,`Pos`,`Impr`,`CTR`,`Queries`,`Fetch`,`Check`,`Action`],a.items.map(e=>[e.primaryQuery,e.template.label,e.position.toFixed(1),Math.round(e.impressions),e.ctr.toFixed(3),W(e.queryCount),ws(e.fetchDiagnostics),Ts(e.contentVerification?.classification),e.recommendation.action])),J(`Top second-page actions`,a.items.map(e=>({label:e.primaryQuery,context:`${e.template.label}, pos ${is(e.position)}, ${W(e.impressions)} impressions`,action:e.recommendation.action}))),process.stdout.write(`${a.ledgerSummary}\n`)}}),dd=T({meta:{name:`seo-to-ai-query`,description:`Convert GSC search queries into AI-style monitoring prompts`},args:{site:{type:`string`},project:{type:`string`,description:`Saved project id or name.`},client:{type:`string`,description:`Legacy alias for --project.`},"start-date":{type:`string`,description:`Exact GSC start date (YYYY-MM-DD). Use with --end-date.`},"end-date":{type:`string`,description:`Exact GSC end date (YYYY-MM-DD). Use with --start-date.`},"max-rows":{type:`string`,description:`Maximum retained GSC query rows. Defaults to 50000.`},"brand-terms":{type:`string`,description:`Comma-separated brand terms to exclude.`},...X([`days`,`limit`,`minImpressions`,`includeBrand`,`refresh`],{days:{description:`GSC lookback within the current 16-month retention.`},limit:{description:`Maximum source queries to convert. Defaults to 25.`},minImpressions:{description:`Minimum query impressions. Defaults to 20.`},includeBrand:{description:`Include branded queries in prompt generation.`},refresh:{description:`Bypass local GSC cache.`}}),json:{type:`boolean`,default:!1}},run:async({args:e})=>{let t=F(e),n=await H({client:k(e),site:O(e.site),options:{json:t,refresh:A(e.refresh)}}),r=await pn({site:n.site,days:M(e.days,`--days`),startDate:O(e[`start-date`]),endDate:O(e[`end-date`]),limit:M(e.limit,`--limit`),minImpressions:M(e[`min-impressions`],`--min-impressions`),maxRows:M(e[`max-rows`],`--max-rows`),brandTerms:[...n.client?.brandTerms??[],...N(e[`brand-terms`])??[]],includeBrand:A(e[`include-brand`]),refresh:A(e.refresh)});if(t){R(r);return}K({title:`SEO to AI query prompts`,target:r.site,status:r.dataStatus===`available`||r.dataStatus===`empty`?`info`:`unknown`,summary:r.summary.verdict,metrics:[{label:`Evidence`,value:r.dataStatus},{label:`Eligible queries`,value:W(r.summary.eligibleQueries)},{label:`Returned queries`,value:W(r.summary.returnedQueries)},{label:`Prompts`,value:W(r.summary.prompts)},{label:`GSC completeness`,value:r.source.completeness}]}),q([`Query`,`Impr`,`Prompt`],r.items.flatMap(e=>e.prompts.map(t=>[G(e.query,42),W(e.impressions),G(t,86)]))),Y(`Report caveats`,r.caveats),Y(`Warnings`,r.warnings)}}),fd=T({meta:{name:`audit-page`,description:`Fetch one page and audit title, metadata, content, links, and schema`},args:{url:{type:`string`,required:!0,description:`URL to audit.`},site:{type:`string`,description:`Optional GSC property for page search metrics, for example sc-domain:example.com.`},client:{type:`string`,description:`Legacy alias for --project.`},project:{type:`string`,description:`Saved project id or name.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`},js:{type:`boolean`,default:!1,description:`Force JavaScript rendering for extraction.`},refresh:{type:`boolean`,default:!1,description:`Bypass local HTTP cache.`}},run:async({args:e})=>{let t=k(e),n=O(e.site),r=t?await U({project:t,options:{json:F(e)}}):void 0,i=t||n?void 0:await U({options:{json:F(e)}}),a=await tr({url:O(e.url)??``,site:r?.siteUrl??n??i?.siteUrl,js:A(e.js)?!0:`auto`,refresh:A(e.refresh)});if(F(e)){R(a);return}K({title:`Page audit`,target:a.url,status:a.issues.some(e=>e.severity===`high`)?`fail`:a.issues.length>0?`warning`:`pass`,summary:a.issues.length>0?`${a.issues.length} ${a.issues.length===1?`issue needs`:`issues need`} review.`:`No page issues were found by the available checks.`,metrics:[{label:`Final URL`,value:a.page.finalUrl},{label:`Title`,value:a.page.title??`Missing`},{label:`Meta description`,value:a.page.metaDescription??`Missing`},{label:`Words`,value:a.page.wordCount},{label:`Fetch`,value:ws(a.fetchDiagnostics)}],diagnostics:[{title:`Issues`,items:a.issues.map(e=>({status:e.severity===`high`?`fail`:`warning`,title:`${e.code}: ${e.principle}`,explanation:e.detail}))}]})}});function pd(e){let t=O(e)??`mobile`;if(t!==`mobile`&&t!==`desktop`)throw new C(`INVALID_INPUT`,`Invalid --strategy. Use mobile or desktop.`);return t}function md(e){return e?e.displayValue??String(e.value??`not measured`):`not measured`}function hd(e){if(!e)return`not available`;let t=e.unit===`milliseconds`?`ms`:``;return`${e.p75}${t} (${e.rating})`}const gd=T({meta:{name:`perf`,description:`Audit lab performance and field Core Web Vitals`},subCommands:{audit:T({meta:{name:`audit`,description:`Run a Lighthouse/Core Web Vitals performance audit`},args:{url:{type:`string`,description:`URL to audit. Defaults to the selected project crawl URL.`},client:{type:`string`,description:`Legacy alias for --project.`},project:{type:`string`,description:`Saved project id or name.`},strategy:{type:`string`,default:`mobile`,description:`Audit strategy: mobile or desktop.`},"lighthouse-bin":{type:`string`,description:`Custom Lighthouse binary path. Defaults to the bundled version.`},"crux-key":{type:`string`,description:`CrUX API key. Prefer SEO_CRUX_API_KEY to avoid shell history.`},raw:{type:`boolean`,default:!1,description:`Include the full Lighthouse JSON in JSON output.`},refresh:{type:`boolean`,default:!1,description:`Bypass the local performance report cache.`},json:{type:`boolean`,default:!1}},run:async({args:e})=>{let t=F(e),n=await U({client:k(e),options:{json:t}}),r=O(e.url)??n?.startUrl;if(!r)throw Error(`No URL selected. Pass --url or use --project with a crawl URL.`);let i=await En({url:r,strategy:pd(e.strategy),lighthouseBin:O(e[`lighthouse-bin`]),cruxApiKey:O(e[`crux-key`]),refresh:A(e.refresh),includeRaw:t&&A(e.raw)});if(t){R(i);return}K({title:`Performance report`,target:i.url,status:i.dataStatus===`partial`?`unknown`:i.grade===`good`?`pass`:`warning`,summary:i.headline,metrics:[{label:`Source`,value:i.source},{label:`Strategy`,value:i.strategy},{label:`Lab score`,value:i.score===void 0?`Unknown`:`${i.score}/100`},{label:`Lab grade`,value:i.grade},{label:`Evidence`,value:i.dataStatus},{label:`FCP`,value:md(i.metrics.firstContentfulPaint)},{label:`LCP`,value:md(i.metrics.largestContentfulPaint)},{label:`TBT`,value:md(i.metrics.totalBlockingTime)},{label:`CLS`,value:md(i.metrics.cumulativeLayoutShift)},{label:`INP`,value:md(i.metrics.interactionToNextPaint)},{label:`Server response`,value:md(i.metrics.serverResponseTime)},{label:`Fallback fetch`,value:md(i.metrics.fallbackFetchDuration)},{label:`Field data`,value:i.fieldData?`${i.fieldData.scope} ${i.fieldData.formFactor.toLowerCase()} (${i.fieldData.assessment.status})`:i.fieldDataStatus.status},{label:`Field LCP p75`,value:hd(i.fieldData?.metrics.largestContentfulPaint)},{label:`Field INP p75`,value:hd(i.fieldData?.metrics.interactionToNextPaint)},{label:`Field CLS p75`,value:hd(i.fieldData?.metrics.cumulativeLayoutShift)},{label:`Field period`,value:i.fieldData?.collectionPeriod?.firstDate&&i.fieldData.collectionPeriod.lastDate?`${i.fieldData.collectionPeriod.firstDate} to ${i.fieldData.collectionPeriod.lastDate}`:`Unavailable`}]}),J(`Top actions`,i.topActions.map(e=>({label:e.title,action:e.action,context:e.plainEnglish}))),Y(`Caveats`,i.caveats)}})}}),_d=T({meta:{name:`diagnose`,description:`Find where Google Search performance changed`},args:{site:{type:`string`,description:`GSC property URL, for example sc-domain:example.com.`},client:{type:`string`,description:`Legacy alias for --project.`},project:{type:`string`,description:`Saved project id or name.`},...X([`days`,`recentDays`,`limit`,`includeBrand`,`refresh`],{days:{description:`Baseline window length in days. Defaults to 90.`},limit:{description:`Maximum rows per diagnostic section. Defaults to 10.`}}),json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=F(e),n=await H({client:k(e),site:O(e.site),options:{json:t,refresh:A(e.refresh)}}),r=await ut({site:n.site,days:j(e.days),recentDays:j(e.recent),limit:j(e.limit),brandTerms:n.client?.brandTerms,includeBrand:A(e[`include-brand`]),refresh:A(e.refresh),progress:Ul(!t)});if(t){R(r);return}K({title:`SEO diagnosis`,target:r.site,status:r.dataStatus===`complete`?r.priorities.length>0?`warning`:`pass`:`unknown`,summary:r.priorities.length>0?`${r.priorities.length} priorities need review.`:`No priorities were returned by the available evidence.`,metrics:[{label:`Evidence`,value:r.dataStatus},{label:`Update overlap`,value:r.summary.updateAttribution.replaceAll(`-`,` `)},{label:`Significant anomalies`,value:r.summary.significantAnomalies},{label:`Update matches`,value:r.summary.updateMatches},{label:`Decay items`,value:r.summary.decayItems},{label:`Cannibal items`,value:r.summary.cannibalItems},{label:`Striking distance`,value:r.summary.strikingDistanceItems},{label:`Quick wins`,value:r.summary.quickWinItems}]}),B([`Priority`,`Confidence`,`Reason`,`Action`],r.priorities.map(e=>[e.label,e.confidence,e.reason,e.action])),r.skippedSections?.length&&B([`Skipped section`,`Reason`],r.skippedSections.map(e=>[e.section,e.reason])),J(`Priority action details`,r.priorities.map(e=>({label:e.label,context:e.confidence,action:e.action})))}}),vd=T({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 xe();if(F(e)){R(t),t.ok||(process.exitCode=1);return}Do(`SEO doctor`,vo(t.checks)),process.stdout.write(`
207
+ `),Oo(t.checks),process.stdout.write(`\nChecked ${t.generatedAt}\n`),t.ok||(process.exitCode=1)}}),yd=e=>{let t=O(e)??`page`;if(t!==`page`&&t!==`query`&&t!==`country`&&t!==`device`)throw Error(`Invalid --dimension. Use page, query, country, or device.`);return t},bd=T({meta:{name:`segment-impact`,description:`Compare GSC movement by page, query, device, or country`},args:{site:{type:`string`,description:`GSC property URL, for example sc-domain:example.com.`},client:{type:`string`,description:`Legacy alias for --project.`},project:{type:`string`,description:`Saved project id or name.`},dimension:{type:`string`,default:`page`,description:`Segment by page, query, country, or device.`},...X([`days`,`limit`,`refresh`],{days:{description:`Recent window length from 1 to 240 days. Defaults to 28.`},limit:{description:`Maximum segment rows to print. Defaults to 25.`},refresh:{description:`Bypass local cache and fetch fresh GSC data.`}}),compare:{type:`string`,description:`Legacy 1-240 day comparison length. Must equal the current window.`},"start-date":{type:`string`,description:`Exact current-window start date in YYYY-MM-DD format.`},"end-date":{type:`string`,description:`Exact current-window end date in YYYY-MM-DD format.`},"max-rows":{type:`string`,description:`Maximum retained GSC rows per window. Defaults to 100000.`},"unmatched-limit":{type:`string`,description:`Maximum one-window evidence rows to return. Defaults to 25.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=F(e),n=yd(e.dimension),r=M(e.days,`--days`),i=M(e.compare,`--compare`),a=M(e.limit,`--limit`),o=M(e[`unmatched-limit`],`--unmatched-limit`),s=M(e[`max-rows`],`--max-rows`),c=await Qt({site:(await H({client:k(e),site:O(e.site),options:{json:t,refresh:A(e.refresh)}})).site,dimension:n,days:r,compareDays:i,startDate:O(e[`start-date`]),endDate:O(e[`end-date`]),limit:a,unmatchedLimit:o,maxRows:s,refresh:A(e.refresh)});if(t){R(c);return}K({title:`Segment impact`,target:c.site,status:c.dataStatus===`complete`?`info`:`unknown`,summary:c.summary.verdict,metrics:[{label:`Dimension`,value:c.dimension},{label:`Evidence`,value:c.dataStatus},{label:`Before`,value:`${c.before.startDate} to ${c.before.endDate}`},{label:`After`,value:`${c.after.startDate} to ${c.after.endDate}`}]}),B([`Segment`,`Clicks before`,`Clicks after`,`Delta`,`Pos delta`],c.items.map(e=>[e.key,e.beforeClicks,e.afterClicks,e.clickDelta,e.positionDelta??`-`])),c.unmatchedSegments.length&&(process.stdout.write(`
208
208
  One-window evidence (not treated as zero)
209
- `),V([`Segment`,`Retained in`,`Clicks`,`Impressions`],c.unmatchedSegments.map(e=>[e.key,e.retainedIn,e.clicks,e.impressions]))),X(`Warnings`,c.warnings)}});function bd(e){return`${G(e)} ${e===1?`row`:`rows`}`}const xd=E({meta:{name:`striking-distance`,description:`Find query/page rows averaging positions above 10 through 20`},args:{site:{type:`string`,description:`GSC property URL, for example sc-domain:example.com.`},client:{type:`string`,description:`Legacy alias for --project.`},project:{type:`string`,description:`Saved project id or name.`},...Z([`days`,`minImpressions`,`limit`,`includeBrand`,`verifyContent`,`verifyLimit`,`js`,`fetchConcurrency`,`fetchIntervalCap`,`fetchIntervalMs`,`refresh`]),json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=I(e),n=M(e[`verify-limit`]),r=j(e[`verify-content`])===!0||n!==void 0,i=await U({client:A(e),site:k(e.site),options:{json:t,refresh:j(e.refresh)}}),a=await xt({site:i.site,days:M(e.days),minImpressions:M(e[`min-impressions`]),limit:M(e.limit),brandTerms:i.client?.brandTerms,includeBrand:j(e[`include-brand`]),verifyContent:r,verifyLimit:n,js:j(e.js)?!0:void 0,rate:za(e),refresh:j(e.refresh)});if(t){z(a);return}if(q({title:`Striking-distance opportunities`,target:a.site,status:a.source.possiblyTruncated?`unknown`:`info`,summary:a.summary.verdict,metrics:[{label:`Eligible rows`,value:G(a.summary.eligibleRows)},{label:`Returned rows`,value:G(a.summary.returnedRows)},{label:`Template groups`,value:G(a.summary.groups)},{label:`Impressions`,value:G(a.summary.eligibleImpressions)},{label:`Brand queries`,value:a.summary.brandFiltering},{label:`Verification`,value:a.verification.requested?`Requested`:`Off`},{label:`Evidence`,value:a.dataStatus}]}),X(`Why this matters`,[`These rows have a GSC average position above 10 and at most 20. Treat them as candidates for investigation, not guaranteed page-two rankings.`,`Groups use all eligible rows. A shared-template label is only a candidate until recurring evidence is verified across distinct URLs.`]),X(`Recommended actions`,a.recommendations),X(`Report caveats`,a.caveats),a.groups.length&&(J([`Group`,`Rows`,`URLs`,`Impr`,`Avg pos`,`Scope`,`Action`],a.groups.map(e=>[K(e.label,36),G(e.rowCount),G(e.uniqueUrls),G(e.totalImpressions),rs(e.impressionWeightedPosition),e.actionScope,K(e.recommendation.action,72)])),Y(`Top striking-distance group actions`,a.groups.map(e=>({label:e.label,context:`${bd(e.rowCount)}, ${G(e.totalImpressions)} impressions`,action:e.recommendation.action})))),!a.items.length){process.stdout.write(`No striking-distance opportunities matched these filters.
210
- `);return}J([`Query`,`Template`,`URL`,`Impr`,`CTR`,`Pos`,`Score`,`Fetch`,`Check`,`Action`],a.items.map(e=>[K(e.query,36),K(e.template.label,24),K(e.url,48),G(e.impressions),is(e.ctr),rs(e.position),e.priority.score,Cs(e.contentVerification?.fetchDiagnostics),ws(e.contentVerification?.classification),K(e.recommendation.action,72)])),Y(`Top striking-distance actions`,a.items.map(e=>({label:e.query,context:`${e.template.label}, pos ${rs(e.position)}, ${G(e.impressions)} impressions`,action:e.recommendation.action})))}});function Sd(e){return e===`keychain`?`system keychain`:e===`file`?`private local file`:e??`missing`}function Q(e){return e===null?`unavailable`:`$${(e/1e6).toFixed(2)}`}function Cd(e,t,n={}){if(e===void 0)return;if(k(e)?.trim().toLowerCase()===`off`)return n.notice?0:null;let r=M(e);if(r===void 0||r<0||r>1e6)throw new w(`INVALID_INPUT`,`${t} must be a USD amount from 0 to 1000000, or off.`);let i=Math.round(r*1e6);if(!Number.isSafeInteger(i))throw new w(`INVALID_INPUT`,`${t} is too precise.`);return i}function wd(e,t,n,r){if(e===void 0)return;let i=M(e);if(i===void 0||!Number.isInteger(i)||i<n||i>r)throw new w(`INVALID_INPUT`,`${t} must be an integer from ${n} to ${r}.`);return i}function Td(e){return E({meta:{name:`limits`,description:`Show or change local ${e.displayName} spend limits`},args:{"daily-notice":{type:`string`,description:`UTC daily notice in USD, or off.`},"daily-limit":{type:`string`,description:`UTC daily hard limit in USD, or off.`},"monthly-limit":{type:`string`,description:`UTC monthly hard limit in USD, or off.`},requests:{type:`string`,description:`Maximum provider requests in one report run.`},rows:{type:`string`,description:`Maximum requested provider rows in one report run.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:t})=>{let n=ft(e.provider),r=Cd(t[`daily-notice`],`--daily-notice`,{notice:!0}),i=Cd(t[`daily-limit`],`--daily-limit`),a=Cd(t[`monthly-limit`],`--monthly-limit`),o=wd(t.requests,`--requests`,1,100),s=wd(t.rows,`--rows`,1,1e5),c=[r,i,a,o,s].some(e=>e!==void 0),l=c?ue(e.provider,{dailyNoticeMicros:r===void 0?n.dailyNoticeMicros:r,dailyHardLimitMicros:i===void 0?n.dailyHardLimitMicros:i,monthlyHardLimitMicros:a===void 0?n.monthlyHardLimitMicros:a,maxRequestsPerReport:o??n.maxRequestsPerReport,maxRowsPerReport:s??n.maxRowsPerReport}):n,u={provider:e.provider,changed:c,limits:l};if(I(t)){z(u);return}B([[`UTC daily notice`,l.dailyNoticeMicros===0?`off`:Q(l.dailyNoticeMicros)],[`UTC daily hard limit`,l.dailyHardLimitMicros===null?`off`:Q(l.dailyHardLimitMicros)],[`UTC monthly hard limit`,l.monthlyHardLimitMicros===null?`off`:Q(l.monthlyHardLimitMicros)],[`Requests per report`,String(l.maxRequestsPerReport)],[`Rows per report`,String(l.maxRowsPerReport)]])}})}function Ed(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 Dd=E({meta:{name:`connect`,description:`Validate and save an Ahrefs API v3 key`},args:{json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{if(!R({json:I(e)}))throw new w(`AUTH_REQUIRED`,`Run \`seo providers ahrefs connect\` in a terminal. Agents and CI can set ${jr}.`);Ei(`Connect Ahrefs`),D(`Paste an API v3 key from Ahrefs Account settings. The validation check and Domain Rating are free; other research can consume API units.`,`API key`);let t=H(await ji({message:`Ahrefs API v3 key`,validate:e=>e?.trim()?void 0:`API key is required`})),n=await new yr({apiKey:t}).limitsAndUsage(),r=await et(t);D(`${n.subscription} subscription. API key units: ${Ed(n.apiKeyUnits)}.`,`Connection verified`),Ai(`Saved in the ${Sd(r)}. Run seo providers ahrefs status --check to verify it again.`)}}),Od=E({meta:{name:`status`,description:`Show the local Ahrefs connection`},args:{check:{type:`boolean`,default:!1,description:`Verify the API key with the free account endpoint.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=await Dr(),n=!!e.check,r=n&&t?await new yr().limitsAndUsage():void 0,i={connected:!!t,apiVersion:t?3:null,credentialSource:t?.source,liveCheck:r?{status:`passed`,subscription:r.subscription,apiKeyExpiresAt:r.apiKeyExpiresAt,usageResetsAt:r.usageResetsAt,apiKeyUnits:r.apiKeyUnits,workspaceUnits:r.workspaceUnits,observedAt:r.observedAt,requestCostUnits:r.requestCostUnits}:{status:n?`unavailable`:`not-requested`}};if(I(e)){z(i);return}B([[`Connected`,i.connected?`yes`:`no`],[`API version`,i.apiVersion?`Version 3`:`not connected`],[`Credential`,Sd(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`,Ed(r.apiKeyUnits)],[`Workspace units`,Ed(r.workspaceUnits)],[`Usage resets`,r.usageResetsAt],[`Key expires`,r.apiKeyExpiresAt]]:[]])}}),kd=E({meta:{name:`disconnect`,description:`Remove the saved Ahrefs API v3 key`},args:{json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{await Vt();let t=!!process.env[jr]?.trim(),n={savedCredentialRemoved:!0,environmentCredential:t?`active`:`missing`,note:t?`The environment variable was not changed. Clear ${jr} to fully disconnect.`:`Ahrefs is disconnected.`};I(e)?z(n):process.stdout.write(`${n.note}\n`)}}),Ad=E({meta:{name:`ahrefs`,description:`Connect Ahrefs for optional search and link data`},subCommands:{connect:Dd,status:Od,limits:E({meta:{name:`limits`,description:`Show or change local Ahrefs report work limits`},args:{requests:{type:`string`,description:`Maximum Ahrefs requests in one report run.`},rows:{type:`string`,description:`Maximum requested Ahrefs rows in one report run.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=ft(`ahrefs`),n=wd(e.requests,`--requests`,1,100),r=wd(e.rows,`--rows`,1,1e5),i=n!==void 0||r!==void 0,a=i?ue(`ahrefs`,{...t,maxRequestsPerReport:n??t.maxRequestsPerReport,maxRowsPerReport:r??t.maxRowsPerReport}):t,o={provider:`ahrefs`,maxRequestsPerReport:a.maxRequestsPerReport,maxRowsPerReport:a.maxRowsPerReport,changed:i,note:`Paid requests also preflight the live API-unit balance and enforce fixed per-request and per-report unit caps.`};if(I(e)){z(o);return}B([[`Requests per report`,String(o.maxRequestsPerReport)],[`Rows per report`,String(o.maxRowsPerReport)],[`Changed`,o.changed?`yes`:`no`],[`Note`,o.note]])}}),disconnect:kd}});function jd(e){return e.filter(e=>e.isVerified)}function Md(e){if(e.startsWith(`sc-domain:`))return e.slice(10).replace(/^www\./,``).toLowerCase();try{return new URL(e).hostname.replace(/^www\./,``).toLowerCase()}catch{return}}function Nd(e,t){let n=Md(e);if(!n)return;let r=jd(t).filter(e=>Md(e.url)===n);return r.length===1?r[0]:void 0}async function Pd(e){let t=jd(e.sites),n=e.explicitSite??e.savedSite;if(n){let e=t.find(e=>e.url===n);if(!e)throw new w(`PROPERTY_NOT_FOUND`,`Bing Webmaster did not return a verified site matching ${n}.`);return e.url}let r=e.projectSite?Nd(e.projectSite,t):void 0;if(r)return r.url;if(t.length===1)return t[0]?.url;if(t.length!==0){if(!R({json:e.json}))throw new w(`INVALID_INPUT`,`Several verified Bing sites are available. Pass --site to choose one.`);return H(await Mi({message:`Choose a verified Bing Webmaster site`,options:t.map(e=>({value:e.url,label:e.url}))}))}}const Fd={project:{type:`string`,description:`Saved project id or name.`},client:{type:`string`,description:`Legacy alias for --project.`},site:{type:`string`,description:`Verified Bing Webmaster site URL.`}},Id=E({meta:{name:`bing`,description:`Connect and report on Bing Webmaster`},subCommands:{connect:E({meta:{name:`connect`,description:`Connect Bing Webmaster and attach a verified site`},args:{...Fd,json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=I(e);if(!R({json:t}))throw new w(`AUTH_REQUIRED`,"Run `seo providers bing connect` in a terminal. Agents and CI can set SEO_BING_API_KEY and run the report directly.");Ei(`Connect Bing Webmaster`),D(`Paste the API key from Bing Webmaster Tools Settings, then API Access. The key stays on this machine.`,`API key`);let n=H(await ji({message:`Bing Webmaster API key`,validate:e=>e?.trim()?void 0:`API key is required`})),r=await new kn({apiKey:n}).listSites(),i=await W({project:A(e),options:{json:t}}),a=await Pd({explicitSite:k(e.site),projectSite:i?.siteUrl,savedSite:ge(i),sites:r.sites,json:t});if(!a)throw new w(`PROPERTY_NOT_FOUND`,`No verified Bing Webmaster sites were found for this API key.`);await Xe(or,n);let o=i?Rn(i.id,a):void 0;D(`${a}${o?` is attached to ${o.name}.`:` is ready for direct reports.`}`,`Bing connected`),Ai(o?`Run seo providers bing report --project ${o.id}`:`Run seo providers bing report --site ${a}`)}}),status:E({meta:{name:`status`,description:`Show the local Bing connection`},args:{project:{type:`string`,description:`Saved project id or name.`},client:{type:`string`,description:`Legacy alias for --project.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=await Ee({name:or,envVar:Un}),n=await W({project:A(e),options:{json:I(e)}}),r={connected:!!t,credentialSource:t?.source,project:n?{id:n.id,name:n.name,site:ge(n)}:void 0};if(I(e)){z(r);return}B([[`Connected`,r.connected?`yes`:`no`],[`Credential`,r.credentialSource??`missing`],[`Project`,r.project?.name??`not selected`],[`Bing site`,r.project?.site??`not attached`]])}}),disconnect:E({meta:{name:`disconnect`,description:`Remove the saved Bing Webmaster credential`},args:{json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{await me(or);let t={disconnected:!0,note:`Saved project site mappings were kept for a later reconnect.`};I(e)?z(t):process.stdout.write(`${t.note}\n`)}}),sites:E({meta:{name:`sites`,description:`List Bing Webmaster sites`},args:{json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let{client:t,credentialSource:n}=await Gt(),r=await t.listSites(),i={...r,credentialSource:n};if(I(e)){z(i);return}V([`Site`,`Verified`],r.sites.map(e=>[e.url,e.isVerified?`yes`:`no`]))}}),report:E({meta:{name:`report`,description:`Find Bing traffic, crawl, query, and page insights`},args:{...Fd,json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=I(e),n=await W({project:A(e),options:{json:t}}),r=k(e.site)??ge(n);if(!r)throw new w(`INVALID_INPUT`,`Pass --site or connect Bing Webmaster to a saved project first.`);let i=await Rt({site:r});if(t){z(i);return}let a=i.traffic.status===`unavailable`?void 0:i.traffic.data,o=i.crawl.status===`unavailable`?void 0:i.crawl.data,s=a?.analysis;q({title:`Bing Webmaster report`,target:i.site,status:i.dataStatus===`complete`?i.summary.warnings>0?`warning`:`pass`:`unknown`,summary:i.summary.findings>0?`${i.summary.findings} findings need review across Bing traffic and crawl evidence.`:`No findings were returned by the available Bing evidence.`,metrics:[{label:`Evidence`,value:i.dataStatus},{label:`28-day clicks`,value:s?.current.clicks??`Unavailable`},{label:`Click change`,value:s?.changes.clicksPercent===null||s?.changes.clicksPercent===void 0?`Unavailable`:`${s.changes.clicksPercent.toFixed(1)}%`},{label:`28-day impressions`,value:s?.current.impressions??`Unavailable`},{label:`Impression change`,value:s?.changes.impressionsPercent===null||s?.changes.impressionsPercent===void 0?`Unavailable`:`${s.changes.impressionsPercent.toFixed(1)}%`},{label:`Crawled pages`,value:o?.latest?.crawledPages??`Unavailable`},{label:`4xx responses`,value:o?.latest?.code4xx??`Unavailable`},{label:`Findings`,value:i.summary.findings}],diagnostics:[{title:`Review first`,items:i.findings.slice(0,5).map(e=>({status:e.severity===`warning`?`warning`:`info`,title:e.title,explanation:e.interpretation,evidence:[e.verification]}))}],notes:i.caveats})}})}}),Ld=E({meta:{name:`connect`,description:`Validate and save DataForSEO API credentials`},args:{json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{if(!R({json:I(e)}))throw new w(`AUTH_REQUIRED`,`Run \`seo providers dataforseo connect\` in a terminal. Agents and CI can set ${bn} and ${Ae}.`);Ei(`Connect DataForSEO`),D(`Use the API login and API password from DataForSEO API Access. The validation call is free; later research reports can make charged requests.`,`API credentials`);let t=H(await Ni({message:`DataForSEO API login`,validate:e=>e?.trim()?void 0:`API login is required`})),n=H(await ji({message:`DataForSEO API password`,validate:e=>e?void 0:`API password is required`})),r={login:t.trim(),password:n},i=await new o({credentials:()=>r}).userData(),a=await S(r);D(`${i.login} is connected with an account balance of ${Q(i.balanceMicros)}.`,`Connection verified`),Ai(`Saved in the ${Sd(a)}. Run seo providers dataforseo status --check to verify it again.`)}}),Rd=E({meta:{name:`status`,description:`Show the local DataForSEO connection`},args:{check:{type:`boolean`,default:!1,description:`Verify credentials with the free account endpoint.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=await He(),n=!!e.check,r=n&&t?await new o().userData():void 0,i={connected:!!t,credentialSource:t?.source,migratedLegacyCredentials:t?.migrated??!1,liveCheck:r?{status:`passed`,account:r.login,timezone:r.timezone,balanceMicros:r.balanceMicros,observedAt:r.observedAt,requestCostMicros:r.requestCostMicros}:{status:n?`unavailable`:`not-requested`}};if(I(e)){z(i);return}B([[`Connected`,i.connected?`yes`:`no`],[`Credential`,Sd(i.credentialSource)],[`Live check`,i.liveCheck.status===`passed`?`passed at ${i.liveCheck.observedAt}`:i.liveCheck.status===`unavailable`?`not available without credentials`:`not requested; pass --check to verify`],...r?[[`Account`,r.login],[`Balance`,Q(r.balanceMicros)]]:[]])}}),zd=E({meta:{name:`disconnect`,description:`Remove saved DataForSEO credentials`},args:{json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{await Pe();let t=!!process.env[bn]?.trim(),n=!!process.env[Ae],r={savedCredentialsRemoved:!0,environmentCredentials:t&&n?`active`:t||n?`partial`:`missing`,note:t||n?`Environment variables were not changed. Clear ${bn} and ${Ae} to fully disconnect.`:`DataForSEO is disconnected.`};I(e)?z(r):process.stdout.write(`${r.note}\n`)}}),Bd=E({meta:{name:`dataforseo`,description:`Connect DataForSEO for optional search data`},subCommands:{connect:Ld,status:Rd,limits:Td({provider:`dataforseo`,displayName:`DataForSEO`}),spend:E({meta:{name:`spend`,description:`Show local DataForSEO spend and account context`},args:{account:Pa(`Read account-wide context from the free endpoint.`,`Show only the local spend ledger.`),json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=u(`dataforseo`),n=e.account?await He():void 0,r;if(!e.account)r={status:`not-requested`};else if(!n)r={status:`unavailable`,reason:`DataForSEO is not connected. Local spend remains available without account context.`};else try{let e=await new o().userData();r={status:`observed`,account:e.login,balanceMicros:e.balanceMicros,dailySpendMicros:e.accountDailySpendMicros,dailySpendPeriod:e.accountDailySpendPeriod,dailyLimitMicros:e.accountDailyLimitMicros,observedAt:e.observedAt,requestCostMicros:e.requestCostMicros,semantics:`Account values include every client using these credentials. Local values include only requests recorded by this installation.`}}catch(e){r={status:`unavailable`,reason:e instanceof Error?e.message:`DataForSEO account context is unavailable.`}}let i={local:t,account:r};if(I(e)){z(i);return}B([[`Local today (UTC)`,`${Q(t.today.effectiveCostMicros)} across ${t.today.requests} requests`],[`Local month (UTC)`,`${Q(t.month.effectiveCostMicros)} across ${t.month.requests} requests`],[`Unknown local cost`,Q(t.month.estimatedOrUnknownCostMicros)],[`Account today`,r.status===`observed`?Q(r.dailySpendMicros):r.status===`unavailable`?r.reason:`not requested`],[`Account balance`,r.status===`observed`?Q(r.balanceMicros):`unavailable`]]),r.status===`observed`&&process.stdout.write(`${r.semantics}\n`)}}),disconnect:zd}});function Vd(e){if(!e)return{};let t;try{t=JSON.parse(e)}catch{throw new w(`INVALID_INPUT`,`--account must be a JSON object.`)}if(!t||Array.isArray(t)||typeof t!=`object`)throw new w(`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 w(`INVALID_INPUT`,`--account must contain at most 20 bounded string fields.`);return Object.fromEntries(n.map(([e,t])=>[e,t.trim()]))}function Hd(e){if(!e)return{};let t;try{t=JSON.parse(e)}catch{throw new w(`INVALID_INPUT`,`--params must be a JSON object.`)}if(!t||Array.isArray(t)||typeof t!=`object`)throw new w(`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 w(`INVALID_INPUT`,`--params must be a JSON object smaller than 64 KiB.`);return Object.fromEntries(n)}async function Ud(e){let t=await le(),n=t.registry.get(e);if(n)return n;let r=t.failures.find(t=>t.id===e);throw r?new w(`INVALID_INPUT`,`Provider ${e} could not load: ${r.message}`):new w(`INVALID_INPUT`,`Provider ${e} is not installed.`)}function Wd(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 w(`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 w(`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 w(`INVALID_INPUT`,`${t.label} is required.`);return i}async function Gd(){return i()}const Kd={list:E({meta:{name:`list`,description:`List installed provider packages`},args:{json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=await Gd();if(I(e)){z(t);return}t.installed.length>0?V([`Installed provider`,`Package`,`Version`,`Status`],t.installed.map(e=>[e.id,e.package,e.version,e.loadStatus])):process.stdout.write(`No provider extensions are installed.
211
- `)}}),describe:E({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 Sn(k(e.id)??``);if(I(e)){z(t);return}B([[`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:E({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=k(e.package)??``,n=I(e),r=j(e.yes),i=R({json:n});if(!r&&!i)throw new w(`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 b(t)}catch(e){throw new w(`INVALID_INPUT`,e instanceof Error?e.message:String(e))}if(!r&&(Ei(`Install provider package`),D([`${a.package}@${a.version}`,`Publisher: ${a.publisher??`not supplied by npm`}`,`Repository: ${a.repository??`not supplied by npm`}`,`Integrity: ${a.integrity}`].join(`
212
- `),`Package`),D(`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`),!H(await Ti({message:`Install ${a.package}@${a.version}?`,initialValue:!1})))){Ai(`Provider was not installed.`);return}let o;try{o=await g(a)}catch(e){throw new w(`PROVIDER_UNAVAILABLE`,e instanceof Error?e.message:String(e))}let s={installed:o,package:a};if(n){z(s);return}Ai(`Installed provider ${o.id} from ${o.package}@${o.version}.`)}}),remove:E({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=k(e.id)??``,n=I(e),r=j(e.yes),i=R({json:n});if(!r&&!i)throw new w(`INVALID_INPUT`,`Pass --yes to remove a provider extension in JSON or CI mode.`);if(!r&&!H(await Ti({message:`Remove provider ${t}?`,initialValue:!1})))return;let a=await Ie(t);if(!a)throw new w(`INVALID_INPUT`,`Provider ${t} is not installed.`);if(n){z({removed:a});return}process.stdout.write(`Removed ${t}.\n`)}}),connect:E({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 n=k(e.id)??``,r=I(e),i=R({json:r}),a=await Ud(n),o={...Ge(n),...Vd(k(e.account))},s={};if(i){Ei(`Connect ${a.displayName}`),a.connection.verificationNotice&&D(a.connection.verificationNotice,`Connection check`);for(let e of a.connection.fields){if(e.kind===`account`){let t=H(await Ni({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=H(await ji({message:e.label,validate:t=>e.required!==!1&&!t?.trim()?`${e.label} is required`:void 0}));t?.trim()&&(s[e.id]=t.trim())}}o=Wd(a,o);let c={...s};Object.keys(c).length===0&&(c=await ht({providerId:n,account:o,fields:a.connection.fields})),await dt({providerId:n,account:o,credentials:c}),Object.keys(s).length>0&&await ae({providerId:n,account:o,credentials:s}),t(n,o);let l={provider:n,connected:!0,account:o,capabilities:(a.capabilities??[]).map(e=>e.id),actions:(a.actions??[]).map(e=>e.id)};r?z(l):Ai(`Connected ${a.displayName}. Run seo providers describe ${n} to see what it can do.`)}}),status:E({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=k(e.id)??``,n=await Ud(t),r=Ge(t),i;try{i=await ht({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=j(e.check);if(o&&!a)throw new w(`AUTH_REQUIRED`,`Provider ${t} is not connected. Run \`seo providers connect ${t}\`.`);o&&i&&await dt({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(I(e)){z(s);return}V([`Provider`,`Connected`,`Capabilities`,`Check`],[[n.displayName,a?`yes`:`no`,s.capabilities.join(`, `),s.check]])}}),disconnect:E({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=k(e.id)??``;await Ud(t),await st({providerId:t,account:Ge(t)}),Dt(t);let n={provider:t,disconnected:!0,note:`Environment variables were not changed.`};I(e)?z(n):process.stdout.write(`Disconnected ${t}. ${n.note}\n`)}}),run:E({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=k(e.id)??``,n=k(e.action)??``;await Ud(t),z(await vn({providerId:t,actionId:n,account:{...Ge(t),...Vd(k(e.account))},params:Hd(k(e.params)),refresh:j(e.refresh)}))}}),limits:E({meta:{name:`limits`,description:`Show or change local limits for one installed provider`},args:{id:{type:`positional`,required:!0,description:`Installed provider id.`},"daily-notice":{type:`string`,description:`UTC daily notice in USD, or off.`},"daily-limit":{type:`string`,description:`UTC daily hard limit in USD, or off.`},"monthly-limit":{type:`string`,description:`UTC monthly hard limit in USD, or off.`},requests:{type:`string`,description:`Maximum provider requests in one report run.`},rows:{type:`string`,description:`Maximum requested provider rows in one report run.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=k(e.id)??``;await Ud(t);let n=ft(t),r={dailyNoticeMicros:Cd(e[`daily-notice`],`--daily-notice`,{notice:!0}),dailyHardLimitMicros:Cd(e[`daily-limit`],`--daily-limit`),monthlyHardLimitMicros:Cd(e[`monthly-limit`],`--monthly-limit`),maxRequestsPerReport:wd(e.requests,`--requests`,1,100),maxRowsPerReport:wd(e.rows,`--rows`,1,1e5)},i=Object.values(r).some(e=>e!==void 0),a=i?ue(t,{dailyNoticeMicros:r.dailyNoticeMicros??n.dailyNoticeMicros,dailyHardLimitMicros:r.dailyHardLimitMicros===void 0?n.dailyHardLimitMicros:r.dailyHardLimitMicros,monthlyHardLimitMicros:r.monthlyHardLimitMicros===void 0?n.monthlyHardLimitMicros:r.monthlyHardLimitMicros,maxRequestsPerReport:r.maxRequestsPerReport??n.maxRequestsPerReport,maxRowsPerReport:r.maxRowsPerReport??n.maxRowsPerReport}):n,o={provider:t,changed:i,limits:a};if(I(e)){z(o);return}B([[`UTC daily notice`,a.dailyNoticeMicros===0?`off`:Q(a.dailyNoticeMicros)],[`UTC daily hard limit`,a.dailyHardLimitMicros===null?`off`:Q(a.dailyHardLimitMicros)],[`UTC monthly hard limit`,a.monthlyHardLimitMicros===null?`off`:Q(a.monthlyHardLimitMicros)],[`Requests per report`,String(a.maxRequestsPerReport)],[`Rows per report`,String(a.maxRowsPerReport)]])}}),spend:E({meta:{name:`spend`,description:`Show locally recorded spend for one installed provider`},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=k(e.id)??``;await Ud(t);let n={provider:t,local:u(t)};if(I(e)){z(n);return}B([[`Today`,Q(n.local.today.effectiveCostMicros)],[`This month`,Q(n.local.month.effectiveCostMicros)],[`Requests today`,String(n.local.today.requests)]])}}),doctor:E({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 Gd(),n={ok:t.installed.every(e=>e.loadStatus===`ready`),installed:t.installed};if(I(e)){z(n);return}if(n.installed.length===0){process.stdout.write(`No provider extensions are installed.
213
- `);return}V([`Provider`,`Package`,`Version`,`Status`],n.installed.map(e=>[e.id,e.package,e.version,e.error??e.loadStatus]))}})},qd=E({meta:{name:`semrush`,description:`Connect Semrush Version 3 for optional search data`},subCommands:{connect:E({meta:{name:`connect`,description:`Validate and save a Semrush Version 3 API key`},args:{json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{if(!R({json:I(e)}))throw new w(`AUTH_REQUIRED`,`Run \`seo providers semrush connect\` in a terminal. Agents and CI can set ${Jt}.`);Ei(`Connect Semrush`),D(`Use the permanent Version 3 API Key shown on your Semrush API Keys page. Semrush creates this key automatically. The balance check is free; research reports consume API units.`,`API key`);let t=H(await ji({message:`Semrush Version 3 API key`,validate:e=>e?.trim()?void 0:`API key is required`})),n=await new hr({apiKey:t}).apiUnitBalance(),r=await Mn(t);D(`${n.remainingUnits.toLocaleString(`en-US`)} API units remain.`,`Connection verified`),Ai(`Saved in the ${Sd(r)}. Run seo providers semrush status --check to verify it again.`)}}),status:E({meta:{name:`status`,description:`Show the local Semrush connection`},args:{check:{type:`boolean`,default:!1,description:`Verify the Version 3 API key with a free balance request.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=await Kn(),n=!!e.check,r=n&&t?await new hr().apiUnitBalance():void 0,i={connected:!!t,apiVersion:t?3:null,credentialSource:t?.source,migratedLegacyCredential:t?.migrated??!1,liveCheck:r?{status:`passed`,remainingUnits:r.remainingUnits,observedAt:r.observedAt,requestCostUnits:0}:{status:n?`unavailable`:`not-requested`}};if(I(e)){z(i);return}B([[`Connected`,i.connected?`yes`:`no`],[`API version`,i.apiVersion?`Version 3`:`not connected`],[`Credential`,Sd(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?[[`API units remaining`,r.remainingUnits.toLocaleString(`en-US`)]]:[]])}}),disconnect:E({meta:{name:`disconnect`,description:`Remove the saved Semrush Version 3 API key`},args:{json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{await nn();let t=!!process.env[Jt],n={savedCredentialRemoved:!0,environmentCredential:t?`active`:`missing`,note:t?`The environment variable was not changed. Clear ${Jt} to fully disconnect.`:`Semrush is disconnected.`};I(e)?z(n):process.stdout.write(`${n.note}\n`)}})}}),Jd=E({meta:{name:`providers`,description:`Connect optional data providers`},subCommands:{...Kd,ahrefs:Ad,bing:Id,dataforseo:Bd,semrush:qd}});function Yd(e){let t=e.indexed+e.notIndexed+e.unknown+e.warnings;return t?`${e.indexed}/${t} indexed`:`-`}function Xd(e){if(!e.samples.length)return`-`;let t=e.medianWordCount===void 0?`words ?`:`${G(e.medianWordCount)} words`,n=e.weakQueryCoverage?`; ${e.weakQueryCoverage} weak coverage`:``;return`${t}; ${e.usable}/${e.samples.length} usable${n}`}function Zd(e){return e===`serp-framing-review`?`wording review`:e===`body-term-review`?`literal term review`:`covered terms`}function Qd(e){let t=e.staticSegments.map(e=>`/${e.value}`).join(``),n=e.variableSegments.slice(0,3).map(e=>{let t=e.examples.slice(0,3).join(`, `);return`${e.placeholder}@${e.index+1}${t?` (${t})`:``}`});return[t?`static ${t}`:`no fixed path prefix`,n.length?`variables ${n.join(`; `)}`:`no variables`].join(`; `)}function $d(e){if(e.checkedQueries)return`${Math.round(e.impressionShare*100)}% impression fit across ${Dn(e.checkedQueries,`checked query/page row`)}`}function ef(e){return e.startsWith(`theme: `)?`${e.slice(7)}-related`:e===`general`?`broad`:e}function tf(e){for(let t of e.slice(0,5)){process.stdout.write(`\n${t.signature}\n`),t.evidence.length&&process.stdout.write(` Evidence: ${t.evidence.join(`; `)}.\n`),process.stdout.write(` Shape: ${Qd(t.shape)}\n`);let e=$d(t.metrics.entityFit);e&&process.stdout.write(` Entity fit: ${e}\n`);let n=t.metrics.queryPatterns.slice(0,3).map(e=>`${ef(e.label)} (${G(e.impressions)} impr: ${e.examples.slice(0,2).join(`; `)})`);n.length&&process.stdout.write(` Demand: ${n.join(` | `)}\n`);let r=t.metrics.topQueries.slice(0,3);if(r.length){process.stdout.write(` Top queries:
214
- `);for(let e of r)process.stdout.write(` - ${K(e.query,72)} (${G(e.impressions)} impr, pos ${rs(e.position)})\n`)}let i=t.crawl.samples.map(e=>e.queryCoverage).filter(e=>!!e).slice(0,3);if(i.length){process.stdout.write(` Sample coverage:
215
- `);for(let e of i){let t=e.missingTerms.length?` missing ${e.missingTerms.slice(0,4).join(`, `)}`:``;process.stdout.write(` - ${Zd(e.classification)}: ${K(e.query,64)} (body ${(e.bodyCoverage*100).toFixed(0)}%${t})\n`)}}let a=t.metrics.entityFit.weakExamples.slice(0,2);if(a.length){process.stdout.write(` Weak entity-fit examples:
216
- `);for(let e of a){let t=e.pathTerms.slice(0,4);process.stdout.write(` - ${K(e.query,64)} -> expected ${Dn(t.length,`path term`)}: ${t.join(`, `)}\n`)}}process.stdout.write(` Action: ${t.recommendation}\n`)}}const nf=E({meta:{name:`pseo`,description:`Programmatic SEO audits that cluster URL patterns and sample template pages`},subCommands:{audit:E({meta:{name:`audit`,description:`Group GSC/sitemap URLs into pSEO templates, then sample pages for crawl and index evidence`},args:{site:{type:`string`},project:{type:`string`,description:`Saved project id or name.`},client:{type:`string`,description:`Legacy alias for --project.`},...Z([`days`,`limit`,`includeBrand`,`js`,`refresh`],{limit:{description:`Maximum templates to show. Defaults to 25.`},includeBrand:{description:`Include branded queries in template metrics.`},js:{description:`Force JavaScript rendering for crawl samples.`},refresh:{description:`Bypass local GSC and HTTP cache.`}}),sitemap:{type:`string`,description:`Comma-separated sitemap URLs to include in template counts.`},"crawl-samples":{type:`string`,description:`Sample URLs to fetch per detected template. This is not crawl depth.`},"inspect-samples":{type:`string`,description:`Sample URLs to check per detected template with URL Inspection.`},"max-sitemap-urls":{type:`string`,description:`Maximum discovered URLs per sitemap. Defaults to 50000.`},"minimum-template-urls":{type:`string`,description:`Minimum repeated URLs required for a template. Defaults to 3.`},"minimum-template-share":{type:`string`,description:`Minimum share of discovered URLs from 0 to 1. Defaults to 0.`},"minimum-template-impressions":{type:`string`,description:`Minimum retained page impressions for a template. Defaults to 0.`},"brand-terms":{type:`string`,description:`Comma-separated brand terms to exclude from query evidence.`},"fetch-concurrency":{type:`string`,description:`Maximum concurrent page fetches per host. Defaults to 4.`},"fetch-interval-cap":{type:`string`,description:`Maximum page fetches per interval per host. Defaults to 4.`},"fetch-interval-ms":{type:`string`,description:`Fetch rate interval in milliseconds. Defaults to 1000.`},json:{type:`boolean`,default:!1}},run:async({args:e})=>{let t=I(e),n=_u(e),r=await U({client:A(e),site:k(e.site),options:{json:t,refresh:j(e.refresh)}}),i=await ze({site:r.site,...n,brandTerms:n.brandTerms??r.client?.brandTerms,progress:Hl(!t)});if(t){z(i);return}if(q({title:`Programmatic SEO audit`,target:i.site,status:i.templates.length>0?`info`:`unknown`,summary:i.templates.length>0?`${G(i.summary.templates)} page templates were analysed.`:`No page templates could be established from the available evidence.`,metrics:[{label:`Templates`,value:G(i.summary.templates)},{label:`GSC pages`,value:G(i.summary.gscPages)},{label:`Sitemap URLs`,value:G(i.summary.sitemapUrls)},{label:`Clicks`,value:G(i.summary.clicks)},{label:`Impressions`,value:G(i.summary.impressions)},{label:`Usable crawls`,value:`${G(i.summary.crawledUrls)}/${G(i.summary.crawlAttempts)}`},{label:`Inspections`,value:`${G(i.summary.inspectedUrls)}/${G(i.summary.inspectionAttempts)}`}]}),X(`Report caveats`,i.caveats),!i.templates.length){process.stdout.write(`No pSEO templates found from GSC/sitemap data.
217
- `);return}J([`Verdict`,`Template`,`URLs`,`Clicks`,`Impr`,`CTR`,`Pos`,`Index`,`Crawl`,`Action`],i.templates.map(e=>[`${e.verdict} (${e.confidence})`,K(e.signature,28),G(e.urlCount),G(e.metrics.clicks),G(e.metrics.impressions),is(e.metrics.ctr),e.metrics.position.toFixed(1),Yd(e.inspection),Xd(e.crawl),K(e.recommendation,72)])),tf(i.templates),i.warnings.length&&process.stdout.write(`Warnings: ${i.warnings.join(`; `)}\n`),cs(`seo export pseo ${r.client?`--project ${JSON.stringify(r.client.id)}`:`--site ${JSON.stringify(r.site)}`}`)}})}}),rf={"ai-search":`AI search`,crawl:`Crawling and technical checks`,diagnosis:`Diagnosis`,experiments:`Experiments`,monitoring:`Monitoring`,opportunities:`Opportunities`,reporting:`Reporting`,setup:`Setup`,workflows:`Workflows`};function af(e){let t=k(e);if(t){if(!sr.includes(t))throw new w(`INVALID_INPUT`,`Unknown report category: ${t}. Choose ${sr.join(`, `)}.`);return t}}async function of(e,t){let n=k(e),r=k(t);if(n&&r)throw new w(`INVALID_INPUT`,`Use either --params or --params-file, not both.`);if(!n&&!r)return{};let i=n??await ui(r??``,`utf8`),a;try{a=JSON.parse(i)}catch{throw new w(`INVALID_INPUT`,`Report parameters must be valid JSON.`)}if(!a||typeof a!=`object`||Array.isArray(a))throw new w(`INVALID_INPUT`,`Report parameters must be an object.`);return a}function sf(e){if(!e.isError)return;let t=e.structuredContent;throw new w(t?.error?.code??`INTERNAL_ERROR`,t?.error?.message??`The report could not be completed.`)}function cf(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 lf=E({meta:{name:`reports`,description:`Discover and run the complete report catalog`},subCommands:{list:E({meta:{name:`list`,description:`List report ids available to the CLI and MCP server`},args:{category:{type:`string`,description:`Filter by category: ${sr.join(`, `)}.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=nr(af(e.category));if(I(e)){z({reports:t,categories:sr});return}ko(t,{noun:`report`,categoryLabels:rf}),process.stdout.write(`
218
- `),To({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:E({meta:{name:`describe`,description:`Show the purpose and parameter schema for one report`},args:{id:{type:`positional`,required:!0,description:"Report id from `seo reports list`."},check:{type:`string`,description:`Show fix guidance for one check id from the report output.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=k(e.check);if(t){let n=Pt(k(e.id)??``,t);if(I(e)){z({report:n});return}Eo(`${n.name}: ${n.check}`,n.checkFix.goal),process.stdout.write(`
219
- `),jo(`Fix`,n.checkFix.fix),process.stdout.write(`
220
- `),jo(`Agent prompt`,n.checkFix.prompt),process.stdout.write(`
221
- `),jo(`Verify`,n.checkFix.verify),n.checkFix.resources.length>0&&(process.stdout.write(`
222
- `),Mo(`Resources`,n.checkFix.resources.map(e=>`${e.title}: ${e.url}`))),process.stdout.write(`
223
- `);return}let n=pn(k(e.id)??``);if(I(e)){z({report:n});return}Eo(n.name,n.description),process.stdout.write(`
224
- `),B([[`ID`,n.id],[`Category`,n.category],[`Outcome`,n.outcome]]),process.stdout.write(`
225
- `),Mo(`Use when`,n.useWhen),process.stdout.write(`
226
- `),Mo(`Avoid when`,n.avoidWhen),process.stdout.write(`
227
- `),Mo(`Read in order`,n.readOrder),process.stdout.write(`
228
- `),Mo(`Do not claim`,n.doNotClaim),process.stdout.write(`
229
- `),jo(`Verify`,n.verify),process.stdout.write(`
230
- `),To({title:`Implement findings`,body:n.agentWorkflow.completion,command:n.agentWorkflow.actionView.cli}),n.related.length>0&&(process.stdout.write(`
231
- `),Mo(`Related reports`,n.related.map(e=>`${e.id}: ${e.reason}`))),process.stdout.write(`
209
+ `),B([`Segment`,`Retained in`,`Clicks`,`Impressions`],c.unmatchedSegments.map(e=>[e.key,e.retainedIn,e.clicks,e.impressions]))),Y(`Warnings`,c.warnings)}});function xd(e){return`${W(e)} ${e===1?`row`:`rows`}`}const Sd=T({meta:{name:`striking-distance`,description:`Find query/page rows averaging positions above 10 through 20`},args:{site:{type:`string`,description:`GSC property URL, for example sc-domain:example.com.`},client:{type:`string`,description:`Legacy alias for --project.`},project:{type:`string`,description:`Saved project id or name.`},...X([`days`,`minImpressions`,`limit`,`includeBrand`,`verifyContent`,`verifyLimit`,`js`,`fetchConcurrency`,`fetchIntervalCap`,`fetchIntervalMs`,`refresh`]),json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=F(e),n=j(e[`verify-limit`]),r=A(e[`verify-content`])===!0||n!==void 0,i=await H({client:k(e),site:O(e.site),options:{json:t,refresh:A(e.refresh)}}),a=await St({site:i.site,days:j(e.days),minImpressions:j(e[`min-impressions`]),limit:j(e.limit),brandTerms:i.client?.brandTerms,includeBrand:A(e[`include-brand`]),verifyContent:r,verifyLimit:n,js:A(e.js)?!0:void 0,rate:Ba(e),refresh:A(e.refresh)});if(t){R(a);return}if(K({title:`Striking-distance opportunities`,target:a.site,status:a.source.possiblyTruncated?`unknown`:`info`,summary:a.summary.verdict,metrics:[{label:`Eligible rows`,value:W(a.summary.eligibleRows)},{label:`Returned rows`,value:W(a.summary.returnedRows)},{label:`Template groups`,value:W(a.summary.groups)},{label:`Impressions`,value:W(a.summary.eligibleImpressions)},{label:`Brand queries`,value:a.summary.brandFiltering},{label:`Verification`,value:a.verification.requested?`Requested`:`Off`},{label:`Evidence`,value:a.dataStatus}]}),Y(`Why this matters`,[`These rows have a GSC average position above 10 and at most 20. Treat them as candidates for investigation, not guaranteed page-two rankings.`,`Groups use all eligible rows. A shared-template label is only a candidate until recurring evidence is verified across distinct URLs.`]),Y(`Recommended actions`,a.recommendations),Y(`Report caveats`,a.caveats),a.groups.length&&(q([`Group`,`Rows`,`URLs`,`Impr`,`Avg pos`,`Scope`,`Action`],a.groups.map(e=>[G(e.label,36),W(e.rowCount),W(e.uniqueUrls),W(e.totalImpressions),is(e.impressionWeightedPosition),e.actionScope,G(e.recommendation.action,72)])),J(`Top striking-distance group actions`,a.groups.map(e=>({label:e.label,context:`${xd(e.rowCount)}, ${W(e.totalImpressions)} impressions`,action:e.recommendation.action})))),!a.items.length){process.stdout.write(`No striking-distance opportunities matched these filters.
210
+ `);return}q([`Query`,`Template`,`URL`,`Impr`,`CTR`,`Pos`,`Score`,`Fetch`,`Check`,`Action`],a.items.map(e=>[G(e.query,36),G(e.template.label,24),G(e.url,48),W(e.impressions),as(e.ctr),is(e.position),e.priority.score,ws(e.contentVerification?.fetchDiagnostics),Ts(e.contentVerification?.classification),G(e.recommendation.action,72)])),J(`Top striking-distance actions`,a.items.map(e=>({label:e.query,context:`${e.template.label}, pos ${is(e.position)}, ${W(e.impressions)} impressions`,action:e.recommendation.action})))}});function Cd(e){return e===`keychain`?`system keychain`:e===`file`?`private local file`:e??`missing`}function Z(e){return e===null?`unavailable`:`$${(e/1e6).toFixed(2)}`}function wd(e,t,n={}){if(e===void 0)return;if(O(e)?.trim().toLowerCase()===`off`)return n.notice?0:null;let r=j(e);if(r===void 0||r<0||r>1e6)throw new C(`INVALID_INPUT`,`${t} must be a USD amount from 0 to 1000000, or off.`);let i=Math.round(r*1e6);if(!Number.isSafeInteger(i))throw new C(`INVALID_INPUT`,`${t} is too precise.`);return i}function Td(e,t,n,r){if(e===void 0)return;let i=j(e);if(i===void 0||!Number.isInteger(i)||i<n||i>r)throw new C(`INVALID_INPUT`,`${t} must be an integer from ${n} to ${r}.`);return i}function Ed(e){return T({meta:{name:`limits`,description:`Show or change local ${e.displayName} spend limits`},args:{"daily-notice":{type:`string`,description:`UTC daily notice in USD, or off.`},"daily-limit":{type:`string`,description:`UTC daily hard limit in USD, or off.`},"monthly-limit":{type:`string`,description:`UTC monthly hard limit in USD, or off.`},requests:{type:`string`,description:`Maximum provider requests in one report run.`},rows:{type:`string`,description:`Maximum requested provider rows in one report run.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:t})=>{let n=pt(e.provider),r=wd(t[`daily-notice`],`--daily-notice`,{notice:!0}),i=wd(t[`daily-limit`],`--daily-limit`),a=wd(t[`monthly-limit`],`--monthly-limit`),o=Td(t.requests,`--requests`,1,100),s=Td(t.rows,`--rows`,1,1e5),c=[r,i,a,o,s].some(e=>e!==void 0),l=c?de(e.provider,{dailyNoticeMicros:r===void 0?n.dailyNoticeMicros:r,dailyHardLimitMicros:i===void 0?n.dailyHardLimitMicros:i,monthlyHardLimitMicros:a===void 0?n.monthlyHardLimitMicros:a,maxRequestsPerReport:o??n.maxRequestsPerReport,maxRowsPerReport:s??n.maxRowsPerReport}):n,u={provider:e.provider,changed:c,limits:l};if(F(t)){R(u);return}z([[`UTC daily notice`,l.dailyNoticeMicros===0?`off`:Z(l.dailyNoticeMicros)],[`UTC daily hard limit`,l.dailyHardLimitMicros===null?`off`:Z(l.dailyHardLimitMicros)],[`UTC monthly hard limit`,l.monthlyHardLimitMicros===null?`off`:Z(l.monthlyHardLimitMicros)],[`Requests per report`,String(l.maxRequestsPerReport)],[`Rows per report`,String(l.maxRowsPerReport)]])}})}function Dd(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 Od=T({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(!L({json:F(e)}))throw new C(`AUTH_REQUIRED`,`Run \`seo providers ahrefs connect\` in a terminal. Agents and CI can set ${Mr}.`);Di(`Connect Ahrefs`),E(`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=V(await Mi({message:`Ahrefs API v3 key`,validate:e=>e?.trim()?void 0:`API key is required`})),n=await new br({apiKey:t}).limitsAndUsage(),r=await tt(t);E(`${n.subscription} subscription. API key units: ${Dd(n.apiKeyUnits)}.`,`Connection verified`),ji(`Saved in the ${Cd(r)}. Run seo providers ahrefs status --check to verify it again.`)}}),kd=T({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 Or(),n=!!e.check,r=n&&t?await new br().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(F(e)){R(i);return}z([[`Connected`,i.connected?`yes`:`no`],[`API version`,i.apiVersion?`Version 3`:`not connected`],[`Credential`,Cd(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`,Dd(r.apiKeyUnits)],[`Workspace units`,Dd(r.workspaceUnits)],[`Usage resets`,r.usageResetsAt],[`Key expires`,r.apiKeyExpiresAt]]:[]])}}),Ad=T({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 Ht();let t=!!process.env[Mr]?.trim(),n={savedCredentialRemoved:!0,environmentCredential:t?`active`:`missing`,note:t?`The environment variable was not changed. Clear ${Mr} to fully disconnect.`:`Ahrefs is disconnected.`};F(e)?R(n):process.stdout.write(`${n.note}\n`)}}),jd=T({meta:{name:`ahrefs`,description:`Connect Ahrefs for optional search and link data`},subCommands:{connect:Od,status:kd,limits:T({meta:{name:`limits`,description:`Show or change local Ahrefs report work limits`},args:{requests:{type:`string`,description:`Maximum Ahrefs requests in one report run.`},rows:{type:`string`,description:`Maximum requested Ahrefs rows in one report run.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=pt(`ahrefs`),n=Td(e.requests,`--requests`,1,100),r=Td(e.rows,`--rows`,1,1e5),i=n!==void 0||r!==void 0,a=i?de(`ahrefs`,{...t,maxRequestsPerReport:n??t.maxRequestsPerReport,maxRowsPerReport:r??t.maxRowsPerReport}):t,o={provider:`ahrefs`,maxRequestsPerReport:a.maxRequestsPerReport,maxRowsPerReport:a.maxRowsPerReport,changed:i,note:`Paid requests also preflight the live API-unit balance and enforce fixed per-request and per-report unit caps.`};if(F(e)){R(o);return}z([[`Requests per report`,String(o.maxRequestsPerReport)],[`Rows per report`,String(o.maxRowsPerReport)],[`Changed`,o.changed?`yes`:`no`],[`Note`,o.note]])}}),disconnect:Ad}});function Md(e){return e.filter(e=>e.isVerified)}function Nd(e){if(e.startsWith(`sc-domain:`))return e.slice(10).replace(/^www\./,``).toLowerCase();try{return new URL(e).hostname.replace(/^www\./,``).toLowerCase()}catch{return}}function Pd(e,t){let n=Nd(e);if(!n)return;let r=Md(t).filter(e=>Nd(e.url)===n);return r.length===1?r[0]:void 0}async function Fd(e){let t=Md(e.sites),n=e.explicitSite??e.savedSite;if(n){let e=t.find(e=>e.url===n);if(!e)throw new C(`PROPERTY_NOT_FOUND`,`Bing Webmaster did not return a verified site matching ${n}.`);return e.url}let r=e.projectSite?Pd(e.projectSite,t):void 0;if(r)return r.url;if(t.length===1)return t[0]?.url;if(t.length!==0){if(!L({json:e.json}))throw new C(`INVALID_INPUT`,`Several verified Bing sites are available. Pass --site to choose one.`);return V(await Ni({message:`Choose a verified Bing Webmaster site`,options:t.map(e=>({value:e.url,label:e.url}))}))}}const Id={project:{type:`string`,description:`Saved project id or name.`},client:{type:`string`,description:`Legacy alias for --project.`},site:{type:`string`,description:`Verified Bing Webmaster site URL.`}},Ld=T({meta:{name:`bing`,description:`Connect and report on Bing Webmaster`},subCommands:{connect:T({meta:{name:`connect`,description:`Connect Bing Webmaster and attach a verified site`},args:{...Id,json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=F(e);if(!L({json:t}))throw new C(`AUTH_REQUIRED`,"Run `seo providers bing connect` in a terminal. Agents and CI can set SEO_BING_API_KEY and run the report directly.");Di(`Connect Bing Webmaster`),E(`Paste the API key from Bing Webmaster Tools Settings, then API Access. The key stays on this machine.`,`API key`);let n=V(await Mi({message:`Bing Webmaster API key`,validate:e=>e?.trim()?void 0:`API key is required`})),r=await new An({apiKey:n}).listSites(),i=await U({project:k(e),options:{json:t}}),a=await Fd({explicitSite:O(e.site),projectSite:i?.siteUrl,savedSite:_e(i),sites:r.sites,json:t});if(!a)throw new C(`PROPERTY_NOT_FOUND`,`No verified Bing Webmaster sites were found for this API key.`);await Ze(sr,n);let o=i?zn(i.id,a):void 0;E(`${a}${o?` is attached to ${o.name}.`:` is ready for direct reports.`}`,`Bing connected`),ji(o?`Run seo providers bing report --project ${o.id}`:`Run seo providers bing report --site ${a}`)}}),status:T({meta:{name:`status`,description:`Show the local Bing connection`},args:{project:{type:`string`,description:`Saved project id or name.`},client:{type:`string`,description:`Legacy alias for --project.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=await De({name:sr,envVar:Wn}),n=await U({project:k(e),options:{json:F(e)}}),r={connected:!!t,credentialSource:t?.source,project:n?{id:n.id,name:n.name,site:_e(n)}:void 0};if(F(e)){R(r);return}z([[`Connected`,r.connected?`yes`:`no`],[`Credential`,r.credentialSource??`missing`],[`Project`,r.project?.name??`not selected`],[`Bing site`,r.project?.site??`not attached`]])}}),disconnect:T({meta:{name:`disconnect`,description:`Remove the saved Bing Webmaster credential`},args:{json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{await he(sr);let t={disconnected:!0,note:`Saved project site mappings were kept for a later reconnect.`};F(e)?R(t):process.stdout.write(`${t.note}\n`)}}),sites:T({meta:{name:`sites`,description:`List Bing Webmaster sites`},args:{json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let{client:t,credentialSource:n}=await Kt(),r=await t.listSites(),i={...r,credentialSource:n};if(F(e)){R(i);return}B([`Site`,`Verified`],r.sites.map(e=>[e.url,e.isVerified?`yes`:`no`]))}}),report:T({meta:{name:`report`,description:`Find Bing traffic, crawl, query, and page insights`},args:{...Id,json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=F(e),n=await U({project:k(e),options:{json:t}}),r=O(e.site)??_e(n);if(!r)throw new C(`INVALID_INPUT`,`Pass --site or connect Bing Webmaster to a saved project first.`);let i=await zt({site:r});if(t){R(i);return}let a=i.traffic.status===`unavailable`?void 0:i.traffic.data,o=i.crawl.status===`unavailable`?void 0:i.crawl.data,s=a?.analysis;K({title:`Bing Webmaster report`,target:i.site,status:i.dataStatus===`complete`?i.summary.warnings>0?`warning`:`pass`:`unknown`,summary:i.summary.findings>0?`${i.summary.findings} findings need review across Bing traffic and crawl evidence.`:`No findings were returned by the available Bing evidence.`,metrics:[{label:`Evidence`,value:i.dataStatus},{label:`28-day clicks`,value:s?.current.clicks??`Unavailable`},{label:`Click change`,value:s?.changes.clicksPercent===null||s?.changes.clicksPercent===void 0?`Unavailable`:`${s.changes.clicksPercent.toFixed(1)}%`},{label:`28-day impressions`,value:s?.current.impressions??`Unavailable`},{label:`Impression change`,value:s?.changes.impressionsPercent===null||s?.changes.impressionsPercent===void 0?`Unavailable`:`${s.changes.impressionsPercent.toFixed(1)}%`},{label:`Crawled pages`,value:o?.latest?.crawledPages??`Unavailable`},{label:`4xx responses`,value:o?.latest?.code4xx??`Unavailable`},{label:`Findings`,value:i.summary.findings}],diagnostics:[{title:`Review first`,items:i.findings.slice(0,5).map(e=>({status:e.severity===`warning`?`warning`:`info`,title:e.title,explanation:e.interpretation,evidence:[e.verification]}))}],notes:i.caveats})}})}}),Rd=T({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(!L({json:F(e)}))throw new C(`AUTH_REQUIRED`,`Run \`seo providers dataforseo connect\` in a terminal. Agents and CI can set ${xn} and ${je}.`);Di(`Connect DataForSEO`),E(`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=V(await Pi({message:`DataForSEO API login`,validate:e=>e?.trim()?void 0:`API login is required`})),n=V(await Mi({message:`DataForSEO API password`,validate:e=>e?void 0:`API password is required`})),r={login:t.trim(),password:n},i=await new o({credentials:()=>r}).userData(),a=await ie(r);E(`${i.login} is connected with an account balance of ${Z(i.balanceMicros)}.`,`Connection verified`),ji(`Saved in the ${Cd(a)}. Run seo providers dataforseo status --check to verify it again.`)}}),zd=T({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 Ue(),n=!!e.check,r=n&&t?await new o().userData():void 0,i={connected:!!t,credentialSource:t?.source,migratedLegacyCredentials:t?.migrated??!1,liveCheck:r?{status:`passed`,account:r.login,timezone:r.timezone,balanceMicros:r.balanceMicros,observedAt:r.observedAt,requestCostMicros:r.requestCostMicros}:{status:n?`unavailable`:`not-requested`}};if(F(e)){R(i);return}z([[`Connected`,i.connected?`yes`:`no`],[`Credential`,Cd(i.credentialSource)],[`Live check`,i.liveCheck.status===`passed`?`passed at ${i.liveCheck.observedAt}`:i.liveCheck.status===`unavailable`?`not available without credentials`:`not requested; pass --check to verify`],...r?[[`Account`,r.login],[`Balance`,Z(r.balanceMicros)]]:[]])}}),Bd=T({meta:{name:`disconnect`,description:`Remove saved DataForSEO credentials`},args:{json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{await Fe();let t=!!process.env[xn]?.trim(),n=!!process.env[je],r={savedCredentialsRemoved:!0,environmentCredentials:t&&n?`active`:t||n?`partial`:`missing`,note:t||n?`Environment variables were not changed. Clear ${xn} and ${je} to fully disconnect.`:`DataForSEO is disconnected.`};F(e)?R(r):process.stdout.write(`${r.note}\n`)}}),Vd=T({meta:{name:`dataforseo`,description:`Connect DataForSEO for optional search data`},subCommands:{connect:Rd,status:zd,limits:Ed({provider:`dataforseo`,displayName:`DataForSEO`}),spend:T({meta:{name:`spend`,description:`Show local DataForSEO spend and account context`},args:{account:Fa(`Read account-wide context from the free endpoint.`,`Show only the local spend ledger.`),json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=u(`dataforseo`),n=e.account?await Ue():void 0,r;if(!e.account)r={status:`not-requested`};else if(!n)r={status:`unavailable`,reason:`DataForSEO is not connected. Local spend remains available without account context.`};else try{let e=await new o().userData();r={status:`observed`,account:e.login,balanceMicros:e.balanceMicros,dailySpendMicros:e.accountDailySpendMicros,dailySpendPeriod:e.accountDailySpendPeriod,dailyLimitMicros:e.accountDailyLimitMicros,observedAt:e.observedAt,requestCostMicros:e.requestCostMicros,semantics:`Account values include every client using these credentials. Local values include only requests recorded by this installation.`}}catch(e){r={status:`unavailable`,reason:e instanceof Error?e.message:`DataForSEO account context is unavailable.`}}let i={local:t,account:r};if(F(e)){R(i);return}z([[`Local today (UTC)`,`${Z(t.today.effectiveCostMicros)} across ${t.today.requests} requests`],[`Local month (UTC)`,`${Z(t.month.effectiveCostMicros)} across ${t.month.requests} requests`],[`Unknown local cost`,Z(t.month.estimatedOrUnknownCostMicros)],[`Account today`,r.status===`observed`?Z(r.dailySpendMicros):r.status===`unavailable`?r.reason:`not requested`],[`Account balance`,r.status===`observed`?Z(r.balanceMicros):`unavailable`]]),r.status===`observed`&&process.stdout.write(`${r.semantics}\n`)}}),disconnect:Bd}});function Hd(e){if(!e)return{};let t;try{t=JSON.parse(e)}catch{throw new C(`INVALID_INPUT`,`--account must be a JSON object.`)}if(!t||Array.isArray(t)||typeof t!=`object`)throw new C(`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 C(`INVALID_INPUT`,`--account must contain at most 20 bounded string fields.`);return Object.fromEntries(n.map(([e,t])=>[e,t.trim()]))}function Ud(e){if(!e)return{};let t;try{t=JSON.parse(e)}catch{throw new C(`INVALID_INPUT`,`--params must be a JSON object.`)}if(!t||Array.isArray(t)||typeof t!=`object`)throw new C(`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 C(`INVALID_INPUT`,`--params must be a JSON object smaller than 64 KiB.`);return Object.fromEntries(n)}async function Wd(e){let t=await ue(),n=t.registry.get(e);if(n)return n;let r=t.failures.find(t=>t.id===e);throw r?new C(`INVALID_INPUT`,`Provider ${e} could not load: ${r.message}`):new C(`INVALID_INPUT`,`Provider ${e} is not installed.`)}function Gd(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 C(`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 C(`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 C(`INVALID_INPUT`,`${t.label} is required.`);return i}async function Kd(){return i()}const qd={list:T({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 Kd();if(F(e)){R(t);return}t.installed.length>0?B([`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.
211
+ `)}}),describe:T({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 Cn(O(e.id)??``);if(F(e)){R(t);return}z([[`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:T({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=O(e.package)??``,n=F(e),r=A(e.yes),i=L({json:n});if(!r&&!i)throw new C(`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 b(t)}catch(e){throw new C(`INVALID_INPUT`,e instanceof Error?e.message:String(e))}if(!r&&(Di(`Install provider package`),E([`${a.package}@${a.version}`,`Publisher: ${a.publisher??`not supplied by npm`}`,`Repository: ${a.repository??`not supplied by npm`}`,`Integrity: ${a.integrity}`].join(`
212
+ `),`Package`),E(`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`),!V(await Ei({message:`Install ${a.package}@${a.version}?`,initialValue:!1})))){ji(`Provider was not installed.`);return}let o;try{o=await g(a)}catch(e){throw new C(`PROVIDER_UNAVAILABLE`,e instanceof Error?e.message:String(e))}let s={installed:o,package:a};if(n){R(s);return}ji(`Installed provider ${o.id} from ${o.package}@${o.version}.`)}}),remove:T({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=O(e.id)??``,n=F(e),r=A(e.yes),i=L({json:n});if(!r&&!i)throw new C(`INVALID_INPUT`,`Pass --yes to remove a provider extension in JSON or CI mode.`);if(!r&&!V(await Ei({message:`Remove provider ${t}?`,initialValue:!1})))return;let a=await Le(t);if(!a)throw new C(`INVALID_INPUT`,`Provider ${t} is not installed.`);if(n){R({removed:a});return}process.stdout.write(`Removed ${t}.\n`)}}),connect:T({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 n=O(e.id)??``,r=F(e),i=L({json:r}),a=await Wd(n),o={...Ke(n),...Hd(O(e.account))},s={};if(i){Di(`Connect ${a.displayName}`),a.connection.verificationNotice&&E(a.connection.verificationNotice,`Connection check`);for(let e of a.connection.fields){if(e.kind===`account`){let t=V(await Pi({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=V(await Mi({message:e.label,validate:t=>e.required!==!1&&!t?.trim()?`${e.label} is required`:void 0}));t?.trim()&&(s[e.id]=t.trim())}}o=Gd(a,o);let c={...s};Object.keys(c).length===0&&(c=await gt({providerId:n,account:o,fields:a.connection.fields})),await ft({providerId:n,account:o,credentials:c}),Object.keys(s).length>0&&await oe({providerId:n,account:o,credentials:s}),t(n,o);let l={provider:n,connected:!0,account:o,capabilities:(a.capabilities??[]).map(e=>e.id),actions:(a.actions??[]).map(e=>e.id)};r?R(l):ji(`Connected ${a.displayName}. Run seo providers describe ${n} to see what it can do.`)}}),status:T({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=O(e.id)??``,n=await Wd(t),r=Ke(t),i;try{i=await gt({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=A(e.check);if(o&&!a)throw new C(`AUTH_REQUIRED`,`Provider ${t} is not connected. Run \`seo providers connect ${t}\`.`);o&&i&&await ft({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(F(e)){R(s);return}B([`Provider`,`Connected`,`Capabilities`,`Check`],[[n.displayName,a?`yes`:`no`,s.capabilities.join(`, `),s.check]])}}),disconnect:T({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=O(e.id)??``;await Wd(t),await ct({providerId:t,account:Ke(t)}),Ot(t);let n={provider:t,disconnected:!0,note:`Environment variables were not changed.`};F(e)?R(n):process.stdout.write(`Disconnected ${t}. ${n.note}\n`)}}),run:T({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=O(e.id)??``,n=O(e.action)??``;await Wd(t),R(await yn({providerId:t,actionId:n,account:{...Ke(t),...Hd(O(e.account))},params:Ud(O(e.params)),refresh:A(e.refresh)}))}}),limits:T({meta:{name:`limits`,description:`Show or change local limits for one installed provider`},args:{id:{type:`positional`,required:!0,description:`Installed provider id.`},"daily-notice":{type:`string`,description:`UTC daily notice in USD, or off.`},"daily-limit":{type:`string`,description:`UTC daily hard limit in USD, or off.`},"monthly-limit":{type:`string`,description:`UTC monthly hard limit in USD, or off.`},requests:{type:`string`,description:`Maximum provider requests in one report run.`},rows:{type:`string`,description:`Maximum requested provider rows in one report run.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=O(e.id)??``;await Wd(t);let n=pt(t),r={dailyNoticeMicros:wd(e[`daily-notice`],`--daily-notice`,{notice:!0}),dailyHardLimitMicros:wd(e[`daily-limit`],`--daily-limit`),monthlyHardLimitMicros:wd(e[`monthly-limit`],`--monthly-limit`),maxRequestsPerReport:Td(e.requests,`--requests`,1,100),maxRowsPerReport:Td(e.rows,`--rows`,1,1e5)},i=Object.values(r).some(e=>e!==void 0),a=i?de(t,{dailyNoticeMicros:r.dailyNoticeMicros??n.dailyNoticeMicros,dailyHardLimitMicros:r.dailyHardLimitMicros===void 0?n.dailyHardLimitMicros:r.dailyHardLimitMicros,monthlyHardLimitMicros:r.monthlyHardLimitMicros===void 0?n.monthlyHardLimitMicros:r.monthlyHardLimitMicros,maxRequestsPerReport:r.maxRequestsPerReport??n.maxRequestsPerReport,maxRowsPerReport:r.maxRowsPerReport??n.maxRowsPerReport}):n,o={provider:t,changed:i,limits:a};if(F(e)){R(o);return}z([[`UTC daily notice`,a.dailyNoticeMicros===0?`off`:Z(a.dailyNoticeMicros)],[`UTC daily hard limit`,a.dailyHardLimitMicros===null?`off`:Z(a.dailyHardLimitMicros)],[`UTC monthly hard limit`,a.monthlyHardLimitMicros===null?`off`:Z(a.monthlyHardLimitMicros)],[`Requests per report`,String(a.maxRequestsPerReport)],[`Rows per report`,String(a.maxRowsPerReport)]])}}),spend:T({meta:{name:`spend`,description:`Show locally recorded spend for one installed provider`},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=O(e.id)??``;await Wd(t);let n={provider:t,local:u(t)};if(F(e)){R(n);return}z([[`Today`,Z(n.local.today.effectiveCostMicros)],[`This month`,Z(n.local.month.effectiveCostMicros)],[`Requests today`,String(n.local.today.requests)]])}}),doctor:T({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 Kd(),n={ok:t.installed.every(e=>e.loadStatus===`ready`),installed:t.installed};if(F(e)){R(n);return}if(n.installed.length===0){process.stdout.write(`No provider extensions are installed.
213
+ `);return}B([`Provider`,`Package`,`Version`,`Status`],n.installed.map(e=>[e.id,e.package,e.version,e.error??e.loadStatus]))}})},Jd=T({meta:{name:`semrush`,description:`Connect Semrush Version 3 for optional search data`},subCommands:{connect:T({meta:{name:`connect`,description:`Validate and save a Semrush Version 3 API key`},args:{json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{if(!L({json:F(e)}))throw new C(`AUTH_REQUIRED`,`Run \`seo providers semrush connect\` in a terminal. Agents and CI can set ${Yt}.`);Di(`Connect Semrush`),E(`Use the permanent Version 3 API Key shown on your Semrush API Keys page. Semrush creates this key automatically. The balance check is free; research reports consume API units.`,`API key`);let t=V(await Mi({message:`Semrush Version 3 API key`,validate:e=>e?.trim()?void 0:`API key is required`})),n=await new gr({apiKey:t}).apiUnitBalance(),r=await Nn(t);E(`${n.remainingUnits.toLocaleString(`en-US`)} API units remain.`,`Connection verified`),ji(`Saved in the ${Cd(r)}. Run seo providers semrush status --check to verify it again.`)}}),status:T({meta:{name:`status`,description:`Show the local Semrush connection`},args:{check:{type:`boolean`,default:!1,description:`Verify the Version 3 API key with a free balance request.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=await qn(),n=!!e.check,r=n&&t?await new gr().apiUnitBalance():void 0,i={connected:!!t,apiVersion:t?3:null,credentialSource:t?.source,migratedLegacyCredential:t?.migrated??!1,liveCheck:r?{status:`passed`,remainingUnits:r.remainingUnits,observedAt:r.observedAt,requestCostUnits:0}:{status:n?`unavailable`:`not-requested`}};if(F(e)){R(i);return}z([[`Connected`,i.connected?`yes`:`no`],[`API version`,i.apiVersion?`Version 3`:`not connected`],[`Credential`,Cd(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?[[`API units remaining`,r.remainingUnits.toLocaleString(`en-US`)]]:[]])}}),disconnect:T({meta:{name:`disconnect`,description:`Remove the saved Semrush Version 3 API key`},args:{json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{await rn();let t=!!process.env[Yt],n={savedCredentialRemoved:!0,environmentCredential:t?`active`:`missing`,note:t?`The environment variable was not changed. Clear ${Yt} to fully disconnect.`:`Semrush is disconnected.`};F(e)?R(n):process.stdout.write(`${n.note}\n`)}})}}),Yd=T({meta:{name:`providers`,description:`Connect optional data providers`},subCommands:{...qd,ahrefs:jd,bing:Ld,dataforseo:Vd,semrush:Jd}});function Xd(e){let t=e.indexed+e.notIndexed+e.unknown+e.warnings;return t?`${e.indexed}/${t} indexed`:`-`}function Zd(e){if(!e.samples.length)return`-`;let t=e.medianWordCount===void 0?`words ?`:`${W(e.medianWordCount)} words`,n=e.weakQueryCoverage?`; ${e.weakQueryCoverage} weak coverage`:``;return`${t}; ${e.usable}/${e.samples.length} usable${n}`}function Qd(e){return e===`serp-framing-review`?`wording review`:e===`body-term-review`?`literal term review`:`covered terms`}function $d(e){let t=e.staticSegments.map(e=>`/${e.value}`).join(``),n=e.variableSegments.slice(0,3).map(e=>{let t=e.examples.slice(0,3).join(`, `);return`${e.placeholder}@${e.index+1}${t?` (${t})`:``}`});return[t?`static ${t}`:`no fixed path prefix`,n.length?`variables ${n.join(`; `)}`:`no variables`].join(`; `)}function ef(e){if(e.checkedQueries)return`${Math.round(e.impressionShare*100)}% impression fit across ${On(e.checkedQueries,`checked query/page row`)}`}function tf(e){return e.startsWith(`theme: `)?`${e.slice(7)}-related`:e===`general`?`broad`:e}function nf(e){for(let t of e.slice(0,5)){process.stdout.write(`\n${t.signature}\n`),t.evidence.length&&process.stdout.write(` Evidence: ${t.evidence.join(`; `)}.\n`),process.stdout.write(` Shape: ${$d(t.shape)}\n`);let e=ef(t.metrics.entityFit);e&&process.stdout.write(` Entity fit: ${e}\n`);let n=t.metrics.queryPatterns.slice(0,3).map(e=>`${tf(e.label)} (${W(e.impressions)} impr: ${e.examples.slice(0,2).join(`; `)})`);n.length&&process.stdout.write(` Demand: ${n.join(` | `)}\n`);let r=t.metrics.topQueries.slice(0,3);if(r.length){process.stdout.write(` Top queries:
214
+ `);for(let e of r)process.stdout.write(` - ${G(e.query,72)} (${W(e.impressions)} impr, pos ${is(e.position)})\n`)}let i=t.crawl.samples.map(e=>e.queryCoverage).filter(e=>!!e).slice(0,3);if(i.length){process.stdout.write(` Sample coverage:
215
+ `);for(let e of i){let t=e.missingTerms.length?` missing ${e.missingTerms.slice(0,4).join(`, `)}`:``;process.stdout.write(` - ${Qd(e.classification)}: ${G(e.query,64)} (body ${(e.bodyCoverage*100).toFixed(0)}%${t})\n`)}}let a=t.metrics.entityFit.weakExamples.slice(0,2);if(a.length){process.stdout.write(` Weak entity-fit examples:
216
+ `);for(let e of a){let t=e.pathTerms.slice(0,4);process.stdout.write(` - ${G(e.query,64)} -> expected ${On(t.length,`path term`)}: ${t.join(`, `)}\n`)}}process.stdout.write(` Action: ${t.recommendation}\n`)}}const rf=T({meta:{name:`pseo`,description:`Programmatic SEO audits that cluster URL patterns and sample template pages`},subCommands:{audit:T({meta:{name:`audit`,description:`Group GSC/sitemap URLs into pSEO templates, then sample pages for crawl and index evidence`},args:{site:{type:`string`},project:{type:`string`,description:`Saved project id or name.`},client:{type:`string`,description:`Legacy alias for --project.`},...X([`days`,`limit`,`includeBrand`,`js`,`refresh`],{limit:{description:`Maximum templates to show. Defaults to 25.`},includeBrand:{description:`Include branded queries in template metrics.`},js:{description:`Force JavaScript rendering for crawl samples.`},refresh:{description:`Bypass local GSC and HTTP cache.`}}),sitemap:{type:`string`,description:`Comma-separated sitemap URLs to include in template counts.`},"crawl-samples":{type:`string`,description:`Sample URLs to fetch per detected template. This is not crawl depth.`},"inspect-samples":{type:`string`,description:`Sample URLs to check per detected template with URL Inspection.`},"max-sitemap-urls":{type:`string`,description:`Maximum discovered URLs per sitemap. Defaults to 50000.`},"minimum-template-urls":{type:`string`,description:`Minimum repeated URLs required for a template. Defaults to 3.`},"minimum-template-share":{type:`string`,description:`Minimum share of discovered URLs from 0 to 1. Defaults to 0.`},"minimum-template-impressions":{type:`string`,description:`Minimum retained page impressions for a template. Defaults to 0.`},"brand-terms":{type:`string`,description:`Comma-separated brand terms to exclude from query evidence.`},"fetch-concurrency":{type:`string`,description:`Maximum concurrent page fetches per host. Defaults to 4.`},"fetch-interval-cap":{type:`string`,description:`Maximum page fetches per interval per host. Defaults to 4.`},"fetch-interval-ms":{type:`string`,description:`Fetch rate interval in milliseconds. Defaults to 1000.`},json:{type:`boolean`,default:!1}},run:async({args:e})=>{let t=F(e),n=vu(e),r=await H({client:k(e),site:O(e.site),options:{json:t,refresh:A(e.refresh)}}),i=await Be({site:r.site,...n,brandTerms:n.brandTerms??r.client?.brandTerms,progress:Ul(!t)});if(t){R(i);return}if(K({title:`Programmatic SEO audit`,target:i.site,status:i.templates.length>0?`info`:`unknown`,summary:i.templates.length>0?`${W(i.summary.templates)} page templates were analysed.`:`No page templates could be established from the available evidence.`,metrics:[{label:`Templates`,value:W(i.summary.templates)},{label:`GSC pages`,value:W(i.summary.gscPages)},{label:`Sitemap URLs`,value:W(i.summary.sitemapUrls)},{label:`Clicks`,value:W(i.summary.clicks)},{label:`Impressions`,value:W(i.summary.impressions)},{label:`Usable crawls`,value:`${W(i.summary.crawledUrls)}/${W(i.summary.crawlAttempts)}`},{label:`Inspections`,value:`${W(i.summary.inspectedUrls)}/${W(i.summary.inspectionAttempts)}`}]}),Y(`Report caveats`,i.caveats),!i.templates.length){process.stdout.write(`No pSEO templates found from GSC/sitemap data.
217
+ `);return}q([`Verdict`,`Template`,`URLs`,`Clicks`,`Impr`,`CTR`,`Pos`,`Index`,`Crawl`,`Action`],i.templates.map(e=>[`${e.verdict} (${e.confidence})`,G(e.signature,28),W(e.urlCount),W(e.metrics.clicks),W(e.metrics.impressions),as(e.metrics.ctr),e.metrics.position.toFixed(1),Xd(e.inspection),Zd(e.crawl),G(e.recommendation,72)])),nf(i.templates),i.warnings.length&&process.stdout.write(`Warnings: ${i.warnings.join(`; `)}\n`),ls(`seo export pseo ${r.client?`--project ${JSON.stringify(r.client.id)}`:`--site ${JSON.stringify(r.site)}`}`)}})}}),af={"ai-search":`AI search`,crawl:`Crawling and technical checks`,diagnosis:`Diagnosis`,experiments:`Experiments`,monitoring:`Monitoring`,opportunities:`Opportunities`,reporting:`Reporting`,setup:`Setup`,workflows:`Workflows`};function of(e){let t=O(e);if(t){if(!cr.includes(t))throw new C(`INVALID_INPUT`,`Unknown report category: ${t}. Choose ${cr.join(`, `)}.`);return t}}async function sf(e,t){let n=O(e),r=O(t);if(n&&r)throw new C(`INVALID_INPUT`,`Use either --params or --params-file, not both.`);if(!n&&!r)return{};let i=n??await di(r??``,`utf8`),a;try{a=JSON.parse(i)}catch{throw new C(`INVALID_INPUT`,`Report parameters must be valid JSON.`)}if(!a||typeof a!=`object`||Array.isArray(a))throw new C(`INVALID_INPUT`,`Report parameters must be an object.`);return a}function cf(e){if(!e.isError)return;let t=e.structuredContent;throw new C(t?.error?.code??`INTERNAL_ERROR`,t?.error?.message??`The report could not be completed.`)}function lf(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 uf=T({meta:{name:`reports`,description:`Discover and run the complete report catalog`},subCommands:{list:T({meta:{name:`list`,description:`List report ids available to the CLI and MCP server`},args:{category:{type:`string`,description:`Filter by category: ${cr.join(`, `)}.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=rr(of(e.category));if(F(e)){R({reports:t,categories:cr});return}Ao(t,{noun:`report`,categoryLabels:af}),process.stdout.write(`
218
+ `),Eo({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:T({meta:{name:`describe`,description:`Show the purpose and parameter schema for one report`},args:{id:{type:`positional`,required:!0,description:"Report id from `seo reports list`."},check:{type:`string`,description:`Show fix guidance for one check id from the report output.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=O(e.check);if(t){let n=Ft(O(e.id)??``,t);if(F(e)){R({report:n});return}Do(`${n.name}: ${n.check}`,n.checkFix.goal),process.stdout.write(`
219
+ `),Mo(`Fix`,n.checkFix.fix),process.stdout.write(`
220
+ `),Mo(`Agent prompt`,n.checkFix.prompt),process.stdout.write(`
221
+ `),Mo(`Verify`,n.checkFix.verify),n.checkFix.resources.length>0&&(process.stdout.write(`
222
+ `),No(`Resources`,n.checkFix.resources.map(e=>`${e.title}: ${e.url}`))),process.stdout.write(`
223
+ `);return}let n=mn(O(e.id)??``);if(F(e)){R({report:n});return}Do(n.name,n.description),process.stdout.write(`
224
+ `),z([[`ID`,n.id],[`Category`,n.category],[`Outcome`,n.outcome]]),process.stdout.write(`
225
+ `),No(`Use when`,n.useWhen),process.stdout.write(`
226
+ `),No(`Avoid when`,n.avoidWhen),process.stdout.write(`
227
+ `),No(`Read in order`,n.readOrder),process.stdout.write(`
228
+ `),No(`Do not claim`,n.doNotClaim),process.stdout.write(`
229
+ `),Mo(`Verify`,n.verify),process.stdout.write(`
230
+ `),Eo({title:`Implement findings`,body:n.agentWorkflow.completion,command:n.agentWorkflow.actionView.cli}),n.related.length>0&&(process.stdout.write(`
231
+ `),No(`Related reports`,n.related.map(e=>`${e.id}: ${e.reason}`))),process.stdout.write(`
232
232
  Parameters
233
- `),No(cf(n.inputSchema)),n.fixableChecks&&(process.stdout.write(`
234
- `),To({title:`Fix a failed check`,body:`Get the fix steps, an agent prompt, and spec links for one check id from the report output.`,command:`seo reports describe ${n.id} --check <check-id>`})),process.stdout.write(`
235
- `),To({title:`Need the exact schema?`,command:`seo reports describe ${n.id} --json`})}}),run:E({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 zn(k(e.id)??``,await of(e.params,e[`params-file`]),{view:j(e[`actions-only`])?`actions`:`full`});if(sf(t),I(e)){z(t.structuredContent??{});return}for(let e of t.content)process.stdout.write(`${e.text}\n`)}})}}),uf={format:{type:`string`,description:`Output format: terminal or html. Defaults to terminal.`},output:{type:`string`,description:`HTML output file. A dated filename is used by default.`},view:{type:`string`,description:`HTML detail level: client or analyst. Defaults to client.`}};function df(e){let t=k(e.format)??`terminal`;if(t!==`terminal`&&t!==`html`)throw new w(`INVALID_INPUT`,`Choose terminal or html for --format.`);if(t===`terminal`){if(k(e.output)||k(e.view))throw new w(`INVALID_INPUT`,`Use --output and --view with --format html.`);return}if(j(e.json))throw new w(`INVALID_INPUT`,`Use either --json or --format html, not both.`);let n=k(e.view)??`client`;if(n!==`client`&&n!==`analyst`)throw new w(`INVALID_INPUT`,`Choose client or analyst for --view.`);return{output:k(e.output),view:n}}function ff(e){return e.toLowerCase().replace(/^sc-domain:/,``).replace(/^https?:\/\//,``).replace(/[^a-z0-9._-]+/g,`-`).replace(/^-+|-+$/g,``).slice(0,80)}function pf(e){let t=(e.date??new Date).toISOString().slice(0,10),n=ff(e.projectId??e.site)||`site`;return ni(process.cwd(),`${n}-${ff(e.reportName)}-${t}.html`)}async function mf(e){let t=ni(e.options.output??pf({reportName:e.reportName,projectId:e.projectId,site:e.report.site}));return await ci(Zr(t),{recursive:!0}),await gi(t,Xi({report:e.report,title:e.title,view:e.options.view,additionalSections:e.additionalSections}),`utf8`),t}function hf(e){process.stdout.write(`Wrote HTML report to ${e}\n`)}const gf=E({meta:{name:`monthly-report`,description:`Generate a monthly SEO report narrative`},args:{...Ul,month:{type:`string`,description:`Report month as YYYY-MM. Defaults to the latest month with final GSC data.`},...Z([`limit`],{limit:{description:`Maximum rows per diagnostic section. Defaults to 10.`}}),...Wl,...uf},run:async({args:e})=>{let t=I(e),n=df(e),r=Kl(e),i=await U({client:r.client,site:r.site,options:{json:t,refresh:r.refresh}}),a=await Ht({site:i.site,month:k(e.month),limit:M(e.limit),brandTerms:i.client?.brandTerms,...Gl(e),progress:Hl(!t)});if(t){z(a);return}if(n){hf(await mf({report:a,reportName:`monthly-report`,title:`Monthly SEO report for ${a.month}`,options:n,projectId:i.client?.id}));return}process.stdout.write(`${a.markdown}\n`);let o=i.client?`--project ${JSON.stringify(i.client.id)}`:`--site ${JSON.stringify(i.site)}`,s=k(e.month);process.stdout.write(`
236
- `),cs(`seo export monthly ${o}${s?` --month ${JSON.stringify(s)}`:``}`)}}),_f=E({meta:{name:`report-narrative`,description:`Generate a client-ready SEO narrative from diagnosis data`},args:{...Ul,...Z([`days`,`recentDays`],{days:{description:`Diagnosis window length in days. Defaults to 90.`}}),"start-date":{type:`string`,description:`Optional report start date as YYYY-MM-DD.`},"end-date":{type:`string`,description:`Optional report end date as YYYY-MM-DD.`},...Z([`limit`],{limit:{description:`Maximum rows per diagnostic section. Defaults to 10.`}}),"change-limit":{type:`string`,description:`Maximum saved changes to measure. Defaults to 5.`},...Wl,...uf},run:async({args:e})=>{let t=I(e),n=df(e),r=Kl(e),i=await U({client:r.client,site:r.site,options:{json:t,refresh:r.refresh}}),a=await Or({site:i.site,days:M(e.days),recentDays:M(e.recent),startDate:k(e[`start-date`]),endDate:k(e[`end-date`]),limit:M(e.limit),changeLimit:M(e[`change-limit`]),brandTerms:i.client?.brandTerms,...Gl(e),progress:Hl(!t)});if(t){z(a);return}if(n){hf(await mf({report:a,reportName:`narrative-report`,title:`SEO narrative report`,options:n,projectId:i.client?.id}));return}process.stdout.write(`${a.markdown}\n`);let o=i.client?`--project ${JSON.stringify(i.client.id)}`:`--site ${JSON.stringify(i.site)}`;process.stdout.write(`
237
- `),cs(`seo export narrative ${o}`)}}),vf=E({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=k(e.category),n=Fn().filter(e=>!t||e.category===t);if(I(e)){z({rules:n});return}ko(n.map(e=>({id:e.id,category:e.category,name:`${e.title} · ${e.defaultSeverity}`})),{noun:`rule`})}}),yf=E({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=k(e.rule)??``,n=cr(t);if(!n)throw Error(`Unknown rule: ${t}`);if(I(e)){z(n);return}B([[`Rule`,n.id],[`Title`,n.title],[`Category`,n.category],[`Severity`,n.defaultSeverity]]),process.stdout.write(`
238
- `),jo(`Why it matters`,n.whyItMatters),process.stdout.write(`
239
- `),jo(`How to fix`,n.howToFix),process.stdout.write(`
240
- `),jo(`Impact if ignored`,n.impactIfIgnored),process.stdout.write(`
241
- `),jo(`How to verify`,n.howToVerify)}});function bf(e){return JSON.stringify(e)}function xf(e){let t=e.cadence??`weekly`;if(![`daily`,`weekly`,`monthly`].includes(t))throw new w(`INVALID_INPUT`,`Rank tracking cron cadence must be daily, weekly, or monthly.`);let n=e.devices?.length?[...new Set(e.devices)]:void 0;if(n?.some(e=>![`desktop`,`mobile`].includes(e))||(n?.length??0)>2)throw new w(`INVALID_INPUT`,`Rank tracking devices must be desktop, mobile, or both.`);if(e.depth!==void 0&&(!Number.isSafeInteger(e.depth)||e.depth<1||e.depth>100))throw new w(`INVALID_INPUT`,`Rank tracking depth must be from 1 to 100.`);if(e.provider!==void 0&&![`dataforseo`,`serpbase`].includes(e.provider))throw new w(`INVALID_INPUT`,`Exact rank collection supports dataforseo or serpbase.`);let r=e.provider===`serpbase`?25:1e3;if(e.keywordLimit!==void 0&&(!Number.isSafeInteger(e.keywordLimit)||e.keywordLimit<1||e.keywordLimit>r))throw new w(`INVALID_INPUT`,e.provider===`serpbase`?`Live SerpBase rank tracking can collect 1 to 25 keywords per run.`:`Queued rank tracking can collect 1 to 1000 keywords per run.`);let i={projectId:e.projectId,set:e.set,targetDomain:e.targetDomain,...e.tag?{tag:e.tag}:{},...n?{devices:n}:{},...e.provider?{provider:e.provider}:{},collectionMethod:e.provider===`serpbase`?`live`:`queued`,cadence:t,...e.depth===void 0?{}:{depth:e.depth},...e.keywordLimit===void 0?{}:{keywordLimit:e.keywordLimit},start:!0};return{name:`rank-tracking`,cron:`${e.minute} ${e.hour} * * *`,command:[`seo reports run rank-tracking`,`--params ${bf(JSON.stringify(i))}`,`--json`].join(` `)}}const Sf=E({meta:{name:`schedule`,description:`Print local cron entries for recurring SEO workflows`},subCommands:{cron:E({meta:{name:`cron`,description:`Print crontab lines for local recurring SEO workflows`},args:{site:{type:`string`,description:`GSC property URL, for example sc-domain:example.com.`},client:{type:`string`,description:`Legacy alias for --project.`},project:{type:`string`,description:`Saved project id or name.`},url:{type:`string`,description:`Start URL for technical-watch. Defaults from the GSC property when possible.`},urls:{type:`string`,description:`Comma-separated URLs for index-watch.`},hour:{type:`string`,default:`9`,description:`Hour in local cron time. Defaults to 9.`},minute:{type:`string`,default:`0`,description:`Minute in local cron time. Defaults to 0.`},weekday:{type:`string`,description:`Weekly technical-watch day. Defaults to client setting, then Monday.`},day:{type:`string`,description:`Monthly report day. Defaults to client setting, then 1.`},"rank-set":{type:`string`,description:`Saved keyword set to collect with the rank-tracking report.`},"rank-domain":{type:`string`,description:`Target domain for exact rank matching.`},"rank-tag":{type:`string`,description:`Optional saved keyword tag to track.`},"rank-devices":{type:`string`,description:`Comma-separated desktop and mobile devices.`},"rank-depth":{type:`string`,description:`Organic result depth from 1 to 100.`},"rank-limit":{type:`string`,description:`Maximum saved keywords from 1 to 1000.`},"rank-provider":{type:`string`,description:`Connected provider for exact rank collection.`},"rank-cadence":{type:`string`,description:`Rank collection cadence: daily, weekly, or monthly.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=I(e),n=await U({client:A(e),site:k(e.site),options:{json:t}}),r=n.site,i=k(e.url)??n.client?.startUrl??bs(n.site),a=M(e.hour)??9,o=M(e.minute)??0,s=M(e.weekday)??n.client?.technicalWeekday??1,c=M(e.day)??n.client?.reportDay??1,l=n.client?`--project ${bf(n.client.id)}`:`--site ${bf(r)}`,u=k(e.urls)??n.client?.watchUrls.join(`,`),d=[{name:`technical-watch`,cron:`${o} ${a} * * ${s}`,command:[`seo technical-watch`,l,i?`--url ${bf(i)}`:void 0,u?`--urls ${bf(u)}`:void 0,`--json`].filter(Boolean).join(` `)},{name:`monthly-report`,cron:`${o} ${a} ${c} * *`,command:[`seo monthly-report`,l,`--json`].join(` `)}],f=k(e[`rank-set`]),p=k(e[`rank-domain`]);if(f||p){if(!f||!p)throw new w(`INVALID_INPUT`,`Pass both --rank-set and --rank-domain for scheduled rank tracking.`);if(!n.client)throw new w(`INVALID_INPUT`,`Scheduled rank tracking needs a saved project profile.`);d.push(xf({projectId:n.client.id,set:f,targetDomain:p,tag:k(e[`rank-tag`]),devices:F(e[`rank-devices`]),depth:M(e[`rank-depth`]),keywordLimit:M(e[`rank-limit`]),provider:k(e[`rank-provider`]),cadence:k(e[`rank-cadence`]),hour:a,minute:o,weekday:s,day:c}))}if(t){z({site:r,lines:d});return}let m=Bn();B([[`Property`,r],[`Install`,`crontab -e`]]);for(let e of d)process.stdout.write(`${e.cron} seo logs prune --quiet; ${e.command} >> ${bf(`${m.logDir}/${e.name}.log`)} 2>&1\n`)}})}}),Cf=E({meta:{name:`server-logs`,description:`Analyze local web server access logs`},subCommands:{analyze:E({meta:{name:`analyze`,description:`Stream a local access log into bounded crawler evidence`},args:{file:{type:`string`,description:`Local combined, JSONL, or NDJSON access log.`,required:!0},format:{type:`string`,description:`Input format override: combined or jsonl.`},"row-limit":{type:`string`,description:`Maximum rows to parse. Defaults to 1000000.`},"path-limit":{type:`string`,description:`Maximum crawler and path aggregates to retain. Defaults to 25000.`},limit:{type:`string`,description:`Maximum crawler path rows returned. Defaults to 100.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`},csv:{type:`string`,description:`CSV table: crawler-summary, crawler-paths, crawler-errors, or status-codes.`},output:{type:`string`,description:`Write CSV output to this path instead of stdout.`}},run:async({args:e})=>{let t=k(e.file);if(!t)throw new w(`INVALID_INPUT`,`Pass --file.`);let n=I(e),r=e.csv===void 0?void 0:wf(e.csv),i=k(e.output);if(n&&r)throw new w(`INVALID_INPUT`,`Use either --json or --csv, not both.`);if(i&&!r)throw new w(`INVALID_INPUT`,`--output requires --csv.`);let a=k(e.format);if(a&&![`combined`,`jsonl`].includes(a))throw new w(`INVALID_INPUT`,`--format must be combined or jsonl.`);let o=await xr({file:t,format:a,rowLimit:N(e[`row-limit`],`--row-limit`),pathLimit:N(e[`path-limit`],`--path-limit`)}),s=f({evidence:o,limit:N(e.limit,`--limit`)});if(r){let e=nt(o,r);await Tf(i,e.content),e.capped&&process.stderr.write(`Warning: ${e.omittedRows.toLocaleString()} CSV rows were omitted by the export limit.\n`);return}if(n){z(s);return}q({title:`Server log report`,status:s.dataStatus===`complete`?s.summary.invalidRows>0?`warning`:`info`:`unknown`,summary:`${G(s.summary.crawlerRows)} crawler requests found in ${G(s.summary.parsedRows)} parsed requests.`,metrics:[{label:`Evidence`,value:s.dataStatus},{label:`Parsed requests`,value:G(s.summary.parsedRows)},{label:`Crawler requests`,value:G(s.summary.crawlerRows)},{label:`Crawler families`,value:G(s.crawlers.length)},{label:`Invalid rows`,value:G(s.summary.invalidRows)},{label:`Returned paths`,value:`${G(s.selection.returnedCrawlerPaths)} of ${G(s.selection.availableCrawlerPaths)}`}]}),s.crawlers.length&&(process.stdout.write(`
233
+ `),Po(lf(n.inputSchema)),n.fixableChecks&&(process.stdout.write(`
234
+ `),Eo({title:`Fix a failed check`,body:`Get the fix steps, an agent prompt, and spec links for one check id from the report output.`,command:`seo reports describe ${n.id} --check <check-id>`})),process.stdout.write(`
235
+ `),Eo({title:`Need the exact schema?`,command:`seo reports describe ${n.id} --json`})}}),run:T({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 Bn(O(e.id)??``,await sf(e.params,e[`params-file`]),{view:A(e[`actions-only`])?`actions`:`full`});if(cf(t),F(e)){R(t.structuredContent??{});return}for(let e of t.content)process.stdout.write(`${e.text}\n`)}})}}),df={format:{type:`string`,description:`Output format: terminal or html. Defaults to terminal.`},output:{type:`string`,description:`HTML output file. A dated filename is used by default.`},view:{type:`string`,description:`HTML detail level: client or analyst. Defaults to client.`}};function ff(e){let t=O(e.format)??`terminal`;if(t!==`terminal`&&t!==`html`)throw new C(`INVALID_INPUT`,`Choose terminal or html for --format.`);if(t===`terminal`){if(O(e.output)||O(e.view))throw new C(`INVALID_INPUT`,`Use --output and --view with --format html.`);return}if(A(e.json))throw new C(`INVALID_INPUT`,`Use either --json or --format html, not both.`);let n=O(e.view)??`client`;if(n!==`client`&&n!==`analyst`)throw new C(`INVALID_INPUT`,`Choose client or analyst for --view.`);return{output:O(e.output),view:n}}function pf(e){return e.toLowerCase().replace(/^sc-domain:/,``).replace(/^https?:\/\//,``).replace(/[^a-z0-9._-]+/g,`-`).replace(/^-+|-+$/g,``).slice(0,80)}function mf(e){let t=(e.date??new Date).toISOString().slice(0,10),n=pf(e.projectId??e.site)||`site`;return ri(process.cwd(),`${n}-${pf(e.reportName)}-${t}.html`)}async function hf(e){let t=ri(e.options.output??mf({reportName:e.reportName,projectId:e.projectId,site:e.report.site}));return await li(Qr(t),{recursive:!0}),await _i(t,Zi({report:e.report,title:e.title,view:e.options.view,additionalSections:e.additionalSections}),`utf8`),t}function gf(e){process.stdout.write(`Wrote HTML report to ${e}\n`)}const _f=T({meta:{name:`monthly-report`,description:`Generate a monthly SEO report narrative`},args:{...Wl,month:{type:`string`,description:`Report month as YYYY-MM. Defaults to the latest month with final GSC data.`},...X([`limit`],{limit:{description:`Maximum rows per diagnostic section. Defaults to 10.`}}),...Gl,...df},run:async({args:e})=>{let t=F(e),n=ff(e),r=ql(e),i=await H({client:r.client,site:r.site,options:{json:t,refresh:r.refresh}}),a=await Ut({site:i.site,month:O(e.month),limit:j(e.limit),brandTerms:i.client?.brandTerms,...Kl(e),progress:Ul(!t)});if(t){R(a);return}if(n){gf(await hf({report:a,reportName:`monthly-report`,title:`Monthly SEO report for ${a.month}`,options:n,projectId:i.client?.id}));return}process.stdout.write(`${a.markdown}\n`);let o=i.client?`--project ${JSON.stringify(i.client.id)}`:`--site ${JSON.stringify(i.site)}`,s=O(e.month);process.stdout.write(`
236
+ `),ls(`seo export monthly ${o}${s?` --month ${JSON.stringify(s)}`:``}`)}}),vf=T({meta:{name:`report-narrative`,description:`Generate a client-ready SEO narrative from diagnosis data`},args:{...Wl,...X([`days`,`recentDays`],{days:{description:`Diagnosis window length in days. Defaults to 90.`}}),"start-date":{type:`string`,description:`Optional report start date as YYYY-MM-DD.`},"end-date":{type:`string`,description:`Optional report end date as YYYY-MM-DD.`},...X([`limit`],{limit:{description:`Maximum rows per diagnostic section. Defaults to 10.`}}),"change-limit":{type:`string`,description:`Maximum saved changes to measure. Defaults to 5.`},...Gl,...df},run:async({args:e})=>{let t=F(e),n=ff(e),r=ql(e),i=await H({client:r.client,site:r.site,options:{json:t,refresh:r.refresh}}),a=await kr({site:i.site,days:j(e.days),recentDays:j(e.recent),startDate:O(e[`start-date`]),endDate:O(e[`end-date`]),limit:j(e.limit),changeLimit:j(e[`change-limit`]),brandTerms:i.client?.brandTerms,...Kl(e),progress:Ul(!t)});if(t){R(a);return}if(n){gf(await hf({report:a,reportName:`narrative-report`,title:`SEO narrative report`,options:n,projectId:i.client?.id}));return}process.stdout.write(`${a.markdown}\n`);let o=i.client?`--project ${JSON.stringify(i.client.id)}`:`--site ${JSON.stringify(i.site)}`;process.stdout.write(`
237
+ `),ls(`seo export narrative ${o}`)}}),yf=T({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=O(e.category),n=In().filter(e=>!t||e.category===t);if(F(e)){R({rules:n});return}Ao(n.map(e=>({id:e.id,category:e.category,name:`${e.title} · ${e.defaultSeverity}`})),{noun:`rule`})}}),bf=T({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=O(e.rule)??``,n=lr(t);if(!n)throw Error(`Unknown rule: ${t}`);if(F(e)){R(n);return}z([[`Rule`,n.id],[`Title`,n.title],[`Category`,n.category],[`Severity`,n.defaultSeverity]]),process.stdout.write(`
238
+ `),Mo(`Why it matters`,n.whyItMatters),process.stdout.write(`
239
+ `),Mo(`How to fix`,n.howToFix),process.stdout.write(`
240
+ `),Mo(`Impact if ignored`,n.impactIfIgnored),process.stdout.write(`
241
+ `),Mo(`How to verify`,n.howToVerify)}});function xf(e){return JSON.stringify(e)}function Sf(e){let t=e.cadence??`weekly`;if(![`daily`,`weekly`,`monthly`].includes(t))throw new C(`INVALID_INPUT`,`Rank tracking cron cadence must be daily, weekly, or monthly.`);let n=e.devices?.length?[...new Set(e.devices)]:void 0;if(n?.some(e=>![`desktop`,`mobile`].includes(e))||(n?.length??0)>2)throw new C(`INVALID_INPUT`,`Rank tracking devices must be desktop, mobile, or both.`);if(e.depth!==void 0&&(!Number.isSafeInteger(e.depth)||e.depth<1||e.depth>100))throw new C(`INVALID_INPUT`,`Rank tracking depth must be from 1 to 100.`);if(e.provider!==void 0&&![`dataforseo`,`serpbase`].includes(e.provider))throw new C(`INVALID_INPUT`,`Exact rank collection supports dataforseo or serpbase.`);let r=e.provider===`serpbase`?25:1e3;if(e.keywordLimit!==void 0&&(!Number.isSafeInteger(e.keywordLimit)||e.keywordLimit<1||e.keywordLimit>r))throw new C(`INVALID_INPUT`,e.provider===`serpbase`?`Live SerpBase rank tracking can collect 1 to 25 keywords per run.`:`Queued rank tracking can collect 1 to 1000 keywords per run.`);let i={projectId:e.projectId,set:e.set,targetDomain:e.targetDomain,...e.tag?{tag:e.tag}:{},...n?{devices:n}:{},...e.provider?{provider:e.provider}:{},collectionMethod:e.provider===`serpbase`?`live`:`queued`,cadence:t,...e.depth===void 0?{}:{depth:e.depth},...e.keywordLimit===void 0?{}:{keywordLimit:e.keywordLimit},start:!0};return{name:`rank-tracking`,cron:`${e.minute} ${e.hour} * * *`,command:[`seo reports run rank-tracking`,`--params ${xf(JSON.stringify(i))}`,`--json`].join(` `)}}const Cf=T({meta:{name:`schedule`,description:`Print local cron entries for recurring SEO workflows`},subCommands:{cron:T({meta:{name:`cron`,description:`Print crontab lines for local recurring SEO workflows`},args:{site:{type:`string`,description:`GSC property URL, for example sc-domain:example.com.`},client:{type:`string`,description:`Legacy alias for --project.`},project:{type:`string`,description:`Saved project id or name.`},url:{type:`string`,description:`Start URL for technical-watch. Defaults from the GSC property when possible.`},urls:{type:`string`,description:`Comma-separated URLs for index-watch.`},hour:{type:`string`,default:`9`,description:`Hour in local cron time. Defaults to 9.`},minute:{type:`string`,default:`0`,description:`Minute in local cron time. Defaults to 0.`},weekday:{type:`string`,description:`Weekly technical-watch day. Defaults to client setting, then Monday.`},day:{type:`string`,description:`Monthly report day. Defaults to client setting, then 1.`},"rank-set":{type:`string`,description:`Saved keyword set to collect with the rank-tracking report.`},"rank-domain":{type:`string`,description:`Target domain for exact rank matching.`},"rank-tag":{type:`string`,description:`Optional saved keyword tag to track.`},"rank-devices":{type:`string`,description:`Comma-separated desktop and mobile devices.`},"rank-depth":{type:`string`,description:`Organic result depth from 1 to 100.`},"rank-limit":{type:`string`,description:`Maximum saved keywords from 1 to 1000.`},"rank-provider":{type:`string`,description:`Connected provider for exact rank collection.`},"rank-cadence":{type:`string`,description:`Rank collection cadence: daily, weekly, or monthly.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=F(e),n=await H({client:k(e),site:O(e.site),options:{json:t}}),r=n.site,i=O(e.url)??n.client?.startUrl??xs(n.site),a=j(e.hour)??9,o=j(e.minute)??0,s=j(e.weekday)??n.client?.technicalWeekday??1,c=j(e.day)??n.client?.reportDay??1,l=n.client?`--project ${xf(n.client.id)}`:`--site ${xf(r)}`,u=O(e.urls)??n.client?.watchUrls.join(`,`),d=[{name:`technical-watch`,cron:`${o} ${a} * * ${s}`,command:[`seo technical-watch`,l,i?`--url ${xf(i)}`:void 0,u?`--urls ${xf(u)}`:void 0,`--json`].filter(Boolean).join(` `)},{name:`monthly-report`,cron:`${o} ${a} ${c} * *`,command:[`seo monthly-report`,l,`--json`].join(` `)}],f=O(e[`rank-set`]),p=O(e[`rank-domain`]);if(f||p){if(!f||!p)throw new C(`INVALID_INPUT`,`Pass both --rank-set and --rank-domain for scheduled rank tracking.`);if(!n.client)throw new C(`INVALID_INPUT`,`Scheduled rank tracking needs a saved project profile.`);d.push(Sf({projectId:n.client.id,set:f,targetDomain:p,tag:O(e[`rank-tag`]),devices:P(e[`rank-devices`]),depth:j(e[`rank-depth`]),keywordLimit:j(e[`rank-limit`]),provider:O(e[`rank-provider`]),cadence:O(e[`rank-cadence`]),hour:a,minute:o,weekday:s,day:c}))}if(t){R({site:r,lines:d});return}let m=Vn();z([[`Property`,r],[`Install`,`crontab -e`]]);for(let e of d)process.stdout.write(`${e.cron} seo logs prune --quiet; ${e.command} >> ${xf(`${m.logDir}/${e.name}.log`)} 2>&1\n`)}})}}),wf=T({meta:{name:`server-logs`,description:`Analyze local web server access logs`},subCommands:{analyze:T({meta:{name:`analyze`,description:`Stream a local access log into bounded crawler evidence`},args:{file:{type:`string`,description:`Local combined, JSONL, or NDJSON access log.`,required:!0},format:{type:`string`,description:`Input format override: combined or jsonl.`},"row-limit":{type:`string`,description:`Maximum rows to parse. Defaults to 1000000.`},"path-limit":{type:`string`,description:`Maximum crawler and path aggregates to retain. Defaults to 25000.`},limit:{type:`string`,description:`Maximum crawler path rows returned. Defaults to 100.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`},csv:{type:`string`,description:`CSV table: crawler-summary, crawler-paths, crawler-errors, or status-codes.`},output:{type:`string`,description:`Write CSV output to this path instead of stdout.`}},run:async({args:e})=>{let t=O(e.file);if(!t)throw new C(`INVALID_INPUT`,`Pass --file.`);let n=F(e),r=e.csv===void 0?void 0:Tf(e.csv),i=O(e.output);if(n&&r)throw new C(`INVALID_INPUT`,`Use either --json or --csv, not both.`);if(i&&!r)throw new C(`INVALID_INPUT`,`--output requires --csv.`);let a=O(e.format);if(a&&![`combined`,`jsonl`].includes(a))throw new C(`INVALID_INPUT`,`--format must be combined or jsonl.`);let o=await Sr({file:t,format:a,rowLimit:M(e[`row-limit`],`--row-limit`),pathLimit:M(e[`path-limit`],`--path-limit`)}),s=f({evidence:o,limit:M(e.limit,`--limit`)});if(r){let e=rt(o,r);await Ef(i,e.content),e.capped&&process.stderr.write(`Warning: ${e.omittedRows.toLocaleString()} CSV rows were omitted by the export limit.\n`);return}if(n){R(s);return}K({title:`Server log report`,status:s.dataStatus===`complete`?s.summary.invalidRows>0?`warning`:`info`:`unknown`,summary:`${W(s.summary.crawlerRows)} crawler requests found in ${W(s.summary.parsedRows)} parsed requests.`,metrics:[{label:`Evidence`,value:s.dataStatus},{label:`Parsed requests`,value:W(s.summary.parsedRows)},{label:`Crawler requests`,value:W(s.summary.crawlerRows)},{label:`Crawler families`,value:W(s.crawlers.length)},{label:`Invalid rows`,value:W(s.summary.invalidRows)},{label:`Returned paths`,value:`${W(s.selection.returnedCrawlerPaths)} of ${W(s.selection.availableCrawlerPaths)}`}]}),s.crawlers.length&&(process.stdout.write(`
242
242
  Crawler activity
243
- `),J([`Crawler`,`Type`,`Requests`,`4xx`,`5xx`],s.crawlers.map(e=>[e.family,e.category,String(e.requests),String(e.clientError),String(e.serverError)]))),s.crawlerPaths.length&&(process.stdout.write(`
243
+ `),q([`Crawler`,`Type`,`Requests`,`4xx`,`5xx`],s.crawlers.map(e=>[e.family,e.category,String(e.requests),String(e.clientError),String(e.serverError)]))),s.crawlerPaths.length&&(process.stdout.write(`
244
244
  Crawler paths
245
- `),J([`Crawler`,`Path`,`Requests`,`4xx`,`5xx`],s.crawlerPaths.map(e=>[e.family,K(e.path,72),String(e.requests),String(e.clientError),String(e.serverError)])));for(let e of s.warnings)process.stdout.write(`\nWarning: ${e}\n`);process.stdout.write(`\nNote: ${s.caveats[0]}\n`)}})}});function wf(e){let t=k(e);if(t&&[`crawler-summary`,`crawler-paths`,`crawler-errors`,`status-codes`].includes(t))return t;throw new w(`INVALID_INPUT`,`CSV table must be crawler-summary, crawler-paths, crawler-errors, or status-codes.`)}async function Tf(e,t){if(!e){process.stdout.write(t);return}await ci(Zr(e),{recursive:!0}),await gi(e,t),process.stdout.write(`Wrote ${e}\n`)}function Ef(){return Of(`skills`,process.env.SEO_SKILLS_DIR)}function Df(){return Of(`evals`,process.env.SEO_EVALS_DIR)}function Of(e,t){if(t)return ni(t);let n=Zr(yi(import.meta.url));for(let t=0;t<6;t+=1){let t=T(n,e);if(Hr(T(t,`README.md`)))return t;n=Zr(n)}throw new w(`INTERNAL_ERROR`,`Packaged ${e} could not be found. Reinstall \`seo\` and try again.`)}function kf(e){return Wr(T(e,`SKILL.md`),`utf8`).match(/^description:\s*(.+)$/m)?.[1]?.trim()??``}const Af=`claude -p`;function jf(){let e=Df();return Gr(e,{withFileTypes:!0}).filter(e=>e.isFile()&&e.name.endsWith(`.json`)).map(t=>({subject:Xr(t.name,`.json`),path:T(e,t.name)})).sort((e,t)=>e.subject<t.subject?-1:+(e.subject>t.subject))}function Mf(e,t){let n;try{n=Wr(t,`utf8`)}catch{throw new w(`INVALID_INPUT`,`Cannot read eval file for ${e}.`)}let r;try{r=JSON.parse(n)}catch{throw new w(`INVALID_INPUT`,`Eval file ${e}.json is not valid JSON.`)}if(!r||typeof r!=`object`||Array.isArray(r))throw new w(`INVALID_INPUT`,`Eval file ${e}.json must be an object.`);let i=r;if(i.subject!==e)throw new w(`INVALID_INPUT`,`Eval file ${e}.json must set subject to "${e}".`);if(!Array.isArray(i.evals)||i.evals.length===0)throw new w(`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 w(`INVALID_INPUT`,`${i}: id must be an integer.`);let o=r.id;if(a.has(o))throw new w(`INVALID_INPUT`,`${i}: duplicate id ${o}.`);if(a.add(o),typeof r.prompt!=`string`||r.prompt.trim().length===0)throw new w(`INVALID_INPUT`,`${i}: prompt must be a non-empty string.`);if(typeof r.expected_output!=`string`||r.expected_output.trim().length===0)throw new w(`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 w(`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 w(`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 Nf(){let e=T(Ef(),`seo`,`SKILL.md`);if(!Hr(e))throw new w(`INTERNAL_ERROR`,"The seo router skill could not be found. Reinstall `seo` or use --no-skill.");return Wr(e,`utf8`)}function Pf(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===`
246
- `||s===`\r`){r&&=(t.push(n),n=``,!1);continue}n+=s,r=!0}if(i||a)throw new w(`INVALID_INPUT`,`Unbalanced quote in agent command.`);return r&&t.push(n),t}function Ff(e,t){let n=Pf(e);if(n.length===0)throw new w(`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 If(e,t){return e?`${e.trim()}\n\n---\n\nUser request:\n${t}`:t}function Lf(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(`
245
+ `),q([`Crawler`,`Path`,`Requests`,`4xx`,`5xx`],s.crawlerPaths.map(e=>[e.family,G(e.path,72),String(e.requests),String(e.clientError),String(e.serverError)])));for(let e of s.warnings)process.stdout.write(`\nWarning: ${e}\n`);process.stdout.write(`\nNote: ${s.caveats[0]}\n`)}})}});function Tf(e){let t=O(e);if(t&&[`crawler-summary`,`crawler-paths`,`crawler-errors`,`status-codes`].includes(t))return t;throw new C(`INVALID_INPUT`,`CSV table must be crawler-summary, crawler-paths, crawler-errors, or status-codes.`)}async function Ef(e,t){if(!e){process.stdout.write(t);return}await li(Qr(e),{recursive:!0}),await _i(e,t),process.stdout.write(`Wrote ${e}\n`)}function Df(){return kf(`skills`,process.env.SEO_SKILLS_DIR)}function Of(){return kf(`evals`,process.env.SEO_EVALS_DIR)}function kf(e,t){if(t)return ri(t);let n=Qr(bi(import.meta.url));for(let t=0;t<6;t+=1){let t=w(n,e);if(Ur(w(t,`README.md`)))return t;n=Qr(n)}throw new C(`INTERNAL_ERROR`,`Packaged ${e} could not be found. Reinstall \`seo\` and try again.`)}function Af(e){return Gr(w(e,`SKILL.md`),`utf8`).match(/^description:\s*(.+)$/m)?.[1]?.trim()??``}const jf=`claude -p`;function Mf(){let e=Of();return Kr(e,{withFileTypes:!0}).filter(e=>e.isFile()&&e.name.endsWith(`.json`)).map(t=>({subject:Zr(t.name,`.json`),path:w(e,t.name)})).sort((e,t)=>e.subject<t.subject?-1:+(e.subject>t.subject))}function Nf(e,t){let n;try{n=Gr(t,`utf8`)}catch{throw new C(`INVALID_INPUT`,`Cannot read eval file for ${e}.`)}let r;try{r=JSON.parse(n)}catch{throw new C(`INVALID_INPUT`,`Eval file ${e}.json is not valid JSON.`)}if(!r||typeof r!=`object`||Array.isArray(r))throw new C(`INVALID_INPUT`,`Eval file ${e}.json must be an object.`);let i=r;if(i.subject!==e)throw new C(`INVALID_INPUT`,`Eval file ${e}.json must set subject to "${e}".`);if(!Array.isArray(i.evals)||i.evals.length===0)throw new C(`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 C(`INVALID_INPUT`,`${i}: id must be an integer.`);let o=r.id;if(a.has(o))throw new C(`INVALID_INPUT`,`${i}: duplicate id ${o}.`);if(a.add(o),typeof r.prompt!=`string`||r.prompt.trim().length===0)throw new C(`INVALID_INPUT`,`${i}: prompt must be a non-empty string.`);if(typeof r.expected_output!=`string`||r.expected_output.trim().length===0)throw new C(`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 C(`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 C(`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 Pf(){let e=w(Df(),`seo`,`SKILL.md`);if(!Ur(e))throw new C(`INTERNAL_ERROR`,"The seo router skill could not be found. Reinstall `seo` or use --no-skill.");return Gr(e,`utf8`)}function Ff(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===`
246
+ `||s===`\r`){r&&=(t.push(n),n=``,!1);continue}n+=s,r=!0}if(i||a)throw new C(`INVALID_INPUT`,`Unbalanced quote in agent command.`);return r&&t.push(n),t}function If(e,t){let n=Ff(e);if(n.length===0)throw new C(`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 Lf(e,t){return e?`${e.trim()}\n\n---\n\nUser request:\n${t}`:t}function Rf(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(`
247
247
  `),`</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(`
248
- `);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 Rf(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 zf(e,t){let n=Rf(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 Bf(e,t){return new Promise(n=>{let r=Date.now(),[i,...a]=e,o;try{o=bi(i,a,{stdio:[`pipe`,`pipe`,`pipe`]})}catch(e){n({stdout:``,stderr:``,code:null,timedOut:!1,spawnError:e instanceof Error?e.message:String(e),durationMs:Date.now()-r});return}let s=``,c=``,l=!1,u=!1,d,f=setTimeout(()=>{u=!0,o.kill(`SIGTERM`),d=setTimeout(()=>{try{o.kill(`SIGKILL`)}catch{}},2e3),d.unref()},t.timeoutMs),p=e=>{l||(l=!0,clearTimeout(f),d&&clearTimeout(d),n({...e,durationMs:Date.now()-r}))};o.stdout?.on(`data`,e=>{s+=e}),o.stderr?.on(`data`,e=>{c+=e}),o.on(`error`,e=>{p({stdout:s,stderr:c,code:null,timedOut:u,spawnError:e.message})}),o.on(`close`,e=>{p({stdout:s,stderr:c,code:e,timedOut:u})}),o.stdin&&(t.input===void 0?o.stdin.end():o.stdin.end(t.input))})}async function Vf(e,t,n){let{argv:r,usesPlaceholder:i}=Ff(e,t);return Bf(r,{input:i?void 0:t,timeoutMs:n})}async function Hf(e,t){let n=Date.now(),r=If(t.skillBody,e.prompt),i=await Vf(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:Uf(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:Uf(e.assertions)};if(a.length===0)return{...o,status:`errored`,error:`Agent returned no output on stdout.`,durationMs:Date.now()-n,assertions:Uf(e.assertions)};if(!t.judge)return{...o,status:`unjudged`,durationMs:Date.now()-n,assertions:Uf(e.assertions)};let s=0,c=``;for(let r=0;r<2;r+=1){let i=Lf(e.prompt,a,e.assertions,r>0),l=await Vf(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:Uf(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:Uf(e.assertions)};let u=zf(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:Uf(e.assertions)}}function Uf(e){return e.map(e=>({assertion:e,verdict:`unjudged`}))}async function Wf(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 Gf(e){let t={passed:0,failed:0,errored:0,unjudged:0};for(let n of e)t[n.status]+=1;return t}function Kf(e){let t=[],n=k(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 qf(e){let t=jf();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 w(`INVALID_INPUT`,`Unknown eval subject: ${e}.${n?` Known subjects: ${n}.`:``}`)}return r})}function Jf(e){let t=jf().map(e=>{let t=Mf(e.subject,e.path);return{subject:e.subject,evals:t.evals.length}});if(e){z({schemaVersion:1,subjects:t});return}if(t.length===0){process.stdout.write(`No eval subjects found.
248
+ `);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 zf(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 Bf(e,t){let n=zf(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 Vf(e,t){return new Promise(n=>{let r=Date.now(),[i,...a]=e,o;try{o=xi(i,a,{stdio:[`pipe`,`pipe`,`pipe`]})}catch(e){n({stdout:``,stderr:``,code:null,timedOut:!1,spawnError:e instanceof Error?e.message:String(e),durationMs:Date.now()-r});return}let s=``,c=``,l=!1,u=!1,d,f=setTimeout(()=>{u=!0,o.kill(`SIGTERM`),d=setTimeout(()=>{try{o.kill(`SIGKILL`)}catch{}},2e3),d.unref()},t.timeoutMs),p=e=>{l||(l=!0,clearTimeout(f),d&&clearTimeout(d),n({...e,durationMs:Date.now()-r}))};o.stdout?.on(`data`,e=>{s+=e}),o.stderr?.on(`data`,e=>{c+=e}),o.on(`error`,e=>{p({stdout:s,stderr:c,code:null,timedOut:u,spawnError:e.message})}),o.on(`close`,e=>{p({stdout:s,stderr:c,code:e,timedOut:u})}),o.stdin&&(t.input===void 0?o.stdin.end():o.stdin.end(t.input))})}async function Hf(e,t,n){let{argv:r,usesPlaceholder:i}=If(e,t);return Vf(r,{input:i?void 0:t,timeoutMs:n})}async function Uf(e,t){let n=Date.now(),r=Lf(t.skillBody,e.prompt),i=await Hf(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:Wf(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:Wf(e.assertions)};if(a.length===0)return{...o,status:`errored`,error:`Agent returned no output on stdout.`,durationMs:Date.now()-n,assertions:Wf(e.assertions)};if(!t.judge)return{...o,status:`unjudged`,durationMs:Date.now()-n,assertions:Wf(e.assertions)};let s=0,c=``;for(let r=0;r<2;r+=1){let i=Rf(e.prompt,a,e.assertions,r>0),l=await Hf(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:Wf(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:Wf(e.assertions)};let u=Bf(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:Wf(e.assertions)}}function Wf(e){return e.map(e=>({assertion:e,verdict:`unjudged`}))}async function Gf(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 Kf(e){let t={passed:0,failed:0,errored:0,unjudged:0};for(let n of e)t[n.status]+=1;return t}function qf(e){let t=[],n=O(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 Jf(e){let t=Mf();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 C(`INVALID_INPUT`,`Unknown eval subject: ${e}.${n?` Known subjects: ${n}.`:``}`)}return r})}function Yf(e){let t=Mf().map(e=>{let t=Nf(e.subject,e.path);return{subject:e.subject,evals:t.evals.length}});if(e){R({schemaVersion:1,subjects:t});return}if(t.length===0){process.stdout.write(`No eval subjects found.
249
249
  `);return}let n=Math.max(...t.map(e=>e.subject.length));process.stdout.write(`Eval subjects
250
- `);for(let e of t){let t=e.evals===1?`eval`:`evals`;process.stdout.write(` ${e.subject.padEnd(n)} ${e.evals} ${t}\n`)}}function Yf(e){return e===`passed`?`PASS`:e===`failed`?`FAIL`:e===`errored`?`ERROR`:`UNJUDGED`}function Xf(e,t){t.judged?process.stdout.write(`Assertion verdicts below are judge verdicts, model judgments from the judge agent, not ground truth.
250
+ `);for(let e of t){let t=e.evals===1?`eval`:`evals`;process.stdout.write(` ${e.subject.padEnd(n)} ${e.evals} ${t}\n`)}}function Xf(e){return e===`passed`?`PASS`:e===`failed`?`FAIL`:e===`errored`?`ERROR`:`UNJUDGED`}function Zf(e,t){t.judged?process.stdout.write(`Assertion verdicts below are judge verdicts, model judgments from the judge agent, not ground truth.
251
251
 
252
252
  `):process.stdout.write(`Judging was skipped. Review each agent reply against the assertion checklist by hand.
253
253
 
254
- `);for(let n of e){process.stdout.write(`# ${n.subject}\n`);for(let e of n.evals){if(process.stdout.write(`\n[${Yf(e.status)}] eval ${e.id} (${e.durationMs}ms)\n`),process.stdout.write(` prompt: ${Qf(e.prompt,100)}\n`),e.error&&process.stdout.write(` error: ${e.error}\n`),!t.judged){process.stdout.write(` reply: ${Qf(e.reply,200)}\n`),process.stdout.write(` assertions to review:
255
- `);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=Gf(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=Zf(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 Zf(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 Qf(e,t){let n=e.replace(/\s+/g,` `).trim();return n.length>t?`${n.slice(0,t-1)}…`:n}const $f=E({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:Af,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:Pa(`Judge assertions with an agent.`,`Skip judging and print replies with an assertion checklist.`),skill:Pa(`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=I(e);if(e.list===!0){Jf(t);return}let n=qf(Kf(e)),r=M(e.id);if(r!==void 0&&n.length!==1)throw new w(`INVALID_INPUT`,`--id needs exactly one subject. Name the subject to filter.`);let i=M(e.timeout)??300;if(!(i>0))throw new w(`INVALID_INPUT`,`--timeout must be a positive number of seconds.`);let a=Math.max(1,Math.min(4,M(e.concurrency)??1)),o=k(e.agent)??Af,s=e.judge===!1?null:k(e[`judge-agent`])??o,c=e.skill===!1?null:Nf(),l={agent:o,judge:s,skillBody:c,timeoutMs:i*1e3},u=[];for(let e of n){let n=Mf(e.subject,e.path).evals;if(r!==void 0&&(n=n.filter(e=>e.id===r),n.length===0))throw new w(`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 Wf(n,a,e=>Hf(e,l)),o=Gf(i);u.push({subject:e.subject,...o,evals:i})}let d=Zf(u),f=d.failed>0||d.errored>0;t?z({schemaVersion:1,agent:o,judge:s,judged:s!==null,skillContext:c!==null,timeoutSeconds:i,concurrency:a,subjects:u,totals:d}):Xf(u,{judged:s!==null,agent:o,judge:s}),f&&(process.exitCode=1)}});function ep(){let e=Ef();return Gr(e,{withFileTypes:!0}).filter(t=>t.isDirectory()&&Hr(T(e,t.name,`SKILL.md`))).map(t=>({name:t.name,description:kf(T(e,t.name)),path:T(e,t.name)})).sort((e,t)=>e.name<t.name?-1:+(e.name>t.name))}function tp(e){let t=ep();if(!e)return t;let n=t.find(t=>t.name===e);if(!n)throw new w(`INVALID_INPUT`,`Unknown skill: ${e}.`);return[n]}const np=E({meta:{name:`skill`,description:`Inspect the packaged SEO skill and run its evals`},subCommands:{list:E({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=ep();if(I(e)){z({skills:t});return}Eo(`SEO skill`,`Packaged with this CLI.`),process.stdout.write(`
256
- `);for(let e of t)B([[`Name`,e.name],[`Purpose`,`Helps agents choose and run the right SEO audit or report.`],[`Path`,e.path]]);process.stdout.write(`
257
- `),To({title:`Add it to an agent`,body:`Run setup to install the skill and connect your first site.`,command:`seo start`})}}),path:E({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=k(e.name),n=t?tp(t)[0]?.path:Ef();I(e)?z({name:t,path:n}):process.stdout.write(`${n}\n`)}}),install:E({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=I(e),n=ec({quiet:t});if(t){z(n),n.status===`failed`&&(process.exitCode=1);return}if(n.status===`failed`)throw new w(`INTERNAL_ERROR`,`Skill install failed: ${n.error}`);process.stdout.write(`SEO skill installed. Agents that support skills can discover it now.
258
- `)}}),eval:$f}}),rp=E({meta:{name:`privacy`,description:`Show local config, token, cache, and log storage paths`},run:async()=>{let e=gt(),t=Ft();B(e.map(e=>[e.label,`${e.path} · ${Po(e.sizeBytes)} · ${e.mode}`])),process.stdout.write(`
259
- `),B(Object.entries(t.counts).map(([e,t])=>[e,String(t)]))}}),ip=E({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=I(e);if(!e.yes&&!R({json:t}))throw new w(`INVALID_INPUT`,`Cannot prompt here. Pass --yes to confirm reset.`);if(!(e.yes||H(await Ti({message:`Delete saved credentials and all local data?`,initialValue:!1})))){wi(`Reset aborted.`);return}await Qe(),t?z({reset:!0}):process.stdout.write(`Reset complete.
260
- `)}}),ap=E({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 m(j(e.refresh),k(e.account));if(I(e)){z({sites:t});return}V([`Property`,`Permission`],t.map(e=>[e.siteUrl,e.permissionLevel??`unknown`]))}}),op=E({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 Tt({product:k(e.product),limit:M(e.limit)});if(I(e)){z({updates:t});return}Ao(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.`})}}),sp={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 cp(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 lp(e,t){let n=cp(e);if(t){z(n);return}B([[`Anonymous telemetry`,e.enabled?`enabled`:`disabled`],[`Reason`,e.reason?sp[e.reason]:`local setting`],[`State file`,e.stateFile],[`Install cohort`,e.state?.cohort??`not created`]]),process.stdout.write(`
254
+ `);for(let n of e){process.stdout.write(`# ${n.subject}\n`);for(let e of n.evals){if(process.stdout.write(`\n[${Xf(e.status)}] eval ${e.id} (${e.durationMs}ms)\n`),process.stdout.write(` prompt: ${$f(e.prompt,100)}\n`),e.error&&process.stdout.write(` error: ${e.error}\n`),!t.judged){process.stdout.write(` reply: ${$f(e.reply,200)}\n`),process.stdout.write(` assertions to review:
255
+ `);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=Kf(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=Qf(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 Qf(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 $f(e,t){let n=e.replace(/\s+/g,` `).trim();return n.length>t?`${n.slice(0,t-1)}…`:n}const ep=T({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:jf,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:Fa(`Judge assertions with an agent.`,`Skip judging and print replies with an assertion checklist.`),skill:Fa(`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=F(e);if(e.list===!0){Yf(t);return}let n=Jf(qf(e)),r=j(e.id);if(r!==void 0&&n.length!==1)throw new C(`INVALID_INPUT`,`--id needs exactly one subject. Name the subject to filter.`);let i=j(e.timeout)??300;if(!(i>0))throw new C(`INVALID_INPUT`,`--timeout must be a positive number of seconds.`);let a=Math.max(1,Math.min(4,j(e.concurrency)??1)),o=O(e.agent)??jf,s=e.judge===!1?null:O(e[`judge-agent`])??o,c=e.skill===!1?null:Pf(),l={agent:o,judge:s,skillBody:c,timeoutMs:i*1e3},u=[];for(let e of n){let n=Nf(e.subject,e.path).evals;if(r!==void 0&&(n=n.filter(e=>e.id===r),n.length===0))throw new C(`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 Gf(n,a,e=>Uf(e,l)),o=Kf(i);u.push({subject:e.subject,...o,evals:i})}let d=Qf(u),f=d.failed>0||d.errored>0;t?R({schemaVersion:1,agent:o,judge:s,judged:s!==null,skillContext:c!==null,timeoutSeconds:i,concurrency:a,subjects:u,totals:d}):Zf(u,{judged:s!==null,agent:o,judge:s}),f&&(process.exitCode=1)}});function tp(){let e=Df();return Kr(e,{withFileTypes:!0}).filter(t=>t.isDirectory()&&Ur(w(e,t.name,`SKILL.md`))).map(t=>({name:t.name,description:Af(w(e,t.name)),path:w(e,t.name)})).sort((e,t)=>e.name<t.name?-1:+(e.name>t.name))}function np(e){let t=tp();if(!e)return t;let n=t.find(t=>t.name===e);if(!n)throw new C(`INVALID_INPUT`,`Unknown skill: ${e}.`);return[n]}const rp=T({meta:{name:`skill`,description:`Inspect the packaged SEO skill and run its evals`},subCommands:{list:T({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=tp();if(F(e)){R({skills:t});return}Do(`SEO skill`,`Packaged with this CLI.`),process.stdout.write(`
256
+ `);for(let e of t)z([[`Name`,e.name],[`Purpose`,`Helps agents choose and run the right SEO audit or report.`],[`Path`,e.path]]);process.stdout.write(`
257
+ `),Eo({title:`Add it to an agent`,body:`Run setup to install the skill and connect your first site.`,command:`seo start`})}}),path:T({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=O(e.name),n=t?np(t)[0]?.path:Df();F(e)?R({name:t,path:n}):process.stdout.write(`${n}\n`)}}),install:T({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=F(e),n=tc({quiet:t});if(t){R(n),n.status===`failed`&&(process.exitCode=1);return}if(n.status===`failed`)throw new C(`INTERNAL_ERROR`,`Skill install failed: ${n.error}`);process.stdout.write(`SEO skill installed. Agents that support skills can discover it now.
258
+ `)}}),eval:ep}}),ip=T({meta:{name:`privacy`,description:`Show local config, token, cache, and log storage paths`},run:async()=>{let e=_t(),t=It();z(e.map(e=>[e.label,`${e.path} · ${Fo(e.sizeBytes)} · ${e.mode}`])),process.stdout.write(`
259
+ `),z(Object.entries(t.counts).map(([e,t])=>[e,String(t)]))}}),ap=T({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=F(e);if(!e.yes&&!L({json:t}))throw new C(`INVALID_INPUT`,`Cannot prompt here. Pass --yes to confirm reset.`);if(!(e.yes||V(await Ei({message:`Delete saved credentials and all local data?`,initialValue:!1})))){Ti(`Reset aborted.`);return}await $e(),t?R({reset:!0}):process.stdout.write(`Reset complete.
260
+ `)}}),op=T({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 m(A(e.refresh),O(e.account));if(F(e)){R({sites:t});return}B([`Property`,`Permission`],t.map(e=>[e.siteUrl,e.permissionLevel??`unknown`]))}}),sp=T({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 Et({product:O(e.product),limit:j(e.limit)});if(F(e)){R({updates:t});return}jo(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.`})}}),cp={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 lp(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 up(e,t){let n=lp(e);if(t){R(n);return}z([[`Anonymous telemetry`,e.enabled?`enabled`:`disabled`],[`Reason`,e.reason?cp[e.reason]:`local setting`],[`State file`,e.stateFile],[`Install cohort`,e.state?.cohort??`not created`]]),process.stdout.write(`
261
261
  Only tool usage is sent. URLs, report data, Google data, and identifiers are never sent.
262
262
  `),process.stdout.write(`Details: https://seoskill.dev/telemetry
263
- `)}const up={type:`boolean`,default:!1,description:`Print machine-readable JSON.`},dp=E({meta:{name:`telemetry`,description:`Check or change anonymous usage telemetry`},subCommands:{status:E({meta:{name:`status`,description:`Show whether anonymous usage telemetry is enabled`},args:{json:up},run:async({args:e})=>lp(Kt(),I(e))}),enable:E({meta:{name:`enable`,description:`Enable anonymous usage telemetry in local settings`},args:{json:up},run:async({args:e})=>{lp(An(!0),I(e))}}),disable:E({meta:{name:`disable`,description:`Disable anonymous usage telemetry in local settings`},args:{json:up},run:async({args:e})=>{lp(An(!1),I(e))}})}}),fp=E({meta:{name:`crawl-queue`,description:`Run a crawl and return a ranked implementation queue`},args:{crawlUrl:{type:`positional`,required:!1,description:`Start URL to crawl.`},url:{type:`string`,description:`Start URL to crawl. Optional when --project has a saved crawl URL.`},site:{type:`string`,description:`GSC property URL for joining page metrics.`},client:{type:`string`,description:`Legacy alias for --project.`},project:{type:`string`,description:`Saved project id or name.`},...$c,mode:{type:`string`,description:`Crawl mode: site, page, list, or sitemap. Defaults to site.`},urls:{type:`string`,description:`Comma-separated URLs for list mode.`},"urls-file":{type:`string`,description:`File with one URL per line for list mode.`},"max-pages":{type:`string`,description:`Maximum pages to crawl. Defaults to 500.`},"max-depth":{type:`string`,description:`Maximum click depth. Defaults to 16.`},concurrency:{type:`string`,description:`Parallel page fetches. Defaults to 8.`},"fetch-interval-cap":{type:`string`,description:`Maximum page fetches per interval per host.`},"fetch-interval-ms":{type:`string`,description:`Fetch rate interval in milliseconds.`},refresh:{type:`boolean`,default:!1,description:`Bypass local HTTP cache and fetch fresh pages.`},include:{type:`string`,description:`Comma-separated URL patterns to include.`},exclude:{type:`string`,description:`Comma-separated URL patterns to exclude.`},limit:{type:`string`,description:`Maximum queue items to print. Defaults to 25.`},"no-sitemap":{type:`boolean`,default:!1,description:`Do not seed URLs from sitemap.xml.`},"no-robots":{type:`boolean`,default:!1,description:`Do not skip URLs disallowed by robots.txt.`},"no-external":{type:`boolean`,default:!1,description:`Do not check external links.`},js:{type:`boolean`,default:!1,description:`Force JavaScript rendering when Playwright is installed.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=I(e),n=A(e),r=await hp(e),i=mp(e),a=k(e.site)||n||!i&&!r.length?await U({client:n,site:k(e.site),options:{json:t}}):void 0,o=i??a?.client?.startUrl??(a?.site?bs(a.site):void 0)??r[0];if(!o)throw Error(`No crawl URL selected. Pass a URL, --url, or use --project with a saved crawl URL.`);let s=await Nn({url:o,projectId:a?.client?.id,site:a?.site,mode:pp(e.mode)??(r.length?`list`:void 0),urls:r,maxPages:M(e[`max-pages`]),maxDepth:M(e[`max-depth`]),concurrency:M(e.concurrency),refresh:j(e.refresh),fetchRate:za({...e,"fetch-concurrency":e.concurrency}),include:P(e.include),exclude:P(e.exclude),googleAnalyticsPropertyId:k(e[`google-analytics-property`]),analyticsConnection:el(e,Jn(a?.client)),useSitemap:!Ia(e,`sitemap`),respectRobots:!Ia(e,`robots`),checkExternal:!Ia(e,`external`),js:!!j(e.js),limit:M(e.limit)});if(t){z(s);return}zu(s),V([`Rank`,`Score`,`Rule`,`Severity`,`Affected`,`Target`,`Action`],s.output.queue.map((e,t)=>[t+1,e.score,e.ruleId,e.severity,e.affectedUrls,e.target,e.action])),Y(`Implementation queue details`,s.output.queue.map(e=>({label:`${e.ruleId} (${e.affectedUrls} URLs)`,context:`${e.severity}, score ${e.score}`,action:`${e.action} Verify with: ${e.verification.command}`})))}});function pp(e){let t=k(e);if(t){if([`site`,`page`,`list`,`sitemap`].includes(t))return t;throw Error(`Mode must be one of: site, page, list, sitemap.`)}}function mp(e){let t=k(e.crawlUrl),n=k(e.url);if(t&&n&&t!==n)throw Error(`Use either a URL argument or --url, not both.`);return t??n}async function hp(e){let t=P(e.urls)??[],n=k(e[`urls-file`]);return n&&t.push(...gp(await ui(n,`utf8`))),[...new Set(t)]}function gp(e){return e.split(/[\n,]/).map(e=>e.trim()).filter(e=>e&&!e.startsWith(`#`))}function _p(e,t){let{narrative:n}=e.output,{diagnosis:r}=n;return{...e,...t?{workflow:t}:{},output:{narrative:{site:n.site,generatedAt:n.generatedAt,dataStatus:n.dataStatus,periodDays:n.periodDays,period:n.period,headline:n.headline,caveats:n.caveats,sections:n.sections,priorities:n.priorities,diagnosis:{site:r.site,generatedAt:r.generatedAt,dataStatus:r.dataStatus,summary:r.summary,skippedSections:r.skippedSections,partialReasons:r.partialReasons,priorities:r.priorities}}}}}function vp(e){return/^[A-Za-z0-9_./:@-]+$/.test(e)?e:`'${e.replaceAll(`'`,`'\\''`)}'`}function yp(e){try{return new URL(e).origin}catch{throw new w(`INVALID_INPUT`,`Pass a valid absolute URL with --url.`)}}function bp(e){let t=e.topRows??20,{evidence:n}=e;return{source:n.source,exportedAt:n.exportedAt,importedAt:n.importedAt,files:n.files,queries:{totalRows:n.queries.totalRows,capped:n.queries.capped,topRowsShown:Math.min(t,n.queries.rows.length),rows:n.queries.rows.slice(0,t)},pages:{totalRows:n.pages.totalRows,capped:n.pages.capped,topRowsShown:Math.min(t,n.pages.rows.length),rows:n.pages.rows.slice(0,t)},...e.reconciliation?{reconciliation:e.reconciliation}:{},...e.pageInventory?{pageInventory:e.pageInventory}:{},warnings:n.warnings,caveats:n.caveats}}function xp(e){if(!e.rows.length)return;let t=e.rows.slice(0,15);process.stdout.write(`\nContent inventory${e.pageCount>1?` page ${e.page} of ${e.pageCount}`:``} (${e.rows.length} exported page${e.rows.length===1?``:`s`} with suggested dispositions)\n`),V([`Path`,`Clicks`,`Impressions`,`Position`,`Suggested`],t.map(e=>[K(e.path,48),e.clicks,e.impressions,e.position??`-`,e.suggestedDisposition])),e.rows.length>t.length&&process.stdout.write(`Showing ${t.length} of ${e.rows.length} tiered rows.\n`),e.pageCount>1&&process.stdout.write(`Showing inventory page ${e.page} of ${e.pageCount}; ${e.totalPages} retained rows are available.\n`),process.stdout.write(`${e.note}\n`)}function Sp(e){let t=e.evidence.queries.totalRows,n=e.evidence.pages.totalRows,r=`Loaded a local Search Console export (${t} query row${t===1?``:`s`}, ${n} page row${n===1?``:`s`}).`;if(!e.reconciliation)return r;let i=e.reconciliation.unreachedCount;return i===0?`${r} Every exported page URL was reached by the crawl.`:`${r} ${i} exported page URL${i===1?``:`s`} with impressions were not reached by this crawl.`}function Cp(e){let t=e.crawledUrls;return`Found ${e.highIssues} high, ${e.mediumIssues} medium, and ${e.lowIssues} low technical issues across ${t} crawled ${t===1?`page`:`pages`}.`}function wp(e){return vr(e,{limit:e.issues.length})}function Tp(e){return jt(e,{limit:e.issues.length})}function Ep(e){if(!e)return[];let t=e=>{let t=e.window?` in the last ${e.window.days} days`:``,n=`for ${e.joinedPages} of ${e.totalPages} crawled URLs`;return e.status===`skipped`?`${e.label}: not connected.`:e.status===`unavailable`?`${e.label}: unavailable. ${e.warning??`No data was joined.`}`:e.status===`partial`?`${e.label}: partial data ${n}${t}. ${e.warning??``}`.trim():e.status===`none`?`${e.label}: no matching crawled URLs${t}.`:`${e.label}: joined ${n}${t}.`};return[t({label:`Search Console`,status:e.searchConsole.status,joinedPages:Math.max(e.searchConsole.joinedMetricPages,e.searchConsole.joinedQueryPages),totalPages:e.searchConsole.totalPages,window:e.searchConsole.window,warning:e.searchConsole.warning}),t({label:`Google Analytics`,status:e.analytics.status,joinedPages:e.analytics.joinedPages,totalPages:e.analytics.totalPages,window:e.analytics.window,warning:e.analytics.warning})]}function Dp(e,t){if(e===t)return 0;let n=[...e],r=[...t],i=Math.min(n.length,r.length);for(let e=0;e<i;e+=1){let t=(n[e]?.codePointAt(0)??0)-(r[e]?.codePointAt(0)??0);if(t!==0)return t}return n.length-r.length}function Op(e){return e.toLowerCase().replace(/[^\p{L}\p{N}]+/gu,` `).trim()}function kp(e){try{let t=new URL(e),n=t.pathname.length>1&&t.pathname.endsWith(`/`)?t.pathname.slice(0,-1):t.pathname;return`${t.origin}${n}${t.search}`}catch{return e.length>1&&e.endsWith(`/`)?e.slice(0,-1):e}}function Ap(e,t=[]){return[...e.map(e=>({url:e.finalUrl||e.url,title:e.title})),...t]}function jp(e){try{let t=new URL(e);return`${t.pathname}${t.search}`||`/`}catch{return e}}function Mp(e){let t;try{t=new URL(e)}catch{return null}return`${t.pathname!==`/`&&t.pathname.endsWith(`/`)?t.pathname.slice(0,-1):t.pathname}${t.search}`}const Np=[`review: shares a search phrase with another page title; confirm query-to-page evidence and intent before consolidating`,`keep: has clicks in the export window`,`review: has impressions but no crawl path reached it`,`update: has impressions with a weak average position (average position above 20 in the export)`,`review: low observed demand in the export window`];function Pp(e){let t=e.maxRows??50,n=e.page??1;if(!Number.isSafeInteger(t)||t<1)throw new w(`INVALID_INPUT`,`Inventory page size must be positive.`);if(!Number.isSafeInteger(n)||n<1)throw new w(`INVALID_INPUT`,`Inventory page must be positive.`);let r=new Set;for(let t of e.reconciliation?.unreachedPages??[]){let e=Mp(t.url);e!==null&&r.add(e)}let i=new Map,a=[...e.crawledPages].sort((e,t)=>Dp(e.url,t.url));for(let e of a){if(typeof e.title!=`string`)continue;let t=Mp(e.url);t!==null&&(i.has(t)||i.set(t,e.title))}let o=new Map;for(let t of e.overlap?.candidates??[])for(let e of t.pages){let n=Mp(e.url);n!==null&&(o.has(n)||o.set(n,t.query))}let s=[...e.pages].sort((e,t)=>t.impressions-e.impressions||t.clicks-e.clicks||Dp(e.url,t.url)),c=Math.ceil(s.length/t);if(c>0&&n>c)throw new w(`INVALID_INPUT`,`Inventory page ${n} does not exist; choose a page from 1 to ${c}.`);let l=(n-1)*t,u=s.slice(l,l+t).map(t=>{let n=Mp(t.url),a=n===null||!e.reconciliation?null:r.has(n)?!1:!e.reconciliation.capped||null,s=n===null?null:i.get(n)??null,c=n===null?null:o.get(n)??null,l,u;return c===null?t.clicks>0?(l=`keep`,u=`has clicks in the export window`):t.impressions>0&&a===!1?(l=`review`,u=`has impressions but no crawl path reached it`):t.impressions>0&&t.position!==null&&t.position>20?(l=`update`,u=`has impressions with a weak average position`):(l=`review`,u=`low observed demand in the export window`):(l=`review`,u=`shares a search phrase with another page title and needs query-to-page and intent verification`),{url:t.url,path:jp(t.url),clicks:t.clicks,impressions:t.impressions,position:t.position,reachedByCrawl:a,title:s,overlapQuery:c,suggestedDisposition:l,tier:u}}),d=c>1,f=[`Suggested dispositions are heuristic tiers from the export numbers and crawl reach. Decide each page with the owner.`];return d&&f.push(`This is inventory page ${n} of ${c}. Fetch every page before treating the returned inventory as complete.`),e.sourceCapped===!0&&f.push(`The source import was capped, so rows missing from this inventory are unknown.`),{rows:u,totalPages:e.pages.length,capped:e.sourceCapped===!0||d,sourceCapped:e.sourceCapped===!0,page:n,pageSize:t,pageCount:c,nextPage:n<c?n+1:null,criteria:[...Np],note:f.join(` `)}}function Fp(e){let t=e.maxQueries??50,n=e.maxCandidates??10,r=new Map;for(let t of e.queries){let e=Op(t.query);if(e.split(` `).length<2)continue;let n=r.get(e);n?(n.impressions+=t.impressions,n.clicks+=t.clicks):r.set(e,{impressions:t.impressions,clicks:t.clicks})}let i=r.size,a=[...r.entries()].map(([e,t])=>({query:e,...t})).sort((e,t)=>t.impressions-e.impressions||t.clicks-e.clicks||Dp(e.query,t.query)).slice(0,t),o=new Map,s=[...e.pages].sort((e,t)=>Dp(e.url,t.url));for(let e of s){if(typeof e.title!=`string`)continue;let t=Op(e.title);if(!t)continue;let n=kp(e.url);o.has(n)||o.set(n,{url:e.url,title:e.title,normalizedTitle:t})}let c=[...o.values()],l=[];for(let e of a){let t=` ${e.query} `,n=c.filter(e=>` ${e.normalizedTitle} `.includes(t));n.length<2||l.push({query:e.query,impressions:e.impressions,clicks:e.clicks,pages:n.map(e=>({url:e.url,title:e.title})).sort((e,t)=>Dp(e.url,t.url))})}l.sort((e,t)=>t.impressions-e.impressions||t.clicks-e.clicks||Dp(e.query,t.query));let u=i>a.length,d=l.length>n;return{candidates:l.slice(0,n),eligibleQueries:i,consideredQueries:a.length,queryLimitReached:u,candidateLimitReached:d,capped:u||d}}const Ip={high:0,medium:1,low:2};function Lp(e,t){let n=Ip[e.fix.severity]-Ip[t.fix.severity];return n===0?t.fix.count===e.fix.count?t.fix.score===e.fix.score?e.fix.ruleId<t.fix.ruleId?-1:+(e.fix.ruleId>t.fix.ruleId):t.fix.score-e.fix.score:t.fix.count-e.fix.count:n}function Rp(e){let t=e.unreachedAudit,n=[...e.topFixes.map(e=>({fix:e,prefix:``,source:`crawl`})),...(t?.topFixes??[]).map(e=>({fix:e,prefix:`${t?.titlePrefix} `,source:`unreached`}))].sort(Lp),r=[...e.reviewObservations.map(e=>({fix:e,prefix:``,source:`crawl`})),...(t?.reviewObservations??[]).map(e=>({fix:e,prefix:`${t?.titlePrefix} `,source:`unreached`}))].sort(Lp),i=[];for(let{fix:t,prefix:r,source:a}of n){let n=a===`crawl`?e.crawlReportId:e.unreachedAudit?.reportId;i.push({id:`${a}:${t.ruleId}`,kind:`fix`,title:`${r}${t.ruleId}: ${t.title} (${t.count} URL${t.count===1?``:`s`})`,action:t.howToFix,confidence:`high`,severity:t.severity,affectedCount:t.count,sampleUrls:t.sampleUrls,...n?{affectedUrlsReport:{id:`affected-urls`,params:{reportId:n,ruleId:t.ruleId}}}:{},evidence:{ruleId:t.ruleId,source:a,whyThisRanks:t.whyThisRanks},verification:t.verification})}let a=e.reconciliation;if(a&&a.unreachedCount>0){let e=a.unreachedPages.reduce((e,t)=>e+t.impressions,0);i.push({id:`export:unreached-pages`,kind:`review`,title:`${a.unreachedCount} exported page URL${a.unreachedCount===1?``:`s`} with search impressions were not reached by this crawl`,action:`These pages carry ${e} combined impressions across the ${a.unreachedPages.length} retained export rows, but no crawl path reached them. Add internal links or sitemap entries when they should be reachable. The join used URL paths from a local export; treat it as a discovery lead within this crawl bound, not proof of orphan status.`,confidence:`medium`,affectedCount:a.unreachedCount,evidence:{retainedRows:a.unreachedPages.length,impressions:e},review:{question:`Should each retained exported page be reachable from the current site?`,changeOnlyIf:`Add a crawl path only for a page that should remain part of the current site and has no intended exclusion.`,ifNotNeeded:`Record no change for a page that is intentionally retired, excluded, or awaiting an owner decision.`,doNot:[`Do not call a page orphaned from this path-only join.`,`Do not redirect unrelated pages to one generic destination.`]},verification:{expected:`Every retained exported page is reachable in the rerun or explicitly accounted for as deferred or no-change with owner intent.`}})}let o=e.topicOverlap,s=o?.candidates[0];if(o&&s){let e=o.candidates.length,t=s.pages.map(e=>jp(e.url));i.push({id:`export:topic-overlap`,kind:`review`,title:`topic overlap candidates: ${e} exported ${e===1?`query appears`:`queries appear`} in 2+ page titles`,action:`Top candidate: "${s.query}" (${s.impressions} impressions, ${s.clicks} clicks) appears in the titles of ${t.join(` and `)}. This is a title-based heuristic built from the export's separate query and page tables. The export contains no query-to-page mapping, so it cannot show which pages Google served for the query. Verify with the cannibalisation report (seo cannibal) after connecting a Search Console property.`,confidence:`medium`,affectedCount:e,evidence:{topQuery:s.query,topPages:s.pages,impressions:s.impressions,clicks:s.clicks},review:{question:`Does query-to-page evidence confirm that these pages compete for the same search intent?`,changeOnlyIf:`Consolidate or redirect only after query-to-page evidence and page intent confirm real overlap.`,ifNotNeeded:`Keep the pages separate and record why their intents differ.`,doNot:[`Do not merge or redirect pages from a title-match heuristic alone.`]},verification:{expected:`Confirm intent with query-to-page evidence before consolidating or redirecting either page.`}})}let c=e.pageInventory,l=c?.rows[0];if(c&&l){let e={keep:0,update:0,review:0};for(let t of c.rows)e[t.suggestedDisposition]+=1;let t=[...c.pageCount>1?[`page ${c.page} of ${c.pageCount}; ${c.rows.length} of ${c.totalPages} retained rows`]:[],...c.sourceCapped?[`source import capped`]:[]],n=t.length?` (${t.join(`; `)})`:``;i.push({id:`export:content-inventory`,kind:`review`,title:`content inventory: ${c.rows.length} exported page${c.rows.length===1?``:`s`} tiered with suggested dispositions`,action:`Suggested tiers: keep ${e.keep}, update ${e.update}, review ${e.review}${n}. Example: ${l.path} (${l.clicks} clicks, ${l.impressions} impressions, position ${l.position??`not in the export`}) is tiered ${l.suggestedDisposition} because it ${l.tier}. ${c.note} Work the inventory page by page instead of applying one blanket policy.`,confidence:`medium`,affectedCount:c.rows.length,evidence:{dispositions:e,capped:c.capped},review:{question:`Has every returned inventory row received an evidence-backed disposition?`,changeOnlyIf:`Apply a content change, removal, or redirect only after deciding that row from its own evidence and product relevance.`,ifConfirmed:`Record one disposition and reason for every returned inventory row.`,ifNotNeeded:`Keep a page unchanged when its evidence and current product role support keeping it.`,doNot:[`Do not use one blanket disposition for the inventory.`,`Do not redirect unrelated URLs to a generic hub.`]},verification:{expected:`Every returned inventory row has an owner-confirmed keep, update, or review decision; every inventory page has been fetched; redirected rows resolve to the intended destination.`}})}for(let{fix:t,prefix:n,source:a}of r){let r=a===`crawl`?e.crawlReportId:e.unreachedAudit?.reportId;i.push({id:`${a}:${t.ruleId}`,kind:`review`,title:`${n}${t.ruleId}: ${t.title} (${t.count} URL${t.count===1?``:`s`})`,action:`Observed state that needs intent confirmation before treating it as a defect. ${t.howToFix}`,confidence:`medium`,severity:t.severity,affectedCount:t.count,sampleUrls:t.sampleUrls,...r?{affectedUrlsReport:{id:`affected-urls`,params:{reportId:r,ruleId:t.ruleId}}}:{},evidence:{ruleId:t.ruleId,source:a,whyThisRanks:t.whyThisRanks},verification:t.verification})}return i}function zp(e,t){let n=[];for(let r of e.unreachedPages.slice(0,t))try{let t=new URL(r.url);n.push(`${e.crawlOrigin}${t.pathname}${t.search}`)}catch{}return n}function Bp(e,t,n){e.some(e=>e.command===t)||e.push({command:t,why:n})}function Vp(e){let t=e.report;return t?{status:e.status,...e.reason?{reason:e.reason}:{},reportId:t.id,generatedAt:t.generatedAt,crawlStatus:t.status,capped:t.summary.pageLimitReached,maxPages:t.config.maxPages,searchDataJoined:(t.dataSources?.searchConsole.joinedMetricPages??0)>0||(t.dataSources?.searchConsole.joinedQueryPages??0)>0,dataSources:t.dataSources,summary:t.summary,topFixes:wp(t),reviewObservations:Tp(t),issueGroupsComplete:!0}:{status:e.status,...e.reason?{reason:e.reason}:{}}}function Hp(e){return{ruleId:e.ruleId,title:e.title,category:e.category,severity:e.severity,recommendation:e.recommendation,count:e.count,sampleUrls:e.sampleUrls.slice(0,3),score:e.score,scoreFactors:e.scoreFactors,whyThisRanks:e.whyThisRanks,detailsCommand:`seo explain --rule ${e.ruleId}`}}function Up(e){return`topFixes`in e?{...e,topFixes:e.topFixes.map(Hp),reviewObservations:e.reviewObservations.map(Hp)}:e}function Wp(e,t={}){if(!(`topFixes`in e)){process.stdout.write(`\nTechnical crawl evidence: ${e.reason??e.status}\n`);return}let n=e.status===`reused`?`saved`:`new`;if(process.stdout.write(`\nTechnical crawl evidence (${n})\n`),e.reason&&process.stdout.write(`${e.reason}\n`),e.capped&&process.stdout.write(`Coverage is capped at ${e.maxPages} pages. Run \`seo crawl\` for a broader investigation.\n`),!t.providerFree)for(let t of Ep(e.dataSources))process.stdout.write(`${t}\n`);if(!e.topFixes.length){process.stdout.write(`No prioritised technical fixes were found.
263
+ `)}const dp={type:`boolean`,default:!1,description:`Print machine-readable JSON.`},fp=T({meta:{name:`telemetry`,description:`Check or change anonymous usage telemetry`},subCommands:{status:T({meta:{name:`status`,description:`Show whether anonymous usage telemetry is enabled`},args:{json:dp},run:async({args:e})=>up(qt(),F(e))}),enable:T({meta:{name:`enable`,description:`Enable anonymous usage telemetry in local settings`},args:{json:dp},run:async({args:e})=>{up(jn(!0),F(e))}}),disable:T({meta:{name:`disable`,description:`Disable anonymous usage telemetry in local settings`},args:{json:dp},run:async({args:e})=>{up(jn(!1),F(e))}})}}),pp=T({meta:{name:`crawl-queue`,description:`Run a crawl and return a ranked implementation queue`},args:{crawlUrl:{type:`positional`,required:!1,description:`Start URL to crawl.`},url:{type:`string`,description:`Start URL to crawl. Optional when --project has a saved crawl URL.`},site:{type:`string`,description:`GSC property URL for joining page metrics.`},client:{type:`string`,description:`Legacy alias for --project.`},project:{type:`string`,description:`Saved project id or name.`},...el,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=F(e),n=k(e),r=await gp(e),i=hp(e),a=O(e.site)||n||!i&&!r.length?await H({client:n,site:O(e.site),options:{json:t}}):void 0,o=i??a?.client?.startUrl??(a?.site?xs(a.site):void 0)??r[0];if(!o)throw Error(`No crawl URL selected. Pass a URL, --url, or use --project with a saved crawl URL.`);let s=await Pn({url:o,projectId:a?.client?.id,site:a?.site,mode:mp(e.mode)??(r.length?`list`:void 0),urls:r,maxPages:j(e[`max-pages`]),maxDepth:j(e[`max-depth`]),concurrency:j(e.concurrency),refresh:A(e.refresh),fetchRate:Ba({...e,"fetch-concurrency":e.concurrency}),include:N(e.include),exclude:N(e.exclude),googleAnalyticsPropertyId:O(e[`google-analytics-property`]),analyticsConnection:tl(e,Yn(a?.client)),useSitemap:!La(e,`sitemap`),respectRobots:!La(e,`robots`),checkExternal:!La(e,`external`),js:!!A(e.js),limit:j(e.limit)});if(t){R(s);return}Bu(s),B([`Rank`,`Score`,`Rule`,`Severity`,`Affected`,`Target`,`Action`],s.output.queue.map((e,t)=>[t+1,e.score,e.ruleId,e.severity,e.affectedUrls,e.target,e.action])),J(`Implementation queue details`,s.output.queue.map(e=>({label:`${e.ruleId} (${e.affectedUrls} URLs)`,context:`${e.severity}, score ${e.score}`,action:`${e.action} Verify with: ${e.verification.command}`})))}});function mp(e){let t=O(e);if(t){if([`site`,`page`,`list`,`sitemap`].includes(t))return t;throw Error(`Mode must be one of: site, page, list, sitemap.`)}}function hp(e){let t=O(e.crawlUrl),n=O(e.url);if(t&&n&&t!==n)throw Error(`Use either a URL argument or --url, not both.`);return t??n}async function gp(e){let t=N(e.urls)??[],n=O(e[`urls-file`]);return n&&t.push(..._p(await di(n,`utf8`))),[...new Set(t)]}function _p(e){return e.split(/[\n,]/).map(e=>e.trim()).filter(e=>e&&!e.startsWith(`#`))}function vp(e,t){let{narrative:n}=e.output,{diagnosis:r}=n;return{...e,...t?{workflow:t}:{},output:{narrative:{site:n.site,generatedAt:n.generatedAt,dataStatus:n.dataStatus,periodDays:n.periodDays,period:n.period,headline:n.headline,caveats:n.caveats,sections:n.sections,priorities:n.priorities,diagnosis:{site:r.site,generatedAt:r.generatedAt,dataStatus:r.dataStatus,summary:r.summary,skippedSections:r.skippedSections,partialReasons:r.partialReasons,priorities:r.priorities}}}}}function yp(e){return/^[A-Za-z0-9_./:@-]+$/.test(e)?e:`'${e.replaceAll(`'`,`'\\''`)}'`}function bp(e){try{return new URL(e).origin}catch{throw new C(`INVALID_INPUT`,`Pass a valid absolute URL with --url.`)}}function xp(e){let t=e.topRows??20,{evidence:n}=e;return{source:n.source,exportedAt:n.exportedAt,importedAt:n.importedAt,files:n.files,queries:{totalRows:n.queries.totalRows,capped:n.queries.capped,topRowsShown:Math.min(t,n.queries.rows.length),rows:n.queries.rows.slice(0,t)},pages:{totalRows:n.pages.totalRows,capped:n.pages.capped,topRowsShown:Math.min(t,n.pages.rows.length),rows:n.pages.rows.slice(0,t)},...e.reconciliation?{reconciliation:e.reconciliation}:{},...e.pageInventory?{pageInventory:e.pageInventory}:{},warnings:n.warnings,caveats:n.caveats}}function Sp(e){if(!e.rows.length)return;let t=e.rows.slice(0,15);process.stdout.write(`\nContent inventory${e.pageCount>1?` page ${e.page} of ${e.pageCount}`:``} (${e.rows.length} exported page${e.rows.length===1?``:`s`} with suggested dispositions)\n`),B([`Path`,`Clicks`,`Impressions`,`Position`,`Suggested`],t.map(e=>[G(e.path,48),e.clicks,e.impressions,e.position??`-`,e.suggestedDisposition])),e.rows.length>t.length&&process.stdout.write(`Showing ${t.length} of ${e.rows.length} tiered rows.\n`),e.pageCount>1&&process.stdout.write(`Showing inventory page ${e.page} of ${e.pageCount}; ${e.totalPages} retained rows are available.\n`),process.stdout.write(`${e.note}\n`)}function Cp(e){let t=e.evidence.queries.totalRows,n=e.evidence.pages.totalRows,r=`Loaded a local Search Console export (${t} query row${t===1?``:`s`}, ${n} page row${n===1?``:`s`}).`;if(!e.reconciliation)return r;let i=e.reconciliation.unreachedCount;return i===0?`${r} Every exported page URL was reached by the crawl.`:`${r} ${i} exported page URL${i===1?``:`s`} with impressions were not reached by this crawl.`}function wp(e){let t=e.crawledUrls;return`Found ${e.highIssues} high, ${e.mediumIssues} medium, and ${e.lowIssues} low technical issues across ${t} crawled ${t===1?`page`:`pages`}.`}function Tp(e){return yr(e,{limit:e.issues.length})}function Ep(e){return Mt(e,{limit:e.issues.length})}function Dp(e){if(!e)return[];let t=e=>{let t=e.window?` in the last ${e.window.days} days`:``,n=`for ${e.joinedPages} of ${e.totalPages} crawled URLs`;return e.status===`skipped`?`${e.label}: not connected.`:e.status===`unavailable`?`${e.label}: unavailable. ${e.warning??`No data was joined.`}`:e.status===`partial`?`${e.label}: partial data ${n}${t}. ${e.warning??``}`.trim():e.status===`none`?`${e.label}: no matching crawled URLs${t}.`:`${e.label}: joined ${n}${t}.`};return[t({label:`Search Console`,status:e.searchConsole.status,joinedPages:Math.max(e.searchConsole.joinedMetricPages,e.searchConsole.joinedQueryPages),totalPages:e.searchConsole.totalPages,window:e.searchConsole.window,warning:e.searchConsole.warning}),t({label:`Google Analytics`,status:e.analytics.status,joinedPages:e.analytics.joinedPages,totalPages:e.analytics.totalPages,window:e.analytics.window,warning:e.analytics.warning})]}function Op(e,t){if(e===t)return 0;let n=[...e],r=[...t],i=Math.min(n.length,r.length);for(let e=0;e<i;e+=1){let t=(n[e]?.codePointAt(0)??0)-(r[e]?.codePointAt(0)??0);if(t!==0)return t}return n.length-r.length}function kp(e){return e.toLowerCase().replace(/[^\p{L}\p{N}]+/gu,` `).trim()}function Ap(e){try{let t=new URL(e),n=t.pathname.length>1&&t.pathname.endsWith(`/`)?t.pathname.slice(0,-1):t.pathname;return`${t.origin}${n}${t.search}`}catch{return e.length>1&&e.endsWith(`/`)?e.slice(0,-1):e}}function jp(e,t=[]){return[...e.map(e=>({url:e.finalUrl||e.url,title:e.title})),...t]}function Mp(e){try{let t=new URL(e);return`${t.pathname}${t.search}`||`/`}catch{return e}}function Np(e){let t;try{t=new URL(e)}catch{return null}return`${t.pathname!==`/`&&t.pathname.endsWith(`/`)?t.pathname.slice(0,-1):t.pathname}${t.search}`}const Pp=[`review: shares a search phrase with another page title; confirm query-to-page evidence and intent before consolidating`,`keep: has clicks in the export window`,`review: has impressions but no crawl path reached it`,`update: has impressions with a weak average position (average position above 20 in the export)`,`review: low observed demand in the export window`];function Fp(e){let t=e.maxRows??50,n=e.page??1;if(!Number.isSafeInteger(t)||t<1)throw new C(`INVALID_INPUT`,`Inventory page size must be positive.`);if(!Number.isSafeInteger(n)||n<1)throw new C(`INVALID_INPUT`,`Inventory page must be positive.`);let r=new Set;for(let t of e.reconciliation?.unreachedPages??[]){let e=Np(t.url);e!==null&&r.add(e)}let i=new Map,a=[...e.crawledPages].sort((e,t)=>Op(e.url,t.url));for(let e of a){if(typeof e.title!=`string`)continue;let t=Np(e.url);t!==null&&(i.has(t)||i.set(t,e.title))}let o=new Map;for(let t of e.overlap?.candidates??[])for(let e of t.pages){let n=Np(e.url);n!==null&&(o.has(n)||o.set(n,t.query))}let s=[...e.pages].sort((e,t)=>t.impressions-e.impressions||t.clicks-e.clicks||Op(e.url,t.url)),c=Math.ceil(s.length/t);if(c>0&&n>c)throw new C(`INVALID_INPUT`,`Inventory page ${n} does not exist; choose a page from 1 to ${c}.`);let l=(n-1)*t,u=s.slice(l,l+t).map(t=>{let n=Np(t.url),a=n===null||!e.reconciliation?null:r.has(n)?!1:!e.reconciliation.capped||null,s=n===null?null:i.get(n)??null,c=n===null?null:o.get(n)??null,l,u;return c===null?t.clicks>0?(l=`keep`,u=`has clicks in the export window`):t.impressions>0&&a===!1?(l=`review`,u=`has impressions but no crawl path reached it`):t.impressions>0&&t.position!==null&&t.position>20?(l=`update`,u=`has impressions with a weak average position`):(l=`review`,u=`low observed demand in the export window`):(l=`review`,u=`shares a search phrase with another page title and needs query-to-page and intent verification`),{url:t.url,path:Mp(t.url),clicks:t.clicks,impressions:t.impressions,position:t.position,reachedByCrawl:a,title:s,overlapQuery:c,suggestedDisposition:l,tier:u}}),d=c>1,f=[`Suggested dispositions are heuristic tiers from the export numbers and crawl reach. Decide each page with the owner.`];return d&&f.push(`This is inventory page ${n} of ${c}. Fetch every page before treating the returned inventory as complete.`),e.sourceCapped===!0&&f.push(`The source import was capped, so rows missing from this inventory are unknown.`),{rows:u,totalPages:e.pages.length,capped:e.sourceCapped===!0||d,sourceCapped:e.sourceCapped===!0,page:n,pageSize:t,pageCount:c,nextPage:n<c?n+1:null,criteria:[...Pp],note:f.join(` `)}}function Ip(e){let t=e.maxQueries??50,n=e.maxCandidates??10,r=new Map;for(let t of e.queries){let e=kp(t.query);if(e.split(` `).length<2)continue;let n=r.get(e);n?(n.impressions+=t.impressions,n.clicks+=t.clicks):r.set(e,{impressions:t.impressions,clicks:t.clicks})}let i=r.size,a=[...r.entries()].map(([e,t])=>({query:e,...t})).sort((e,t)=>t.impressions-e.impressions||t.clicks-e.clicks||Op(e.query,t.query)).slice(0,t),o=new Map,s=[...e.pages].sort((e,t)=>Op(e.url,t.url));for(let e of s){if(typeof e.title!=`string`)continue;let t=kp(e.title);if(!t)continue;let n=Ap(e.url);o.has(n)||o.set(n,{url:e.url,title:e.title,normalizedTitle:t})}let c=[...o.values()],l=[];for(let e of a){let t=` ${e.query} `,n=c.filter(e=>` ${e.normalizedTitle} `.includes(t));n.length<2||l.push({query:e.query,impressions:e.impressions,clicks:e.clicks,pages:n.map(e=>({url:e.url,title:e.title})).sort((e,t)=>Op(e.url,t.url))})}l.sort((e,t)=>t.impressions-e.impressions||t.clicks-e.clicks||Op(e.query,t.query));let u=i>a.length,d=l.length>n;return{candidates:l.slice(0,n),eligibleQueries:i,consideredQueries:a.length,queryLimitReached:u,candidateLimitReached:d,capped:u||d}}const Lp={high:0,medium:1,low:2};function Rp(e,t){let n=Lp[e.fix.severity]-Lp[t.fix.severity];return n===0?t.fix.count===e.fix.count?t.fix.score===e.fix.score?e.fix.ruleId<t.fix.ruleId?-1:+(e.fix.ruleId>t.fix.ruleId):t.fix.score-e.fix.score:t.fix.count-e.fix.count:n}function zp(e){let t=e.unreachedAudit,n=[...e.topFixes.map(e=>({fix:e,prefix:``,source:`crawl`})),...(t?.topFixes??[]).map(e=>({fix:e,prefix:`${t?.titlePrefix} `,source:`unreached`}))].sort(Rp),r=[...e.reviewObservations.map(e=>({fix:e,prefix:``,source:`crawl`})),...(t?.reviewObservations??[]).map(e=>({fix:e,prefix:`${t?.titlePrefix} `,source:`unreached`}))].sort(Rp),i=[];for(let{fix:t,prefix:r,source:a}of n){let n=a===`crawl`?e.crawlReportId:e.unreachedAudit?.reportId;i.push({id:`${a}:${t.ruleId}`,kind:`fix`,title:`${r}${t.ruleId}: ${t.title} (${t.count} URL${t.count===1?``:`s`})`,action:t.howToFix,confidence:`high`,severity:t.severity,affectedCount:t.count,sampleUrls:t.sampleUrls,...n?{affectedUrlsReport:{id:`affected-urls`,params:{reportId:n,ruleId:t.ruleId}}}:{},evidence:{ruleId:t.ruleId,source:a,whyThisRanks:t.whyThisRanks},verification:t.verification})}let a=e.reconciliation;if(a&&a.unreachedCount>0){let e=a.unreachedPages.reduce((e,t)=>e+t.impressions,0);i.push({id:`export:unreached-pages`,kind:`review`,title:`${a.unreachedCount} exported page URL${a.unreachedCount===1?``:`s`} with search impressions were not reached by this crawl`,action:`These pages carry ${e} combined impressions across the ${a.unreachedPages.length} retained export rows, but no crawl path reached them. Add internal links or sitemap entries when they should be reachable. The join used URL paths from a local export; treat it as a discovery lead within this crawl bound, not proof of orphan status.`,confidence:`medium`,affectedCount:a.unreachedCount,evidence:{retainedRows:a.unreachedPages.length,impressions:e},review:{question:`Should each retained exported page be reachable from the current site?`,changeOnlyIf:`Add a crawl path only for a page that should remain part of the current site and has no intended exclusion.`,ifNotNeeded:`Record no change for a page that is intentionally retired, excluded, or awaiting an owner decision.`,doNot:[`Do not call a page orphaned from this path-only join.`,`Do not redirect unrelated pages to one generic destination.`]},verification:{expected:`Every retained exported page is reachable in the rerun or explicitly accounted for as deferred or no-change with owner intent.`}})}let o=e.topicOverlap,s=o?.candidates[0];if(o&&s){let e=o.candidates.length,t=s.pages.map(e=>Mp(e.url));i.push({id:`export:topic-overlap`,kind:`review`,title:`topic overlap candidates: ${e} exported ${e===1?`query appears`:`queries appear`} in 2+ page titles`,action:`Top candidate: "${s.query}" (${s.impressions} impressions, ${s.clicks} clicks) appears in the titles of ${t.join(` and `)}. This is a title-based heuristic built from the export's separate query and page tables. The export contains no query-to-page mapping, so it cannot show which pages Google served for the query. Verify with the cannibalisation report (seo cannibal) after connecting a Search Console property.`,confidence:`medium`,affectedCount:e,evidence:{topQuery:s.query,topPages:s.pages,impressions:s.impressions,clicks:s.clicks},review:{question:`Does query-to-page evidence confirm that these pages compete for the same search intent?`,changeOnlyIf:`Consolidate or redirect only after query-to-page evidence and page intent confirm real overlap.`,ifNotNeeded:`Keep the pages separate and record why their intents differ.`,doNot:[`Do not merge or redirect pages from a title-match heuristic alone.`]},verification:{expected:`Confirm intent with query-to-page evidence before consolidating or redirecting either page.`}})}let c=e.pageInventory,l=c?.rows[0];if(c&&l){let e={keep:0,update:0,review:0};for(let t of c.rows)e[t.suggestedDisposition]+=1;let t=[...c.pageCount>1?[`page ${c.page} of ${c.pageCount}; ${c.rows.length} of ${c.totalPages} retained rows`]:[],...c.sourceCapped?[`source import capped`]:[]],n=t.length?` (${t.join(`; `)})`:``;i.push({id:`export:content-inventory`,kind:`review`,title:`content inventory: ${c.rows.length} exported page${c.rows.length===1?``:`s`} tiered with suggested dispositions`,action:`Suggested tiers: keep ${e.keep}, update ${e.update}, review ${e.review}${n}. Example: ${l.path} (${l.clicks} clicks, ${l.impressions} impressions, position ${l.position??`not in the export`}) is tiered ${l.suggestedDisposition} because it ${l.tier}. ${c.note} Work the inventory page by page instead of applying one blanket policy.`,confidence:`medium`,affectedCount:c.rows.length,evidence:{dispositions:e,capped:c.capped},review:{question:`Has every returned inventory row received an evidence-backed disposition?`,changeOnlyIf:`Apply a content change, removal, or redirect only after deciding that row from its own evidence and product relevance.`,ifConfirmed:`Record one disposition and reason for every returned inventory row.`,ifNotNeeded:`Keep a page unchanged when its evidence and current product role support keeping it.`,doNot:[`Do not use one blanket disposition for the inventory.`,`Do not redirect unrelated URLs to a generic hub.`]},verification:{expected:`Every returned inventory row has an owner-confirmed keep, update, or review decision; every inventory page has been fetched; redirected rows resolve to the intended destination.`}})}for(let{fix:t,prefix:n,source:a}of r){let r=a===`crawl`?e.crawlReportId:e.unreachedAudit?.reportId;i.push({id:`${a}:${t.ruleId}`,kind:`review`,title:`${n}${t.ruleId}: ${t.title} (${t.count} URL${t.count===1?``:`s`})`,action:`Observed state that needs intent confirmation before treating it as a defect. ${t.howToFix}`,confidence:`medium`,severity:t.severity,affectedCount:t.count,sampleUrls:t.sampleUrls,...r?{affectedUrlsReport:{id:`affected-urls`,params:{reportId:r,ruleId:t.ruleId}}}:{},evidence:{ruleId:t.ruleId,source:a,whyThisRanks:t.whyThisRanks},verification:t.verification})}return i}function Bp(e,t){let n=[];for(let r of e.unreachedPages.slice(0,t))try{let t=new URL(r.url);n.push(`${e.crawlOrigin}${t.pathname}${t.search}`)}catch{}return n}function Q(e,t,n){e.some(e=>e.command===t)||e.push({command:t,why:n})}function Vp(e){let t=e.report;return t?{status:e.status,...e.reason?{reason:e.reason}:{},reportId:t.id,generatedAt:t.generatedAt,crawlStatus:t.status,capped:t.summary.pageLimitReached,maxPages:t.config.maxPages,searchDataJoined:(t.dataSources?.searchConsole.joinedMetricPages??0)>0||(t.dataSources?.searchConsole.joinedQueryPages??0)>0,dataSources:t.dataSources,summary:t.summary,topFixes:Tp(t),reviewObservations:Ep(t),issueGroupsComplete:!0}:{status:e.status,...e.reason?{reason:e.reason}:{}}}function Hp(e){return{ruleId:e.ruleId,title:e.title,category:e.category,severity:e.severity,recommendation:e.recommendation,count:e.count,sampleUrls:e.sampleUrls.slice(0,3),score:e.score,scoreFactors:e.scoreFactors,whyThisRanks:e.whyThisRanks,detailsCommand:`seo explain --rule ${e.ruleId}`}}function Up(e){return`topFixes`in e?{...e,topFixes:e.topFixes.map(Hp),reviewObservations:e.reviewObservations.map(Hp)}:e}function Wp(e,t){return t?[...e,...t]:[...e]}function Gp(e,t={}){if(!(`topFixes`in e)){process.stdout.write(`\nTechnical crawl evidence: ${e.reason??e.status}\n`);return}let n=e.status===`reused`?`saved`:`new`;if(process.stdout.write(`\nTechnical crawl evidence (${n})\n`),e.reason&&process.stdout.write(`${e.reason}\n`),e.capped&&process.stdout.write(`Coverage is capped at ${e.maxPages} pages. Run \`seo crawl\` for a broader investigation.\n`),!t.providerFree)for(let t of Dp(e.dataSources))process.stdout.write(`${t}\n`);if(!e.topFixes.length){process.stdout.write(`No prioritised technical fixes were found.
264
264
  `),e.reviewObservations.length&&process.stdout.write(`${e.reviewObservations.length} review observation${e.reviewObservations.length===1?``:`s`} need confirmation before they become implementation work.\n`);return}process.stdout.write(t.providerFree?`
265
265
  Prioritised technical fixes
266
266
  `:e.searchDataJoined?`
267
267
  Technical fixes with search value
268
268
  `:`
269
269
  Technical fixes (no Search Console data joined)
270
- `),e.searchDataJoined?V([`Score`,`Rule`,`Severity`,`Search value`,`Verify`],e.topFixes.map(e=>[e.score,e.ruleId,e.severity,`${e.scoreFactors.clicks} clicks / ${e.scoreFactors.sessions} visits${e.scoreFactors.conversions===void 0?``:` / ${e.scoreFactors.conversions} conv.`}`,K(e.howToVerify,72)])):V([`#`,`Rule`,`Severity`,`Affected URLs`,`Verify`],e.topFixes.map((e,t)=>[t+1,e.ruleId,e.severity,e.count,K(e.howToVerify,72)])),e.reviewObservations.length&&(t.providerFree?(process.stdout.write(`
270
+ `),e.searchDataJoined?B([`Score`,`Rule`,`Severity`,`Search value`,`Verify`],e.topFixes.map(e=>[e.score,e.ruleId,e.severity,`${e.scoreFactors.clicks} clicks / ${e.scoreFactors.sessions} visits${e.scoreFactors.conversions===void 0?``:` / ${e.scoreFactors.conversions} conv.`}`,G(e.howToVerify,72)])):B([`#`,`Rule`,`Severity`,`Affected URLs`,`Verify`],e.topFixes.map((e,t)=>[t+1,e.ruleId,e.severity,e.count,G(e.howToVerify,72)])),e.reviewObservations.length&&(t.providerFree?(process.stdout.write(`
271
271
  Review observations (confirm intent before treating as defects)
272
- `),V([`Rule`,`Severity`,`Affected URLs`,`Sample`],e.reviewObservations.map(e=>[e.ruleId,e.severity,e.count,K(e.sampleUrls[0]??``,60)]))):process.stdout.write(`\n${e.reviewObservations.length} review observation${e.reviewObservations.length===1?``:`s`} were kept out of this action queue.\n`))}function Gp(e){process.stdout.write(`
272
+ `),B([`Rule`,`Severity`,`Affected URLs`,`Sample`],e.reviewObservations.map(e=>[e.ruleId,e.severity,e.count,G(e.sampleUrls[0]??``,60)]))):process.stdout.write(`\n${e.reviewObservations.length} review observation${e.reviewObservations.length===1?``:`s`} were kept out of this action queue.\n`))}function Kp(e){process.stdout.write(`
273
273
  Search Console export (local file evidence)
274
- `),process.stdout.write(`${Sp(e)}\n`);for(let t of e.evidence.warnings)process.stdout.write(`Warning: ${t}\n`);let t=e.reconciliation?.unreachedPages??[];if(t.length&&(process.stdout.write(`
274
+ `),process.stdout.write(`${Cp(e)}\n`);for(let t of e.evidence.warnings)process.stdout.write(`Warning: ${t}\n`);let t=e.reconciliation?.unreachedPages??[];if(t.length&&(process.stdout.write(`
275
275
  Exported page URLs not reached by this crawl (by impressions)
276
- `),V([`URL`,`Clicks`,`Impressions`,`Position`],t.slice(0,10).map(e=>[K(e.url,64),e.clicks,e.impressions,e.position??`-`]))),e.unreachedAudit){let t=e.unreachedAudit;process.stdout.write(`\nAudited ${t.auditedUrls} of the ${t.totalUnreached} unreached URLs in this run\n`);let n=[...t.topFixes,...t.reviewObservations];n.length&&V([`Rule`,`Severity`,`Affected URLs`,`Sample`],n.map(e=>[e.ruleId,e.severity,e.count,K(e.sampleUrls[0]??``,60)]))}e.topicOverlap?.candidates.length&&(process.stdout.write(`
276
+ `),B([`URL`,`Clicks`,`Impressions`,`Position`],t.slice(0,10).map(e=>[G(e.url,64),e.clicks,e.impressions,e.position??`-`]))),e.unreachedAudit){let t=e.unreachedAudit;process.stdout.write(`\nAudited ${t.auditedUrls} of the ${t.totalUnreached} unreached URLs in this run\n`);let n=[...t.topFixes,...t.reviewObservations];n.length&&B([`Rule`,`Severity`,`Affected URLs`,`Sample`],n.map(e=>[e.ruleId,e.severity,e.count,G(e.sampleUrls[0]??``,60)]))}e.topicOverlap?.candidates.length&&(process.stdout.write(`
277
277
  Topic overlap candidates (title-based heuristic; the export has no query-to-page mapping)
278
- `),V([`Query`,`Impressions`,`Pages`],e.topicOverlap.candidates.map(e=>[K(e.query,40),e.impressions,K(e.pages.map(e=>jp(e.url)).join(`, `),64)]))),e.pageInventory&&xp(e.pageInventory);for(let t of e.evidence.caveats)process.stdout.write(`Note: ${t}\n`)}function Kp(e){process.stdout.write(`
278
+ `),B([`Query`,`Impressions`,`Pages`],e.topicOverlap.candidates.map(e=>[G(e.query,40),e.impressions,G(e.pages.map(e=>Mp(e.url)).join(`, `),64)]))),e.pageInventory&&Sp(e.pageInventory);for(let t of e.evidence.caveats)process.stdout.write(`Note: ${t}\n`)}function qp(e){process.stdout.write(`
279
279
  Provider-backed sections skipped
280
- `);let t=`dataSources`in e?Ep(e.dataSources):[],n=t.length?t:[`Search Console: not connected.`,`Google Analytics: not connected.`];for(let e of n)process.stdout.write(`${e}\n`);process.stdout.write("Run `seo start` when you want traffic, query, ranking, and analytics evidence added to the report.\n")}function qp(e){let t=[],n=e.technicalCrawl;return n&&(`topFixes`in n?t.push({title:`Technical crawl evidence`,summary:`${n.summary.crawledUrls} pages were included in the ${n.status===`reused`?`saved`:`new`} crawl evidence.`,items:[...n.topFixes.map(e=>`${e.ruleId}: ${e.title}. ${e.count} affected URLs. ${e.recommendation}`),...Ep(n.dataSources)]}):t.push({title:`Technical crawl evidence`,summary:n.reason??`Technical evidence was ${n.status}.`,items:[]})),e.followups?.length&&t.push({title:`Recommended next commands`,items:e.followups.map(e=>`${e.command}: ${e.why}`)}),t}function Jp(e,t,n){if(!(`topFixes`in e))return`Technical crawl evidence was ${e.status}. Search Console and Google Analytics sections were skipped because they are not connected.`;let r=[Cp(e.summary)];return t&&r.push(Sp(t)),n&&r.push(`Audited ${n.auditedUrls} of the ${n.totalUnreached} unreached URLs in this run; their findings are in the action list.`),r.push(`Search Console and Google Analytics are not connected; provider-backed sections were skipped.`),r.join(` `)}function Yp(e){if(!(`topFixes`in e))return{tool:`seo_crawl`,status:`skipped`,summary:e.reason??`Technical crawl evidence was ${e.status}.`};let t=e.summary.crawledUrls;return{tool:`seo_crawl`,status:`completed`,summary:`${e.status===`reused`?`Loaded`:`Completed`} technical evidence for ${t} crawled ${t===1?`page`:`pages`}.`}}function Xp(e){return e?[{tool:`seo_search_console_export`,status:`completed`,summary:Sp(e)}]:[]}function Zp(e){if(e.unreachedCount===0)return;let t=[];for(let n of e.unreachedPages.slice(0,10))try{let r=new URL(n.url);t.push(`${e.crawlOrigin}${r.pathname}${r.search}`)}catch{}if(!t.length)return;let n=e.unreachedCount-t.length;return{command:`seo reports run audit-urls --params '${JSON.stringify({urls:t})}' --json`,why:`Fetch page-level evidence for exported URLs this crawl never reached${n>0?` (first ${t.length} of ${e.unreachedCount})`:``}.${e.originMismatch?` URLs were mapped onto the --url origin by path because the export origin differs.`:``}`}}function Qp(e,t={}){if(t.searchDataAvailable===!1){let e=[];return t.crawlStartUrl&&t.technicalBaselineStatus!==`created`&&t.technicalBaselineStatus!==`refreshed`&&t.technicalBaselineStatus!==`reused`?(Bp(e,`seo crawl --url ${vp(t.crawlStartUrl)} --health`,`Run the fast sitemap response and access gate before fetching page bodies.`),Bp(e,`seo crawl --url ${vp(t.crawlStartUrl)} --save`,`Create the technical crawl that this report needs before opening a page-level follow-up.`)):t.crawlStartUrl&&Bp(e,`seo audit-page --url ${vp(t.crawlStartUrl)}`,`Inspect one important page in detail after the site-level crawl.`),Bp(e,`seo start`,`Connect Search Console when you want traffic, query, and ranking evidence alongside the crawl.`),e}let n=t.projectId?`--project ${vp(t.projectId)}`:`--site ${vp(e.site)}`,r=e.output.narrative.diagnosis,i=r.decay.rangeDays,a=[],o=new Set((r.skippedSections??[]).map(e=>e.section));Bp(a,`seo refresh-priorities ${n} --days ${i} --verify-content --limit 25`,`Turn the report into a ranked action queue with content checks.`),r.decay.summary.eligibleRows>0&&Bp(a,`seo decaying ${n} --days ${i} --limit 25`,`Inspect query/page declines observed in both retained-row windows.`),r.cannibalization.items.length>0&&Bp(a,`seo cannibal ${n} --days ${i} --limit 25`,`Find queries split across competing URLs.`),r.quickWins.summary.eligibleRows>0&&Bp(a,`seo quick-wins ${n} --days ${i} --verify-content --verify-limit 5`,`Review average-position rows below their heuristic CTR target with optional page evidence.`),r.strikingDistance.summary.eligibleRows>0&&Bp(a,`seo second-page ${n} --days ${i} --verify-content --verify-limit 5`,`Work pages sitting just outside page-one rankings.`),(o.has(`traffic anomaly`)||o.has(`update correlation`))&&(Bp(a,`seo quick-wins ${n} --days ${i} --min-impressions 10 --verify-content --verify-limit 5`,`Sparse GSC data: lower thresholds and look for early content wins.`),Bp(a,`seo second-page ${n} --days ${i} --min-impressions 10 --verify-content --verify-limit 5`,`Sparse GSC data: inspect early second-page opportunities.`));let s=r.segments.page.items.find(e=>e.key.startsWith(`http`));return s&&Bp(a,`seo audit-page --url ${vp(s.key)}`,`Audit the biggest moved URL for title, metadata, links, and schema.`),t.crawlStartUrl&&t.technicalBaselineStatus!==`created`&&t.technicalBaselineStatus!==`refreshed`&&t.technicalBaselineStatus!==`reused`&&(Bp(a,`seo crawl --url ${vp(t.crawlStartUrl)} --health`,`Run the fast sitemap response and access gate before fetching page bodies.`),Bp(a,`seo crawl --url ${vp(t.crawlStartUrl)} ${n} --save`,`Create the first technical crawler baseline with plain-English fixes and JSON-ready issue data.`)),Bp(a,`seo technical-watch ${n} --limit 50`,`Save a crawl/index baseline so future reports can flag technical drift.`),a.slice(0,6)}function $p(e){e.length&&(process.stdout.write(`
280
+ `);let t=`dataSources`in e?Dp(e.dataSources):[],n=t.length?t:[`Search Console: not connected.`,`Google Analytics: not connected.`];for(let e of n)process.stdout.write(`${e}\n`);process.stdout.write("Run `seo start` when you want traffic, query, ranking, and analytics evidence added to the report.\n")}function Jp(e){let t=[],n=e.technicalCrawl;return n&&(`topFixes`in n?t.push({title:`Technical crawl evidence`,summary:`${n.summary.crawledUrls} pages were included in the ${n.status===`reused`?`saved`:`new`} crawl evidence.`,items:[...n.topFixes.map(e=>`${e.ruleId}: ${e.title}. ${e.count} affected URLs. ${e.recommendation}`),...Dp(n.dataSources)]}):t.push({title:`Technical crawl evidence`,summary:n.reason??`Technical evidence was ${n.status}.`,items:[]})),e.followups?.length&&t.push({title:`Recommended next commands`,items:e.followups.map(e=>`${e.command}: ${e.why}`)}),t}function Yp(e,t,n){if(!(`topFixes`in e))return`Technical crawl evidence was ${e.status}. Search Console and Google Analytics sections were skipped because they are not connected.`;let r=[wp(e.summary)];return t&&r.push(Cp(t)),n&&r.push(`Audited ${n.auditedUrls} of the ${n.totalUnreached} unreached URLs in this run; their findings are in the action list.`),r.push(`Search Console and Google Analytics are not connected; provider-backed sections were skipped.`),r.join(` `)}function Xp(e){if(!(`topFixes`in e))return{tool:`seo_crawl`,status:`skipped`,summary:e.reason??`Technical crawl evidence was ${e.status}.`};let t=e.summary.crawledUrls;return{tool:`seo_crawl`,status:`completed`,summary:`${e.status===`reused`?`Loaded`:`Completed`} technical evidence for ${t} crawled ${t===1?`page`:`pages`}.`}}function Zp(e){return e?[{tool:`seo_search_console_export`,status:`completed`,summary:Cp(e)}]:[]}function Qp(e){if(e.unreachedCount===0)return;let t=[];for(let n of e.unreachedPages.slice(0,10))try{let r=new URL(n.url);t.push(`${e.crawlOrigin}${r.pathname}${r.search}`)}catch{}if(!t.length)return;let n=e.unreachedCount-t.length;return{command:`seo reports run audit-urls --params '${JSON.stringify({urls:t})}' --json`,why:`Fetch page-level evidence for exported URLs this crawl never reached${n>0?` (first ${t.length} of ${e.unreachedCount})`:``}.${e.originMismatch?` URLs were mapped onto the --url origin by path because the export origin differs.`:``}`}}function $p(e,t={}){if(t.searchDataAvailable===!1){let e=[];return t.crawlStartUrl&&t.technicalBaselineStatus!==`created`&&t.technicalBaselineStatus!==`refreshed`&&t.technicalBaselineStatus!==`reused`?(Q(e,`seo crawl --url ${yp(t.crawlStartUrl)} --health`,`Run the fast sitemap response and access gate before fetching page bodies.`),Q(e,`seo crawl --url ${yp(t.crawlStartUrl)} --save`,`Create the technical crawl that this report needs before opening a page-level follow-up.`)):t.crawlStartUrl&&Q(e,`seo audit-page --url ${yp(t.crawlStartUrl)}`,`Inspect one important page in detail after the site-level crawl.`),Q(e,`seo start`,`Connect Search Console when you want traffic, query, and ranking evidence alongside the crawl.`),e}let n=t.projectId?`--project ${yp(t.projectId)}`:`--site ${yp(e.site)}`,r=e.output.narrative.diagnosis,i=r.decay.rangeDays,a=[],o=new Set((r.skippedSections??[]).map(e=>e.section));Q(a,`seo refresh-priorities ${n} --days ${i} --verify-content --limit 25`,`Turn the report into a ranked action queue with content checks.`),r.decay.summary.eligibleRows>0&&Q(a,`seo decaying ${n} --days ${i} --limit 25`,`Inspect query/page declines observed in both retained-row windows.`),r.cannibalization.items.length>0&&Q(a,`seo cannibal ${n} --days ${i} --limit 25`,`Find queries split across competing URLs.`),r.quickWins.summary.eligibleRows>0&&Q(a,`seo quick-wins ${n} --days ${i} --verify-content --verify-limit 5`,`Review average-position rows below their heuristic CTR target with optional page evidence.`),r.strikingDistance.summary.eligibleRows>0&&Q(a,`seo second-page ${n} --days ${i} --verify-content --verify-limit 5`,`Work pages sitting just outside page-one rankings.`),(o.has(`traffic anomaly`)||o.has(`update correlation`))&&(Q(a,`seo quick-wins ${n} --days ${i} --min-impressions 10 --verify-content --verify-limit 5`,`Sparse GSC data: lower thresholds and look for early content wins.`),Q(a,`seo second-page ${n} --days ${i} --min-impressions 10 --verify-content --verify-limit 5`,`Sparse GSC data: inspect early second-page opportunities.`));let s=r.segments.page.items.find(e=>e.key.startsWith(`http`));return s&&Q(a,`seo audit-page --url ${yp(s.key)}`,`Audit the biggest moved URL for title, metadata, links, and schema.`),t.crawlStartUrl&&t.technicalBaselineStatus!==`created`&&t.technicalBaselineStatus!==`refreshed`&&t.technicalBaselineStatus!==`reused`&&(Q(a,`seo crawl --url ${yp(t.crawlStartUrl)} --health`,`Run the fast sitemap response and access gate before fetching page bodies.`),Q(a,`seo crawl --url ${yp(t.crawlStartUrl)} ${n} --save`,`Create the first technical crawler baseline with plain-English fixes and JSON-ready issue data.`)),Q(a,`seo technical-watch ${n} --limit 50`,`Save a crawl/index baseline so future reports can flag technical drift.`),a.slice(0,6)}function em(e){e.length&&(process.stdout.write(`
281
281
  Recommended next commands
282
- `),V([`Command`,`Why`],e.map(e=>[e.command,e.why])))}function em(e){process.stdout.write(`# Technical SEO report: ${e}\n\n`)}function tm(e){return E({meta:e,args:{site:{type:`string`,description:`GSC property URL, for example sc-domain:example.com.`},...e.printFollowups?{url:{type:`string`,description:`Crawl URL for a technical-only report. Search Console analyses are skipped unless you also pass --site or --project.`}}:{},client:{type:`string`,description:`Legacy alias for --project.`},project:{type:`string`,description:`Saved project id or name.`},...Z([`days`,`recentDays`,`limit`,`includeBrand`,`refresh`],{days:{description:`Search performance window in days. Defaults to 90.`},limit:{description:`Maximum rows per section. Defaults to 10.`}}),...e.printFollowups?{crawl:Pa(`Create or reuse a bounded technical crawl before the report.`,`Skip technical crawl evidence for this report.`),"crawl-max-pages":{type:`string`,description:`Maximum pages for the report crawl. Defaults to 100.`},"crawl-max-depth":{type:`string`,description:`Maximum link depth for the report crawl. Defaults to 4.`},full:{type:`boolean`,default:!1,description:`Include the full report in JSON output. Default JSON is a compact summary for agents.`},"actions-only":{type:`boolean`,default:!1,description:`Return findings, verification, and the content inventory without the full report body. JSON only.`},"search-console-export":{type:`string`,description:`Path to a local Search Console export (a directory or CSV with the standard query and page tables). URL mode only; query and page tables stay separate.`},"inventory-page":{type:`string`,default:`1`,description:`Page of local Search Console export inventory rows to return. URL mode only.`},...uf}:{},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:t})=>{let r=I(t),i=e.printFollowups?df(t):void 0,a=e.printFollowups&&j(t.full),o=e.printFollowups&&j(t[`actions-only`]);if(o&&!r)throw new w(`INVALID_INPUT`,`Use --actions-only with --json so findings and completion outcomes stay machine-readable.`);if(o&&a)throw new w(`INVALID_INPUT`,`Choose either --actions-only or --full, not both.`);let s=A(t),c=k(t.site),l=e.printFollowups?k(t.url):void 0,u=!!(s||c||!l),d=e.printFollowups?k(t[`search-console-export`]):void 0,f=e.printFollowups?M(t[`inventory-page`])??1:1;if(!Number.isSafeInteger(f)||f<1)throw new w(`INVALID_INPUT`,`--inventory-page must be a positive integer.`);if(f!==1&&!d)throw new w(`INVALID_INPUT`,`Use --inventory-page only with --search-console-export.`);if(d&&u)throw new w(`INVALID_INPUT`,`Use --search-console-export only with --url. A connected property already provides query and page evidence, and imported tables must not be blended with it.`);let p=u?await U({client:s,site:c,options:{json:r,refresh:j(t.refresh)}}):void 0,m=p?.site??yp(l??``),h=await qn({site:m,days:M(t.days),recentDays:M(t.recent),limit:M(t.limit),brandTerms:p?.client?.brandTerms,includeBrand:j(t[`include-brand`]),refresh:j(t.refresh),skipSearchData:!u}),g=e.workflowName?{...h,workflow:e.workflowName}:h,_=e.printFollowups?await Sr({site:m,searchSite:p?.site,url:l??p?.client?.startUrl??bs(m),projectId:p?.client?.id,googleAnalyticsPropertyId:p?.client?.analytics.google?.propertyId,analyticsConnection:Jn(p?.client),crawl:!Ia(t,`crawl`),refresh:j(t.refresh),maxPages:M(t[`crawl-max-pages`]),maxDepth:M(t[`crawl-max-depth`])}):void 0,v=_?Vp(_):void 0,y=d?await Da({path:d}):void 0,b=y&&_?.report&&l?Ma({pages:y.pages.rows,crawledUrls:_.report.pages.flatMap(e=>[e.url,e.finalUrl]),crawlOrigin:yp(l)}):void 0,ee=y?{evidence:y,reconciliation:b}:void 0,x=await(async()=>{if(!b||b.unreachedCount===0)return;let e=zp(b,20);if(!e.length)return;let n=await rt({url:e[0]??``,urls:e,mode:`list`,maxPages:e.length,useSitemap:!1,refresh:j(t.refresh)});return yn(n,void 0,{retention:`baseline`}),{reportId:n.id,auditedUrls:n.summary.totalPages,totalUnreached:b.unreachedCount,capped:b.unreachedCount>e.length,issueGroupsComplete:!0,topFixes:wp(n),reviewObservations:Tp(n),pages:n.pages.map(e=>({url:e.finalUrl||e.url,title:e.title??null}))}})(),te=_?.report?Ap(_.report.pages,x?.pages):void 0,ne=y&&te?Fp({queries:y.queries.rows,pages:te}):void 0,re=y&&te?Pp({pages:y.pages.rows,sourceCapped:y.pages.capped,page:f,reconciliation:b,crawledPages:te,overlap:ne}):void 0,S=_?.report?Rp({crawlReportId:_.report.id,topFixes:wp(_.report),reviewObservations:Tp(_.report),reconciliation:b,...x?{unreachedAudit:{reportId:x.reportId,topFixes:x.topFixes,reviewObservations:x.reviewObservations,titlePrefix:`unreached pages:`}}:{},...ne?{topicOverlap:ne}:{},...re?{pageInventory:re}:{}}):void 0,C=e.printFollowups?Qp(g,{crawlStartUrl:l??p?.client?.startUrl??bs(m),projectId:p?.client?.id,technicalBaselineStatus:_?.status,searchDataAvailable:u}):void 0;if(C&&b&&(!x||x.capped)){let e=Zp(b);e&&C.unshift(e)}if(r){let t=e.printFollowups&&!a?_p(h,e.workflowName):g,r=!u&&v?{...t,summary:Jp(v,ee,x),steps:[Yp(v),...Xp(ee),...t.steps],...S?{actions:S}:{},...a?{}:{output:{searchSections:{status:`skipped`,reason:`No Search Console property was selected. Pass --site or --project, or run seo start to connect one.`}}}}:t,i=v||C?{...r,...e.printFollowups?{detail:a?`full`:`summary`}:{},...v?{technicalCrawl:e.printFollowups&&!a?Up(v):v}:{},...y?{searchConsoleExport:{...bp({evidence:y,reconciliation:b,pageInventory:re}),...x?{unreachedPagesAudit:x}:{},...ne?{topicOverlapCandidates:ne}:{}}}:{},...C?{nextCommands:C}:{}}:t,s={reportId:`report`,preferRootActions:!0,verify:`Rerun seo report with the same inputs and confirm the finding is absent or its evidence now matches the intended state.`,...!u&&v&&`issueGroupsComplete`in v&&v.issueGroupsComplete?{coverage:`complete`}:{}};z(Pn(o?n(i,s):Xt(i,s)));return}if(i){hf(await mf({report:g.output.narrative,reportName:`report`,title:u?`SEO report`:`Technical SEO report`,options:i,projectId:p?.client?.id,additionalSections:qp({technicalCrawl:v,followups:C})}));return}u?(process.stdout.write(`${g.output.narrative.markdown}\n\n`),zu(g)):em(m),v&&(Wp(v,{providerFree:!u}),y&&Gp({evidence:y,reconciliation:b,unreachedAudit:x,topicOverlap:ne,pageInventory:re}),u||Kp(v)),C&&$p(C)}})}const nm=tm({name:`diagnose-property`,description:`Find what changed in Google Search and what to inspect next`}),rm=tm({name:`report`,workflowName:`report`,printFollowups:!0,description:`Run the main SEO report and recommend the next best follow-up commands`}),im=E({meta:{name:`refresh-priorities`,description:`Agent workflow for a ranked SEO action queue`},args:{site:{type:`string`,description:`GSC property URL, for example sc-domain:example.com.`},client:{type:`string`,description:`Legacy alias for --project.`},project:{type:`string`,description:`Saved project id or name.`},...Z([`days`,`recentDays`,`limit`,`includeBrand`,`refresh`],{days:{description:`Diagnosis window length in days. Defaults to 90.`},limit:{description:`Maximum queue items to print. Defaults to 25.`}}),...$c,"verify-content":Pa(`Verify top opportunities against page title, meta, and content. Defaults to true.`,`Skip page content verification.`),"verify-limit":{type:`string`,description:`Maximum opportunity URLs to verify. Defaults to 5.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=I(e),n=await U({client:A(e),site:k(e.site),options:{json:t,refresh:j(e.refresh)}}),r=await rn({site:n.site,days:M(e.days),recentDays:M(e.recent),limit:M(e.limit),brandTerms:n.client?.brandTerms,includeBrand:j(e[`include-brand`]),googleAnalyticsPropertyId:k(e[`google-analytics-property`]),analyticsConnection:el(e,Jn(n.client)),verifyContent:j(e[`verify-content`]),verifyLimit:M(e[`verify-limit`]),refresh:j(e.refresh)});if(t){z(r);return}zu(r),V([`Source`,`Category`,`Score`,`Findings`,`Template`,`Analytics`,`Target`,`Action`],r.output.queue.map(e=>[e.source,e.category,e.score,e.grouped?.count??1,e.template?.label??`-`,e.analytics?.sessions??`-`,e.target,e.action])),Y(`Queue action details`,r.output.queue.map(e=>({label:e.target,context:`${e.category}, score ${e.score}`,action:e.action})))}}),am=E({meta:{name:`technical-watch`,description:`Agent workflow for crawl-diff and index-watch monitoring`},args:{site:{type:`string`,description:`GSC property URL, for example sc-domain:example.com.`},client:{type:`string`,description:`Legacy alias for --project.`},project:{type:`string`,description:`Saved project id or name.`},url:{type:`string`,description:`Start URL to crawl. Defaults from the GSC property when possible.`},urls:{type:`string`,description:`Comma-separated URLs to inspect with URL Inspection.`},sitemaps:{type:`string`,description:`Comma-separated XML sitemap URLs for quota-aware index monitoring.`},properties:{type:`string`,description:`Comma-separated GSC properties for sitemap index monitoring.`},limit:{type:`string`,description:`Maximum pages to crawl. Defaults to 50.`},"daily-limit":{type:`string`,description:`URL Inspection daily limit per property. Defaults to 2000.`},"inspect-limit":{type:`string`,description:`Maximum sitemap URLs to inspect in this run. Defaults to 100.`},"max-urls":{type:`string`,description:`Maximum sitemap URLs to load for monitoring. Defaults to 50000.`},language:{type:`string`,description:`Optional URL Inspection language code.`},"recover-links":Pa(`Check search-value GSC pages for broken, blocked, or poorly redirected URLs. Defaults to true.`,`Skip link recovery checks.`),"recover-days":{type:`string`,description:`GSC lookback window for link recovery. Defaults to 90.`},"recover-limit":{type:`string`,description:`Maximum search-value pages to check for link recovery. Defaults to 10.`},"recover-min-clicks":{type:`string`,description:`Minimum clicks for link recovery candidates. Defaults to 1.`},"recover-min-impressions":{type:`string`,description:`Minimum impressions for link recovery candidates. Defaults to 100.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`},refresh:{type:`boolean`,default:!1,description:`Bypass local HTTP/GSC cache where supported.`},js:{type:`boolean`,default:!1,description:`Force JavaScript rendering when Playwright is installed.`}},run:async({args:e})=>{let t=I(e),n=await U({client:A(e),site:k(e.site),options:{json:t,refresh:j(e.refresh)}}),r=F(e.urls),i=F(e.sitemaps),a=k(e.url)??n.client?.startUrl??bs(n.site),o=await Yt({site:n.site,startUrl:a,urls:r.length?r:n.client?.watchUrls,sitemaps:i.length?i:void 0,properties:F(e.properties).length?F(e.properties):void 0,limit:M(e.limit),languageCode:k(e.language),dailyLimit:M(e[`daily-limit`]),inspectLimit:M(e[`inspect-limit`]),maxUrls:M(e[`max-urls`]),refresh:j(e.refresh),js:j(e.js)?!0:`auto`,recoverLinks:j(e[`recover-links`]),recoverDays:M(e[`recover-days`]),recoverLimit:M(e[`recover-limit`]),recoverMinClicks:M(e[`recover-min-clicks`]),recoverMinImpressions:M(e[`recover-min-impressions`])});if(t){z(o);return}Bu(o)}});function om(e){return e===null?`-`:e.toLocaleString(`en-GB`,{maximumFractionDigits:e>=100?0:1})}function sm(e){return[...e.winners.map(e=>({direction:`winner`,item:e})),...e.losers.map(e=>({direction:`loser`,item:e}))]}function cm(e,t){let n=sm(t);n.length&&(process.stdout.write(`\n${e}\n`),V([`Direction`,`Key`,`Clicks`,`Impr.`,`Position`],n.map(({direction:e,item:t})=>[e,t.key,om(t.clickDelta),om(t.impressionDelta),om(t.positionDelta)])))}function lm(e){process.stdout.write(`
282
+ `),B([`Command`,`Why`],e.map(e=>[e.command,e.why])))}function tm(e){process.stdout.write(`# Technical SEO report: ${e}\n\n`)}function nm(e){return T({meta:e,args:{site:{type:`string`,description:`GSC property URL, for example sc-domain:example.com.`},...e.printFollowups?{url:{type:`string`,description:`Crawl URL for a technical-only report. Search Console analyses are skipped unless you also pass --site or --project.`}}:{},client:{type:`string`,description:`Legacy alias for --project.`},project:{type:`string`,description:`Saved project id or name.`},...X([`days`,`recentDays`,`limit`,`includeBrand`,`refresh`],{days:{description:`Search performance window in days. Defaults to 90.`},limit:{description:`Maximum rows per section. Defaults to 10.`}}),...e.printFollowups?{crawl:Fa(`Create or reuse a bounded technical crawl before the report.`,`Skip technical crawl evidence for this report.`),"crawl-max-pages":{type:`string`,description:`Maximum pages for the report crawl. Defaults to 100.`},"crawl-max-depth":{type:`string`,description:`Maximum link depth for the report crawl. Defaults to 4.`},full:{type:`boolean`,default:!1,description:`Include the full report in JSON output. Default JSON is a compact summary for agents.`},"actions-only":{type:`boolean`,default:!1,description:`Return findings, verification, and the content inventory without the full report body. JSON only.`},"search-console-export":{type:`string`,description:`Path to a local Search Console export (a directory or CSV with the standard query and page tables). URL mode only; query and page tables stay separate.`},"inventory-page":{type:`string`,default:`1`,description:`Page of local Search Console export inventory rows to return. URL mode only.`},...df}:{},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:t})=>{let r=F(t),i=e.printFollowups?ff(t):void 0,a=e.printFollowups&&A(t.full),o=e.printFollowups&&A(t[`actions-only`]);if(o&&!r)throw new C(`INVALID_INPUT`,`Use --actions-only with --json so findings and completion outcomes stay machine-readable.`);if(o&&a)throw new C(`INVALID_INPUT`,`Choose either --actions-only or --full, not both.`);let s=k(t),c=O(t.site),l=e.printFollowups?O(t.url):void 0,u=!!(s||c||!l),d=e.printFollowups?O(t[`search-console-export`]):void 0,f=e.printFollowups?j(t[`inventory-page`])??1:1;if(!Number.isSafeInteger(f)||f<1)throw new C(`INVALID_INPUT`,`--inventory-page must be a positive integer.`);if(f!==1&&!d)throw new C(`INVALID_INPUT`,`Use --inventory-page only with --search-console-export.`);if(d&&u)throw new C(`INVALID_INPUT`,`Use --search-console-export only with --url. A connected property already provides query and page evidence, and imported tables must not be blended with it.`);let p=u?await H({client:s,site:c,options:{json:r,refresh:A(t.refresh)}}):void 0,m=p?.site??bp(l??``),h=await Jn({site:m,days:j(t.days),recentDays:j(t.recent),limit:j(t.limit),brandTerms:p?.client?.brandTerms,includeBrand:A(t[`include-brand`]),refresh:A(t.refresh),skipSearchData:!u}),g=e.workflowName?{...h,workflow:e.workflowName}:h,_=e.printFollowups?await Cr({site:m,searchSite:p?.site,url:l??p?.client?.startUrl??xs(m),projectId:p?.client?.id,googleAnalyticsPropertyId:p?.client?.analytics.google?.propertyId,analyticsConnection:Yn(p?.client),crawl:!La(t,`crawl`),refresh:A(t.refresh),maxPages:j(t[`crawl-max-pages`]),maxDepth:j(t[`crawl-max-depth`])}):void 0,v=_?Vp(_):void 0,y=d?await Oa({path:d}):void 0,b=y&&_?.report&&l?Na({pages:y.pages.rows,crawledUrls:_.report.pages.flatMap(e=>[e.url,e.finalUrl]),crawlOrigin:bp(l)}):void 0,ee=y?{evidence:y,reconciliation:b}:void 0,x=await(async()=>{if(!b||b.unreachedCount===0)return;let e=Bp(b,20);if(!e.length)return;let n=await it({url:e[0]??``,urls:e,mode:`list`,maxPages:e.length,useSitemap:!1,refresh:A(t.refresh)});return bn(n,void 0,{retention:`baseline`}),{reportId:n.id,auditedUrls:n.summary.totalPages,totalUnreached:b.unreachedCount,capped:b.unreachedCount>e.length,issueGroupsComplete:!0,topFixes:Tp(n),reviewObservations:Ep(n),pages:n.pages.map(e=>({url:e.finalUrl||e.url,title:e.title??null}))}})(),te=_?.report?jp(_.report.pages,x?.pages):void 0,ne=y&&te?Ip({queries:y.queries.rows,pages:te}):void 0,re=y&&te?Fp({pages:y.pages.rows,sourceCapped:y.pages.capped,page:f,reconciliation:b,crawledPages:te,overlap:ne}):void 0,ie=_?.report?zp({crawlReportId:_.report.id,topFixes:Tp(_.report),reviewObservations:Ep(_.report),reconciliation:b,...x?{unreachedAudit:{reportId:x.reportId,topFixes:x.topFixes,reviewObservations:x.reviewObservations,titlePrefix:`unreached pages:`}}:{},...ne?{topicOverlap:ne}:{},...re?{pageInventory:re}:{}}):void 0,S=e.printFollowups?$p(g,{crawlStartUrl:l??p?.client?.startUrl??xs(m),projectId:p?.client?.id,technicalBaselineStatus:_?.status,searchDataAvailable:u}):void 0;if(S&&b&&(!x||x.capped)){let e=Qp(b);e&&S.unshift(e)}if(r){let t=e.printFollowups&&!a?vp(h,e.workflowName):g,r=Wp(t.actions,ie),i=!u&&v?{...t,summary:Yp(v,ee,x),steps:[Xp(v),...Zp(ee),...t.steps],actions:r,...a?{}:{output:{searchSections:{status:`skipped`,reason:`No Search Console property was selected. Pass --site or --project, or run seo start to connect one.`}}}}:{...t,actions:r},s=v||S?{...i,...e.printFollowups?{detail:a?`full`:`summary`}:{},...v?{technicalCrawl:e.printFollowups&&!a?Up(v):v}:{},...y?{searchConsoleExport:{...xp({evidence:y,reconciliation:b,pageInventory:re}),...x?{unreachedPagesAudit:x}:{},...ne?{topicOverlapCandidates:ne}:{}}}:{},...S?{nextCommands:S}:{}}:t,c={reportId:`report`,preferRootActions:!0,verify:`Rerun seo report with the same inputs and confirm the finding is absent or its evidence now matches the intended state.`,...!u&&v&&`issueGroupsComplete`in v&&v.issueGroupsComplete?{coverage:`complete`}:{}};R(Fn(o?n(s,c):Zt(s,c)));return}if(i){gf(await hf({report:g.output.narrative,reportName:`report`,title:u?`SEO report`:`Technical SEO report`,options:i,projectId:p?.client?.id,additionalSections:Jp({technicalCrawl:v,followups:S})}));return}u?(process.stdout.write(`${g.output.narrative.markdown}\n\n`),Bu(g)):tm(m),v&&(Gp(v,{providerFree:!u}),y&&Kp({evidence:y,reconciliation:b,unreachedAudit:x,topicOverlap:ne,pageInventory:re}),u||qp(v)),S&&em(S)}})}const rm=nm({name:`diagnose-property`,description:`Find what changed in Google Search and what to inspect next`}),im=nm({name:`report`,workflowName:`report`,printFollowups:!0,description:`Run the main SEO report and recommend the next best follow-up commands`}),am=T({meta:{name:`refresh-priorities`,description:`Agent workflow for a ranked SEO action queue`},args:{site:{type:`string`,description:`GSC property URL, for example sc-domain:example.com.`},client:{type:`string`,description:`Legacy alias for --project.`},project:{type:`string`,description:`Saved project id or name.`},...X([`days`,`recentDays`,`limit`,`includeBrand`,`refresh`],{days:{description:`Diagnosis window length in days. Defaults to 90.`},limit:{description:`Maximum queue items to print. Defaults to 25.`}}),...el,"verify-content":Fa(`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=F(e),n=await H({client:k(e),site:O(e.site),options:{json:t,refresh:A(e.refresh)}}),r=await an({site:n.site,days:j(e.days),recentDays:j(e.recent),limit:j(e.limit),brandTerms:n.client?.brandTerms,includeBrand:A(e[`include-brand`]),googleAnalyticsPropertyId:O(e[`google-analytics-property`]),analyticsConnection:tl(e,Yn(n.client)),verifyContent:A(e[`verify-content`]),verifyLimit:j(e[`verify-limit`]),refresh:A(e.refresh)});if(t){R(r);return}Bu(r),B([`Source`,`Category`,`Score`,`Findings`,`Template`,`Analytics`,`Target`,`Action`],r.output.queue.map(e=>[e.source,e.category,e.score,e.grouped?.count??1,e.template?.label??`-`,e.analytics?.sessions??`-`,e.target,e.action])),J(`Queue action details`,r.output.queue.map(e=>({label:e.target,context:`${e.category}, score ${e.score}`,action:e.action})))}}),om=T({meta:{name:`technical-watch`,description:`Agent workflow for crawl-diff and index-watch monitoring`},args:{site:{type:`string`,description:`GSC property URL, for example sc-domain:example.com.`},client:{type:`string`,description:`Legacy alias for --project.`},project:{type:`string`,description:`Saved project id or name.`},url:{type:`string`,description:`Start URL to crawl. Defaults from the GSC property when possible.`},urls:{type:`string`,description:`Comma-separated URLs to inspect with URL Inspection.`},sitemaps:{type:`string`,description:`Comma-separated XML sitemap URLs for quota-aware index monitoring.`},properties:{type:`string`,description:`Comma-separated GSC properties for sitemap index monitoring.`},limit:{type:`string`,description:`Maximum pages to crawl. Defaults to 50.`},"daily-limit":{type:`string`,description:`URL Inspection daily limit per property. Defaults to 2000.`},"inspect-limit":{type:`string`,description:`Maximum sitemap URLs to inspect in this run. Defaults to 100.`},"max-urls":{type:`string`,description:`Maximum sitemap URLs to load for monitoring. Defaults to 50000.`},language:{type:`string`,description:`Optional URL Inspection language code.`},"recover-links":Fa(`Check search-value GSC pages for broken, blocked, or poorly redirected URLs. Defaults to true.`,`Skip link recovery checks.`),"recover-days":{type:`string`,description:`GSC lookback window for link recovery. Defaults to 90.`},"recover-limit":{type:`string`,description:`Maximum search-value pages to check for link recovery. Defaults to 10.`},"recover-min-clicks":{type:`string`,description:`Minimum clicks for link recovery candidates. Defaults to 1.`},"recover-min-impressions":{type:`string`,description:`Minimum impressions for link recovery candidates. Defaults to 100.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`},refresh:{type:`boolean`,default:!1,description:`Bypass local HTTP/GSC cache where supported.`},js:{type:`boolean`,default:!1,description:`Force JavaScript rendering when Playwright is installed.`}},run:async({args:e})=>{let t=F(e),n=await H({client:k(e),site:O(e.site),options:{json:t,refresh:A(e.refresh)}}),r=P(e.urls),i=P(e.sitemaps),a=O(e.url)??n.client?.startUrl??xs(n.site),o=await Xt({site:n.site,startUrl:a,urls:r.length?r:n.client?.watchUrls,sitemaps:i.length?i:void 0,properties:P(e.properties).length?P(e.properties):void 0,limit:j(e.limit),languageCode:O(e.language),dailyLimit:j(e[`daily-limit`]),inspectLimit:j(e[`inspect-limit`]),maxUrls:j(e[`max-urls`]),refresh:A(e.refresh),js:A(e.js)?!0:`auto`,recoverLinks:A(e[`recover-links`]),recoverDays:j(e[`recover-days`]),recoverLimit:j(e[`recover-limit`]),recoverMinClicks:j(e[`recover-min-clicks`]),recoverMinImpressions:j(e[`recover-min-impressions`])});if(t){R(o);return}Vu(o)}});function sm(e){return e===null?`-`:e.toLocaleString(`en-GB`,{maximumFractionDigits:e>=100?0:1})}function cm(e){return[...e.winners.map(e=>({direction:`winner`,item:e})),...e.losers.map(e=>({direction:`loser`,item:e}))]}function lm(e,t){let n=cm(t);n.length&&(process.stdout.write(`\n${e}\n`),B([`Direction`,`Key`,`Clicks`,`Impr.`,`Position`],n.map(({direction:e,item:t})=>[e,t.key,sm(t.clickDelta),sm(t.impressionDelta),sm(t.positionDelta)])))}function um(e){process.stdout.write(`
283
283
  Segment coverage
284
- `),V([`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 um(e){e.length&&(process.stdout.write(`
284
+ `),B([`Dimension`,`Eligible`,`Returned`,`Limited`,`Result cap`,`Source cap`,`Source capped`],[`page`,`query`,`device`,`country`].map(t=>{let n=e[t].coverage;return[t,n.eligibleRows,n.returnedRows,n.limitedRows,n.resultLimit,n.sourceRowLimit,n.sourcePossiblyTruncated?`yes`:`no`]}))}function dm(e){e.length&&(process.stdout.write(`
285
285
  Template movement
286
- `),V([`Direction`,`Template`,`Pattern confidence`,`URLs`,`Clicks`,`Returned share`,`Common terms`],e.map(e=>[e.direction,e.signature,e.confidence,e.urlCount,om(e.clickDelta),`${Math.round(e.movementShare*100)}%`,e.commonTerms.join(`, `)||`-`])),X(`Template actions`,e.map(e=>e.summary)),X(`Template evidence scope`,[`Shares use returned directional page click movement. Pattern confidence applies only to returned matched-retained page segments.`]))}const dm=E({meta:{name:`update-postmortem`,description:`Agent workflow for Google update winner/loser analysis`},args:{site:{type:`string`,description:`GSC property URL, for example sc-domain:example.com.`},client:{type:`string`,description:`Legacy alias for --project.`},project:{type:`string`,description:`Saved project id or name.`},...Z([`days`,`recentDays`,`limit`,`includeBrand`,`refresh`],{days:{description:`Diagnosis window length in days. Defaults to 90.`},limit:{description:`Maximum winners/losers per segment. Defaults to 20.`}}),"known-change":{type:`string`,description:`Manual site-side change to treat as a confounder, for example pruning pages or blocking traffic.`},"ignore-change-log":{type:`boolean`,default:!1,description:`Do not use saved change-log entries as confounders.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=I(e),n=await U({client:A(e),site:k(e.site),options:{json:t,refresh:j(e.refresh)}}),r=await gr({site:n.site,days:M(e.days),recentDays:M(e.recent),limit:M(e.limit),brandTerms:n.client?.brandTerms,includeBrand:j(e[`include-brand`]),knownConfounders:k(e[`known-change`])?[k(e[`known-change`])??``]:void 0,includeChangeLog:!j(e[`ignore-change-log`]),refresh:j(e.refresh)});if(t){z(r);return}q({title:`Update postmortem`,target:r.site,status:`unknown`,summary:r.summary,metrics:[{label:`Attribution`,value:r.output.update.attribution.replaceAll(`-`,` `)},{label:`Confidence`,value:r.output.update.confidence},{label:`Known confounders`,value:r.output.update.confounders.length}]}),X(`Recommended actions`,r.actions.map(e=>`${e.title} (${e.confidence}): ${e.action}`)),X(`Postmortem findings`,r.output.insights.map(e=>e.summary)),X(`Segment evidence warnings`,[...r.output.segments.page.warnings,...r.output.segments.query.warnings,...r.output.segments.device.warnings,...r.output.segments.country.warnings].filter((e,t,n)=>n.indexOf(e)===t)),um(r.output.templateMovement),X(`Update evidence`,r.output.update.evidence),lm(r.output.segments),cm(`Page winners and losers`,r.output.segments.page),cm(`Query winners and losers`,r.output.segments.query),cm(`Device winners and losers`,r.output.segments.device),cm(`Country winners and losers`,r.output.segments.country),cs(`seo export update-postmortem ${n.client?`--project ${JSON.stringify(n.client.id)}`:`--site ${JSON.stringify(n.site)}`}`)}}),fm=new Set([`database`,`filesystem`,`internal`,`unknown`]);function pm(e){return e.replace(/^v/,``).split(`.`)[0]||`unknown`}function mm(e){return Tr(e[0])?e[0]:void 0}function hm(e,t){return Wn(e)?`seo ${e}`:t?`seo ${t}`:`seo command`}function gm(e){let t=zt(e.error);if(!fm.has(t.errorCategory))return;let n=mm(e.args),r=hm(e.report,n),i=[pr(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??Fr),a.searchParams.set(`runtime`,`Node ${pm(e.nodeVersion??process.version)} on ${e.processPlatform??oi()} (${e.processArch??ii()})`),a.searchParams.set(`diagnostics`,i),a.toString()}function _m(e){return e.replaceAll(`\\`,`/`).toLowerCase()}function vm(e,t){return t===`win32`?`${e}.cmd`:e}function ym(e,t=process.platform){let n=_m(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:vm(`pnpm`,t),args:[`add`,`--global`,`seo@latest`]};if(n.includes(`/.bun/install/global/`))return{command:vm(`bun`,t),args:[`add`,`--global`,`seo@latest`]};if(n.includes(`/.config/yarn/global/`))return{command:vm(`yarn`,t),args:[`global`,`add`,`seo@latest`]};if(n.includes(`/node_modules/seo/`))return{command:vm(`npm`,t),args:[`install`,`--global`,`seo@latest`]}}}function bm(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 xm(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 Sm(e){if(e)try{return Kr(e)}catch{return e}}async function Cm(e){let t=await Mi({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 Di(t)?`later`:t}function wm(e){return xi(e.command,e.args,{stdio:`inherit`}).status}async function Tm(e,t={}){if(!xm({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=Sm(t.cliPath??process.argv[1]),r=n?ym(n,t.platform??process.platform):void 0;if(!r)return;let i=(t.createNotifier??(e=>Hi({pkg:e,shouldNotifyInNpmScript:!1})))(e).update;if(!(!i||!bm(i.current,i.latest))&&await(t.prompt??Cm)({command:r,update:i})!==`later`)return(t.run??wm)(r)===0?(Oi.success(`Updated seo to ${i.latest}. Run your command again.`),0):(Oi.error(`The update failed. Your current version is unchanged.`),1)}const Em={name:`seo`,version:Fr},Dm=[{title:`Start here`,commands:[[`seo start`,`Connect first-party data and save a project profile`],[`seo report`,`Run the main SEO report for the default project`],[`seo report --site sc-domain:example.com`,`Run without a profile`],[`seo report --url https://example.com`,`Start with a local technical report`]]},{title:`Projects`,commands:[[`seo projects list`,`List saved project profiles`],[`seo projects add`,`Create or update a project profile`],[`seo sites`,`List Search Console properties`],[`seo doctor`,`Check local auth and config`]]},{title:`Act on a report`,commands:[[`seo refresh-priorities`,`Rank the next best SEO actions`],[`seo quick-wins`,`Find ranking 4-10 low-CTR wins`],[`seo second-page`,`Investigate URLs averaging positions 10-20`],[`seo technical-watch`,`Crawl and index-monitor a site`]]},{title:`Agent and power tools`,commands:[[`seo report --json`,`Run the main report as structured JSON`],[`seo export diagnose`,`Export report data to CSV`],[`seo mcp install`,`Install SEO tools into MCP clients`],[`seo skill list`,`Show the packaged SEO skill`],[`seo reports list`,`Discover every structured report`]]}],Om=[...Dm,{title:`Deeper analysis`,commands:[[`seo decaying`,`Find pages and queries losing clicks`],[`seo cannibal`,`Review queries exposed across multiple URLs`],[`seo ctr-underperformers`,`Find weak CTR by ranking position`],[`seo query-cluster`,`Cluster repeated demand themes`],[`seo page-opportunities`,`Analyze one URL for growth ideas`],[`seo content optimize`,`Build a content optimization report`],[`seo perf audit`,`Run a page performance audit`],[`seo internal-links`,`Find internal link opportunities`],[`seo community-intent`,`Find forum, review, and comparison intent`],[`seo ai-referrals`,`Find AI referral traffic in Google Analytics`],[`seo seo-to-ai-query`,`Convert searches into AI-monitor prompts`]]},{title:`Technical and data`,commands:[[`seo audit-page`,`Audit one page`],[`seo crawl --sitemap-url <url> --health`,`Run the fast sitemap gate before a full crawl`],[`seo crawl`,`Run a full crawl for page and content evidence`],[`seo agent-readiness`,`Check content-site readiness for AI agents`],[`seo ai-readiness`,`Review AI-search technical evidence`],[`seo llms audit`,`Inspect optional llms.txt presence`],[`seo entity-readiness`,`Check brand/entity signals from a saved crawl`],[`seo okf export`,`Export a saved crawl as an OKF knowledge bundle`],[`seo crawl-queue`,`Run a crawl and rank the implementation queue`],[`seo crawl-reports`,`List saved crawl reports`],[`seo rules`,`List crawler rule ids`],[`seo explain --rule missing_title`,`Explain a crawler rule`],[`seo crawl-diff`,`Compare crawl changes`],[`seo index-coverage`,`Choose pages for URL Inspection`],[`seo index-watch`,`Check URL Inspection status`],[`seo indexnow submit`,`Notify IndexNow about changed URLs`],[`seo link-recover`,`Find broken search-value URLs`],[`seo links`,`Review bounded referring-link evidence`],[`seo server-logs analyze`,`Review crawler requests in an access log`],[`seo redirect-trace`,`Trace redirects`],[`seo gsc-query`,`Run a raw GSC query`],[`seo url-inspect`,`Run URL Inspection`],[`seo analytics google properties`,`List Google Analytics properties`],[`seo analytics google report`,`Run a Google Analytics report`],[`seo updates`,`List official Google ranking updates`]]},{title:`Reports and ops`,commands:[[`seo monthly-report`,`Generate a monthly narrative`],[`seo report-narrative`,`Generate a client-ready narrative`],[`seo update-postmortem`,`Analyze update winners and losers`],[`seo schedule cron`,`Print cron entries`],[`seo monitoring`,`Run and review monitoring`],[`seo change-log`,`Track SEO changes`],[`seo tests`,`Create and report local SEO tests`],[`seo content-groups`,`Manage reusable page/query groups`],[`seo pseo`,`Audit programmatic SEO templates`],[`seo auth`,`Manage Google auth`],[`seo providers bing`,`Connect and report on Bing Webmaster`],[`seo providers dataforseo`,`Connect optional search data`],[`seo providers semrush`,`Connect optional Semrush data`],[`seo providers list`,`List optional provider packages`],[`seo providers connect`,`Connect an installed provider package`],[`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 km(e){Eo(`seo v${Em.version}`,`Run SEO audits, find what needs fixing, and ship the changes with your agent.`),process.stdout.write(`
287
- `),ko(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(`
288
- `),jo(`More help`,"Use `seo help <command>` or `seo <command> --help` for command help.","Use `seo help all` for the longer command list.")}const Am=E({meta:{name:`seo`,version:Em.version,description:`Local-first SEO CLI and MCP server`},subCommands:{init:Tu,indexnow:wu,analytics:us,llms:Au,auth:hs,mcp:Pu,okf:Qu,doctor:_d,privacy:rp,telemetry:dp,reset:ip,cache:vs,logs:ju,crawl:nl,"crawl-queue":fp,"crawl-reports":_l,client:Hc,content:Wc,project:Uc,projects:Uc,providers:Jd,"change-log":kl,tests:Vl,"content-groups":Ml,"crawl-diff":Fu,diagnose:gd,"diagnose-property":nm,export:bu,"index-coverage":Ku,"index-watch":qu,"link-recover":Ju,links:Eu,"monthly-report":gf,monitoring:Gu,"report-narrative":_f,rules:vf,explain:yf,"refresh-priorities":im,"redirect-trace":Yu,schedule:Sf,"server-logs":Cf,skill:np,start:vc,setup:_c,report:rm,reports:lf,"segment-impact":yd,"striking-distance":xd,"technical-watch":am,"update-postmortem":dm,sites:ap,"gsc-query":vl,"url-inspect":yl,updates:op,"traffic-anomaly":Cl,"update-correlate":wl,"ai-referrals":$u,"agent-readiness":Go,"ai-readiness":Ho,"entity-readiness":El,"audit-page":dd,"second-page":ld,cannibal:ed,"community-intent":td,decaying:id,"quick-wins":cd,"page-opportunities":od,perf:hd,"internal-links":ad,"ctr-underperformers":nd,"query-cluster":sd,"seo-to-ai-query":ud,pseo:nf},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")}}),jm={cannibal:`cannibalisation`,crawl:`site-crawl`,"crawl-queue":`top-fixes`,decaying:`decaying-pages`,"diagnose-property":`search-performance-overview`,doctor:`setup-check`,explain:`explain-crawl-issue`,"link-recover":`link-recovery`,"monthly-report":`monthly-report`,"query-cluster":`query-clusters`,report:`search-performance-overview`,"report-narrative":`narrative-report`,rules:`crawler-rules`,"update-correlate":`update-correlation`},Mm=new Set([`agent-readiness`,`ai-readiness`,`ai-referrals`,`audit-page`,`community-intent`,`crawl-diff`,`ctr-underperformers`,`entity-readiness`,`index-coverage`,`index-watch`,`internal-links`,`link-evidence`,`page-opportunities`,`quick-wins`,`redirect-trace`,`refresh-priorities`,`second-page`,`segment-impact`,`seo-to-ai-query`,`striking-distance`,`technical-watch`,`traffic-anomaly`,`update-postmortem`]),Nm=new Set(nr().map(e=>e.id));function Pm(e){let[t,n,r]=e;if(t)return t===`reports`&&n===`run`?r&&Nm.has(r)?r:void 0:t===`content`&&n===`optimize`?`content-optimization`:t===`change-log`&&n===`measure`?`measure-change`:t===`llms`&&n===`audit`?`llms-txt-audit`:t===`links`?`link-evidence`:t===`llms`&&n===`generate`?`generate-llms-txt`:t===`okf`&&n===`export`?`okf-build`:t===`okf`&&n===`validate`?`okf-validate`:t===`monitoring`&&n===`run`?`technical-watch`:t===`perf`&&n===`audit`?`performance-audit`:t===`pseo`&&n===`audit`?`pseo-audit`:t===`tests`&&n===`report`?`measure-change`:jm[t]??(Mm.has(t)?t:void 0)}const $=process.argv.slice(2),Fm={agent:fr()},Im=$[0]===`telemetry`,Lm=$[0]===`mcp`&&$[1]===`serve`;!Im&&!Lm&&en(Fm);const Rm=await Tm(Em,{argv:$});Rm!==void 0&&process.exit(Rm),($.length===0||$.length===1&&[`help`,`--help`,`-h`].includes($[0]??``))&&(km(Dm),process.exit(0)),$[0]===`help`&&$[1]===`all`&&(km(Om),process.exit(0));const zm=$[0]===`help`&&$.length>1?[...$.slice(1),`--help`]:$;zm!==$&&(process.argv=[process.argv[0]??`node`,process.argv[1]??`seo`,...zm]);const Bm=zm.some(e=>[`--help`,`-h`].includes(e)),Vm=$.length===1&&[`--version`,`-v`].includes($[0]??``),Hm=Bm||Vm?void 0:Pm(zm);if(Bm||Vm)await Ci(Am);else{Hm&&_r(Hm,Fm);try{await Si(Am,{rawArgs:zm}),Hm&&an(Hm,Fm),[`init`,`setup`,`start`].includes(zm[0]??``)&&!zm.includes(`--dry-run`)&&kr(Fm)}catch(e){if(Hm)At(Hm,e,Fm);else if(!Im){let t=mm(zm);t&&ln(t,e,Fm)}let t=pr(e);if($.includes(`--json`))process.stdout.write(`${JSON.stringify(En(t),null,2)}\n`);else{process.stderr.write(`Error: ${t.message}\n`);let n=gm({args:zm,error:e,report:Hm});n&&process.stderr.write(`Report this bug: ${n}\n`)}process.exitCode=t.exitCode}}export{};
286
+ `),B([`Direction`,`Template`,`Pattern confidence`,`URLs`,`Clicks`,`Returned share`,`Common terms`],e.map(e=>[e.direction,e.signature,e.confidence,e.urlCount,sm(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 fm=T({meta:{name:`update-postmortem`,description:`Agent workflow for Google update winner/loser analysis`},args:{site:{type:`string`,description:`GSC property URL, for example sc-domain:example.com.`},client:{type:`string`,description:`Legacy alias for --project.`},project:{type:`string`,description:`Saved project id or name.`},...X([`days`,`recentDays`,`limit`,`includeBrand`,`refresh`],{days:{description:`Diagnosis window length in days. Defaults to 90.`},limit:{description:`Maximum winners/losers per segment. Defaults to 20.`}}),"known-change":{type:`string`,description:`Manual site-side change to treat as a confounder, for example pruning pages or blocking traffic.`},"ignore-change-log":{type:`boolean`,default:!1,description:`Do not use saved change-log entries as confounders.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=F(e),n=await H({client:k(e),site:O(e.site),options:{json:t,refresh:A(e.refresh)}}),r=await _r({site:n.site,days:j(e.days),recentDays:j(e.recent),limit:j(e.limit),brandTerms:n.client?.brandTerms,includeBrand:A(e[`include-brand`]),knownConfounders:O(e[`known-change`])?[O(e[`known-change`])??``]:void 0,includeChangeLog:!A(e[`ignore-change-log`]),refresh:A(e.refresh)});if(t){R(r);return}K({title:`Update postmortem`,target:r.site,status:`unknown`,summary:r.summary,metrics:[{label:`Attribution`,value:r.output.update.attribution.replaceAll(`-`,` `)},{label:`Confidence`,value:r.output.update.confidence},{label:`Known confounders`,value:r.output.update.confounders.length}]}),Y(`Recommended actions`,r.actions.map(e=>`${e.title} (${e.confidence}): ${e.action}`)),Y(`Postmortem findings`,r.output.insights.map(e=>e.summary)),Y(`Segment evidence warnings`,[...r.output.segments.page.warnings,...r.output.segments.query.warnings,...r.output.segments.device.warnings,...r.output.segments.country.warnings].filter((e,t,n)=>n.indexOf(e)===t)),dm(r.output.templateMovement),Y(`Update evidence`,r.output.update.evidence),um(r.output.segments),lm(`Page winners and losers`,r.output.segments.page),lm(`Query winners and losers`,r.output.segments.query),lm(`Device winners and losers`,r.output.segments.device),lm(`Country winners and losers`,r.output.segments.country),ls(`seo export update-postmortem ${n.client?`--project ${JSON.stringify(n.client.id)}`:`--site ${JSON.stringify(n.site)}`}`)}}),pm=new Set([`database`,`filesystem`,`internal`,`unknown`]);function mm(e){return e.replace(/^v/,``).split(`.`)[0]||`unknown`}function hm(e){return Er(e[0])?e[0]:void 0}function gm(e,t){return Gn(e)?`seo ${e}`:t?`seo ${t}`:`seo command`}function _m(e){let t=Bt(e.error);if(!pm.has(t.errorCategory))return;let n=hm(e.args),r=gm(e.report,n),i=[mr(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??Ir),a.searchParams.set(`runtime`,`Node ${mm(e.nodeVersion??process.version)} on ${e.processPlatform??si()} (${e.processArch??ai()})`),a.searchParams.set(`diagnostics`,i),a.toString()}function vm(e){return e.replaceAll(`\\`,`/`).toLowerCase()}function ym(e,t){return t===`win32`?`${e}.cmd`:e}function bm(e,t=process.platform){let n=vm(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:ym(`pnpm`,t),args:[`add`,`--global`,`seo@latest`]};if(n.includes(`/.bun/install/global/`))return{command:ym(`bun`,t),args:[`add`,`--global`,`seo@latest`]};if(n.includes(`/.config/yarn/global/`))return{command:ym(`yarn`,t),args:[`global`,`add`,`seo@latest`]};if(n.includes(`/node_modules/seo/`))return{command:ym(`npm`,t),args:[`install`,`--global`,`seo@latest`]}}}function xm(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 Sm(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 Cm(e){if(e)try{return qr(e)}catch{return e}}async function wm(e){let t=await Ni({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 Oi(t)?`later`:t}function Tm(e){return Si(e.command,e.args,{stdio:`inherit`}).status}async function Em(e,t={}){if(!Sm({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=Cm(t.cliPath??process.argv[1]),r=n?bm(n,t.platform??process.platform):void 0;if(!r)return;let i=(t.createNotifier??(e=>Ui({pkg:e,shouldNotifyInNpmScript:!1})))(e).update;if(!(!i||!xm(i.current,i.latest))&&await(t.prompt??wm)({command:r,update:i})!==`later`)return(t.run??Tm)(r)===0?(ki.success(`Updated seo to ${i.latest}. Run your command again.`),0):(ki.error(`The update failed. Your current version is unchanged.`),1)}const Dm={name:`seo`,version:Ir},Om=[{title:`Start here`,commands:[[`seo start`,`Connect first-party data and save a project profile`],[`seo report`,`Run the main SEO report for the default project`],[`seo report --site sc-domain:example.com`,`Run without a profile`],[`seo report --url https://example.com`,`Start with a local technical report`]]},{title:`Projects`,commands:[[`seo projects list`,`List saved project profiles`],[`seo projects add`,`Create or update a project profile`],[`seo sites`,`List Search Console properties`],[`seo doctor`,`Check local auth and config`]]},{title:`Act on a report`,commands:[[`seo refresh-priorities`,`Rank the next best SEO actions`],[`seo quick-wins`,`Find ranking 4-10 low-CTR wins`],[`seo second-page`,`Investigate URLs averaging positions 10-20`],[`seo technical-watch`,`Crawl and index-monitor a site`]]},{title:`Agent and power tools`,commands:[[`seo report --json`,`Run the main report as structured JSON`],[`seo export diagnose`,`Export report data to CSV`],[`seo mcp install`,`Install SEO tools into MCP clients`],[`seo skill list`,`Show the packaged SEO skill`],[`seo reports list`,`Discover every structured report`]]}],km=[...Om,{title:`Deeper analysis`,commands:[[`seo decaying`,`Find pages and queries losing clicks`],[`seo cannibal`,`Review queries exposed across multiple URLs`],[`seo ctr-underperformers`,`Find weak CTR by ranking position`],[`seo query-cluster`,`Cluster repeated demand themes`],[`seo page-opportunities`,`Analyze one URL for growth ideas`],[`seo content optimize`,`Build a content optimization report`],[`seo perf audit`,`Run a page performance audit`],[`seo internal-links`,`Find internal link opportunities`],[`seo community-intent`,`Find forum, review, and comparison intent`],[`seo ai-referrals`,`Find AI referral traffic in Google Analytics`],[`seo seo-to-ai-query`,`Convert searches into AI-monitor prompts`]]},{title:`Technical and data`,commands:[[`seo audit-page`,`Audit one page`],[`seo crawl --sitemap-url <url> --health`,`Run the fast sitemap gate before a full crawl`],[`seo crawl`,`Run a full crawl for page and content evidence`],[`seo agent-readiness`,`Check content-site readiness for AI agents`],[`seo ai-readiness`,`Review AI-search technical evidence`],[`seo llms audit`,`Inspect optional llms.txt presence`],[`seo entity-readiness`,`Check brand/entity signals from a saved crawl`],[`seo okf export`,`Export a saved crawl as an OKF knowledge bundle`],[`seo crawl-queue`,`Run a crawl and rank the implementation queue`],[`seo crawl-reports`,`List saved crawl reports`],[`seo rules`,`List crawler rule ids`],[`seo explain --rule missing_title`,`Explain a crawler rule`],[`seo crawl-diff`,`Compare crawl changes`],[`seo index-coverage`,`Choose pages for URL Inspection`],[`seo index-watch`,`Check URL Inspection status`],[`seo indexnow submit`,`Notify IndexNow about changed URLs`],[`seo link-recover`,`Find broken search-value URLs`],[`seo links`,`Review bounded referring-link evidence`],[`seo server-logs analyze`,`Review crawler requests in an access log`],[`seo redirect-trace`,`Trace redirects`],[`seo gsc-query`,`Run a raw GSC query`],[`seo url-inspect`,`Run URL Inspection`],[`seo analytics google properties`,`List Google Analytics properties`],[`seo analytics google report`,`Run a Google Analytics report`],[`seo updates`,`List official Google ranking updates`]]},{title:`Reports and ops`,commands:[[`seo monthly-report`,`Generate a monthly narrative`],[`seo report-narrative`,`Generate a client-ready narrative`],[`seo update-postmortem`,`Analyze update winners and losers`],[`seo schedule cron`,`Print cron entries`],[`seo monitoring`,`Run and review monitoring`],[`seo change-log`,`Track SEO changes`],[`seo tests`,`Create and report local SEO tests`],[`seo content-groups`,`Manage reusable page/query groups`],[`seo pseo`,`Audit programmatic SEO templates`],[`seo auth`,`Manage Google auth`],[`seo providers bing`,`Connect and report on Bing Webmaster`],[`seo providers dataforseo`,`Connect optional search data`],[`seo providers semrush`,`Connect optional Semrush data`],[`seo providers list`,`List optional provider packages`],[`seo providers connect`,`Connect an installed provider package`],[`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 Am(e){Do(`seo v${Dm.version}`,`Run SEO audits, find what needs fixing, and ship the changes with your agent.`),process.stdout.write(`
287
+ `),Ao(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(`
288
+ `),Mo(`More help`,"Use `seo help <command>` or `seo <command> --help` for command help.","Use `seo help all` for the longer command list.")}const jm=T({meta:{name:`seo`,version:Dm.version,description:`Local-first SEO CLI and MCP server`},subCommands:{init:Eu,indexnow:Tu,analytics:ds,llms:ju,auth:gs,mcp:Fu,okf:$u,doctor:vd,privacy:ip,telemetry:fp,reset:ap,cache:ys,logs:Mu,crawl:rl,"crawl-queue":pp,"crawl-reports":vl,client:Uc,content:Gc,project:Wc,projects:Wc,providers:Yd,"change-log":Al,tests:Hl,"content-groups":Nl,"crawl-diff":Iu,diagnose:_d,"diagnose-property":rm,export:xu,"index-coverage":qu,"index-watch":Ju,"link-recover":Yu,links:Du,"monthly-report":_f,monitoring:Ku,"report-narrative":vf,rules:yf,explain:bf,"refresh-priorities":am,"redirect-trace":Xu,schedule:Cf,"server-logs":wf,skill:rp,start:yc,setup:vc,report:im,reports:uf,"segment-impact":bd,"striking-distance":Sd,"technical-watch":om,"update-postmortem":fm,sites:op,"gsc-query":yl,"url-inspect":bl,updates:sp,"traffic-anomaly":wl,"update-correlate":Tl,"ai-referrals":ed,"agent-readiness":Ko,"ai-readiness":Uo,"entity-readiness":Dl,"audit-page":fd,"second-page":ud,cannibal:td,"community-intent":nd,decaying:ad,"quick-wins":ld,"page-opportunities":sd,perf:gd,"internal-links":od,"ctr-underperformers":rd,"query-cluster":cd,"seo-to-ai-query":dd,pseo:rf},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")}}),Mm={cannibal:`cannibalisation`,crawl:`site-crawl`,"crawl-queue":`top-fixes`,decaying:`decaying-pages`,"diagnose-property":`search-performance-overview`,doctor:`setup-check`,explain:`explain-crawl-issue`,"link-recover":`link-recovery`,"monthly-report":`monthly-report`,"query-cluster":`query-clusters`,report:`search-performance-overview`,"report-narrative":`narrative-report`,rules:`crawler-rules`,"update-correlate":`update-correlation`},Nm=new Set([`agent-readiness`,`ai-readiness`,`ai-referrals`,`audit-page`,`community-intent`,`crawl-diff`,`ctr-underperformers`,`entity-readiness`,`index-coverage`,`index-watch`,`internal-links`,`link-evidence`,`page-opportunities`,`quick-wins`,`redirect-trace`,`refresh-priorities`,`second-page`,`segment-impact`,`seo-to-ai-query`,`striking-distance`,`technical-watch`,`traffic-anomaly`,`update-postmortem`]),Pm=new Set(rr().map(e=>e.id));function Fm(e){let[t,n,r]=e;if(t)return t===`reports`&&n===`run`?r&&Pm.has(r)?r:void 0:t===`content`&&n===`optimize`?`content-optimization`:t===`change-log`&&n===`measure`?`measure-change`:t===`llms`&&n===`audit`?`llms-txt-audit`:t===`links`?`link-evidence`:t===`llms`&&n===`generate`?`generate-llms-txt`:t===`okf`&&n===`export`?`okf-build`:t===`okf`&&n===`validate`?`okf-validate`:t===`monitoring`&&n===`run`?`technical-watch`:t===`perf`&&n===`audit`?`performance-audit`:t===`pseo`&&n===`audit`?`pseo-audit`:t===`tests`&&n===`report`?`measure-change`:Mm[t]??(Nm.has(t)?t:void 0)}const $=process.argv.slice(2),Im={agent:pr()},Lm=$[0]===`telemetry`,Rm=$[0]===`mcp`&&$[1]===`serve`;!Lm&&!Rm&&tn(Im);const zm=await Em(Dm,{argv:$});zm!==void 0&&process.exit(zm),($.length===0||$.length===1&&[`help`,`--help`,`-h`].includes($[0]??``))&&(Am(Om),process.exit(0)),$[0]===`help`&&$[1]===`all`&&(Am(km),process.exit(0));const Bm=$[0]===`help`&&$.length>1?[...$.slice(1),`--help`]:$;Bm!==$&&(process.argv=[process.argv[0]??`node`,process.argv[1]??`seo`,...Bm]);const Vm=Bm.some(e=>[`--help`,`-h`].includes(e)),Hm=$.length===1&&[`--version`,`-v`].includes($[0]??``),Um=Vm||Hm?void 0:Fm(Bm);if(Vm||Hm)await wi(jm);else{Um&&vr(Um,Im);try{await Ci(jm,{rawArgs:Bm}),Um&&on(Um,Im),[`init`,`setup`,`start`].includes(Bm[0]??``)&&!Bm.includes(`--dry-run`)&&Ar(Im)}catch(e){if(Um)jt(Um,e,Im);else if(!Lm){let t=hm(Bm);t&&un(t,e,Im)}let t=mr(e);if($.includes(`--json`))process.stdout.write(`${JSON.stringify(Dn(t),null,2)}\n`);else{process.stderr.write(`Error: ${t.message}\n`);let n=_m({args:Bm,error:e,report:Um});n&&process.stderr.write(`Report this bug: ${n}\n`)}process.exitCode=t.exitCode}}export{};
289
289
  //# sourceMappingURL=cli.js.map