seo 0.2.2 → 0.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -77,7 +77,7 @@ technical report before connecting Google.
77
77
  Running `seo help` shows the shape of the tool:
78
78
 
79
79
  ```txt
80
- seo v0.2.2
80
+ seo v0.2.3
81
81
 
82
82
  Run SEO audits, find what needs fixing, and ship the changes with your agent.
83
83
 
package/dist/cli.js CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire as __seoCreateRequire } from 'node:module'; const require = __seoCreateRequire(import.meta.url);
3
- import{$ as e,$t as t,A as n,At as r,B as i,Bt as a,C as o,Ct as s,D as c,Dt as l,E as u,Et as d,F as f,Ft as p,G as m,Gt as h,H as g,Ht as _,I as ee,It as te,J as ne,Jt as re,K as ie,Kt as ae,L as oe,Lt as se,M as ce,Mt as le,N as ue,Nt as de,O as fe,Ot as pe,P as me,Pt as he,Q as ge,Qt as _e,R as ve,Rt as ye,S as be,St as xe,T as Se,Tt as Ce,U as we,Ut as Te,V as Ee,Vt as De,W as Oe,Wt as ke,X as Ae,Xt as je,Y as Me,Yt as Ne,Z as Pe,Zt as Fe,_ as Ie,_t as Le,a as Re,an as ze,at as Be,b as Ve,bt as He,c as Ue,cn as We,ct as Ge,d as Ke,dt as qe,en as Je,et as Ye,f as Xe,ft as Ze,g as Qe,gt as $e,h as et,ht as tt,i as nt,in as rt,it,j as at,jt as ot,k as st,kt as ct,l as lt,ln as ut,lt as dt,m as ft,mt as pt,nn as mt,nt as ht,on as gt,ot as _t,p as vt,pt as yt,q as bt,qt as xt,r as St,rn as Ct,rt as wt,sn as v,st as Tt,t as Et,tn as Dt,tt as Ot,u as kt,ut as At,v as jt,vt as Mt,w as Nt,wt as Pt,x as Ft,xt as It,y as Lt,yt as Rt,z as zt,zt as Bt}from"./dist-CpTkpH4F.js";import{constants as Vt,copyFileSync as Ht,existsSync as y,mkdirSync as Ut,readFileSync as Wt,readdirSync as Gt,realpathSync as Kt,renameSync as qt,rmSync as Jt,statSync as Yt,writeFileSync as Xt}from"node:fs";import{homedir as Zt}from"node:os";import{basename as Qt,dirname as b,isAbsolute as $t,join as x,posix as en,relative as tn,resolve as nn,win32 as rn}from"node:path";import{randomUUID as an}from"node:crypto";import{spawn as on,spawnSync as sn}from"node:child_process";import{fileURLToPath as cn}from"node:url";import{isDeepStrictEqual as ln}from"node:util";import{lstat as un,mkdir as dn,mkdtemp as fn,readFile as pn,readdir as mn,rename as hn,rm as gn,writeFile as _n}from"node:fs/promises";import{defineCommand as S,runCommand as vn,runMain as yn}from"citty";import{cancel as bn,confirm as xn,intro as Sn,multiselect as Cn,note as C,outro as wn,password as Tn,select as En,text as Dn}from"@clack/prompts";import On from"cli-table3";import kn from"picocolors";import An from"update-notifier";import{applyEdits as jn,modify as Mn,parse as Nn,printParseErrorCode as Pn}from"jsonc-parser";const w={summary:`workflow.site.generated_at.summary.attribution.confidence.classification.updates_matched.known_confounders.page_segment_status.page_segment_eligible_rows.page_segment_returned_rows.page_segment_limited_rows.page_segment_result_limit.page_segment_source_row_limit.page_segment_source_possibly_truncated.page_segment_warnings.query_segment_status.query_segment_eligible_rows.query_segment_returned_rows.query_segment_limited_rows.query_segment_result_limit.query_segment_source_row_limit.query_segment_source_possibly_truncated.query_segment_warnings.device_segment_status.device_segment_eligible_rows.device_segment_returned_rows.device_segment_limited_rows.device_segment_result_limit.device_segment_source_row_limit.device_segment_source_possibly_truncated.device_segment_warnings.country_segment_status.country_segment_eligible_rows.country_segment_returned_rows.country_segment_limited_rows.country_segment_result_limit.country_segment_source_row_limit.country_segment_source_possibly_truncated.country_segment_warnings`.split(`.`),steps:[`rank`,`tool`,`status`,`summary`],actions:[`rank`,`title`,`confidence`,`action`],findings:[`dimension`,`data_status`,`eligible_rows`,`returned_rows`,`limited_rows`,`result_limit`,`unmatched_rows`,`summary`,`winner_key`,`winner_click_delta`,`winner_impression_delta`,`winner_position_delta`,`loser_key`,`loser_click_delta`,`loser_impression_delta`,`loser_position_delta`],templates:[`rank`,`direction`,`template`,`confidence`,`url_count`,`click_delta`,`impression_delta`,`movement_share`,`movement_share_scope`,`confidence_scope`,`eligible_rows`,`returned_rows`,`limited_rows`,`result_limit`,`source_possibly_truncated`,`common_terms`,`sample_urls`,`summary`],evidence:[`rank`,`evidence`],confounders:[`rank`,`source`,`title`,`date`,`scope`,`target`,`description`],segment:[`rank`,`dimension`,`data_status`,`evidence_scope`,`key`,`before_clicks`,`after_clicks`,`click_delta`,`before_impressions`,`after_impressions`,`impression_delta`,`before_position`,`after_position`,`position_delta`],unmatchedSegment:[`rank`,`dimension`,`data_status`,`key`,`retained_in`,`clicks`,`impressions`,`position`,`reason`]};function Fn(e,t){return[...e.output.segments[t].winners,...e.output.segments[t].losers].map((n,r)=>({rank:r+1,dimension:t,data_status:e.output.segments[t].dataStatus,evidence_scope:n.evidenceScope,key:n.key,before_clicks:n.beforeClicks,after_clicks:n.afterClicks,click_delta:n.clickDelta,before_impressions:n.beforeImpressions,after_impressions:n.afterImpressions,impression_delta:n.impressionDelta,before_position:n.beforePosition,after_position:n.afterPosition,position_delta:n.positionDelta}))}function In(e,t){return e.output.segments[t].unmatchedSegments.map((n,r)=>({rank:r+1,dimension:t,data_status:e.output.segments[t].dataStatus,key:n.key,retained_in:n.retainedIn,clicks:n.clicks,impressions:n.impressions,position:n.position,reason:n.reason}))}function Ln(e){return[{filename:`postmortem-summary.csv`,headers:[...w.summary],rows:[{workflow:e.workflow,site:e.site,generated_at:e.generatedAt,summary:e.summary,attribution:e.output.update.attribution,confidence:e.output.update.confidence,classification:e.output.update.classification,updates_matched:e.output.update.overlappingUpdates.length,known_confounders:e.output.update.confounders.length,page_segment_status:e.output.segments.page.dataStatus,page_segment_eligible_rows:e.output.segments.page.coverage.eligibleRows,page_segment_returned_rows:e.output.segments.page.coverage.returnedRows,page_segment_limited_rows:e.output.segments.page.coverage.limitedRows,page_segment_result_limit:e.output.segments.page.coverage.resultLimit,page_segment_source_row_limit:e.output.segments.page.coverage.sourceRowLimit,page_segment_source_possibly_truncated:e.output.segments.page.coverage.sourcePossiblyTruncated,page_segment_warnings:e.output.segments.page.warnings.join(`; `),query_segment_status:e.output.segments.query.dataStatus,query_segment_eligible_rows:e.output.segments.query.coverage.eligibleRows,query_segment_returned_rows:e.output.segments.query.coverage.returnedRows,query_segment_limited_rows:e.output.segments.query.coverage.limitedRows,query_segment_result_limit:e.output.segments.query.coverage.resultLimit,query_segment_source_row_limit:e.output.segments.query.coverage.sourceRowLimit,query_segment_source_possibly_truncated:e.output.segments.query.coverage.sourcePossiblyTruncated,query_segment_warnings:e.output.segments.query.warnings.join(`; `),device_segment_status:e.output.segments.device.dataStatus,device_segment_eligible_rows:e.output.segments.device.coverage.eligibleRows,device_segment_returned_rows:e.output.segments.device.coverage.returnedRows,device_segment_limited_rows:e.output.segments.device.coverage.limitedRows,device_segment_result_limit:e.output.segments.device.coverage.resultLimit,device_segment_source_row_limit:e.output.segments.device.coverage.sourceRowLimit,device_segment_source_possibly_truncated:e.output.segments.device.coverage.sourcePossiblyTruncated,device_segment_warnings:e.output.segments.device.warnings.join(`; `),country_segment_status:e.output.segments.country.dataStatus,country_segment_eligible_rows:e.output.segments.country.coverage.eligibleRows,country_segment_returned_rows:e.output.segments.country.coverage.returnedRows,country_segment_limited_rows:e.output.segments.country.coverage.limitedRows,country_segment_result_limit:e.output.segments.country.coverage.resultLimit,country_segment_source_row_limit:e.output.segments.country.coverage.sourceRowLimit,country_segment_source_possibly_truncated:e.output.segments.country.coverage.sourcePossiblyTruncated,country_segment_warnings:e.output.segments.country.warnings.join(`; `)}]},{filename:`workflow-steps.csv`,headers:[...w.steps],rows:e.steps.map((e,t)=>({rank:t+1,tool:e.tool,status:e.status,summary:e.summary}))},{filename:`workflow-actions.csv`,headers:[...w.actions],rows:e.actions.map((e,t)=>({rank:t+1,title:e.title,confidence:e.confidence,action:e.action}))},{filename:`postmortem-findings.csv`,headers:[...w.findings],rows:e.output.insights.map(e=>({dimension:e.dimension,data_status:e.dataStatus,eligible_rows:e.coverage.eligibleRows,returned_rows:e.coverage.returnedRows,limited_rows:e.coverage.limitedRows,result_limit:e.coverage.resultLimit,unmatched_rows:e.unmatchedRows,summary:e.summary,winner_key:e.winner?.key,winner_click_delta:e.winner?.clickDelta,winner_impression_delta:e.winner?.impressionDelta,winner_position_delta:e.winner?.positionDelta,loser_key:e.loser?.key,loser_click_delta:e.loser?.clickDelta,loser_impression_delta:e.loser?.impressionDelta,loser_position_delta:e.loser?.positionDelta}))},{filename:`postmortem-template-movement.csv`,headers:[...w.templates],rows:e.output.templateMovement.map((e,t)=>({rank:t+1,direction:e.direction,template:e.signature,confidence:e.confidence,url_count:e.urlCount,click_delta:e.clickDelta,impression_delta:e.impressionDelta,movement_share:e.movementShare,movement_share_scope:e.movementShareScope,confidence_scope:e.confidenceScope,eligible_rows:e.segmentCoverage.eligibleRows,returned_rows:e.segmentCoverage.returnedRows,limited_rows:e.segmentCoverage.limitedRows,result_limit:e.segmentCoverage.resultLimit,source_possibly_truncated:e.segmentCoverage.sourcePossiblyTruncated,common_terms:e.commonTerms.join(`; `),sample_urls:e.sampleUrls.join(`; `),summary:e.summary}))},{filename:`postmortem-update-evidence.csv`,headers:[...w.evidence],rows:e.output.update.evidence.map((e,t)=>({rank:t+1,evidence:e}))},{filename:`postmortem-confounders.csv`,headers:[...w.confounders],rows:e.output.update.confounders.map((e,t)=>({rank:t+1,source:e.source,title:e.title,date:e.date,scope:e.scope,target:e.target,description:e.description}))},...[`page`,`query`,`device`,`country`].map(t=>({filename:`postmortem-segment-${t}.csv`,headers:[...w.segment],rows:Fn(e,t)})),...[`page`,`query`,`device`,`country`].map(t=>({filename:`postmortem-segment-${t}-unmatched.csv`,headers:[...w.unmatchedSegment],rows:In(e,t)}))]}function Rn(e){try{let t=new URL(e);return t.protocol!==`http:`&&t.protocol!==`https:`?void 0:t.hostname.toLowerCase().replace(/\.$/,``)||void 0}catch{return}}function zn(e){if(!e.startsWith(`sc-domain:`))return;let t=e.slice(10).trim().toLowerCase();if(!(!t||/[/:]/.test(t)))return t.replace(/\.$/,``)||void 0}function Bn(e,t){let n=zn(e);return n?t===n||t.endsWith(`.${n}`)?`domain`:void 0:Rn(e)===t?`hostname`:void 0}function Vn(e,t){return t.flatMap(t=>{let n=t.stream.webStreamData?.defaultUri;if(!n)return[];let r=Rn(n);if(!r)return[];let i=Bn(e,r);return i?[{...t,defaultUri:n,hostname:r,match:i}]:[]})}function Hn(e,t){let n=e.stream.displayName??e.defaultUri,r=e.match===`domain`?`is inside`:`matches`;return`Matched GA4 web stream ${n} (${e.defaultUri}): its hostname ${r} ${t}.`}function Un(e){let t=e-Date.now();if(t<=0)return`expired`;let n=Math.round(t/6e4);if(n<60)return`in ${n}m`;let r=Math.floor(n/60),i=n%60;return i?`in ${r}h ${i}m`:`in ${r}h`}function T(e,t){return{type:`boolean`,default:!0,description:e,negativeDescription:t}}function E(e){return typeof e==`string`?e:void 0}function D(e){let t=E(e.project),n=E(e.client);if(t&&n&&t!==n)throw Error(`Use either --project or --client, not both.`);return t??n}function O(e){return typeof e==`boolean`?e:void 0}function Wn(e){let t=E(e.rendering);if(t!==void 0&&t!==`auto`&&t!==`on`&&t!==`off`)throw new v(`INVALID_INPUT`,`--rendering must be auto, on, or off.`);if(O(e.js)===!0&&t&&t!==`on`)throw new v(`INVALID_INPUT`,`Use either --js or --rendering on, not both with different modes.`);return t??(O(e.js)?`on`:`auto`)}function Gn(e,t){let n=O(e[t]);if(n===!1)return!0;if(n===!0)return!1;let r=O(e[Kn(t)]);if(r===!1)return!0;if(r===!0)return!1;if(O(e[`no-${t}`])===!0||O(e[`no${qn(t)}`])===!0)return!0}function Kn(e){return e.replace(/-([a-z])/g,(e,t)=>t.toUpperCase())}function qn(e){let t=Kn(e);return t?`${t[0]?.toUpperCase()}${t.slice(1)}`:``}function k(e){if(typeof e==`number`)return e;if(typeof e!=`string`||!e.trim())return;let t=Number(e);return Number.isFinite(t)?t:void 0}function A(e,t){if(e===void 0||e===``)return;let n=k(e);if(n===void 0)throw new v(`INVALID_INPUT`,`${t} must be a number.`);return n}function j(e){if(typeof e!=`string`)return;let t=e.split(`,`).map(e=>e.trim()).filter(Boolean);return t.length?t:void 0}function M(e){return j(e)??[]}function N(e){let t={concurrency:k(e[`fetch-concurrency`]),intervalCap:k(e[`fetch-interval-cap`]),intervalMs:k(e[`fetch-interval-ms`])};return Object.values(t).some(e=>e!==void 0)?t:void 0}function Jn(e){let t={concurrency:A(e[`fetch-concurrency`],`--fetch-concurrency`),intervalCap:A(e[`fetch-interval-cap`],`--fetch-interval-cap`),intervalMs:A(e[`fetch-interval-ms`],`--fetch-interval-ms`)};return Object.values(t).some(e=>e!==void 0)?t:void 0}function P(e){return e.json===!0}async function Yn(e,t){let n=E(e),r=E(t);if(n&&r)throw Error(`Use either --body or --body-file, not both.`);if(!n&&!r)return;let i=n??await pn(r??``,`utf8`),a=JSON.parse(i);if(!a||typeof a!=`object`||Array.isArray(a))throw Error(`JSON body must be an object.`);return a}const Xn=kn.createColors(!!(process.stdout.isTTY&&!process.env.NO_COLOR));function Zn(e){process.env.CI||process.env.NO_UPDATE_NOTIFIER||An({pkg:e,shouldNotifyInNpmScript:!1}).notify()}function F(e={}){return!!(!e.json&&process.stdin.isTTY&&process.stdout.isTTY&&!process.env.CI)}function I(e){process.stdout.write(`${JSON.stringify(e,null,2)}\n`)}function L(e){let t=Math.max(...e.map(([e])=>e.length),0);for(let[n,r]of e)process.stdout.write(`${Xn.bold(n.padEnd(t))} ${r}\n`)}function R(e,t){let n=new On({head:e});for(let e of t)n.push(e);process.stdout.write(`${n.toString()}\n`)}function Qn(e){return e<1024?`${e} B`:e<1024*1024?`${(e/1024).toFixed(1)} KB`:`${(e/(1024*1024)).toFixed(1)} MB`}function z(e){return typeof e==`symbol`&&process.exit(1),e}function $n(e,t){let n=t.trim().toLowerCase();return!n||e.some(e=>e.toLowerCase().includes(n))}async function er(e){let t=e.choices;if(e.choices.length>8){let n=z(await Dn({message:e.searchMessage,placeholder:`Type part of the name or URL, or leave blank`}));if(t=e.choices.filter(t=>$n(e.searchValues(t),n)),!t.length)throw new v(`INVALID_INPUT`,e.emptyMessage)}return z(await En({message:e.message,options:t.map(t=>{let n=e.hint?.(t);return{value:t,label:e.label(t),...n?{hint:n}:{}}})}))}async function B(e){if(e.site)return e.site;let t=Dt(),n=!!t.defaultSite&&(t.clients.some(e=>e.siteUrl===t.defaultSite)||t.sites.some(e=>e.siteUrl===t.defaultSite));if(t.defaultSite&&n)return t.defaultSite;if(e.options?.json||!F())throw new v(`INVALID_INPUT`,t.defaultSite?`Saved default site ${t.defaultSite} does not match any saved project. Pass --site or --project, or run \`seo start\` to save a new default.`:"No site selected. Pass --site, use --project on supported commands, or run `seo start` in a terminal.");let r=await ye(e.options?.refresh);if(!r.length)throw new v(`PROPERTY_NOT_FOUND`,`No Search Console properties found for this Google login.`);return r.length===1?r[0]?.siteUrl??``:(await er({message:`Choose a Search Console property`,searchMessage:`Search Search Console properties`,emptyMessage:`No Search Console properties matched that search.`,choices:r,label:e=>e.siteUrl,hint:e=>e.permissionLevel,searchValues:e=>[e.siteUrl,e.permissionLevel??``]})).siteUrl}async function V(e){if(e.client&&e.project&&e.client!==e.project)throw new v(`INVALID_INPUT`,`Use either --project or --client, not both.`);let t=e.project??e.client;if(t){let e=Ke(t);if(!e)throw new v(`INVALID_INPUT`,`Project not found: ${t}`);return{client:e,site:e.siteUrl}}let n=Ke();return!e.site&&n?{client:n,site:n.siteUrl}:{site:await B({site:e.site,options:e.options})}}async function tr(e){if(e.client&&e.project&&e.client!==e.project)throw new v(`INVALID_INPUT`,`Use either --project or --client, not both.`);let t=e.project??e.client;if(t){let e=Ke(t);if(!e)throw new v(`INVALID_INPUT`,`Project not found: ${t}`);return e}let n=Xe();if(!n.length)return;let r=Ke();if(r)return r;if(!(e.options?.json||!F()))return er({message:`Choose a project`,searchMessage:`Search projects`,emptyMessage:`No projects matched that search.`,choices:n,label:e=>e.name,hint:e=>e.siteUrl,searchValues:e=>[e.id,e.name,e.siteUrl]})}async function nr(e){if(e.property)return e.property;let t=Dt();if(t.google.defaultGa4PropertyId)return t.google.defaultGa4PropertyId;if(e.options?.json||!F())throw new v(`INVALID_INPUT`,`No GA4 property selected. Pass --property or run this command in a terminal to choose one.`);let n=(await ke()).flatMap(e=>e.propertySummaries.map(t=>({property:_(t.property),displayName:t.displayName??t.property,account:e.displayName??e.account})));if(!n.length)throw new v(`PROPERTY_NOT_FOUND`,`No GA4 properties found for this Google login.`);return n.length===1?n[0]?.property??``:(await er({message:`Choose a GA4 property`,searchMessage:`Search GA4 properties`,emptyMessage:`No GA4 properties matched that search.`,choices:n,label:e=>`${e.displayName} (${e.property})`,hint:e=>e.account,searchValues:e=>[e.property,e.displayName,e.account]})).property}function H(e){return Math.round(e).toLocaleString(`en-GB`)}function U(e){return e.toFixed(1)}function W(e){return`${(e*100).toFixed(1)}%`}function G(e,t=72){return e.length<=t?e:`${e.slice(0,t-3)}...`}function rr(e){return e.verification?.requested?`${e.verification.verified} checked, ${e.verification.failed} failed`:`off`}function K(e,t){R(e,t.slice(0,25)),t.length>25&&process.stdout.write(`Showing 25 of ${t.length}. Use --json for full data.\n`)}function q(e,t,n=5){let r=t.filter(e=>e.action.trim().length>0).slice(0,n);if(r.length){process.stdout.write(`\n${e}\n`);for(let[e,t]of r.entries()){let n=t.context?` (${t.context})`:``;process.stdout.write(`${e+1}. ${t.label}${n}: ${t.action}\n`)}t.length>r.length&&process.stdout.write(`Showing ${r.length} of ${t.length}. Use --json for full data.\n`)}}function ir(e){process.stdout.write(`\nNext: ${e}\n`)}function J(e,t){let n=t.filter(e=>e.trim().length>0);if(n.length){process.stdout.write(`\n${e}\n`);for(let e of n)process.stdout.write(`- ${e}\n`)}}async function ar(e,t={}){let n=E(e[`report-id`]);if(n){let e=s(n);if(!e)throw Error(`No saved crawl report found for ${n}.`);return e}let r=D(e),i=E(e.site),a=r||i?await V({client:r,site:i,options:t}):void 0,o=It(a?.site);if(!o)throw Error(a?.site?`No saved crawl report found for ${a.site}. Run \`seo crawl --project ${a.client?.id??a.site} --save\` first.`:"No saved crawl report found. Run `seo crawl <url> --save` first, or pass --report-id.");return o}function or(e){process.stdout.write(`AI-search evidence for ${e.url}\n\n`),L([[`Assessment`,`evidence only`],[`Data`,e.dataStatus],[`Report`,e.reportId]]),process.stdout.write(`\n${e.headline}\n`),e.topActions.length&&(process.stdout.write(`
3
+ import{$ as e,$t as t,A as n,At as r,B as i,Bt as a,C as o,Ct as s,D as c,Dt as l,E as u,Et as d,F as f,Ft as p,G as m,Gt as h,H as g,Ht as _,I as ee,It as te,J as ne,Jt as re,K as ie,Kt as ae,L as oe,Lt as se,M as ce,Mt as le,N as ue,Nt as de,O as fe,Ot as pe,P as me,Pt as he,Q as ge,Qt as _e,R as ve,Rt as ye,S as be,St as xe,T as Se,Tt as Ce,U as we,Ut as Te,V as Ee,Vt as De,W as Oe,Wt as ke,X as Ae,Xt as je,Y as Me,Yt as Ne,Z as Pe,Zt as Fe,_ as Ie,_t as Le,a as Re,an as ze,at as Be,b as Ve,bt as He,c as Ue,cn as We,ct as Ge,d as Ke,dt as qe,en as Je,et as Ye,f as Xe,ft as Ze,g as Qe,gt as $e,h as et,ht as tt,i as nt,in as rt,it,j as at,jt as ot,k as st,kt as ct,l as lt,ln as ut,lt as dt,m as ft,mt as pt,nn as mt,nt as ht,on as gt,ot as _t,p as vt,pt as yt,q as bt,qt as xt,r as St,rn as Ct,rt as wt,sn as v,st as Tt,t as Et,tn as Dt,tt as Ot,u as kt,ut as At,v as jt,vt as Mt,w as Nt,wt as Pt,x as Ft,xt as It,y as Lt,yt as Rt,z as zt,zt as Bt}from"./dist-BnXs3Jen.js";import{constants as Vt,copyFileSync as Ht,existsSync as y,mkdirSync as Ut,readFileSync as Wt,readdirSync as Gt,realpathSync as Kt,renameSync as qt,rmSync as Jt,statSync as Yt,writeFileSync as Xt}from"node:fs";import{homedir as Zt}from"node:os";import{basename as Qt,dirname as b,isAbsolute as $t,join as x,posix as en,relative as tn,resolve as nn,win32 as rn}from"node:path";import{randomUUID as an}from"node:crypto";import{spawn as on,spawnSync as sn}from"node:child_process";import{fileURLToPath as cn}from"node:url";import{isDeepStrictEqual as ln}from"node:util";import{lstat as un,mkdir as dn,mkdtemp as fn,readFile as pn,readdir as mn,rename as hn,rm as gn,writeFile as _n}from"node:fs/promises";import{defineCommand as S,runCommand as vn,runMain as yn}from"citty";import{cancel as bn,confirm as xn,intro as Sn,multiselect as Cn,note as C,outro as wn,password as Tn,select as En,text as Dn}from"@clack/prompts";import On from"cli-table3";import kn from"picocolors";import An from"update-notifier";import{applyEdits as jn,modify as Mn,parse as Nn,printParseErrorCode as Pn}from"jsonc-parser";const w={summary:`workflow.site.generated_at.summary.attribution.confidence.classification.updates_matched.known_confounders.page_segment_status.page_segment_eligible_rows.page_segment_returned_rows.page_segment_limited_rows.page_segment_result_limit.page_segment_source_row_limit.page_segment_source_possibly_truncated.page_segment_warnings.query_segment_status.query_segment_eligible_rows.query_segment_returned_rows.query_segment_limited_rows.query_segment_result_limit.query_segment_source_row_limit.query_segment_source_possibly_truncated.query_segment_warnings.device_segment_status.device_segment_eligible_rows.device_segment_returned_rows.device_segment_limited_rows.device_segment_result_limit.device_segment_source_row_limit.device_segment_source_possibly_truncated.device_segment_warnings.country_segment_status.country_segment_eligible_rows.country_segment_returned_rows.country_segment_limited_rows.country_segment_result_limit.country_segment_source_row_limit.country_segment_source_possibly_truncated.country_segment_warnings`.split(`.`),steps:[`rank`,`tool`,`status`,`summary`],actions:[`rank`,`title`,`confidence`,`action`],findings:[`dimension`,`data_status`,`eligible_rows`,`returned_rows`,`limited_rows`,`result_limit`,`unmatched_rows`,`summary`,`winner_key`,`winner_click_delta`,`winner_impression_delta`,`winner_position_delta`,`loser_key`,`loser_click_delta`,`loser_impression_delta`,`loser_position_delta`],templates:[`rank`,`direction`,`template`,`confidence`,`url_count`,`click_delta`,`impression_delta`,`movement_share`,`movement_share_scope`,`confidence_scope`,`eligible_rows`,`returned_rows`,`limited_rows`,`result_limit`,`source_possibly_truncated`,`common_terms`,`sample_urls`,`summary`],evidence:[`rank`,`evidence`],confounders:[`rank`,`source`,`title`,`date`,`scope`,`target`,`description`],segment:[`rank`,`dimension`,`data_status`,`evidence_scope`,`key`,`before_clicks`,`after_clicks`,`click_delta`,`before_impressions`,`after_impressions`,`impression_delta`,`before_position`,`after_position`,`position_delta`],unmatchedSegment:[`rank`,`dimension`,`data_status`,`key`,`retained_in`,`clicks`,`impressions`,`position`,`reason`]};function Fn(e,t){return[...e.output.segments[t].winners,...e.output.segments[t].losers].map((n,r)=>({rank:r+1,dimension:t,data_status:e.output.segments[t].dataStatus,evidence_scope:n.evidenceScope,key:n.key,before_clicks:n.beforeClicks,after_clicks:n.afterClicks,click_delta:n.clickDelta,before_impressions:n.beforeImpressions,after_impressions:n.afterImpressions,impression_delta:n.impressionDelta,before_position:n.beforePosition,after_position:n.afterPosition,position_delta:n.positionDelta}))}function In(e,t){return e.output.segments[t].unmatchedSegments.map((n,r)=>({rank:r+1,dimension:t,data_status:e.output.segments[t].dataStatus,key:n.key,retained_in:n.retainedIn,clicks:n.clicks,impressions:n.impressions,position:n.position,reason:n.reason}))}function Ln(e){return[{filename:`postmortem-summary.csv`,headers:[...w.summary],rows:[{workflow:e.workflow,site:e.site,generated_at:e.generatedAt,summary:e.summary,attribution:e.output.update.attribution,confidence:e.output.update.confidence,classification:e.output.update.classification,updates_matched:e.output.update.overlappingUpdates.length,known_confounders:e.output.update.confounders.length,page_segment_status:e.output.segments.page.dataStatus,page_segment_eligible_rows:e.output.segments.page.coverage.eligibleRows,page_segment_returned_rows:e.output.segments.page.coverage.returnedRows,page_segment_limited_rows:e.output.segments.page.coverage.limitedRows,page_segment_result_limit:e.output.segments.page.coverage.resultLimit,page_segment_source_row_limit:e.output.segments.page.coverage.sourceRowLimit,page_segment_source_possibly_truncated:e.output.segments.page.coverage.sourcePossiblyTruncated,page_segment_warnings:e.output.segments.page.warnings.join(`; `),query_segment_status:e.output.segments.query.dataStatus,query_segment_eligible_rows:e.output.segments.query.coverage.eligibleRows,query_segment_returned_rows:e.output.segments.query.coverage.returnedRows,query_segment_limited_rows:e.output.segments.query.coverage.limitedRows,query_segment_result_limit:e.output.segments.query.coverage.resultLimit,query_segment_source_row_limit:e.output.segments.query.coverage.sourceRowLimit,query_segment_source_possibly_truncated:e.output.segments.query.coverage.sourcePossiblyTruncated,query_segment_warnings:e.output.segments.query.warnings.join(`; `),device_segment_status:e.output.segments.device.dataStatus,device_segment_eligible_rows:e.output.segments.device.coverage.eligibleRows,device_segment_returned_rows:e.output.segments.device.coverage.returnedRows,device_segment_limited_rows:e.output.segments.device.coverage.limitedRows,device_segment_result_limit:e.output.segments.device.coverage.resultLimit,device_segment_source_row_limit:e.output.segments.device.coverage.sourceRowLimit,device_segment_source_possibly_truncated:e.output.segments.device.coverage.sourcePossiblyTruncated,device_segment_warnings:e.output.segments.device.warnings.join(`; `),country_segment_status:e.output.segments.country.dataStatus,country_segment_eligible_rows:e.output.segments.country.coverage.eligibleRows,country_segment_returned_rows:e.output.segments.country.coverage.returnedRows,country_segment_limited_rows:e.output.segments.country.coverage.limitedRows,country_segment_result_limit:e.output.segments.country.coverage.resultLimit,country_segment_source_row_limit:e.output.segments.country.coverage.sourceRowLimit,country_segment_source_possibly_truncated:e.output.segments.country.coverage.sourcePossiblyTruncated,country_segment_warnings:e.output.segments.country.warnings.join(`; `)}]},{filename:`workflow-steps.csv`,headers:[...w.steps],rows:e.steps.map((e,t)=>({rank:t+1,tool:e.tool,status:e.status,summary:e.summary}))},{filename:`workflow-actions.csv`,headers:[...w.actions],rows:e.actions.map((e,t)=>({rank:t+1,title:e.title,confidence:e.confidence,action:e.action}))},{filename:`postmortem-findings.csv`,headers:[...w.findings],rows:e.output.insights.map(e=>({dimension:e.dimension,data_status:e.dataStatus,eligible_rows:e.coverage.eligibleRows,returned_rows:e.coverage.returnedRows,limited_rows:e.coverage.limitedRows,result_limit:e.coverage.resultLimit,unmatched_rows:e.unmatchedRows,summary:e.summary,winner_key:e.winner?.key,winner_click_delta:e.winner?.clickDelta,winner_impression_delta:e.winner?.impressionDelta,winner_position_delta:e.winner?.positionDelta,loser_key:e.loser?.key,loser_click_delta:e.loser?.clickDelta,loser_impression_delta:e.loser?.impressionDelta,loser_position_delta:e.loser?.positionDelta}))},{filename:`postmortem-template-movement.csv`,headers:[...w.templates],rows:e.output.templateMovement.map((e,t)=>({rank:t+1,direction:e.direction,template:e.signature,confidence:e.confidence,url_count:e.urlCount,click_delta:e.clickDelta,impression_delta:e.impressionDelta,movement_share:e.movementShare,movement_share_scope:e.movementShareScope,confidence_scope:e.confidenceScope,eligible_rows:e.segmentCoverage.eligibleRows,returned_rows:e.segmentCoverage.returnedRows,limited_rows:e.segmentCoverage.limitedRows,result_limit:e.segmentCoverage.resultLimit,source_possibly_truncated:e.segmentCoverage.sourcePossiblyTruncated,common_terms:e.commonTerms.join(`; `),sample_urls:e.sampleUrls.join(`; `),summary:e.summary}))},{filename:`postmortem-update-evidence.csv`,headers:[...w.evidence],rows:e.output.update.evidence.map((e,t)=>({rank:t+1,evidence:e}))},{filename:`postmortem-confounders.csv`,headers:[...w.confounders],rows:e.output.update.confounders.map((e,t)=>({rank:t+1,source:e.source,title:e.title,date:e.date,scope:e.scope,target:e.target,description:e.description}))},...[`page`,`query`,`device`,`country`].map(t=>({filename:`postmortem-segment-${t}.csv`,headers:[...w.segment],rows:Fn(e,t)})),...[`page`,`query`,`device`,`country`].map(t=>({filename:`postmortem-segment-${t}-unmatched.csv`,headers:[...w.unmatchedSegment],rows:In(e,t)}))]}function Rn(e){try{let t=new URL(e);return t.protocol!==`http:`&&t.protocol!==`https:`?void 0:t.hostname.toLowerCase().replace(/\.$/,``)||void 0}catch{return}}function zn(e){if(!e.startsWith(`sc-domain:`))return;let t=e.slice(10).trim().toLowerCase();if(!(!t||/[/:]/.test(t)))return t.replace(/\.$/,``)||void 0}function Bn(e,t){let n=zn(e);return n?t===n||t.endsWith(`.${n}`)?`domain`:void 0:Rn(e)===t?`hostname`:void 0}function Vn(e,t){return t.flatMap(t=>{let n=t.stream.webStreamData?.defaultUri;if(!n)return[];let r=Rn(n);if(!r)return[];let i=Bn(e,r);return i?[{...t,defaultUri:n,hostname:r,match:i}]:[]})}function Hn(e,t){let n=e.stream.displayName??e.defaultUri,r=e.match===`domain`?`is inside`:`matches`;return`Matched GA4 web stream ${n} (${e.defaultUri}): its hostname ${r} ${t}.`}function Un(e){let t=e-Date.now();if(t<=0)return`expired`;let n=Math.round(t/6e4);if(n<60)return`in ${n}m`;let r=Math.floor(n/60),i=n%60;return i?`in ${r}h ${i}m`:`in ${r}h`}function T(e,t){return{type:`boolean`,default:!0,description:e,negativeDescription:t}}function E(e){return typeof e==`string`?e:void 0}function D(e){let t=E(e.project),n=E(e.client);if(t&&n&&t!==n)throw Error(`Use either --project or --client, not both.`);return t??n}function O(e){return typeof e==`boolean`?e:void 0}function Wn(e){let t=E(e.rendering);if(t!==void 0&&t!==`auto`&&t!==`on`&&t!==`off`)throw new v(`INVALID_INPUT`,`--rendering must be auto, on, or off.`);if(O(e.js)===!0&&t&&t!==`on`)throw new v(`INVALID_INPUT`,`Use either --js or --rendering on, not both with different modes.`);return t??(O(e.js)?`on`:`auto`)}function Gn(e,t){let n=O(e[t]);if(n===!1)return!0;if(n===!0)return!1;let r=O(e[Kn(t)]);if(r===!1)return!0;if(r===!0)return!1;if(O(e[`no-${t}`])===!0||O(e[`no${qn(t)}`])===!0)return!0}function Kn(e){return e.replace(/-([a-z])/g,(e,t)=>t.toUpperCase())}function qn(e){let t=Kn(e);return t?`${t[0]?.toUpperCase()}${t.slice(1)}`:``}function k(e){if(typeof e==`number`)return e;if(typeof e!=`string`||!e.trim())return;let t=Number(e);return Number.isFinite(t)?t:void 0}function A(e,t){if(e===void 0||e===``)return;let n=k(e);if(n===void 0)throw new v(`INVALID_INPUT`,`${t} must be a number.`);return n}function j(e){if(typeof e!=`string`)return;let t=e.split(`,`).map(e=>e.trim()).filter(Boolean);return t.length?t:void 0}function M(e){return j(e)??[]}function N(e){let t={concurrency:k(e[`fetch-concurrency`]),intervalCap:k(e[`fetch-interval-cap`]),intervalMs:k(e[`fetch-interval-ms`])};return Object.values(t).some(e=>e!==void 0)?t:void 0}function Jn(e){let t={concurrency:A(e[`fetch-concurrency`],`--fetch-concurrency`),intervalCap:A(e[`fetch-interval-cap`],`--fetch-interval-cap`),intervalMs:A(e[`fetch-interval-ms`],`--fetch-interval-ms`)};return Object.values(t).some(e=>e!==void 0)?t:void 0}function P(e){return e.json===!0}async function Yn(e,t){let n=E(e),r=E(t);if(n&&r)throw Error(`Use either --body or --body-file, not both.`);if(!n&&!r)return;let i=n??await pn(r??``,`utf8`),a=JSON.parse(i);if(!a||typeof a!=`object`||Array.isArray(a))throw Error(`JSON body must be an object.`);return a}const Xn=kn.createColors(!!(process.stdout.isTTY&&!process.env.NO_COLOR));function Zn(e){process.env.CI||process.env.NO_UPDATE_NOTIFIER||An({pkg:e,shouldNotifyInNpmScript:!1}).notify()}function F(e={}){return!!(!e.json&&process.stdin.isTTY&&process.stdout.isTTY&&!process.env.CI)}function I(e){process.stdout.write(`${JSON.stringify(e,null,2)}\n`)}function L(e){let t=Math.max(...e.map(([e])=>e.length),0);for(let[n,r]of e)process.stdout.write(`${Xn.bold(n.padEnd(t))} ${r}\n`)}function R(e,t){let n=new On({head:e});for(let e of t)n.push(e);process.stdout.write(`${n.toString()}\n`)}function Qn(e){return e<1024?`${e} B`:e<1024*1024?`${(e/1024).toFixed(1)} KB`:`${(e/(1024*1024)).toFixed(1)} MB`}function z(e){return typeof e==`symbol`&&process.exit(1),e}function $n(e,t){let n=t.trim().toLowerCase();return!n||e.some(e=>e.toLowerCase().includes(n))}async function er(e){let t=e.choices;if(e.choices.length>8){let n=z(await Dn({message:e.searchMessage,placeholder:`Type part of the name or URL, or leave blank`}));if(t=e.choices.filter(t=>$n(e.searchValues(t),n)),!t.length)throw new v(`INVALID_INPUT`,e.emptyMessage)}return z(await En({message:e.message,options:t.map(t=>{let n=e.hint?.(t);return{value:t,label:e.label(t),...n?{hint:n}:{}}})}))}async function B(e){if(e.site)return e.site;let t=Dt(),n=!!t.defaultSite&&(t.clients.some(e=>e.siteUrl===t.defaultSite)||t.sites.some(e=>e.siteUrl===t.defaultSite));if(t.defaultSite&&n)return t.defaultSite;if(e.options?.json||!F())throw new v(`INVALID_INPUT`,t.defaultSite?`Saved default site ${t.defaultSite} does not match any saved project. Pass --site or --project, or run \`seo start\` to save a new default.`:"No site selected. Pass --site, use --project on supported commands, or run `seo start` in a terminal.");let r=await ye(e.options?.refresh);if(!r.length)throw new v(`PROPERTY_NOT_FOUND`,`No Search Console properties found for this Google login.`);return r.length===1?r[0]?.siteUrl??``:(await er({message:`Choose a Search Console property`,searchMessage:`Search Search Console properties`,emptyMessage:`No Search Console properties matched that search.`,choices:r,label:e=>e.siteUrl,hint:e=>e.permissionLevel,searchValues:e=>[e.siteUrl,e.permissionLevel??``]})).siteUrl}async function V(e){if(e.client&&e.project&&e.client!==e.project)throw new v(`INVALID_INPUT`,`Use either --project or --client, not both.`);let t=e.project??e.client;if(t){let e=Ke(t);if(!e)throw new v(`INVALID_INPUT`,`Project not found: ${t}`);return{client:e,site:e.siteUrl}}let n=Ke();return!e.site&&n?{client:n,site:n.siteUrl}:{site:await B({site:e.site,options:e.options})}}async function tr(e){if(e.client&&e.project&&e.client!==e.project)throw new v(`INVALID_INPUT`,`Use either --project or --client, not both.`);let t=e.project??e.client;if(t){let e=Ke(t);if(!e)throw new v(`INVALID_INPUT`,`Project not found: ${t}`);return e}let n=Xe();if(!n.length)return;let r=Ke();if(r)return r;if(!(e.options?.json||!F()))return er({message:`Choose a project`,searchMessage:`Search projects`,emptyMessage:`No projects matched that search.`,choices:n,label:e=>e.name,hint:e=>e.siteUrl,searchValues:e=>[e.id,e.name,e.siteUrl]})}async function nr(e){if(e.property)return e.property;let t=Dt();if(t.google.defaultGa4PropertyId)return t.google.defaultGa4PropertyId;if(e.options?.json||!F())throw new v(`INVALID_INPUT`,`No GA4 property selected. Pass --property or run this command in a terminal to choose one.`);let n=(await ke()).flatMap(e=>e.propertySummaries.map(t=>({property:_(t.property),displayName:t.displayName??t.property,account:e.displayName??e.account})));if(!n.length)throw new v(`PROPERTY_NOT_FOUND`,`No GA4 properties found for this Google login.`);return n.length===1?n[0]?.property??``:(await er({message:`Choose a GA4 property`,searchMessage:`Search GA4 properties`,emptyMessage:`No GA4 properties matched that search.`,choices:n,label:e=>`${e.displayName} (${e.property})`,hint:e=>e.account,searchValues:e=>[e.property,e.displayName,e.account]})).property}function H(e){return Math.round(e).toLocaleString(`en-GB`)}function U(e){return e.toFixed(1)}function W(e){return`${(e*100).toFixed(1)}%`}function G(e,t=72){return e.length<=t?e:`${e.slice(0,t-3)}...`}function rr(e){return e.verification?.requested?`${e.verification.verified} checked, ${e.verification.failed} failed`:`off`}function K(e,t){R(e,t.slice(0,25)),t.length>25&&process.stdout.write(`Showing 25 of ${t.length}. Use --json for full data.\n`)}function q(e,t,n=5){let r=t.filter(e=>e.action.trim().length>0).slice(0,n);if(r.length){process.stdout.write(`\n${e}\n`);for(let[e,t]of r.entries()){let n=t.context?` (${t.context})`:``;process.stdout.write(`${e+1}. ${t.label}${n}: ${t.action}\n`)}t.length>r.length&&process.stdout.write(`Showing ${r.length} of ${t.length}. Use --json for full data.\n`)}}function ir(e){process.stdout.write(`\nNext: ${e}\n`)}function J(e,t){let n=t.filter(e=>e.trim().length>0);if(n.length){process.stdout.write(`\n${e}\n`);for(let e of n)process.stdout.write(`- ${e}\n`)}}async function ar(e,t={}){let n=E(e[`report-id`]);if(n){let e=s(n);if(!e)throw Error(`No saved crawl report found for ${n}.`);return e}let r=D(e),i=E(e.site),a=r||i?await V({client:r,site:i,options:t}):void 0,o=It(a?.site);if(!o)throw Error(a?.site?`No saved crawl report found for ${a.site}. Run \`seo crawl --project ${a.client?.id??a.site} --save\` first.`:"No saved crawl report found. Run `seo crawl <url> --save` first, or pass --report-id.");return o}function or(e){process.stdout.write(`AI-search evidence for ${e.url}\n\n`),L([[`Assessment`,`evidence only`],[`Data`,e.dataStatus],[`Report`,e.reportId]]),process.stdout.write(`\n${e.headline}\n`),e.topActions.length&&(process.stdout.write(`
4
4
  Top actions
5
5
  `),R([`Status`,`Check`,`Action`],e.topActions.map(e=>[e.status,e.title,G(e.action,96)]))),e.botAccess.length&&(process.stdout.write(`
6
6
  Bot access
@@ -47,7 +47,7 @@ Control comparison
47
47
  `),L([[`Control`,e.control.change.title],[`Adjusted clicks`,e.control.adjusted.clickDelta===null?`n/a`:H(e.control.adjusted.clickDelta)],[`Adjusted click %`,e.control.adjusted.clickPctPoints===null?`n/a`:`${e.control.adjusted.clickPctPoints.toFixed(1)} pts`],[`Note`,e.control.note]])),J(`Warnings`,e.warnings),J(`Caveats`,e.caveats)}const ra=S({meta:{name:`tests`,description:`Create and report local SEO tests from GSC and GA4 data`},subCommands:{list:S({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=P(e),n=D(e),r=m({site:(n||E(e.site)?await V({client:n,site:E(e.site),options:{json:t}}):void 0)?.site,limit:k(e.limit)});if(t){I({tests:r});return}R([`ID`,`Date`,`Scope`,`Target`,`Title`],r.map(e=>[e.id,e.changedAt,e.scope,e.target,e.title]))}}),create:S({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=P(e),n=await V({client:D(e),site:E(e.site),options:{json:t}}),r=ie({site:n.site,scope:Xi(e.scope),target:E(e.target)??``,title:E(e.title)??``,description:E(e.description),changedAt:E(e.date)??Yi()});if(t){I(r);return}L([[`ID`,r.id],[`Date`,r.changedAt],[`Project`,n.client?.name??n.site],[`Scope`,r.scope],[`Target`,r.target],[`Title`,r.title]])}}),report:S({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:`GA4 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 local GSC and GA4 cache.`},json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}},run:async({args:e})=>{let t=P(e),n=E(e.id),r=!n||D(e)||E(e.site)?await V({client:D(e),site:E(e.site),options:{json:t,refresh:O(e.refresh)}}):void 0,i=await oe({id:n,site:n?void 0:r?.site,scope:n?void 0:Zi(e.scope),target:n?void 0:E(e.target),title:E(e.title),changedAt:n?void 0:E(e.date),ga4PropertyId:E(e.property)??r?.client?.ga4PropertyId,controlScope:Zi(e[`control-scope`]),controlTarget:E(e[`control-target`]),controlTitle:E(e[`control-title`]),beforeDays:A(e.before,`--before`),afterDays:A(e.after,`--after`),refresh:O(e.refresh)});if(t){I(i);return}na(i)}})}});function ia(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 aa={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.`}},oa={...X([`includeBrand`,`verifyContent`,`verifyLimit`,`js`,`fetchConcurrency`,`fetchIntervalCap`,`fetchIntervalMs`,`refresh`]),json:{type:`boolean`,default:!1,description:`Print machine-readable JSON.`}};function sa(e){return{includeBrand:O(e[`include-brand`]),verifyContent:O(e[`verify-content`]),verifyLimit:k(e[`verify-limit`])??3,js:O(e.js)?!0:void 0,rate:N(e),refresh:O(e.refresh)}}function ca(e){return{client:D(e),site:E(e.site),refresh:O(e.refresh)}}const{json:la,...ua}=oa,da=ua,fa={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 pa(e){return{type:`string`,description:`Output folder. Defaults to ./seo-export/<project>-${e}-YYYY-MM-DD.`}}function ma(e){return e.toLowerCase().replace(/^sc-domain:/,``).replace(/^https?:\/\//,``).replace(/[^a-z0-9._-]+/g,`-`).replace(/^-+|-+$/g,``)}function ha(e){let t=new Date().toISOString().slice(0,10),n=ma(e.clientId??e.site);return nn(process.cwd(),`seo-export`,`${n}-${e.report}-${t}`)}async function ga(e,t){await dn(e,{recursive:!0});let n=[];for(let r of t){let t=nn(e,r.filename);await _n(t,Le(r.rows,r.headers),`utf8`),n.push(t)}return n}function _a(t,n){process.stdout.write(`Wrote ${e(n.length,`CSV file`)} to ${t}\n`);for(let e of n)process.stdout.write(`- ${e}\n`)}const va=S({meta:{name:`diagnose`,description:`Export diagnosis tables: priorities, anomalies, movement, decay, cannibalisation, striking-distance, and quick wins.`},args:{...fa,out:pa(`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:D(e),site:E(e.site)}),n=await Ee({site:t.site,days:k(e.days),recentDays:k(e.recent),limit:k(e.limit),brandTerms:t.client?.brandTerms,includeBrand:O(e[`include-brand`]),verifyContent:O(e[`verify-content`]),verifyLimit:k(e[`verify-limit`]),js:O(e.js)?!0:`auto`,rate:N(e),refresh:O(e.refresh),progress:ia(!0)}),r=E(e.out)??ha({report:`diagnose`,clientId:t.client?.id,site:t.site});_a(r,await ga(r,yt(n)))}}),ya=`.seo-okf.json`,ba=`${JSON.stringify({generator:`seo`,format:`okf`,version:1})}\n`;function xa(e,t){let n=nn(e,t),r=tn(nn(e),n);if(!r||r.startsWith(`..`)||$t(r))throw new v(`INVALID_INPUT`,`Unsafe OKF file path: ${t}`);return n}async function Sa(e,t){for(let n of t){let t=xa(e,n.path);await dn(b(t),{recursive:!0}),await _n(t,n.content)}await _n(x(e,ya),ba)}async function Ca(e){let t=[];async function n(e,r=``){let i=await mn(e,{withFileTypes:!0});for(let a of i){let i=r?`${r}/${a.name}`:a.name,o=x(e,a.name);a.isDirectory()?await n(o,i):a.isFile()&&a.name.endsWith(`.md`)&&t.push({path:i,content:await pn(o,`utf8`)})}}return await n(e),t.sort((e,t)=>e.path<t.path?-1:+(e.path>t.path))}async function wa(e){try{let t=await un(e);if(t.isSymbolicLink()||!t.isDirectory())throw new v(`INVALID_INPUT`,`OKF output must be a real directory: ${e}`);if(!(await mn(e)).length)return!0;try{if(await pn(x(e,ya),`utf8`)===ba)return!0}catch{}throw new v(`INVALID_INPUT`,`Refusing to replace non-empty unmanaged directory: ${e}`)}catch(e){if(e.code===`ENOENT`)return!1;throw e}}async function Ta(e,t){if(!it(t).valid)throw new v(`INVALID_INPUT`,`Generated bundle did not pass seo OKF checks and was not written.`);let n=nn(e),r=b(n);await dn(r,{recursive:!0});let i=await wa(n),a=await fn(x(r,`.${Qt(n)}-staging-`)),o=x(r,`.${Qt(n)}-backup-${an()}`),s=!1;try{await Sa(a,t);let e=it(await Ca(a));if(!e.valid)throw new v(`INTERNAL_ERROR`,`Staged OKF files failed validation after writing.`);return i&&(await hn(n,o),s=!0),await hn(a,n),s&&await gn(o,{recursive:!0,force:!0}),e}catch(e){throw await gn(a,{recursive:!0,force:!0}),s&&(await gn(n,{recursive:!0,force:!0}),await hn(o,n)),e}}async function Ea(e,t){if(!e){process.stdout.write(t);return}await dn(b(e),{recursive:!0}),await _n(e,t),process.stdout.write(`Wrote ${e}\n`)}function Da(e){let t=E(e)??`okf`;if([`okf`,`markdown`,`json`].includes(t))return t;throw Error(`Format must be one of: okf, markdown, json.`)}function Oa(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:ct(e),entityReadiness:r(e),llmsTxt:l(e)}}function ka(e){let t=Oa(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(`
48
48
  `)}\n`}const Aa=S({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=P(e),n=Da(e.format),r=n===`okf`?wt(A(e[`max-concepts`],`--max-concepts`)):void 0,i=await ar(e,{json:t}),a=E(e.output);if(n===`okf`){let e=Ot(i,{maxConcepts:r}),o=a??`./okf`,s=await Ta(o,e.files);if(t){I({format:n,output:o,bundle:e,validation:s});return}process.stdout.write(`Wrote OKF bundle to ${o}\n\n`),L([[`Files`,String(e.files.length)],[`Concepts`,String(e.conceptCount)],[`Valid`,s.valid?`yes`:`no`]]);return}if(n===`json`){await Ea(a,`${JSON.stringify(Oa(i),null,2)}\n`);return}await Ea(a,ka(i))}}),ja=S({meta:{name:`monthly`,description:`Export monthly report tables plus the underlying diagnosis detail CSVs.`},args:{...fa,out:pa(`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.`}}),...da},run:async({args:e})=>{let t=await V({client:D(e),site:E(e.site),options:{refresh:O(e.refresh)}}),n=await be({site:t.site,month:E(e.month),limit:k(e.limit),brandTerms:t.client?.brandTerms,...sa(e),progress:ia(!0)}),r=E(e.out)??ha({report:`monthly`,clientId:t.client?.id,site:t.site});_a(r,await ga(r,pt(n)))}}),Ma=S({meta:{name:`narrative`,description:`Export report narrative tables plus the underlying diagnosis detail CSVs.`},args:{...fa,out:pa(`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.`},...da},run:async({args:e})=>{let t=await V({client:D(e),site:E(e.site),options:{refresh:O(e.refresh)}}),n=await o({site:t.site,days:k(e.days),recentDays:k(e.recent),startDate:E(e[`start-date`]),endDate:E(e[`end-date`]),limit:k(e.limit),changeLimit:k(e[`change-limit`]),brandTerms:t.client?.brandTerms,...sa(e),progress:ia(!0)}),r=E(e.out)??ha({report:`narrative`,clientId:t.client?.id,site:t.site});_a(r,await ga(r,pt(n)))}});function Na(e){return Se({days:A(e.days,`--days`),sitemaps:j(e.sitemap),maxSitemapUrls:A(e[`max-sitemap-urls`],`--max-sitemap-urls`),templateLimit:A(e.limit,`--limit`),minimumTemplateUrls:A(e[`minimum-template-urls`],`--minimum-template-urls`),minimumTemplateShare:A(e[`minimum-template-share`],`--minimum-template-share`),minimumTemplateImpressions:A(e[`minimum-template-impressions`],`--minimum-template-impressions`),crawlSamples:A(e[`crawl-samples`],`--crawl-samples`),inspectSamples:A(e[`inspect-samples`],`--inspect-samples`),brandTerms:j(e[`brand-terms`]),includeBrand:O(e[`include-brand`]),js:O(e.js)?!0:`auto`,refresh:O(e.refresh),rate:Jn(e)})}const Pa=S({meta:{name:`pseo`,description:`Export pSEO audit tables: templates, demand patterns, top queries, sample coverage, crawl samples, and inspection samples.`},args:{...fa,out:pa(`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=Na(e),n=await V({client:D(e),site:E(e.site)}),r=await u({site:n.site,...t,brandTerms:t.brandTerms??n.client?.brandTerms,progress:ia(!0)}),i=E(e.out)??ha({report:`pseo`,clientId:n.client?.id,site:n.site});_a(i,await ga(i,tt(r)))}}),Fa=S({meta:{name:`refresh-priorities`,description:`Export the ranked SEO priority queue, score breakdowns, grouped findings, and diagnosis detail CSVs.`},args:{...fa,out:pa(`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.`}}),"ga4-property":{type:`string`,description:`GA4 property ID to use for analytics value. Defaults from the selected project.`},"verify-content":T(`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:D(e),site:E(e.site),options:{refresh:O(e.refresh)}}),n=await Ie({site:t.site,days:k(e.days),recentDays:k(e.recent),limit:k(e.limit),brandTerms:t.client?.brandTerms,includeBrand:O(e[`include-brand`]),ga4PropertyId:E(e[`ga4-property`])??t.client?.ga4PropertyId,verifyContent:O(e[`verify-content`]),verifyLimit:k(e[`verify-limit`]),refresh:O(e.refresh)}),r=E(e.out)??ha({report:`refresh-priorities`,clientId:t.client?.id,site:t.site});_a(r,await ga(r,$e(n)))}}),Ia=S({meta:{name:`export`,description:`Export full report data to CSV files`},subCommands:{diagnose:va,knowledge:Aa,narrative:Ma,monthly:ja,"update-postmortem":S({meta:{name:`update-postmortem`,description:`Export update postmortem attribution, findings, template movement, and segment movers.`},args:{...fa,out:pa(`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:D(e),site:E(e.site),options:{refresh:O(e.refresh)}}),n=await et({site:t.site,days:k(e.days),recentDays:k(e.recent),limit:k(e.limit),brandTerms:t.client?.brandTerms,includeBrand:O(e[`include-brand`]),knownConfounders:E(e[`known-change`])?[E(e[`known-change`])??``]:void 0,includeChangeLog:!O(e[`ignore-change-log`]),refresh:O(e.refresh)}),r=E(e.out)??ha({report:`update-postmortem`,clientId:t.client?.id,site:t.site});_a(r,await ga(r,Ln(n)))}}),"refresh-priorities":Fa,pseo:Pa}}),La=S({...ai,meta:{name:`init`,description:"Legacy alias for `seo start`."}});async function Ra(e,t){if(!e){process.stdout.write(t);return}await dn(b(e),{recursive:!0}),await _n(e,t),process.stdout.write(`Wrote ${e}\n`)}const za={"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.`}},Ba=S({meta:{name:`llms`,description:`Inspect or generate optional llms.txt from saved crawl data`},subCommands:{audit:S({meta:{name:`audit`,description:`Inspect optional llms.txt presence from a saved crawl`},args:za,run:async({args:e})=>{let t=P(e),n=l(await ar(e,{json:t}));if(t){I(n);return}process.stdout.write(`llms.txt audit for ${n.url}\n\n`),L([[`SEO impact`,n.googleSearchImpact],[`Optional`,n.optional?`yes`:`no`],[`Found`,n.exists?`yes`:`no`],[`URL`,n.llmsTxtUrl],[`Status`,n.status?String(n.status):`-`]]),process.stdout.write(`\n${n.headline}\n`),n.issues.length&&(process.stdout.write(`
49
49
  Issues
50
- `),R([`Severity`,`Issue`,`Action`],n.issues.map(e=>[e.severity,e.title,G(e.action,96)]))),J(`Recommended pages`,n.recommendedPages.slice(0,10).map(e=>`${e.section}: ${e.url}`))}}),generate:S({meta:{name:`generate`,description:`Generate an llms.txt draft from a saved crawl`},args:{...za,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=P(e),n=pe(await ar(e,{json:t}),{maxUrls:k(e[`max-urls`]),tokenBudget:k(e[`token-budget`]),exclude:j(e.exclude),title:E(e.title),description:E(e.description)});if(t){I(n);return}await Ra(E(e.output),n.content),E(e.output)&&L([[`URLs`,String(n.includedUrls)],[`Estimated tokens`,String(n.estimatedTokens)]])}})}}),Va=[{client:`claude-desktop`,flag:`claude-desktop`},{client:`claude-code`,flag:`claude-code`},{client:`codex`,flag:`codex`},{client:`cursor`,flag:`cursor`}];function Ha(e,t){return O(e.all)?t:Va.filter(({flag:t})=>O(e[t])).map(({client:e})=>e)}const Ua=S({meta:{name:`mcp`,description:`MCP server helpers`},subCommands:{serve:S({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-DUfbYI4E.js`);await t({test:O(e.test)})}}),install:S({meta:{name:`install`,description:`Install or remove SEO MCP config for local clients`},args:{uninstall:{type:`boolean`,default:!1,description:`Remove the SEO MCP config instead of adding it`},reinstall:{type:`boolean`,default:!1,description:`Rewrite an existing managed SEO MCP config`},"claude-desktop":{type:`boolean`,default:!1,description:`Update Claude Desktop`},"claude-code":{type:`boolean`,default:!1,description:`Update Claude Code`},codex:{type:`boolean`,default:!1,description:`Update Codex CLI, app, and IDE extension`},cursor:{type:`boolean`,default:!1,description:`Update Cursor`},all:{type:`boolean`,default:!1,description:`Update every detected MCP client`},json:{type:`boolean`,default:!1,description:`Print structured JSON output`}},run:async({args:e})=>{if(O(e.uninstall)&&O(e.reinstall))throw new v(`INVALID_INPUT`,`Use either --uninstall or --reinstall, not both.`);let t=xr(),n=wr({targets:t}),r=P(e),i=Ha(e,n.map(e=>e.client));if(i.length===0){if(!F({json:r}))throw new v(`INVALID_INPUT`,`Choose an MCP client with --claude-desktop, --claude-code, --codex, --cursor, or --all.`);let a=n.length>0?n:t;i=z(await Cn({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 v(`INVALID_INPUT`,`MCP setup is not supported for ${a.join(`, `)} on ${process.platform}.`);let o=t.filter(e=>i.includes(e.client)).map(t=>O(e.uninstall)?Ur(t):Hr(t,{reinstall:O(e.reinstall)}));if(r){I({operation:O(e.uninstall)?`uninstall`:`install`,results:o});return}for(let e of o)process.stdout.write(`${e.changed?`updated`:`skipped`} ${e.client} · ${e.path}\n`)}})}}),Wa=S({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 f({startUrl:E(e.url)??``,site:E(e.site),limit:k(e.limit),refresh:O(e.refresh),js:O(e.js)?!0:`auto`});if(P(e)){I(t);return}if(L([[`Run`,t.run.id],[`Start URL`,t.run.startUrl],[`Crawled`,String(t.summary.crawled)],[`Previous run`,t.previousRun?.id??`none`],[`Added`,String(t.summary.added)],[`Removed`,String(t.summary.removed)],[`Changed`,String(t.summary.changed)],[`New errors`,String(t.summary.newErrors)],[`Indexability flips`,String(t.summary.indexabilityFlips)],[`High-priority actions`,String(t.summary.highPriorityRecommendations)]]),t.recommendations.length&&(process.stdout.write(`
50
+ `),R([`Severity`,`Issue`,`Action`],n.issues.map(e=>[e.severity,e.title,G(e.action,96)]))),J(`Recommended pages`,n.recommendedPages.slice(0,10).map(e=>`${e.section}: ${e.url}`))}}),generate:S({meta:{name:`generate`,description:`Generate an llms.txt draft from a saved crawl`},args:{...za,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=P(e),n=pe(await ar(e,{json:t}),{maxUrls:k(e[`max-urls`]),tokenBudget:k(e[`token-budget`]),exclude:j(e.exclude),title:E(e.title),description:E(e.description)});if(t){I(n);return}await Ra(E(e.output),n.content),E(e.output)&&L([[`URLs`,String(n.includedUrls)],[`Estimated tokens`,String(n.estimatedTokens)]])}})}}),Va=[{client:`claude-desktop`,flag:`claude-desktop`},{client:`claude-code`,flag:`claude-code`},{client:`codex`,flag:`codex`},{client:`cursor`,flag:`cursor`}];function Ha(e,t){return O(e.all)?t:Va.filter(({flag:t})=>O(e[t])).map(({client:e})=>e)}const Ua=S({meta:{name:`mcp`,description:`MCP server helpers`},subCommands:{serve:S({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-BG734YzY.js`);await t({test:O(e.test)})}}),install:S({meta:{name:`install`,description:`Install or remove SEO MCP config for local clients`},args:{uninstall:{type:`boolean`,default:!1,description:`Remove the SEO MCP config instead of adding it`},reinstall:{type:`boolean`,default:!1,description:`Rewrite an existing managed SEO MCP config`},"claude-desktop":{type:`boolean`,default:!1,description:`Update Claude Desktop`},"claude-code":{type:`boolean`,default:!1,description:`Update Claude Code`},codex:{type:`boolean`,default:!1,description:`Update Codex CLI, app, and IDE extension`},cursor:{type:`boolean`,default:!1,description:`Update Cursor`},all:{type:`boolean`,default:!1,description:`Update every detected MCP client`},json:{type:`boolean`,default:!1,description:`Print structured JSON output`}},run:async({args:e})=>{if(O(e.uninstall)&&O(e.reinstall))throw new v(`INVALID_INPUT`,`Use either --uninstall or --reinstall, not both.`);let t=xr(),n=wr({targets:t}),r=P(e),i=Ha(e,n.map(e=>e.client));if(i.length===0){if(!F({json:r}))throw new v(`INVALID_INPUT`,`Choose an MCP client with --claude-desktop, --claude-code, --codex, --cursor, or --all.`);let a=n.length>0?n:t;i=z(await Cn({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 v(`INVALID_INPUT`,`MCP setup is not supported for ${a.join(`, `)} on ${process.platform}.`);let o=t.filter(e=>i.includes(e.client)).map(t=>O(e.uninstall)?Ur(t):Hr(t,{reinstall:O(e.reinstall)}));if(r){I({operation:O(e.uninstall)?`uninstall`:`install`,results:o});return}for(let e of o)process.stdout.write(`${e.changed?`updated`:`skipped`} ${e.client} · ${e.path}\n`)}})}}),Wa=S({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 f({startUrl:E(e.url)??``,site:E(e.site),limit:k(e.limit),refresh:O(e.refresh),js:O(e.js)?!0:`auto`});if(P(e)){I(t);return}if(L([[`Run`,t.run.id],[`Start URL`,t.run.startUrl],[`Crawled`,String(t.summary.crawled)],[`Previous run`,t.previousRun?.id??`none`],[`Added`,String(t.summary.added)],[`Removed`,String(t.summary.removed)],[`Changed`,String(t.summary.changed)],[`New errors`,String(t.summary.newErrors)],[`Indexability flips`,String(t.summary.indexabilityFlips)],[`High-priority actions`,String(t.summary.highPriorityRecommendations)]]),t.recommendations.length&&(process.stdout.write(`
51
51
  Recommended actions
52
52
  `),R([`Severity`,`Category`,`URL`,`Action`],t.recommendations.slice(0,10).map(e=>[e.severity,e.category,G(e.url,56),G(e.action,72)])),q(`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(`
53
53
  Changed URLs
@@ -1,3 +1,3 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire as __seoCreateRequire } from 'node:module'; const require = __seoCreateRequire(import.meta.url);
3
- import{s as e}from"./dist-CpTkpH4F.js";export{e as startMcpServer};
3
+ import{s as e}from"./dist-BnXs3Jen.js";export{e as startMcpServer};