seo 0.2.16 → 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 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-BeDz2qIL.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(`&`,`&amp;`).replaceAll(`<`,`&lt;`).replaceAll(`>`,`&gt;`).replaceAll(`"`,`&quot;`).replaceAll(`'`,`&#39;`)}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">
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(`&`,`&amp;`).replaceAll(`<`,`&lt;`).replaceAll(`>`,`&gt;`).replaceAll(`"`,`&quot;`).replaceAll(`'`,`&#39;`)}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">
@@ -142,11 +142,11 @@ Revoke at https://myaccount.google.com/permissions if you also want Google to fo
142
142
  `)}function _o(e,t,n){if(n)return;let r=ue().filter(t=>t.siteUrl===e);return r.length===1?r[0]:r.find(e=>e.isDefault)??r.find(e=>e.name.toLocaleLowerCase()===t.toLocaleLowerCase())}async function vo(e){let t=M(e);if(t||wr(process.argv[2]===`start`?`seo start`:`seo setup`),e[`dry-run`]){let e=[`seo auth login`,`seo start`,`seo report --project acme`,`seo refresh-priorities --project acme --verify-content`,`seo technical-watch --project acme`];t?L({dryRun:!0,next:e}):(S(e.join(`
143
143
  `),`This setup will guide you through`),Or(`Dry run complete.`));return}$t();let n={site:T(e.site),options:{json:t,refresh:D(e.refresh)}},r=t?await ia(n):void 0,i=await so(e),a=r??await ia(n),o=Aa(a);if(I({json:t})&&S(`A project profile remembers the site and report defaults so future commands stay short.`,`Project profile`),!(D(e[`skip-profile`])!==!0&&(!I({json:t})||B(await Cr({message:`Save this site as a project profile?`,initialValue:!0}))))){let n=await uo(e),r=await lo(e),o=`--site ${po(a)}`,s=[`seo report ${o}`,`seo refresh-priorities ${o} --verify-content`,`seo technical-watch ${o}`],c={site:a,auth:i,mcp:n,skill:r,next:s};if(t){L(c);return}R([[`Project profile`,`not saved`],[`GSC property`,a],[`Auth`,i],[`MCP`,ho(n)],[`SEO skill`,fo(r)]]);let l=go(n);l&&S(l,`MCP setup skipped`),r.error&&S(r.error,`SEO skill install failed`),S(s.join(`
144
144
  `),`Try next`),Or(`Setup complete.`),mo();return}let s=T(e.name)??(I({json:t})?B(await jr({message:`Project name`,placeholder:o,defaultValue:o})):o),c=T(e.id),l=c??_o(a,s,c)?.id??ja(s),u=ka(a)??``,d=T(e.url)??(I({json:t})?B(await jr({message:`Website URL to crawl`,placeholder:u||`https://example.com`,defaultValue:u})):u||void 0),f=j(e.urls).length>0?j(e.urls):[],p=await co({property:T(e[`google-analytics-property`]),site:a,interactive:I({json:t})}),m=p?.propertyId,h=Vt({id:l,name:s,siteUrl:a}),ee=j(e.brand).length>0?j(e.brand):h,te=O(e[`report-day`])??1,ne=O(e.weekday)??1,re=D(e.default)??!0,g=ye({id:l,name:s,siteUrl:a,startUrl:d,watchUrls:f,brandTerms:ee,analytics:m?{google:{propertyId:m}}:void 0,reportDay:te,technicalWeekday:ne,isDefault:re}),ie=await uo(e),ae=await lo(e),oe=[`seo report --project ${g.id}`,`seo refresh-priorities --project ${g.id} --verify-content`,`seo technical-watch --project ${g.id}`],se={client:g,site:a,auth:i,googleAnalytics:p,mcp:ie,skill:ae,next:oe};if(t){L(se);return}R([[`Project profile`,`${g.name} (${g.id})`],[`GSC property`,g.siteUrl],[`Crawl URL`,g.startUrl??`not set`],[`Watch URLs`,String(g.watchUrls.length)],[`Brand terms`,g.brandTerms.join(`, `)||`not set`],[`Google Analytics property`,g.analytics.google?.propertyId??`not connected (optional)`],...p?[[`Google Analytics selection`,p.reason]]:[],[`Auth`,i],[`MCP`,ho(ie)],[`SEO skill`,fo(ae)]]);let ce=go(ie);ce&&S(ce,`MCP setup skipped`),ae.error&&S(ae.error,`SEO skill install failed`),S(oe.join(`
