seo 0.2.13 → 0.2.14
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/README.md +1 -1
- package/dist/cli.js +2 -2
- package/dist/{dist-BB0yH7qK.js → dist-Bc8MJ1gT.js} +1 -1
- package/dist/{dist-Dwdogl4j.js → dist-sYeCtY3Q.js} +87 -87
- package/dist/dist-sYeCtY3Q.js.map +1 -0
- package/dist/index.d.ts +70 -2
- package/dist/index.js +88 -38
- package/dist/index.js.map +1 -1
- package/dist/mcp.js +88 -38
- package/dist/mcp.js.map +1 -1
- package/dist/{package-B_swz2Xd.js → package-Cp8DwqCy.js} +2 -2
- package/dist/{package-B_swz2Xd.js.map → package-Cp8DwqCy.js.map} +1 -1
- package/package.json +1 -1
- package/dist/dist-Dwdogl4j.js.map +0 -1
package/README.md
CHANGED
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,$t as t,A as n,An as r,At as i,B as a,Bn as o,Bt as s,C as c,Cn as l,Ct as u,D as d,Dn as f,Dt as p,E as m,En as h,Et as ee,F as g,Fn as te,Ft as ne,G as re,Gt as _,H as v,Hn as y,Ht as ie,I as ae,In as oe,It as se,J as ce,Jt as le,K as ue,Kt as de,L as fe,Ln as pe,Lt as me,M as he,Mn as ge,Mt as _e,N as ve,Nn as ye,Nt as be,O as xe,On as Se,Ot as Ce,P as we,Pn as Te,Pt as Ee,Q as De,Qt as Oe,R as ke,Rn as Ae,Rt as je,S as Me,Sn as Ne,St as Pe,T as Fe,Tn as Ie,Tt as Le,U as Re,Un as ze,Ut as Be,V as Ve,Vn as He,Vt as Ue,W as We,Wn as Ge,Wt as Ke,X as qe,Xt as Je,Y as Ye,Yt as Xe,Z as Ze,Zt as Qe,_ as $e,_n as et,_t as tt,a as nt,an as rt,at as it,b as at,bn as ot,bt as st,cn as ct,ct as lt,d as ut,dn as dt,dt as ft,en as pt,et as mt,f as ht,fn as gt,ft as _t,g as vt,gn as yt,gt as bt,h as xt,hn as St,ht as Ct,i as wt,in as Tt,it as Et,j as Dt,jn as Ot,jt as kt,k as At,kn as jt,kt as Mt,l as Nt,ln as Pt,lt as Ft,m as It,mn as Lt,mt as Rt,nn as zt,nt as Bt,o as Vt,on as Ht,ot as Ut,p as Wt,pn as Gt,pt as Kt,q as qt,qt as Jt,r as Yt,rn as Xt,rt as Zt,sn as Qt,st as $t,t as en,tn,tt as nn,u as rn,un as an,ut as on,v as sn,vn as cn,vt as ln,w as un,wn as dn,wt as fn,x as pn,xn as mn,xt as hn,y as gn,yn as _n,yt as vn,z as yn,zn as bn,zt as xn}from"./dist-
|
|
3
|
+
import{$ as e,$t as t,A as n,An as r,At as i,B as a,Bn as o,Bt as s,C as c,Cn as l,Ct as u,D as d,Dn as f,Dt as p,E as m,En as h,Et as ee,F as g,Fn as te,Ft as ne,G as re,Gt as _,H as v,Hn as y,Ht as ie,I as ae,In as oe,It as se,J as ce,Jt as le,K as ue,Kt as de,L as fe,Ln as pe,Lt as me,M as he,Mn as ge,Mt as _e,N as ve,Nn as ye,Nt as be,O as xe,On as Se,Ot as Ce,P as we,Pn as Te,Pt as Ee,Q as De,Qt as Oe,R as ke,Rn as Ae,Rt as je,S as Me,Sn as Ne,St as Pe,T as Fe,Tn as Ie,Tt as Le,U as Re,Un as ze,Ut as Be,V as Ve,Vn as He,Vt as Ue,W as We,Wn as Ge,Wt as Ke,X as qe,Xt as Je,Y as Ye,Yt as Xe,Z as Ze,Zt as Qe,_ as $e,_n as et,_t as tt,a as nt,an as rt,at as it,b as at,bn as ot,bt as st,cn as ct,ct as lt,d as ut,dn as dt,dt as ft,en as pt,et as mt,f as ht,fn as gt,ft as _t,g as vt,gn as yt,gt as bt,h as xt,hn as St,ht as Ct,i as wt,in as Tt,it as Et,j as Dt,jn as Ot,jt as kt,k as At,kn as jt,kt as Mt,l as Nt,ln as Pt,lt as Ft,m as It,mn as Lt,mt as Rt,nn as zt,nt as Bt,o as Vt,on as Ht,ot as Ut,p as Wt,pn as Gt,pt as Kt,q as qt,qt as Jt,r as Yt,rn as Xt,rt as Zt,sn as Qt,st as $t,t as en,tn,tt as nn,u as rn,un as an,ut as on,v as sn,vn as cn,vt as ln,w as un,wn as dn,wt as fn,x as pn,xn as mn,xt as hn,y as gn,yn as _n,yt as vn,z as yn,zn as bn,zt as xn}from"./dist-sYeCtY3Q.js";import{constants as Sn,copyFileSync as Cn,createWriteStream as wn,existsSync as b,mkdirSync as Tn,readFileSync as En,readdirSync as Dn,realpathSync as On,renameSync as kn,rmSync as An,statSync as jn,writeFileSync as Mn}from"node:fs";import{homedir as Nn}from"node:os";import{basename as Pn,dirname as x,isAbsolute as Fn,join as S,posix as In,relative as Ln,resolve as Rn,win32 as zn}from"node:path";import{randomUUID as Bn}from"node:crypto";import{spawn as Vn,spawnSync as Hn}from"node:child_process";import{fileURLToPath as Un}from"node:url";import{isDeepStrictEqual as Wn,stripVTControlCharacters as Gn}from"node:util";import{lstat as Kn,mkdir as qn,mkdtemp as Jn,readFile as Yn,readdir as Xn,rename as Zn,rm as Qn,stat as $n,unlink as er,writeFile as tr}from"node:fs/promises";import{defineCommand as C,runCommand as nr,runMain as rr}from"citty";import{cancel as ir,confirm as ar,intro as or,isCancel as sr,log as cr,multiselect as lr,note as w,outro as ur,password as dr,select as fr,text as pr}from"@clack/prompts";import mr from"picocolors";import hr from"string-width";import gr from"cli-table3";import{applyEdits as _r,modify as vr,parse as yr,printParseErrorCode as br}from"jsonc-parser";import{finished as xr}from"node:stream/promises";import Sr from"update-notifier";function T(e){return e.replaceAll(`&`,`&`).replaceAll(`<`,`<`).replaceAll(`>`,`>`).replaceAll(`"`,`"`).replaceAll(`'`,`'`)}function Cr(e){try{let t=new URL(e);return t.protocol===`http:`||t.protocol===`https:`?t.toString():void 0}catch{return}}function wr(e){let t=Cr(e),n=T(e);return t?`<a href="${T(t)}" rel="noreferrer">${n}</a>`:n}function Tr(e){return e.length===0?`<p class="empty">No items to report.</p>`:`<ul>${e.map(e=>`<li>${T(e)}</li>`).join(``)}</ul>`}function Er(e){return e.sections.map(e=>`<section class="panel">
|
|
4
4
|
<h2>${T(e.title)}</h2>
|
|
5
5
|
${Tr(e.bullets)}
|
|
6
6
|
</section>`).join(``)}function Dr(e){return e.map(e=>`<section class="panel">
|
|
@@ -164,7 +164,7 @@ Google Analytics impact
|
|
|
164
164
|
`),B([`Metric`,`Before`,`After`,`Delta`],[[`Sessions`,U(e.analytics.before.metrics.sessions),U(e.analytics.after.metrics.sessions),`${U(e.analytics.delta.sessions)} (${ns(e.analytics.delta.sessionPct)})`],[`Conversions`,U(e.analytics.before.metrics.conversions),U(e.analytics.after.metrics.conversions),`${U(e.analytics.delta.conversions)} (${ns(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)} (${ns(e.analytics.delta.revenuePct)})`]])),e.control&&(process.stdout.write(`
|
|
165
165
|
Control comparison
|
|
166
166
|
`),z([[`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 ss=C({meta:{name:`tests`,description:`Create and report local SEO tests from Search Console and Google Analytics data`},subCommands:{list:C({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=N(e),n=D(e),r=fn({site:(n||E(e.site)?await H({client:n,site:E(e.site),options:{json:t}}):void 0)?.site,limit:k(e.limit)});if(t){R({tests:r});return}B([`ID`,`Date`,`Scope`,`Target`,`Title`],r.map(e=>[e.id,e.changedAt,e.scope,e.target,e.title]))}}),create:C({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=N(e),n=await H({client:D(e),site:E(e.site),options:{json:t}}),r=Le({site:n.site,scope:es(e.scope),target:E(e.target)??``,title:E(e.title)??``,description:E(e.description),changedAt:E(e.date)??$o()});if(t){R(r);return}z([[`ID`,r.id],[`Date`,r.changedAt],[`Project`,n.client?.name??n.site],[`Scope`,r.scope],[`Target`,r.target],[`Title`,r.title]])}}),report:C({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=N(e),n=E(e.id),r=!n||D(e)||E(e.site)?await H({client:D(e),site:E(e.site),options:{json:t,refresh:O(e.refresh)}}):void 0,i=await bt({id:n,site:n?void 0:r?.site,scope:n?void 0:ts(e.scope),target:n?void 0:E(e.target),title:E(e.title),changedAt:n?void 0:E(e.date),googleAnalyticsPropertyId:E(e.property)??r?.client?.analytics.google?.propertyId,controlScope:ts(e[`control-scope`]),controlTarget:E(e[`control-target`]),controlTitle:E(e[`control-title`]),beforeDays:A(e.before,`--before`),afterDays:A(e.after,`--after`),refresh:O(e.refresh)});if(t){R(i);return}os(i)}})}});function cs(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 ls={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.`}},us={...X([`includeBrand`,`verifyContent`,`verifyLimit`,`js`,`fetchConcurrency`,`fetchIntervalCap`,`fetchIntervalMs`,`refresh`]),json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}};function ds(e){return{includeBrand:O(e[`include-brand`]),verifyContent:O(e[`verify-content`]),verifyLimit:k(e[`verify-limit`])??3,js:O(e.js)?!0:void 0,rate:Kr(e),refresh:O(e.refresh)}}function fs(e){return{client:D(e),site:E(e.site),refresh:O(e.refresh)}}const{json:ps,...ms}=us,hs=ms,gs={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 _s(e){return{type:`string`,description:`Output folder. Defaults to ./seo-export/<project>-${e}-YYYY-MM-DD.`}}function vs(e){return e.toLowerCase().replace(/^sc-domain:/,``).replace(/^https?:\/\//,``).replace(/[^a-z0-9._-]+/g,`-`).replace(/^-+|-+$/g,``)}function ys(e){let t=new Date().toISOString().slice(0,10),n=vs(e.clientId??e.site);return Rn(process.cwd(),`seo-export`,`${n}-${e.report}-${t}`)}async function bs(e,t){await qn(e,{recursive:!0});let n=[];for(let r of t){let t=Rn(e,r.filename);await tr(t,Qe(r.rows,r.headers),`utf8`),n.push(t)}return n}function xs(e,t){process.stdout.write(`Wrote ${me(t.length,`CSV file`)} to ${e}\n`);for(let e of t)process.stdout.write(`- ${e}\n`)}const Ss=C({meta:{name:`diagnose`,description:`Export diagnosis tables: priorities, anomalies, movement, decay, cannibalisation, striking-distance, and quick wins.`},args:{...gs,out:_s(`diagnose`),days:{type:`string`,description:`GSC lookback window. Defaults to 90.`},recent:{type:`string`,description:`Recent anomaly window in days.`},limit:{type:`string`,description:`Maximum rows per movement/opportunity table.`},"include-brand":{type:`boolean`,default:!1,description:`Include branded queries in opportunity reports.`},"verify-content":{type:`boolean`,default:!1,description:`Verify top opportunities against page title, meta, and content.`},"verify-limit":{type:`string`,description:`Maximum opportunity URLs to verify.`},js:{type:`boolean`,default:!1,description:`Force JavaScript rendering for verified pages.`},"fetch-concurrency":{type:`string`,description:`Maximum concurrent page fetches per host. Defaults to 4.`},"fetch-interval-cap":{type:`string`,description:`Maximum page fetches per interval per host. Defaults to 4.`},"fetch-interval-ms":{type:`string`,description:`Fetch rate interval in milliseconds. Defaults to 1000.`},refresh:{type:`boolean`,default:!1,description:`Bypass local cache and fetch fresh data.`}},run:async({args:e})=>{let t=await H({client:D(e),site:E(e.site)}),n=await st({site:t.site,days:k(e.days),recentDays:k(e.recent),limit:k(e.limit),brandTerms:t.client?.brandTerms,includeBrand:O(e[`include-brand`]),verifyContent:O(e[`verify-content`]),verifyLimit:k(e[`verify-limit`]),js:O(e.js)?!0:`auto`,rate:Kr(e),refresh:O(e.refresh),progress:cs(!0)}),r=E(e.out)??ys({report:`diagnose`,clientId:t.client?.id,site:t.site});xs(r,await bs(r,Jt(n)))}}),Cs=`.seo-okf.json`,ws=`${JSON.stringify({generator:`seo`,format:`okf`,version:1})}\n`;function Ts(e,t){let n=Rn(e,t),r=Ln(Rn(e),n);if(!r||r.startsWith(`..`)||Fn(r))throw new y(`INVALID_INPUT`,`Unsafe OKF file path: ${t}`);return n}async function Es(e,t){for(let n of t){let t=Ts(e,n.path);await qn(x(t),{recursive:!0}),await tr(t,n.content)}await tr(S(e,Cs),ws)}async function Ds(e){let t=[];async function n(e,r=``){let i=await Xn(e,{withFileTypes:!0});for(let a of i){let i=r?`${r}/${a.name}`:a.name,o=S(e,a.name);a.isDirectory()?await n(o,i):a.isFile()&&a.name.endsWith(`.md`)&&t.push({path:i,content:await Yn(o,`utf8`)})}}return await n(e),t.sort((e,t)=>e.path<t.path?-1:+(e.path>t.path))}async function Os(e){try{let t=await Kn(e);if(t.isSymbolicLink()||!t.isDirectory())throw new y(`INVALID_INPUT`,`OKF output must be a real directory: ${e}`);if(!(await Xn(e)).length)return!0;try{if(await Yn(S(e,Cs),`utf8`)===ws)return!0}catch{}throw new y(`INVALID_INPUT`,`Refusing to replace non-empty unmanaged directory: ${e}`)}catch(e){if(e.code===`ENOENT`)return!1;throw e}}async function ks(e,t){if(!se(t).valid)throw new y(`INVALID_INPUT`,`Generated bundle did not pass seo OKF checks and was not written.`);let n=Rn(e),r=x(n);await qn(r,{recursive:!0});let i=await Os(n),a=await Jn(S(r,`.${Pn(n)}-staging-`)),o=S(r,`.${Pn(n)}-backup-${Bn()}`),s=!1;try{await Es(a,t);let e=se(await Ds(a));if(!e.valid)throw new y(`INTERNAL_ERROR`,`Staged OKF files failed validation after writing.`);return i&&(await Zn(n,o),s=!0),await Zn(a,n),s&&await Qn(o,{recursive:!0,force:!0}),e}catch(e){throw await Qn(a,{recursive:!0,force:!0}),s&&(await Qn(n,{recursive:!0,force:!0}),await Zn(o,n)),e}}async function As(e,t){if(!e){process.stdout.write(t);return}await qn(x(e),{recursive:!0}),await tr(e,t),process.stdout.write(`Wrote ${e}\n`)}function js(e){let t=E(e)??`okf`;if([`okf`,`markdown`,`json`].includes(t))return t;throw Error(`Format must be one of: okf, markdown, json.`)}function Ms(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:Pt(e),entityReadiness:an(e),llmsTxt:Qt(e)}}function Ns(e){let t=Ms(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(`
|
|
167
|
-
`)}\n`}const Ps=C({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=N(e),n=js(e.format),r=n===`okf`?ne(A(e[`max-concepts`],`--max-concepts`)):void 0,i=await Wi(e,{json:t}),a=E(e.output);if(n===`okf`){let e=be(i,{maxConcepts:r}),o=a??`./okf`,s=await ks(o,e.files);if(t){R({format:n,output:o,bundle:e,validation:s});return}process.stdout.write(`Wrote OKF bundle to ${o}\n\n`),z([[`Files`,String(e.files.length)],[`Concepts`,String(e.conceptCount)],[`Valid`,s.valid?`yes`:`no`]]);return}if(n===`json`){await As(a,`${JSON.stringify(Ms(i),null,2)}\n`);return}await As(a,Ns(i))}}),Fs=C({meta:{name:`monthly`,description:`Export monthly report tables plus the underlying diagnosis detail CSVs.`},args:{...gs,out:_s(`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.`}}),...hs},run:async({args:e})=>{let t=await H({client:D(e),site:E(e.site),options:{refresh:O(e.refresh)}}),n=await nn({site:t.site,month:E(e.month),limit:k(e.limit),brandTerms:t.client?.brandTerms,...ds(e),progress:cs(!0)}),r=E(e.out)??ys({report:`monthly`,clientId:t.client?.id,site:t.site});xs(r,await bs(r,le(n)))}}),Is=C({meta:{name:`narrative`,description:`Export report narrative tables plus the underlying diagnosis detail CSVs.`},args:{...gs,out:_s(`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.`},...hs},run:async({args:e})=>{let t=await H({client:D(e),site:E(e.site),options:{refresh:O(e.refresh)}}),n=await Bt({site:t.site,days:k(e.days),recentDays:k(e.recent),startDate:E(e[`start-date`]),endDate:E(e[`end-date`]),limit:k(e.limit),changeLimit:k(e[`change-limit`]),brandTerms:t.client?.brandTerms,...ds(e),progress:cs(!0)}),r=E(e.out)??ys({report:`narrative`,clientId:t.client?.id,site:t.site});xs(r,await bs(r,le(n)))}});function Ls(e){return Et({days:A(e.days,`--days`),sitemaps:j(e.sitemap),maxSitemapUrls:A(e[`max-sitemap-urls`],`--max-sitemap-urls`),templateLimit:A(e.limit,`--limit`),minimumTemplateUrls:A(e[`minimum-template-urls`],`--minimum-template-urls`),minimumTemplateShare:A(e[`minimum-template-share`],`--minimum-template-share`),minimumTemplateImpressions:A(e[`minimum-template-impressions`],`--minimum-template-impressions`),crawlSamples:A(e[`crawl-samples`],`--crawl-samples`),inspectSamples:A(e[`inspect-samples`],`--inspect-samples`),brandTerms:j(e[`brand-terms`]),includeBrand:O(e[`include-brand`]),js:O(e.js)?!0:`auto`,refresh:O(e.refresh),rate:qr(e)})}const Rs=C({meta:{name:`pseo`,description:`Export pSEO audit tables: templates, demand patterns, top queries, sample coverage, crawl samples, and inspection samples.`},args:{...gs,out:_s(`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=Ls(e),n=await H({client:D(e),site:E(e.site)}),r=await it({site:n.site,...t,brandTerms:t.brandTerms??n.client?.brandTerms,progress:cs(!0)}),i=E(e.out)??ys({report:`pseo`,clientId:n.client?.id,site:n.site});xs(i,await bs(i,Xe(r)))}}),zs=C({meta:{name:`refresh-priorities`,description:`Export the ranked SEO priority queue, score breakdowns, grouped findings, and diagnosis detail CSVs.`},args:{...gs,out:_s(`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":Vr(`Verify top opportunities against page title, meta, and content. Defaults to true.`,`Skip page content verification.`),"verify-limit":{type:`string`,description:`Maximum opportunity URLs to verify. Defaults to 5.`}},run:async({args:e})=>{let t=await H({client:D(e),site:E(e.site),options:{refresh:O(e.refresh)}}),n=await qe({site:t.site,days:k(e.days),recentDays:k(e.recent),limit:k(e.limit),brandTerms:t.client?.brandTerms,includeBrand:O(e[`include-brand`]),googleAnalyticsPropertyId:E(e[`google-analytics-property`])??t.client?.analytics.google?.propertyId,verifyContent:O(e[`verify-content`]),verifyLimit:k(e[`verify-limit`]),refresh:O(e.refresh)}),r=E(e.out)??ys({report:`refresh-priorities`,clientId:t.client?.id,site:t.site});xs(r,await bs(r,Je(n)))}}),Bs=C({meta:{name:`export`,description:`Export full report data to CSV files`},subCommands:{diagnose:Ss,knowledge:Ps,narrative:Is,monthly:Fs,"update-postmortem":C({meta:{name:`update-postmortem`,description:`Export update postmortem attribution, findings, template movement, and segment movers.`},args:{...gs,out:_s(`update-postmortem`),...X([`days`,`recentDays`,`limit`,`includeBrand`,`refresh`],{days:{description:`Diagnosis window length in days. Defaults to 90.`},limit:{description:`Maximum winners/losers per segment. Defaults to 20.`}}),"known-change":{type:`string`,description:`Manual site-side change to treat as a confounder, for example pruning pages or blocking traffic.`},"ignore-change-log":{type:`boolean`,default:!1,description:`Do not use saved change-log entries as confounders.`}},run:async({args:e})=>{let t=await H({client:D(e),site:E(e.site),options:{refresh:O(e.refresh)}}),n=await ce({site:t.site,days:k(e.days),recentDays:k(e.recent),limit:k(e.limit),brandTerms:t.client?.brandTerms,includeBrand:O(e[`include-brand`]),knownConfounders:E(e[`known-change`])?[E(e[`known-change`])??``]:void 0,includeChangeLog:!O(e[`ignore-change-log`]),refresh:O(e.refresh)}),r=E(e.out)??ys({report:`update-postmortem`,clientId:t.client?.id,site:t.site});xs(r,await bs(r,Pr(n)))}}),"refresh-priorities":zs,pseo:Rs}}),Vs={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 Hs(e){let t=E(e.site);if(t)return t;let n=await Hi({project:D(e),options:{json:N(e)}});if(!n)throw new y(`INVALID_INPUT`,`Pass --site or select a saved project.`);return n.siteUrl}async function Us(e){let t=E(e.url),n=j(e.urls)??[],r=E(e.file);if(!t&&n.length===0&&!r)throw new y(`INVALID_INPUT`,`Pass --url, comma-separated --urls, or a newline-delimited --file.`);let i=[...t?[t]:[],...n];if(r){let e=await $n(r).catch(()=>void 0);if(!e)throw new y(`INVALID_INPUT`,`IndexNow URL file not found: ${r}`);if(!e.isFile()||e.size>21e5)throw new y(`INVALID_INPUT`,`IndexNow URL files must be regular files no larger than 2.1 MB.`);i.push(...(await Yn(r,`utf8`)).split(/\r?\n/).map(e=>e.trim()).filter(Boolean))}return i}const Ws=C({meta:{name:`indexnow`,description:`Set up and submit changed URLs with IndexNow`},subCommands:{setup:C({meta:{name:`setup`,description:`Generate an IndexNow key file for one site`},args:{...Vs,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=E(e.output);if(!t)throw new y(`INVALID_INPUT`,`Pass --output with the public asset directory that deploys at the site root.`);let r=m({site:await Hs(e)}),i=Rn(t),a=S(i,`${r.key}.txt`);await qn(i,{recursive:!0});try{await tr(a,`${r.key}\n`,{encoding:`utf8`,flag:`wx`,mode:384})}catch(e){throw e.code===`EEXIST`?new y(`INVALID_INPUT`,`Refusing to overwrite the existing file at ${a}.`):e}let o;try{o=await n(r)}catch(e){throw await er(a).catch(()=>void 0),e}let s={configured:!0,host:r.host,keyLocation:r.keyLocation,keyFile:a,credentialSource:o,nextStep:`Deploy ${a}, then run seo indexnow verify --site ${new URL(r.keyLocation).origin}`};N(e)?R(s):z([[`Host`,s.host],[`Key file`,s.keyFile],[`Public URL`,s.keyLocation],[`Saved in`,s.credentialSource],[`Next`,s.nextStep]])}}),status:C({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 d(),n={configuredSites:t.length,sites:t};N(e)?R(n):B([`Host`,`Key location`,`Created`],t.map(e=>[e.host,e.keyLocation,e.createdAt]))}}),verify:C({meta:{name:`verify`,description:`Check the public IndexNow key file`},args:{...Vs,json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=await At({site:await Hs(e)}),n={...await Fe({record:t.record}),credentialSource:t.source};N(e)?R(n):z([[`Verified`,n.verified?`yes`:`no`],[`HTTP status`,String(n.status)],[`Key location`,n.keyLocation],[`Credential`,n.credentialSource]]),n.verified||(process.exitCode=4)}}),submit:C({meta:{name:`submit`,description:`Notify IndexNow about changed URLs`},args:{...Vs,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 At({site:await Hs(e)}),n=await un({record:t.record,urls:await Us(e),dryRun:O(e[`dry-run`])}),r={...n,credentialSource:t.source};if(N(e)){R(r);return}z([[`Host`,n.host],[`Status`,n.status],[`URLs`,String(n.submittedUrls)],[`Dry run`,n.dryRun?`yes`:`no`],[`Key location`,n.keyLocation]]);for(let e of n.caveats)process.stdout.write(`\n${e}\n`)}}),remove:C({meta:{name:`remove`,description:`Remove a saved IndexNow key`},args:{...Vs,json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=await xe(await Hs(e)),n={removed:t,note:`The deployed key file was not removed.`};N(e)?R(n):process.stdout.write(`${t?`Saved key removed.`:`No saved key found.`} ${n.note}\n`)}})}}),Gs=C({...to,meta:{name:`init`,description:"Legacy alias for `seo start`."}}),Ks=C({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=N(e),n=E(e.file),r=E(e.format);if(r&&![`csv`,`json`,`jsonl`].includes(r))throw new y(`INVALID_INPUT`,`--format must be csv, json, or jsonl.`);let i=A(e[`row-limit`],`--row-limit`),a;if(n)a=await Me({file:n,format:r,rowLimit:i});else{let n=await Hi({project:D(e),options:{json:t}}),r=E(e.site)??he(n);if(!r)throw new y(`INVALID_INPUT`,`Pass --site, connect Bing to a saved project, or import a link file with --file.`);a=await c({site:r,rowLimit:i,targetLimit:A(e[`target-limit`],`--target-limit`),detailPagesPerTarget:A(e[`detail-pages`],`--detail-pages`)})}let o=pn({evidence:a,limit:A(e.limit,`--limit`)});if(t){R(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 qs(e,t){if(!e){process.stdout.write(t);return}await qn(x(e),{recursive:!0}),await tr(e,t),process.stdout.write(`Wrote ${e}\n`)}const Js={"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.`}},Ys=C({meta:{name:`llms`,description:`Inspect or generate optional llms.txt from saved crawl data`},subCommands:{audit:C({meta:{name:`audit`,description:`Inspect optional llms.txt presence from a saved crawl`},args:Js,run:async({args:e})=>{let t=N(e),n=Qt(await Wi(e,{json:t}));if(t){R(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:C({meta:{name:`generate`,description:`Generate an llms.txt draft from a saved crawl`},args:{...Js,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=N(e),n=ct(await Wi(e,{json:t}),{maxUrls:k(e[`max-urls`]),tokenBudget:k(e[`token-budget`]),exclude:j(e.exclude),title:E(e.title),description:E(e.description)});if(t){R(n);return}await qs(E(e.output),n.content),E(e.output)&&z([[`URLs`,String(n.includedUrls)],[`Estimated tokens`,String(n.estimatedTokens)]])}})}}),Xs=C({meta:{name:`logs`,description:`Manage local log retention`},subCommands:{prune:C({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=at();if(N(e)){R(t);return}e.quiet||process.stdout.write(`Rotated ${t.rotated} and removed ${t.removed} log files. ${Ri(t.sizeBytes)} retained.\n`)}})}}),Zs=[{client:`claude-desktop`,flag:`claude-desktop`},{client:`claude-code`,flag:`claude-code`},{client:`codex`,flag:`codex`},{client:`cursor`,flag:`cursor`}];function Qs(e,t){return O(e.all)?t:Zs.filter(({flag:t})=>O(e[t])).map(({client:e})=>e)}const $s=C({meta:{name:`mcp`,description:`MCP server helpers`},subCommands:{serve:C({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-BB0yH7qK.js`);await t({test:O(e.test)})}}),install:C({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(O(e.uninstall)&&O(e.reinstall))throw new y(`INVALID_INPUT`,`Use either --uninstall or --reinstall, not both.`);let t=_a(),n=ba({targets:t}),r=N(e),i=Qs(e,n.map(e=>e.client));if(i.length===0){if(!L({json:r}))throw new y(`INVALID_INPUT`,`Choose an MCP client with --claude-desktop, --claude-code, --codex, --cursor, or --all.`);let a=n.length>0?n:t;i=V(await lr({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 y(`INVALID_INPUT`,`MCP setup is not supported for ${a.join(`, `)} on ${process.platform}.`);let o=t.filter(e=>i.includes(e.client)).map(t=>O(e.uninstall)?za(t):Ra(t,{reinstall:O(e.reinstall)}));if(r){R({operation:O(e.uninstall)?`uninstall`:`install`,results:o});return}for(let e of o)process.stdout.write(`${e.changed?`updated`:`skipped`} ${e.client} · ${e.path}\n`)}})}}),ec=C({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 Rt({startUrl:E(e.url)??``,site:E(e.site),limit:k(e.limit),refresh:O(e.refresh),js:O(e.js)?!0:`auto`});if(N(e)){R(t);return}if(K({title:`Crawl history comparison`,target:t.run.startUrl,status:t.previousRun===void 0?`unknown`:t.summary.newErrors>0||t.summary.indexabilityFlips>0?`warning`:`pass`,summary:t.previousRun===void 0?`No previous crawl was available for comparison.`:`${t.summary.changed} changed, ${t.summary.added} added, and ${t.summary.removed} removed URLs.`,metrics:[{label:`Run`,value:t.run.id},{label:`Crawled`,value:t.summary.crawled},{label:`Previous run`,value:t.previousRun?.id??`None`},{label:`Added`,value:t.summary.added},{label:`Removed`,value:t.summary.removed},{label:`Changed`,value:t.summary.changed},{label:`New errors`,value:t.summary.newErrors},{label:`Indexability flips`,value:t.summary.indexabilityFlips},{label:`High priority`,value:t.summary.highPriorityRecommendations}]}),t.recommendations.length&&(process.stdout.write(`
|
|
167
|
+
`)}\n`}const Ps=C({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=N(e),n=js(e.format),r=n===`okf`?ne(A(e[`max-concepts`],`--max-concepts`)):void 0,i=await Wi(e,{json:t}),a=E(e.output);if(n===`okf`){let e=be(i,{maxConcepts:r}),o=a??`./okf`,s=await ks(o,e.files);if(t){R({format:n,output:o,bundle:e,validation:s});return}process.stdout.write(`Wrote OKF bundle to ${o}\n\n`),z([[`Files`,String(e.files.length)],[`Concepts`,String(e.conceptCount)],[`Valid`,s.valid?`yes`:`no`]]);return}if(n===`json`){await As(a,`${JSON.stringify(Ms(i),null,2)}\n`);return}await As(a,Ns(i))}}),Fs=C({meta:{name:`monthly`,description:`Export monthly report tables plus the underlying diagnosis detail CSVs.`},args:{...gs,out:_s(`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.`}}),...hs},run:async({args:e})=>{let t=await H({client:D(e),site:E(e.site),options:{refresh:O(e.refresh)}}),n=await nn({site:t.site,month:E(e.month),limit:k(e.limit),brandTerms:t.client?.brandTerms,...ds(e),progress:cs(!0)}),r=E(e.out)??ys({report:`monthly`,clientId:t.client?.id,site:t.site});xs(r,await bs(r,le(n)))}}),Is=C({meta:{name:`narrative`,description:`Export report narrative tables plus the underlying diagnosis detail CSVs.`},args:{...gs,out:_s(`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.`},...hs},run:async({args:e})=>{let t=await H({client:D(e),site:E(e.site),options:{refresh:O(e.refresh)}}),n=await Bt({site:t.site,days:k(e.days),recentDays:k(e.recent),startDate:E(e[`start-date`]),endDate:E(e[`end-date`]),limit:k(e.limit),changeLimit:k(e[`change-limit`]),brandTerms:t.client?.brandTerms,...ds(e),progress:cs(!0)}),r=E(e.out)??ys({report:`narrative`,clientId:t.client?.id,site:t.site});xs(r,await bs(r,le(n)))}});function Ls(e){return Et({days:A(e.days,`--days`),sitemaps:j(e.sitemap),maxSitemapUrls:A(e[`max-sitemap-urls`],`--max-sitemap-urls`),templateLimit:A(e.limit,`--limit`),minimumTemplateUrls:A(e[`minimum-template-urls`],`--minimum-template-urls`),minimumTemplateShare:A(e[`minimum-template-share`],`--minimum-template-share`),minimumTemplateImpressions:A(e[`minimum-template-impressions`],`--minimum-template-impressions`),crawlSamples:A(e[`crawl-samples`],`--crawl-samples`),inspectSamples:A(e[`inspect-samples`],`--inspect-samples`),brandTerms:j(e[`brand-terms`]),includeBrand:O(e[`include-brand`]),js:O(e.js)?!0:`auto`,refresh:O(e.refresh),rate:qr(e)})}const Rs=C({meta:{name:`pseo`,description:`Export pSEO audit tables: templates, demand patterns, top queries, sample coverage, crawl samples, and inspection samples.`},args:{...gs,out:_s(`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=Ls(e),n=await H({client:D(e),site:E(e.site)}),r=await it({site:n.site,...t,brandTerms:t.brandTerms??n.client?.brandTerms,progress:cs(!0)}),i=E(e.out)??ys({report:`pseo`,clientId:n.client?.id,site:n.site});xs(i,await bs(i,Xe(r)))}}),zs=C({meta:{name:`refresh-priorities`,description:`Export the ranked SEO priority queue, score breakdowns, grouped findings, and diagnosis detail CSVs.`},args:{...gs,out:_s(`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":Vr(`Verify top opportunities against page title, meta, and content. Defaults to true.`,`Skip page content verification.`),"verify-limit":{type:`string`,description:`Maximum opportunity URLs to verify. Defaults to 5.`}},run:async({args:e})=>{let t=await H({client:D(e),site:E(e.site),options:{refresh:O(e.refresh)}}),n=await qe({site:t.site,days:k(e.days),recentDays:k(e.recent),limit:k(e.limit),brandTerms:t.client?.brandTerms,includeBrand:O(e[`include-brand`]),googleAnalyticsPropertyId:E(e[`google-analytics-property`])??t.client?.analytics.google?.propertyId,verifyContent:O(e[`verify-content`]),verifyLimit:k(e[`verify-limit`]),refresh:O(e.refresh)}),r=E(e.out)??ys({report:`refresh-priorities`,clientId:t.client?.id,site:t.site});xs(r,await bs(r,Je(n)))}}),Bs=C({meta:{name:`export`,description:`Export full report data to CSV files`},subCommands:{diagnose:Ss,knowledge:Ps,narrative:Is,monthly:Fs,"update-postmortem":C({meta:{name:`update-postmortem`,description:`Export update postmortem attribution, findings, template movement, and segment movers.`},args:{...gs,out:_s(`update-postmortem`),...X([`days`,`recentDays`,`limit`,`includeBrand`,`refresh`],{days:{description:`Diagnosis window length in days. Defaults to 90.`},limit:{description:`Maximum winners/losers per segment. Defaults to 20.`}}),"known-change":{type:`string`,description:`Manual site-side change to treat as a confounder, for example pruning pages or blocking traffic.`},"ignore-change-log":{type:`boolean`,default:!1,description:`Do not use saved change-log entries as confounders.`}},run:async({args:e})=>{let t=await H({client:D(e),site:E(e.site),options:{refresh:O(e.refresh)}}),n=await ce({site:t.site,days:k(e.days),recentDays:k(e.recent),limit:k(e.limit),brandTerms:t.client?.brandTerms,includeBrand:O(e[`include-brand`]),knownConfounders:E(e[`known-change`])?[E(e[`known-change`])??``]:void 0,includeChangeLog:!O(e[`ignore-change-log`]),refresh:O(e.refresh)}),r=E(e.out)??ys({report:`update-postmortem`,clientId:t.client?.id,site:t.site});xs(r,await bs(r,Pr(n)))}}),"refresh-priorities":zs,pseo:Rs}}),Vs={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 Hs(e){let t=E(e.site);if(t)return t;let n=await Hi({project:D(e),options:{json:N(e)}});if(!n)throw new y(`INVALID_INPUT`,`Pass --site or select a saved project.`);return n.siteUrl}async function Us(e){let t=E(e.url),n=j(e.urls)??[],r=E(e.file);if(!t&&n.length===0&&!r)throw new y(`INVALID_INPUT`,`Pass --url, comma-separated --urls, or a newline-delimited --file.`);let i=[...t?[t]:[],...n];if(r){let e=await $n(r).catch(()=>void 0);if(!e)throw new y(`INVALID_INPUT`,`IndexNow URL file not found: ${r}`);if(!e.isFile()||e.size>21e5)throw new y(`INVALID_INPUT`,`IndexNow URL files must be regular files no larger than 2.1 MB.`);i.push(...(await Yn(r,`utf8`)).split(/\r?\n/).map(e=>e.trim()).filter(Boolean))}return i}const Ws=C({meta:{name:`indexnow`,description:`Set up and submit changed URLs with IndexNow`},subCommands:{setup:C({meta:{name:`setup`,description:`Generate an IndexNow key file for one site`},args:{...Vs,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=E(e.output);if(!t)throw new y(`INVALID_INPUT`,`Pass --output with the public asset directory that deploys at the site root.`);let r=m({site:await Hs(e)}),i=Rn(t),a=S(i,`${r.key}.txt`);await qn(i,{recursive:!0});try{await tr(a,`${r.key}\n`,{encoding:`utf8`,flag:`wx`,mode:384})}catch(e){throw e.code===`EEXIST`?new y(`INVALID_INPUT`,`Refusing to overwrite the existing file at ${a}.`):e}let o;try{o=await n(r)}catch(e){throw await er(a).catch(()=>void 0),e}let s={configured:!0,host:r.host,keyLocation:r.keyLocation,keyFile:a,credentialSource:o,nextStep:`Deploy ${a}, then run seo indexnow verify --site ${new URL(r.keyLocation).origin}`};N(e)?R(s):z([[`Host`,s.host],[`Key file`,s.keyFile],[`Public URL`,s.keyLocation],[`Saved in`,s.credentialSource],[`Next`,s.nextStep]])}}),status:C({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 d(),n={configuredSites:t.length,sites:t};N(e)?R(n):B([`Host`,`Key location`,`Created`],t.map(e=>[e.host,e.keyLocation,e.createdAt]))}}),verify:C({meta:{name:`verify`,description:`Check the public IndexNow key file`},args:{...Vs,json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=await At({site:await Hs(e)}),n={...await Fe({record:t.record}),credentialSource:t.source};N(e)?R(n):z([[`Verified`,n.verified?`yes`:`no`],[`HTTP status`,String(n.status)],[`Key location`,n.keyLocation],[`Credential`,n.credentialSource]]),n.verified||(process.exitCode=4)}}),submit:C({meta:{name:`submit`,description:`Notify IndexNow about changed URLs`},args:{...Vs,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 At({site:await Hs(e)}),n=await un({record:t.record,urls:await Us(e),dryRun:O(e[`dry-run`])}),r={...n,credentialSource:t.source};if(N(e)){R(r);return}z([[`Host`,n.host],[`Status`,n.status],[`URLs`,String(n.submittedUrls)],[`Dry run`,n.dryRun?`yes`:`no`],[`Key location`,n.keyLocation]]);for(let e of n.caveats)process.stdout.write(`\n${e}\n`)}}),remove:C({meta:{name:`remove`,description:`Remove a saved IndexNow key`},args:{...Vs,json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=await xe(await Hs(e)),n={removed:t,note:`The deployed key file was not removed.`};N(e)?R(n):process.stdout.write(`${t?`Saved key removed.`:`No saved key found.`} ${n.note}\n`)}})}}),Gs=C({...to,meta:{name:`init`,description:"Legacy alias for `seo start`."}}),Ks=C({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=N(e),n=E(e.file),r=E(e.format);if(r&&![`csv`,`json`,`jsonl`].includes(r))throw new y(`INVALID_INPUT`,`--format must be csv, json, or jsonl.`);let i=A(e[`row-limit`],`--row-limit`),a;if(n)a=await Me({file:n,format:r,rowLimit:i});else{let n=await Hi({project:D(e),options:{json:t}}),r=E(e.site)??he(n);if(!r)throw new y(`INVALID_INPUT`,`Pass --site, connect Bing to a saved project, or import a link file with --file.`);a=await c({site:r,rowLimit:i,targetLimit:A(e[`target-limit`],`--target-limit`),detailPagesPerTarget:A(e[`detail-pages`],`--detail-pages`)})}let o=pn({evidence:a,limit:A(e.limit,`--limit`)});if(t){R(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 qs(e,t){if(!e){process.stdout.write(t);return}await qn(x(e),{recursive:!0}),await tr(e,t),process.stdout.write(`Wrote ${e}\n`)}const Js={"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.`}},Ys=C({meta:{name:`llms`,description:`Inspect or generate optional llms.txt from saved crawl data`},subCommands:{audit:C({meta:{name:`audit`,description:`Inspect optional llms.txt presence from a saved crawl`},args:Js,run:async({args:e})=>{let t=N(e),n=Qt(await Wi(e,{json:t}));if(t){R(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:C({meta:{name:`generate`,description:`Generate an llms.txt draft from a saved crawl`},args:{...Js,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=N(e),n=ct(await Wi(e,{json:t}),{maxUrls:k(e[`max-urls`]),tokenBudget:k(e[`token-budget`]),exclude:j(e.exclude),title:E(e.title),description:E(e.description)});if(t){R(n);return}await qs(E(e.output),n.content),E(e.output)&&z([[`URLs`,String(n.includedUrls)],[`Estimated tokens`,String(n.estimatedTokens)]])}})}}),Xs=C({meta:{name:`logs`,description:`Manage local log retention`},subCommands:{prune:C({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=at();if(N(e)){R(t);return}e.quiet||process.stdout.write(`Rotated ${t.rotated} and removed ${t.removed} log files. ${Ri(t.sizeBytes)} retained.\n`)}})}}),Zs=[{client:`claude-desktop`,flag:`claude-desktop`},{client:`claude-code`,flag:`claude-code`},{client:`codex`,flag:`codex`},{client:`cursor`,flag:`cursor`}];function Qs(e,t){return O(e.all)?t:Zs.filter(({flag:t})=>O(e[t])).map(({client:e})=>e)}const $s=C({meta:{name:`mcp`,description:`MCP server helpers`},subCommands:{serve:C({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-Bc8MJ1gT.js`);await t({test:O(e.test)})}}),install:C({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(O(e.uninstall)&&O(e.reinstall))throw new y(`INVALID_INPUT`,`Use either --uninstall or --reinstall, not both.`);let t=_a(),n=ba({targets:t}),r=N(e),i=Qs(e,n.map(e=>e.client));if(i.length===0){if(!L({json:r}))throw new y(`INVALID_INPUT`,`Choose an MCP client with --claude-desktop, --claude-code, --codex, --cursor, or --all.`);let a=n.length>0?n:t;i=V(await lr({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 y(`INVALID_INPUT`,`MCP setup is not supported for ${a.join(`, `)} on ${process.platform}.`);let o=t.filter(e=>i.includes(e.client)).map(t=>O(e.uninstall)?za(t):Ra(t,{reinstall:O(e.reinstall)}));if(r){R({operation:O(e.uninstall)?`uninstall`:`install`,results:o});return}for(let e of o)process.stdout.write(`${e.changed?`updated`:`skipped`} ${e.client} · ${e.path}\n`)}})}}),ec=C({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 Rt({startUrl:E(e.url)??``,site:E(e.site),limit:k(e.limit),refresh:O(e.refresh),js:O(e.js)?!0:`auto`});if(N(e)){R(t);return}if(K({title:`Crawl history comparison`,target:t.run.startUrl,status:t.previousRun===void 0?`unknown`:t.summary.newErrors>0||t.summary.indexabilityFlips>0?`warning`:`pass`,summary:t.previousRun===void 0?`No previous crawl was available for comparison.`:`${t.summary.changed} changed, ${t.summary.added} added, and ${t.summary.removed} removed URLs.`,metrics:[{label:`Run`,value:t.run.id},{label:`Crawled`,value:t.summary.crawled},{label:`Previous run`,value:t.previousRun?.id??`None`},{label:`Added`,value:t.summary.added},{label:`Removed`,value:t.summary.removed},{label:`Changed`,value:t.summary.changed},{label:`New errors`,value:t.summary.newErrors},{label:`Indexability flips`,value:t.summary.indexabilityFlips},{label:`High priority`,value:t.summary.highPriorityRecommendations}]}),t.recommendations.length&&(process.stdout.write(`
|
|
168
168
|
Recommended actions
|
|
169
169
|
`),B([`Severity`,`Category`,`URL`,`Action`],t.recommendations.slice(0,10).map(e=>[e.severity,e.category,G(e.url,56),G(e.action,72)])),J(`Top crawl actions`,t.recommendations.map(e=>({label:e.category,context:`${e.severity}, ${G(e.url,96)}`,action:e.action})))),t.items.length&&(process.stdout.write(`
|
|
170
170
|
Changed URLs
|
|
@@ -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{c as e}from"./dist-
|
|
3
|
+
import{c as e}from"./dist-sYeCtY3Q.js";export{e as startMcpServer};
|