seo 0.2.17 → 0.2.18
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 +2 -2
- package/dist/cli.js.map +1 -1
- package/dist/{dist-B9Clu4T_.js → dist-B7Sb0jRt.js} +1 -1
- package/dist/{dist-B36ZRY2E.js → dist-e8x4JREt.js} +6 -6
- package/dist/{dist-B36ZRY2E.js.map → dist-e8x4JREt.js.map} +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/mcp.js +1 -1
- package/dist/mcp.js.map +1 -1
- package/dist/{package-C-Oa9Mgz.js → package-DLnOXW-d.js} +2 -2
- package/dist/{package-C-Oa9Mgz.js.map → package-DLnOXW-d.js.map} +1 -1
- package/package.json +9 -9
package/dist/cli.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { createRequire as __seoCreateRequire } from 'node:module'; const require = __seoCreateRequire(import.meta.url);
|
|
3
|
-
import{$ as e,$n as t,$t as n,A as r,An as i,At as a,B as o,Bn as s,Bt as c,C as l,Cn as u,Ct as d,D as f,Dn as p,Dt as m,E as h,En as ee,Et as te,F as ne,Fn as re,Ft as g,G as ie,Gn as ae,Gt as oe,H as se,Hn as ce,Ht as le,I as ue,In as de,It as fe,J as pe,Jn as me,Jt as he,K as ge,Kn as _e,Kt as ve,L as ye,Ln as be,Lt as xe,M as Se,Mn as Ce,Mt as we,N as Te,Nn as Ee,Nt as De,O as Oe,On as ke,Ot as Ae,P as je,Pn as Me,Pt as Ne,Q as Pe,Qn as Fe,Qt as Ie,R as Le,Rn as Re,Rt as ze,S as Be,Sn as Ve,St as He,T as Ue,Tn as We,Tt as Ge,U as Ke,Un as qe,Ut as Je,V as Ye,Vn as Xe,Vt as Ze,W as Qe,Wn as $e,Wt as et,X as tt,Xn as nt,Xt as rt,Y as it,Yn as at,Yt as ot,Z as st,Zn as ct,Zt as lt,_ as ut,_n as dt,_t as ft,a as pt,an as mt,ar as ht,at as gt,b as _t,bn as vt,bt as yt,cn as bt,ct as xt,d as St,dn as Ct,dt as wt,en as Tt,er as Et,et as Dt,f as Ot,fn as kt,ft as At,g as jt,gn as Mt,gt as Nt,h as Pt,hn as Ft,ht as It,i as Lt,in as Rt,ir as _,it as zt,j as Bt,jn as Vt,jt as Ht,k as Ut,kn as Wt,kt as Gt,l as Kt,ln as qt,lt as Jt,m as Yt,mn as Xt,mt as Zt,nn as Qt,nr as $t,nt as en,o as tn,on as nn,or as rn,ot as an,p as on,pn as sn,pt as cn,q as ln,qn as un,qt as dn,r as fn,rn as pn,rr as mn,rt as hn,sn as gn,st as _n,t as vn,tn as yn,tr as bn,tt as xn,u as Sn,un as Cn,ut as wn,v as Tn,vn as En,vt as Dn,w as On,wn as kn,wt as An,x as jn,xn as Mn,xt as Nn,y as Pn,yn as Fn,yt as In,z as Ln,zn as Rn,zt as zn}from"./dist-
|
|
3
|
+
import{$ as e,$n as t,$t as n,A as r,An as i,At as a,B as o,Bn as s,Bt as c,C as l,Cn as u,Ct as d,D as f,Dn as p,Dt as m,E as h,En as ee,Et as te,F as ne,Fn as re,Ft as g,G as ie,Gn as ae,Gt as oe,H as se,Hn as ce,Ht as le,I as ue,In as de,It as fe,J as pe,Jn as me,Jt as he,K as ge,Kn as _e,Kt as ve,L as ye,Ln as be,Lt as xe,M as Se,Mn as Ce,Mt as we,N as Te,Nn as Ee,Nt as De,O as Oe,On as ke,Ot as Ae,P as je,Pn as Me,Pt as Ne,Q as Pe,Qn as Fe,Qt as Ie,R as Le,Rn as Re,Rt as ze,S as Be,Sn as Ve,St as He,T as Ue,Tn as We,Tt as Ge,U as Ke,Un as qe,Ut as Je,V as Ye,Vn as Xe,Vt as Ze,W as Qe,Wn as $e,Wt as et,X as tt,Xn as nt,Xt as rt,Y as it,Yn as at,Yt as ot,Z as st,Zn as ct,Zt as lt,_ as ut,_n as dt,_t as ft,a as pt,an as mt,ar as ht,at as gt,b as _t,bn as vt,bt as yt,cn as bt,ct as xt,d as St,dn as Ct,dt as wt,en as Tt,er as Et,et as Dt,f as Ot,fn as kt,ft as At,g as jt,gn as Mt,gt as Nt,h as Pt,hn as Ft,ht as It,i as Lt,in as Rt,ir as _,it as zt,j as Bt,jn as Vt,jt as Ht,k as Ut,kn as Wt,kt as Gt,l as Kt,ln as qt,lt as Jt,m as Yt,mn as Xt,mt as Zt,nn as Qt,nr as $t,nt as en,o as tn,on as nn,or as rn,ot as an,p as on,pn as sn,pt as cn,q as ln,qn as un,qt as dn,r as fn,rn as pn,rr as mn,rt as hn,sn as gn,st as _n,t as vn,tn as yn,tr as bn,tt as xn,u as Sn,un as Cn,ut as wn,v as Tn,vn as En,vt as Dn,w as On,wn as kn,wt as An,x as jn,xn as Mn,xt as Nn,y as Pn,yn as Fn,yt as In,z as Ln,zn as Rn,zt as zn}from"./dist-e8x4JREt.js";import{constants as Bn,copyFileSync as Vn,createWriteStream as Hn,existsSync as v,mkdirSync as Un,readFileSync as Wn,readdirSync as Gn,realpathSync as Kn,renameSync as qn,rmSync as Jn,statSync as Yn,writeFileSync as Xn}from"node:fs";import{homedir as Zn}from"node:os";import{basename as Qn,dirname as y,isAbsolute as $n,join as b,posix as er,relative as tr,resolve as nr,win32 as rr}from"node:path";import{randomUUID as ir}from"node:crypto";import{spawn as ar,spawnSync as or}from"node:child_process";import{fileURLToPath as sr}from"node:url";import{isDeepStrictEqual as cr,stripVTControlCharacters as lr}from"node:util";import{lstat as ur,mkdir as dr,mkdtemp as fr,readFile as pr,readdir as mr,rename as hr,rm as gr,stat as _r,unlink as vr,writeFile as yr}from"node:fs/promises";import{defineCommand as x,runCommand as br,runMain as xr}from"citty";import{cancel as Sr,confirm as Cr,intro as wr,isCancel as Tr,log as Er,multiselect as Dr,note as S,outro as Or,password as kr,select as Ar,text as jr}from"@clack/prompts";import Mr from"picocolors";import Nr from"string-width";import Pr from"cli-table3";import{applyEdits as Fr,modify as Ir,parse as Lr,printParseErrorCode as Rr}from"jsonc-parser";import{finished as zr}from"node:stream/promises";import Br from"update-notifier";function C(e){return e.replaceAll(`&`,`&`).replaceAll(`<`,`<`).replaceAll(`>`,`>`).replaceAll(`"`,`"`).replaceAll(`'`,`'`)}function Vr(e){try{let t=new URL(e);return t.protocol===`http:`||t.protocol===`https:`?t.toString():void 0}catch{return}}function Hr(e){let t=Vr(e),n=C(e);return t?`<a href="${C(t)}" rel="noreferrer">${n}</a>`:n}function Ur(e){return e.length===0?`<p class="empty">No items to report.</p>`:`<ul>${e.map(e=>`<li>${C(e)}</li>`).join(``)}</ul>`}function Wr(e){return e.sections.map(e=>`<section class="panel">
|
|
4
4
|
<h2>${C(e.title)}</h2>
|
|
5
5
|
${Ur(e.bullets)}
|
|
6
6
|
</section>`).join(``)}function Gr(e){return e.map(e=>`<section class="panel">
|
|
@@ -168,7 +168,7 @@ Google Analytics impact
|
|
|
168
168
|
`),z([`Metric`,`Before`,`After`,`Delta`],[[`Sessions`,U(e.analytics.before.metrics.sessions),U(e.analytics.after.metrics.sessions),`${U(e.analytics.delta.sessions)} (${Is(e.analytics.delta.sessionPct)})`],[`Conversions`,U(e.analytics.before.metrics.conversions),U(e.analytics.after.metrics.conversions),`${U(e.analytics.delta.conversions)} (${Is(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)} (${Is(e.analytics.delta.revenuePct)})`]])),e.control&&(process.stdout.write(`
|
|
169
169
|
Control comparison
|
|
170
170
|
`),R([[`Control`,e.control.change.title],[`Adjusted clicks`,e.control.adjusted.clickDelta===null?`n/a`:U(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 Vs=x({meta:{name:`tests`,description:`Create and report local SEO tests from Search Console and Google Analytics data`},subCommands:{list:x({meta:{name:`list`,description:`List saved SEO tests`},args:{site:{type:`string`,description:`GSC property URL.`},client:{type:`string`,description:`Legacy alias for --project.`},project:{type:`string`,description:`Saved project id or name.`},limit:{type:`string`,description:`Maximum tests to list. Defaults to 20.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=M(e),n=E(e),r=c({site:(n||T(e.site)?await V({client:n,site:T(e.site),options:{json:t}}):void 0)?.site,limit:O(e.limit)});if(t){L({tests:r});return}z([`ID`,`Date`,`Scope`,`Target`,`Title`],r.map(e=>[e.id,e.changedAt,e.scope,e.target,e.title]))}}),create:x({meta:{name:`create`,description:`Create a local SEO test`},args:{site:{type:`string`,description:`GSC property URL.`},client:{type:`string`,description:`Legacy alias for --project.`},project:{type:`string`,description:`Saved project id or name.`},scope:{type:`string`,default:`page`,description:`Change scope: site, page, query, or group.`},target:{type:`string`,required:!0,description:`Changed URL, query, group pattern, or site marker.`},title:{type:`string`,required:!0,description:`Plain-English name for the test.`},description:{type:`string`,description:`Optional note about what changed.`},date:{type:`string`,description:`Change date in YYYY-MM-DD. Defaults to today.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=M(e),n=await V({client:E(e),site:T(e.site),options:{json:t}}),r=Ze({site:n.site,scope:Ps(e.scope),target:T(e.target)??``,title:T(e.title)??``,description:T(e.description),changedAt:T(e.date)??Ns()});if(t){L(r);return}R([[`ID`,r.id],[`Date`,r.changedAt],[`Project`,n.client?.name??n.site],[`Scope`,r.scope],[`Target`,r.target],[`Title`,r.title]])}}),report:x({meta:{name:`report`,description:`Report an SEO test with equal finalized before/after windows`},args:{id:{type:`string`,description:`Saved SEO test id.`},site:{type:`string`,description:`GSC property URL.`},client:{type:`string`,description:`Legacy alias for --project.`},project:{type:`string`,description:`Saved project id or name.`},scope:{type:`string`,description:`Ad-hoc change scope: site, page, query, or group.`},target:{type:`string`,description:`Ad-hoc changed URL, query, group pattern, or site marker.`},title:{type:`string`,description:`Ad-hoc test title.`},date:{type:`string`,description:`Ad-hoc change date in YYYY-MM-DD.`},property:{type:`string`,description:`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=M(e),n=T(e.id),r=!n||E(e)||T(e.site)?await V({client:E(e),site:T(e.site),options:{json:t,refresh:D(e.refresh)}}):void 0,i=await we({id:n,site:n?void 0:r?.site,scope:n?void 0:Fs(e.scope),target:n?void 0:T(e.target),title:T(e.title),changedAt:n?void 0:T(e.date),googleAnalyticsPropertyId:T(e.property)??r?.client?.analytics.google?.propertyId,controlScope:Fs(e[`control-scope`]),controlTarget:T(e[`control-target`]),controlTitle:T(e[`control-title`]),beforeDays:k(e.before,`--before`),afterDays:k(e.after,`--after`),refresh:D(e.refresh)});if(t){L(i);return}Bs(i)}})}});function Hs(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 Us={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.`}},Ws={...X([`includeBrand`,`verifyContent`,`verifyLimit`,`js`,`fetchConcurrency`,`fetchIntervalCap`,`fetchIntervalMs`,`refresh`]),json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}};function Gs(e){return{includeBrand:D(e[`include-brand`]),verifyContent:D(e[`verify-content`]),verifyLimit:O(e[`verify-limit`])??3,js:D(e.js)?!0:void 0,rate:li(e),refresh:D(e.refresh)}}function Ks(e){return{client:E(e),site:T(e.site),refresh:D(e.refresh)}}const{json:qs,...Js}=Ws,Ys=Js,Xs={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 Zs(e){return{type:`string`,description:`Output folder. Defaults to ./seo-export/<project>-${e}-YYYY-MM-DD.`}}function Qs(e){return e.toLowerCase().replace(/^sc-domain:/,``).replace(/^https?:\/\//,``).replace(/[^a-z0-9._-]+/g,`-`).replace(/^-+|-+$/g,``)}function $s(e){let t=new Date().toISOString().slice(0,10),n=Qs(e.clientId??e.site);return nr(process.cwd(),`seo-export`,`${n}-${e.report}-${t}`)}async function ec(e,t){await dr(e,{recursive:!0});let n=[];for(let r of t){let t=nr(e,r.filename);await yr(t,sn(r.rows,r.headers),`utf8`),n.push(t)}return n}function tc(e,t){process.stdout.write(`Wrote ${n(t.length,`CSV file`)} to ${e}\n`);for(let e of t)process.stdout.write(`- ${e}\n`)}const nc=x({meta:{name:`diagnose`,description:`Export diagnosis tables: priorities, anomalies, movement, decay, cannibalisation, striking-distance, and quick wins.`},args:{...Xs,out:Zs(`diagnose`),days:{type:`string`,description:`GSC lookback window. Defaults to 90.`},recent:{type:`string`,description:`Recent anomaly window in days.`},limit:{type:`string`,description:`Maximum rows per movement/opportunity table.`},"include-brand":{type:`boolean`,default:!1,description:`Include branded queries in opportunity reports.`},"verify-content":{type:`boolean`,default:!1,description:`Verify top opportunities against page title, meta, and content.`},"verify-limit":{type:`string`,description:`Maximum opportunity URLs to verify.`},js:{type:`boolean`,default:!1,description:`Force JavaScript rendering for verified pages.`},"fetch-concurrency":{type:`string`,description:`Maximum concurrent page fetches per host. Defaults to 4.`},"fetch-interval-cap":{type:`string`,description:`Maximum page fetches per interval per host. Defaults to 4.`},"fetch-interval-ms":{type:`string`,description:`Fetch rate interval in milliseconds. Defaults to 1000.`},refresh:{type:`boolean`,default:!1,description:`Bypass local cache and fetch fresh data.`}},run:async({args:e})=>{let t=await V({client:E(e),site:T(e.site)}),n=await fe({site:t.site,days:O(e.days),recentDays:O(e.recent),limit:O(e.limit),brandTerms:t.client?.brandTerms,includeBrand:D(e[`include-brand`]),verifyContent:D(e[`verify-content`]),verifyLimit:O(e[`verify-limit`]),js:D(e.js)?!0:`auto`,rate:li(e),refresh:D(e.refresh),progress:Hs(!0)}),r=T(e.out)??$s({report:`diagnose`,clientId:t.client?.id,site:t.site});tc(r,await ec(r,qt(n)))}}),rc=`.seo-okf.json`,ic=`${JSON.stringify({generator:`seo`,format:`okf`,version:1})}\n`;function ac(e,t){let n=nr(e,t),r=tr(nr(e),n);if(!r||r.startsWith(`..`)||$n(r))throw new _(`INVALID_INPUT`,`Unsafe OKF file path: ${t}`);return n}async function oc(e,t){for(let n of t){let t=ac(e,n.path);await dr(y(t),{recursive:!0}),await yr(t,n.content)}await yr(b(e,rc),ic)}async function sc(e){let t=[];async function n(e,r=``){let i=await mr(e,{withFileTypes:!0});for(let a of i){let i=r?`${r}/${a.name}`:a.name,o=b(e,a.name);a.isDirectory()?await n(o,i):a.isFile()&&a.name.endsWith(`.md`)&&t.push({path:i,content:await pr(o,`utf8`)})}}return await n(e),t.sort((e,t)=>e.path<t.path?-1:+(e.path>t.path))}async function cc(e){try{let t=await ur(e);if(t.isSymbolicLink()||!t.isDirectory())throw new _(`INVALID_INPUT`,`OKF output must be a real directory: ${e}`);if(!(await mr(e)).length)return!0;try{if(await pr(b(e,rc),`utf8`)===ic)return!0}catch{}throw new _(`INVALID_INPUT`,`Refusing to replace non-empty unmanaged directory: ${e}`)}catch(e){if(e.code===`ENOENT`)return!1;throw e}}async function lc(e,t){if(!Ie(t).valid)throw new _(`INVALID_INPUT`,`Generated bundle did not pass seo OKF checks and was not written.`);let n=nr(e),r=y(n);await dr(r,{recursive:!0});let i=await cc(n),a=await fr(b(r,`.${Qn(n)}-staging-`)),o=b(r,`.${Qn(n)}-backup-${ir()}`),s=!1;try{await oc(a,t);let e=Ie(await sc(a));if(!e.valid)throw new _(`INTERNAL_ERROR`,`Staged OKF files failed validation after writing.`);return i&&(await hr(n,o),s=!0),await hr(a,n),s&&await gr(o,{recursive:!0,force:!0}),e}catch(e){throw await gr(a,{recursive:!0,force:!0}),s&&(await gr(n,{recursive:!0,force:!0}),await hr(o,n)),e}}async function uc(e,t){if(!e){process.stdout.write(t);return}await dr(y(e),{recursive:!0}),await yr(e,t),process.stdout.write(`Wrote ${e}\n`)}function dc(e){let t=T(e)??`okf`;if([`okf`,`markdown`,`json`].includes(t))return t;throw Error(`Format must be one of: okf, markdown, json.`)}function fc(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:We(e),entityReadiness:ee(e),llmsTxt:u(e)}}function pc(e){let t=fc(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 mc=x({meta:{name:`knowledge`,description:`Export site knowledge from a saved crawl as OKF, Markdown, or JSON`},args:{"report-id":{type:`string`,description:`Saved crawl report id to export.`},site:{type:`string`,description:`GSC property URL for selecting the latest saved crawl.`},client:{type:`string`,description:`Legacy alias for --project.`},project:{type:`string`,description:`Saved project id or name.`},format:{type:`string`,description:`Output format: okf, markdown, or json. Defaults to okf.`},output:{type:`string`,description:`Output directory for OKF, or output file for Markdown/JSON.`},"max-concepts":{type:`string`,description:`Maximum OKF concept files. Defaults to 500.`},json:{type:`boolean`,default:!1,description:`Print command metadata as JSON.`}},run:async({args:e})=>{let t=M(e),n=dc(e.format),r=n===`okf`?lt(k(e[`max-concepts`],`--max-concepts`)):void 0,i=await oa(e,{json:t}),a=T(e.output);if(n===`okf`){let e=ot(i,{maxConcepts:r}),o=a??`./okf`,s=await lc(o,e.files);if(t){L({format:n,output:o,bundle:e,validation:s});return}process.stdout.write(`Wrote OKF bundle to ${o}\n\n`),R([[`Files`,String(e.files.length)],[`Concepts`,String(e.conceptCount)],[`Valid`,s.valid?`yes`:`no`]]);return}if(n===`json`){await uc(a,`${JSON.stringify(fc(i),null,2)}\n`);return}await uc(a,pc(i))}}),hc=x({meta:{name:`monthly`,description:`Export monthly report tables plus the underlying diagnosis detail CSVs.`},args:{...Xs,out:Zs(`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.`}}),...Ys},run:async({args:e})=>{let t=await V({client:E(e),site:T(e.site),options:{refresh:D(e.refresh)}}),n=await xn({site:t.site,month:T(e.month),limit:O(e.limit),brandTerms:t.client?.brandTerms,...Gs(e),progress:Hs(!0)}),r=T(e.out)??$s({report:`monthly`,clientId:t.client?.id,site:t.site});tc(r,await ec(r,Cn(n)))}}),gc=x({meta:{name:`narrative`,description:`Export report narrative tables plus the underlying diagnosis detail CSVs.`},args:{...Xs,out:Zs(`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.`},...Ys},run:async({args:e})=>{let t=await V({client:E(e),site:T(e.site),options:{refresh:D(e.refresh)}}),n=await en({site:t.site,days:O(e.days),recentDays:O(e.recent),startDate:T(e[`start-date`]),endDate:T(e[`end-date`]),limit:O(e.limit),changeLimit:O(e[`change-limit`]),brandTerms:t.client?.brandTerms,...Gs(e),progress:Hs(!0)}),r=T(e.out)??$s({report:`narrative`,clientId:t.client?.id,site:t.site});tc(r,await ec(r,Cn(n)))}});function _c(e){return xt({days:k(e.days,`--days`),sitemaps:A(e.sitemap),maxSitemapUrls:k(e[`max-sitemap-urls`],`--max-sitemap-urls`),templateLimit:k(e.limit,`--limit`),minimumTemplateUrls:k(e[`minimum-template-urls`],`--minimum-template-urls`),minimumTemplateShare:k(e[`minimum-template-share`],`--minimum-template-share`),minimumTemplateImpressions:k(e[`minimum-template-impressions`],`--minimum-template-impressions`),crawlSamples:k(e[`crawl-samples`],`--crawl-samples`),inspectSamples:k(e[`inspect-samples`],`--inspect-samples`),brandTerms:A(e[`brand-terms`]),includeBrand:D(e[`include-brand`]),js:D(e.js)?!0:`auto`,refresh:D(e.refresh),rate:ui(e)})}const vc=x({meta:{name:`pseo`,description:`Export pSEO audit tables: templates, demand patterns, top queries, sample coverage, crawl samples, and inspection samples.`},args:{...Xs,out:Zs(`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=_c(e),n=await V({client:E(e),site:T(e.site)}),r=await Jt({site:n.site,...t,brandTerms:t.brandTerms??n.client?.brandTerms,progress:Hs(!0)}),i=T(e.out)??$s({report:`pseo`,clientId:n.client?.id,site:n.site});tc(i,await ec(i,Ct(r)))}}),yc=x({meta:{name:`refresh-priorities`,description:`Export the ranked SEO priority queue, score breakdowns, grouped findings, and diagnosis detail CSVs.`},args:{...Xs,out:Zs(`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.`}}),"google-analytics-property":{type:`string`,description:`Google Analytics property ID to use for analytics value. Defaults from the selected project.`},"verify-content":w(`Verify top opportunities against page title, meta, and content. Defaults to true.`,`Skip page content verification.`),"verify-limit":{type:`string`,description:`Maximum opportunity URLs to verify. Defaults to 5.`}},run:async({args:e})=>{let t=await V({client:E(e),site:T(e.site),options:{refresh:D(e.refresh)}}),n=await ln({site:t.site,days:O(e.days),recentDays:O(e.recent),limit:O(e.limit),brandTerms:t.client?.brandTerms,includeBrand:D(e[`include-brand`]),googleAnalyticsPropertyId:T(e[`google-analytics-property`])??t.client?.analytics.google?.propertyId,verifyContent:D(e[`verify-content`]),verifyLimit:O(e[`verify-limit`]),refresh:D(e.refresh)}),r=T(e.out)??$s({report:`refresh-priorities`,clientId:t.client?.id,site:t.site});tc(r,await ec(r,kt(n)))}}),bc=x({meta:{name:`export`,description:`Export full report data to CSV files`},subCommands:{diagnose:nc,knowledge:mc,narrative:gc,monthly:hc,"update-postmortem":x({meta:{name:`update-postmortem`,description:`Export update postmortem attribution, findings, template movement, and segment movers.`},args:{...Xs,out:Zs(`update-postmortem`),...X([`days`,`recentDays`,`limit`,`includeBrand`,`refresh`],{days:{description:`Diagnosis window length in days. Defaults to 90.`},limit:{description:`Maximum winners/losers per segment. Defaults to 20.`}}),"known-change":{type:`string`,description:`Manual site-side change to treat as a confounder, for example pruning pages or blocking traffic.`},"ignore-change-log":{type:`boolean`,default:!1,description:`Do not use saved change-log entries as confounders.`}},run:async({args:e})=>{let t=await V({client:E(e),site:T(e.site),options:{refresh:D(e.refresh)}}),n=await ie({site:t.site,days:O(e.days),recentDays:O(e.recent),limit:O(e.limit),brandTerms:t.client?.brandTerms,includeBrand:D(e[`include-brand`]),knownConfounders:T(e[`known-change`])?[T(e[`known-change`])??``]:void 0,includeChangeLog:!D(e[`ignore-change-log`]),refresh:D(e.refresh)}),r=T(e.out)??$s({report:`update-postmortem`,clientId:t.client?.id,site:t.site});tc(r,await ec(r,Qr(n)))}}),"refresh-priorities":yc,pseo:vc}}),xc={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 Sc(e){let t=T(e.site);if(t)return t;let n=await H({project:E(e),options:{json:M(e)}});if(!n)throw new _(`INVALID_INPUT`,`Pass --site or select a saved project.`);return n.siteUrl}async function Cc(e){let t=T(e.url),n=A(e.urls)??[],r=T(e.file);if(!t&&n.length===0&&!r)throw new _(`INVALID_INPUT`,`Pass --url, comma-separated --urls, or a newline-delimited --file.`);let i=[...t?[t]:[],...n];if(r){let e=await _r(r).catch(()=>void 0);if(!e)throw new _(`INVALID_INPUT`,`IndexNow URL file not found: ${r}`);if(!e.isFile()||e.size>21e5)throw new _(`INVALID_INPUT`,`IndexNow URL files must be regular files no larger than 2.1 MB.`);i.push(...(await pr(r,`utf8`)).split(/\r?\n/).map(e=>e.trim()).filter(Boolean))}return i}const wc=x({meta:{name:`indexnow`,description:`Set up and submit changed URLs with IndexNow`},subCommands:{setup:x({meta:{name:`setup`,description:`Generate an IndexNow key file for one site`},args:{...xc,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=T(e.output);if(!t)throw new _(`INVALID_INPUT`,`Pass --output with the public asset directory that deploys at the site root.`);let n=h({site:await Sc(e)}),i=nr(t),a=b(i,`${n.key}.txt`);await dr(i,{recursive:!0});try{await yr(a,`${n.key}\n`,{encoding:`utf8`,flag:`wx`,mode:384})}catch(e){throw e.code===`EEXIST`?new _(`INVALID_INPUT`,`Refusing to overwrite the existing file at ${a}.`):e}let o;try{o=await r(n)}catch(e){throw await vr(a).catch(()=>void 0),e}let s={configured:!0,host:n.host,keyLocation:n.keyLocation,keyFile:a,credentialSource:o,nextStep:`Deploy ${a}, then run seo indexnow verify --site ${new URL(n.keyLocation).origin}`};M(e)?L(s):R([[`Host`,s.host],[`Key file`,s.keyFile],[`Public URL`,s.keyLocation],[`Saved in`,s.credentialSource],[`Next`,s.nextStep]])}}),status:x({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 f(),n={configuredSites:t.length,sites:t};M(e)?L(n):z([`Host`,`Key location`,`Created`],t.map(e=>[e.host,e.keyLocation,e.createdAt]))}}),verify:x({meta:{name:`verify`,description:`Check the public IndexNow key file`},args:{...xc,json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=await Ut({site:await Sc(e)}),n={...await Ue({record:t.record}),credentialSource:t.source};M(e)?L(n):R([[`Verified`,n.verified?`yes`:`no`],[`HTTP status`,String(n.status)],[`Key location`,n.keyLocation],[`Credential`,n.credentialSource]]),n.verified||(process.exitCode=4)}}),submit:x({meta:{name:`submit`,description:`Notify IndexNow about changed URLs`},args:{...xc,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 Ut({site:await Sc(e)}),n=await On({record:t.record,urls:await Cc(e),dryRun:D(e[`dry-run`])}),r={...n,credentialSource:t.source};if(M(e)){L(r);return}R([[`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:x({meta:{name:`remove`,description:`Remove a saved IndexNow key`},args:{...xc,json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=await Oe(await Sc(e)),n={removed:t,note:`The deployed key file was not removed.`};M(e)?L(n):process.stdout.write(`${t?`Saved key removed.`:`No saved key found.`} ${n.note}\n`)}})}}),Tc=x({...bo,meta:{name:`init`,description:"Legacy alias for `seo start`."}}),Ec=x({meta:{name:`links`,description:`Review bounded referring-link evidence from 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.`},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 imported or Bing link rows. Defaults to 500 for Bing and 10000 for 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.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=M(e),n=T(e.file),r=T(e.format);if(r&&![`csv`,`json`,`jsonl`].includes(r))throw new _(`INVALID_INPUT`,`--format must be csv, json, or jsonl.`);let i=k(e[`row-limit`],`--row-limit`),a;if(n)a=await Be({file:n,format:r,rowLimit:i});else{let n=await H({project:E(e),options:{json:t}}),r=T(e.site)??Se(n);if(!r)throw new _(`INVALID_INPUT`,`Pass --site, connect Bing to a saved project, or import a link file with --file.`);a=await l({site:r,rowLimit:i,targetLimit:k(e[`target-limit`],`--target-limit`),detailPagesPerTarget:k(e[`detail-pages`],`--detail-pages`)})}let o=jn({evidence:a,limit:k(e.limit,`--limit`)});if(t){L(o);return}K({title:`Inbound link evidence`,target:o.provenance.provider,status:o.dataStatus===`complete`?`info`:`unknown`,summary:`${U(o.summary.observedLinks)} observed links from ${U(o.summary.referringDomains)} referring domains.`,metrics:[{label:`Evidence`,value:o.dataStatus},{label:`Observed links`,value:U(o.summary.observedLinks)},{label:`Referring domains`,value:U(o.summary.referringDomains)},{label:`Target pages`,value:U(o.summary.targetPages)},{label:`Returned rows`,value:`${U(o.selection.returnedRows)} of ${U(o.selection.availableRows)}`}]}),o.links.length&&q([`Source`,`Target`,`Anchor`],o.links.map(e=>[G(e.sourceUrl,58),G(e.targetUrl,58),G(e.anchorText??``,36)])),Y(`Warnings`,o.warnings),Y(`Report caveats`,o.caveats)}});async function Dc(e,t){if(!e){process.stdout.write(t);return}await dr(y(e),{recursive:!0}),await yr(e,t),process.stdout.write(`Wrote ${e}\n`)}const Oc={"report-id":{type:`string`,description:`Saved crawl report id to use.`},site:{type:`string`,description:`GSC property URL for selecting the latest saved crawl.`},client:{type:`string`,description:`Legacy alias for --project.`},project:{type:`string`,description:`Saved project id or name.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},kc=x({meta:{name:`llms`,description:`Inspect or generate optional llms.txt from saved crawl data`},subCommands:{audit:x({meta:{name:`audit`,description:`Inspect optional llms.txt presence from a saved crawl`},args:Oc,run:async({args:e})=>{let t=M(e),n=u(await oa(e,{json:t}));if(t){L(n);return}K({title:`llms.txt audit`,target:n.url,status:n.issues.length>0?`warning`:`pass`,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(`Recommended pages`,n.recommendedPages.slice(0,10).map(e=>`${e.section}: ${e.url}`))}}),generate:x({meta:{name:`generate`,description:`Generate an llms.txt draft from a saved crawl`},args:{...Oc,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=M(e),n=kn(await oa(e,{json:t}),{maxUrls:O(e[`max-urls`]),tokenBudget:O(e[`token-budget`]),exclude:A(e.exclude),title:T(e.title),description:T(e.description)});if(t){L(n);return}await Dc(T(e.output),n.content),T(e.output)&&R([[`URLs`,String(n.includedUrls)],[`Estimated tokens`,String(n.estimatedTokens)]])}})}}),Ac=x({meta:{name:`logs`,description:`Manage local log retention`},subCommands:{prune:x({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=_t();if(M(e)){L(t);return}e.quiet||process.stdout.write(`Rotated ${t.rotated} and removed ${t.removed} log files. ${ta(t.sizeBytes)} retained.\n`)}})}}),jc=[{client:`claude-desktop`,flag:`claude-desktop`},{client:`claude-code`,flag:`claude-code`},{client:`codex`,flag:`codex`},{client:`cursor`,flag:`cursor`}];function Mc(e,t){return D(e.all)?t:jc.filter(({flag:t})=>D(e[t])).map(({client:e})=>e)}const Nc=x({meta:{name:`mcp`,description:`MCP server helpers`},subCommands:{serve:x({meta:{name:`serve`,description:`Run the SEO MCP server`},args:{test:{type:`boolean`,default:!1,description:`Start in test mode`}},run:async({args:e})=>{let{startMcpServer:t}=await import(`./dist-B9Clu4T_.js`);await t({test:D(e.test)})}}),install:x({meta:{name:`install`,description:`Install or remove SEO MCP config for local clients`},args:{uninstall:{type:`boolean`,default:!1,description:`Remove the SEO MCP config instead of adding it`},reinstall:{type:`boolean`,default:!1,description:`Rewrite an existing managed SEO MCP config`},"claude-desktop":{type:`boolean`,default:!1,description:`Update Claude Desktop`},"claude-code":{type:`boolean`,default:!1,description:`Update Claude Code`},codex:{type:`boolean`,default:!1,description:`Update Codex CLI, app, and IDE extension`},cursor:{type:`boolean`,default:!1,description:`Update Cursor`},all:{type:`boolean`,default:!1,description:`Update every detected MCP client`},json:{type:`boolean`,default:!1,description:`Print structured JSON output`}},run:async({args:e})=>{if(D(e.uninstall)&&D(e.reinstall))throw new _(`INVALID_INPUT`,`Use either --uninstall or --reinstall, not both.`);let t=Fa(),n=Ra({targets:t}),r=M(e),i=Mc(e,n.map(e=>e.client));if(i.length===0){if(!I({json:r}))throw new _(`INVALID_INPUT`,`Choose an MCP client with --claude-desktop, --claude-code, --codex, --cursor, or --all.`);let a=n.length>0?n:t;i=B(await Dr({message:e.uninstall?`Remove seo from which MCP clients?`:`Install seo into which MCP clients?`,options:a.map(e=>({value:e.client,label:e.label,hint:e.path})),initialValues:n.map(e=>e.client),required:!0}))}let a=i.filter(e=>!t.some(t=>t.client===e));if(a.length>0)throw new _(`INVALID_INPUT`,`MCP setup is not supported for ${a.join(`, `)} on ${process.platform}.`);let o=t.filter(e=>i.includes(e.client)).map(t=>D(e.uninstall)?ro(t):no(t,{reinstall:D(e.reinstall)}));if(r){L({operation:D(e.uninstall)?`uninstall`:`install`,results:o});return}for(let e of o)process.stdout.write(`${e.changed?`updated`:`skipped`} ${e.client} · ${e.path}\n`)}})}}),Pc=x({meta:{name:`crawl-diff`,description:`Crawl a site and diff technical/page changes vs last run`},args:{url:{type:`string`,required:!0,description:`Start URL to crawl.`},site:{type:`string`,description:`Optional GSC property to associate with the crawl.`},limit:{type:`string`,description:`Maximum pages to crawl. Defaults to 50.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`},refresh:{type:`boolean`,default:!1,description:`Bypass local HTTP cache.`},js:{type:`boolean`,default:!1,description:`Force JavaScript rendering when Playwright is installed.`}},run:async({args:e})=>{let t=await Dn({startUrl:T(e.url)??``,site:T(e.site),limit:O(e.limit),refresh:D(e.refresh),js:D(e.js)?!0:`auto`});if(M(e)){L(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(`
|
|
171
|
+
`)}\n`}const mc=x({meta:{name:`knowledge`,description:`Export site knowledge from a saved crawl as OKF, Markdown, or JSON`},args:{"report-id":{type:`string`,description:`Saved crawl report id to export.`},site:{type:`string`,description:`GSC property URL for selecting the latest saved crawl.`},client:{type:`string`,description:`Legacy alias for --project.`},project:{type:`string`,description:`Saved project id or name.`},format:{type:`string`,description:`Output format: okf, markdown, or json. Defaults to okf.`},output:{type:`string`,description:`Output directory for OKF, or output file for Markdown/JSON.`},"max-concepts":{type:`string`,description:`Maximum OKF concept files. Defaults to 500.`},json:{type:`boolean`,default:!1,description:`Print command metadata as JSON.`}},run:async({args:e})=>{let t=M(e),n=dc(e.format),r=n===`okf`?lt(k(e[`max-concepts`],`--max-concepts`)):void 0,i=await oa(e,{json:t}),a=T(e.output);if(n===`okf`){let e=ot(i,{maxConcepts:r}),o=a??`./okf`,s=await lc(o,e.files);if(t){L({format:n,output:o,bundle:e,validation:s});return}process.stdout.write(`Wrote OKF bundle to ${o}\n\n`),R([[`Files`,String(e.files.length)],[`Concepts`,String(e.conceptCount)],[`Valid`,s.valid?`yes`:`no`]]);return}if(n===`json`){await uc(a,`${JSON.stringify(fc(i),null,2)}\n`);return}await uc(a,pc(i))}}),hc=x({meta:{name:`monthly`,description:`Export monthly report tables plus the underlying diagnosis detail CSVs.`},args:{...Xs,out:Zs(`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.`}}),...Ys},run:async({args:e})=>{let t=await V({client:E(e),site:T(e.site),options:{refresh:D(e.refresh)}}),n=await xn({site:t.site,month:T(e.month),limit:O(e.limit),brandTerms:t.client?.brandTerms,...Gs(e),progress:Hs(!0)}),r=T(e.out)??$s({report:`monthly`,clientId:t.client?.id,site:t.site});tc(r,await ec(r,Cn(n)))}}),gc=x({meta:{name:`narrative`,description:`Export report narrative tables plus the underlying diagnosis detail CSVs.`},args:{...Xs,out:Zs(`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.`},...Ys},run:async({args:e})=>{let t=await V({client:E(e),site:T(e.site),options:{refresh:D(e.refresh)}}),n=await en({site:t.site,days:O(e.days),recentDays:O(e.recent),startDate:T(e[`start-date`]),endDate:T(e[`end-date`]),limit:O(e.limit),changeLimit:O(e[`change-limit`]),brandTerms:t.client?.brandTerms,...Gs(e),progress:Hs(!0)}),r=T(e.out)??$s({report:`narrative`,clientId:t.client?.id,site:t.site});tc(r,await ec(r,Cn(n)))}});function _c(e){return xt({days:k(e.days,`--days`),sitemaps:A(e.sitemap),maxSitemapUrls:k(e[`max-sitemap-urls`],`--max-sitemap-urls`),templateLimit:k(e.limit,`--limit`),minimumTemplateUrls:k(e[`minimum-template-urls`],`--minimum-template-urls`),minimumTemplateShare:k(e[`minimum-template-share`],`--minimum-template-share`),minimumTemplateImpressions:k(e[`minimum-template-impressions`],`--minimum-template-impressions`),crawlSamples:k(e[`crawl-samples`],`--crawl-samples`),inspectSamples:k(e[`inspect-samples`],`--inspect-samples`),brandTerms:A(e[`brand-terms`]),includeBrand:D(e[`include-brand`]),js:D(e.js)?!0:`auto`,refresh:D(e.refresh),rate:ui(e)})}const vc=x({meta:{name:`pseo`,description:`Export pSEO audit tables: templates, demand patterns, top queries, sample coverage, crawl samples, and inspection samples.`},args:{...Xs,out:Zs(`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=_c(e),n=await V({client:E(e),site:T(e.site)}),r=await Jt({site:n.site,...t,brandTerms:t.brandTerms??n.client?.brandTerms,progress:Hs(!0)}),i=T(e.out)??$s({report:`pseo`,clientId:n.client?.id,site:n.site});tc(i,await ec(i,Ct(r)))}}),yc=x({meta:{name:`refresh-priorities`,description:`Export the ranked SEO priority queue, score breakdowns, grouped findings, and diagnosis detail CSVs.`},args:{...Xs,out:Zs(`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.`}}),"google-analytics-property":{type:`string`,description:`Google Analytics property ID to use for analytics value. Defaults from the selected project.`},"verify-content":w(`Verify top opportunities against page title, meta, and content. Defaults to true.`,`Skip page content verification.`),"verify-limit":{type:`string`,description:`Maximum opportunity URLs to verify. Defaults to 5.`}},run:async({args:e})=>{let t=await V({client:E(e),site:T(e.site),options:{refresh:D(e.refresh)}}),n=await ln({site:t.site,days:O(e.days),recentDays:O(e.recent),limit:O(e.limit),brandTerms:t.client?.brandTerms,includeBrand:D(e[`include-brand`]),googleAnalyticsPropertyId:T(e[`google-analytics-property`])??t.client?.analytics.google?.propertyId,verifyContent:D(e[`verify-content`]),verifyLimit:O(e[`verify-limit`]),refresh:D(e.refresh)}),r=T(e.out)??$s({report:`refresh-priorities`,clientId:t.client?.id,site:t.site});tc(r,await ec(r,kt(n)))}}),bc=x({meta:{name:`export`,description:`Export full report data to CSV files`},subCommands:{diagnose:nc,knowledge:mc,narrative:gc,monthly:hc,"update-postmortem":x({meta:{name:`update-postmortem`,description:`Export update postmortem attribution, findings, template movement, and segment movers.`},args:{...Xs,out:Zs(`update-postmortem`),...X([`days`,`recentDays`,`limit`,`includeBrand`,`refresh`],{days:{description:`Diagnosis window length in days. Defaults to 90.`},limit:{description:`Maximum winners/losers per segment. Defaults to 20.`}}),"known-change":{type:`string`,description:`Manual site-side change to treat as a confounder, for example pruning pages or blocking traffic.`},"ignore-change-log":{type:`boolean`,default:!1,description:`Do not use saved change-log entries as confounders.`}},run:async({args:e})=>{let t=await V({client:E(e),site:T(e.site),options:{refresh:D(e.refresh)}}),n=await ie({site:t.site,days:O(e.days),recentDays:O(e.recent),limit:O(e.limit),brandTerms:t.client?.brandTerms,includeBrand:D(e[`include-brand`]),knownConfounders:T(e[`known-change`])?[T(e[`known-change`])??``]:void 0,includeChangeLog:!D(e[`ignore-change-log`]),refresh:D(e.refresh)}),r=T(e.out)??$s({report:`update-postmortem`,clientId:t.client?.id,site:t.site});tc(r,await ec(r,Qr(n)))}}),"refresh-priorities":yc,pseo:vc}}),xc={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 Sc(e){let t=T(e.site);if(t)return t;let n=await H({project:E(e),options:{json:M(e)}});if(!n)throw new _(`INVALID_INPUT`,`Pass --site or select a saved project.`);return n.siteUrl}async function Cc(e){let t=T(e.url),n=A(e.urls)??[],r=T(e.file);if(!t&&n.length===0&&!r)throw new _(`INVALID_INPUT`,`Pass --url, comma-separated --urls, or a newline-delimited --file.`);let i=[...t?[t]:[],...n];if(r){let e=await _r(r).catch(()=>void 0);if(!e)throw new _(`INVALID_INPUT`,`IndexNow URL file not found: ${r}`);if(!e.isFile()||e.size>21e5)throw new _(`INVALID_INPUT`,`IndexNow URL files must be regular files no larger than 2.1 MB.`);i.push(...(await pr(r,`utf8`)).split(/\r?\n/).map(e=>e.trim()).filter(Boolean))}return i}const wc=x({meta:{name:`indexnow`,description:`Set up and submit changed URLs with IndexNow`},subCommands:{setup:x({meta:{name:`setup`,description:`Generate an IndexNow key file for one site`},args:{...xc,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=T(e.output);if(!t)throw new _(`INVALID_INPUT`,`Pass --output with the public asset directory that deploys at the site root.`);let n=h({site:await Sc(e)}),i=nr(t),a=b(i,`${n.key}.txt`);await dr(i,{recursive:!0});try{await yr(a,`${n.key}\n`,{encoding:`utf8`,flag:`wx`,mode:384})}catch(e){throw e.code===`EEXIST`?new _(`INVALID_INPUT`,`Refusing to overwrite the existing file at ${a}.`):e}let o;try{o=await r(n)}catch(e){throw await vr(a).catch(()=>void 0),e}let s={configured:!0,host:n.host,keyLocation:n.keyLocation,keyFile:a,credentialSource:o,nextStep:`Deploy ${a}, then run seo indexnow verify --site ${new URL(n.keyLocation).origin}`};M(e)?L(s):R([[`Host`,s.host],[`Key file`,s.keyFile],[`Public URL`,s.keyLocation],[`Saved in`,s.credentialSource],[`Next`,s.nextStep]])}}),status:x({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 f(),n={configuredSites:t.length,sites:t};M(e)?L(n):z([`Host`,`Key location`,`Created`],t.map(e=>[e.host,e.keyLocation,e.createdAt]))}}),verify:x({meta:{name:`verify`,description:`Check the public IndexNow key file`},args:{...xc,json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=await Ut({site:await Sc(e)}),n={...await Ue({record:t.record}),credentialSource:t.source};M(e)?L(n):R([[`Verified`,n.verified?`yes`:`no`],[`HTTP status`,String(n.status)],[`Key location`,n.keyLocation],[`Credential`,n.credentialSource]]),n.verified||(process.exitCode=4)}}),submit:x({meta:{name:`submit`,description:`Notify IndexNow about changed URLs`},args:{...xc,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 Ut({site:await Sc(e)}),n=await On({record:t.record,urls:await Cc(e),dryRun:D(e[`dry-run`])}),r={...n,credentialSource:t.source};if(M(e)){L(r);return}R([[`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:x({meta:{name:`remove`,description:`Remove a saved IndexNow key`},args:{...xc,json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=await Oe(await Sc(e)),n={removed:t,note:`The deployed key file was not removed.`};M(e)?L(n):process.stdout.write(`${t?`Saved key removed.`:`No saved key found.`} ${n.note}\n`)}})}}),Tc=x({...bo,meta:{name:`init`,description:"Legacy alias for `seo start`."}}),Ec=x({meta:{name:`links`,description:`Review bounded referring-link evidence from 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.`},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 imported or Bing link rows. Defaults to 500 for Bing and 10000 for 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.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=M(e),n=T(e.file),r=T(e.format);if(r&&![`csv`,`json`,`jsonl`].includes(r))throw new _(`INVALID_INPUT`,`--format must be csv, json, or jsonl.`);let i=k(e[`row-limit`],`--row-limit`),a;if(n)a=await Be({file:n,format:r,rowLimit:i});else{let n=await H({project:E(e),options:{json:t}}),r=T(e.site)??Se(n);if(!r)throw new _(`INVALID_INPUT`,`Pass --site, connect Bing to a saved project, or import a link file with --file.`);a=await l({site:r,rowLimit:i,targetLimit:k(e[`target-limit`],`--target-limit`),detailPagesPerTarget:k(e[`detail-pages`],`--detail-pages`)})}let o=jn({evidence:a,limit:k(e.limit,`--limit`)});if(t){L(o);return}K({title:`Inbound link evidence`,target:o.provenance.provider,status:o.dataStatus===`complete`?`info`:`unknown`,summary:`${U(o.summary.observedLinks)} observed links from ${U(o.summary.referringDomains)} referring domains.`,metrics:[{label:`Evidence`,value:o.dataStatus},{label:`Observed links`,value:U(o.summary.observedLinks)},{label:`Referring domains`,value:U(o.summary.referringDomains)},{label:`Target pages`,value:U(o.summary.targetPages)},{label:`Returned rows`,value:`${U(o.selection.returnedRows)} of ${U(o.selection.availableRows)}`}]}),o.links.length&&q([`Source`,`Target`,`Anchor`],o.links.map(e=>[G(e.sourceUrl,58),G(e.targetUrl,58),G(e.anchorText??``,36)])),Y(`Warnings`,o.warnings),Y(`Report caveats`,o.caveats)}});async function Dc(e,t){if(!e){process.stdout.write(t);return}await dr(y(e),{recursive:!0}),await yr(e,t),process.stdout.write(`Wrote ${e}\n`)}const Oc={"report-id":{type:`string`,description:`Saved crawl report id to use.`},site:{type:`string`,description:`GSC property URL for selecting the latest saved crawl.`},client:{type:`string`,description:`Legacy alias for --project.`},project:{type:`string`,description:`Saved project id or name.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},kc=x({meta:{name:`llms`,description:`Inspect or generate optional llms.txt from saved crawl data`},subCommands:{audit:x({meta:{name:`audit`,description:`Inspect optional llms.txt presence from a saved crawl`},args:Oc,run:async({args:e})=>{let t=M(e),n=u(await oa(e,{json:t}));if(t){L(n);return}K({title:`llms.txt audit`,target:n.url,status:n.issues.length>0?`warning`:`pass`,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(`Recommended pages`,n.recommendedPages.slice(0,10).map(e=>`${e.section}: ${e.url}`))}}),generate:x({meta:{name:`generate`,description:`Generate an llms.txt draft from a saved crawl`},args:{...Oc,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=M(e),n=kn(await oa(e,{json:t}),{maxUrls:O(e[`max-urls`]),tokenBudget:O(e[`token-budget`]),exclude:A(e.exclude),title:T(e.title),description:T(e.description)});if(t){L(n);return}await Dc(T(e.output),n.content),T(e.output)&&R([[`URLs`,String(n.includedUrls)],[`Estimated tokens`,String(n.estimatedTokens)]])}})}}),Ac=x({meta:{name:`logs`,description:`Manage local log retention`},subCommands:{prune:x({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=_t();if(M(e)){L(t);return}e.quiet||process.stdout.write(`Rotated ${t.rotated} and removed ${t.removed} log files. ${ta(t.sizeBytes)} retained.\n`)}})}}),jc=[{client:`claude-desktop`,flag:`claude-desktop`},{client:`claude-code`,flag:`claude-code`},{client:`codex`,flag:`codex`},{client:`cursor`,flag:`cursor`}];function Mc(e,t){return D(e.all)?t:jc.filter(({flag:t})=>D(e[t])).map(({client:e})=>e)}const Nc=x({meta:{name:`mcp`,description:`MCP server helpers`},subCommands:{serve:x({meta:{name:`serve`,description:`Run the SEO MCP server`},args:{test:{type:`boolean`,default:!1,description:`Start in test mode`}},run:async({args:e})=>{let{startMcpServer:t}=await import(`./dist-B7Sb0jRt.js`);await t({test:D(e.test)})}}),install:x({meta:{name:`install`,description:`Install or remove SEO MCP config for local clients`},args:{uninstall:{type:`boolean`,default:!1,description:`Remove the SEO MCP config instead of adding it`},reinstall:{type:`boolean`,default:!1,description:`Rewrite an existing managed SEO MCP config`},"claude-desktop":{type:`boolean`,default:!1,description:`Update Claude Desktop`},"claude-code":{type:`boolean`,default:!1,description:`Update Claude Code`},codex:{type:`boolean`,default:!1,description:`Update Codex CLI, app, and IDE extension`},cursor:{type:`boolean`,default:!1,description:`Update Cursor`},all:{type:`boolean`,default:!1,description:`Update every detected MCP client`},json:{type:`boolean`,default:!1,description:`Print structured JSON output`}},run:async({args:e})=>{if(D(e.uninstall)&&D(e.reinstall))throw new _(`INVALID_INPUT`,`Use either --uninstall or --reinstall, not both.`);let t=Fa(),n=Ra({targets:t}),r=M(e),i=Mc(e,n.map(e=>e.client));if(i.length===0){if(!I({json:r}))throw new _(`INVALID_INPUT`,`Choose an MCP client with --claude-desktop, --claude-code, --codex, --cursor, or --all.`);let a=n.length>0?n:t;i=B(await Dr({message:e.uninstall?`Remove seo from which MCP clients?`:`Install seo into which MCP clients?`,options:a.map(e=>({value:e.client,label:e.label,hint:e.path})),initialValues:n.map(e=>e.client),required:!0}))}let a=i.filter(e=>!t.some(t=>t.client===e));if(a.length>0)throw new _(`INVALID_INPUT`,`MCP setup is not supported for ${a.join(`, `)} on ${process.platform}.`);let o=t.filter(e=>i.includes(e.client)).map(t=>D(e.uninstall)?ro(t):no(t,{reinstall:D(e.reinstall)}));if(r){L({operation:D(e.uninstall)?`uninstall`:`install`,results:o});return}for(let e of o)process.stdout.write(`${e.changed?`updated`:`skipped`} ${e.client} · ${e.path}\n`)}})}}),Pc=x({meta:{name:`crawl-diff`,description:`Crawl a site and diff technical/page changes vs last run`},args:{url:{type:`string`,required:!0,description:`Start URL to crawl.`},site:{type:`string`,description:`Optional GSC property to associate with the crawl.`},limit:{type:`string`,description:`Maximum pages to crawl. Defaults to 50.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`},refresh:{type:`boolean`,default:!1,description:`Bypass local HTTP cache.`},js:{type:`boolean`,default:!1,description:`Force JavaScript rendering when Playwright is installed.`}},run:async({args:e})=>{let t=await Dn({startUrl:T(e.url)??``,site:T(e.site),limit:O(e.limit),refresh:D(e.refresh),js:D(e.js)?!0:`auto`});if(M(e)){L(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
173
|
`),z([`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
|