145
- `),`Try next`),Or(`Setup complete.`),mo()}const yo=x({meta:{name:`setup`,description:`Guided setup for auth, one project profile, MCP, and next commands`},args:{id:{type:`string`,description:`Short stable project id.`},name:{type:`string`,description:`Human project name.`},site:{type:`string`,description:`GSC property URL, for example sc-domain:example.com.`},url:{type:`string`,description:`Default technical crawl start URL.`},urls:{type:`string`,description:`Comma-separated URLs to watch with URL Inspection.`},"google-analytics-property":{type:`string`,description:`Optional Google Analytics property ID.`},brand:{type:`string`,description:`Comma-separated branded query terms to exclude by default.`},"report-day":{type:`string`,description:`Preferred monthly report day. Defaults to 1.`},weekday:{type:`string`,description:`Preferred technical-watch weekday. Defaults to Monday.`},default:{type:`boolean`,description:`Make this the default project.`},"skip-profile":{type:`boolean`,default:!1,description:`Do not save a project profile during setup.`},"skip-auth":{type:`boolean`,default:!1,description:`Skip Google sign-in during setup.`},"skip-mcp":{type:`boolean`,default:!1,description:`Skip MCP install prompts.`},"skip-skill":{type:`boolean`,default:!1,description:`Skip the SEO skill install prompt.`},refresh:{type:`boolean`,default:!1,description:`Refresh GSC property discovery.`},"dry-run":{type:`boolean`,default:!1,description:`Show what setup does without changing files.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>vo(e)}),bo=x({...yo,meta:{name:`start`,description:`Start here: connect Google, save a profile, and get the first report commands`}}),xo=x({...yo,meta:{name:`setup`,description:`Guided setup for one saved project profile`}}),So=x({meta:{name:`brand`,description:`Detect and manage project brand query terms`},subCommands:{detect:x({meta:{name:`detect`,description:`Suggest branded query terms from GSC navigational queries`},args:{client:{type:`string`,description:`Legacy alias for --project.`},project:{type:`string`,description:`Saved project id or name. Defaults to default.`},site:{type:`string`,description:`GSC property URL, for example sc-domain:example.com.`},days:{type:`string`,description:`Detection window length in days. Defaults to 28.`},limit:{type:`string`,description:`Maximum candidate terms. Defaults to 10.`},"min-impressions":{type:`string`,description:`Minimum query impressions. Defaults to 10.`},save:{type:`boolean`,default:!1,description:`Save suggested terms to the selected project.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`},refresh:{type:`boolean`,default:!1,description:`Bypass local GSC cache.`}},run:async({args:e})=>{let t=M(e),n=je(E(e)),r=await Ce({site:await ia({site:T(e.site)??n?.siteUrl,options:{json:t,refresh:D(e.refresh)}}),id:n?.id,name:n?.name,days:O(e.days),limit:O(e.limit),minImpressions:O(e[`min-impressions`]),refresh:D(e.refresh)}),i=D(e.save)&&n?ye({id:n.id,name:n.name,siteUrl:n.siteUrl,startUrl:n.startUrl,watchUrls:n.watchUrls,analytics:n.analytics,brandTerms:r.suggestedTerms,reportDay:n.reportDay,technicalWeekday:n.technicalWeekday,isDefault:n.isDefault}):void 0;if(D(e.save)&&!n)throw Error(`Pass --project to save detected brand terms.`);if(t){L({...r,saved:i});return}R([[`Property`,r.site],[`Derived terms`,r.derivedTerms.join(`, `)],[`Suggested terms`,r.suggestedTerms.join(`, `)],[`Saved`,i?i.id:`no`]]),z([`Term`,`Score`,`Evidence`],r.candidates.map(e=>[e.term,e.score,e.evidence.map(e=>`${e.query} (${Math.round(e.clicks)} clicks)`).join(`, `)]))}})}}),Co={project:{type:`string`,description:`Saved project id or name.`},client:{type:`string`,description:`Legacy alias for --project.`}},wo={type:`boolean`,default:!1,description:`Print machine-readable JSON.`};async function To(e){let t=await H({project:E(e),options:{json:M(e)}});if(!t)throw new _(`INVALID_INPUT`,`Select a saved project with --project or make one the default.`);return t}async function Eo(e){let t=T(e.keyword),n=T(e.keywords)?.split(`,`).map(e=>e.trim()).filter(Boolean),r=T(e.file),i=[...t?[t]:[],...n??[]];if(r){let e=await _r(r).catch(()=>void 0);if(!e?.isFile()||e.size>1e5)throw new _(`INVALID_INPUT`,`Keyword files must be regular files no larger than 100 KB.`);i.push(...(await pr(r,`utf8`)).split(/\r?\n/u).map(e=>e.trim()).filter(Boolean))}if(i.length===0)throw new _(`INVALID_INPUT`,`Pass --keyword, comma-separated --keywords, or a newline-delimited --file.`);return i}const Do=x({meta:{name:`add`,description:`Add or update keywords in a saved set`},args:{...Co,set:{type:`string`,description:`Keyword set id or name.`,required:!0},keyword:{type:`string`,description:`One keyword.`},keywords:{type:`string`,description:`Comma-separated keywords.`},file:{type:`string`,description:`Newline-delimited keyword file.`},tags:{type:`string`,description:`Comma-separated tags applied to every keyword.`},"target-url":{type:`string`,description:`Existing target page for every keyword.`},"proposed-url":{type:`string`,description:`Proposed page for every keyword.`},json:wo},run:async({args:e})=>{let t=T(e[`target-url`]),n=T(e[`proposed-url`]);if(t&&n)throw new _(`INVALID_INPUT`,`Use either --target-url or --proposed-url.`);let r=hn({projectId:(await To(e)).id,idOrName:T(e.set)??``,items:(await Eo(e)).map(r=>({keyword:r,tags:j(e.tags),...t||n?{page:{kind:t?`target`:`proposed`,url:t??n??``}}:{}}))});M(e)?L(r):R([[`Set`,r.setId],[`Added`,String(r.added)],[`Updated`,String(r.updated)],[`Keywords`,String(r.keywordCount)]])}}),Oo=x({meta:{name:`create`,description:`Create an empty saved keyword set`},args:{...Co,name:{type:`string`,description:`Keyword set name.`,required:!0},country:{type:`string`,description:`Two-letter country code.`,required:!0},language:{type:`string`,description:`Search language code.`,required:!0},"search-engine":{type:`string`,default:`google`,description:`Search engine: google or bing.`},"location-code":{type:`string`,description:`Optional provider location code.`},location:{type:`string`,description:`Optional canonical location name.`},device:{type:`string`,description:`Optional device: desktop or mobile.`},provider:{type:`string`,description:`Optional preferred provider.`},"source-report":{type:`string`,description:`Report that produced the initial research.`},json:wo},run:async({args:t})=>{let n=O(t[`location-code`]);if(n!==void 0&&!Number.isSafeInteger(n))throw new _(`INVALID_INPUT`,`--location-code must be an integer.`);let r=await To(t),i=e({projectId:r.id,name:T(t.name)??``,market:{searchEngine:T(t[`search-engine`]),countryCode:T(t.country),languageCode:T(t.language),...n!==void 0||T(t.location)?{location:{code:n,name:T(t.location)}}:{},device:T(t.device)},provider:T(t.provider),sourceReport:T(t[`source-report`])});M(t)?L(i):R([[`Created`,i.name],[`Project`,r.name],[`Keywords`,`0`],[`Id`,i.id]])}}),ko=x({meta:{name:`delete`,description:`Delete a saved keyword set`},args:{...Co,set:{type:`string`,description:`Keyword set id or name.`,required:!0},yes:{type:`boolean`,default:!1,description:`Confirm permanent deletion.`},json:wo},run:async({args:e})=>{if(!D(e.yes))throw new _(`INVALID_INPUT`,`Pass --yes to delete the keyword set.`);let t=await To(e),n=T(e.set)??``;if(!Dt({projectId:t.id,idOrName:n}))throw new _(`INVALID_INPUT`,`Keyword set not found: ${n}`);let r={deleted:!0,projectId:t.id,set:n};M(e)?L(r):process.stdout.write(`Deleted keyword set ${n}.\n`)}}),Ao=x({meta:{name:`export`,description:`Export a bounded saved keyword set`},args:{...Co,set:{type:`string`,description:`Keyword set id or name.`,required:!0},format:{type:`string`,default:`csv`,description:`Output format: csv or json.`},output:{type:`string`,description:`Output file, or - for stdout.`,required:!0},tag:{type:`string`,description:`Only export keywords with this tag.`},limit:{type:`string`,default:`1000`,description:`Rows to export, from 1 to 1000.`},offset:{type:`string`,default:`0`,description:`Rows to skip.`}},run:async({args:e})=>{let t=T(e.format);if(t!==`csv`&&t!==`json`)throw new _(`INVALID_INPUT`,`--format must be csv or json.`);let n=O(e.limit),r=O(e.offset);if(!Number.isSafeInteger(n)||!Number.isSafeInteger(r))throw new _(`INVALID_INPUT`,`--limit and --offset must be integers.`);let i=xn({projectId:(await To(e)).id,idOrName:T(e.set)??``,tag:T(e.tag),limit:n,offset:r}),a=t===`json`?`${JSON.stringify(i,null,2)}\n`:jo(i),o=T(e.output)??``;if(o===`-`)process.stdout.write(a);else{try{await yr(o,a,{encoding:`utf8`,flag:`wx`,mode:384})}catch(e){throw e.code===`EEXIST`?new _(`INVALID_INPUT`,`Refusing to overwrite the existing file at ${o}.`):e}process.stdout.write(`Wrote ${i.items.length} keywords to ${o}.\n`)}}});function jo(e){return sn(e.items.map(e=>{let t=e.latestMetric?.metric;return{keyword:e.keyword,normalized_keyword:e.normalizedKeyword,tags:e.tags.join(`; `),page_kind:e.page?.kind,page_url:e.page?.url,metric_provider:e.latestMetric?.provider,metric_observed_at:e.latestMetric?.observedAt,monthly_search_volume:Mo(t?.monthlySearchVolume),cpc_usd:Mo(t?.cpcUsd),paid_competition:Mo(t?.paidCompetition),keyword_difficulty:Mo(t?.keywordDifficulty),intent:t?.intent.state===`observed`?t.intent.value:null,result_count:Mo(t?.resultCount)}}),[`keyword`,`normalized_keyword`,`tags`,`page_kind`,`page_url`,`metric_provider`,`metric_observed_at`,`monthly_search_volume`,`cpc_usd`,`paid_competition`,`keyword_difficulty`,`intent`,`result_count`])}function Mo(e){return e?.state===`observed`?e.value:null}function No(e){if(e.length===0){process.stdout.write(`No keyword sets saved for this project.
145
+ `),`Try next`),Or(`Setup complete.`),mo()}const yo=x({meta:{name:`setup`,description:`Guided setup for auth, one project profile, MCP, and next commands`},args:{id:{type:`string`,description:`Short stable project id.`},name:{type:`string`,description:`Human project name.`},site:{type:`string`,description:`GSC property URL, for example sc-domain:example.com.`},url:{type:`string`,description:`Default technical crawl start URL.`},urls:{type:`string`,description:`Comma-separated URLs to watch with URL Inspection.`},"google-analytics-property":{type:`string`,description:`Optional Google Analytics property ID.`},brand:{type:`string`,description:`Comma-separated branded query terms to exclude by default.`},"report-day":{type:`string`,description:`Preferred monthly report day. Defaults to 1.`},weekday:{type:`string`,description:`Preferred technical-watch weekday. Defaults to Monday.`},default:{type:`boolean`,description:`Make this the default project.`},"skip-profile":{type:`boolean`,default:!1,description:`Do not save a project profile during setup.`},"skip-auth":{type:`boolean`,default:!1,description:`Skip Google sign-in during setup.`},"skip-mcp":{type:`boolean`,default:!1,description:`Skip MCP install prompts.`},"skip-skill":{type:`boolean`,default:!1,description:`Skip the SEO skill install prompt.`},refresh:{type:`boolean`,default:!1,description:`Refresh GSC property discovery.`},"dry-run":{type:`boolean`,default:!1,description:`Show what setup does without changing files.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>vo(e)}),bo=x({...yo,meta:{name:`start`,description:`Start here: connect Google, save a profile, and get the first report commands`}}),xo=x({...yo,meta:{name:`setup`,description:`Guided setup for one saved project profile`}}),So=x({meta:{name:`brand`,description:`Detect and manage project brand query terms`},subCommands:{detect:x({meta:{name:`detect`,description:`Suggest branded query terms from GSC navigational queries`},args:{client:{type:`string`,description:`Legacy alias for --project.`},project:{type:`string`,description:`Saved project id or name. Defaults to default.`},site:{type:`string`,description:`GSC property URL, for example sc-domain:example.com.`},days:{type:`string`,description:`Detection window length in days. Defaults to 28.`},limit:{type:`string`,description:`Maximum candidate terms. Defaults to 10.`},"min-impressions":{type:`string`,description:`Minimum query impressions. Defaults to 10.`},save:{type:`boolean`,default:!1,description:`Save suggested terms to the selected project.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`},refresh:{type:`boolean`,default:!1,description:`Bypass local GSC cache.`}},run:async({args:e})=>{let t=M(e),n=je(E(e)),r=await Ce({site:await ia({site:T(e.site)??n?.siteUrl,options:{json:t,refresh:D(e.refresh)}}),id:n?.id,name:n?.name,days:O(e.days),limit:O(e.limit),minImpressions:O(e[`min-impressions`]),refresh:D(e.refresh)}),i=D(e.save)&&n?ye({id:n.id,name:n.name,siteUrl:n.siteUrl,startUrl:n.startUrl,watchUrls:n.watchUrls,analytics:n.analytics,brandTerms:r.suggestedTerms,reportDay:n.reportDay,technicalWeekday:n.technicalWeekday,isDefault:n.isDefault}):void 0;if(D(e.save)&&!n)throw Error(`Pass --project to save detected brand terms.`);if(t){L({...r,saved:i});return}R([[`Property`,r.site],[`Derived terms`,r.derivedTerms.join(`, `)],[`Suggested terms`,r.suggestedTerms.join(`, `)],[`Saved`,i?i.id:`no`]]),z([`Term`,`Score`,`Evidence`],r.candidates.map(e=>[e.term,e.score,e.evidence.map(e=>`${e.query} (${Math.round(e.clicks)} clicks)`).join(`, `)]))}})}}),Co={project:{type:`string`,description:`Saved project id or name.`},client:{type:`string`,description:`Legacy alias for --project.`}},wo={type:`boolean`,default:!1,description:`Print machine-readable JSON.`};async function To(e){let t=await H({project:E(e),options:{json:M(e)}});if(!t)throw new _(`INVALID_INPUT`,`Select a saved project with --project or make one the default.`);return t}async function Eo(e){let t=T(e.keyword),n=T(e.keywords)?.split(`,`).map(e=>e.trim()).filter(Boolean),r=T(e.file),i=[...t?[t]:[],...n??[]];if(r){let e=await _r(r).catch(()=>void 0);if(!e?.isFile()||e.size>1e5)throw new _(`INVALID_INPUT`,`Keyword files must be regular files no larger than 100 KB.`);i.push(...(await pr(r,`utf8`)).split(/\r?\n/u).map(e=>e.trim()).filter(Boolean))}if(i.length===0)throw new _(`INVALID_INPUT`,`Pass --keyword, comma-separated --keywords, or a newline-delimited --file.`);return i}const Do=x({meta:{name:`add`,description:`Add or update keywords in a saved set`},args:{...Co,set:{type:`string`,description:`Keyword set id or name.`,required:!0},keyword:{type:`string`,description:`One keyword.`},keywords:{type:`string`,description:`Comma-separated keywords.`},file:{type:`string`,description:`Newline-delimited keyword file.`},tags:{type:`string`,description:`Comma-separated tags applied to every keyword.`},"target-url":{type:`string`,description:`Existing target page for every keyword.`},"proposed-url":{type:`string`,description:`Proposed page for every keyword.`},json:wo},run:async({args:t})=>{let n=T(t[`target-url`]),r=T(t[`proposed-url`]);if(n&&r)throw new _(`INVALID_INPUT`,`Use either --target-url or --proposed-url.`);let i=e({projectId:(await To(t)).id,idOrName:T(t.set)??``,items:(await Eo(t)).map(e=>({keyword:e,tags:j(t.tags),...n||r?{page:{kind:n?`target`:`proposed`,url:n??r??``}}:{}}))});M(t)?L(i):R([[`Set`,i.setId],[`Added`,String(i.added)],[`Updated`,String(i.updated)],[`Keywords`,String(i.keywordCount)]])}}),Oo=x({meta:{name:`create`,description:`Create an empty saved keyword set`},args:{...Co,name:{type:`string`,description:`Keyword set name.`,required:!0},country:{type:`string`,description:`Two-letter country code.`,required:!0},language:{type:`string`,description:`Search language code.`,required:!0},"search-engine":{type:`string`,default:`google`,description:`Search engine: google or bing.`},"location-code":{type:`string`,description:`Optional provider location code.`},location:{type:`string`,description:`Optional canonical location name.`},device:{type:`string`,description:`Optional device: desktop or mobile.`},provider:{type:`string`,description:`Optional preferred provider.`},"source-report":{type:`string`,description:`Report that produced the initial research.`},json:wo},run:async({args:e})=>{let t=O(e[`location-code`]);if(t!==void 0&&!Number.isSafeInteger(t))throw new _(`INVALID_INPUT`,`--location-code must be an integer.`);let n=await To(e),r=hn({projectId:n.id,name:T(e.name)??``,market:{searchEngine:T(e[`search-engine`]),countryCode:T(e.country),languageCode:T(e.language),...t!==void 0||T(e.location)?{location:{code:t,name:T(e.location)}}:{},device:T(e.device)},provider:T(e.provider),sourceReport:T(e[`source-report`])});M(e)?L(r):R([[`Created`,r.name],[`Project`,n.name],[`Keywords`,`0`],[`Id`,r.id]])}}),ko=x({meta:{name:`delete`,description:`Delete a saved keyword set`},args:{...Co,set:{type:`string`,description:`Keyword set id or name.`,required:!0},yes:{type:`boolean`,default:!1,description:`Confirm permanent deletion.`},json:wo},run:async({args:e})=>{if(!D(e.yes))throw new _(`INVALID_INPUT`,`Pass --yes to delete the keyword set.`);let t=await To(e),n=T(e.set)??``;if(!zt({projectId:t.id,idOrName:n}))throw new _(`INVALID_INPUT`,`Keyword set not found: ${n}`);let r={deleted:!0,projectId:t.id,set:n};M(e)?L(r):process.stdout.write(`Deleted keyword set ${n}.\n`)}}),Ao=x({meta:{name:`export`,description:`Export a bounded saved keyword set`},args:{...Co,set:{type:`string`,description:`Keyword set id or name.`,required:!0},format:{type:`string`,default:`csv`,description:`Output format: csv or json.`},output:{type:`string`,description:`Output file, or - for stdout.`,required:!0},tag:{type:`string`,description:`Only export keywords with this tag.`},limit:{type:`string`,default:`1000`,description:`Rows to export, from 1 to 1000.`},offset:{type:`string`,default:`0`,description:`Rows to skip.`}},run:async({args:e})=>{let t=T(e.format);if(t!==`csv`&&t!==`json`)throw new _(`INVALID_INPUT`,`--format must be csv or json.`);let n=O(e.limit),r=O(e.offset);if(!Number.isSafeInteger(n)||!Number.isSafeInteger(r))throw new _(`INVALID_INPUT`,`--limit and --offset must be integers.`);let i=gt({projectId:(await To(e)).id,idOrName:T(e.set)??``,tag:T(e.tag),limit:n,offset:r}),a=t===`json`?`${JSON.stringify(i,null,2)}\n`:jo(i),o=T(e.output)??``;if(o===`-`)process.stdout.write(a);else{try{await yr(o,a,{encoding:`utf8`,flag:`wx`,mode:384})}catch(e){throw e.code===`EEXIST`?new _(`INVALID_INPUT`,`Refusing to overwrite the existing file at ${o}.`):e}process.stdout.write(`Wrote ${i.items.length} keywords to ${o}.\n`)}}});function jo(e){return sn(e.items.map(e=>{let t=e.latestMetric?.metric;return{keyword:e.keyword,normalized_keyword:e.normalizedKeyword,tags:e.tags.join(`; `),page_kind:e.page?.kind,page_url:e.page?.url,metric_provider:e.latestMetric?.provider,metric_observed_at:e.latestMetric?.observedAt,monthly_search_volume:Mo(t?.monthlySearchVolume),cpc_usd:Mo(t?.cpcUsd),paid_competition:Mo(t?.paidCompetition),keyword_difficulty:Mo(t?.keywordDifficulty),intent:t?.intent.state===`observed`?t.intent.value:null,result_count:Mo(t?.resultCount)}}),[`keyword`,`normalized_keyword`,`tags`,`page_kind`,`page_url`,`metric_provider`,`metric_observed_at`,`monthly_search_volume`,`cpc_usd`,`paid_competition`,`keyword_difficulty`,`intent`,`result_count`])}function Mo(e){return e?.state===`observed`?e.value:null}function No(e){if(e.length===0){process.stdout.write(`No keyword sets saved for this project.
146
146
  `);return}z([`Name`,`Keywords`,`Market`,`Provider`,`Updated`],e.map(e=>[e.name,e.keywordCount,`${e.market.countryCode}/${e.market.languageCode}`,e.provider??`automatic`,e.updatedAt]))}function Po(e){if(R([[`Name`,e.set.name],[`Project`,e.set.projectId],[`Keywords`,String(e.set.keywordCount)],[`Market`,`${e.set.market.searchEngine} ${e.set.market.countryCode}/${e.set.market.languageCode}`],[`Provider`,e.set.provider??`automatic`],[`Last refreshed`,e.set.lastRefreshedAt??`never`]]),e.items.length===0){process.stdout.write(`
147
147
  No keywords matched this view.
148
148
  `);return}process.stdout.write(`
149
- `),z([`Keyword`,`Tags`,`Volume`,`Page`],e.items.map(e=>[e.keyword,e.tags.join(`, `)||`-`,e.latestMetric?.metric.monthlySearchVolume.state===`observed`?e.latestMetric.metric.monthlySearchVolume.value:`-`,e.page?.url??`-`])),e.pagination.nextOffset!==null&&process.stdout.write(`\nMore rows are available. Continue with --offset ${e.pagination.nextOffset}.\n`)}const Fo=x({meta:{name:`list`,description:`List saved keyword sets for a project`},args:{...Co,json:wo},run:async({args:e})=>{let t=await To(e),n=en({projectId:t.id});M(e)?L({projectId:t.id,sets:n}):No(n)}}),Io=x({meta:{name:`refresh`,description:`Preview or refresh saved keyword metrics`},args:{...Co,set:{type:`string`,description:`Keyword set id or name.`,required:!0},provider:{type:`string`,description:`Optional keyword provider.`},limit:{type:`string`,default:`1000`,description:`Keywords to refresh, from 1 to 1000.`},offset:{type:`string`,default:`0`,description:`Saved keyword offset, from 0 to 100000.`},yes:{type:`boolean`,default:!1,description:`Run the paid refresh after estimating its cost.`},json:wo},run:async({args:e})=>{let t=O(e.limit),n=O(e.offset);if(!Number.isSafeInteger(t))throw new _(`INVALID_INPUT`,`--limit must be an integer.`);if(!Number.isSafeInteger(n))throw new _(`INVALID_INPUT`,`--offset must be an integer.`);let r=await Pe({projectId:(await To(e)).id,idOrName:T(e.set)??``,provider:T(e.provider),limit:t,offset:n,execute:D(e.yes)});if(M(e)){L(r);return}R([[`Set`,r.set.name],[`Mode`,r.mode],[`Keywords`,String(r.selection.selectedKeywords)],[`Offset`,String(r.selection.offset)],[`Requests`,String(r.cost.requestCount)],[`Estimated cost`,r.cost.estimatedMicros===null?`unavailable`:`$${(r.cost.estimatedMicros/1e6).toFixed(4)}`],...r.execution?[[`Saved snapshots`,String(r.execution.savedSnapshots)],[`Partial batches`,String(r.execution.partialBatches)],[`Failed batches`,String(r.execution.failedBatches)],[`Actual cost`,r.execution.actualMicros===null?`unavailable`:`$${(r.execution.actualMicros/1e6).toFixed(4)}`]]:[]]),r.mode===`preview`&&process.stdout.write(`\nRun again with --yes to refresh these ${r.selection.selectedKeywords} keywords.\n`)}}),Lo=x({meta:{name:`remove`,description:`Remove keywords from a saved set`},args:{...Co,set:{type:`string`,description:`Keyword set id or name.`,required:!0},keyword:{type:`string`,description:`One keyword.`},keywords:{type:`string`,description:`Comma-separated keywords.`},file:{type:`string`,description:`Newline-delimited keyword file.`},json:wo},run:async({args:e})=>{let t=zt({projectId:(await To(e)).id,idOrName:T(e.set)??``,keywords:await Eo(e)});M(e)?L(t):R([[`Set`,t.setId],[`Removed`,String(t.removed)],[`Keywords`,String(t.keywordCount)]])}}),Ro=x({meta:{name:`keyword-sets`,description:`Manage project keyword research sets`},subCommands:{create:Oo,list:Fo,show:x({meta:{name:`show`,description:`Show a bounded saved keyword set view`},args:{...Co,set:{type:`string`,description:`Keyword set id or name.`,required:!0},tag:{type:`string`,description:`Only return keywords with this tag.`},limit:{type:`string`,default:`100`,description:`Rows to return, from 1 to 1000.`},offset:{type:`string`,default:`0`,description:`Rows to skip.`},json:wo},run:async({args:e})=>{let t=O(e.limit),n=O(e.offset);if(!Number.isSafeInteger(t)||!Number.isSafeInteger(n))throw new _(`INVALID_INPUT`,`--limit and --offset must be integers.`);let r=xn({projectId:(await To(e)).id,idOrName:T(e.set)??``,tag:T(e.tag),limit:t,offset:n});M(e)?L(r):Po(r)}}),add:Do,remove:Lo,refresh:Io,export:Ao,delete:ko}});function zo(e){Zi(e.map(e=>({title:`${e.name}${e.isDefault?` (default)`:``}`,description:e.siteUrl,meta:[e.id,e.startUrl?`crawl ${e.startUrl}`:``,e.watchUrls.length?`${e.watchUrls.length} watched URL${e.watchUrls.length===1?``:`s`}`:``,e.brandTerms.length?`${e.brandTerms.length} brand term${e.brandTerms.length===1?``:`s`}`:``]})),{empty:`No saved projects.`})}function Bo(e){R([[`ID`,e.id],[`Name`,e.name],[`GSC property`,e.siteUrl],[`Crawl URL`,e.startUrl??`not set`],[`Watch URLs`,e.watchUrls.join(`, `)||`not set`],[`Brand terms`,e.brandTerms.join(`, `)||`not set`],[`Google Analytics property`,e.analytics.google?.propertyId??`not set`],[`Report day`,e.reportDay?String(e.reportDay):`not set`],[`Technical weekday`,e.technicalWeekday===void 0?`not set`:String(e.technicalWeekday)],[`Default`,e.isDefault?`yes`:`no`]])}const Vo=x({meta:{name:`add`,description:`Create or update a project profile`},args:{id:{type:`string`,description:`Short stable project id, for example acme.`},name:{type:`string`,description:`Human project name.`},site:{type:`string`,description:`GSC property URL, for example sc-domain:example.com.`},url:{type:`string`,description:`Default technical crawl start URL.`},urls:{type:`string`,description:`Comma-separated URLs to watch with URL Inspection.`},"google-analytics-property":{type:`string`,description:`Optional Google Analytics property ID for this project.`},brand:{type:`string`,description:`Comma-separated branded query terms to exclude by default.`},"report-day":{type:`string`,description:`Preferred monthly report day, 1-31.`},weekday:{type:`string`,description:`Preferred technical-watch weekday, 0-7.`},default:{type:`boolean`,description:`Make this the default project.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=M(e),n=ye({id:T(e.id),name:T(e.name),siteUrl:await ia({site:T(e.site),options:{json:t}}),startUrl:T(e.url),watchUrls:j(e.urls),brandTerms:j(e.brand),analytics:T(e[`google-analytics-property`])?{google:{propertyId:T(e[`google-analytics-property`])??``}}:void 0,reportDay:O(e[`report-day`]),technicalWeekday:O(e.weekday),isDefault:D(e.default)});if(t){L(n);return}Bo(n)}}),Ho=x({meta:{name:`default`,description:`Set the default project`},args:{id:{type:`string`,required:!0,description:`Project id or name.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=Ln(T(e.id)??``);if(M(e)){L(t);return}process.stdout.write(`Default project set to ${t.id}.\n`)}}),Uo=x({meta:{name:`delete`,description:`Delete a project profile`},args:{id:{type:`string`,required:!0,description:`Project id or name.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=T(e.id)??``,n=Te(t);if(M(e)){L({id:t,deleted:n});return}process.stdout.write(`${n?`Deleted`:`Not found`} ${t}.\n`)}}),Wo=x({meta:{name:`client`,description:`Manage saved project profiles`},subCommands:{setup:xo,brand:So,"keyword-sets":Ro,list:x({meta:{name:`list`,description:`List saved project profiles`},args:{json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=ue();if(M(e)){L({clients:t});return}zo(t)}}),add:Vo,show:x({meta:{name:`show`,description:`Show one project profile`},args:{id:{type:`string`,description:`Project id or name. Defaults to the default project.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=je(T(e.id));if(!t)throw Error(`Project not found.`);if(M(e)){L(t);return}Bo(t)}}),default:Ho,delete:Uo}}),Go=x({...Wo,meta:{name:`projects`,description:`Manage saved project profiles`}}),Ko=x({meta:{name:`content`,description:`Optimize page content from real GSC queries`},subCommands:{optimize:x({meta:{name:`optimize`,description:`Build a content optimization report for one URL`},args:{site:{type:`string`},client:{type:`string`,description:`Legacy alias for --project.`},project:{type:`string`,description:`Saved project id or name.`},url:{type:`string`,required:!0},days:{type:`string`,description:`GSC lookback days. Defaults to 28.`},limit:{type:`string`,description:`Maximum GSC queries to inspect. Defaults to 25.`},"min-impressions":{type:`string`,description:`Minimum query impressions. Defaults to 10.`},"include-brand":{type:`boolean`,default:!1,description:`Include branded queries.`},"verify-content":w(`Fetch the page for content checks. Defaults to true.`,`Skip fetching the page for content checks.`),js:{type:`boolean`,default:!1,description:`Force JavaScript rendering for page extraction.`},refresh:{type:`boolean`,default:!1,description:`Bypass local GSC and HTTP cache.`},json:{type:`boolean`,default:!1}},run:async({args:e})=>{let t=M(e),n=await V({client:E(e),site:T(e.site),options:{json:t,refresh:D(e.refresh)}}),r=await ke({site:n.site,url:T(e.url)??``,days:O(e.days),limit:O(e.limit),minImpressions:O(e[`min-impressions`]),brandTerms:n.client?.brandTerms,includeBrand:D(e[`include-brand`]),verifyContent:D(e[`verify-content`])!==!1,js:D(e.js)?!0:`auto`,refresh:D(e.refresh)});if(t){L(r);return}K({title:`Content optimization`,target:r.url,status:`info`,summary:r.summary.verdict,metrics:[{label:`Score`,value:`${r.summary.score}/100`},{label:`Primary intent`,value:r.summary.primaryIntent},{label:`Primary query`,value:r.summary.primaryQuery??`None`},{label:`Queries`,value:U(r.summary.queries)},{label:`Opportunities`,value:U(r.summary.opportunities)},{label:`Estimated lift`,value:U(r.summary.estimatedClickLift)}]}),J(`Top actions`,r.topActions.map(e=>({label:e.title,action:e.action,context:e.plainEnglish}))),r.brief.sections.length&&(process.stdout.write(`
149
+ `),z([`Keyword`,`Tags`,`Volume`,`Page`],e.items.map(e=>[e.keyword,e.tags.join(`, `)||`-`,e.latestMetric?.metric.monthlySearchVolume.state===`observed`?e.latestMetric.metric.monthlySearchVolume.value:`-`,e.page?.url??`-`])),e.pagination.nextOffset!==null&&process.stdout.write(`\nMore rows are available. Continue with --offset ${e.pagination.nextOffset}.\n`)}const Fo=x({meta:{name:`list`,description:`List saved keyword sets for a project`},args:{...Co,json:wo},run:async({args:e})=>{let t=await To(e),n=an({projectId:t.id});M(e)?L({projectId:t.id,sets:n}):No(n)}}),Io=x({meta:{name:`refresh`,description:`Preview or refresh saved keyword metrics`},args:{...Co,set:{type:`string`,description:`Keyword set id or name.`,required:!0},provider:{type:`string`,description:`Optional keyword provider.`},limit:{type:`string`,default:`1000`,description:`Keywords to refresh, from 1 to 1000.`},offset:{type:`string`,default:`0`,description:`Saved keyword offset, from 0 to 100000.`},yes:{type:`boolean`,default:!1,description:`Run the paid refresh after estimating its cost.`},json:wo},run:async({args:e})=>{let t=O(e.limit),n=O(e.offset);if(!Number.isSafeInteger(t))throw new _(`INVALID_INPUT`,`--limit must be an integer.`);if(!Number.isSafeInteger(n))throw new _(`INVALID_INPUT`,`--offset must be an integer.`);let r=await Pe({projectId:(await To(e)).id,idOrName:T(e.set)??``,provider:T(e.provider),limit:t,offset:n,execute:D(e.yes)});if(M(e)){L(r);return}R([[`Set`,r.set.name],[`Mode`,r.mode],[`Keywords`,String(r.selection.selectedKeywords)],[`Offset`,String(r.selection.offset)],[`Requests`,String(r.cost.requestCount)],[`Estimated cost`,r.cost.estimatedMicros===null?`unavailable`:`$${(r.cost.estimatedMicros/1e6).toFixed(4)}`],...r.execution?[[`Saved snapshots`,String(r.execution.savedSnapshots)],[`Partial batches`,String(r.execution.partialBatches)],[`Failed batches`,String(r.execution.failedBatches)],[`Actual cost`,r.execution.actualMicros===null?`unavailable`:`$${(r.execution.actualMicros/1e6).toFixed(4)}`]]:[]]),r.mode===`preview`&&process.stdout.write(`\nRun again with --yes to refresh these ${r.selection.selectedKeywords} keywords.\n`)}}),Lo=x({meta:{name:`remove`,description:`Remove keywords from a saved set`},args:{...Co,set:{type:`string`,description:`Keyword set id or name.`,required:!0},keyword:{type:`string`,description:`One keyword.`},keywords:{type:`string`,description:`Comma-separated keywords.`},file:{type:`string`,description:`Newline-delimited keyword file.`},json:wo},run:async({args:e})=>{let t=Dt({projectId:(await To(e)).id,idOrName:T(e.set)??``,keywords:await Eo(e)});M(e)?L(t):R([[`Set`,t.setId],[`Removed`,String(t.removed)],[`Keywords`,String(t.keywordCount)]])}}),Ro=x({meta:{name:`keyword-sets`,description:`Manage project keyword research sets`},subCommands:{create:Oo,list:Fo,show:x({meta:{name:`show`,description:`Show a bounded saved keyword set view`},args:{...Co,set:{type:`string`,description:`Keyword set id or name.`,required:!0},tag:{type:`string`,description:`Only return keywords with this tag.`},limit:{type:`string`,default:`100`,description:`Rows to return, from 1 to 1000.`},offset:{type:`string`,default:`0`,description:`Rows to skip.`},json:wo},run:async({args:e})=>{let t=O(e.limit),n=O(e.offset);if(!Number.isSafeInteger(t)||!Number.isSafeInteger(n))throw new _(`INVALID_INPUT`,`--limit and --offset must be integers.`);let r=gt({projectId:(await To(e)).id,idOrName:T(e.set)??``,tag:T(e.tag),limit:t,offset:n});M(e)?L(r):Po(r)}}),add:Do,remove:Lo,refresh:Io,export:Ao,delete:ko}});function zo(e){Zi(e.map(e=>({title:`${e.name}${e.isDefault?` (default)`:``}`,description:e.siteUrl,meta:[e.id,e.startUrl?`crawl ${e.startUrl}`:``,e.watchUrls.length?`${e.watchUrls.length} watched URL${e.watchUrls.length===1?``:`s`}`:``,e.brandTerms.length?`${e.brandTerms.length} brand term${e.brandTerms.length===1?``:`s`}`:``]})),{empty:`No saved projects.`})}function Bo(e){R([[`ID`,e.id],[`Name`,e.name],[`GSC property`,e.siteUrl],[`Crawl URL`,e.startUrl??`not set`],[`Watch URLs`,e.watchUrls.join(`, `)||`not set`],[`Brand terms`,e.brandTerms.join(`, `)||`not set`],[`Google Analytics property`,e.analytics.google?.propertyId??`not set`],[`Report day`,e.reportDay?String(e.reportDay):`not set`],[`Technical weekday`,e.technicalWeekday===void 0?`not set`:String(e.technicalWeekday)],[`Default`,e.isDefault?`yes`:`no`]])}const Vo=x({meta:{name:`add`,description:`Create or update a project profile`},args:{id:{type:`string`,description:`Short stable project id, for example acme.`},name:{type:`string`,description:`Human project name.`},site:{type:`string`,description:`GSC property URL, for example sc-domain:example.com.`},url:{type:`string`,description:`Default technical crawl start URL.`},urls:{type:`string`,description:`Comma-separated URLs to watch with URL Inspection.`},"google-analytics-property":{type:`string`,description:`Optional Google Analytics property ID for this project.`},brand:{type:`string`,description:`Comma-separated branded query terms to exclude by default.`},"report-day":{type:`string`,description:`Preferred monthly report day, 1-31.`},weekday:{type:`string`,description:`Preferred technical-watch weekday, 0-7.`},default:{type:`boolean`,description:`Make this the default project.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=M(e),n=ye({id:T(e.id),name:T(e.name),siteUrl:await ia({site:T(e.site),options:{json:t}}),startUrl:T(e.url),watchUrls:j(e.urls),brandTerms:j(e.brand),analytics:T(e[`google-analytics-property`])?{google:{propertyId:T(e[`google-analytics-property`])??``}}:void 0,reportDay:O(e[`report-day`]),technicalWeekday:O(e.weekday),isDefault:D(e.default)});if(t){L(n);return}Bo(n)}}),Ho=x({meta:{name:`default`,description:`Set the default project`},args:{id:{type:`string`,required:!0,description:`Project id or name.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=Ln(T(e.id)??``);if(M(e)){L(t);return}process.stdout.write(`Default project set to ${t.id}.\n`)}}),Uo=x({meta:{name:`delete`,description:`Delete a project profile`},args:{id:{type:`string`,required:!0,description:`Project id or name.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=T(e.id)??``,n=Te(t);if(M(e)){L({id:t,deleted:n});return}process.stdout.write(`${n?`Deleted`:`Not found`} ${t}.\n`)}}),Wo=x({meta:{name:`client`,description:`Manage saved project profiles`},subCommands:{setup:xo,brand:So,"keyword-sets":Ro,list:x({meta:{name:`list`,description:`List saved project profiles`},args:{json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=ue();if(M(e)){L({clients:t});return}zo(t)}}),add:Vo,show:x({meta:{name:`show`,description:`Show one project profile`},args:{id:{type:`string`,description:`Project id or name. Defaults to the default project.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=je(T(e.id));if(!t)throw Error(`Project not found.`);if(M(e)){L(t);return}Bo(t)}}),default:Ho,delete:Uo}}),Go=x({...Wo,meta:{name:`projects`,description:`Manage saved project profiles`}}),Ko=x({meta:{name:`content`,description:`Optimize page content from real GSC queries`},subCommands:{optimize:x({meta:{name:`optimize`,description:`Build a content optimization report for one URL`},args:{site:{type:`string`},client:{type:`string`,description:`Legacy alias for --project.`},project:{type:`string`,description:`Saved project id or name.`},url:{type:`string`,required:!0},days:{type:`string`,description:`GSC lookback days. Defaults to 28.`},limit:{type:`string`,description:`Maximum GSC queries to inspect. Defaults to 25.`},"min-impressions":{type:`string`,description:`Minimum query impressions. Defaults to 10.`},"include-brand":{type:`boolean`,default:!1,description:`Include branded queries.`},"verify-content":w(`Fetch the page for content checks. Defaults to true.`,`Skip fetching the page for content checks.`),js:{type:`boolean`,default:!1,description:`Force JavaScript rendering for page extraction.`},refresh:{type:`boolean`,default:!1,description:`Bypass local GSC and HTTP cache.`},json:{type:`boolean`,default:!1}},run:async({args:e})=>{let t=M(e),n=await V({client:E(e),site:T(e.site),options:{json:t,refresh:D(e.refresh)}}),r=await ke({site:n.site,url:T(e.url)??``,days:O(e.days),limit:O(e.limit),minImpressions:O(e[`min-impressions`]),brandTerms:n.client?.brandTerms,includeBrand:D(e[`include-brand`]),verifyContent:D(e[`verify-content`])!==!1,js:D(e.js)?!0:`auto`,refresh:D(e.refresh)});if(t){L(r);return}K({title:`Content optimization`,target:r.url,status:`info`,summary:r.summary.verdict,metrics:[{label:`Score`,value:`${r.summary.score}/100`},{label:`Primary intent`,value:r.summary.primaryIntent},{label:`Primary query`,value:r.summary.primaryQuery??`None`},{label:`Queries`,value:U(r.summary.queries)},{label:`Opportunities`,value:U(r.summary.opportunities)},{label:`Estimated lift`,value:U(r.summary.estimatedClickLift)}]}),J(`Top actions`,r.topActions.map(e=>({label:e.title,action:e.action,context:e.plainEnglish}))),r.brief.sections.length&&(process.stdout.write(`
150
150
  Suggested sections
151
151
  `),q([`Heading`,`Queries`],r.brief.sections.map(e=>[G(e.heading,48),G(e.queries.join(`, `),80)]))),r.intentMix.length&&(process.stdout.write(`
152
152
  Intent mix
@@ -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 gt({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 an({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-Hd0YW5ui.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
@@ -228,50 +228,50 @@ One-window evidence (not treated as zero)
228
228
  Parameters
229
229
  `),ea(Gl(n.inputSchema)),n.fixableChecks&&(process.stdout.write(`
230
230
  `),Ki({title:`Fix a failed check`,body:`Get the fix steps, an agent prompt, and spec links for one check id from the report output.`,command:`seo reports describe ${n.id} --check <check-id>`})),process.stdout.write(`
231
- `),Ki({title:`Need the exact schema?`,command:`seo reports describe ${n.id} --json`})}}),run:x({meta:{name:`run`,description:`Run one report with JSON parameters`},args:{id:{type:`positional`,required:!0,description:"Report id from `seo reports list`."},params:{type:`string`,description:`Report parameters as a JSON object.`},"params-file":{type:`string`,description:`Read report parameters from a JSON file.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=await pt(T(e.id)??``,await Ul(e.params,e[`params-file`]));if(Wl(t),M(e)){L(t.structuredContent??{});return}for(let e of t.content)process.stdout.write(`${e.text}\n`)}})}}),ql={format:{type:`string`,description:`Output format: terminal or html. Defaults to terminal.`},output:{type:`string`,description:`HTML output file. A dated filename is used by default.`},view:{type:`string`,description:`HTML detail level: client or analyst. Defaults to client.`}};function Jl(e){let t=T(e.format)??`terminal`;if(t!==`terminal`&&t!==`html`)throw new _(`INVALID_INPUT`,`Choose terminal or html for --format.`);if(t===`terminal`){if(T(e.output)||T(e.view))throw new _(`INVALID_INPUT`,`Use --output and --view with --format html.`);return}if(D(e.json))throw new _(`INVALID_INPUT`,`Use either --json or --format html, not both.`);let n=T(e.view)??`client`;if(n!==`client`&&n!==`analyst`)throw new _(`INVALID_INPUT`,`Choose client or analyst for --view.`);return{output:T(e.output),view:n}}function Yl(e){return e.toLowerCase().replace(/^sc-domain:/,``).replace(/^https?:\/\//,``).replace(/[^a-z0-9._-]+/g,`-`).replace(/^-+|-+$/g,``).slice(0,80)}function Xl(e){let t=(e.date??new Date).toISOString().slice(0,10),n=Yl(e.projectId??e.site)||`site`;return nr(process.cwd(),`${n}-${Yl(e.reportName)}-${t}.html`)}async function Zl(e){let t=nr(e.options.output??Xl({reportName:e.reportName,projectId:e.projectId,site:e.report.site}));return await dr(y(t),{recursive:!0}),await yr(t,Jr({report:e.report,title:e.title,view:e.options.view,additionalSections:e.additionalSections}),`utf8`),t}function Ql(e){process.stdout.write(`Wrote HTML report to ${e}\n`)}const $l=x({meta:{name:`monthly-report`,description:`Generate a monthly SEO report narrative`},args:{...Us,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.`}}),...Ws,...ql},run:async({args:e})=>{let t=M(e),n=Jl(e),r=Ks(e),i=await V({client:r.client,site:r.site,options:{json:t,refresh:r.refresh}}),a=await gt({site:i.site,month:T(e.month),limit:O(e.limit),brandTerms:i.client?.brandTerms,...Gs(e),progress:Hs(!t)});if(t){L(a);return}if(n){Ql(await Zl({report:a,reportName:`monthly-report`,title:`Monthly SEO report for ${a.month}`,options:n,projectId:i.client?.id}));return}process.stdout.write(`${a.markdown}\n`);let o=i.client?`--project ${JSON.stringify(i.client.id)}`:`--site ${JSON.stringify(i.site)}`,s=T(e.month);process.stdout.write(`
232
- `),_a(`seo export monthly ${o}${s?` --month ${JSON.stringify(s)}`:``}`)}}),eu=x({meta:{name:`report-narrative`,description:`Generate a client-ready SEO narrative from diagnosis data`},args:{...Us,...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.`},...Ws,...ql},run:async({args:e})=>{let t=M(e),n=Jl(e),r=Ks(e),i=await V({client:r.client,site:r.site,options:{json:t,refresh:r.refresh}}),a=await an({site:i.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:i.client?.brandTerms,...Gs(e),progress:Hs(!t)});if(t){L(a);return}if(n){Ql(await Zl({report:a,reportName:`narrative-report`,title:`SEO narrative report`,options:n,projectId:i.client?.id}));return}process.stdout.write(`${a.markdown}\n`);let o=i.client?`--project ${JSON.stringify(i.client.id)}`:`--site ${JSON.stringify(i.site)}`;process.stdout.write(`
231
+ `),Ki({title:`Need the exact schema?`,command:`seo reports describe ${n.id} --json`})}}),run:x({meta:{name:`run`,description:`Run one report with JSON parameters`},args:{id:{type:`positional`,required:!0,description:"Report id from `seo reports list`."},params:{type:`string`,description:`Report parameters as a JSON object.`},"params-file":{type:`string`,description:`Read report parameters from a JSON file.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=await pt(T(e.id)??``,await Ul(e.params,e[`params-file`]));if(Wl(t),M(e)){L(t.structuredContent??{});return}for(let e of t.content)process.stdout.write(`${e.text}\n`)}})}}),ql={format:{type:`string`,description:`Output format: terminal or html. Defaults to terminal.`},output:{type:`string`,description:`HTML output file. A dated filename is used by default.`},view:{type:`string`,description:`HTML detail level: client or analyst. Defaults to client.`}};function Jl(e){let t=T(e.format)??`terminal`;if(t!==`terminal`&&t!==`html`)throw new _(`INVALID_INPUT`,`Choose terminal or html for --format.`);if(t===`terminal`){if(T(e.output)||T(e.view))throw new _(`INVALID_INPUT`,`Use --output and --view with --format html.`);return}if(D(e.json))throw new _(`INVALID_INPUT`,`Use either --json or --format html, not both.`);let n=T(e.view)??`client`;if(n!==`client`&&n!==`analyst`)throw new _(`INVALID_INPUT`,`Choose client or analyst for --view.`);return{output:T(e.output),view:n}}function Yl(e){return e.toLowerCase().replace(/^sc-domain:/,``).replace(/^https?:\/\//,``).replace(/[^a-z0-9._-]+/g,`-`).replace(/^-+|-+$/g,``).slice(0,80)}function Xl(e){let t=(e.date??new Date).toISOString().slice(0,10),n=Yl(e.projectId??e.site)||`site`;return nr(process.cwd(),`${n}-${Yl(e.reportName)}-${t}.html`)}async function Zl(e){let t=nr(e.options.output??Xl({reportName:e.reportName,projectId:e.projectId,site:e.report.site}));return await dr(y(t),{recursive:!0}),await yr(t,Jr({report:e.report,title:e.title,view:e.options.view,additionalSections:e.additionalSections}),`utf8`),t}function Ql(e){process.stdout.write(`Wrote HTML report to ${e}\n`)}const $l=x({meta:{name:`monthly-report`,description:`Generate a monthly SEO report narrative`},args:{...Us,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.`}}),...Ws,...ql},run:async({args:e})=>{let t=M(e),n=Jl(e),r=Ks(e),i=await V({client:r.client,site:r.site,options:{json:t,refresh:r.refresh}}),a=await xn({site:i.site,month:T(e.month),limit:O(e.limit),brandTerms:i.client?.brandTerms,...Gs(e),progress:Hs(!t)});if(t){L(a);return}if(n){Ql(await Zl({report:a,reportName:`monthly-report`,title:`Monthly SEO report for ${a.month}`,options:n,projectId:i.client?.id}));return}process.stdout.write(`${a.markdown}\n`);let o=i.client?`--project ${JSON.stringify(i.client.id)}`:`--site ${JSON.stringify(i.site)}`,s=T(e.month);process.stdout.write(`
232
+ `),_a(`seo export monthly ${o}${s?` --month ${JSON.stringify(s)}`:``}`)}}),eu=x({meta:{name:`report-narrative`,description:`Generate a client-ready SEO narrative from diagnosis data`},args:{...Us,...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.`},...Ws,...ql},run:async({args:e})=>{let t=M(e),n=Jl(e),r=Ks(e),i=await V({client:r.client,site:r.site,options:{json:t,refresh:r.refresh}}),a=await en({site:i.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:i.client?.brandTerms,...Gs(e),progress:Hs(!t)});if(t){L(a);return}if(n){Ql(await Zl({report:a,reportName:`narrative-report`,title:`SEO narrative report`,options:n,projectId:i.client?.id}));return}process.stdout.write(`${a.markdown}\n`);let o=i.client?`--project ${JSON.stringify(i.client.id)}`:`--site ${JSON.stringify(i.site)}`;process.stdout.write(`
233
233
  `),_a(`seo export narrative ${o}`)}}),tu=x({meta:{name:`rules`,description:`List crawler rule ids and default guidance`},args:{category:{type:`string`,description:`Filter rules by category.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=T(e.category),n=Ve().filter(e=>!t||e.category===t);if(M(e)){L({rules:n});return}Xi(n.map(e=>({id:e.id,category:e.category,name:`${e.title} · ${e.defaultSeverity}`})),{noun:`rule`})}}),nu=x({meta:{name:`explain`,description:`Explain one crawler rule in plain English`},args:{rule:{type:`string`,required:!0,description:`Rule id to explain, for example missing_title.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=T(e.rule)??``,n=Mn(t);if(!n)throw Error(`Unknown rule: ${t}`);if(M(e)){L(n);return}R([[`Rule`,n.id],[`Title`,n.title],[`Category`,n.category],[`Severity`,n.defaultSeverity]]),process.stdout.write(`
234
234
  `),Qi(`Why it matters`,n.whyItMatters),process.stdout.write(`
235
235
  `),Qi(`How to fix`,n.howToFix),process.stdout.write(`
236
236
  `),Qi(`Impact if ignored`,n.impactIfIgnored),process.stdout.write(`
237
- `),Qi(`How to verify`,n.howToVerify)}});function ru(e){return JSON.stringify(e)}const iu=x({meta:{name:`schedule`,description:`Print local cron entries for recurring SEO workflows`},subCommands:{cron:x({meta:{name:`cron`,description:`Print crontab lines for local recurring SEO workflows`},args:{site:{type:`string`,description:`GSC property URL, for example sc-domain:example.com.`},client:{type:`string`,description:`Legacy alias for --project.`},project:{type:`string`,description:`Saved project id or name.`},url:{type:`string`,description:`Start URL for technical-watch. Defaults from the GSC property when possible.`},urls:{type:`string`,description:`Comma-separated URLs for index-watch.`},hour:{type:`string`,default:`9`,description:`Hour in local cron time. Defaults to 9.`},minute:{type:`string`,default:`0`,description:`Minute in local cron time. Defaults to 0.`},weekday:{type:`string`,description:`Weekly technical-watch day. Defaults to client setting, then Monday.`},day:{type:`string`,description:`Monthly report day. Defaults to client setting, then 1.`},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=n.site,i=T(e.url)??n.client?.startUrl??ka(n.site),a=O(e.hour)??9,o=O(e.minute)??0,s=O(e.weekday)??n.client?.technicalWeekday??1,c=O(e.day)??n.client?.reportDay??1,l=n.client?`--project ${ru(n.client.id)}`:`--site ${ru(r)}`,u=T(e.urls)??n.client?.watchUrls.join(`,`),d=[{name:`technical-watch`,cron:`${o} ${a} * * ${s}`,command:[`seo technical-watch`,l,i?`--url ${ru(i)}`:void 0,u?`--urls ${ru(u)}`:void 0,`--json`].filter(Boolean).join(` `)},{name:`monthly-report`,cron:`${o} ${a} ${c} * *`,command:[`seo monthly-report`,l,`--json`].join(` `)}];if(t){L({site:r,lines:d});return}let f=$t();R([[`Property`,r],[`Install`,`crontab -e`]]);for(let e of d)process.stdout.write(`${e.cron} seo logs prune --quiet; ${e.command} >> ${ru(`${f.logDir}/${e.name}.log`)} 2>&1\n`)}})}}),au=x({meta:{name:`server-logs`,description:`Analyze local web server access logs`},subCommands:{analyze:x({meta:{name:`analyze`,description:`Stream a local access log into bounded crawler evidence`},args:{file:{type:`string`,description:`Local combined, JSONL, or NDJSON access log.`,required:!0},format:{type:`string`,description:`Input format override: combined or jsonl.`},"row-limit":{type:`string`,description:`Maximum rows to parse. Defaults to 1000000.`},"path-limit":{type:`string`,description:`Maximum crawler and path aggregates to retain. Defaults to 25000.`},limit:{type:`string`,description:`Maximum crawler path rows returned. Defaults to 100.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=T(e.file);if(!t)throw new _(`INVALID_INPUT`,`Pass --file.`);let n=T(e.format);if(n&&![`combined`,`jsonl`].includes(n))throw new _(`INVALID_INPUT`,`--format must be combined or jsonl.`);let r=Tn({evidence:await Pn({file:t,format:n,rowLimit:k(e[`row-limit`],`--row-limit`),pathLimit:k(e[`path-limit`],`--path-limit`)}),limit:k(e.limit,`--limit`)});if(M(e)){L(r);return}K({title:`Server log report`,status:r.dataStatus===`complete`?r.summary.invalidRows>0?`warning`:`info`:`unknown`,summary:`${U(r.summary.crawlerRows)} crawler requests found in ${U(r.summary.parsedRows)} parsed requests.`,metrics:[{label:`Evidence`,value:r.dataStatus},{label:`Parsed requests`,value:U(r.summary.parsedRows)},{label:`Crawler requests`,value:U(r.summary.crawlerRows)},{label:`Crawler families`,value:U(r.crawlers.length)},{label:`Invalid rows`,value:U(r.summary.invalidRows)},{label:`Returned paths`,value:`${U(r.selection.returnedCrawlerPaths)} of ${U(r.selection.availableCrawlerPaths)}`}]}),r.crawlers.length&&(process.stdout.write(`
237
+ `),Qi(`How to verify`,n.howToVerify)}});function ru(e){return JSON.stringify(e)}function iu(e){let t=e.cadence??`weekly`;if(![`daily`,`weekly`,`monthly`].includes(t))throw new _(`INVALID_INPUT`,`Rank tracking cron cadence must be daily, weekly, or monthly.`);let n=e.devices?.length?[...new Set(e.devices)]:void 0;if(n?.some(e=>![`desktop`,`mobile`].includes(e))||(n?.length??0)>2)throw new _(`INVALID_INPUT`,`Rank tracking devices must be desktop, mobile, or both.`);if(e.depth!==void 0&&(!Number.isSafeInteger(e.depth)||e.depth<1||e.depth>100))throw new _(`INVALID_INPUT`,`Rank tracking depth must be from 1 to 100.`);if(e.keywordLimit!==void 0&&(!Number.isSafeInteger(e.keywordLimit)||e.keywordLimit<1||e.keywordLimit>1e3))throw new _(`INVALID_INPUT`,`Queued rank tracking can collect 1 to 1000 keywords per run.`);let r={projectId:e.projectId,set:e.set,targetDomain:e.targetDomain,...e.tag?{tag:e.tag}:{},...n?{devices:n}:{},...e.provider?{provider:e.provider}:{},collectionMethod:`queued`,cadence:t,...e.depth===void 0?{}:{depth:e.depth},...e.keywordLimit===void 0?{}:{keywordLimit:e.keywordLimit},start:!0};return{name:`rank-tracking`,cron:`${e.minute} ${e.hour} * * *`,command:[`seo reports run rank-tracking`,`--params ${ru(JSON.stringify(r))}`,`--json`].join(` `)}}const au=x({meta:{name:`schedule`,description:`Print local cron entries for recurring SEO workflows`},subCommands:{cron:x({meta:{name:`cron`,description:`Print crontab lines for local recurring SEO workflows`},args:{site:{type:`string`,description:`GSC property URL, for example sc-domain:example.com.`},client:{type:`string`,description:`Legacy alias for --project.`},project:{type:`string`,description:`Saved project id or name.`},url:{type:`string`,description:`Start URL for technical-watch. Defaults from the GSC property when possible.`},urls:{type:`string`,description:`Comma-separated URLs for index-watch.`},hour:{type:`string`,default:`9`,description:`Hour in local cron time. Defaults to 9.`},minute:{type:`string`,default:`0`,description:`Minute in local cron time. Defaults to 0.`},weekday:{type:`string`,description:`Weekly technical-watch day. Defaults to client setting, then Monday.`},day:{type:`string`,description:`Monthly report day. Defaults to client setting, then 1.`},"rank-set":{type:`string`,description:`Saved keyword set to collect with the rank-tracking report.`},"rank-domain":{type:`string`,description:`Target domain for exact rank matching.`},"rank-tag":{type:`string`,description:`Optional saved keyword tag to track.`},"rank-devices":{type:`string`,description:`Comma-separated desktop and mobile devices.`},"rank-depth":{type:`string`,description:`Organic result depth from 1 to 100.`},"rank-limit":{type:`string`,description:`Maximum saved keywords from 1 to 1000.`},"rank-provider":{type:`string`,description:`Connected provider for exact rank collection.`},"rank-cadence":{type:`string`,description:`Rank collection cadence: daily, weekly, or monthly.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=M(e),n=await V({client:E(e),site:T(e.site),options:{json:t}}),r=n.site,i=T(e.url)??n.client?.startUrl??ka(n.site),a=O(e.hour)??9,o=O(e.minute)??0,s=O(e.weekday)??n.client?.technicalWeekday??1,c=O(e.day)??n.client?.reportDay??1,l=n.client?`--project ${ru(n.client.id)}`:`--site ${ru(r)}`,u=T(e.urls)??n.client?.watchUrls.join(`,`),d=[{name:`technical-watch`,cron:`${o} ${a} * * ${s}`,command:[`seo technical-watch`,l,i?`--url ${ru(i)}`:void 0,u?`--urls ${ru(u)}`:void 0,`--json`].filter(Boolean).join(` `)},{name:`monthly-report`,cron:`${o} ${a} ${c} * *`,command:[`seo monthly-report`,l,`--json`].join(` `)}],f=T(e[`rank-set`]),p=T(e[`rank-domain`]);if(f||p){if(!f||!p)throw new _(`INVALID_INPUT`,`Pass both --rank-set and --rank-domain for scheduled rank tracking.`);if(!n.client)throw new _(`INVALID_INPUT`,`Scheduled rank tracking needs a saved project profile.`);d.push(iu({projectId:n.client.id,set:f,targetDomain:p,tag:T(e[`rank-tag`]),devices:j(e[`rank-devices`]),depth:O(e[`rank-depth`]),keywordLimit:O(e[`rank-limit`]),provider:T(e[`rank-provider`]),cadence:T(e[`rank-cadence`]),hour:a,minute:o,weekday:s,day:c}))}if(t){L({site:r,lines:d});return}let m=$t();R([[`Property`,r],[`Install`,`crontab -e`]]);for(let e of d)process.stdout.write(`${e.cron} seo logs prune --quiet; ${e.command} >> ${ru(`${m.logDir}/${e.name}.log`)} 2>&1\n`)}})}}),ou=x({meta:{name:`server-logs`,description:`Analyze local web server access logs`},subCommands:{analyze:x({meta:{name:`analyze`,description:`Stream a local access log into bounded crawler evidence`},args:{file:{type:`string`,description:`Local combined, JSONL, or NDJSON access log.`,required:!0},format:{type:`string`,description:`Input format override: combined or jsonl.`},"row-limit":{type:`string`,description:`Maximum rows to parse. Defaults to 1000000.`},"path-limit":{type:`string`,description:`Maximum crawler and path aggregates to retain. Defaults to 25000.`},limit:{type:`string`,description:`Maximum crawler path rows returned. Defaults to 100.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=T(e.file);if(!t)throw new _(`INVALID_INPUT`,`Pass --file.`);let n=T(e.format);if(n&&![`combined`,`jsonl`].includes(n))throw new _(`INVALID_INPUT`,`--format must be combined or jsonl.`);let r=Tn({evidence:await Pn({file:t,format:n,rowLimit:k(e[`row-limit`],`--row-limit`),pathLimit:k(e[`path-limit`],`--path-limit`)}),limit:k(e.limit,`--limit`)});if(M(e)){L(r);return}K({title:`Server log report`,status:r.dataStatus===`complete`?r.summary.invalidRows>0?`warning`:`info`:`unknown`,summary:`${U(r.summary.crawlerRows)} crawler requests found in ${U(r.summary.parsedRows)} parsed requests.`,metrics:[{label:`Evidence`,value:r.dataStatus},{label:`Parsed requests`,value:U(r.summary.parsedRows)},{label:`Crawler requests`,value:U(r.summary.crawlerRows)},{label:`Crawler families`,value:U(r.crawlers.length)},{label:`Invalid rows`,value:U(r.summary.invalidRows)},{label:`Returned paths`,value:`${U(r.selection.returnedCrawlerPaths)} of ${U(r.selection.availableCrawlerPaths)}`}]}),r.crawlers.length&&(process.stdout.write(`
238
238
  Crawler activity
239
239
  `),q([`Crawler`,`Type`,`Requests`,`4xx`,`5xx`],r.crawlers.map(e=>[e.family,e.category,String(e.requests),String(e.clientError),String(e.serverError)]))),r.crawlerPaths.length&&(process.stdout.write(`
240
240
  Crawler paths
241
- `),q([`Crawler`,`Path`,`Requests`,`4xx`,`5xx`],r.crawlerPaths.map(e=>[e.family,G(e.path,72),String(e.requests),String(e.clientError),String(e.serverError)])));for(let e of r.warnings)process.stdout.write(`\nWarning: ${e}\n`);process.stdout.write(`\nNote: ${r.caveats[0]}\n`)}})}});function ou(){return cu(`skills`,process.env.SEO_SKILLS_DIR)}function su(){return cu(`evals`,process.env.SEO_EVALS_DIR)}function cu(e,t){if(t)return nr(t);let n=y(sr(import.meta.url));for(let t=0;t<6;t+=1){let t=b(n,e);if(v(b(t,`README.md`)))return t;n=y(n)}throw new _(`INTERNAL_ERROR`,`Packaged ${e} could not be found. Reinstall \`seo\` and try again.`)}function lu(e){return Wn(b(e,`SKILL.md`),`utf8`).match(/^description:\s*(.+)$/m)?.[1]?.trim()??``}const uu=`claude -p`;function du(){let e=su();return Gn(e,{withFileTypes:!0}).filter(e=>e.isFile()&&e.name.endsWith(`.json`)).map(t=>({subject:Qn(t.name,`.json`),path:b(e,t.name)})).sort((e,t)=>e.subject<t.subject?-1:+(e.subject>t.subject))}function fu(e,t){let n;try{n=Wn(t,`utf8`)}catch{throw new _(`INVALID_INPUT`,`Cannot read eval file for ${e}.`)}let r;try{r=JSON.parse(n)}catch{throw new _(`INVALID_INPUT`,`Eval file ${e}.json is not valid JSON.`)}if(!r||typeof r!=`object`||Array.isArray(r))throw new _(`INVALID_INPUT`,`Eval file ${e}.json must be an object.`);let i=r;if(i.subject!==e)throw new _(`INVALID_INPUT`,`Eval file ${e}.json must set subject to "${e}".`);if(!Array.isArray(i.evals)||i.evals.length===0)throw new _(`INVALID_INPUT`,`Eval file ${e}.json must have a non-empty evals array.`);let a=new Set;return{subject:e,evals:i.evals.map((t,n)=>{let r=t,i=`${e}.json eval ${n+1}`;if(!Number.isInteger(r?.id))throw new _(`INVALID_INPUT`,`${i}: id must be an integer.`);let o=r.id;if(a.has(o))throw new _(`INVALID_INPUT`,`${i}: duplicate id ${o}.`);if(a.add(o),typeof r.prompt!=`string`||r.prompt.trim().length===0)throw new _(`INVALID_INPUT`,`${i}: prompt must be a non-empty string.`);if(typeof r.expected_output!=`string`||r.expected_output.trim().length===0)throw new _(`INVALID_INPUT`,`${i}: expected_output must be a non-empty string.`);if(!Array.isArray(r.assertions)||r.assertions.length===0||r.assertions.some(e=>typeof e!=`string`||e.trim().length===0))throw new _(`INVALID_INPUT`,`${i}: assertions must be a non-empty array of non-empty strings.`);if(r.files!==void 0&&!Array.isArray(r.files))throw new _(`INVALID_INPUT`,`${i}: files must be an array.`);return{id:o,prompt:r.prompt,expected_output:r.expected_output,assertions:r.assertions,files:r.files??[]}})}}function pu(){let e=b(ou(),`seo`,`SKILL.md`);if(!v(e))throw new _(`INTERNAL_ERROR`,"The seo router skill could not be found. Reinstall `seo` or use --no-skill.");return Wn(e,`utf8`)}function mu(e){let t=[],n=``,r=!1,i=!1,a=!1;for(let o=0;o<e.length;o+=1){let s=e[o];if(i){s===`'`?i=!1:n+=s,r=!0;continue}if(a){s===`"`?a=!1:s===`\\`&&(e[o+1]===`"`||e[o+1]===`\\`)?(n+=e[o+1],o+=1):n+=s,r=!0;continue}if(s===`'`){i=!0,r=!0;continue}if(s===`"`){a=!0,r=!0;continue}if(s===`\\`){o+1<e.length&&(n+=e[o+1],o+=1,r=!0);continue}if(s===` `||s===` `||s===`
242
- `||s===`\r`){r&&=(t.push(n),n=``,!1);continue}n+=s,r=!0}if(i||a)throw new _(`INVALID_INPUT`,`Unbalanced quote in agent command.`);return r&&t.push(n),t}function hu(e,t){let n=mu(e);if(n.length===0)throw new _(`INVALID_INPUT`,`Agent command must not be empty.`);let r=n.some(e=>e.includes(`{prompt}`));return{argv:r?n.map(e=>e.split(`{prompt}`).join(t)):n,usesPlaceholder:r}}function gu(e,t){return e?`${e.trim()}\n\n---\n\nUser request:\n${t}`:t}function _u(e,t,n,r=!1){let i=[`You are judging whether an AI agent response satisfies a list of assertions.`,`Judge only against the assertions. Be strict but fair.`,``,`Original request:`,e,``,`Agent response:`,t,``,`Assertions to evaluate, in this exact order:`,`<assertions>`,n.map((e,t)=>`${t+1}. ${e}`).join(`
241
+ `),q([`Crawler`,`Path`,`Requests`,`4xx`,`5xx`],r.crawlerPaths.map(e=>[e.family,G(e.path,72),String(e.requests),String(e.clientError),String(e.serverError)])));for(let e of r.warnings)process.stdout.write(`\nWarning: ${e}\n`);process.stdout.write(`\nNote: ${r.caveats[0]}\n`)}})}});function su(){return lu(`skills`,process.env.SEO_SKILLS_DIR)}function cu(){return lu(`evals`,process.env.SEO_EVALS_DIR)}function lu(e,t){if(t)return nr(t);let n=y(sr(import.meta.url));for(let t=0;t<6;t+=1){let t=b(n,e);if(v(b(t,`README.md`)))return t;n=y(n)}throw new _(`INTERNAL_ERROR`,`Packaged ${e} could not be found. Reinstall \`seo\` and try again.`)}function uu(e){return Wn(b(e,`SKILL.md`),`utf8`).match(/^description:\s*(.+)$/m)?.[1]?.trim()??``}const du=`claude -p`;function fu(){let e=cu();return Gn(e,{withFileTypes:!0}).filter(e=>e.isFile()&&e.name.endsWith(`.json`)).map(t=>({subject:Qn(t.name,`.json`),path:b(e,t.name)})).sort((e,t)=>e.subject<t.subject?-1:+(e.subject>t.subject))}function pu(e,t){let n;try{n=Wn(t,`utf8`)}catch{throw new _(`INVALID_INPUT`,`Cannot read eval file for ${e}.`)}let r;try{r=JSON.parse(n)}catch{throw new _(`INVALID_INPUT`,`Eval file ${e}.json is not valid JSON.`)}if(!r||typeof r!=`object`||Array.isArray(r))throw new _(`INVALID_INPUT`,`Eval file ${e}.json must be an object.`);let i=r;if(i.subject!==e)throw new _(`INVALID_INPUT`,`Eval file ${e}.json must set subject to "${e}".`);if(!Array.isArray(i.evals)||i.evals.length===0)throw new _(`INVALID_INPUT`,`Eval file ${e}.json must have a non-empty evals array.`);let a=new Set;return{subject:e,evals:i.evals.map((t,n)=>{let r=t,i=`${e}.json eval ${n+1}`;if(!Number.isInteger(r?.id))throw new _(`INVALID_INPUT`,`${i}: id must be an integer.`);let o=r.id;if(a.has(o))throw new _(`INVALID_INPUT`,`${i}: duplicate id ${o}.`);if(a.add(o),typeof r.prompt!=`string`||r.prompt.trim().length===0)throw new _(`INVALID_INPUT`,`${i}: prompt must be a non-empty string.`);if(typeof r.expected_output!=`string`||r.expected_output.trim().length===0)throw new _(`INVALID_INPUT`,`${i}: expected_output must be a non-empty string.`);if(!Array.isArray(r.assertions)||r.assertions.length===0||r.assertions.some(e=>typeof e!=`string`||e.trim().length===0))throw new _(`INVALID_INPUT`,`${i}: assertions must be a non-empty array of non-empty strings.`);if(r.files!==void 0&&!Array.isArray(r.files))throw new _(`INVALID_INPUT`,`${i}: files must be an array.`);return{id:o,prompt:r.prompt,expected_output:r.expected_output,assertions:r.assertions,files:r.files??[]}})}}function mu(){let e=b(su(),`seo`,`SKILL.md`);if(!v(e))throw new _(`INTERNAL_ERROR`,"The seo router skill could not be found. Reinstall `seo` or use --no-skill.");return Wn(e,`utf8`)}function hu(e){let t=[],n=``,r=!1,i=!1,a=!1;for(let o=0;o<e.length;o+=1){let s=e[o];if(i){s===`'`?i=!1:n+=s,r=!0;continue}if(a){s===`"`?a=!1:s===`\\`&&(e[o+1]===`"`||e[o+1]===`\\`)?(n+=e[o+1],o+=1):n+=s,r=!0;continue}if(s===`'`){i=!0,r=!0;continue}if(s===`"`){a=!0,r=!0;continue}if(s===`\\`){o+1<e.length&&(n+=e[o+1],o+=1,r=!0);continue}if(s===` `||s===` `||s===`
242
+ `||s===`\r`){r&&=(t.push(n),n=``,!1);continue}n+=s,r=!0}if(i||a)throw new _(`INVALID_INPUT`,`Unbalanced quote in agent command.`);return r&&t.push(n),t}function gu(e,t){let n=hu(e);if(n.length===0)throw new _(`INVALID_INPUT`,`Agent command must not be empty.`);let r=n.some(e=>e.includes(`{prompt}`));return{argv:r?n.map(e=>e.split(`{prompt}`).join(t)):n,usesPlaceholder:r}}function _u(e,t){return e?`${e.trim()}\n\n---\n\nUser request:\n${t}`:t}function vu(e,t,n,r=!1){let i=[`You are judging whether an AI agent response satisfies a list of assertions.`,`Judge only against the assertions. Be strict but fair.`,``,`Original request:`,e,``,`Agent response:`,t,``,`Assertions to evaluate, in this exact order:`,`<assertions>`,n.map((e,t)=>`${t+1}. ${e}`).join(`
243
243
  `),`</assertions>`,``,`Judge outcomes, not narration. An assertion about reading, checking, or`,`following fields or steps passes when the reply demonstrably used that`,`information or produced the result it protects, even if the reply never`,`names the field or step. Fail it only when the reply contradicts it or`,`shows the work was not done.`,``,`Reply with STRICT JSON only. No prose, no markdown, no code fence:`,`{"results":[{"assertion":"<assertion text>","pass":true,"reason":"<short reason>"}]}`,`Return exactly ${n.length} result${n.length===1?``:`s`}, one per assertion, in the same order.`].join(`
244
- `);return r?`${i}\n\nYour previous reply could not be parsed as the required JSON with one result per assertion. Reply again with STRICT JSON only, in the exact shape shown, and nothing else.`:i}function vu(e){let t=e.indexOf(`{`);if(t===-1)return;let n=0,r=!1,i=!1;for(let a=t;a<e.length;a+=1){let o=e[a];if(r){i?i=!1:o===`\\`?i=!0:o===`"`&&(r=!1);continue}if(o===`"`)r=!0;else if(o===`{`)n+=1;else if(o===`}`&&(--n,n===0))try{return JSON.parse(e.slice(t,a+1))}catch{return}}}function yu(e,t){let n=vu(e);if(!n||typeof n!=`object`)return{error:`Judge reply did not contain a JSON object.`};let r=n.results;return Array.isArray(r)?r.length===t.length?{verdicts:t.map((e,t)=>{let n=r[t];return{assertion:e,verdict:n?.pass===!0?`pass`:`fail`,reason:typeof n?.reason==`string`&&n.reason.trim().length>0?n.reason.trim():`No reason given.`,source:`judge verdict`}})}:{error:`Judge returned ${r.length} results for ${t.length} assertions.`}:{error:`Judge reply had no results array.`}}function bu(e,t){return new Promise(n=>{let r=Date.now(),[i,...a]=e,o;try{o=ar(i,a,{stdio:[`pipe`,`pipe`,`pipe`]})}catch(e){n({stdout:``,stderr:``,code:null,timedOut:!1,spawnError:e instanceof Error?e.message:String(e),durationMs:Date.now()-r});return}let s=``,c=``,l=!1,u=!1,d,f=setTimeout(()=>{u=!0,o.kill(`SIGTERM`),d=setTimeout(()=>{try{o.kill(`SIGKILL`)}catch{}},2e3),d.unref()},t.timeoutMs),p=e=>{l||(l=!0,clearTimeout(f),d&&clearTimeout(d),n({...e,durationMs:Date.now()-r}))};o.stdout?.on(`data`,e=>{s+=e}),o.stderr?.on(`data`,e=>{c+=e}),o.on(`error`,e=>{p({stdout:s,stderr:c,code:null,timedOut:u,spawnError:e.message})}),o.on(`close`,e=>{p({stdout:s,stderr:c,code:e,timedOut:u})}),o.stdin&&(t.input===void 0?o.stdin.end():o.stdin.end(t.input))})}async function xu(e,t,n){let{argv:r,usesPlaceholder:i}=hu(e,t);return bu(r,{input:i?void 0:t,timeoutMs:n})}async function Su(e,t){let n=Date.now(),r=gu(t.skillBody,e.prompt),i=await xu(t.agent,r,t.timeoutMs),a=i.stdout.trim(),o={id:e.id,prompt:e.prompt,reply:a,agentDurationMs:i.durationMs};if(i.spawnError)return{...o,status:`errored`,error:`Agent command failed to start: ${i.spawnError}`,durationMs:Date.now()-n,assertions:Cu(e.assertions)};if(i.timedOut)return{...o,status:`errored`,error:`Agent timed out after ${Math.round(t.timeoutMs/1e3)}s.`,durationMs:Date.now()-n,assertions:Cu(e.assertions)};if(a.length===0)return{...o,status:`errored`,error:`Agent returned no output on stdout.`,durationMs:Date.now()-n,assertions:Cu(e.assertions)};if(!t.judge)return{...o,status:`unjudged`,durationMs:Date.now()-n,assertions:Cu(e.assertions)};let s=0,c=``;for(let r=0;r<2;r+=1){let i=_u(e.prompt,a,e.assertions,r>0),l=await xu(t.judge,i,t.timeoutMs);if(s+=l.durationMs,l.spawnError)return{...o,status:`errored`,error:`Judge command failed to start: ${l.spawnError}`,judgeDurationMs:s,durationMs:Date.now()-n,assertions:Cu(e.assertions)};if(l.timedOut)return{...o,status:`errored`,error:`Judge timed out after ${Math.round(t.timeoutMs/1e3)}s.`,judgeDurationMs:s,durationMs:Date.now()-n,assertions:Cu(e.assertions)};let u=yu(l.stdout,e.assertions);if(`verdicts`in u){let e=u.verdicts.some(e=>e.verdict===`fail`);return{...o,status:e?`failed`:`passed`,judgeDurationMs:s,durationMs:Date.now()-n,assertions:u.verdicts}}c=u.error}return{...o,status:`errored`,error:`Judge output could not be parsed after a retry: ${c}`,judgeDurationMs:s,durationMs:Date.now()-n,assertions:Cu(e.assertions)}}function Cu(e){return e.map(e=>({assertion:e,verdict:`unjudged`}))}async function wu(e,t,n){let r=Array(e.length),i=0,a=async()=>{for(;;){let t=i;if(i+=1,t>=e.length)return;r[t]=await n(e[t],t)}},o=Math.max(1,Math.min(t,e.length));return await Promise.all(Array.from({length:o},()=>a())),r}function Tu(e){let t={passed:0,failed:0,errored:0,unjudged:0};for(let n of e)t[n.status]+=1;return t}function Eu(e){let t=[],n=T(e.subject);n&&t.push(n);let r=e._;if(Array.isArray(r))for(let e of r)typeof e==`string`&&e.length>0&&t.push(e);return[...new Set(t)]}function Du(e){let t=du();if(e.length===0)return t;let n=new Map(t.map(e=>[e.subject,e]));return e.map(e=>{let r=n.get(e);if(!r){let n=t.map(e=>e.subject).join(`, `);throw new _(`INVALID_INPUT`,`Unknown eval subject: ${e}.${n?` Known subjects: ${n}.`:``}`)}return r})}function Ou(e){let t=du().map(e=>{let t=fu(e.subject,e.path);return{subject:e.subject,evals:t.evals.length}});if(e){L({schemaVersion:1,subjects:t});return}if(t.length===0){process.stdout.write(`No eval subjects found.
244
+ `);return r?`${i}\n\nYour previous reply could not be parsed as the required JSON with one result per assertion. Reply again with STRICT JSON only, in the exact shape shown, and nothing else.`:i}function yu(e){let t=e.indexOf(`{`);if(t===-1)return;let n=0,r=!1,i=!1;for(let a=t;a<e.length;a+=1){let o=e[a];if(r){i?i=!1:o===`\\`?i=!0:o===`"`&&(r=!1);continue}if(o===`"`)r=!0;else if(o===`{`)n+=1;else if(o===`}`&&(--n,n===0))try{return JSON.parse(e.slice(t,a+1))}catch{return}}}function bu(e,t){let n=yu(e);if(!n||typeof n!=`object`)return{error:`Judge reply did not contain a JSON object.`};let r=n.results;return Array.isArray(r)?r.length===t.length?{verdicts:t.map((e,t)=>{let n=r[t];return{assertion:e,verdict:n?.pass===!0?`pass`:`fail`,reason:typeof n?.reason==`string`&&n.reason.trim().length>0?n.reason.trim():`No reason given.`,source:`judge verdict`}})}:{error:`Judge returned ${r.length} results for ${t.length} assertions.`}:{error:`Judge reply had no results array.`}}function xu(e,t){return new Promise(n=>{let r=Date.now(),[i,...a]=e,o;try{o=ar(i,a,{stdio:[`pipe`,`pipe`,`pipe`]})}catch(e){n({stdout:``,stderr:``,code:null,timedOut:!1,spawnError:e instanceof Error?e.message:String(e),durationMs:Date.now()-r});return}let s=``,c=``,l=!1,u=!1,d,f=setTimeout(()=>{u=!0,o.kill(`SIGTERM`),d=setTimeout(()=>{try{o.kill(`SIGKILL`)}catch{}},2e3),d.unref()},t.timeoutMs),p=e=>{l||(l=!0,clearTimeout(f),d&&clearTimeout(d),n({...e,durationMs:Date.now()-r}))};o.stdout?.on(`data`,e=>{s+=e}),o.stderr?.on(`data`,e=>{c+=e}),o.on(`error`,e=>{p({stdout:s,stderr:c,code:null,timedOut:u,spawnError:e.message})}),o.on(`close`,e=>{p({stdout:s,stderr:c,code:e,timedOut:u})}),o.stdin&&(t.input===void 0?o.stdin.end():o.stdin.end(t.input))})}async function Su(e,t,n){let{argv:r,usesPlaceholder:i}=gu(e,t);return xu(r,{input:i?void 0:t,timeoutMs:n})}async function Cu(e,t){let n=Date.now(),r=_u(t.skillBody,e.prompt),i=await Su(t.agent,r,t.timeoutMs),a=i.stdout.trim(),o={id:e.id,prompt:e.prompt,reply:a,agentDurationMs:i.durationMs};if(i.spawnError)return{...o,status:`errored`,error:`Agent command failed to start: ${i.spawnError}`,durationMs:Date.now()-n,assertions:wu(e.assertions)};if(i.timedOut)return{...o,status:`errored`,error:`Agent timed out after ${Math.round(t.timeoutMs/1e3)}s.`,durationMs:Date.now()-n,assertions:wu(e.assertions)};if(a.length===0)return{...o,status:`errored`,error:`Agent returned no output on stdout.`,durationMs:Date.now()-n,assertions:wu(e.assertions)};if(!t.judge)return{...o,status:`unjudged`,durationMs:Date.now()-n,assertions:wu(e.assertions)};let s=0,c=``;for(let r=0;r<2;r+=1){let i=vu(e.prompt,a,e.assertions,r>0),l=await Su(t.judge,i,t.timeoutMs);if(s+=l.durationMs,l.spawnError)return{...o,status:`errored`,error:`Judge command failed to start: ${l.spawnError}`,judgeDurationMs:s,durationMs:Date.now()-n,assertions:wu(e.assertions)};if(l.timedOut)return{...o,status:`errored`,error:`Judge timed out after ${Math.round(t.timeoutMs/1e3)}s.`,judgeDurationMs:s,durationMs:Date.now()-n,assertions:wu(e.assertions)};let u=bu(l.stdout,e.assertions);if(`verdicts`in u){let e=u.verdicts.some(e=>e.verdict===`fail`);return{...o,status:e?`failed`:`passed`,judgeDurationMs:s,durationMs:Date.now()-n,assertions:u.verdicts}}c=u.error}return{...o,status:`errored`,error:`Judge output could not be parsed after a retry: ${c}`,judgeDurationMs:s,durationMs:Date.now()-n,assertions:wu(e.assertions)}}function wu(e){return e.map(e=>({assertion:e,verdict:`unjudged`}))}async function Tu(e,t,n){let r=Array(e.length),i=0,a=async()=>{for(;;){let t=i;if(i+=1,t>=e.length)return;r[t]=await n(e[t],t)}},o=Math.max(1,Math.min(t,e.length));return await Promise.all(Array.from({length:o},()=>a())),r}function Eu(e){let t={passed:0,failed:0,errored:0,unjudged:0};for(let n of e)t[n.status]+=1;return t}function Du(e){let t=[],n=T(e.subject);n&&t.push(n);let r=e._;if(Array.isArray(r))for(let e of r)typeof e==`string`&&e.length>0&&t.push(e);return[...new Set(t)]}function Ou(e){let t=fu();if(e.length===0)return t;let n=new Map(t.map(e=>[e.subject,e]));return e.map(e=>{let r=n.get(e);if(!r){let n=t.map(e=>e.subject).join(`, `);throw new _(`INVALID_INPUT`,`Unknown eval subject: ${e}.${n?` Known subjects: ${n}.`:``}`)}return r})}function ku(e){let t=fu().map(e=>{let t=pu(e.subject,e.path);return{subject:e.subject,evals:t.evals.length}});if(e){L({schemaVersion:1,subjects:t});return}if(t.length===0){process.stdout.write(`No eval subjects found.
245
245
  `);return}let n=Math.max(...t.map(e=>e.subject.length));process.stdout.write(`Eval subjects
246
- `);for(let e of t){let t=e.evals===1?`eval`:`evals`;process.stdout.write(` ${e.subject.padEnd(n)} ${e.evals} ${t}\n`)}}function ku(e){return e===`passed`?`PASS`:e===`failed`?`FAIL`:e===`errored`?`ERROR`:`UNJUDGED`}function Au(e,t){t.judged?process.stdout.write(`Assertion verdicts below are judge verdicts, model judgments from the judge agent, not ground truth.
246
+ `);for(let e of t){let t=e.evals===1?`eval`:`evals`;process.stdout.write(` ${e.subject.padEnd(n)} ${e.evals} ${t}\n`)}}function Au(e){return e===`passed`?`PASS`:e===`failed`?`FAIL`:e===`errored`?`ERROR`:`UNJUDGED`}function ju(e,t){t.judged?process.stdout.write(`Assertion verdicts below are judge verdicts, model judgments from the judge agent, not ground truth.
247
247
 
248
248
  `):process.stdout.write(`Judging was skipped. Review each agent reply against the assertion checklist by hand.
249
249
 
250
- `);for(let n of e){process.stdout.write(`# ${n.subject}\n`);for(let e of n.evals){if(process.stdout.write(`\n[${ku(e.status)}] eval ${e.id} (${e.durationMs}ms)\n`),process.stdout.write(` prompt: ${Mu(e.prompt,100)}\n`),e.error&&process.stdout.write(` error: ${e.error}\n`),!t.judged){process.stdout.write(` reply: ${Mu(e.reply,200)}\n`),process.stdout.write(` assertions to review:
251
- `);for(let t of e.assertions)process.stdout.write(` - [ ] ${t.assertion}\n`);continue}for(let t of e.assertions){let e=t.verdict===`pass`?`pass`:t.verdict===`fail`?`fail`:`unjudged`;process.stdout.write(` - ${e}: ${t.assertion}\n`),t.reason&&process.stdout.write(` judge verdict: ${t.reason}\n`)}}let e=Tu(n.evals);process.stdout.write(`\nSubject ${n.subject}: ${e.passed} passed, ${e.failed} failed, ${e.errored} errored${e.unjudged?`, ${e.unjudged} unjudged`:``}.\n\n`)}let n=ju(e);process.stdout.write(`Totals: ${n.passed} passed, ${n.failed} failed, ${n.errored} errored${n.unjudged?`, ${n.unjudged} unjudged`:``} across ${n.evals} evals.\n`)}function ju(e){let t={passed:0,failed:0,errored:0,unjudged:0,evals:0};for(let n of e)t.passed+=n.passed,t.failed+=n.failed,t.errored+=n.errored,t.unjudged+=n.unjudged,t.evals+=n.evals.length;return t}function Mu(e,t){let n=e.replace(/\s+/g,` `).trim();return n.length>t?`${n.slice(0,t-1)}…`:n}const Nu=x({meta:{name:`eval`,description:`Run behaviour evals for the seo skill against a local agent and judge the results`},args:{subject:{type:`positional`,required:!1,description:`Eval subject (report id or job). Omit to run every subject.`},list:{type:`boolean`,default:!1,description:`List eval subjects with their eval counts and exit.`},id:{type:`string`,description:`Run one eval id within a single subject.`},agent:{type:`string`,default:uu,description:`Agent command. Use {prompt} to place the prompt in argv, otherwise it is sent on stdin.`},"judge-agent":{type:`string`,description:`Judge command. Defaults to the same command as --agent.`},judge:w(`Judge assertions with an agent.`,`Skip judging and print replies with an assertion checklist.`),skill:w(`Send the seo router skill as context before the prompt.`,`Omit the seo router skill context.`),timeout:{type:`string`,default:`300`,description:`Timeout in seconds per agent or judge call.`},concurrency:{type:`string`,default:`1`,description:`Evals to run in parallel (max 4).`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=M(e);if(e.list===!0){Ou(t);return}let n=Du(Eu(e)),r=O(e.id);if(r!==void 0&&n.length!==1)throw new _(`INVALID_INPUT`,`--id needs exactly one subject. Name the subject to filter.`);let i=O(e.timeout)??300;if(!(i>0))throw new _(`INVALID_INPUT`,`--timeout must be a positive number of seconds.`);let a=Math.max(1,Math.min(4,O(e.concurrency)??1)),o=T(e.agent)??uu,s=e.judge===!1?null:T(e[`judge-agent`])??o,c=e.skill===!1?null:pu(),l={agent:o,judge:s,skillBody:c,timeoutMs:i*1e3},u=[];for(let e of n){let n=fu(e.subject,e.path).evals;if(r!==void 0&&(n=n.filter(e=>e.id===r),n.length===0))throw new _(`INVALID_INPUT`,`No eval with id ${r} in subject ${e.subject}.`);t||process.stderr.write(`Running ${n.length} eval${n.length===1?``:`s`} for ${e.subject}...\n`);let i=await wu(n,a,e=>Su(e,l)),o=Tu(i);u.push({subject:e.subject,...o,evals:i})}let d=ju(u),f=d.failed>0||d.errored>0;t?L({schemaVersion:1,agent:o,judge:s,judged:s!==null,skillContext:c!==null,timeoutSeconds:i,concurrency:a,subjects:u,totals:d}):Au(u,{judged:s!==null,agent:o,judge:s}),f&&(process.exitCode=1)}});function Pu(){let e=ou();return Gn(e,{withFileTypes:!0}).filter(t=>t.isDirectory()&&v(b(e,t.name,`SKILL.md`))).map(t=>({name:t.name,description:lu(b(e,t.name)),path:b(e,t.name)})).sort((e,t)=>e.name<t.name?-1:+(e.name>t.name))}function Fu(e){let t=Pu();if(!e)return t;let n=t.find(t=>t.name===e);if(!n)throw new _(`INVALID_INPUT`,`Unknown skill: ${e}.`);return[n]}const Iu=x({meta:{name:`skill`,description:`Inspect the packaged SEO skill and run its evals`},subCommands:{list:x({meta:{name:`list`,description:`Show the packaged SEO skill`},args:{json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:({args:e})=>{let t=Pu();if(M(e)){L({skills:t});return}qi(`SEO skill`,`Packaged with this CLI.`),process.stdout.write(`
250
+ `);for(let n of e){process.stdout.write(`# ${n.subject}\n`);for(let e of n.evals){if(process.stdout.write(`\n[${Au(e.status)}] eval ${e.id} (${e.durationMs}ms)\n`),process.stdout.write(` prompt: ${Nu(e.prompt,100)}\n`),e.error&&process.stdout.write(` error: ${e.error}\n`),!t.judged){process.stdout.write(` reply: ${Nu(e.reply,200)}\n`),process.stdout.write(` assertions to review:
251
+ `);for(let t of e.assertions)process.stdout.write(` - [ ] ${t.assertion}\n`);continue}for(let t of e.assertions){let e=t.verdict===`pass`?`pass`:t.verdict===`fail`?`fail`:`unjudged`;process.stdout.write(` - ${e}: ${t.assertion}\n`),t.reason&&process.stdout.write(` judge verdict: ${t.reason}\n`)}}let e=Eu(n.evals);process.stdout.write(`\nSubject ${n.subject}: ${e.passed} passed, ${e.failed} failed, ${e.errored} errored${e.unjudged?`, ${e.unjudged} unjudged`:``}.\n\n`)}let n=Mu(e);process.stdout.write(`Totals: ${n.passed} passed, ${n.failed} failed, ${n.errored} errored${n.unjudged?`, ${n.unjudged} unjudged`:``} across ${n.evals} evals.\n`)}function Mu(e){let t={passed:0,failed:0,errored:0,unjudged:0,evals:0};for(let n of e)t.passed+=n.passed,t.failed+=n.failed,t.errored+=n.errored,t.unjudged+=n.unjudged,t.evals+=n.evals.length;return t}function Nu(e,t){let n=e.replace(/\s+/g,` `).trim();return n.length>t?`${n.slice(0,t-1)}…`:n}const Pu=x({meta:{name:`eval`,description:`Run behaviour evals for the seo skill against a local agent and judge the results`},args:{subject:{type:`positional`,required:!1,description:`Eval subject (report id or job). Omit to run every subject.`},list:{type:`boolean`,default:!1,description:`List eval subjects with their eval counts and exit.`},id:{type:`string`,description:`Run one eval id within a single subject.`},agent:{type:`string`,default:du,description:`Agent command. Use {prompt} to place the prompt in argv, otherwise it is sent on stdin.`},"judge-agent":{type:`string`,description:`Judge command. Defaults to the same command as --agent.`},judge:w(`Judge assertions with an agent.`,`Skip judging and print replies with an assertion checklist.`),skill:w(`Send the seo router skill as context before the prompt.`,`Omit the seo router skill context.`),timeout:{type:`string`,default:`300`,description:`Timeout in seconds per agent or judge call.`},concurrency:{type:`string`,default:`1`,description:`Evals to run in parallel (max 4).`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=M(e);if(e.list===!0){ku(t);return}let n=Ou(Du(e)),r=O(e.id);if(r!==void 0&&n.length!==1)throw new _(`INVALID_INPUT`,`--id needs exactly one subject. Name the subject to filter.`);let i=O(e.timeout)??300;if(!(i>0))throw new _(`INVALID_INPUT`,`--timeout must be a positive number of seconds.`);let a=Math.max(1,Math.min(4,O(e.concurrency)??1)),o=T(e.agent)??du,s=e.judge===!1?null:T(e[`judge-agent`])??o,c=e.skill===!1?null:mu(),l={agent:o,judge:s,skillBody:c,timeoutMs:i*1e3},u=[];for(let e of n){let n=pu(e.subject,e.path).evals;if(r!==void 0&&(n=n.filter(e=>e.id===r),n.length===0))throw new _(`INVALID_INPUT`,`No eval with id ${r} in subject ${e.subject}.`);t||process.stderr.write(`Running ${n.length} eval${n.length===1?``:`s`} for ${e.subject}...\n`);let i=await Tu(n,a,e=>Cu(e,l)),o=Eu(i);u.push({subject:e.subject,...o,evals:i})}let d=Mu(u),f=d.failed>0||d.errored>0;t?L({schemaVersion:1,agent:o,judge:s,judged:s!==null,skillContext:c!==null,timeoutSeconds:i,concurrency:a,subjects:u,totals:d}):ju(u,{judged:s!==null,agent:o,judge:s}),f&&(process.exitCode=1)}});function Fu(){let e=su();return Gn(e,{withFileTypes:!0}).filter(t=>t.isDirectory()&&v(b(e,t.name,`SKILL.md`))).map(t=>({name:t.name,description:uu(b(e,t.name)),path:b(e,t.name)})).sort((e,t)=>e.name<t.name?-1:+(e.name>t.name))}function Iu(e){let t=Fu();if(!e)return t;let n=t.find(t=>t.name===e);if(!n)throw new _(`INVALID_INPUT`,`Unknown skill: ${e}.`);return[n]}const Lu=x({meta:{name:`skill`,description:`Inspect the packaged SEO skill and run its evals`},subCommands:{list:x({meta:{name:`list`,description:`Show the packaged SEO skill`},args:{json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:({args:e})=>{let t=Fu();if(M(e)){L({skills:t});return}qi(`SEO skill`,`Packaged with this CLI.`),process.stdout.write(`
252
252
  `);for(let e of t)R([[`Name`,e.name],[`Purpose`,`Helps agents choose and run the right SEO audit or report.`],[`Path`,e.path]]);process.stdout.write(`
253
- `),Ki({title:`Add it to an agent`,body:`Run setup to install the skill and connect your first site.`,command:`seo start`})}}),path:x({meta:{name:`path`,description:`Print a packaged agent skill path`},args:{name:{type:`positional`,description:`Skill name. Omit it for the skill directory.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:({args:e})=>{let t=T(e.name),n=t?Fu(t)[0]?.path:ou();M(e)?L({name:t,path:n}):process.stdout.write(`${n}\n`)}}),install:x({meta:{name:`install`,description:`Install the SEO skill for coding agents`},args:{json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:({args:e})=>{let t=M(e),n=io({quiet:t});if(t){L(n),n.status===`failed`&&(process.exitCode=1);return}if(n.status===`failed`)throw new _(`INTERNAL_ERROR`,`Skill install failed: ${n.error}`);process.stdout.write(`SEO skill installed. Agents that support skills can discover it now.
254
- `)}}),eval:Nu}}),Lu=x({meta:{name:`privacy`,description:`Show local config, token, cache, and log storage paths`},run:async()=>{let e=nt(),t=ae();R(e.map(e=>[e.label,`${e.path} · ${ta(e.sizeBytes)} · ${e.mode}`])),process.stdout.write(`
255
- `),R(Object.entries(t.counts).map(([e,t])=>[e,String(t)]))}}),Ru=x({meta:{name:`reset`,description:`Delete local seo config, tokens, cache, and logs`},args:{yes:{type:`boolean`,default:!1,description:`Confirm deletion without prompting.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=mn(),n=M(e);if(!e.yes&&!I({json:n}))throw new _(`INVALID_INPUT`,`Cannot prompt here. Pass --yes to confirm reset.`);if(!(e.yes||B(await Cr({message:`Delete config, tokens, cache, and logs?`,initialValue:!1})))){Sr(`Reset aborted.`);return}await at(),Jn(t.configDir,{recursive:!0,force:!0}),Jn(t.cacheDir,{recursive:!0,force:!0}),Jn(t.logDir,{recursive:!0,force:!0}),n?L({reset:!0}):process.stdout.write(`Reset complete.
256
- `)}}),zu=x({meta:{name:`sites`,description:`List Search Console properties available to this Google login`},args:{json:{type:`boolean`,default:!1},refresh:{type:`boolean`,default:!1}},run:async({args:e})=>{let t=await Me(D(e.refresh));if(M(e)){L({sites:t});return}z([`Property`,`Permission`],t.map(e=>[e.siteUrl,e.permissionLevel??`unknown`]))}}),Bu=x({meta:{name:`updates`,description:`List official Google Search Status ranking updates`},args:{product:{type:`string`,default:`Ranking`},limit:{type:`string`,default:`10`},json:{type:`boolean`,default:!1}},run:async({args:e})=>{let t=await le({product:T(e.product),limit:O(e.limit)});if(M(e)){L({updates:t});return}Zi(t.map(e=>({title:e.name,meta:[e.type,e.status,`${e.start.slice(0,10)} to ${e.end?.slice(0,10)??`present`}`]})),{empty:`No matching Google Search updates.`})}}),Vu={ci:`disabled automatically in CI or tests`,do_not_track:`disabled by DO_NOT_TRACK`,environment:`disabled by a telemetry environment variable`,local_setting:`disabled in local settings`,invalid_state:`disabled because the local state file is invalid`};function Hu(e){return{enabled:e.enabled,...e.reason?{reason:e.reason}:{},stateFile:e.stateFile,...e.state?{firstRunAt:e.state.firstRunAt,cohort:e.state.cohort,sentMilestones:e.state.sentMilestones}:{}}}function Uu(e,t){let n=Hu(e);if(t){L(n);return}R([[`Anonymous telemetry`,e.enabled?`enabled`:`disabled`],[`Reason`,e.reason?Vu[e.reason]:`local setting`],[`State file`,e.stateFile],[`Install cohort`,e.state?.cohort??`not created`]]),process.stdout.write(`
253
+ `),Ki({title:`Add it to an agent`,body:`Run setup to install the skill and connect your first site.`,command:`seo start`})}}),path:x({meta:{name:`path`,description:`Print a packaged agent skill path`},args:{name:{type:`positional`,description:`Skill name. Omit it for the skill directory.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:({args:e})=>{let t=T(e.name),n=t?Iu(t)[0]?.path:su();M(e)?L({name:t,path:n}):process.stdout.write(`${n}\n`)}}),install:x({meta:{name:`install`,description:`Install the SEO skill for coding agents`},args:{json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:({args:e})=>{let t=M(e),n=io({quiet:t});if(t){L(n),n.status===`failed`&&(process.exitCode=1);return}if(n.status===`failed`)throw new _(`INTERNAL_ERROR`,`Skill install failed: ${n.error}`);process.stdout.write(`SEO skill installed. Agents that support skills can discover it now.
254
+ `)}}),eval:Pu}}),Ru=x({meta:{name:`privacy`,description:`Show local config, token, cache, and log storage paths`},run:async()=>{let e=nt(),t=ae();R(e.map(e=>[e.label,`${e.path} · ${ta(e.sizeBytes)} · ${e.mode}`])),process.stdout.write(`
255
+ `),R(Object.entries(t.counts).map(([e,t])=>[e,String(t)]))}}),zu=x({meta:{name:`reset`,description:`Delete local seo config, tokens, cache, and logs`},args:{yes:{type:`boolean`,default:!1,description:`Confirm deletion without prompting.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=mn(),n=M(e);if(!e.yes&&!I({json:n}))throw new _(`INVALID_INPUT`,`Cannot prompt here. Pass --yes to confirm reset.`);if(!(e.yes||B(await Cr({message:`Delete config, tokens, cache, and logs?`,initialValue:!1})))){Sr(`Reset aborted.`);return}await at(),Jn(t.configDir,{recursive:!0,force:!0}),Jn(t.cacheDir,{recursive:!0,force:!0}),Jn(t.logDir,{recursive:!0,force:!0}),n?L({reset:!0}):process.stdout.write(`Reset complete.
256
+ `)}}),Bu=x({meta:{name:`sites`,description:`List Search Console properties available to this Google login`},args:{json:{type:`boolean`,default:!1},refresh:{type:`boolean`,default:!1}},run:async({args:e})=>{let t=await Me(D(e.refresh));if(M(e)){L({sites:t});return}z([`Property`,`Permission`],t.map(e=>[e.siteUrl,e.permissionLevel??`unknown`]))}}),Vu=x({meta:{name:`updates`,description:`List official Google Search Status ranking updates`},args:{product:{type:`string`,default:`Ranking`},limit:{type:`string`,default:`10`},json:{type:`boolean`,default:!1}},run:async({args:e})=>{let t=await le({product:T(e.product),limit:O(e.limit)});if(M(e)){L({updates:t});return}Zi(t.map(e=>({title:e.name,meta:[e.type,e.status,`${e.start.slice(0,10)} to ${e.end?.slice(0,10)??`present`}`]})),{empty:`No matching Google Search updates.`})}}),Hu={ci:`disabled automatically in CI or tests`,do_not_track:`disabled by DO_NOT_TRACK`,environment:`disabled by a telemetry environment variable`,local_setting:`disabled in local settings`,invalid_state:`disabled because the local state file is invalid`};function Uu(e){return{enabled:e.enabled,...e.reason?{reason:e.reason}:{},stateFile:e.stateFile,...e.state?{firstRunAt:e.state.firstRunAt,cohort:e.state.cohort,sentMilestones:e.state.sentMilestones}:{}}}function Wu(e,t){let n=Uu(e);if(t){L(n);return}R([[`Anonymous telemetry`,e.enabled?`enabled`:`disabled`],[`Reason`,e.reason?Hu[e.reason]:`local setting`],[`State file`,e.stateFile],[`Install cohort`,e.state?.cohort??`not created`]]),process.stdout.write(`
257
257
  Only tool usage is sent. URLs, report data, Google data, and identifiers are never sent.
258
258
  `),process.stdout.write(`Details: https://seoskill.dev/telemetry
259
- `)}const Wu={type:`boolean`,default:!1,description:`Print machine-readable JSON.`},Gu=x({meta:{name:`telemetry`,description:`Check or change anonymous usage telemetry`},subCommands:{status:x({meta:{name:`status`,description:`Show whether anonymous usage telemetry is enabled`},args:{json:Wu},run:async({args:e})=>Uu(Sn(),M(e))}),enable:x({meta:{name:`enable`,description:`Enable anonymous usage telemetry in local settings`},args:{json:Wu},run:async({args:e})=>{Uu(Ot(!0),M(e))}}),disable:x({meta:{name:`disable`,description:`Disable anonymous usage telemetry in local settings`},args:{json:Wu},run:async({args:e})=>{Uu(Ot(!1),M(e))}})}}),Ku=x({meta:{name:`crawl-queue`,description:`Run a crawl and return a ranked implementation queue`},args:{crawlUrl:{type:`positional`,required:!1,description:`Start URL to crawl.`},url:{type:`string`,description:`Start URL to crawl. Optional when --project has a saved crawl URL.`},site:{type:`string`,description:`GSC property URL for joining page metrics.`},client:{type:`string`,description:`Legacy alias for --project.`},project:{type:`string`,description:`Saved project id or name.`},"google-analytics-property":{type:`string`,description:`Google Analytics property ID for landing-page sessions. Defaults from --project when saved.`},mode:{type:`string`,description:`Crawl mode: site, page, list, or sitemap. Defaults to site.`},urls:{type:`string`,description:`Comma-separated URLs for list mode.`},"urls-file":{type:`string`,description:`File with one URL per line for list mode.`},"max-pages":{type:`string`,description:`Maximum pages to crawl. Defaults to 500.`},"max-depth":{type:`string`,description:`Maximum click depth. Defaults to 16.`},concurrency:{type:`string`,description:`Parallel page fetches. Defaults to 8.`},"fetch-interval-cap":{type:`string`,description:`Maximum page fetches per interval per host.`},"fetch-interval-ms":{type:`string`,description:`Fetch rate interval in milliseconds.`},refresh:{type:`boolean`,default:!1,description:`Bypass local HTTP cache and fetch fresh pages.`},include:{type:`string`,description:`Comma-separated URL patterns to include.`},exclude:{type:`string`,description:`Comma-separated URL patterns to exclude.`},limit:{type:`string`,description:`Maximum queue items to print. Defaults to 25.`},"no-sitemap":{type:`boolean`,default:!1,description:`Do not seed URLs from sitemap.xml.`},"no-robots":{type:`boolean`,default:!1,description:`Do not skip URLs disallowed by robots.txt.`},"no-external":{type:`boolean`,default:!1,description:`Do not check external links.`},js:{type:`boolean`,default:!1,description:`Force JavaScript rendering when Playwright is installed.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=M(e),n=E(e),r=await Yu(e),i=Ju(e),a=T(e.site)||n||!i&&!r.length?await V({client:n,site:T(e.site),options:{json:t}}):void 0,o=i??a?.client?.startUrl??(a?.site?ka(a.site):void 0)??r[0];if(!o)throw Error(`No crawl URL selected. Pass a URL, --url, or use --project with a saved crawl URL.`);let s=await it({url:o,projectId:a?.client?.id,site:a?.site,mode:qu(e.mode)??(r.length?`list`:void 0),urls:r,maxPages:O(e[`max-pages`]),maxDepth:O(e[`max-depth`]),concurrency:O(e.concurrency),refresh:D(e.refresh),fetchRate:li({...e,"fetch-concurrency":e.concurrency}),include:A(e.include),exclude:A(e.exclude),googleAnalyticsPropertyId:T(e[`google-analytics-property`])??a?.client?.analytics.google?.propertyId,useSitemap:!oi(e,`sitemap`),respectRobots:!oi(e,`robots`),checkExternal:!oi(e,`external`),js:!!D(e.js),limit:O(e.limit)});if(t){L(s);return}Rc(s),z([`Rank`,`Score`,`Rule`,`Severity`,`Affected`,`Target`,`Action`],s.output.queue.map((e,t)=>[t+1,e.score,e.ruleId,e.severity,e.affectedUrls,e.target,e.action])),J(`Implementation queue details`,s.output.queue.map(e=>({label:`${e.ruleId} (${e.affectedUrls} URLs)`,context:`${e.severity}, score ${e.score}`,action:`${e.action} Verify with: ${e.verification.command}`})))}});function qu(e){let t=T(e);if(t){if([`site`,`page`,`list`,`sitemap`].includes(t))return t;throw Error(`Mode must be one of: site, page, list, sitemap.`)}}function Ju(e){let t=T(e.crawlUrl),n=T(e.url);if(t&&n&&t!==n)throw Error(`Use either a URL argument or --url, not both.`);return t??n}async function Yu(e){let t=A(e.urls)??[],n=T(e[`urls-file`]);return n&&t.push(...Xu(await pr(n,`utf8`))),[...new Set(t)]}function Xu(e){return e.split(/[\n,]/).map(e=>e.trim()).filter(e=>e&&!e.startsWith(`#`))}function Zu(e,t){let{narrative:n}=e.output,{diagnosis:r}=n;return{...e,...t?{workflow:t}:{},output:{narrative:{site:n.site,generatedAt:n.generatedAt,dataStatus:n.dataStatus,periodDays:n.periodDays,period:n.period,headline:n.headline,caveats:n.caveats,sections:n.sections,priorities:n.priorities,diagnosis:{site:r.site,generatedAt:r.generatedAt,dataStatus:r.dataStatus,summary:r.summary,skippedSections:r.skippedSections,partialReasons:r.partialReasons,priorities:r.priorities}}}}}function Qu(e){return/^[A-Za-z0-9_./:@-]+$/.test(e)?e:`'${e.replaceAll(`'`,`'\\''`)}'`}function $u(e){try{return new URL(e).origin}catch{throw new _(`INVALID_INPUT`,`Pass a valid absolute URL with --url.`)}}function Q(e,t,n){e.some(e=>e.command===t)||e.push({command:t,why:n})}function ed(e){if(!e)return[];let t=e=>{let t=e.window?` in the last ${e.window.days} days`:``,n=`for ${e.joinedPages} of ${e.totalPages} crawled URLs`;return e.status===`skipped`?`${e.label}: not connected.`:e.status===`unavailable`?`${e.label}: unavailable. ${e.warning??`No data was joined.`}`:e.status===`partial`?`${e.label}: partial data ${n}${t}. ${e.warning??``}`.trim():e.status===`none`?`${e.label}: no matching crawled URLs${t}.`:`${e.label}: joined ${n}${t}.`};return[t({label:`Search Console`,status:e.searchConsole.status,joinedPages:Math.max(e.searchConsole.joinedMetricPages,e.searchConsole.joinedQueryPages),totalPages:e.searchConsole.totalPages,window:e.searchConsole.window,warning:e.searchConsole.warning}),t({label:`Google Analytics`,status:e.analytics.status,joinedPages:e.analytics.joinedPages,totalPages:e.analytics.totalPages,window:e.analytics.window,warning:e.analytics.warning})]}function td(e){let t=e.report;return t?{status:e.status,...e.reason?{reason:e.reason}:{},reportId:t.id,generatedAt:t.generatedAt,crawlStatus:t.status,capped:t.summary.pageLimitReached,maxPages:t.config.maxPages,searchDataJoined:(t.dataSources?.searchConsole.joinedMetricPages??0)>0||(t.dataSources?.searchConsole.joinedQueryPages??0)>0,dataSources:t.dataSources,summary:t.summary,topFixes:bt(t,{limit:5}),reviewObservations:gn(t,{limit:5})}:{status:e.status,...e.reason?{reason:e.reason}:{}}}function nd(e){return{ruleId:e.ruleId,title:e.title,category:e.category,severity:e.severity,recommendation:e.recommendation,count:e.count,sampleUrls:e.sampleUrls.slice(0,3),score:e.score,scoreFactors:e.scoreFactors,whyThisRanks:e.whyThisRanks,detailsCommand:`seo explain --rule ${e.ruleId}`}}function rd(e){return`topFixes`in e?{...e,topFixes:e.topFixes.slice(0,3).map(nd),reviewObservations:e.reviewObservations.slice(0,3).map(nd)}:e}function id(e,t={}){if(!(`topFixes`in e)){process.stdout.write(`\nTechnical crawl evidence: ${e.reason??e.status}\n`);return}let n=e.status===`reused`?`saved`:`new`;if(process.stdout.write(`\nTechnical crawl evidence (${n})\n`),e.reason&&process.stdout.write(`${e.reason}\n`),e.capped&&process.stdout.write(`Coverage is capped at ${e.maxPages} pages. Run \`seo crawl\` for a broader investigation.\n`),!t.providerFree)for(let t of ed(e.dataSources))process.stdout.write(`${t}\n`);if(!e.topFixes.length){process.stdout.write(`No prioritised technical fixes were found.
259
+ `)}const Gu={type:`boolean`,default:!1,description:`Print machine-readable JSON.`},Ku=x({meta:{name:`telemetry`,description:`Check or change anonymous usage telemetry`},subCommands:{status:x({meta:{name:`status`,description:`Show whether anonymous usage telemetry is enabled`},args:{json:Gu},run:async({args:e})=>Wu(Sn(),M(e))}),enable:x({meta:{name:`enable`,description:`Enable anonymous usage telemetry in local settings`},args:{json:Gu},run:async({args:e})=>{Wu(Ot(!0),M(e))}}),disable:x({meta:{name:`disable`,description:`Disable anonymous usage telemetry in local settings`},args:{json:Gu},run:async({args:e})=>{Wu(Ot(!1),M(e))}})}}),qu=x({meta:{name:`crawl-queue`,description:`Run a crawl and return a ranked implementation queue`},args:{crawlUrl:{type:`positional`,required:!1,description:`Start URL to crawl.`},url:{type:`string`,description:`Start URL to crawl. Optional when --project has a saved crawl URL.`},site:{type:`string`,description:`GSC property URL for joining page metrics.`},client:{type:`string`,description:`Legacy alias for --project.`},project:{type:`string`,description:`Saved project id or name.`},"google-analytics-property":{type:`string`,description:`Google Analytics property ID for landing-page sessions. Defaults from --project when saved.`},mode:{type:`string`,description:`Crawl mode: site, page, list, or sitemap. Defaults to site.`},urls:{type:`string`,description:`Comma-separated URLs for list mode.`},"urls-file":{type:`string`,description:`File with one URL per line for list mode.`},"max-pages":{type:`string`,description:`Maximum pages to crawl. Defaults to 500.`},"max-depth":{type:`string`,description:`Maximum click depth. Defaults to 16.`},concurrency:{type:`string`,description:`Parallel page fetches. Defaults to 8.`},"fetch-interval-cap":{type:`string`,description:`Maximum page fetches per interval per host.`},"fetch-interval-ms":{type:`string`,description:`Fetch rate interval in milliseconds.`},refresh:{type:`boolean`,default:!1,description:`Bypass local HTTP cache and fetch fresh pages.`},include:{type:`string`,description:`Comma-separated URL patterns to include.`},exclude:{type:`string`,description:`Comma-separated URL patterns to exclude.`},limit:{type:`string`,description:`Maximum queue items to print. Defaults to 25.`},"no-sitemap":{type:`boolean`,default:!1,description:`Do not seed URLs from sitemap.xml.`},"no-robots":{type:`boolean`,default:!1,description:`Do not skip URLs disallowed by robots.txt.`},"no-external":{type:`boolean`,default:!1,description:`Do not check external links.`},js:{type:`boolean`,default:!1,description:`Force JavaScript rendering when Playwright is installed.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=M(e),n=E(e),r=await Xu(e),i=Yu(e),a=T(e.site)||n||!i&&!r.length?await V({client:n,site:T(e.site),options:{json:t}}):void 0,o=i??a?.client?.startUrl??(a?.site?ka(a.site):void 0)??r[0];if(!o)throw Error(`No crawl URL selected. Pass a URL, --url, or use --project with a saved crawl URL.`);let s=await it({url:o,projectId:a?.client?.id,site:a?.site,mode:Ju(e.mode)??(r.length?`list`:void 0),urls:r,maxPages:O(e[`max-pages`]),maxDepth:O(e[`max-depth`]),concurrency:O(e.concurrency),refresh:D(e.refresh),fetchRate:li({...e,"fetch-concurrency":e.concurrency}),include:A(e.include),exclude:A(e.exclude),googleAnalyticsPropertyId:T(e[`google-analytics-property`])??a?.client?.analytics.google?.propertyId,useSitemap:!oi(e,`sitemap`),respectRobots:!oi(e,`robots`),checkExternal:!oi(e,`external`),js:!!D(e.js),limit:O(e.limit)});if(t){L(s);return}Rc(s),z([`Rank`,`Score`,`Rule`,`Severity`,`Affected`,`Target`,`Action`],s.output.queue.map((e,t)=>[t+1,e.score,e.ruleId,e.severity,e.affectedUrls,e.target,e.action])),J(`Implementation queue details`,s.output.queue.map(e=>({label:`${e.ruleId} (${e.affectedUrls} URLs)`,context:`${e.severity}, score ${e.score}`,action:`${e.action} Verify with: ${e.verification.command}`})))}});function Ju(e){let t=T(e);if(t){if([`site`,`page`,`list`,`sitemap`].includes(t))return t;throw Error(`Mode must be one of: site, page, list, sitemap.`)}}function Yu(e){let t=T(e.crawlUrl),n=T(e.url);if(t&&n&&t!==n)throw Error(`Use either a URL argument or --url, not both.`);return t??n}async function Xu(e){let t=A(e.urls)??[],n=T(e[`urls-file`]);return n&&t.push(...Zu(await pr(n,`utf8`))),[...new Set(t)]}function Zu(e){return e.split(/[\n,]/).map(e=>e.trim()).filter(e=>e&&!e.startsWith(`#`))}function Qu(e,t){let{narrative:n}=e.output,{diagnosis:r}=n;return{...e,...t?{workflow:t}:{},output:{narrative:{site:n.site,generatedAt:n.generatedAt,dataStatus:n.dataStatus,periodDays:n.periodDays,period:n.period,headline:n.headline,caveats:n.caveats,sections:n.sections,priorities:n.priorities,diagnosis:{site:r.site,generatedAt:r.generatedAt,dataStatus:r.dataStatus,summary:r.summary,skippedSections:r.skippedSections,partialReasons:r.partialReasons,priorities:r.priorities}}}}}function $u(e){return/^[A-Za-z0-9_./:@-]+$/.test(e)?e:`'${e.replaceAll(`'`,`'\\''`)}'`}function ed(e){try{return new URL(e).origin}catch{throw new _(`INVALID_INPUT`,`Pass a valid absolute URL with --url.`)}}function Q(e,t,n){e.some(e=>e.command===t)||e.push({command:t,why:n})}function td(e){if(!e)return[];let t=e=>{let t=e.window?` in the last ${e.window.days} days`:``,n=`for ${e.joinedPages} of ${e.totalPages} crawled URLs`;return e.status===`skipped`?`${e.label}: not connected.`:e.status===`unavailable`?`${e.label}: unavailable. ${e.warning??`No data was joined.`}`:e.status===`partial`?`${e.label}: partial data ${n}${t}. ${e.warning??``}`.trim():e.status===`none`?`${e.label}: no matching crawled URLs${t}.`:`${e.label}: joined ${n}${t}.`};return[t({label:`Search Console`,status:e.searchConsole.status,joinedPages:Math.max(e.searchConsole.joinedMetricPages,e.searchConsole.joinedQueryPages),totalPages:e.searchConsole.totalPages,window:e.searchConsole.window,warning:e.searchConsole.warning}),t({label:`Google Analytics`,status:e.analytics.status,joinedPages:e.analytics.joinedPages,totalPages:e.analytics.totalPages,window:e.analytics.window,warning:e.analytics.warning})]}function nd(e){let t=e.report;return t?{status:e.status,...e.reason?{reason:e.reason}:{},reportId:t.id,generatedAt:t.generatedAt,crawlStatus:t.status,capped:t.summary.pageLimitReached,maxPages:t.config.maxPages,searchDataJoined:(t.dataSources?.searchConsole.joinedMetricPages??0)>0||(t.dataSources?.searchConsole.joinedQueryPages??0)>0,dataSources:t.dataSources,summary:t.summary,topFixes:bt(t,{limit:5}),reviewObservations:gn(t,{limit:5})}:{status:e.status,...e.reason?{reason:e.reason}:{}}}function rd(e){return{ruleId:e.ruleId,title:e.title,category:e.category,severity:e.severity,recommendation:e.recommendation,count:e.count,sampleUrls:e.sampleUrls.slice(0,3),score:e.score,scoreFactors:e.scoreFactors,whyThisRanks:e.whyThisRanks,detailsCommand:`seo explain --rule ${e.ruleId}`}}function id(e){return`topFixes`in e?{...e,topFixes:e.topFixes.slice(0,3).map(rd),reviewObservations:e.reviewObservations.slice(0,3).map(rd)}:e}function ad(e,t={}){if(!(`topFixes`in e)){process.stdout.write(`\nTechnical crawl evidence: ${e.reason??e.status}\n`);return}let n=e.status===`reused`?`saved`:`new`;if(process.stdout.write(`\nTechnical crawl evidence (${n})\n`),e.reason&&process.stdout.write(`${e.reason}\n`),e.capped&&process.stdout.write(`Coverage is capped at ${e.maxPages} pages. Run \`seo crawl\` for a broader investigation.\n`),!t.providerFree)for(let t of td(e.dataSources))process.stdout.write(`${t}\n`);if(!e.topFixes.length){process.stdout.write(`No prioritised technical fixes were found.
260
260
  `),e.reviewObservations.length&&process.stdout.write(`${e.reviewObservations.length} review observation${e.reviewObservations.length===1?``:`s`} need confirmation before they become implementation work.\n`);return}process.stdout.write(t.providerFree?`
261
261
  Prioritised technical fixes
262
262
  `:e.searchDataJoined?`
263
263
  Technical fixes with search value
264
264
  `:`
265
265
  Technical fixes (no Search Console data joined)
266
- `),e.searchDataJoined?z([`Score`,`Rule`,`Severity`,`Search value`,`Verify`],e.topFixes.map(e=>[e.score,e.ruleId,e.severity,`${e.scoreFactors.clicks} clicks / ${e.scoreFactors.sessions} sessions / ${e.scoreFactors.conversions} conv.`,G(e.howToVerify,72)])):z([`#`,`Rule`,`Severity`,`Affected URLs`,`Verify`],e.topFixes.map((e,t)=>[t+1,e.ruleId,e.severity,e.count,G(e.howToVerify,72)])),e.reviewObservations.length&&process.stdout.write(`\n${e.reviewObservations.length} review observation${e.reviewObservations.length===1?``:`s`} were kept out of this action queue.\n`)}function ad(e){process.stdout.write(`
266
+ `),e.searchDataJoined?z([`Score`,`Rule`,`Severity`,`Search value`,`Verify`],e.topFixes.map(e=>[e.score,e.ruleId,e.severity,`${e.scoreFactors.clicks} clicks / ${e.scoreFactors.sessions} sessions / ${e.scoreFactors.conversions} conv.`,G(e.howToVerify,72)])):z([`#`,`Rule`,`Severity`,`Affected URLs`,`Verify`],e.topFixes.map((e,t)=>[t+1,e.ruleId,e.severity,e.count,G(e.howToVerify,72)])),e.reviewObservations.length&&process.stdout.write(`\n${e.reviewObservations.length} review observation${e.reviewObservations.length===1?``:`s`} were kept out of this action queue.\n`)}function od(e){process.stdout.write(`
267
267
  Provider-backed sections skipped
268
- `);let t=`dataSources`in e?ed(e.dataSources):[],n=t.length?t:[`Search Console: not connected.`,`Google Analytics: not connected.`];for(let e of n)process.stdout.write(`${e}\n`);process.stdout.write("Run `seo start` when you want traffic, query, ranking, and analytics evidence added to the report.\n")}function od(e){let t=[],n=e.technicalCrawl;return n&&(`topFixes`in n?t.push({title:`Technical crawl evidence`,summary:`${n.summary.crawledUrls} pages were included in the ${n.status===`reused`?`saved`:`new`} crawl evidence.`,items:[...n.topFixes.map(e=>`${e.ruleId}: ${e.title}. ${e.count} affected URLs. ${e.recommendation}`),...ed(n.dataSources)]}):t.push({title:`Technical crawl evidence`,summary:n.reason??`Technical evidence was ${n.status}.`,items:[]})),e.followups?.length&&t.push({title:`Recommended next commands`,items:e.followups.map(e=>`${e.command}: ${e.why}`)}),t}function sd(e){if(!(`topFixes`in e))return`Technical crawl evidence was ${e.status}. Search Console and Google Analytics sections were skipped because they are not connected.`;let t=e.status===`reused`?`Loaded`:`Completed`,n=e.summary.crawledUrls;return`${t} a technical crawl of ${n} ${n===1?`page`:`pages`}. Search Console and Google Analytics sections were skipped because they are not connected.`}function cd(e){if(!(`topFixes`in e))return{tool:`seo_crawl`,status:`skipped`,summary:e.reason??`Technical crawl evidence was ${e.status}.`};let t=e.summary.crawledUrls;return{tool:`seo_crawl`,status:`completed`,summary:`${e.status===`reused`?`Loaded`:`Completed`} technical evidence for ${t} crawled ${t===1?`page`:`pages`}.`}}function ld(e,t={}){if(t.searchDataAvailable===!1){let e=[];return t.crawlStartUrl&&t.technicalBaselineStatus!==`created`&&t.technicalBaselineStatus!==`refreshed`&&t.technicalBaselineStatus!==`reused`?(Q(e,`seo crawl --url ${Qu(t.crawlStartUrl)} --health`,`Run the fast sitemap response and access gate before fetching page bodies.`),Q(e,`seo crawl --url ${Qu(t.crawlStartUrl)} --save`,`Create the technical crawl that this report needs before opening a page-level follow-up.`)):t.crawlStartUrl&&Q(e,`seo audit-page --url ${Qu(t.crawlStartUrl)}`,`Inspect one important page in detail after the site-level crawl.`),Q(e,`seo start`,`Connect Search Console when you want traffic, query, and ranking evidence alongside the crawl.`),e}let n=t.projectId?`--project ${Qu(t.projectId)}`:`--site ${Qu(e.site)}`,r=e.output.narrative.diagnosis,i=r.decay.rangeDays,a=[],o=new Set((r.skippedSections??[]).map(e=>e.section));Q(a,`seo refresh-priorities ${n} --days ${i} --verify-content --limit 25`,`Turn the report into a ranked action queue with content checks.`),r.decay.summary.eligibleRows>0&&Q(a,`seo decaying ${n} --days ${i} --limit 25`,`Inspect query/page declines observed in both retained-row windows.`),r.cannibalization.items.length>0&&Q(a,`seo cannibal ${n} --days ${i} --limit 25`,`Find queries split across competing URLs.`),r.quickWins.summary.eligibleRows>0&&Q(a,`seo quick-wins ${n} --days ${i} --verify-content --verify-limit 5`,`Review average-position rows below their heuristic CTR target with optional page evidence.`),r.strikingDistance.summary.eligibleRows>0&&Q(a,`seo second-page ${n} --days ${i} --verify-content --verify-limit 5`,`Work pages sitting just outside page-one rankings.`),(o.has(`traffic anomaly`)||o.has(`update correlation`))&&(Q(a,`seo quick-wins ${n} --days ${i} --min-impressions 10 --verify-content --verify-limit 5`,`Sparse GSC data: lower thresholds and look for early content wins.`),Q(a,`seo second-page ${n} --days ${i} --min-impressions 10 --verify-content --verify-limit 5`,`Sparse GSC data: inspect early second-page opportunities.`));let s=r.segments.page.items.find(e=>e.key.startsWith(`http`));return s&&Q(a,`seo audit-page --url ${Qu(s.key)}`,`Audit the biggest moved URL for title, metadata, links, and schema.`),t.crawlStartUrl&&t.technicalBaselineStatus!==`created`&&t.technicalBaselineStatus!==`refreshed`&&t.technicalBaselineStatus!==`reused`&&(Q(a,`seo crawl --url ${Qu(t.crawlStartUrl)} --health`,`Run the fast sitemap response and access gate before fetching page bodies.`),Q(a,`seo crawl --url ${Qu(t.crawlStartUrl)} ${n} --save`,`Create the first technical crawler baseline with plain-English fixes and JSON-ready issue data.`)),Q(a,`seo technical-watch ${n} --limit 50`,`Save a crawl/index baseline so future reports can flag technical drift.`),a.slice(0,6)}function ud(e){e.length&&(process.stdout.write(`
268
+ `);let t=`dataSources`in e?td(e.dataSources):[],n=t.length?t:[`Search Console: not connected.`,`Google Analytics: not connected.`];for(let e of n)process.stdout.write(`${e}\n`);process.stdout.write("Run `seo start` when you want traffic, query, ranking, and analytics evidence added to the report.\n")}function sd(e){let t=[],n=e.technicalCrawl;return n&&(`topFixes`in n?t.push({title:`Technical crawl evidence`,summary:`${n.summary.crawledUrls} pages were included in the ${n.status===`reused`?`saved`:`new`} crawl evidence.`,items:[...n.topFixes.map(e=>`${e.ruleId}: ${e.title}. ${e.count} affected URLs. ${e.recommendation}`),...td(n.dataSources)]}):t.push({title:`Technical crawl evidence`,summary:n.reason??`Technical evidence was ${n.status}.`,items:[]})),e.followups?.length&&t.push({title:`Recommended next commands`,items:e.followups.map(e=>`${e.command}: ${e.why}`)}),t}function cd(e){if(!(`topFixes`in e))return`Technical crawl evidence was ${e.status}. Search Console and Google Analytics sections were skipped because they are not connected.`;let t=e.status===`reused`?`Loaded`:`Completed`,n=e.summary.crawledUrls;return`${t} a technical crawl of ${n} ${n===1?`page`:`pages`}. Search Console and Google Analytics sections were skipped because they are not connected.`}function ld(e){if(!(`topFixes`in e))return{tool:`seo_crawl`,status:`skipped`,summary:e.reason??`Technical crawl evidence was ${e.status}.`};let t=e.summary.crawledUrls;return{tool:`seo_crawl`,status:`completed`,summary:`${e.status===`reused`?`Loaded`:`Completed`} technical evidence for ${t} crawled ${t===1?`page`:`pages`}.`}}function ud(e,t={}){if(t.searchDataAvailable===!1){let e=[];return t.crawlStartUrl&&t.technicalBaselineStatus!==`created`&&t.technicalBaselineStatus!==`refreshed`&&t.technicalBaselineStatus!==`reused`?(Q(e,`seo crawl --url ${$u(t.crawlStartUrl)} --health`,`Run the fast sitemap response and access gate before fetching page bodies.`),Q(e,`seo crawl --url ${$u(t.crawlStartUrl)} --save`,`Create the technical crawl that this report needs before opening a page-level follow-up.`)):t.crawlStartUrl&&Q(e,`seo audit-page --url ${$u(t.crawlStartUrl)}`,`Inspect one important page in detail after the site-level crawl.`),Q(e,`seo start`,`Connect Search Console when you want traffic, query, and ranking evidence alongside the crawl.`),e}let n=t.projectId?`--project ${$u(t.projectId)}`:`--site ${$u(e.site)}`,r=e.output.narrative.diagnosis,i=r.decay.rangeDays,a=[],o=new Set((r.skippedSections??[]).map(e=>e.section));Q(a,`seo refresh-priorities ${n} --days ${i} --verify-content --limit 25`,`Turn the report into a ranked action queue with content checks.`),r.decay.summary.eligibleRows>0&&Q(a,`seo decaying ${n} --days ${i} --limit 25`,`Inspect query/page declines observed in both retained-row windows.`),r.cannibalization.items.length>0&&Q(a,`seo cannibal ${n} --days ${i} --limit 25`,`Find queries split across competing URLs.`),r.quickWins.summary.eligibleRows>0&&Q(a,`seo quick-wins ${n} --days ${i} --verify-content --verify-limit 5`,`Review average-position rows below their heuristic CTR target with optional page evidence.`),r.strikingDistance.summary.eligibleRows>0&&Q(a,`seo second-page ${n} --days ${i} --verify-content --verify-limit 5`,`Work pages sitting just outside page-one rankings.`),(o.has(`traffic anomaly`)||o.has(`update correlation`))&&(Q(a,`seo quick-wins ${n} --days ${i} --min-impressions 10 --verify-content --verify-limit 5`,`Sparse GSC data: lower thresholds and look for early content wins.`),Q(a,`seo second-page ${n} --days ${i} --min-impressions 10 --verify-content --verify-limit 5`,`Sparse GSC data: inspect early second-page opportunities.`));let s=r.segments.page.items.find(e=>e.key.startsWith(`http`));return s&&Q(a,`seo audit-page --url ${$u(s.key)}`,`Audit the biggest moved URL for title, metadata, links, and schema.`),t.crawlStartUrl&&t.technicalBaselineStatus!==`created`&&t.technicalBaselineStatus!==`refreshed`&&t.technicalBaselineStatus!==`reused`&&(Q(a,`seo crawl --url ${$u(t.crawlStartUrl)} --health`,`Run the fast sitemap response and access gate before fetching page bodies.`),Q(a,`seo crawl --url ${$u(t.crawlStartUrl)} ${n} --save`,`Create the first technical crawler baseline with plain-English fixes and JSON-ready issue data.`)),Q(a,`seo technical-watch ${n} --limit 50`,`Save a crawl/index baseline so future reports can flag technical drift.`),a.slice(0,6)}function dd(e){e.length&&(process.stdout.write(`
269
269
  Recommended next commands
270
- `),z([`Command`,`Why`],e.map(e=>[e.command,e.why])))}function dd(e){process.stdout.write(`# Technical SEO report: ${e}\n\n`)}function fd(e){return x({meta:e,args:{site:{type:`string`,description:`GSC property URL, for example sc-domain:example.com.`},...e.printFollowups?{url:{type:`string`,description:`Crawl URL for a technical-only report. Search Console analyses are skipped unless you also pass --site or --project.`}}:{},client:{type:`string`,description:`Legacy alias for --project.`},project:{type:`string`,description:`Saved project id or name.`},...X([`days`,`recentDays`,`limit`,`includeBrand`,`refresh`],{days:{description:`Search performance window in days. Defaults to 90.`},limit:{description:`Maximum rows per section. Defaults to 10.`}}),...e.printFollowups?{crawl:w(`Create or reuse a bounded technical crawl before the report.`,`Skip technical crawl evidence for this report.`),"crawl-max-pages":{type:`string`,description:`Maximum pages for the report crawl. Defaults to 100.`},"crawl-max-depth":{type:`string`,description:`Maximum link depth for the report crawl. Defaults to 4.`},full:{type:`boolean`,default:!1,description:`Include the full report in JSON output. Default JSON is a compact summary for agents.`},...ql}:{},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:t})=>{let n=M(t),r=e.printFollowups?Jl(t):void 0,i=e.printFollowups&&D(t.full),a=E(t),o=T(t.site),s=e.printFollowups?T(t.url):void 0,c=!!(a||o||!s),l=c?await V({client:a,site:o,options:{json:n,refresh:D(t.refresh)}}):void 0,u=l?.site??$u(s??``),d=await pe({site:u,days:O(t.days),recentDays:O(t.recent),limit:O(t.limit),brandTerms:l?.client?.brandTerms,includeBrand:D(t[`include-brand`]),refresh:D(t.refresh),skipSearchData:!c}),f=e.workflowName?{...d,workflow:e.workflowName}:d,p=e.printFollowups?await Xt({site:u,searchSite:l?.site,url:s??l?.client?.startUrl??ka(u),projectId:l?.client?.id,googleAnalyticsPropertyId:l?.client?.analytics.google?.propertyId,crawl:!oi(t,`crawl`),refresh:D(t.refresh),maxPages:O(t[`crawl-max-pages`]),maxDepth:O(t[`crawl-max-depth`])}):void 0,m=p?td(p):void 0,h=e.printFollowups?ld(f,{crawlStartUrl:s??l?.client?.startUrl??ka(u),projectId:l?.client?.id,technicalBaselineStatus:p?.status,searchDataAvailable:c}):void 0;if(n){let t=e.printFollowups&&!i?Zu(d,e.workflowName):f,n=!c&&m?{...t,summary:sd(m),steps:[cd(m),...t.steps]}:t;L(m||h?{...n,...e.printFollowups?{detail:i?`full`:`summary`}:{},...m?{technicalCrawl:e.printFollowups&&!i?rd(m):m}:{},...h?{nextCommands:h}:{}}:t);return}if(r){Ql(await Zl({report:f.output.narrative,reportName:`report`,title:c?`SEO report`:`Technical SEO report`,options:r,projectId:l?.client?.id,additionalSections:od({technicalCrawl:m,followups:h})}));return}c?(process.stdout.write(`${f.output.narrative.markdown}\n\n`),Rc(f)):dd(u),m&&(id(m,{providerFree:!c}),c||ad(m)),h&&ud(h)}})}const pd=fd({name:`diagnose-property`,description:`Find what changed in Google Search and what to inspect next`}),md=fd({name:`report`,workflowName:`report`,printFollowups:!0,description:`Run the main SEO report and recommend the next best follow-up commands`}),hd=x({meta:{name:`refresh-priorities`,description:`Agent workflow for a ranked SEO action queue`},args:{site:{type:`string`,description:`GSC property URL, for example sc-domain:example.com.`},client:{type:`string`,description:`Legacy alias for --project.`},project:{type:`string`,description:`Saved project id or name.`},...X([`days`,`recentDays`,`limit`,`includeBrand`,`refresh`],{days:{description:`Diagnosis window length in days. Defaults to 90.`},limit:{description:`Maximum queue items to print. Defaults to 25.`}}),"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.`},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,refresh:D(e.refresh)}}),r=await ln({site:n.site,days:O(e.days),recentDays:O(e.recent),limit:O(e.limit),brandTerms:n.client?.brandTerms,includeBrand:D(e[`include-brand`]),googleAnalyticsPropertyId:T(e[`google-analytics-property`])??n.client?.analytics.google?.propertyId,verifyContent:D(e[`verify-content`]),verifyLimit:O(e[`verify-limit`]),refresh:D(e.refresh)});if(t){L(r);return}Rc(r),z([`Source`,`Category`,`Score`,`Findings`,`Template`,`Analytics`,`Target`,`Action`],r.output.queue.map(e=>[e.source,e.category,e.score,e.grouped?.count??1,e.template?.label??`-`,e.analytics?.sessions??`-`,e.target,e.action])),J(`Queue action details`,r.output.queue.map(e=>({label:e.target,context:`${e.category}, score ${e.score}`,action:e.action})))}}),gd=x({meta:{name:`technical-watch`,description:`Agent workflow for crawl-diff and index-watch monitoring`},args:{site:{type:`string`,description:`GSC property URL, for example sc-domain:example.com.`},client:{type:`string`,description:`Legacy alias for --project.`},project:{type:`string`,description:`Saved project id or name.`},url:{type:`string`,description:`Start URL to crawl. Defaults from the GSC property when possible.`},urls:{type:`string`,description:`Comma-separated URLs to inspect with URL Inspection.`},sitemaps:{type:`string`,description:`Comma-separated XML sitemap URLs for quota-aware index monitoring.`},properties:{type:`string`,description:`Comma-separated GSC properties for sitemap index monitoring.`},limit:{type:`string`,description:`Maximum pages to crawl. Defaults to 50.`},"daily-limit":{type:`string`,description:`URL Inspection daily limit per property. Defaults to 2000.`},"inspect-limit":{type:`string`,description:`Maximum sitemap URLs to inspect in this run. Defaults to 100.`},"max-urls":{type:`string`,description:`Maximum sitemap URLs to load for monitoring. Defaults to 50000.`},language:{type:`string`,description:`Optional URL Inspection language code.`},"recover-links":w(`Check search-value GSC pages for broken, blocked, or poorly redirected URLs. Defaults to true.`,`Skip link recovery checks.`),"recover-days":{type:`string`,description:`GSC lookback window for link recovery. Defaults to 90.`},"recover-limit":{type:`string`,description:`Maximum search-value pages to check for link recovery. Defaults to 10.`},"recover-min-clicks":{type:`string`,description:`Minimum clicks for link recovery candidates. Defaults to 1.`},"recover-min-impressions":{type:`string`,description:`Minimum impressions for link recovery candidates. Defaults to 100.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`},refresh:{type:`boolean`,default:!1,description:`Bypass local HTTP/GSC cache where supported.`},js:{type:`boolean`,default:!1,description:`Force JavaScript rendering when Playwright is installed.`}},run:async({args:e})=>{let t=M(e),n=await V({client:E(e),site:T(e.site),options:{json:t,refresh:D(e.refresh)}}),r=j(e.urls),i=j(e.sitemaps),a=T(e.url)??n.client?.startUrl??ka(n.site),o=await ge({site:n.site,startUrl:a,urls:r.length?r:n.client?.watchUrls,sitemaps:i.length?i:void 0,properties:j(e.properties).length?j(e.properties):void 0,limit:O(e.limit),languageCode:T(e.language),dailyLimit:O(e[`daily-limit`]),inspectLimit:O(e[`inspect-limit`]),maxUrls:O(e[`max-urls`]),refresh:D(e.refresh),js:D(e.js)?!0:`auto`,recoverLinks:D(e[`recover-links`]),recoverDays:O(e[`recover-days`]),recoverLimit:O(e[`recover-limit`]),recoverMinClicks:O(e[`recover-min-clicks`]),recoverMinImpressions:O(e[`recover-min-impressions`])});if(t){L(o);return}zc(o)}});function _d(e){return e===null?`-`:e.toLocaleString(`en-GB`,{maximumFractionDigits:e>=100?0:1})}function vd(e){return[...e.winners.map(e=>({direction:`winner`,item:e})),...e.losers.map(e=>({direction:`loser`,item:e}))]}function yd(e,t){let n=vd(t);n.length&&(process.stdout.write(`\n${e}\n`),z([`Direction`,`Key`,`Clicks`,`Impr.`,`Position`],n.map(({direction:e,item:t})=>[e,t.key,_d(t.clickDelta),_d(t.impressionDelta),_d(t.positionDelta)])))}function bd(e){process.stdout.write(`
270
+ `),z([`Command`,`Why`],e.map(e=>[e.command,e.why])))}function fd(e){process.stdout.write(`# Technical SEO report: ${e}\n\n`)}function pd(e){return x({meta:e,args:{site:{type:`string`,description:`GSC property URL, for example sc-domain:example.com.`},...e.printFollowups?{url:{type:`string`,description:`Crawl URL for a technical-only report. Search Console analyses are skipped unless you also pass --site or --project.`}}:{},client:{type:`string`,description:`Legacy alias for --project.`},project:{type:`string`,description:`Saved project id or name.`},...X([`days`,`recentDays`,`limit`,`includeBrand`,`refresh`],{days:{description:`Search performance window in days. Defaults to 90.`},limit:{description:`Maximum rows per section. Defaults to 10.`}}),...e.printFollowups?{crawl:w(`Create or reuse a bounded technical crawl before the report.`,`Skip technical crawl evidence for this report.`),"crawl-max-pages":{type:`string`,description:`Maximum pages for the report crawl. Defaults to 100.`},"crawl-max-depth":{type:`string`,description:`Maximum link depth for the report crawl. Defaults to 4.`},full:{type:`boolean`,default:!1,description:`Include the full report in JSON output. Default JSON is a compact summary for agents.`},...ql}:{},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:t})=>{let n=M(t),r=e.printFollowups?Jl(t):void 0,i=e.printFollowups&&D(t.full),a=E(t),o=T(t.site),s=e.printFollowups?T(t.url):void 0,c=!!(a||o||!s),l=c?await V({client:a,site:o,options:{json:n,refresh:D(t.refresh)}}):void 0,u=l?.site??ed(s??``),d=await pe({site:u,days:O(t.days),recentDays:O(t.recent),limit:O(t.limit),brandTerms:l?.client?.brandTerms,includeBrand:D(t[`include-brand`]),refresh:D(t.refresh),skipSearchData:!c}),f=e.workflowName?{...d,workflow:e.workflowName}:d,p=e.printFollowups?await Xt({site:u,searchSite:l?.site,url:s??l?.client?.startUrl??ka(u),projectId:l?.client?.id,googleAnalyticsPropertyId:l?.client?.analytics.google?.propertyId,crawl:!oi(t,`crawl`),refresh:D(t.refresh),maxPages:O(t[`crawl-max-pages`]),maxDepth:O(t[`crawl-max-depth`])}):void 0,m=p?nd(p):void 0,h=e.printFollowups?ud(f,{crawlStartUrl:s??l?.client?.startUrl??ka(u),projectId:l?.client?.id,technicalBaselineStatus:p?.status,searchDataAvailable:c}):void 0;if(n){let t=e.printFollowups&&!i?Qu(d,e.workflowName):f,n=!c&&m?{...t,summary:cd(m),steps:[ld(m),...t.steps]}:t;L(m||h?{...n,...e.printFollowups?{detail:i?`full`:`summary`}:{},...m?{technicalCrawl:e.printFollowups&&!i?id(m):m}:{},...h?{nextCommands:h}:{}}:t);return}if(r){Ql(await Zl({report:f.output.narrative,reportName:`report`,title:c?`SEO report`:`Technical SEO report`,options:r,projectId:l?.client?.id,additionalSections:sd({technicalCrawl:m,followups:h})}));return}c?(process.stdout.write(`${f.output.narrative.markdown}\n\n`),Rc(f)):fd(u),m&&(ad(m,{providerFree:!c}),c||od(m)),h&&dd(h)}})}const md=pd({name:`diagnose-property`,description:`Find what changed in Google Search and what to inspect next`}),hd=pd({name:`report`,workflowName:`report`,printFollowups:!0,description:`Run the main SEO report and recommend the next best follow-up commands`}),gd=x({meta:{name:`refresh-priorities`,description:`Agent workflow for a ranked SEO action queue`},args:{site:{type:`string`,description:`GSC property URL, for example sc-domain:example.com.`},client:{type:`string`,description:`Legacy alias for --project.`},project:{type:`string`,description:`Saved project id or name.`},...X([`days`,`recentDays`,`limit`,`includeBrand`,`refresh`],{days:{description:`Diagnosis window length in days. Defaults to 90.`},limit:{description:`Maximum queue items to print. Defaults to 25.`}}),"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.`},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,refresh:D(e.refresh)}}),r=await ln({site:n.site,days:O(e.days),recentDays:O(e.recent),limit:O(e.limit),brandTerms:n.client?.brandTerms,includeBrand:D(e[`include-brand`]),googleAnalyticsPropertyId:T(e[`google-analytics-property`])??n.client?.analytics.google?.propertyId,verifyContent:D(e[`verify-content`]),verifyLimit:O(e[`verify-limit`]),refresh:D(e.refresh)});if(t){L(r);return}Rc(r),z([`Source`,`Category`,`Score`,`Findings`,`Template`,`Analytics`,`Target`,`Action`],r.output.queue.map(e=>[e.source,e.category,e.score,e.grouped?.count??1,e.template?.label??`-`,e.analytics?.sessions??`-`,e.target,e.action])),J(`Queue action details`,r.output.queue.map(e=>({label:e.target,context:`${e.category}, score ${e.score}`,action:e.action})))}}),_d=x({meta:{name:`technical-watch`,description:`Agent workflow for crawl-diff and index-watch monitoring`},args:{site:{type:`string`,description:`GSC property URL, for example sc-domain:example.com.`},client:{type:`string`,description:`Legacy alias for --project.`},project:{type:`string`,description:`Saved project id or name.`},url:{type:`string`,description:`Start URL to crawl. Defaults from the GSC property when possible.`},urls:{type:`string`,description:`Comma-separated URLs to inspect with URL Inspection.`},sitemaps:{type:`string`,description:`Comma-separated XML sitemap URLs for quota-aware index monitoring.`},properties:{type:`string`,description:`Comma-separated GSC properties for sitemap index monitoring.`},limit:{type:`string`,description:`Maximum pages to crawl. Defaults to 50.`},"daily-limit":{type:`string`,description:`URL Inspection daily limit per property. Defaults to 2000.`},"inspect-limit":{type:`string`,description:`Maximum sitemap URLs to inspect in this run. Defaults to 100.`},"max-urls":{type:`string`,description:`Maximum sitemap URLs to load for monitoring. Defaults to 50000.`},language:{type:`string`,description:`Optional URL Inspection language code.`},"recover-links":w(`Check search-value GSC pages for broken, blocked, or poorly redirected URLs. Defaults to true.`,`Skip link recovery checks.`),"recover-days":{type:`string`,description:`GSC lookback window for link recovery. Defaults to 90.`},"recover-limit":{type:`string`,description:`Maximum search-value pages to check for link recovery. Defaults to 10.`},"recover-min-clicks":{type:`string`,description:`Minimum clicks for link recovery candidates. Defaults to 1.`},"recover-min-impressions":{type:`string`,description:`Minimum impressions for link recovery candidates. Defaults to 100.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`},refresh:{type:`boolean`,default:!1,description:`Bypass local HTTP/GSC cache where supported.`},js:{type:`boolean`,default:!1,description:`Force JavaScript rendering when Playwright is installed.`}},run:async({args:e})=>{let t=M(e),n=await V({client:E(e),site:T(e.site),options:{json:t,refresh:D(e.refresh)}}),r=j(e.urls),i=j(e.sitemaps),a=T(e.url)??n.client?.startUrl??ka(n.site),o=await ge({site:n.site,startUrl:a,urls:r.length?r:n.client?.watchUrls,sitemaps:i.length?i:void 0,properties:j(e.properties).length?j(e.properties):void 0,limit:O(e.limit),languageCode:T(e.language),dailyLimit:O(e[`daily-limit`]),inspectLimit:O(e[`inspect-limit`]),maxUrls:O(e[`max-urls`]),refresh:D(e.refresh),js:D(e.js)?!0:`auto`,recoverLinks:D(e[`recover-links`]),recoverDays:O(e[`recover-days`]),recoverLimit:O(e[`recover-limit`]),recoverMinClicks:O(e[`recover-min-clicks`]),recoverMinImpressions:O(e[`recover-min-impressions`])});if(t){L(o);return}zc(o)}});function vd(e){return e===null?`-`:e.toLocaleString(`en-GB`,{maximumFractionDigits:e>=100?0:1})}function yd(e){return[...e.winners.map(e=>({direction:`winner`,item:e})),...e.losers.map(e=>({direction:`loser`,item:e}))]}function bd(e,t){let n=yd(t);n.length&&(process.stdout.write(`\n${e}\n`),z([`Direction`,`Key`,`Clicks`,`Impr.`,`Position`],n.map(({direction:e,item:t})=>[e,t.key,vd(t.clickDelta),vd(t.impressionDelta),vd(t.positionDelta)])))}function xd(e){process.stdout.write(`
271
271
  Segment coverage
272
- `),z([`Dimension`,`Eligible`,`Returned`,`Limited`,`Result cap`,`Source cap`,`Source capped`],[`page`,`query`,`device`,`country`].map(t=>{let n=e[t].coverage;return[t,n.eligibleRows,n.returnedRows,n.limitedRows,n.resultLimit,n.sourceRowLimit,n.sourcePossiblyTruncated?`yes`:`no`]}))}function xd(e){e.length&&(process.stdout.write(`
272
+ `),z([`Dimension`,`Eligible`,`Returned`,`Limited`,`Result cap`,`Source cap`,`Source capped`],[`page`,`query`,`device`,`country`].map(t=>{let n=e[t].coverage;return[t,n.eligibleRows,n.returnedRows,n.limitedRows,n.resultLimit,n.sourceRowLimit,n.sourcePossiblyTruncated?`yes`:`no`]}))}function Sd(e){e.length&&(process.stdout.write(`
273
273
  Template movement
274
- `),z([`Direction`,`Template`,`Pattern confidence`,`URLs`,`Clicks`,`Returned share`,`Common terms`],e.map(e=>[e.direction,e.signature,e.confidence,e.urlCount,_d(e.clickDelta),`${Math.round(e.movementShare*100)}%`,e.commonTerms.join(`, `)||`-`])),Y(`Template actions`,e.map(e=>e.summary)),Y(`Template evidence scope`,[`Shares use returned directional page click movement. Pattern confidence applies only to returned matched-retained page segments.`]))}const Sd=x({meta:{name:`update-postmortem`,description:`Agent workflow for Google update winner/loser analysis`},args:{site:{type:`string`,description:`GSC property URL, for example sc-domain:example.com.`},client:{type:`string`,description:`Legacy alias for --project.`},project:{type:`string`,description:`Saved project id or name.`},...X([`days`,`recentDays`,`limit`,`includeBrand`,`refresh`],{days:{description:`Diagnosis window length in days. Defaults to 90.`},limit:{description:`Maximum winners/losers per segment. Defaults to 20.`}}),"known-change":{type:`string`,description:`Manual site-side change to treat as a confounder, for example pruning pages or blocking traffic.`},"ignore-change-log":{type:`boolean`,default:!1,description:`Do not use saved change-log entries as confounders.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=M(e),n=await V({client:E(e),site:T(e.site),options:{json:t,refresh:D(e.refresh)}}),r=await ie({site:n.site,days:O(e.days),recentDays:O(e.recent),limit:O(e.limit),brandTerms:n.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)});if(t){L(r);return}K({title:`Update postmortem`,target:r.site,status:`unknown`,summary:r.summary,metrics:[{label:`Attribution`,value:r.output.update.attribution.replaceAll(`-`,` `)},{label:`Confidence`,value:r.output.update.confidence},{label:`Known confounders`,value:r.output.update.confounders.length}]}),Y(`Recommended actions`,r.actions.map(e=>`${e.title} (${e.confidence}): ${e.action}`)),Y(`Postmortem findings`,r.output.insights.map(e=>e.summary)),Y(`Segment evidence warnings`,[...r.output.segments.page.warnings,...r.output.segments.query.warnings,...r.output.segments.device.warnings,...r.output.segments.country.warnings].filter((e,t,n)=>n.indexOf(e)===t)),xd(r.output.templateMovement),Y(`Update evidence`,r.output.update.evidence),bd(r.output.segments),yd(`Page winners and losers`,r.output.segments.page),yd(`Query winners and losers`,r.output.segments.query),yd(`Device winners and losers`,r.output.segments.device),yd(`Country winners and losers`,r.output.segments.country),_a(`seo export update-postmortem ${n.client?`--project ${JSON.stringify(n.client.id)}`:`--site ${JSON.stringify(n.site)}`}`)}});function Cd(e){return e.replaceAll(`\\`,`/`).toLowerCase()}function wd(e,t){return t===`win32`?`${e}.cmd`:e}function Td(e,t=process.platform){let n=Cd(e);if(!(n.includes(`/_npx/`)||n.includes(`/.npm/_npx/`)||n.includes(`/.cache/pnpm/dlx/`)||n.includes(`/.volta/`))){if(n.includes(`/.pnpm/`)||n.includes(`/pnpm/global/`))return{command:wd(`pnpm`,t),args:[`add`,`--global`,`seo@latest`]};if(n.includes(`/.bun/install/global/`))return{command:wd(`bun`,t),args:[`add`,`--global`,`seo@latest`]};if(n.includes(`/.config/yarn/global/`))return{command:wd(`yarn`,t),args:[`global`,`add`,`seo@latest`]};if(n.includes(`/node_modules/seo/`))return{command:wd(`npm`,t),args:[`install`,`--global`,`seo@latest`]}}}function Ed(e,t){let n=e=>e.replace(/^v/,``).split(`-`)[0]?.split(`.`).map(e=>Number.parseInt(e,10))??[],r=n(e),i=n(t);for(let e=0;e<3;e+=1){let t=r[e]??0,n=i[e]??0;if(!Number.isFinite(t)||!Number.isFinite(n))return!1;if(n>t)return!0;if(n<t)return!1}return!1}function Dd(e){return!(!e.stdinIsTty||!e.stdoutIsTty||e.env.CI||e.env.NO_UPDATE_NOTIFIER||e.env.NODE_ENV===`test`||e.env.npm_lifecycle_event||e.env.npm_execpath||e.env.TERM===`dumb`||e.argv.includes(`--json`)||e.argv.some(e=>[`--help`,`-h`,`--version`,`-v`].includes(e))||[`help`,`mcp`].includes(e.argv[0]??``))}function Od(e){if(e)try{return Kn(e)}catch{return e}}async function kd(e){let t=await Ar({message:`seo ${e.update.latest} is available. You have ${e.update.current}.`,options:[{value:`update`,label:`Update now`,hint:[e.command.command,...e.command.args].join(` `)},{value:`later`,label:`Later`}],initialValue:`later`});return Tr(t)?`later`:t}function Ad(e){return or(e.command,e.args,{stdio:`inherit`}).status}async function jd(e,t={}){if(!Dd({argv:t.argv??process.argv.slice(2),env:t.env??process.env,stdinIsTty:t.stdinIsTty??!!process.stdin.isTTY,stdoutIsTty:t.stdoutIsTty??!!process.stdout.isTTY}))return;let n=Od(t.cliPath??process.argv[1]),r=n?Td(n,t.platform??process.platform):void 0;if(!r)return;let i=(t.createNotifier??(e=>Br({pkg:e,shouldNotifyInNpmScript:!1})))(e).update;if(!(!i||!Ed(i.current,i.latest))&&await(t.prompt??kd)({command:r,update:i})!==`later`)return(t.run??Ad)(r)===0?(Er.success(`Updated seo to ${i.latest}. Run your command again.`),0):(Er.error(`The update failed. Your current version is unchanged.`),1)}const Md={name:`seo`,version:be},Nd=[{title:`Start here`,commands:[[`seo start`,`Connect Google and save a project profile`],[`seo report`,`Run the main SEO report for the default project`],[`seo report --site sc-domain:example.com`,`Run without a profile`],[`seo report --url https://example.com`,`Start with a local technical report`]]},{title:`Projects`,commands:[[`seo projects list`,`List saved project profiles`],[`seo projects add`,`Create or update a project profile`],[`seo sites`,`List Search Console properties`],[`seo doctor`,`Check local auth and config`]]},{title:`Act on a report`,commands:[[`seo refresh-priorities`,`Rank the next best SEO actions`],[`seo quick-wins`,`Find ranking 4-10 low-CTR wins`],[`seo second-page`,`Investigate URLs averaging positions 10-20`],[`seo technical-watch`,`Crawl and index-monitor a site`]]},{title:`Agent and power tools`,commands:[[`seo report --json`,`Run the main report as structured JSON`],[`seo export diagnose`,`Export report data to CSV`],[`seo mcp install`,`Install SEO tools into MCP clients`],[`seo skill list`,`Show the packaged SEO skill`],[`seo reports list`,`Discover every structured report`]]}],Pd=[...Nd,{title:`Deeper analysis`,commands:[[`seo decaying`,`Find pages and queries losing clicks`],[`seo cannibal`,`Review queries exposed across multiple URLs`],[`seo ctr-underperformers`,`Find weak CTR by ranking position`],[`seo query-cluster`,`Cluster repeated demand themes`],[`seo page-opportunities`,`Analyze one URL for growth ideas`],[`seo content optimize`,`Build a content optimization report`],[`seo perf audit`,`Run a page performance audit`],[`seo internal-links`,`Find internal link opportunities`],[`seo community-intent`,`Find forum, review, and comparison intent`],[`seo ai-referrals`,`Find AI referral traffic in Google Analytics`],[`seo seo-to-ai-query`,`Convert searches into AI-monitor prompts`]]},{title:`Technical and data`,commands:[[`seo audit-page`,`Audit one page`],[`seo crawl --sitemap-url <url> --health`,`Run the fast sitemap gate before a full crawl`],[`seo crawl`,`Run a full crawl for page and content evidence`],[`seo agent-readiness`,`Check content-site readiness for AI agents`],[`seo ai-readiness`,`Review AI-search technical evidence`],[`seo llms audit`,`Inspect optional llms.txt presence`],[`seo entity-readiness`,`Check brand/entity signals from a saved crawl`],[`seo okf export`,`Export a saved crawl as an OKF knowledge bundle`],[`seo crawl-queue`,`Run a crawl and rank the implementation queue`],[`seo crawl-reports`,`List saved crawl reports`],[`seo rules`,`List crawler rule ids`],[`seo explain --rule missing_title`,`Explain a crawler rule`],[`seo crawl-diff`,`Compare crawl changes`],[`seo index-coverage`,`Choose pages for URL Inspection`],[`seo index-watch`,`Check URL Inspection status`],[`seo indexnow submit`,`Notify IndexNow about changed URLs`],[`seo link-recover`,`Find broken search-value URLs`],[`seo links`,`Review bounded referring-link evidence`],[`seo server-logs analyze`,`Review crawler requests in an access log`],[`seo redirect-trace`,`Trace redirects`],[`seo gsc-query`,`Run a raw GSC query`],[`seo url-inspect`,`Run URL Inspection`],[`seo analytics google properties`,`List Google Analytics properties`],[`seo analytics google report`,`Run a Google Analytics report`],[`seo updates`,`List official Google ranking updates`]]},{title:`Reports and ops`,commands:[[`seo monthly-report`,`Generate a monthly narrative`],[`seo report-narrative`,`Generate a client-ready narrative`],[`seo update-postmortem`,`Analyze update winners and losers`],[`seo schedule cron`,`Print cron entries`],[`seo monitoring`,`Run and review monitoring`],[`seo change-log`,`Track SEO changes`],[`seo tests`,`Create and report local SEO tests`],[`seo content-groups`,`Manage reusable page/query groups`],[`seo pseo`,`Audit programmatic SEO templates`],[`seo auth`,`Manage Google auth`],[`seo providers bing`,`Connect and report on Bing Webmaster`],[`seo providers dataforseo`,`Connect optional search data`],[`seo cache`,`Manage local cache`],[`seo privacy`,`Show local storage paths`],[`seo telemetry status`,`Check anonymous usage telemetry`],[`seo reset`,`Delete local SEO data`]]}];function Fd(e){qi(`seo v${Md.version}`,`Run SEO audits, find what needs fixing, and ship the changes with your agent.`),process.stdout.write(`
274
+ `),z([`Direction`,`Template`,`Pattern confidence`,`URLs`,`Clicks`,`Returned share`,`Common terms`],e.map(e=>[e.direction,e.signature,e.confidence,e.urlCount,vd(e.clickDelta),`${Math.round(e.movementShare*100)}%`,e.commonTerms.join(`, `)||`-`])),Y(`Template actions`,e.map(e=>e.summary)),Y(`Template evidence scope`,[`Shares use returned directional page click movement. Pattern confidence applies only to returned matched-retained page segments.`]))}const Cd=x({meta:{name:`update-postmortem`,description:`Agent workflow for Google update winner/loser analysis`},args:{site:{type:`string`,description:`GSC property URL, for example sc-domain:example.com.`},client:{type:`string`,description:`Legacy alias for --project.`},project:{type:`string`,description:`Saved project id or name.`},...X([`days`,`recentDays`,`limit`,`includeBrand`,`refresh`],{days:{description:`Diagnosis window length in days. Defaults to 90.`},limit:{description:`Maximum winners/losers per segment. Defaults to 20.`}}),"known-change":{type:`string`,description:`Manual site-side change to treat as a confounder, for example pruning pages or blocking traffic.`},"ignore-change-log":{type:`boolean`,default:!1,description:`Do not use saved change-log entries as confounders.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=M(e),n=await V({client:E(e),site:T(e.site),options:{json:t,refresh:D(e.refresh)}}),r=await ie({site:n.site,days:O(e.days),recentDays:O(e.recent),limit:O(e.limit),brandTerms:n.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)});if(t){L(r);return}K({title:`Update postmortem`,target:r.site,status:`unknown`,summary:r.summary,metrics:[{label:`Attribution`,value:r.output.update.attribution.replaceAll(`-`,` `)},{label:`Confidence`,value:r.output.update.confidence},{label:`Known confounders`,value:r.output.update.confounders.length}]}),Y(`Recommended actions`,r.actions.map(e=>`${e.title} (${e.confidence}): ${e.action}`)),Y(`Postmortem findings`,r.output.insights.map(e=>e.summary)),Y(`Segment evidence warnings`,[...r.output.segments.page.warnings,...r.output.segments.query.warnings,...r.output.segments.device.warnings,...r.output.segments.country.warnings].filter((e,t,n)=>n.indexOf(e)===t)),Sd(r.output.templateMovement),Y(`Update evidence`,r.output.update.evidence),xd(r.output.segments),bd(`Page winners and losers`,r.output.segments.page),bd(`Query winners and losers`,r.output.segments.query),bd(`Device winners and losers`,r.output.segments.device),bd(`Country winners and losers`,r.output.segments.country),_a(`seo export update-postmortem ${n.client?`--project ${JSON.stringify(n.client.id)}`:`--site ${JSON.stringify(n.site)}`}`)}});function wd(e){return e.replaceAll(`\\`,`/`).toLowerCase()}function Td(e,t){return t===`win32`?`${e}.cmd`:e}function Ed(e,t=process.platform){let n=wd(e);if(!(n.includes(`/_npx/`)||n.includes(`/.npm/_npx/`)||n.includes(`/.cache/pnpm/dlx/`)||n.includes(`/.volta/`))){if(n.includes(`/.pnpm/`)||n.includes(`/pnpm/global/`))return{command:Td(`pnpm`,t),args:[`add`,`--global`,`seo@latest`]};if(n.includes(`/.bun/install/global/`))return{command:Td(`bun`,t),args:[`add`,`--global`,`seo@latest`]};if(n.includes(`/.config/yarn/global/`))return{command:Td(`yarn`,t),args:[`global`,`add`,`seo@latest`]};if(n.includes(`/node_modules/seo/`))return{command:Td(`npm`,t),args:[`install`,`--global`,`seo@latest`]}}}function Dd(e,t){let n=e=>e.replace(/^v/,``).split(`-`)[0]?.split(`.`).map(e=>Number.parseInt(e,10))??[],r=n(e),i=n(t);for(let e=0;e<3;e+=1){let t=r[e]??0,n=i[e]??0;if(!Number.isFinite(t)||!Number.isFinite(n))return!1;if(n>t)return!0;if(n<t)return!1}return!1}function Od(e){return!(!e.stdinIsTty||!e.stdoutIsTty||e.env.CI||e.env.NO_UPDATE_NOTIFIER||e.env.NODE_ENV===`test`||e.env.npm_lifecycle_event||e.env.npm_execpath||e.env.TERM===`dumb`||e.argv.includes(`--json`)||e.argv.some(e=>[`--help`,`-h`,`--version`,`-v`].includes(e))||[`help`,`mcp`].includes(e.argv[0]??``))}function kd(e){if(e)try{return Kn(e)}catch{return e}}async function Ad(e){let t=await Ar({message:`seo ${e.update.latest} is available. You have ${e.update.current}.`,options:[{value:`update`,label:`Update now`,hint:[e.command.command,...e.command.args].join(` `)},{value:`later`,label:`Later`}],initialValue:`later`});return Tr(t)?`later`:t}function jd(e){return or(e.command,e.args,{stdio:`inherit`}).status}async function Md(e,t={}){if(!Od({argv:t.argv??process.argv.slice(2),env:t.env??process.env,stdinIsTty:t.stdinIsTty??!!process.stdin.isTTY,stdoutIsTty:t.stdoutIsTty??!!process.stdout.isTTY}))return;let n=kd(t.cliPath??process.argv[1]),r=n?Ed(n,t.platform??process.platform):void 0;if(!r)return;let i=(t.createNotifier??(e=>Br({pkg:e,shouldNotifyInNpmScript:!1})))(e).update;if(!(!i||!Dd(i.current,i.latest))&&await(t.prompt??Ad)({command:r,update:i})!==`later`)return(t.run??jd)(r)===0?(Er.success(`Updated seo to ${i.latest}. Run your command again.`),0):(Er.error(`The update failed. Your current version is unchanged.`),1)}const Nd={name:`seo`,version:be},Pd=[{title:`Start here`,commands:[[`seo start`,`Connect Google and save a project profile`],[`seo report`,`Run the main SEO report for the default project`],[`seo report --site sc-domain:example.com`,`Run without a profile`],[`seo report --url https://example.com`,`Start with a local technical report`]]},{title:`Projects`,commands:[[`seo projects list`,`List saved project profiles`],[`seo projects add`,`Create or update a project profile`],[`seo sites`,`List Search Console properties`],[`seo doctor`,`Check local auth and config`]]},{title:`Act on a report`,commands:[[`seo refresh-priorities`,`Rank the next best SEO actions`],[`seo quick-wins`,`Find ranking 4-10 low-CTR wins`],[`seo second-page`,`Investigate URLs averaging positions 10-20`],[`seo technical-watch`,`Crawl and index-monitor a site`]]},{title:`Agent and power tools`,commands:[[`seo report --json`,`Run the main report as structured JSON`],[`seo export diagnose`,`Export report data to CSV`],[`seo mcp install`,`Install SEO tools into MCP clients`],[`seo skill list`,`Show the packaged SEO skill`],[`seo reports list`,`Discover every structured report`]]}],Fd=[...Pd,{title:`Deeper analysis`,commands:[[`seo decaying`,`Find pages and queries losing clicks`],[`seo cannibal`,`Review queries exposed across multiple URLs`],[`seo ctr-underperformers`,`Find weak CTR by ranking position`],[`seo query-cluster`,`Cluster repeated demand themes`],[`seo page-opportunities`,`Analyze one URL for growth ideas`],[`seo content optimize`,`Build a content optimization report`],[`seo perf audit`,`Run a page performance audit`],[`seo internal-links`,`Find internal link opportunities`],[`seo community-intent`,`Find forum, review, and comparison intent`],[`seo ai-referrals`,`Find AI referral traffic in Google Analytics`],[`seo seo-to-ai-query`,`Convert searches into AI-monitor prompts`]]},{title:`Technical and data`,commands:[[`seo audit-page`,`Audit one page`],[`seo crawl --sitemap-url <url> --health`,`Run the fast sitemap gate before a full crawl`],[`seo crawl`,`Run a full crawl for page and content evidence`],[`seo agent-readiness`,`Check content-site readiness for AI agents`],[`seo ai-readiness`,`Review AI-search technical evidence`],[`seo llms audit`,`Inspect optional llms.txt presence`],[`seo entity-readiness`,`Check brand/entity signals from a saved crawl`],[`seo okf export`,`Export a saved crawl as an OKF knowledge bundle`],[`seo crawl-queue`,`Run a crawl and rank the implementation queue`],[`seo crawl-reports`,`List saved crawl reports`],[`seo rules`,`List crawler rule ids`],[`seo explain --rule missing_title`,`Explain a crawler rule`],[`seo crawl-diff`,`Compare crawl changes`],[`seo index-coverage`,`Choose pages for URL Inspection`],[`seo index-watch`,`Check URL Inspection status`],[`seo indexnow submit`,`Notify IndexNow about changed URLs`],[`seo link-recover`,`Find broken search-value URLs`],[`seo links`,`Review bounded referring-link evidence`],[`seo server-logs analyze`,`Review crawler requests in an access log`],[`seo redirect-trace`,`Trace redirects`],[`seo gsc-query`,`Run a raw GSC query`],[`seo url-inspect`,`Run URL Inspection`],[`seo analytics google properties`,`List Google Analytics properties`],[`seo analytics google report`,`Run a Google Analytics report`],[`seo updates`,`List official Google ranking updates`]]},{title:`Reports and ops`,commands:[[`seo monthly-report`,`Generate a monthly narrative`],[`seo report-narrative`,`Generate a client-ready narrative`],[`seo update-postmortem`,`Analyze update winners and losers`],[`seo schedule cron`,`Print cron entries`],[`seo monitoring`,`Run and review monitoring`],[`seo change-log`,`Track SEO changes`],[`seo tests`,`Create and report local SEO tests`],[`seo content-groups`,`Manage reusable page/query groups`],[`seo pseo`,`Audit programmatic SEO templates`],[`seo auth`,`Manage Google auth`],[`seo providers bing`,`Connect and report on Bing Webmaster`],[`seo providers dataforseo`,`Connect optional search data`],[`seo cache`,`Manage local cache`],[`seo privacy`,`Show local storage paths`],[`seo telemetry status`,`Check anonymous usage telemetry`],[`seo reset`,`Delete local SEO data`]]}];function Id(e){qi(`seo v${Nd.version}`,`Run SEO audits, find what needs fixing, and ship the changes with your agent.`),process.stdout.write(`
275
275
  `),Xi(e.flatMap(e=>e.commands.map(([t,n])=>({category:e.title,id:t,name:n}))),{noun:`command`,categoryLabels:Object.fromEntries(e.map(e=>[e.title,e.title]))}),process.stdout.write(`
276
- `),Qi(`More help`,"Use `seo help <command>` or `seo <command> --help` for command help.","Use `seo help all` for the longer command list.")}const Id=x({meta:{name:`seo`,version:Md.version,description:`Local-first SEO CLI and MCP server`},subCommands:{init:Tc,indexnow:wc,analytics:ya,llms:kc,auth:wa,mcp:Nc,okf:Yc,doctor:ml,privacy:Lu,telemetry:Gu,reset:Ru,cache:Da,logs:Ac,crawl:ns,"crawl-queue":Ku,"crawl-reports":_s,client:Wo,content:Ko,project:Go,projects:Go,providers:Ml,"change-log":ks,tests:Vs,"content-groups":Ms,"crawl-diff":Pc,diagnose:pl,"diagnose-property":pd,export:bc,"index-coverage":Wc,"index-watch":Gc,"link-recover":Kc,links:Ec,"monthly-report":$l,monitoring:Uc,"report-narrative":eu,rules:tu,explain:nu,"refresh-priorities":hd,"redirect-trace":qc,schedule:iu,"server-logs":au,skill:Iu,start:bo,setup:yo,report:md,reports:Kl,"segment-impact":gl,"striking-distance":vl,"technical-watch":gd,"update-postmortem":Sd,sites:zu,"gsc-query":vs,"url-inspect":ys,updates:Bu,"traffic-anomaly":Cs,"update-correlate":ws,"ai-referrals":Xc,"agent-readiness":da,"ai-readiness":ca,"entity-readiness":Es,"audit-page":cl,"second-page":ol,cannibal:Zc,"community-intent":Qc,decaying:tl,"quick-wins":al,"page-opportunities":rl,perf:fl,"internal-links":nl,"ctr-underperformers":$c,"query-cluster":il,"seo-to-ai-query":sl,pseo:Bl},run:async()=>{process.argv.slice(2).length===0&&process.stdout.write("Use `seo start` to set up, then `seo report` for the main report.\n")}}),Ld={cannibal:`cannibalisation`,crawl:`site-crawl`,"crawl-queue":`top-fixes`,decaying:`decaying-pages`,"diagnose-property":`search-performance-overview`,doctor:`setup-check`,explain:`explain-crawl-issue`,"link-recover":`link-recovery`,"monthly-report":`monthly-report`,"query-cluster":`query-clusters`,report:`search-performance-overview`,"report-narrative":`narrative-report`,rules:`crawler-rules`,"update-correlate":`update-correlation`},Rd=new Set([`agent-readiness`,`ai-readiness`,`ai-referrals`,`audit-page`,`community-intent`,`crawl-diff`,`ctr-underperformers`,`entity-readiness`,`index-coverage`,`index-watch`,`internal-links`,`link-evidence`,`page-opportunities`,`quick-wins`,`redirect-trace`,`refresh-priorities`,`second-page`,`segment-impact`,`seo-to-ai-query`,`striking-distance`,`technical-watch`,`traffic-anomaly`,`update-postmortem`]),zd=new Set(tn().map(e=>e.id));function Bd(e){let[t,n,r]=e;if(t)return t===`reports`&&n===`run`?r&&zd.has(r)?r:void 0:t===`content`&&n===`optimize`?`content-optimization`:t===`change-log`&&n===`measure`?`measure-change`:t===`llms`&&n===`audit`?`llms-txt-audit`:t===`links`?`link-evidence`:t===`llms`&&n===`generate`?`generate-llms-txt`:t===`okf`&&n===`export`?`okf-build`:t===`okf`&&n===`validate`?`okf-validate`:t===`monitoring`&&n===`run`?`technical-watch`:t===`perf`&&n===`audit`?`performance-audit`:t===`pseo`&&n===`audit`?`pseo-audit`:t===`tests`&&n===`report`?`measure-change`:Ld[t]??(Rd.has(t)?t:void 0)}const $=process.argv.slice(2),Vd={agent:Kt()},Hd=$[0]===`telemetry`,Ud=$[0]===`mcp`&&$[1]===`serve`;!Hd&&!Ud&&St(Vd);const Wd=await jd(Md,{argv:$});Wd!==void 0&&process.exit(Wd),($.length===0||$.length===1&&[`help`,`--help`,`-h`].includes($[0]??``))&&(Fd(Nd),process.exit(0)),$[0]===`help`&&$[1]===`all`&&(Fd(Pd),process.exit(0));const Gd=$[0]===`help`&&$.length>1?[...$.slice(1),`--help`]:$;Gd!==$&&(process.argv=[process.argv[0]??`node`,process.argv[1]??`seo`,...Gd]);const Kd=Gd.some(e=>[`--help`,`-h`].includes(e)),qd=$.length===1&&[`--version`,`-v`].includes($[0]??``),Jd=Kd||qd?void 0:Bd(Gd);if(Kd||qd)await xr(Id);else{Jd&&jt(Jd,Vd);try{await br(Id,{rawArgs:Gd}),Jd&&Yt(Jd,Vd),[`init`,`setup`,`start`].includes(Gd[0]??``)&&!Gd.includes(`--dry-run`)&&ut(Vd)}catch(e){Jd&&Pt(Jd,on(e),Vd);let t=rn(e);$.includes(`--json`)?process.stdout.write(`${JSON.stringify(ht(t),null,2)}\n`):process.stderr.write(`Error: ${t.message}\n`),process.exitCode=t.exitCode}}export{};
276
+ `),Qi(`More help`,"Use `seo help <command>` or `seo <command> --help` for command help.","Use `seo help all` for the longer command list.")}const Ld=x({meta:{name:`seo`,version:Nd.version,description:`Local-first SEO CLI and MCP server`},subCommands:{init:Tc,indexnow:wc,analytics:ya,llms:kc,auth:wa,mcp:Nc,okf:Yc,doctor:ml,privacy:Ru,telemetry:Ku,reset:zu,cache:Da,logs:Ac,crawl:ns,"crawl-queue":qu,"crawl-reports":_s,client:Wo,content:Ko,project:Go,projects:Go,providers:Ml,"change-log":ks,tests:Vs,"content-groups":Ms,"crawl-diff":Pc,diagnose:pl,"diagnose-property":md,export:bc,"index-coverage":Wc,"index-watch":Gc,"link-recover":Kc,links:Ec,"monthly-report":$l,monitoring:Uc,"report-narrative":eu,rules:tu,explain:nu,"refresh-priorities":gd,"redirect-trace":qc,schedule:au,"server-logs":ou,skill:Lu,start:bo,setup:yo,report:hd,reports:Kl,"segment-impact":gl,"striking-distance":vl,"technical-watch":_d,"update-postmortem":Cd,sites:Bu,"gsc-query":vs,"url-inspect":ys,updates:Vu,"traffic-anomaly":Cs,"update-correlate":ws,"ai-referrals":Xc,"agent-readiness":da,"ai-readiness":ca,"entity-readiness":Es,"audit-page":cl,"second-page":ol,cannibal:Zc,"community-intent":Qc,decaying:tl,"quick-wins":al,"page-opportunities":rl,perf:fl,"internal-links":nl,"ctr-underperformers":$c,"query-cluster":il,"seo-to-ai-query":sl,pseo:Bl},run:async()=>{process.argv.slice(2).length===0&&process.stdout.write("Use `seo start` to set up, then `seo report` for the main report.\n")}}),Rd={cannibal:`cannibalisation`,crawl:`site-crawl`,"crawl-queue":`top-fixes`,decaying:`decaying-pages`,"diagnose-property":`search-performance-overview`,doctor:`setup-check`,explain:`explain-crawl-issue`,"link-recover":`link-recovery`,"monthly-report":`monthly-report`,"query-cluster":`query-clusters`,report:`search-performance-overview`,"report-narrative":`narrative-report`,rules:`crawler-rules`,"update-correlate":`update-correlation`},zd=new Set([`agent-readiness`,`ai-readiness`,`ai-referrals`,`audit-page`,`community-intent`,`crawl-diff`,`ctr-underperformers`,`entity-readiness`,`index-coverage`,`index-watch`,`internal-links`,`link-evidence`,`page-opportunities`,`quick-wins`,`redirect-trace`,`refresh-priorities`,`second-page`,`segment-impact`,`seo-to-ai-query`,`striking-distance`,`technical-watch`,`traffic-anomaly`,`update-postmortem`]),Bd=new Set(tn().map(e=>e.id));function Vd(e){let[t,n,r]=e;if(t)return t===`reports`&&n===`run`?r&&Bd.has(r)?r:void 0:t===`content`&&n===`optimize`?`content-optimization`:t===`change-log`&&n===`measure`?`measure-change`:t===`llms`&&n===`audit`?`llms-txt-audit`:t===`links`?`link-evidence`:t===`llms`&&n===`generate`?`generate-llms-txt`:t===`okf`&&n===`export`?`okf-build`:t===`okf`&&n===`validate`?`okf-validate`:t===`monitoring`&&n===`run`?`technical-watch`:t===`perf`&&n===`audit`?`performance-audit`:t===`pseo`&&n===`audit`?`pseo-audit`:t===`tests`&&n===`report`?`measure-change`:Rd[t]??(zd.has(t)?t:void 0)}const $=process.argv.slice(2),Hd={agent:Kt()},Ud=$[0]===`telemetry`,Wd=$[0]===`mcp`&&$[1]===`serve`;!Ud&&!Wd&&St(Hd);const Gd=await Md(Nd,{argv:$});Gd!==void 0&&process.exit(Gd),($.length===0||$.length===1&&[`help`,`--help`,`-h`].includes($[0]??``))&&(Id(Pd),process.exit(0)),$[0]===`help`&&$[1]===`all`&&(Id(Fd),process.exit(0));const Kd=$[0]===`help`&&$.length>1?[...$.slice(1),`--help`]:$;Kd!==$&&(process.argv=[process.argv[0]??`node`,process.argv[1]??`seo`,...Kd]);const qd=Kd.some(e=>[`--help`,`-h`].includes(e)),Jd=$.length===1&&[`--version`,`-v`].includes($[0]??``),Yd=qd||Jd?void 0:Vd(Kd);if(qd||Jd)await xr(Ld);else{Yd&&jt(Yd,Hd);try{await br(Ld,{rawArgs:Kd}),Yd&&Yt(Yd,Hd),[`init`,`setup`,`start`].includes(Kd[0]??``)&&!Kd.includes(`--dry-run`)&&ut(Hd)}catch(e){Yd&&Pt(Yd,on(e),Hd);let t=rn(e);$.includes(`--json`)?process.stdout.write(`${JSON.stringify(ht(t),null,2)}\n`):process.stderr.write(`Error: ${t.message}\n`),process.exitCode=t.exitCode}}export{};
277
277
  //# sourceMappingURL=cli.js.map