seo 0.12.0 → 0.12.1

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/PRIVACY.md CHANGED
@@ -13,7 +13,8 @@ keeps your Search Console or analytics data, your reports or a log of your
13
13
  requests. When you first sign up it also notes how you found it: the
14
14
  sign-in path, the agent or app, and the site that sent you there. The only
15
15
  thing kept about your use is an anonymous usage count (the event, report,
16
- agent, version and week).
16
+ agent, version and week). Accounts in the weekly site audit beta also have
17
+ their audited sites' crawl results kept, as described below.
17
18
 
18
19
  The command-line tool runs on your machine. It does not create an SEO
19
20
  account, upload reports to a hosted SEO service, or send reports or connected
@@ -38,6 +39,18 @@ Cloudflare D1 with only these things:
38
39
  - **Your linked Bing account, if you link one.** Its refresh and access
39
40
  tokens, with read-only access to Bing Webmaster Tools, and a name made
40
41
  from its sites.
42
+ - **Search Console folders you add from the Beta tab.** "Create these in
43
+ Search Console" asks Google, at that moment, for permission to add and
44
+ manage your Search Console properties. The server uses it once, to add
45
+ the folders listed on the page, and does not keep that permission or its
46
+ token. It keeps which folders it added and whether Google verified them.
47
+ - **Alerts, for accounts in the weekly-audit beta.** Which alerts you want
48
+ by email and by webhook, your webhook URL and its signing secret, and
49
+ each alert sent in the last 30 days: the site, the pages that changed
50
+ and whether it was delivered. Alert email goes to your first linked
51
+ Google account's address through PostShiba, our email provider, which
52
+ receives that address and the alert's text to send it. A webhook gets
53
+ the same alert at the URL you save.
41
54
  - **Your API keys, if you save them.** A DataForSEO login and password, an
42
55
  Ahrefs key and a TypeSafe key.
43
56
  - **Your Clicky sites, if you save them.** Each site's numeric site ID,
@@ -83,7 +96,47 @@ it. The token your MCP client keeps carries only your person id.
83
96
 
84
97
  The server keeps nothing else. It does not keep reports, Search Console
85
98
  or Google Analytics rows, lists of your sites, or the pages it checked.
86
- Request logs are turned off.
99
+ Request logs are turned off. The one exception is the weekly site audit
100
+ beta, below, and only for the sites you add to it.
101
+
102
+ ### Weekly site audit (beta)
103
+
104
+ The maintainer turns this beta on for chosen accounts; no one else has it.
105
+ For each Search Console property you add on the account page, the server
106
+ crawls the site once a week, up to 5,000 pages, as `SEOSkillBot`, and
107
+ follows the site's `robots.txt`. It keeps, per site:
108
+
109
+ - the property name, when you added it and when the next crawl is due;
110
+ - the latest crawl and the one before it: each page's URL, status,
111
+ redirect target, title, description, first heading, canonical, robots
112
+ settings, word count and the internal links on it with their link text;
113
+ - the findings worked out from them (such as broken links or missing
114
+ titles), with when each was first seen and resolved; resolved findings
115
+ are removed after 90 days;
116
+ - a one-line summary of each of the last 52 crawls (counts only);
117
+ - Google's URL Inspection result for each known URL of the site (the
118
+ sitemap's, the crawl's and the Search Console pages with clicks or
119
+ impressions, up to 50,000 from the sitemap and crawl): its indexing
120
+ reason and Google's details, when it was checked and is due again, the
121
+ reason before it last changed, and when it last dropped out of or came
122
+ back to Google's index. The server checks these in the background each day, within
123
+ Google's 2,000 checks a day per property, and `seo_page` and `seo_index`
124
+ read and add to the same results;
125
+ - for each of those Search Console pages, its clicks and impressions in the
126
+ last 28 days and its clicks in the 28 days before, replaced each day;
127
+ - how many URL Inspection checks each property used today;
128
+ - how many pages a day Google can check for the site, and the folders worth
129
+ adding as their own Search Console properties, with which of them you
130
+ added from the account page.
131
+
132
+ If you click Create these in Search Console, Google asks you for
133
+ permission to manage Search Console for that Google account. The server
134
+ uses that permission once, to add the listed folders as properties and
135
+ read back whether each is verified. It asks for no refresh token and
136
+ keeps no token afterwards.
137
+
138
+ Remove a site on the account page and all of this is deleted at once.
139
+ Delete everything deletes it for every site.
87
140
 
88
141
  - **Your data.** Search Console, Google Analytics, Clicky and provider responses are
89
142
  fetched for each request, returned to your MCP client and not kept. Pages checked live are fetched for the request and not kept.
@@ -112,7 +165,8 @@ Request logs are turned off.
112
165
 
113
166
  Sign in at [seoskill.dev/account](https://seoskill.dev/account) and choose
114
167
  **Delete everything**. It deletes your person id, linked accounts, keys,
115
- Clicky sites, linked machines, beta features and how you signed up,
168
+ Clicky sites, linked machines, beta features, audited sites with their
169
+ crawl data and how you signed up,
116
170
  and revokes the Google sign-ins. Your agents will ask you to sign in again if
117
171
  you use the hosted server later. You can also revoke SEO Skill in your
118
172
  [Google Account connections](https://myaccount.google.com/connections), which
package/dist/cli.js CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire as __seoCreateRequire } from 'node:module'; const require = __seoCreateRequire(import.meta.url);
3
- import{$ as e,$n as t,$t as n,A as r,An as i,At as a,B as o,Bn as s,Bt as c,C as l,Cn as u,Ct as d,D as ee,Dn as te,Dt as f,E as ne,En as p,Et as m,F as re,Fn as ie,Ft as ae,G as oe,Gn as se,Gt as ce,H as le,Hn as ue,Ht as de,I as fe,In as pe,It as me,J as he,Jn as ge,Jt as _e,K as ve,Kn as ye,Kt as be,L as xe,Ln as Se,Lt as Ce,M as we,Mn as Te,Mt as Ee,N as De,Nn as Oe,Nt as ke,O as Ae,On as je,Ot as Me,P as Ne,Pn as Pe,Pt as Fe,Q as Ie,Qn as Le,Qt as Re,R as ze,Rn as Be,Rt as Ve,S as He,Sn as Ue,St as We,T as Ge,Tn as Ke,Tt as qe,U as Je,Un as Ye,Ut as Xe,V as Ze,Vn as Qe,Vt as $e,W as et,Wn as tt,Wt as nt,X as rt,Xn as h,Xt as it,Y as at,Yn as ot,Yt as st,Z as ct,Zn as lt,Zt as ut,_ as dt,_n as ft,_t as pt,a as mt,an as ht,at as gt,b as _t,bn as vt,bt as yt,c as bt,cn as xt,ct as St,d as Ct,dn as wt,dt as Tt,en as Et,er as Dt,et as Ot,f as kt,fn as At,ft as jt,g as Mt,gn as Nt,gt as Pt,h as Ft,hn as It,ht as Lt,i as Rt,in as zt,it as Bt,j as Vt,jn as Ht,jt as Ut,k as Wt,kn as Gt,kt as Kt,l as qt,ln as Jt,lt as Yt,m as Xt,mn as Zt,mt as Qt,nn as $t,nt as en,o as tn,on as nn,ot as rn,p as an,pn as on,pt as sn,q as cn,qn as ln,qt as un,rn as dn,rt as fn,sn as pn,st as mn,t as hn,tn as gn,tt as _n,u as vn,un as yn,ut as bn,v as xn,vn as Sn,vt as Cn,w as wn,wn as Tn,wt as En,x as Dn,xn as On,xt as kn,y as An,yn as jn,yt as Mn,z as Nn,zn as Pn,zt as Fn}from"./dist-Ban8uP1U.js";import{arch as In,homedir as Ln,platform as Rn}from"node:os";import{basename as zn,dirname as Bn,join as g,posix as Vn,resolve as Hn,win32 as Un}from"node:path";import{constants as Wn,copyFileSync as Gn,createWriteStream as Kn,existsSync as _,mkdirSync as qn,readFileSync as Jn,readdirSync as Yn,realpathSync as Xn,renameSync as Zn,statSync as Qn,writeFileSync as $n}from"node:fs";import{fileURLToPath as er}from"node:url";import{mkdir as tr,readFile as nr,writeFile as rr}from"node:fs/promises";import{isDeepStrictEqual as ir,stripVTControlCharacters as ar}from"node:util";import{spawn as or,spawnSync as sr}from"node:child_process";import{defineCommand as v,runCommand as cr,runMain as lr}from"citty";import{cancel as ur,confirm as dr,intro as y,isCancel as fr,log as pr,multiselect as mr,note as b,outro as x,password as hr,select as gr,text as _r}from"@clack/prompts";import vr from"picocolors";import yr from"string-width";import br from"cli-table3";import{finished as xr}from"node:stream/promises";import{applyEdits as Sr,modify as Cr,parse as wr,printParseErrorCode as Tr}from"jsonc-parser";import Er from"update-notifier";const S={summary:`workflow.site.generated_at.summary.attribution.confidence.classification.updates_matched.known_confounders.page_segment_status.page_segment_eligible_rows.page_segment_returned_rows.page_segment_limited_rows.page_segment_result_limit.page_segment_source_row_limit.page_segment_source_possibly_truncated.page_segment_warnings.query_segment_status.query_segment_eligible_rows.query_segment_returned_rows.query_segment_limited_rows.query_segment_result_limit.query_segment_source_row_limit.query_segment_source_possibly_truncated.query_segment_warnings.device_segment_status.device_segment_eligible_rows.device_segment_returned_rows.device_segment_limited_rows.device_segment_result_limit.device_segment_source_row_limit.device_segment_source_possibly_truncated.device_segment_warnings.country_segment_status.country_segment_eligible_rows.country_segment_returned_rows.country_segment_limited_rows.country_segment_result_limit.country_segment_source_row_limit.country_segment_source_possibly_truncated.country_segment_warnings`.split(`.`),steps:[`rank`,`tool`,`status`,`summary`],actions:[`rank`,`title`,`confidence`,`action`],findings:[`dimension`,`data_status`,`eligible_rows`,`returned_rows`,`limited_rows`,`result_limit`,`unmatched_rows`,`summary`,`winner_key`,`winner_click_delta`,`winner_impression_delta`,`winner_position_delta`,`loser_key`,`loser_click_delta`,`loser_impression_delta`,`loser_position_delta`],templates:[`rank`,`direction`,`template`,`confidence`,`url_count`,`click_delta`,`impression_delta`,`movement_share`,`movement_share_scope`,`confidence_scope`,`eligible_rows`,`returned_rows`,`limited_rows`,`result_limit`,`source_possibly_truncated`,`common_terms`,`sample_urls`,`summary`],evidence:[`rank`,`evidence`],confounders:[`rank`,`source`,`title`,`date`,`scope`,`target`,`description`],segment:[`rank`,`dimension`,`data_status`,`evidence_scope`,`key`,`before_clicks`,`after_clicks`,`click_delta`,`before_impressions`,`after_impressions`,`impression_delta`,`before_position`,`after_position`,`position_delta`],unmatchedSegment:[`rank`,`dimension`,`data_status`,`key`,`retained_in`,`clicks`,`impressions`,`position`,`reason`]};function Dr(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 Or(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 kr(e){return[{filename:`postmortem-summary.csv`,headers:[...S.summary],rows:[{workflow:e.workflow,site:e.site,generated_at:e.generatedAt,summary:e.summary,attribution:e.output.update.attribution,confidence:e.output.update.confidence,classification:e.output.update.classification,updates_matched:e.output.update.overlappingUpdates.length,known_confounders:e.output.update.confounders.length,page_segment_status:e.output.segments.page.dataStatus,page_segment_eligible_rows:e.output.segments.page.coverage.eligibleRows,page_segment_returned_rows:e.output.segments.page.coverage.returnedRows,page_segment_limited_rows:e.output.segments.page.coverage.limitedRows,page_segment_result_limit:e.output.segments.page.coverage.resultLimit,page_segment_source_row_limit:e.output.segments.page.coverage.sourceRowLimit,page_segment_source_possibly_truncated:e.output.segments.page.coverage.sourcePossiblyTruncated,page_segment_warnings:e.output.segments.page.warnings.join(`; `),query_segment_status:e.output.segments.query.dataStatus,query_segment_eligible_rows:e.output.segments.query.coverage.eligibleRows,query_segment_returned_rows:e.output.segments.query.coverage.returnedRows,query_segment_limited_rows:e.output.segments.query.coverage.limitedRows,query_segment_result_limit:e.output.segments.query.coverage.resultLimit,query_segment_source_row_limit:e.output.segments.query.coverage.sourceRowLimit,query_segment_source_possibly_truncated:e.output.segments.query.coverage.sourcePossiblyTruncated,query_segment_warnings:e.output.segments.query.warnings.join(`; `),device_segment_status:e.output.segments.device.dataStatus,device_segment_eligible_rows:e.output.segments.device.coverage.eligibleRows,device_segment_returned_rows:e.output.segments.device.coverage.returnedRows,device_segment_limited_rows:e.output.segments.device.coverage.limitedRows,device_segment_result_limit:e.output.segments.device.coverage.resultLimit,device_segment_source_row_limit:e.output.segments.device.coverage.sourceRowLimit,device_segment_source_possibly_truncated:e.output.segments.device.coverage.sourcePossiblyTruncated,device_segment_warnings:e.output.segments.device.warnings.join(`; `),country_segment_status:e.output.segments.country.dataStatus,country_segment_eligible_rows:e.output.segments.country.coverage.eligibleRows,country_segment_returned_rows:e.output.segments.country.coverage.returnedRows,country_segment_limited_rows:e.output.segments.country.coverage.limitedRows,country_segment_result_limit:e.output.segments.country.coverage.resultLimit,country_segment_source_row_limit:e.output.segments.country.coverage.sourceRowLimit,country_segment_source_possibly_truncated:e.output.segments.country.coverage.sourcePossiblyTruncated,country_segment_warnings:e.output.segments.country.warnings.join(`; `)}]},{filename:`workflow-steps.csv`,headers:[...S.steps],rows:e.steps.map((e,t)=>({rank:t+1,tool:e.tool,status:e.status,summary:e.summary}))},{filename:`workflow-actions.csv`,headers:[...S.actions],rows:e.actions.map((e,t)=>({rank:t+1,title:e.title,confidence:e.confidence,action:e.action}))},{filename:`postmortem-findings.csv`,headers:[...S.findings],rows:e.output.insights.map(e=>({dimension:e.dimension,data_status:e.dataStatus,eligible_rows:e.coverage.eligibleRows,returned_rows:e.coverage.returnedRows,limited_rows:e.coverage.limitedRows,result_limit:e.coverage.resultLimit,unmatched_rows:e.unmatchedRows,summary:e.summary,winner_key:e.winner?.key,winner_click_delta:e.winner?.clickDelta,winner_impression_delta:e.winner?.impressionDelta,winner_position_delta:e.winner?.positionDelta,loser_key:e.loser?.key,loser_click_delta:e.loser?.clickDelta,loser_impression_delta:e.loser?.impressionDelta,loser_position_delta:e.loser?.positionDelta}))},{filename:`postmortem-template-movement.csv`,headers:[...S.templates],rows:e.output.templateMovement.map((e,t)=>({rank:t+1,direction:e.direction,template:e.signature,confidence:e.confidence,url_count:e.urlCount,click_delta:e.clickDelta,impression_delta:e.impressionDelta,movement_share:e.movementShare,movement_share_scope:e.movementShareScope,confidence_scope:e.confidenceScope,eligible_rows:e.segmentCoverage.eligibleRows,returned_rows:e.segmentCoverage.returnedRows,limited_rows:e.segmentCoverage.limitedRows,result_limit:e.segmentCoverage.resultLimit,source_possibly_truncated:e.segmentCoverage.sourcePossiblyTruncated,common_terms:e.commonTerms.join(`; `),sample_urls:e.sampleUrls.join(`; `),summary:e.summary}))},{filename:`postmortem-update-evidence.csv`,headers:[...S.evidence],rows:e.output.update.evidence.map((e,t)=>({rank:t+1,evidence:e}))},{filename:`postmortem-confounders.csv`,headers:[...S.confounders],rows:e.output.update.confounders.map((e,t)=>({rank:t+1,source:e.source,title:e.title,date:e.date,scope:e.scope,target:e.target,description:e.description}))},...[`page`,`query`,`device`,`country`].map(t=>({filename:`postmortem-segment-${t}.csv`,headers:[...S.segment],rows:Dr(e,t)})),...[`page`,`query`,`device`,`country`].map(t=>({filename:`postmortem-segment-${t}-unmatched.csv`,headers:[...S.unmatchedSegment],rows:Or(e,t)}))]}function Ar(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 jr(e,t){return{type:`boolean`,default:!0,description:e,negativeDescription:t}}function C(e){return typeof e==`string`?e:void 0}function w(e){return typeof e==`boolean`?e:void 0}function Mr(e){let t=C(e.rendering);if(t!==void 0&&t!==`auto`&&t!==`on`&&t!==`off`)throw new h(`INVALID_INPUT`,`--rendering must be auto, on, or off.`);if(w(e.js)===!0&&t&&t!==`on`)throw new h(`INVALID_INPUT`,`Use either --js or --rendering on, not both with different modes.`);return t??(w(e.js)?`on`:`auto`)}function Nr(e,t){let n=w(e[t]);if(n===!1)return!0;if(n===!0)return!1;let r=w(e[Pr(t)]);if(r===!1)return!0;if(r===!0)return!1;if(w(e[`no-${t}`])===!0||w(e[`no${Fr(t)}`])===!0)return!0}function Pr(e){return e.replace(/-([a-z])/g,(e,t)=>t.toUpperCase())}function Fr(e){let t=Pr(e);return t?`${t[0]?.toUpperCase()}${t.slice(1)}`:``}function T(e){if(typeof e==`number`)return e;if(typeof e!=`string`||!e.trim())return;let t=Number(e);return Number.isFinite(t)?t:void 0}function E(e,t){if(e===void 0||e===``)return;let n=T(e);if(n===void 0)throw new h(`INVALID_INPUT`,`${t} must be a number.`);return n}function D(e){if(typeof e!=`string`)return;let t=e.split(`,`).map(e=>e.trim()).filter(Boolean);return t.length?t:void 0}function Ir(e){return D(e)??[]}function Lr(e){let t={concurrency:T(e[`fetch-concurrency`]),intervalCap:T(e[`fetch-interval-cap`]),intervalMs:T(e[`fetch-interval-ms`])};return Object.values(t).some(e=>e!==void 0)?t:void 0}function O(e){return e.json===!0}async function Rr(e,t){let n=C(e),r=C(t);if(n&&r)throw Error(`Use either --body or --body-file, not both.`);if(!n&&!r)return;let i=n??await nr(r??``,`utf8`),a=JSON.parse(i);if(!a||typeof a!=`object`||Array.isArray(a))throw Error(`JSON body must be an object.`);return a}function k(e){if(e)return e;throw new h(`INVALID_INPUT`,"No site given. Pass --site with a domain, URL or sc-domain: property. Run `seo sites` to list your Search Console properties.")}async function zr(e,t={}){let n=k(e),r=ye((await dn(t.refresh,t.account)).map(e=>e.siteUrl),n);if(!r)throw new h(`PROPERTY_NOT_FOUND`,`This Google login has no Search Console property for ${n}. Run \`seo sites\` to list the properties it can read.`);return r}function Br(e){if(e)return e;throw new h(`INVALID_INPUT`,"No Google Analytics property given. Pass --property with the numeric property id. Run `seo analytics google properties` to list them.")}function Vr(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 Hr(e={}){let t=e.env??process.env,n=Vr(e.columns)??Vr(e.streamColumns)??Vr(t.COLUMNS)??80;return Math.min(200,Math.max(40,n))}function A(e={}){let t=e.env??process.env,n=e.isTTY??!!process.stdout.isTTY,r=e.color??!!(n&&t.NO_COLOR===void 0);return{columns:Hr({...e,env:t,streamColumns:e.streamColumns??process.stdout.columns}),colors:vr.createColors(r),hasColor:r,isInteractive:n&&!t.CI}}function j(e){return yr(ar(e))}function Ur(e,t){let n=[],r=``;for(let i of e)r&&j(`${r}${i}`)>t?(n.push(r),r=i):r+=i;return r&&n.push(r),n}function Wr(e,t){if(!e)return[``];let n=e.trim().split(/\s+/u),r=[],i=``;for(let e of n){let n=j(e)>t?Ur(e,t):[e];for(let e of n)i?j(`${i} ${e}`)<=t?i+=` ${e}`:(r.push(i),i=e):i=e}return i&&r.push(i),r.length>0?r:[``]}function M(e,t){let n=Math.max(1,t);return e.replaceAll(`\r
3
+ import{$ as e,$n as t,$t as n,A as r,An as i,At as a,B as o,Bn as s,Bt as c,C as l,Cn as u,Ct as d,D as ee,Dn as te,Dt as f,E as ne,En as p,Et as m,F as re,Fn as ie,Ft as ae,G as oe,Gn as se,Gt as ce,H as le,Hn as ue,Ht as de,I as fe,In as pe,It as me,J as he,Jn as ge,Jt as _e,K as ve,Kn as ye,Kt as be,L as xe,Ln as Se,Lt as Ce,M as we,Mn as Te,Mt as Ee,N as De,Nn as Oe,Nt as ke,O as Ae,On as je,Ot as Me,P as Ne,Pn as Pe,Pt as Fe,Q as Ie,Qn as Le,Qt as Re,R as ze,Rn as Be,Rt as Ve,S as He,Sn as Ue,St as We,T as Ge,Tn as Ke,Tt as qe,U as Je,Un as Ye,Ut as Xe,V as Ze,Vn as Qe,Vt as $e,W as et,Wn as tt,Wt as nt,X as rt,Xn as h,Xt as it,Y as at,Yn as ot,Yt as st,Z as ct,Zn as lt,Zt as ut,_ as dt,_n as ft,_t as pt,a as mt,an as ht,at as gt,b as _t,bn as vt,bt as yt,c as bt,cn as xt,ct as St,d as Ct,dn as wt,dt as Tt,en as Et,er as Dt,et as Ot,f as kt,fn as At,ft as jt,g as Mt,gn as Nt,gt as Pt,h as Ft,hn as It,ht as Lt,i as Rt,in as zt,it as Bt,j as Vt,jn as Ht,jt as Ut,k as Wt,kn as Gt,kt as Kt,l as qt,ln as Jt,lt as Yt,m as Xt,mn as Zt,mt as Qt,nn as $t,nt as en,o as tn,on as nn,ot as rn,p as an,pn as on,pt as sn,q as cn,qn as ln,qt as un,rn as dn,rt as fn,sn as pn,st as mn,t as hn,tn as gn,tt as _n,u as vn,un as yn,ut as bn,v as xn,vn as Sn,vt as Cn,w as wn,wn as Tn,wt as En,x as Dn,xn as On,xt as kn,y as An,yn as jn,yt as Mn,z as Nn,zn as Pn,zt as Fn}from"./dist-U5Io-lSG.js";import{arch as In,homedir as Ln,platform as Rn}from"node:os";import{basename as zn,dirname as Bn,join as g,posix as Vn,resolve as Hn,win32 as Un}from"node:path";import{constants as Wn,copyFileSync as Gn,createWriteStream as Kn,existsSync as _,mkdirSync as qn,readFileSync as Jn,readdirSync as Yn,realpathSync as Xn,renameSync as Zn,statSync as Qn,writeFileSync as $n}from"node:fs";import{fileURLToPath as er}from"node:url";import{mkdir as tr,readFile as nr,writeFile as rr}from"node:fs/promises";import{isDeepStrictEqual as ir,stripVTControlCharacters as ar}from"node:util";import{spawn as or,spawnSync as sr}from"node:child_process";import{defineCommand as v,runCommand as cr,runMain as lr}from"citty";import{cancel as ur,confirm as dr,intro as y,isCancel as fr,log as pr,multiselect as mr,note as b,outro as x,password as hr,select as gr,text as _r}from"@clack/prompts";import vr from"picocolors";import yr from"string-width";import br from"cli-table3";import{finished as xr}from"node:stream/promises";import{applyEdits as Sr,modify as Cr,parse as wr,printParseErrorCode as Tr}from"jsonc-parser";import Er from"update-notifier";const S={summary:`workflow.site.generated_at.summary.attribution.confidence.classification.updates_matched.known_confounders.page_segment_status.page_segment_eligible_rows.page_segment_returned_rows.page_segment_limited_rows.page_segment_result_limit.page_segment_source_row_limit.page_segment_source_possibly_truncated.page_segment_warnings.query_segment_status.query_segment_eligible_rows.query_segment_returned_rows.query_segment_limited_rows.query_segment_result_limit.query_segment_source_row_limit.query_segment_source_possibly_truncated.query_segment_warnings.device_segment_status.device_segment_eligible_rows.device_segment_returned_rows.device_segment_limited_rows.device_segment_result_limit.device_segment_source_row_limit.device_segment_source_possibly_truncated.device_segment_warnings.country_segment_status.country_segment_eligible_rows.country_segment_returned_rows.country_segment_limited_rows.country_segment_result_limit.country_segment_source_row_limit.country_segment_source_possibly_truncated.country_segment_warnings`.split(`.`),steps:[`rank`,`tool`,`status`,`summary`],actions:[`rank`,`title`,`confidence`,`action`],findings:[`dimension`,`data_status`,`eligible_rows`,`returned_rows`,`limited_rows`,`result_limit`,`unmatched_rows`,`summary`,`winner_key`,`winner_click_delta`,`winner_impression_delta`,`winner_position_delta`,`loser_key`,`loser_click_delta`,`loser_impression_delta`,`loser_position_delta`],templates:[`rank`,`direction`,`template`,`confidence`,`url_count`,`click_delta`,`impression_delta`,`movement_share`,`movement_share_scope`,`confidence_scope`,`eligible_rows`,`returned_rows`,`limited_rows`,`result_limit`,`source_possibly_truncated`,`common_terms`,`sample_urls`,`summary`],evidence:[`rank`,`evidence`],confounders:[`rank`,`source`,`title`,`date`,`scope`,`target`,`description`],segment:[`rank`,`dimension`,`data_status`,`evidence_scope`,`key`,`before_clicks`,`after_clicks`,`click_delta`,`before_impressions`,`after_impressions`,`impression_delta`,`before_position`,`after_position`,`position_delta`],unmatchedSegment:[`rank`,`dimension`,`data_status`,`key`,`retained_in`,`clicks`,`impressions`,`position`,`reason`]};function Dr(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 Or(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 kr(e){return[{filename:`postmortem-summary.csv`,headers:[...S.summary],rows:[{workflow:e.workflow,site:e.site,generated_at:e.generatedAt,summary:e.summary,attribution:e.output.update.attribution,confidence:e.output.update.confidence,classification:e.output.update.classification,updates_matched:e.output.update.overlappingUpdates.length,known_confounders:e.output.update.confounders.length,page_segment_status:e.output.segments.page.dataStatus,page_segment_eligible_rows:e.output.segments.page.coverage.eligibleRows,page_segment_returned_rows:e.output.segments.page.coverage.returnedRows,page_segment_limited_rows:e.output.segments.page.coverage.limitedRows,page_segment_result_limit:e.output.segments.page.coverage.resultLimit,page_segment_source_row_limit:e.output.segments.page.coverage.sourceRowLimit,page_segment_source_possibly_truncated:e.output.segments.page.coverage.sourcePossiblyTruncated,page_segment_warnings:e.output.segments.page.warnings.join(`; `),query_segment_status:e.output.segments.query.dataStatus,query_segment_eligible_rows:e.output.segments.query.coverage.eligibleRows,query_segment_returned_rows:e.output.segments.query.coverage.returnedRows,query_segment_limited_rows:e.output.segments.query.coverage.limitedRows,query_segment_result_limit:e.output.segments.query.coverage.resultLimit,query_segment_source_row_limit:e.output.segments.query.coverage.sourceRowLimit,query_segment_source_possibly_truncated:e.output.segments.query.coverage.sourcePossiblyTruncated,query_segment_warnings:e.output.segments.query.warnings.join(`; `),device_segment_status:e.output.segments.device.dataStatus,device_segment_eligible_rows:e.output.segments.device.coverage.eligibleRows,device_segment_returned_rows:e.output.segments.device.coverage.returnedRows,device_segment_limited_rows:e.output.segments.device.coverage.limitedRows,device_segment_result_limit:e.output.segments.device.coverage.resultLimit,device_segment_source_row_limit:e.output.segments.device.coverage.sourceRowLimit,device_segment_source_possibly_truncated:e.output.segments.device.coverage.sourcePossiblyTruncated,device_segment_warnings:e.output.segments.device.warnings.join(`; `),country_segment_status:e.output.segments.country.dataStatus,country_segment_eligible_rows:e.output.segments.country.coverage.eligibleRows,country_segment_returned_rows:e.output.segments.country.coverage.returnedRows,country_segment_limited_rows:e.output.segments.country.coverage.limitedRows,country_segment_result_limit:e.output.segments.country.coverage.resultLimit,country_segment_source_row_limit:e.output.segments.country.coverage.sourceRowLimit,country_segment_source_possibly_truncated:e.output.segments.country.coverage.sourcePossiblyTruncated,country_segment_warnings:e.output.segments.country.warnings.join(`; `)}]},{filename:`workflow-steps.csv`,headers:[...S.steps],rows:e.steps.map((e,t)=>({rank:t+1,tool:e.tool,status:e.status,summary:e.summary}))},{filename:`workflow-actions.csv`,headers:[...S.actions],rows:e.actions.map((e,t)=>({rank:t+1,title:e.title,confidence:e.confidence,action:e.action}))},{filename:`postmortem-findings.csv`,headers:[...S.findings],rows:e.output.insights.map(e=>({dimension:e.dimension,data_status:e.dataStatus,eligible_rows:e.coverage.eligibleRows,returned_rows:e.coverage.returnedRows,limited_rows:e.coverage.limitedRows,result_limit:e.coverage.resultLimit,unmatched_rows:e.unmatchedRows,summary:e.summary,winner_key:e.winner?.key,winner_click_delta:e.winner?.clickDelta,winner_impression_delta:e.winner?.impressionDelta,winner_position_delta:e.winner?.positionDelta,loser_key:e.loser?.key,loser_click_delta:e.loser?.clickDelta,loser_impression_delta:e.loser?.impressionDelta,loser_position_delta:e.loser?.positionDelta}))},{filename:`postmortem-template-movement.csv`,headers:[...S.templates],rows:e.output.templateMovement.map((e,t)=>({rank:t+1,direction:e.direction,template:e.signature,confidence:e.confidence,url_count:e.urlCount,click_delta:e.clickDelta,impression_delta:e.impressionDelta,movement_share:e.movementShare,movement_share_scope:e.movementShareScope,confidence_scope:e.confidenceScope,eligible_rows:e.segmentCoverage.eligibleRows,returned_rows:e.segmentCoverage.returnedRows,limited_rows:e.segmentCoverage.limitedRows,result_limit:e.segmentCoverage.resultLimit,source_possibly_truncated:e.segmentCoverage.sourcePossiblyTruncated,common_terms:e.commonTerms.join(`; `),sample_urls:e.sampleUrls.join(`; `),summary:e.summary}))},{filename:`postmortem-update-evidence.csv`,headers:[...S.evidence],rows:e.output.update.evidence.map((e,t)=>({rank:t+1,evidence:e}))},{filename:`postmortem-confounders.csv`,headers:[...S.confounders],rows:e.output.update.confounders.map((e,t)=>({rank:t+1,source:e.source,title:e.title,date:e.date,scope:e.scope,target:e.target,description:e.description}))},...[`page`,`query`,`device`,`country`].map(t=>({filename:`postmortem-segment-${t}.csv`,headers:[...S.segment],rows:Dr(e,t)})),...[`page`,`query`,`device`,`country`].map(t=>({filename:`postmortem-segment-${t}-unmatched.csv`,headers:[...S.unmatchedSegment],rows:Or(e,t)}))]}function Ar(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 jr(e,t){return{type:`boolean`,default:!0,description:e,negativeDescription:t}}function C(e){return typeof e==`string`?e:void 0}function w(e){return typeof e==`boolean`?e:void 0}function Mr(e){let t=C(e.rendering);if(t!==void 0&&t!==`auto`&&t!==`on`&&t!==`off`)throw new h(`INVALID_INPUT`,`--rendering must be auto, on, or off.`);if(w(e.js)===!0&&t&&t!==`on`)throw new h(`INVALID_INPUT`,`Use either --js or --rendering on, not both with different modes.`);return t??(w(e.js)?`on`:`auto`)}function Nr(e,t){let n=w(e[t]);if(n===!1)return!0;if(n===!0)return!1;let r=w(e[Pr(t)]);if(r===!1)return!0;if(r===!0)return!1;if(w(e[`no-${t}`])===!0||w(e[`no${Fr(t)}`])===!0)return!0}function Pr(e){return e.replace(/-([a-z])/g,(e,t)=>t.toUpperCase())}function Fr(e){let t=Pr(e);return t?`${t[0]?.toUpperCase()}${t.slice(1)}`:``}function T(e){if(typeof e==`number`)return e;if(typeof e!=`string`||!e.trim())return;let t=Number(e);return Number.isFinite(t)?t:void 0}function E(e,t){if(e===void 0||e===``)return;let n=T(e);if(n===void 0)throw new h(`INVALID_INPUT`,`${t} must be a number.`);return n}function D(e){if(typeof e!=`string`)return;let t=e.split(`,`).map(e=>e.trim()).filter(Boolean);return t.length?t:void 0}function Ir(e){return D(e)??[]}function Lr(e){let t={concurrency:T(e[`fetch-concurrency`]),intervalCap:T(e[`fetch-interval-cap`]),intervalMs:T(e[`fetch-interval-ms`])};return Object.values(t).some(e=>e!==void 0)?t:void 0}function O(e){return e.json===!0}async function Rr(e,t){let n=C(e),r=C(t);if(n&&r)throw Error(`Use either --body or --body-file, not both.`);if(!n&&!r)return;let i=n??await nr(r??``,`utf8`),a=JSON.parse(i);if(!a||typeof a!=`object`||Array.isArray(a))throw Error(`JSON body must be an object.`);return a}function k(e){if(e)return e;throw new h(`INVALID_INPUT`,"No site given. Pass --site with a domain, URL or sc-domain: property. Run `seo sites` to list your Search Console properties.")}async function zr(e,t={}){let n=k(e),r=ye((await dn(t.refresh,t.account)).map(e=>e.siteUrl),n);if(!r)throw new h(`PROPERTY_NOT_FOUND`,`This Google login has no Search Console property for ${n}. Run \`seo sites\` to list the properties it can read.`);return r}function Br(e){if(e)return e;throw new h(`INVALID_INPUT`,"No Google Analytics property given. Pass --property with the numeric property id. Run `seo analytics google properties` to list them.")}function Vr(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 Hr(e={}){let t=e.env??process.env,n=Vr(e.columns)??Vr(e.streamColumns)??Vr(t.COLUMNS)??80;return Math.min(200,Math.max(40,n))}function A(e={}){let t=e.env??process.env,n=e.isTTY??!!process.stdout.isTTY,r=e.color??!!(n&&t.NO_COLOR===void 0);return{columns:Hr({...e,env:t,streamColumns:e.streamColumns??process.stdout.columns}),colors:vr.createColors(r),hasColor:r,isInteractive:n&&!t.CI}}function j(e){return yr(ar(e))}function Ur(e,t){let n=[],r=``;for(let i of e)r&&j(`${r}${i}`)>t?(n.push(r),r=i):r+=i;return r&&n.push(r),n}function Wr(e,t){if(!e)return[``];let n=e.trim().split(/\s+/u),r=[],i=``;for(let e of n){let n=j(e)>t?Ur(e,t):[e];for(let e of n)i?j(`${i} ${e}`)<=t?i+=` ${e}`:(r.push(i),i=e):i=e}return i&&r.push(i),r.length>0?r:[``]}function M(e,t){let n=Math.max(1,t);return e.replaceAll(`\r
4
4
  `,`
5
5
  `).split(`
6
6
  `).flatMap(e=>Wr(e,n))}function Gr(e,t){if(j(e)<=t)return e;let n=Math.max(1,t-j(`...`)),r=``;for(let t of e){if(j(`${r}${t}`)>n)break;r+=t}return`${r}...`}const Kr={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 qr(e){return e===void 0?``:String(e)}function Jr(e,t){return e.map((e,n)=>{let r=t.map(e=>qr(e[n]));return Math.min(42,Math.max(j(e),...r.map(j),4))})}function Yr(e,t,n){return e.length===0||e.length>4||!e.map((e,n)=>t.every(e=>/^-?(?:\d+(?:\.\d+)?|\d+(?:\.\d+)?%)$/u.test(qr(e[n]).trim()))).some(Boolean)?!1:Jr(e,t).reduce((e,t)=>e+t+2,0)<=n.columns}function Xr(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 Zr(e,t,n){let r=Jr(e,t).map(e=>e+2),i=new br({chars:Kr,colWidths:r,head:e.map(e=>n.colors.bold(e)),style:{border:[],head:[],"padding-left":0,"padding-right":2},wordWrap:!1});for(let a of t)i.push(e.map((e,t)=>M(Xr(e,qr(a[t]),n),Math.max(1,(r[t]??3)-2)).join(`
@@ -57,7 +57,7 @@ Updates
57
57
  `),`Setup will`),b(Uo.join(`
58
58
  `),`Then run`),x(`Dry run complete. Nothing was changed.`));return}tt();let n=N({json:t}),r=await Lo({interactive:n})??await Bo({interactive:n}),i=n?await Ho():[],a=n?await Vo():{status:`skipped`};if(t){P({auth:r,mcp:i,skill:a,next:Uo});return}F([[`Sign-in`,Go(r)],[`MCP server`,qo(i)],[`SEO skill`,Ko(a)]]);let o=i.filter(e=>e.error);o.length&&b(o.map(e=>`${e.client}: ${e.error}`).join(`
59
59
  `),`MCP setup skipped`),a.error&&b(a.error,`SEO skill install failed`),b([`seo sites list your Search Console properties`,`seo report --site <site> run the main report for one of them`].join(`
60
- `),`Try next`),x(`Setup complete.`),Jo()}const Xo=v({meta:{name:`setup`,description:`Sign in with Google, then add the MCP server and skill`},args:{"dry-run":{type:`boolean`,default:!1,description:`Show what setup does without changing anything.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON. Never prompts.`}},run:async({args:e})=>Yo(e)}),Zo=v({...Xo,meta:{name:`start`,description:`Start here: sign in with Google, then add the MCP server and skill to your agents`}}),Qo=v({...Zo,meta:{name:`init`,description:"Legacy alias for `seo start`."}}),$o=v({meta:{name:`logs`,description:`Manage local log retention`},subCommands:{prune:v({meta:{name:`prune`,description:`Rotate large logs and remove old local logs`},args:{quiet:{type:`boolean`,default:!1,description:`Do not print a result.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=He();if(O(e)){P(t);return}e.quiet||process.stdout.write(`Rotated ${t.rotated} and removed ${t.removed} log files. ${Ai(t.sizeBytes)} retained.\n`)}})}}),es=[{client:`claude-desktop`,flag:`claude-desktop`},{client:`claude-code`,flag:`claude-code`},{client:`codex`,flag:`codex`},{client:`cursor`,flag:`cursor`}];function ts(e,t){return w(e.all)?t:es.filter(({flag:t})=>w(e[t])).map(({client:e})=>e)}const ns=v({meta:{name:`mcp`,description:`MCP server helpers`},subCommands:{serve:v({meta:{name:`serve`,description:`Run the SEO MCP server`},args:{test:{type:`boolean`,default:!1,description:`Start in test mode`}},run:async({args:e})=>{let{startMcpServer:t}=await import(`./dist-B1bivk1S.js`);await t({test:w(e.test)})}}),install:v({meta:{name:`install`,description:`Install or remove SEO MCP config for local clients`},args:{uninstall:{type:`boolean`,default:!1,description:`Remove the SEO MCP config instead of adding it`},reinstall:{type:`boolean`,default:!1,description:`Rewrite an existing managed SEO MCP config`},"claude-desktop":{type:`boolean`,default:!1,description:`Update Claude Desktop`},"claude-code":{type:`boolean`,default:!1,description:`Update Claude Code`},codex:{type:`boolean`,default:!1,description:`Update Codex CLI, app, and IDE extension`},cursor:{type:`boolean`,default:!1,description:`Update Cursor`},all:{type:`boolean`,default:!1,description:`Update every detected MCP client`},json:{type:`boolean`,default:!1,description:`Print structured JSON output`}},run:async({args:e})=>{if(w(e.uninstall)&&w(e.reinstall))throw new h(`INVALID_INPUT`,`Use either --uninstall or --reinstall, not both.`);let t=$a(),n=no({targets:t}),r=O(e),i=ts(e,n.map(e=>e.client));if(i.length===0){if(!N({json:r}))throw new h(`INVALID_INPUT`,`Choose an MCP client with --claude-desktop, --claude-code, --codex, --cursor, or --all.`);let a=n.length>0?n:t;i=L(await mr({message:e.uninstall?`Remove seo from which MCP clients?`:`Install seo into which MCP clients?`,options:a.map(e=>({value:e.client,label:e.label,hint:e.path})),initialValues:n.map(e=>e.client),required:!0}))}let a=i.filter(e=>!t.some(t=>t.client===e));if(a.length>0)throw new h(`INVALID_INPUT`,`MCP setup is not supported for ${a.join(`, `)} on ${process.platform}.`);let o=t.filter(e=>i.includes(e.client)).map(t=>w(e.uninstall)?So(t):xo(t,{reinstall:w(e.reinstall)}));if(r){P({operation:w(e.uninstall)?`uninstall`:`install`,results:o});return}for(let e of o)process.stdout.write(`${e.changed?`updated`:`skipped`} ${e.client} · ${e.path}\n`)}})}}),rs=v({meta:{name:`index-coverage`,description:`Find crawlable pages that deserve a closer Google index check`},args:{site:{type:`string`,description:`GSC property URL, for example sc-domain:example.com.`},url:{type:`string`,description:`Start URL to crawl. Defaults to the home page.`},sitemaps:{type:`string`,description:`Comma-separated XML sitemap URLs. Defaults to declarations captured by the crawl.`},days:{type:`string`,description:`Finalized Search Console date range. Defaults to 90 days.`},"row-limit":{type:`string`,description:`Maximum Search Console page rows. Defaults to 100000.`},"max-sitemap-urls":{type:`string`,description:`Maximum sitemap inventory URLs. Defaults to 100000.`},limit:{type:`string`,description:`Maximum URLs returned in each evidence group. Defaults to 100.`},refresh:{type:`boolean`,default:!1,description:`Bypass the local Search Console cache.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=O(e),n=k(C(e.site)),r=Ir(e.sitemaps),i=await kn({site:n,url:C(e.url),sitemaps:r.length?r:void 0,days:T(e.days),rowLimit:T(e[`row-limit`]),maxSitemapUrls:T(e[`max-sitemap-urls`]),itemsPerSection:T(e.limit),refresh:w(e.refresh)});if(t){P(i);return}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(`
60
+ `),`Try next`),x(`Setup complete.`),Jo()}const Xo=v({meta:{name:`setup`,description:`Sign in with Google, then add the MCP server and skill`},args:{"dry-run":{type:`boolean`,default:!1,description:`Show what setup does without changing anything.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON. Never prompts.`}},run:async({args:e})=>Yo(e)}),Zo=v({...Xo,meta:{name:`start`,description:`Start here: sign in with Google, then add the MCP server and skill to your agents`}}),Qo=v({...Zo,meta:{name:`init`,description:"Legacy alias for `seo start`."}}),$o=v({meta:{name:`logs`,description:`Manage local log retention`},subCommands:{prune:v({meta:{name:`prune`,description:`Rotate large logs and remove old local logs`},args:{quiet:{type:`boolean`,default:!1,description:`Do not print a result.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=He();if(O(e)){P(t);return}e.quiet||process.stdout.write(`Rotated ${t.rotated} and removed ${t.removed} log files. ${Ai(t.sizeBytes)} retained.\n`)}})}}),es=[{client:`claude-desktop`,flag:`claude-desktop`},{client:`claude-code`,flag:`claude-code`},{client:`codex`,flag:`codex`},{client:`cursor`,flag:`cursor`}];function ts(e,t){return w(e.all)?t:es.filter(({flag:t})=>w(e[t])).map(({client:e})=>e)}const ns=v({meta:{name:`mcp`,description:`MCP server helpers`},subCommands:{serve:v({meta:{name:`serve`,description:`Run the SEO MCP server`},args:{test:{type:`boolean`,default:!1,description:`Start in test mode`}},run:async({args:e})=>{let{startMcpServer:t}=await import(`./dist-GOZ_hkEB.js`);await t({test:w(e.test)})}}),install:v({meta:{name:`install`,description:`Install or remove SEO MCP config for local clients`},args:{uninstall:{type:`boolean`,default:!1,description:`Remove the SEO MCP config instead of adding it`},reinstall:{type:`boolean`,default:!1,description:`Rewrite an existing managed SEO MCP config`},"claude-desktop":{type:`boolean`,default:!1,description:`Update Claude Desktop`},"claude-code":{type:`boolean`,default:!1,description:`Update Claude Code`},codex:{type:`boolean`,default:!1,description:`Update Codex CLI, app, and IDE extension`},cursor:{type:`boolean`,default:!1,description:`Update Cursor`},all:{type:`boolean`,default:!1,description:`Update every detected MCP client`},json:{type:`boolean`,default:!1,description:`Print structured JSON output`}},run:async({args:e})=>{if(w(e.uninstall)&&w(e.reinstall))throw new h(`INVALID_INPUT`,`Use either --uninstall or --reinstall, not both.`);let t=$a(),n=no({targets:t}),r=O(e),i=ts(e,n.map(e=>e.client));if(i.length===0){if(!N({json:r}))throw new h(`INVALID_INPUT`,`Choose an MCP client with --claude-desktop, --claude-code, --codex, --cursor, or --all.`);let a=n.length>0?n:t;i=L(await mr({message:e.uninstall?`Remove seo from which MCP clients?`:`Install seo into which MCP clients?`,options:a.map(e=>({value:e.client,label:e.label,hint:e.path})),initialValues:n.map(e=>e.client),required:!0}))}let a=i.filter(e=>!t.some(t=>t.client===e));if(a.length>0)throw new h(`INVALID_INPUT`,`MCP setup is not supported for ${a.join(`, `)} on ${process.platform}.`);let o=t.filter(e=>i.includes(e.client)).map(t=>w(e.uninstall)?So(t):xo(t,{reinstall:w(e.reinstall)}));if(r){P({operation:w(e.uninstall)?`uninstall`:`install`,results:o});return}for(let e of o)process.stdout.write(`${e.changed?`updated`:`skipped`} ${e.client} · ${e.path}\n`)}})}}),rs=v({meta:{name:`index-coverage`,description:`Find crawlable pages that deserve a closer Google index check`},args:{site:{type:`string`,description:`GSC property URL, for example sc-domain:example.com.`},url:{type:`string`,description:`Start URL to crawl. Defaults to the home page.`},sitemaps:{type:`string`,description:`Comma-separated XML sitemap URLs. Defaults to declarations captured by the crawl.`},days:{type:`string`,description:`Finalized Search Console date range. Defaults to 90 days.`},"row-limit":{type:`string`,description:`Maximum Search Console page rows. Defaults to 100000.`},"max-sitemap-urls":{type:`string`,description:`Maximum sitemap inventory URLs. Defaults to 100000.`},limit:{type:`string`,description:`Maximum URLs returned in each evidence group. Defaults to 100.`},refresh:{type:`boolean`,default:!1,description:`Bypass the local Search Console cache.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=O(e),n=k(C(e.site)),r=Ir(e.sitemaps),i=await kn({site:n,url:C(e.url),sitemaps:r.length?r:void 0,days:T(e.days),rowLimit:T(e[`row-limit`]),maxSitemapUrls:T(e[`max-sitemap-urls`]),itemsPerSection:T(e.limit),refresh:w(e.refresh)});if(t){P(i);return}K({title:`Index coverage`,target:i.site,status:i.sources.crawl.completeness===`complete`&&i.sources.searchConsole.completeness===`complete`?i.summary.crawlableCandidatesWithoutRetainedSearchVisibility>0?`warning`:`pass`:`unknown`,summary:`${i.summary.crawlableCandidatesWithoutRetainedSearchVisibility} crawlable pages need URL Inspection review.`,metrics:[{label:`Crawl report`,value:i.input.crawlReport.id},{label:`Crawl evidence`,value:i.sources.crawl.completeness},{label:`Sitemap evidence`,value:i.sources.sitemap.completeness},{label:`Search evidence`,value:i.sources.searchConsole.completeness},{label:`Search window`,value:`${i.sources.searchConsole.startDate} to ${i.sources.searchConsole.endDate}`},{label:`Search-visible pages`,value:i.summary.retainedSearchVisibleUrls},{label:`Blocked/non-indexable`,value:i.summary.blockedOrNonIndexableCrawlUrls},{label:`Sitemap only`,value:i.summary.sitemapOnlyUrls},{label:`Search Console only`,value:i.summary.searchConsoleOnlyUrls}]}),i.crawlableWithoutRetainedSearchVisibility.items.length&&(process.stdout.write(`
61
61
  Representative URL Inspection candidates
62
62
  `),I([`URL`,`Status`,`In sitemap`],i.crawlableWithoutRetainedSearchVisibility.items.map(e=>[G(e.url,80),e.status,e.inSitemap?`yes`:`no`]))),Y(`How to read this report`,i.caveats)}}),is=v({meta:{name:`link-recover`,description:`Find search-value URLs that are now broken, blocked, or poorly redirected`},args:{site:{type:`string`},...X([`days`,`limit`,`minImpressions`,`refresh`,`js`],{days:{description:`GSC lookback window. Defaults to 90.`},limit:{description:`Maximum GSC pages to check. Defaults to 50.`},minImpressions:{description:`Minimum impressions to check a page. Defaults to 100.`},refresh:{description:`Bypass local GSC and HTTP caches.`},js:{description:`Force JavaScript rendering for final page extraction.`}}),"min-clicks":{type:`string`,description:`Minimum clicks to check a page. Defaults to 1.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=O(e),n=await Mn({site:k(C(e.site)),days:T(e.days),limit:T(e.limit),minClicks:T(e[`min-clicks`]),minImpressions:T(e[`min-impressions`]),refresh:w(e.refresh),js:w(e.js)?!0:`auto`});if(t){P(n);return}K({title:`Link recovery`,target:n.site,status:n.warnings.length>0?`unknown`:n.summary.recoverable>0?`warning`:`pass`,summary:n.summary.recoverable>0?`${H(n.summary.recoverable)} previously visible URLs need recovery review.`:`No recoverable URLs were found in the retained evidence.`,metrics:[{label:`Range`,value:`${n.range.startDate} to ${n.range.endDate}`},{label:`Checked`,value:H(n.summary.checked)},{label:`Recoverable`,value:H(n.summary.recoverable)},{label:`High severity`,value:H(n.summary.high)},{label:`Clicks at risk`,value:H(n.summary.clicksAtRisk)},{label:`Impressions at risk`,value:H(n.summary.impressionsAtRisk)}]}),n.items.length&&(q([`Severity`,`Issue`,`Clicks`,`Impr`,`URL`,`Action`],n.items.map(e=>[e.severity,e.issue,H(e.clicks),H(e.impressions),G(e.url,56),G(e.recommendation.action,72)])),J(`Top recovery actions`,n.items.map(e=>({label:G(e.url,96),context:`${e.severity}, ${H(e.clicks)} clicks at risk`,action:e.recommendation.action})))),Y(`Warnings`,n.warnings.slice(0,10))}}),as=v({meta:{name:`redirect-trace`,description:`Trace redirects and report final indexability/canonical issues`},args:{url:{type:`string`,required:!0,description:`URL to trace.`},"max-hops":{type:`string`,description:`Maximum redirect hops to follow. Defaults to 10.`},refresh:{type:`boolean`,default:!1,description:`Bypass local HTTP cache for final page extraction.`},js:{type:`boolean`,default:!1,description:`Force JavaScript rendering for final page extraction.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=await yt({url:C(e.url)??``,maxHops:T(e[`max-hops`]),refresh:w(e.refresh),js:w(e.js)?!0:`auto`});if(O(e)){P(t);return}K({title:`Redirect trace`,target:t.url,status:t.summary.issues.length>0?`warning`:t.summary.finalIndexable===void 0?`unknown`:`pass`,summary:t.summary.issues.length>0?t.summary.issues.join(`, `):`${t.summary.hops} redirect hops ended at HTTP ${t.summary.finalStatus}.`,metrics:[{label:`Final URL`,value:t.finalUrl},{label:`Final status`,value:t.summary.finalStatus},{label:`Indexable`,value:t.summary.finalIndexable===void 0?`Unknown`:t.summary.finalIndexable?`Yes`:`No`},{label:`Hops`,value:t.summary.hops}]}),I([`#`,`Status`,`URL`,`Next`],t.chain.map((e,t)=>[t+1,e.status,G(e.url,72),G(e.nextUrl??`-`,72)])),t.finalPage&&(process.stdout.write(`
63
63
  Final page
@@ -1,3 +1,3 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire as __seoCreateRequire } from 'node:module'; const require = __seoCreateRequire(import.meta.url);
3
- import{s as e}from"./dist-Ban8uP1U.js";export{e as startMcpServer};
3
+ import{s as e}from"./dist-U5Io-lSG.js";export{e as startMcpServer};