seo 0.2.3 → 0.2.4

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.
@@ -708,6 +708,6 @@ CREATE INDEX IF NOT EXISTS idx_performance_reports_url ON performance_reports(ur
708
708
  `).trim()}async function rI(e){let t=await JP(e);return zF({workflow:`monthly-report`,site:e.site,summary:`Monthly report generated for ${t.month}. ${t.headline}`,steps:[{tool:`seo_monthly_report`,status:`completed`,summary:`Generated report for ${t.period.startDate} to ${t.period.endDate}.`}],actions:t.priorities,output:{report:t}})}const iI=new Set([`quick-win`,`striking-distance`]);function aI(e){return e?e.id.endsWith(`-salary`)?`salary-page`:e.id:`no-template`}function oI(e){if(!iI.has(e.source)||e.grouped)return;let t=aI(e.template);return[e.source,e.category,t,R_(e.title)].join(`|`)}function sI(e){if(!iI.has(e.source)||e.grouped)return;let t=aI(e.template);return[e.source,e.category,t,R_(e.target)].join(`|`)}function cI(e){let t=e[0];if(!t)return``;let n=new Set(e.map(e=>e.target)).size;return n<=1&&e.length<=1?t.action:n<=1?`${e.length} findings point at this URL. Start with the highest-impact issue first: ${t.action}`:`${n} URLs have the same issue. Apply this action across the affected pages: ${t.action}`}function lI(e){let t=e.reduce((e,t)=>e+t.impact,0),n=new Set(e.map(e=>e.action)).size,r=e.slice(0,3).map(e=>e.target).join(`, `),i=n>1?` ${n} action variants are preserved in grouped findings.`:``;return`Grouped ${e.length} matching findings; total impact ${t.toFixed(2)}.${i} Top targets: ${r}.`}function uI(e){let t=[...e].sort((e,t)=>t.score-e.score),n=t[0];if(!n)throw Error(`Cannot group an empty priority list.`);let r=t.reduce((e,t)=>e+t.impact,0),i=t.reduce((e,t)=>e+t.score,0),a=Number(Math.min(n.score*(1+Math.log1p(t.length-1)/8),300).toFixed(2));return{...n,score:a,impact:Number(r.toFixed(2)),action:cI(t),evidence:lI(t),grouped:{count:t.length,totalImpact:Number(r.toFixed(2)),totalScore:Number(i.toFixed(2)),findings:t.map(e=>({source:e.source,title:e.title,target:e.target,category:e.category,score:e.score,impact:e.impact,impactKind:e.impactKind,confidence:e.confidence,template:e.template,analytics:e.analytics,action:e.action,evidence:e.evidence}))}}}function dI(e,t){let n=new Map,r=[];for(let i of e){let e=t(i);if(!e){r.push(i);continue}let a=n.get(e)??[];a.push(i),n.set(e,a)}return[...r,...[...n.values()].flatMap(e=>e.length>1?[uI(e)]:e)]}function fI(e){return dI(dI(e,sI),oI).sort((e,t)=>t.score-e.score)}function pI(e){let t=e.templates?.find(t=>t.id===e.id);return t?.urlCount??t?.count}function mI(e,t){let n=RF({source:e.source,impact:e.impact,confidence:e.confidence,effort:e.effort,verification:e.verification,templateCount:e.template?.count,analyticsSessions:t?e.analytics?.sessions:void 0});return{...e,category:LF(e.source,e.verification),score:n.final,scoreBreakdown:n}}function hI(e){return e.groups.filter(e=>e.count>=3||e.totalClickLoss>=10).map(t=>({source:`decay`,title:`${t.label} cluster`,target:t.sampleUrls[0]??e.site,impact:t.totalClickLoss,impactKind:`observed_retained_query_clicks`,confidence:`medium`,effort:t.diagnosis===`lost_ctr`?`S`:`M`,template:{id:t.template.id,label:t.template.label,count:t.count},action:t.recommendation,evidence:`${t.count} matching query/page rows retained in both windows had ${t.totalClickLoss.toFixed(0)} fewer clicks. Examples: ${t.sampleQueries.slice(0,3).join(`; `)}.`}))}async function gI(e){let t=await xk({...e,verifyContent:e.verifyContent??!0,verifyLimit:e.verifyLimit??Math.min(e.limit??5,5)}),n=H_(e.days??28),r=await kC({propertyId:e.ga4PropertyId,startDate:n.startDate,endDate:n.endDate}),i=OC({propertyId:e.ga4PropertyId,source:r.source,warning:r.warning}),a=i.warnings,o=[],s=Math.max(e.limit??25,25);for(let e of t.strikingDistance.items){let n=pI({templates:t.strikingDistance.templates,id:e.template.id}),i=EC(r.values,e.url);o.push({source:`striking-distance`,title:e.query,target:e.url,impact:e.priority.score,impactKind:`heuristic_priority_score`,confidence:e.recommendation.confidence,verification:e.contentVerification?.classification,template:{id:e.template.id,label:e.template.label,count:n??1},analytics:i,action:e.recommendation.action,evidence:e.recommendation.evidence})}for(let e of t.quickWins.items.slice(0,s)){let n=pI({templates:t.quickWins.templates,id:e.template.id}),i=EC(r.values,e.url);o.push({source:`quick-win`,title:e.query,target:e.url,impact:Number(e.estimatedCtrClickShortfall.toFixed(2)),impactKind:`heuristic_ctr_click_shortfall`,confidence:e.recommendation.confidence,effort:e.recommendation.effort,verification:e.contentVerification?.classification,template:{id:e.template.id,label:e.template.label,count:n??1},analytics:i,action:e.recommendation.action,evidence:e.recommendation.evidenceRef})}for(let e of t.decay.items){let n=pI({templates:t.decay.templates,id:e.template.id}),i=EC(r.values,e.url);o.push({source:`decay`,title:e.query,target:e.url,impact:e.clickLoss,impactKind:`observed_retained_query_clicks`,confidence:e.recommendation.confidence,effort:e.recommendation.effort,template:{id:e.template.id,label:e.template.label,count:n??1},analytics:i,action:e.recommendation.action,evidence:e.recommendation.evidenceRef})}o.push(...hI({groups:t.decay.groups,site:e.site}));for(let e of t.cannibalization.items)o.push({source:`cannibalization`,title:e.query,target:e.suggestedOwnerUrl,impact:e.priority.score,impactKind:`heuristic_multi_url_exposure`,confidence:e.recommendation.confidence,effort:e.recommendation.effort,template:e.template?{id:e.template.id,label:e.template.label,count:e.pages.length}:void 0,action:e.recommendation.action,evidence:e.recommendation.evidenceRef});for(let n of t.priorities)o.push({source:`diagnosis`,title:n.label,target:e.site,impact:n.confidence===`high`?150:n.confidence===`medium`?75:25,impactKind:`ordinal`,confidence:n.confidence,action:n.action,evidence:n.reason});for(let e of t.quickWins.templateRecommendations)o.push({source:`template`,title:`${e.templateLabel} opportunity template`,target:`${e.templateLabel} template (${e.urlCount} URLs)`,impact:e.totalEstimatedCtrClickShortfall,impactKind:`heuristic_ctr_click_shortfall`,confidence:`medium`,effort:`M`,template:{id:e.templateId,label:e.templateLabel,count:e.urlCount},action:e.action,evidence:e.evidence});let c=fI(o.map(e=>mI(e,i.canRank)).filter(e=>e.score>0).sort((e,t)=>t.score-e.score)).slice(0,e.limit??25);return zF({workflow:`refresh-priorities`,site:e.site,summary:`${c.length} ranked SEO priorities refreshed.`,steps:[{tool:`seo_diagnose_property`,status:`completed`,summary:`Combined diagnosis, decay, striking-distance, quick-win, and cannibalisation signals.`}],actions:c.slice(0,5).map(e=>({title:e.title,action:e.action,confidence:e.confidence})),output:{queue:c,warnings:a,diagnosis:t,analyticsSource:r.source,analyticsRankingEligible:i.canRank,analyticsRankingApplied:c.some(e=>e.scoreBreakdown.analytics>1)}})}function _I(e){return{inspected:e?.summary.inspected??0,alerts:e?.summary.alerts??0,currentIssues:e?.summary.currentIssues??0,failed:e?.summary.failed??0,quotaBlocked:e?.summary.quotaBlocked??0,deferred:e?.summary.deferred??0}}async function vI(e){let t=e.recoverLinks??!0,n=!!(e.urls?.length||e.sitemaps?.length);if(!e.startUrl&&!n&&!t)throw Error(`Pass startUrl, urls, sitemaps, or enable link recovery for technical-watch.`);let[r,i,a]=await Promise.all([e.startUrl?GA({site:e.site,startUrl:e.startUrl,limit:e.limit,refresh:e.refresh,js:e.js}):void 0,e.sitemaps?.length?rM({site:e.site,sitemaps:e.sitemaps,properties:e.properties,dailyLimit:e.dailyLimit,inspectLimit:e.inspectLimit,maxUrls:e.maxUrls,languageCode:e.languageCode,refresh:e.refresh}):e.urls?.length?Zj({site:e.site,urls:e.urls,languageCode:e.languageCode,dailyLimit:e.dailyLimit}):void 0,t?hM({site:e.site,days:e.recoverDays,limit:e.recoverLimit??10,minClicks:e.recoverMinClicks,minImpressions:e.recoverMinImpressions,refresh:e.refresh,js:e.js}):void 0]),o=_I(i),s=(r?.summary.highPriorityRecommendations??0)+o.currentIssues+(a?.summary.high??0)+(a?.summary.medium??0),c=o.failed+o.quotaBlocked+o.deferred,l=[];return a?.items[0]&&l.push({title:`Recover search-value URL`,action:a.items[0].recommendation.action,confidence:a.items[0].recommendation.confidence}),s>0&&l.push({title:`Triage technical findings`,action:`Prioritize current index issues, crawl actions, and recoverable search-value URLs before content work.`,confidence:`high`}),c>0&&l.push({title:`Complete URL Inspection coverage`,action:`Retry failed or deferred URL Inspection checks after resolving access, provider, or quota errors. Do not treat incomplete checks as SEO defects.`,confidence:`high`}),l.length||l.push({title:`No material technical finding`,action:`Keep the saved crawl and index snapshots; repeat the recovery check on the next run.`,confidence:`medium`}),zF({workflow:`technical-watch`,site:e.site,summary:`${Z(s,`material technical finding`)}; ${Z(c,`incomplete URL Inspection check`)}.`,steps:[{tool:`seo_crawl_diff`,status:r?`completed`:`skipped`,summary:r?`Crawled ${Z(r.summary.crawled,`URL`)}; ${r.summary.changed} changed, ${Z(r.summary.highPriorityRecommendations,`high-priority crawl action`)}.`:`No start URL passed.`},{tool:e.sitemaps?.length?`seo_index_monitor`:`seo_index_watch`,status:i?`completed`:`skipped`,summary:i?`Inspected ${Z(o.inspected,`selected URL`)}; ${Z(o.currentIssues,`current review in selected results`)}, ${Z(o.alerts,`new alert`)}, ${Z(o.failed,`failed check`)}, ${o.quotaBlocked+o.deferred} quota-blocked or deferred.`:`No inspection URLs or sitemaps passed.`},{tool:`seo_link_recover`,status:a?`completed`:`skipped`,summary:a?`Checked ${Z(a.summary.checked,`search-value URL`)}; ${Z(a.summary.recoverable,`recoverable issue`)}, ${a.summary.high} high severity.`:`Link recovery disabled.`}],actions:l,output:{crawl:r,index:i,recovery:a}})}function yI(e,t){return e<t?-1:+(e>t)}function bI(e){let t=e.items.filter(e=>e.clickDelta>0).sort((e,t)=>t.clickDelta-e.clickDelta||yI(e.key,t.key)),n=e.items.filter(e=>e.clickDelta<0).sort((e,t)=>e.clickDelta-t.clickDelta||yI(e.key,t.key)),r=t.length+n.length;return{dataStatus:e.dataStatus,coverage:{evidenceScope:`matched-retained-segments`,eligibleRows:e.selection.matchedRows,returnedRows:e.selection.returnedRows,limitedRows:e.selection.limitedRows,resultLimit:e.filters.limit,sourceRowLimit:e.filters.maxRows,movedRows:r,unchangedRows:Math.max(0,e.selection.returnedRows-r),sourcePossiblyTruncated:e.source.before.possiblyTruncated||e.source.after.possiblyTruncated},summary:e.summary,unmatchedSegments:e.unmatchedSegments,warnings:e.warnings,winners:t,losers:n}}function xI(e){return Math.abs(e).toLocaleString(`en-GB`)}function SI(e){return`${Math.round(e*100)}%`}function CI(e,t){return e<t?-1:+(e>t)}function wI(e){return`${e.returnedRows} of ${e.eligibleRows} eligible matched retained rows returned under result limit ${e.resultLimit}${e.sourcePossiblyTruncated?`; at least one source window reached the ${e.sourceRowLimit}-row source limit`:``}`}function TI(e){return e.clickDelta>0?`gained`:e.clickDelta<0?`lost`:`held flat`}function EI(e,t){return e===`page`?t.key.replace(/^https?:\/\//,``):t.key}function DI(e,t){if(t.dataStatus===`unavailable`||t.dataStatus===`empty`)return{dimension:e,dataStatus:t.dataStatus,coverage:t.coverage,unmatchedRows:t.summary.unmatchedRows,summary:`${e}: no matched retained segment movement was available. ${t.summary.verdict} Scope: ${wI(t.coverage)}.`};let n=t.dataStatus===`partial`?` Evidence is partial; ${t.summary.unmatchedRows} one-window rows were not treated as zero.`:``,r=` Scope: ${wI(t.coverage)}.`,i=t.winners[0],a=t.losers[0];return i&&a?{dimension:e,dataStatus:t.dataStatus,coverage:t.coverage,unmatchedRows:t.summary.unmatchedRows,winner:i,loser:a,summary:`${e}: top winner ${EI(e,i)} ${TI(i)} ${xI(i.clickDelta)} clicks; top loser ${EI(e,a)} ${TI(a)} ${xI(a.clickDelta)} clicks.${n}${r}`}:i?{dimension:e,dataStatus:t.dataStatus,coverage:t.coverage,unmatchedRows:t.summary.unmatchedRows,winner:i,summary:`${e}: top winner ${EI(e,i)} ${TI(i)} ${xI(i.clickDelta)} clicks; no material loser appeared in the returned segment rows.${n}${r}`}:a?{dimension:e,dataStatus:t.dataStatus,coverage:t.coverage,unmatchedRows:t.summary.unmatchedRows,loser:a,summary:`${e}: top loser ${EI(e,a)} ${TI(a)} ${xI(a.clickDelta)} clicks; no material winner appeared in the returned segment rows.${n}${r}`}:{dimension:e,dataStatus:t.dataStatus,coverage:t.coverage,unmatchedRows:t.summary.unmatchedRows,summary:`${e}: no material winner or loser appeared in the returned matched retained segment rows.${n}${r}`}}function OI(e){let t=e.insights.find(e=>e.dimension===`page`),n=e.insights.find(e=>e.dimension===`query`);return`Cause not established; ${t?.dataStatus===`empty`||t?.dataStatus===`unavailable`?`page segment evidence unavailable`:t?.winner?`top returned page gained ${xI(t.winner.clickDelta)} clicks`:t?.loser?`top returned page lost ${xI(t.loser.clickDelta)} clicks`:`no material page movement found in returned rows`}; ${n?.dataStatus===`empty`||n?.dataStatus===`unavailable`?`query segment evidence unavailable`:n?.winner?`top returned query gained ${xI(n.winner.clickDelta)} clicks`:n?.loser?`top returned query lost ${xI(n.loser.clickDelta)} clicks`:`no material query movement found in returned rows`}.`}function kI(e){let t=[],n=e.segments.page.winners.length,r=e.segments.page.losers.length,i=e.segments.query.winners.length,a=e.segments.query.losers.length,o=e.templateMovement[0];return o&&t.push({title:`${o.direction===`winner`?`Protect`:`Fix`} ${o.signature}`,action:o.direction===`winner`?`${o.signature} is a repeated winning URL pattern across ${o.urlCount} URLs in the returned matched-retained page subset. Preserve the shared title/H1/content/internal-link pattern before making broad edits.`:`${o.signature} is a repeated losing URL pattern across ${o.urlCount} URLs in the returned matched-retained page subset. Check indexability, canonical/robots state, internal links, and whether the shared template still matches search intent.`,confidence:o.confidence}),e.update.confounders.length>0?t.push({title:`Test overlapping site changes first`,action:`Start with the known overlapping changes, then compare changed sections against unchanged sections. Keep the Google update window as timing context, not a cause.`,confidence:`medium`}):e.update.classification===`significant-movement-with-update-overlap`?t.push({title:`Map movement by template`,action:`Group the top page and query movers into templates or content types. Preserve winning patterns and investigate losing templates, but do not treat timing overlap as proof of an update effect.`,confidence:`medium`}):t.push({title:`Treat this as exploratory`,action:`Use the segment splits as a triage view, but do not over-attribute the movement to a Google update yet.`,confidence:`low`}),n>0&&r===0&&t.push({title:`Protect the winning pages`,action:`The returned page split contains positive movers and no negative movers. Check those pages for common query intent, title/H1 pattern, internal links, and template shape before making broad edits.`,confidence:`medium`}),(r>0||a>0)&&t.push({title:`Triage the largest losers first`,action:`For losing pages and queries, check whether impressions dropped, average position worsened, or CTR fell. That separates ranking/index loss from snippet or SERP layout issues.`,confidence:`medium`}),i>0&&a===0&&t.push({title:`Expand winning query angles`,action:`The returned query split contains positive movers and no negative movers. Use that query wording to improve internal anchors and supporting copy on related pages.`,confidence:`medium`}),t}function AI(e,t){return e.filter(e=>t===`winner`?e.clickDelta>0:e.clickDelta<0)}function jI(e){let t=new Map;for(let n of e.items){let r=tj(n.key,e.clusters);if(r===`/`)continue;let i=e.clusters.find(e=>e.signature===r),a=t.get(r)??{cluster:i,signature:r,items:[],clickDelta:0,impressionDelta:0};a.items.push(n),a.clickDelta+=n.clickDelta,a.impressionDelta+=n.impressionDelta,t.set(r,a)}return[...t.values()]}function MI(e){return e?[...new Set(e.shape.variableSegments.flatMap(e=>e.tokenExamples))].slice(0,6):[]}function NI(e){return jI({items:AI(e.items,e.direction),clusters:e.clusters}).map(t=>{let n=Math.abs(t.clickDelta),r=e.totalMovement?n/e.totalMovement:0;if(t.items.length<3||r<.2)return;let i=t.items.length>=10&&r>=.35&&e.segmentCoverage.limitedRows===0&&!e.segmentCoverage.sourcePossiblyTruncated?`high`:`medium`,a=e.direction===`winner`?`gained`:`lost`,o=MI(t.cluster),s=o.length?` Common URL terms: ${o.join(`, `)}.`:``;return{signature:t.signature,direction:e.direction,confidence:i,urlCount:t.items.length,clickDelta:Number(t.clickDelta.toFixed(3)),impressionDelta:Number(t.impressionDelta.toFixed(3)),movementShare:Number(r.toFixed(3)),movementShareScope:`returned-directional-page-click-movement`,confidenceScope:`pattern-within-returned-matched-retained-page-segments`,segmentCoverage:e.segmentCoverage,commonTerms:o,sampleUrls:t.items.slice(0,5).map(e=>e.key),summary:`${t.signature} ${a} ${xI(t.clickDelta)} clicks across ${t.items.length} moved URLs (${SI(r)} of returned ${e.direction} page click movement). Coverage: ${e.segmentCoverage.returnedRows} of ${e.segmentCoverage.eligibleRows} eligible matched retained page rows returned under result limit ${e.segmentCoverage.resultLimit}.${s}`}}).filter(e=>!!e).sort((e,t)=>Math.abs(t.clickDelta)-Math.abs(e.clickDelta)||t.urlCount-e.urlCount||CI(e.signature,t.signature))}function PI(e){let t=[...e.winners,...e.losers].filter(e=>e.key.startsWith(`http`)).filter(e=>Math.abs(e.clickDelta)>0).sort((e,t)=>Math.abs(t.clickDelta)-Math.abs(e.clickDelta)||CI(e.key,t.key));if(t.length<6)return[];let n=rj(t.map(e=>e.key),{minUrls:3,minShare:.15,limit:20}).filter(e=>e.signature!==`/`&&e.urlCount>=3);if(!n.length)return[];let r=e.winners.reduce((e,t)=>e+Math.max(0,t.clickDelta),0),i=Math.abs(e.losers.reduce((e,t)=>e+Math.min(0,t.clickDelta),0));return[...NI({direction:`winner`,items:t,clusters:n,totalMovement:r,segmentCoverage:e.coverage}),...NI({direction:`loser`,items:t,clusters:n,totalMovement:i,segmentCoverage:e.coverage})].slice(0,6)}async function FI(e,t={}){let n=e.limit??20,r=t.updateCorrelation??lk,i=t.segmentImpact??qE,[a,o,s,c,l]=await Promise.all([r(e),i({...e,dimension:`page`,limit:n}),i({...e,dimension:`query`,limit:n}),i({...e,dimension:`device`,limit:n}),i({...e,dimension:`country`,limit:n})]),u={page:bI(o),query:bI(s),device:bI(c),country:bI(l)},d=[DI(`page`,u.page),DI(`query`,u.query),DI(`device`,u.device),DI(`country`,u.country)],f=PI(u.page),p=[u.page,u.query,u.device,u.country].every(e=>e.dataStatus===`empty`||e.dataStatus===`unavailable`)?`skipped`:`completed`;return zF({workflow:`update-postmortem`,site:e.site,summary:OI({update:a,insights:d}),steps:[{tool:`seo_update_correlate`,status:`completed`,summary:`Cause not established. ${a.summary}`},{tool:`seo_segment_impact`,status:p,summary:d.map(e=>e.summary).join(` `)}],actions:kI({update:a,segments:u,templateMovement:f}),output:{update:a,insights:d,templateMovement:f,segments:u}})}function II(e){return e.toLowerCase().replace(/^https?:\/\//,``).replace(/^sc-domain:/,``).replace(/[^a-z0-9]+/g,`-`).replace(/^-+|-+$/g,``).slice(0,60)}function LI(e=[]){return[...new Set(e.map(e=>e.trim()).filter(Boolean))]}function RI(e){let t=II(e.id??e.name??e.siteUrl);if(!t)throw Error(`Client id could not be derived.`);return t}function zI(){return[...$e().clients].sort((e,t)=>e.name.localeCompare(t.name))}function BI(e){let t=$e().clients;if(!e)return t.find(e=>e.isDefault);let n=e.toLowerCase();return t.find(e=>e.id.toLowerCase()===n||e.name.toLowerCase()===n)}function VI(e){let t=$e(),n=RI(e),r=t.clients.find(e=>e.id===n),i=Date.now(),a={id:n,name:e.name??r?.name??n,siteUrl:e.siteUrl,startUrl:e.startUrl??r?.startUrl,watchUrls:LI(e.watchUrls??r?.watchUrls),brandTerms:LI(e.brandTerms??r?.brandTerms??A_({id:n,name:e.name??r?.name,siteUrl:e.siteUrl})),ga4PropertyId:e.ga4PropertyId??r?.ga4PropertyId,reportDay:e.reportDay??r?.reportDay,technicalWeekday:e.technicalWeekday??r?.technicalWeekday,isDefault:e.isDefault??r?.isDefault,createdAt:r?.createdAt??i,updatedAt:i},o=t.clients.filter(e=>e.id!==n);if(a.isDefault){for(let e of o)e.isDefault=!1;t.defaultSite=a.siteUrl}return t.clients=[...o,a],et(t),a}function HI(e){let t=$e(),n=BI(e);return n?(t.clients=t.clients.filter(e=>e.id!==n.id),t.defaultSite===n.siteUrl&&!t.clients.some(e=>e.siteUrl===n.siteUrl)&&!t.sites.some(e=>e.siteUrl===n.siteUrl)&&(t.defaultSite=void 0),et(t),!0):!1}function UI(e){let t=$e(),n=BI(e);if(!n)throw Error(`Client not found: ${e}`);return t.clients=t.clients.map(e=>({...e,isDefault:e.id===n.id})),t.defaultSite=n.siteUrl,et(t),{...n,isDefault:!0}}const WI=[`https://www.googleapis.com/auth/webmasters.readonly`,`https://www.googleapis.com/auth/analytics.readonly`];async function GI(){let e=Pe(),t=$e(),n=vt(),r=jt(),a=await ct(),o=pt(),s=a?!!bt(a.client_source):void 0,c=[];c.push({id:`config-dir`,label:`Config directory`,status:i(e.configDir)?`pass`:`warn`,detail:e.configDir,fix:i(e.configDir)?void 0:"Run `seo start` or `seo auth setup-client`."}),c.push({id:`oauth-client`,label:`Google credentials`,status:r.configured?`pass`:s===!1||r.error?`fail`:n.sharedConfigured||n.byoConfigured?`pass`:`fail`,detail:r.configured?`Service account configured for ${r.identity}.`:s===!1?`Stored Google login uses the ${a?.client_source===`shared`?`shared seo app`:`BYO client`}, but that client is not configured.`:r.error?r.error:n.sharedConfigured?`Shared client configured.`:n.byoConfigured?`BYO client configured at ${e.oauthClientFile}.`:`No shared or BYO Google OAuth client configured.`,fix:r.configured?void 0:s===!1?a?.client_source===`shared`?"Reinstall `seo`. If the shared client is still missing, report it at https://github.com/iannuttall/seo/issues.":"Restore the same client with `seo auth setup-client`, or run `seo auth logout` before signing in with a different client.":r.error?"Set one valid service account credential source, then run `seo doctor` again.":n.sharedConfigured||n.byoConfigured?void 0:"Run `seo auth setup-client` or set SEO_GOOGLE_CLIENT_ID and SEO_GOOGLE_CLIENT_SECRET."}),c.push({id:`oauth-client-file`,label:`BYO OAuth file`,status:o?`pass`:`warn`,detail:o?e.oauthClientFile:`No BYO OAuth client file. This is fine if using a shared client.`}),c.push({id:`google-login`,label:`Google login`,status:r.configured||a?`pass`:`fail`,detail:r.configured?`Using service account ${r.identity}.`:a?`Signed in as ${a.account_email}.`:`No Google token found.`,fix:r.configured||a?void 0:"Run `seo auth login`, or configure a service account for CI."});let l=new Set(a?.scope.split(/\s+/).filter(Boolean)??[]);for(let e of WI)c.push({id:`scope:${e}`,label:e.endsWith(`webmasters.readonly`)?`GSC scope`:`GA4 scope`,status:r.configured||a&&l.has(e)?`pass`:a?`fail`:`warn`,detail:r.configured?`${e} requested by the service account.`:a?l.has(e)?e:`Missing ${e}.`:`Cannot inspect scopes until login is complete.`,fix:!r.configured&&a&&!l.has(e)?"Run `seo auth logout` then `seo auth login`.":void 0});return c.push({id:`default-site`,label:`Saved GSC property`,status:`pass`,detail:t.defaultSite??`No saved default. Human CLI commands will prompt; agents can pass --site.`}),c.push({id:`default-ga4`,label:`Saved GA4 property`,status:`pass`,detail:t.google.defaultGa4PropertyId??`No saved default. Human CLI commands will prompt; agents can pass --property.`}),{ok:c.every(e=>e.status!==`fail`),generatedAt:new Date().toISOString(),checks:c}}var KI=class{name=`dataforseo`;capabilities={overview:!0};getAuthHeader(){let e=$e(),t=e.providers.dataForSeoLogin,n=e.providers.dataForSeoPassword;if(!t||!n)throw Error(`DataForSEO credentials missing. Add login and password to config.json.`);return`Basic ${Buffer.from(`${t}:${n}`).toString(`base64`)}`}async keywordOverview(e,t={}){let r=await n(`https://api.dataforseo.com/v3/dataforseo_labs/google/keyword_overview/live`,{method:`POST`,headers:{authorization:this.getAuthHeader(),"content-type":`application/json`},body:JSON.stringify([{keyword:e,language_code:`en`,location_code:2840}])});if(!r.ok)throw Error(`DataForSEO request failed with ${r.status}.`);let i=await r.json(),a=i.tasks?.[0]?.result?.[0];return{data:{phrase:a?.keyword??e,volume:a?.search_volume,competition:a?.competition,cpc:a?.cpc,difficulty:a?.keyword_difficulty},usage:{provider:`DataForSEO`,units:1,unitLabel:`tasks`,estimatedUsd:i.cost,calls:1},warnings:t.database?[`DataForSEO overview currently ignores the database option.`]:void 0}}};function qI(e){return e.trim().split(`
709
709
  `).map(e=>e.split(`;`).map(e=>e.replace(/^"|"$/g,``).trim()))}function JI(e){return e/1e3*.05}async function YI(e,t,r,i,a,o=!1){let s=$e().providers.semrushApiKey;if(!s)throw Error(`Semrush API key missing. Add it to config.json first.`);let c=Object.fromEntries(Object.entries({...t,key:s,type:e}).filter(([,e])=>e!==void 0)),l=A(),u=sn([e,c]),d=l.prepare(`SELECT response_json, credits_used FROM semrush_cache WHERE endpoint = ? AND query_hash = ? AND expires_at > ?`).get(e,u,Date.now());if(!o&&d?.response_json)return{data:JSON.parse(d.response_json),usage:{provider:`Semrush`,units:d.credits_used??0,unitLabel:`units`,estimatedUsd:JI(d.credits_used??0),calls:1,cacheHits:1},cached:!0};let f=new URL(`https://api.semrush.com/`);f.search=new URLSearchParams(c).toString();let p=await n(f),m=await p.text();if(!p.ok||m.startsWith(`ERROR ::`))throw Error(m||`Semrush request failed with ${p.status}.`);let h=qI(m),g=r(h),_=Math.max(0,h.length-1)*a;return l.prepare(`INSERT OR REPLACE INTO semrush_cache
710
710
  (endpoint, query_hash, request_json, response_json, credits_used, fetched_at, expires_at)
711
- VALUES (?, ?, ?, ?, ?, ?, ?)`).run(e,u,JSON.stringify(c),JSON.stringify(g),_,Date.now(),Date.now()+i),{data:g,usage:{provider:`Semrush`,units:_,unitLabel:`units`,estimatedUsd:JI(_),calls:1}}}function XI(e){let[t,n]=e;if(!t||!n)return{phrase:``};let r=Object.fromEntries(t.map((e,t)=>[e,n[t]]));return{phrase:r.Ph??``,volume:Number(r.Nq??0)||void 0,cpc:Number(r.Cp??0)||void 0,competition:Number(r.Co??0)||void 0,difficulty:Number(r.Kd??0)||void 0,results:Number(r.Nr??0)||void 0}}function ZI(e){let[t,...n]=e;return t?n.map(e=>{let n=Object.fromEntries(t.map((t,n)=>[t,e[n]]));return{phrase:n.Ph??``,volume:Number(n.Nq??0)||void 0,difficulty:Number(n.Kd??0)||void 0,cpc:Number(n.Cp??0)||void 0,competition:Number(n.Co??0)||void 0,url:n.Ur,domain:n.Dn,position:Number(n.Po??0)||void 0}}):[]}var QI=class{name=`semrush`;capabilities={overview:!0,batchOverview:!0,related:!0,broadMatch:!0,questions:!0,difficulty:!0,urlKeywords:!0,domainKeywords:!0,maxBatchSize:100};async keywordOverview(e,t={}){return YI(`phrase_this`,{phrase:e,database:t.database??`us`,export_columns:`Ph,Nq,Cp,Co,Nr,Td,Kd`},XI,7*864e5,10,t.refresh)}async batchKeywordOverview(e,t={}){return YI(`phrase_these`,{phrase:e.join(`;`),database:t.database??`us`,export_columns:`Ph,Nq,Cp,Co,Nr,Td,Kd`},e=>ZI(e).map(e=>({...e,phrase:e.phrase})),7*864e5,10,t.refresh)}async relatedKeywords(e,t={}){return YI(`phrase_related`,{phrase:e,database:t.database??`us`,display_limit:20,export_columns:`Ph,Nq,Kd,Cp,Co`},ZI,14*864e5,40,t.refresh)}async questions(e,t={}){return YI(`phrase_questions`,{phrase:e,database:t.database??`us`,display_limit:20,export_columns:`Ph,Nq,Kd,Cp,Co`},ZI,14*864e5,40,t.refresh)}async keywordDifficulty(e,t={}){return YI(`phrase_kdi`,{phrase:e.join(`;`),database:t.database??`us`,export_columns:`Ph,Kd`},e=>ZI(e).map(e=>({phrase:e.phrase,kd:e.difficulty??0})),7*864e5,50,t.refresh)}async domainKeywords(e,t={}){return YI(`domain_organic`,{domain:e,database:t.database??`us`,display_limit:100,export_columns:`Ph,Nq,Cp,Co,Kd,Po,Ur,Dn`},ZI,7*864e5,10,t.refresh)}async urlKeywords(e,t={}){return YI(`url_organic`,{url:e,database:t.database??`us`,display_limit:100,export_columns:`Ph,Nq,Cp,Co,Kd,Po,Ur,Dn`},ZI,7*864e5,10,t.refresh)}};function $I(e){let t=$e(),n=e??t.providers.prefer;if(n===`authoritative`&&t.providers.semrushApiKey)return new QI;if(n===`cheap`&&t.providers.dataForSeoLogin&&t.providers.dataForSeoPassword)return new KI;if(t.providers.semrushApiKey)return new QI;if(t.providers.dataForSeoLogin&&t.providers.dataForSeoPassword)return new KI}new class{getGoogleOAuthClient(){let e=pt();if(e)return{clientId:e.clientId,clientSecret:e.clientSecret,source:`byo`};let t=$e(),n=process.env.SEO_GOOGLE_CLIENT_ID??process.env.GSC_CLIENT_ID??_t.clientId??t.auth.sharedClientId,r=process.env.SEO_GOOGLE_CLIENT_SECRET??process.env.GSC_CLIENT_SECRET??_t.clientSecret??t.auth.sharedClientSecret;if(!(!n||!r))return{clientId:n,clientSecret:r,source:`shared`}}readGoogleTokens(){return ct()}writeGoogleTokens(e){return lt(e)}deleteGoogleTokens(){return ut()}};const eL=`0.2.3`,tL=D.iso.date(`Use a YYYY-MM-DD date.`),nL=D.string().regex(/^\d{4}-(?:0[1-9]|1[0-2])$/,`Use a YYYY-MM month.`);function rL(e,t){return e===void 0?t:e}const iL={site:{mcp:`site`,schema:D.string()},days:{mcp:`days`,schema:D.number().optional()},recentDays:{mcp:`recentDays`,schema:D.number().optional()},range:{mcp:`range`,schema:D.number().optional()},limit:{mcp:`limit`,schema:D.number().optional()},checkLimit:{mcp:`checkLimit`,schema:D.number().optional()},minImpressions:{mcp:`minImpressions`,schema:D.number().optional()},includeBrand:{mcp:`includeBrand`,schema:D.boolean().optional()},verifyContent:{mcp:`verifyContent`,schema:D.boolean().optional()},verifyLimit:{mcp:`verifyLimit`,schema:D.number().optional()},js:{mcp:`js`,schema:D.boolean().optional()},fetchConcurrency:{mcp:`fetchConcurrency`,schema:D.number().optional()},fetchIntervalCap:{mcp:`fetchIntervalCap`,schema:D.number().optional()},fetchIntervalMs:{mcp:`fetchIntervalMs`,schema:D.number().optional()},refresh:{mcp:`refresh`,schema:D.boolean().optional()}};function aL(e){return Object.fromEntries(e.map(e=>{let t=iL[e];return[t.mcp,t.schema]}))}function oL(e){return JSON.stringify(e,null,2)}function Q(e){let t=Te(e);return{content:[{type:`text`,text:`Error: ${t.error.message}`}],structuredContent:t,isError:!0}}function $(e,t,n={}){return{content:[{type:`text`,text:n.markdown??e}],structuredContent:t}}const sL=D.union([tL,D.string().regex(/^(?:today|yesterday|\d+daysAgo)$/)]).describe(`Use YYYY-MM-DD or a GA4 relative date.`),cL={site:D.string().trim().min(1),days:D.number().int().min(1).max(548).optional(),startDate:tL.optional(),endDate:tL.optional(),limit:D.number().int().min(1).max(100).optional(),minImpressions:D.number().int().min(0).max(1e9).optional(),maxRows:D.number().int().min(1).max(5e4).optional(),brandTerms:D.array(D.string().trim().min(1)).max(100).optional(),includeBrand:D.boolean().optional(),refresh:D.boolean().optional()};function lL(e,t={}){e.registerTool(`seo_ai_referrals`,{description:`Find AI referral traffic detected in GA4. maxRows bounds retained provider rows; resultLimit bounds returned landing pages.`,inputSchema:{property:D.string().trim().min(1),startDate:sL.optional(),endDate:sL.optional(),maxRows:D.number().int().min(1).max(1e5).describe(`Maximum retained GA4 rows. If limit is also supplied, both values must match.`).optional(),limit:D.number().int().min(1).max(1e5).describe(`Legacy alias for maxRows. If maxRows is also supplied, both values must match.`).optional(),resultLimit:D.number().int().min(1).max(1e3).describe(`Maximum ranked landing pages returned. Defaults to 25.`).optional(),refresh:D.boolean().optional()}},async({property:e,startDate:n,endDate:r,maxRows:i,limit:a,resultLimit:o,refresh:s})=>{try{if(i!==void 0&&a!==void 0&&i!==a)throw new k(`INVALID_INPUT`,`maxRows and the legacy limit option must match when both are provided.`);let c=await(t.aiReferralsReport??Un)({property:e,startDate:n,endDate:r,maxRows:i,limit:a,resultLimit:o,refresh:s});return $(`${Z(c.summary.sessions,`AI referral session`)} detected across ${Z(c.summary.sources,`source`)}; evidence is ${c.dataStatus}.`,c)}catch(e){return Q(e)}}),e.registerTool(`seo_page_opportunities`,{description:`Find first-party traffic growth opportunities for one URL from GSC and page content`,inputSchema:{...aL([`site`,`days`,`limit`,`minImpressions`,`includeBrand`,`verifyContent`,`refresh`,`js`]),days:D.number().int().min(1).max(548).optional(),limit:D.number().int().min(1).max(100).optional(),minImpressions:D.number().int().positive().optional(),url:D.string().url()}},async({site:e,url:n,days:r,limit:i,minImpressions:a,includeBrand:o,verifyContent:s,refresh:c,js:l})=>{try{let u=await(t.pageOpportunitiesReport??dy)({site:e,url:n,days:r,limit:i,minImpressions:a,includeBrand:o,verifyContent:s,refresh:c,js:rL(l,`auto`)});return $(u.summary.verdict,u)}catch(e){return Q(e)}}),e.registerTool(`seo_content_optimization`,{description:`Build a content optimization brief for one URL from first-party GSC queries and page content checks`,inputSchema:{...aL([`site`,`days`,`limit`,`minImpressions`,`includeBrand`,`verifyContent`,`refresh`,`js`]),days:D.number().int().min(1).max(548).optional(),limit:D.number().int().min(1).max(100).optional(),minImpressions:D.number().int().positive().optional(),url:D.string().url()}},async({site:e,url:n,days:r,limit:i,minImpressions:a,includeBrand:o,verifyContent:s,refresh:c,js:l})=>{try{let u=await(t.contentOptimizationReport??xy)({site:e,url:n,days:r,limit:i,minImpressions:a,includeBrand:o,verifyContent:s,refresh:c,js:rL(l,`auto`)});return $(u.summary.verdict,u)}catch(e){return Q(e)}}),e.registerTool(`seo_performance_audit`,{description:`Run a local Lighthouse lab audit and optional device-specific CrUX field Core Web Vitals. Configure CrUX with SEO_CRUX_API_KEY.`,inputSchema:{url:D.string().url(),strategy:D.enum([`mobile`,`desktop`]).optional(),refresh:D.boolean().optional()}},async({url:e,strategy:t,refresh:n})=>{try{let r=await YM({url:e,strategy:t,refresh:n});return $(r.headline,r)}catch(e){return Q(e)}}),e.registerTool(`seo_to_ai_query`,{description:`Convert retained GSC queries into deterministic AI monitoring-prompt suggestions with source completeness and heuristic caveats`,inputSchema:cL},async({site:e,days:n,startDate:r,endDate:i,limit:a,minImpressions:o,maxRows:s,brandTerms:c,includeBrand:l,refresh:u})=>{try{let d=await(t.seoToAiQueryReport??kF)({site:e,days:n,startDate:r,endDate:i,limit:a,minImpressions:o,maxRows:s,brandTerms:c,includeBrand:l,refresh:u});return $(`${Z(d.summary.prompts,`monitoring-prompt suggestion`)} generated from ${Z(d.summary.returnedQueries,`retained GSC query`)}; evidence is ${d.dataStatus}.`,d)}catch(e){return Q(e)}}),e.registerTool(`seo_community_intent`,{description:`Classify explicit forum, review, comparison, experience, and recommendation language in retained GSC queries as review hypotheses`,inputSchema:cL},async({site:e,days:n,startDate:r,endDate:i,limit:a,minImpressions:o,maxRows:s,brandTerms:c,includeBrand:l,refresh:u})=>{try{let d=await(t.communityIntentReport??rv)({site:e,days:n,startDate:r,endDate:i,limit:a,minImpressions:o,maxRows:s,brandTerms:c,includeBrand:l,refresh:u});return $(`Evidence status: ${d.dataStatus}. ${d.summary.verdict}`,d)}catch(e){return Q(e)}})}function uL(e){if(!(e.fetchConcurrency===void 0&&e.fetchIntervalCap===void 0&&e.fetchIntervalMs===void 0))return{concurrency:e.fetchConcurrency,intervalCap:e.fetchIntervalCap,intervalMs:e.fetchIntervalMs}}function dL(e,t={}){let n=e.requestEvidenceStatus===`available`?`Processed ${e.requests.length} URL requests into ${e.summary.totalPages} unique documents.`:e.requestEvidenceStatus===`partial`?`Observed ${e.requests.length} URL requests and retained ${e.summary.totalPages} unique documents; some started requests were still in flight when the crawl stopped.`:`Loaded ${e.summary.totalPages} unique documents; request evidence is unavailable for this legacy report.`,r={id:e.id,definitionId:e.definitionId,headline:`${n} Found ${e.summary.highIssues} high, ${e.summary.mediumIssues} medium, and ${e.summary.lowIssues} low issues.`,status:e.status,requestEvidenceStatus:e.requestEvidenceStatus,configHash:e.configHash,summary:e.summary,dataSources:e.dataSources,ai:e.ai?{robotsTxt:e.ai.robotsTxt,llmsTxt:e.ai.llmsTxt,agentResources:e.ai.agentResources}:void 0,topFixes:Mw(e,{limit:10}),reviewObservations:Nw(e,{limit:10}),warnings:e.warnings,caveats:e.caveats};return t.includeIssues&&(r.issues=e.issues),t.includePages&&(r.requests=e.requests,r.pages=e.pages),r}function fL(e){if(!e.value||e.value===`latest`||e.value===`previous`){let t=vS({site:e.site,limit:20}).filter(t=>t.id!==e.skipId),n=e.value===`previous`?t[1]:t[0];return n?yS(n.id):void 0}return yS(e.value)}function pL(e,t){if(e&&t)throw new k(`INVALID_INPUT`,`Use either url or reportId, not both.`)}function mL(e){e.registerTool(`seo_crawl_site`,{description:`Crawl a site and run technical SEO checks. The compact result separates prioritised fixes from review observations. Set includePages/includeIssues for raw data.`,inputSchema:{url:D.string().url(),site:D.string().optional(),ga4PropertyId:D.string().optional(),maxPages:D.number().int().positive().optional(),maxDepth:D.number().int().nonnegative().optional(),concurrency:D.number().int().positive().optional(),fetchIntervalCap:D.number().int().positive().optional(),fetchIntervalMs:D.number().int().positive().optional(),refresh:D.boolean().optional(),include:D.array(D.string()).optional(),exclude:D.array(D.string()).optional(),useSitemap:D.boolean().optional(),respectRobots:D.boolean().optional(),js:D.boolean().optional(),includePages:D.boolean().optional(),includeIssues:D.boolean().optional(),saveReport:D.boolean().optional()}},async({url:e,site:t,ga4PropertyId:n,maxPages:r,maxDepth:i,concurrency:a,fetchIntervalCap:o,fetchIntervalMs:s,refresh:c,include:l,exclude:u,useSitemap:d,respectRobots:f,js:p,includePages:m,includeIssues:h,saveReport:g})=>{try{let _=await YC({url:e,site:t,ga4PropertyId:n,maxPages:r,maxDepth:i,concurrency:a,refresh:c,fetchRate:uL({fetchConcurrency:a,fetchIntervalCap:o,fetchIntervalMs:s}),include:l,exclude:u,useSitemap:d,respectRobots:f,js:!!p}),v=g?_S(_):void 0;return $(`Crawl complete for ${e}. Found ${_.issues.length} issues across ${_.summary.totalPages} pages.`,{...dL(_,{includePages:m,includeIssues:h}),...v?{saved:v}:{}})}catch(e){return Q(e)}}),e.registerTool(`seo_audit_urls`,{description:`Audit an explicit list of URLs with technical SEO checks. The compact result separates prioritised fixes from review observations. Set includePages/includeIssues for raw data.`,inputSchema:{urls:D.array(D.string().url()).min(1),site:D.string().optional(),ga4PropertyId:D.string().optional(),maxPages:D.number().int().positive().optional(),concurrency:D.number().int().positive().optional(),fetchIntervalCap:D.number().int().positive().optional(),fetchIntervalMs:D.number().int().positive().optional(),refresh:D.boolean().optional(),includePages:D.boolean().optional(),includeIssues:D.boolean().optional(),saveReport:D.boolean().optional()}},async({urls:e,site:t,ga4PropertyId:n,maxPages:r,concurrency:i,fetchIntervalCap:a,fetchIntervalMs:o,refresh:s,includePages:c,includeIssues:l,saveReport:u})=>{try{let d=await YC({url:e[0]??``,urls:e,mode:`list`,site:t,ga4PropertyId:n,maxPages:r??e.length,concurrency:i,refresh:s,fetchRate:uL({fetchConcurrency:i,fetchIntervalCap:a,fetchIntervalMs:o}),useSitemap:!1}),f=u?_S(d):void 0;return $(`URL audit complete. Found ${d.issues.length} issues across ${d.summary.totalPages} pages.`,{...dL(d,{includePages:c,includeIssues:l}),...f?{saved:f}:{}})}catch(e){return Q(e)}}),e.registerTool(`seo_top_fixes`,{description:`Return top technical SEO fixes for a URL by running a compact crawl.`,inputSchema:{url:D.string().url().optional(),reportId:D.string().optional(),site:D.string().optional(),maxPages:D.number().int().positive().optional(),maxDepth:D.number().int().nonnegative().optional(),fetchIntervalCap:D.number().int().positive().optional(),fetchIntervalMs:D.number().int().positive().optional(),refresh:D.boolean().optional(),category:D.string().optional(),limit:D.number().int().min(1).max(1e3).optional()}},async({url:e,reportId:t,site:n,maxPages:r,maxDepth:i,fetchIntervalCap:a,fetchIntervalMs:o,refresh:s,category:c,limit:l})=>{try{pL(e,t);let u=e?await YC({url:e,site:n,maxPages:r,maxDepth:i,refresh:s,fetchRate:uL({fetchIntervalCap:a,fetchIntervalMs:o})}):t?yS(t):xS(n);if(!u)return Q(`No crawl report found. Pass url, reportId, or run seo_crawl_site with saveReport first.`);let d=Mw(u,{category:c,limit:l});return $(`Found ${d.length} top fix groups for ${u.config.url}.`,{url:u.config.url,reportId:u.id,summary:u.summary,dataSources:u.dataSources,topFixes:d,warnings:u.warnings,caveats:u.caveats})}catch(e){return Q(e)}}),e.registerTool(`seo_explain_issue`,{description:`Explain a crawler rule in plain English: why it matters, how to fix it, and how to verify it.`,inputSchema:{ruleId:D.string()}},async({ruleId:e})=>{let t=tx(e);return t?$(`Rule guidance for ${e}.`,t):Q(`Unknown rule: ${e}`)}),e.registerTool(`seo_list_rules`,{description:`List crawler rule ids and guidance metadata. Use this before the explain-crawl-issue report when you need valid rule ids.`,inputSchema:{category:OT.optional()}},async({category:e})=>{let t=ex().filter(t=>!e||t.category===e);return $(`Found ${t.length} crawler rules.`,{rules:t})}),e.registerTool(`seo_affected_urls`,{description:`Return affected URLs for a saved or freshly crawled report, filtered by rule, category, or severity.`,inputSchema:{url:D.string().url().optional(),reportId:D.string().optional(),site:D.string().optional(),ruleId:D.string().optional(),category:D.string().optional(),severity:D.string().optional(),maxPages:D.number().int().positive().optional(),fetchIntervalCap:D.number().int().positive().optional(),fetchIntervalMs:D.number().int().positive().optional(),refresh:D.boolean().optional(),limit:D.number().int().positive().optional()}},async({url:e,reportId:t,site:n,ruleId:r,category:i,severity:a,maxPages:o,fetchIntervalCap:s,fetchIntervalMs:c,refresh:l,limit:u})=>{try{let d=e?await YC({url:e,site:n,maxPages:o,refresh:l,fetchRate:uL({fetchIntervalCap:s,fetchIntervalMs:c})}):t?yS(t):xS(n);if(!d)return Q(`No crawl report found. Pass url, reportId, or run seo_crawl_site with saveReport first.`);let f=Cy(d,{ruleId:r,category:i,severity:a,limit:u});return $(`Returned ${f.selection.returnedRows} of ${f.selection.totalMatchedRows} affected URLs for ${d.config.url}.`,{url:d.config.url,reportId:d.id,dataSources:d.dataSources,affectedUrls:f.affectedUrls,selection:f.selection,warnings:d.warnings,caveats:d.caveats})}catch(e){return Q(e)}}),e.registerTool(`seo_geo_gaps`,{description:`Return crawl, indexability, and page-level snippet controls for Google AI Search eligibility, with optional page observations kept separate. No detected restriction does not guarantee selection or visibility.`,inputSchema:{url:D.string().url().optional(),reportId:D.string().optional(),site:D.string().optional(),maxPages:D.number().int().positive().optional(),fetchIntervalCap:D.number().int().positive().optional(),fetchIntervalMs:D.number().int().positive().optional(),refresh:D.boolean().optional(),limit:D.number().int().positive().optional()}},async({url:e,reportId:t,site:n,maxPages:r,fetchIntervalCap:i,fetchIntervalMs:a,refresh:o,limit:s})=>{try{let c=e?await YC({url:e,site:n,maxPages:r,refresh:o,fetchRate:uL({fetchIntervalCap:i,fetchIntervalMs:a})}):t?yS(t):xS(n);if(!c)return Q(`No crawl report found. Pass url, reportId, or run seo_crawl_site with saveReport first.`);let l=wb(c,{limit:s});return $(`Returned ${l.selection.returnedPages} of ${l.selection.totalMatchedPages} AI Search technical eligibility gap pages for ${c.config.url}; evidence is ${l.dataStatus}.`,{url:c.config.url,reportId:c.id,...l})}catch(e){return Q(e)}}),e.registerTool(`seo_agent_readiness`,{description:`Check a content site for stable Markdown alternatives, agent discovery, crawler access, and identity evidence without an aggregate score.`,inputSchema:{url:D.string().url().optional(),reportId:D.string().optional(),site:D.string().optional(),maxPages:D.number().int().positive().optional(),fetchIntervalCap:D.number().int().positive().optional(),fetchIntervalMs:D.number().int().positive().optional(),refresh:D.boolean().optional()}},async({url:e,reportId:t,site:n,maxPages:r,fetchIntervalCap:i,fetchIntervalMs:a,refresh:o})=>{try{pL(e,t);let s=e?await YC({url:e,site:n,maxPages:r,refresh:o,checkAgentDiscovery:!0,useSitemap:!0,checkExternal:!1,fetchRate:uL({fetchIntervalCap:i,fetchIntervalMs:a})}):t?yS(t):xS(n);if(!s)return Q(`No crawl report found. Pass url, reportId, or run seo_crawl_site with saveReport first.`);let c=rb(s);return $(c.headline,c)}catch(e){return Q(e)}}),e.registerTool(`seo_ai_readiness`,{description:`Return AI-search technical evidence and optional observations without an aggregate score or citation prediction.`,inputSchema:{url:D.string().url().optional(),reportId:D.string().optional(),site:D.string().optional(),maxPages:D.number().int().positive().optional(),fetchIntervalCap:D.number().int().positive().optional(),fetchIntervalMs:D.number().int().positive().optional(),refresh:D.boolean().optional()}},async({url:e,reportId:t,site:n,maxPages:r,fetchIntervalCap:i,fetchIntervalMs:a,refresh:o})=>{try{let s=e?await YC({url:e,site:n,maxPages:r,refresh:o,fetchRate:uL({fetchIntervalCap:i,fetchIntervalMs:a})}):t?yS(t):xS(n);if(!s)return Q(`No crawl report found. Pass url, reportId, or run seo_crawl_site with saveReport first.`);let c=bb(s);return $(c.headline,c)}catch(e){return Q(e)}}),e.registerTool(`seo_ai_search_scorecard`,{description:`Score AI-search technical readiness 0-100 from one crawl using this tool's own weighted checks. The score is a heuristic summary, not a Google or AI-engine requirement, eligibility verdict, or ranking predictor. Unknown checks are excluded and a partial crawl cannot reach 100.`,inputSchema:{url:D.string().url().optional(),reportId:D.string().optional(),site:D.string().optional(),maxPages:D.number().int().positive().optional(),fetchIntervalCap:D.number().int().positive().optional(),fetchIntervalMs:D.number().int().positive().optional(),refresh:D.boolean().optional()}},async({url:e,reportId:t,site:n,maxPages:r,fetchIntervalCap:i,fetchIntervalMs:a,refresh:o})=>{try{let s=e?await YC({url:e,site:n,maxPages:r,refresh:o,fetchRate:uL({fetchIntervalCap:i,fetchIntervalMs:a})}):t?yS(t):xS(n);if(!s)return Q(`No crawl report found. Pass url, reportId, or run seo_crawl_site with saveReport first.`);let c=Xb(s);return $(c.headline,c)}catch(e){return Q(e)}}),e.registerTool(`seo_llms_txt_audit`,{description:`Inspect optional llms.txt presence from a saved or freshly crawled report and return current Google guidance plus candidate pages.`,inputSchema:{url:D.string().url().optional(),reportId:D.string().optional(),site:D.string().optional(),maxPages:D.number().int().positive().optional(),fetchIntervalCap:D.number().int().positive().optional(),fetchIntervalMs:D.number().int().positive().optional(),refresh:D.boolean().optional()}},async({url:e,reportId:t,site:n,maxPages:r,fetchIntervalCap:i,fetchIntervalMs:a,refresh:o})=>{try{let s=e?await YC({url:e,site:n,maxPages:r,refresh:o,fetchRate:uL({fetchIntervalCap:i,fetchIntervalMs:a})}):t?yS(t):xS(n);if(!s)return Q(`No crawl report found. Pass url, reportId, or run seo_crawl_site with saveReport first.`);let c=Mb(s);return $(c.headline,c)}catch(e){return Q(e)}}),e.registerTool(`seo_llms_txt_generate`,{description:`Generate an llms.txt draft from a saved or freshly crawled report. Returns content and metadata.`,inputSchema:{url:D.string().url().optional(),reportId:D.string().optional(),site:D.string().optional(),maxPages:D.number().int().positive().optional(),maxUrls:D.number().int().positive().optional(),tokenBudget:D.number().int().positive().optional(),exclude:D.array(D.string()).optional(),title:D.string().optional(),description:D.string().optional(),fetchIntervalCap:D.number().int().positive().optional(),fetchIntervalMs:D.number().int().positive().optional(),refresh:D.boolean().optional()}},async({url:e,reportId:t,site:n,maxPages:r,maxUrls:i,tokenBudget:a,exclude:o,title:s,description:c,fetchIntervalCap:l,fetchIntervalMs:u,refresh:d})=>{try{let f=e?await YC({url:e,site:n,maxPages:r,refresh:d,fetchRate:uL({fetchIntervalCap:l,fetchIntervalMs:u})}):t?yS(t):xS(n);if(!f)return Q(`No crawl report found. Pass url, reportId, or run seo_crawl_site with saveReport first.`);let p=Nb(f,{maxUrls:i,tokenBudget:a,exclude:o,title:s,description:c});return $(`Generated llms.txt with ${p.includedUrls} URLs.`,p)}catch(e){return Q(e)}}),e.registerTool(`seo_entity_readiness`,{description:`Check schema, sameAs, social profile, author/date, and naming signals from a saved or freshly crawled report.`,inputSchema:{url:D.string().url().optional(),reportId:D.string().optional(),site:D.string().optional(),maxPages:D.number().int().positive().optional(),fetchIntervalCap:D.number().int().positive().optional(),fetchIntervalMs:D.number().int().positive().optional(),refresh:D.boolean().optional()}},async({url:e,reportId:t,site:n,maxPages:r,fetchIntervalCap:i,fetchIntervalMs:a,refresh:o})=>{try{let s=e?await YC({url:e,site:n,maxPages:r,refresh:o,fetchRate:uL({fetchIntervalCap:i,fetchIntervalMs:a})}):t?yS(t):xS(n);if(!s)return Q(`No crawl report found. Pass url, reportId, or run seo_crawl_site with saveReport first.`);let c=fb(s);return $(c.headline,c)}catch(e){return Q(e)}}),e.registerTool(`seo_okf_build`,{description:`Build a compact OKF site knowledge manifest from a saved or freshly crawled report. Set includeFiles for bounded inline markdown.`,inputSchema:{url:D.string().url().optional(),reportId:D.string().optional(),site:D.string().optional(),maxPages:D.number().int().positive().optional(),maxConcepts:D.number().int().min(1).max(100).optional(),includeFiles:D.boolean().optional(),title:D.string().min(1).max(200).optional(),fetchIntervalCap:D.number().int().positive().optional(),fetchIntervalMs:D.number().int().positive().optional(),refresh:D.boolean().optional()}},async({url:e,reportId:t,site:n,maxPages:r,maxConcepts:i,includeFiles:a,title:o,fetchIntervalCap:s,fetchIntervalMs:c,refresh:l})=>{try{let u=e?await YC({url:e,site:n,maxPages:r,refresh:l,fetchRate:uL({fetchIntervalCap:s,fetchIntervalMs:c})}):t?yS(t):xS(n);if(!u)return Q(`No crawl report found. Pass url, reportId, or run seo_crawl_site with saveReport first.`);let d=_T(u,{maxConcepts:i??(a?25:100),title:o}),f=ST(d.files),{files:p,...m}=d;return $(`Built OKF bundle with ${d.conceptCount} concepts.`,{manifest:{...m,filePaths:p.map(e=>e.path)},...a?{files:p}:{},validation:f})}catch(e){return Q(e)}}),e.registerTool(`seo_okf_validate`,{description:`Validate OKF markdown files supplied by an agent. Use the okf-build report to generate files from a crawl.`,inputSchema:{files:D.array(D.object({path:D.string().min(1).max(512),content:D.string().max(2e6)})).min(1).max(5006)}},async({files:e})=>{let t=ST(e);return $(t.valid?`Bundle passes seo OKF checks.`:`Bundle has seo OKF issues.`,{validation:t,explanation:CT(t)})}),e.registerTool(`seo_get_crawl_report`,{description:`Return a saved crawl report by id, or the latest saved report for an optional site. Compact by default.`,inputSchema:{id:D.string().optional(),site:D.string().optional(),includePages:D.boolean().optional(),includeIssues:D.boolean().optional()}},async({id:e,site:t,includePages:n,includeIssues:r})=>{try{let i=e?yS(e):xS(t);return i?$(`Saved crawl report ${i.id}.`,{...dL(i,{includePages:n,includeIssues:r}),url:i.config.url,site:i.site,generatedAt:i.generatedAt}):Q(e?`No saved crawl report found for ${e}.`:`No saved crawl reports found.`)}catch(e){return Q(e)}}),e.registerTool(`seo_list_crawl_reports`,{description:`List saved crawl report metadata, optionally filtered by GSC site/property.`,inputSchema:{site:D.string().optional(),limit:D.number().int().positive().optional()}},async({site:e,limit:t})=>{try{let n=vS({site:e,limit:t});return $(`Found ${n.length} saved crawl reports.`,{reports:n})}catch(e){return Q(e)}}),e.registerTool(`seo_compare_crawl_reports`,{description:`Compare two saved crawl reports and return page, issue, score, and plain-English change summaries.`,inputSchema:{after:D.string().optional(),before:D.string().optional(),site:D.string().optional()}},async({after:e,before:t,site:n})=>{try{let r=fL({value:e??`latest`,site:n});if(!r)return Q(`No newer crawl report found.`);let i=fL({value:t??`previous`,site:r.site??n,skipId:r.id});if(!i)return Q(`No baseline crawl report found. Save at least two reports or pass before.`);let a=oT({before:i,after:r});return $(a.headline,a)}catch(e){return Q(e)}})}function hL(e,t={}){e.registerTool(`seo_doctor`,{description:`Check local auth, scopes, config, and defaults`,inputSchema:{}},async()=>{try{let e=await GI();return $(e.ok?`Local seo setup is ready.`:`Local seo setup needs attention.`,e)}catch(e){return Q(e)}}),e.registerTool(`seo_diagnose_property`,{description:`Run end-to-end property diagnosis across anomaly, update, segment, decay, cannibalisation, and opportunity signals`,inputSchema:{...aL([`site`,`days`,`recentDays`,`limit`,`includeBrand`,`refresh`])}},async({site:e,days:t,recentDays:n,limit:r,includeBrand:i,refresh:a})=>{try{let o=await xk({site:e,days:t,recentDays:n,limit:r,includeBrand:i,refresh:a});return $(`Diagnosis ${o.dataStatus}. Update attribution: ${o.summary.updateAttribution}.`,o)}catch(e){return Q(e)}}),e.registerTool(`seo_segment_impact`,{description:`Compare matched retained GSC segments across adjacent equal-length periods without treating missing rows as zero`,inputSchema:{site:D.string().trim().min(1),days:D.number().int().min(1).max(240).optional(),limit:D.number().int().min(1).max(100).optional(),refresh:D.boolean().optional(),dimension:D.enum([`page`,`query`,`country`,`device`]).optional(),compareDays:D.number().int().min(1).max(240).optional(),startDate:tL.optional(),endDate:tL.optional(),maxRows:D.number().int().min(1).max(25e4).optional(),unmatchedLimit:D.number().int().min(0).max(100).optional()}},async({site:e,dimension:n,days:r,compareDays:i,startDate:a,endDate:o,limit:s,maxRows:c,unmatchedLimit:l,refresh:u})=>{try{let d=await(t.segmentImpact??qE)({site:e,dimension:n,days:r,compareDays:i,startDate:a,endDate:o,limit:s,maxRows:c,unmatchedLimit:l,refresh:u});return $(d.summary.verdict,d)}catch(e){return Q(e)}}),e.registerTool(`seo_striking_distance`,{description:`Find position 11-20 query/page opportunities from GSC`,inputSchema:{...aL([`site`,`days`,`minImpressions`,`limit`,`verifyContent`,`verifyLimit`,`includeBrand`,`js`,`fetchConcurrency`,`fetchIntervalCap`,`fetchIntervalMs`,`refresh`]),days:D.number().int().min(1).max(548).optional(),minImpressions:D.number().int().min(0).max(1e9).optional(),limit:D.number().int().min(1).max(100).optional(),verifyLimit:D.number().int().min(0).max(100).optional(),brandTerms:D.array(D.string().trim().min(1).max(200)).max(20).optional(),fetchConcurrency:D.number().int().min(1).max(16).optional(),fetchIntervalCap:D.number().int().min(1).max(60).optional(),fetchIntervalMs:D.number().int().min(100).max(6e4).optional()}},async({site:e,days:t,minImpressions:n,limit:r,verifyContent:i,verifyLimit:a,includeBrand:o,brandTerms:s,js:c,fetchConcurrency:l,fetchIntervalCap:u,fetchIntervalMs:d,refresh:f})=>{try{let p=await jO({site:e,days:t,minImpressions:n,limit:r,verifyContent:i,verifyLimit:a,includeBrand:o,brandTerms:s,js:rL(c,void 0),rate:uL({fetchConcurrency:l,fetchIntervalCap:u,fetchIntervalMs:d}),refresh:f});return $(p.summary.verdict,p)}catch(e){return Q(e)}}),e.registerTool(`seo_traffic_anomaly`,{description:`Detect statistically significant recent GSC traffic movement`,inputSchema:{...aL([`site`,`days`,`recentDays`,`refresh`])}},async({site:e,days:t,recentDays:n,refresh:r})=>{try{let i=await ck({site:e,days:t,recentDays:n,refresh:r});return $(`${i.anomalies.filter(e=>e.significant).length} significant anomalies found.`,i)}catch(e){return Q(e)}}),e.registerTool(`seo_update_correlate`,{description:`Overlay recent traffic anomalies against official Google ranking update windows`,inputSchema:{...aL([`site`,`days`,`recentDays`,`refresh`]),paddingDays:D.number().optional()}},async({site:e,days:t,recentDays:n,paddingDays:r,refresh:i})=>{try{let a=await lk({site:e,days:t,recentDays:n,paddingDays:r,refresh:i});return $(`Classification: ${a.classification}.`,a)}catch(e){return Q(e)}})}function gL(e){e.registerTool(`seo_content_groups`,{description:`List, create, or delete reusable page/query groups`,inputSchema:{action:D.enum([`list`,`add`,`delete`]),site:D.string().optional(),id:D.string().optional(),name:D.string().optional(),dimension:D.enum([`page`,`query`]).optional(),matchType:D.enum([`equals`,`contains`,`regex`]).optional(),pattern:D.string().optional()}},async({action:e,site:t,id:n,name:r,dimension:i,matchType:a,pattern:o})=>{try{if(e===`list`){let e=Ck(t);return $(`${e.length} content groups found.`,{groups:e})}if(e===`delete`){if(!n)throw Error(`Pass id to delete a content group.`);let e=Tk(n);return $(e?`Content group deleted.`:`Not found.`,{id:n,deleted:e})}if(!t||!r||!o)throw Error(`Pass site, name, and pattern to add a content group.`);let s=Sk({site:t,name:r,dimension:i,matchType:a,pattern:o});return $(`Content group created: ${s.name}.`,s)}catch(e){return Q(e)}}),e.registerTool(`seo_change_log`,{description:`List or record SEO annotations and site changes`,inputSchema:{action:D.enum([`list`,`add`,`delete`]),site:D.string().optional(),id:D.string().optional(),limit:D.number().optional(),scope:D.enum([`site`,`page`,`query`,`group`]).optional(),target:D.string().optional(),title:D.string().optional(),description:D.string().optional(),changedAt:D.string().optional()}},async({action:e,site:t,id:n,limit:r,scope:i,target:a,title:o,description:s,changedAt:c})=>{try{if(e===`list`){let e=BO({site:t,limit:r});return $(`${e.length} changes found.`,{changes:e})}if(e===`delete`){if(!n)throw Error(`Pass id to delete a change.`);let e=HO(n);return $(e?`Change deleted.`:`Not found.`,{id:n,deleted:e})}if(!t||!i||!a||!o)throw Error(`Pass site, scope, target, and title to add a change.`);let l=zO({site:t,scope:i,target:a,title:o,description:s,changedAt:c??new Date().toISOString().slice(0,10)});return $(`Change recorded: ${l.title}.`,l)}catch(e){return Q(e)}}),e.registerTool(`seo_measure_change`,{description:`Measure a saved or ad hoc SEO change with equal finalized GSC windows and optional GA4/control evidence`,inputSchema:{id:D.string().min(1).optional(),site:D.string().min(1).optional(),scope:D.enum([`site`,`page`,`query`,`group`]).optional(),target:D.string().min(1).optional(),title:D.string().min(1).optional(),changedAt:tL.optional(),ga4PropertyId:D.string().min(1).optional(),controlScope:D.enum([`site`,`page`,`query`,`group`]).optional(),controlTarget:D.string().min(1).optional(),controlTitle:D.string().min(1).optional(),beforeDays:D.number().int().min(1).max(548).optional(),afterDays:D.number().int().min(1).max(548).optional(),refresh:D.boolean().optional()}},async({id:e,site:t,scope:n,target:r,title:i,changedAt:a,ga4PropertyId:o,controlScope:s,controlTarget:c,controlTitle:l,beforeDays:u,afterDays:d,refresh:f})=>{try{let p=await $k({id:e,site:t,scope:n,target:r,title:i,changedAt:a,ga4PropertyId:o,controlScope:s,controlTarget:c,controlTitle:l,beforeDays:u,afterDays:d,refresh:f});return $(`Measurement ${p.dataStatus}. Verdict: ${p.verdict}. Compared ${p.window.effectiveDays}/${p.window.requestedDays} finalized days.`,p)}catch(e){return Q(e)}})}function _L(e){e.registerTool(`seo_crawl_diff`,{description:`Crawl a bounded same-origin URL set and compare technical/page changes with the previous run`,inputSchema:{startUrl:D.string().url(),site:D.string().optional(),limit:D.number().optional(),refresh:D.boolean().optional(),js:D.boolean().optional()}},async({startUrl:e,site:t,limit:n,refresh:r,js:i})=>{try{let a=await GA({startUrl:e,site:t,limit:n,refresh:r,js:rL(i,`auto`)});return $(`Crawled ${a.summary.crawled} URLs. ${a.summary.changed} changed vs previous run.`,a)}catch(e){return Q(e)}}),e.registerTool(`seo_index_watch`,{description:`Inspect a bounded URL set against Google's indexed snapshot and separate current issues, regressions, recoveries, and operational failures`,inputSchema:{site:D.string(),urls:D.array(D.string().url()).min(1).max(100),languageCode:D.string().min(1).max(35).optional(),dailyLimit:D.number().int().min(1).max(2e3).optional()}},async({site:e,urls:t,languageCode:n,dailyLimit:r})=>{try{let i=await Zj({site:e,urls:t,languageCode:n,dailyLimit:r});return $(`Inspected ${i.summary.inspected} URLs. ${i.summary.currentIssues} current reviews, ${i.summary.regressions} regressions, ${i.summary.failed} failed, ${i.summary.quotaBlocked} quota-blocked, ${i.summary.deferred} deferred.`,i)}catch(e){return Q(e)}}),e.registerTool(`seo_index_coverage_plan`,{description:`Plan sitemap URL allocation across GSC properties and suggest URL-prefix properties for better URL Inspection coverage`,inputSchema:{site:D.string(),sitemaps:D.array(D.string().url()).min(1).max(20),properties:D.array(D.string()).optional(),dailyLimit:D.number().int().min(1).max(2e3).optional(),targetCycleDays:D.number().int().min(1).max(365).optional(),maxUrls:D.number().int().min(1).max(25e4).optional()}},async({site:e,sitemaps:t,properties:n,dailyLimit:r,targetCycleDays:i,maxUrls:a})=>{try{let o=await Fj({site:e,sitemaps:t,properties:n,dailyLimit:r,targetCycleDays:i,maxUrls:a});return $(`Planned ${o.summary.urlCount} sitemap URLs across ${o.summary.properties} properties. ${o.summary.suggestedProperties} suggested properties.`,o)}catch(e){return Q(e)}}),e.registerTool(`seo_index_coverage`,{description:`Compare a saved crawl and sitemap with Search Console page results, then choose representative URLs for URL Inspection`,inputSchema:{site:D.string().min(1),crawlReportId:D.string().min(1).optional(),sitemaps:D.array(D.string().url()).min(1).max(20).optional(),days:D.number().int().min(1).max(548).optional(),rowLimit:D.number().int().min(1).max(25e4).optional(),maxSitemapUrls:D.number().int().min(1).max(25e4).optional(),itemsPerSection:D.number().int().min(1).max(1e3).optional(),templateClusters:D.number().int().min(1).max(200).optional(),templateSamples:D.number().int().min(1).max(25).optional(),refresh:D.boolean().optional()}},async({site:e,crawlReportId:t,sitemaps:n,days:r,rowLimit:i,maxSitemapUrls:a,itemsPerSection:o,templateClusters:s,templateSamples:c,refresh:l})=>{try{let u=await Cj({site:e,crawlReportId:t,sitemaps:n,days:r,rowLimit:i,maxSitemapUrls:a,itemsPerSection:o,templateClusters:s,templateSamples:c,refresh:l});return $(`${u.summary.retainedSearchVisibleUrls} URLs appeared in the returned Search Console data. ${u.summary.crawlableCandidatesWithoutRetainedSearchVisibility} crawlable URLs need evidence review before choosing representative URL Inspection checks.`,u)}catch(e){return Q(e)}}),e.registerTool(`seo_index_monitor`,{description:`Run bounded, locally quota-enforced URL Inspection monitoring from XML sitemaps and store Google's indexed snapshots`,inputSchema:{site:D.string(),sitemaps:D.array(D.string().url()).min(1).max(20),properties:D.array(D.string()).optional(),dailyLimit:D.number().int().min(1).max(2e3).optional(),inspectLimit:D.number().int().min(1).max(100).optional(),maxUrls:D.number().int().min(1).max(25e4).optional(),languageCode:D.string().min(1).max(35).optional()}},async({site:e,sitemaps:t,properties:n,dailyLimit:r,inspectLimit:i,maxUrls:a,languageCode:o})=>{try{let s=await rM({site:e,sitemaps:t,properties:n,dailyLimit:r,inspectLimit:i,maxUrls:a,languageCode:o});return $(`Inventory ${s.summary.inventoryUrls}; ${s.summary.due} due, ${s.summary.selected} selected, ${s.summary.inspected} inspected, ${s.summary.unselectedDue} due but not selected. ${s.summary.currentIssues} current reviews in selected results, ${s.summary.regressions} regressions, ${s.summary.failed} failed, ${s.summary.quotaBlocked} quota-blocked, ${s.summary.deferred} deferred.`,s)}catch(e){return Q(e)}}),e.registerTool(`seo_redirect_trace`,{description:`Trace redirects and report final indexability/canonical issues for a URL`,inputSchema:{url:D.string().url(),maxHops:D.number().optional(),refresh:D.boolean().optional(),js:D.boolean().optional()}},async({url:e,maxHops:t,refresh:n,js:r})=>{try{let i=await uM({url:e,maxHops:t,refresh:n,js:rL(r,`auto`)});return $(`Traced ${i.summary.hops} hops. Final status ${i.summary.finalStatus}. ${i.summary.issues.length} issues.`,i)}catch(e){return Q(e)}}),e.registerTool(`seo_link_recover`,{description:`Find GSC search-value URLs that are now broken, blocked, or poorly redirected`,inputSchema:{...aL([`site`,`days`,`limit`,`minImpressions`,`refresh`,`js`]),minClicks:D.number().optional()}},async({site:e,days:t,limit:n,minClicks:r,minImpressions:i,refresh:a,js:o})=>{try{let s=await hM({site:e,days:t,limit:n,minClicks:r,minImpressions:i,refresh:a,js:rL(o,`auto`)});return $(`Checked ${s.summary.checked} search-value URLs. ${s.summary.recoverable} recoverable issues.`,s)}catch(e){return Q(e)}})}function vL(e,t={}){e.registerTool(`seo_cannibal`,{description:`Find multi-URL query exposure candidates for intent and technical review`,inputSchema:{...aL([`site`,`days`,`limit`,`minImpressions`,`includeBrand`,`refresh`]),days:D.number().int().min(1).max(548).optional(),limit:D.number().int().min(1).max(100).optional(),minImpressions:D.number().int().min(0).max(1e9).optional(),brandTerms:D.array(D.string().trim().min(1).max(200)).max(20).optional()}},async({site:e,days:t,limit:n,minImpressions:r,brandTerms:i,includeBrand:a,refresh:o})=>{try{let s=await TD({site:e,days:t,limit:n,minImpressions:r,brandTerms:i,includeBrand:a,refresh:o});return $(s.summary.verdict,s)}catch(e){return Q(e)}}),e.registerTool(`seo_decaying`,{description:`Find query/page click declines observed in both retained GSC windows and investigation signals`,inputSchema:{...aL([`site`,`days`,`limit`,`includeBrand`,`refresh`]),days:D.number().int().min(1).max(548).optional(),limit:D.number().int().min(1).max(100).optional(),comparison:D.enum([`previous-period`,`year-over-year`]).optional(),minDropPct:D.number().min(0).max(100).optional(),minPreviousClicks:D.number().min(0).max(1e9).optional(),minClickLoss:D.number().min(0).max(1e9).optional(),brandTerms:D.array(D.string().trim().min(1).max(200)).max(20).optional()}},async({site:e,days:t,limit:n,comparison:r,minDropPct:i,minPreviousClicks:a,minClickLoss:o,brandTerms:s,includeBrand:c,refresh:l})=>{try{let u=await VD({site:e,days:t,limit:n,comparison:r,minDropPct:i,minPreviousClicks:a,minClickLoss:o,brandTerms:s,includeBrand:c,refresh:l});return $(u.summary.verdict,u)}catch(e){return Q(e)}}),e.registerTool(`seo_quick_wins`,{description:`Find quick-win CTR/position opportunities`,inputSchema:{...aL([`site`,`days`,`limit`,`minImpressions`,`verifyContent`,`verifyLimit`,`includeBrand`,`js`,`fetchConcurrency`,`fetchIntervalCap`,`fetchIntervalMs`,`refresh`]),days:D.number().int().min(1).max(548).optional(),limit:D.number().int().min(1).max(100).optional(),minImpressions:D.number().int().min(0).max(1e9).optional(),verifyLimit:D.number().int().min(0).max(100).optional(),brandTerms:D.array(D.string().trim().min(1).max(200)).max(20).optional(),fetchConcurrency:D.number().int().min(1).max(16).optional(),fetchIntervalCap:D.number().int().min(1).max(60).optional(),fetchIntervalMs:D.number().int().min(100).max(6e4).optional()}},async({site:e,days:n,limit:r,minImpressions:i,brandTerms:a,verifyContent:o,verifyLimit:s,includeBrand:c,js:l,fetchConcurrency:u,fetchIntervalCap:d,fetchIntervalMs:f,refresh:p})=>{try{let m=await(t.quickWinsReport??dO)({site:e,days:n,limit:r,minImpressions:i,brandTerms:a,verifyContent:o,verifyLimit:s,includeBrand:c,js:rL(l,void 0),rate:uL({fetchConcurrency:u,fetchIntervalCap:d,fetchIntervalMs:f}),refresh:p});return $(m.summary.verdict,m)}catch(e){return Q(e)}}),e.registerTool(`seo_internal_links`,{description:`Find verified internal-link review candidates for a target URL`,inputSchema:{...aL([`site`,`days`,`limit`,`checkLimit`,`minImpressions`,`includeBrand`,`js`,`fetchConcurrency`,`fetchIntervalCap`,`fetchIntervalMs`,`refresh`]),targetUrl:D.string().url().refine(e=>/^https?:\/\//.test(e),`Use an HTTP(S) URL.`),days:D.number().int().min(1).max(548).optional(),limit:D.number().int().min(1).max(100).optional(),checkLimit:D.number().int().min(1).max(200).optional(),minImpressions:D.number().min(0).max(1e9).optional(),brandTerms:D.array(D.string().trim().min(1).max(200)).max(20).optional(),fetchConcurrency:D.number().int().min(1).max(16).optional(),fetchIntervalCap:D.number().int().min(1).max(60).optional(),fetchIntervalMs:D.number().int().min(100).max(6e4).optional()}},async({site:e,targetUrl:n,days:r,limit:i,checkLimit:a,minImpressions:o,brandTerms:s,includeBrand:c,js:l,fetchConcurrency:u,fetchIntervalCap:d,fetchIntervalMs:f,refresh:p})=>{try{let m=await(t.internalLinksReport??kA)({site:e,targetUrl:n,days:r,limit:i,checkLimit:a,minImpressions:o,brandTerms:s,includeBrand:c,js:rL(l,void 0),rate:uL({fetchConcurrency:u,fetchIntervalCap:d,fetchIntervalMs:f}),refresh:p});return $(m.summary.verdict,m)}catch(e){return Q(e)}}),e.registerTool(`seo_ctr_underperformers`,{description:`Find high-impression queries underperforming CTR expectations`,inputSchema:{...aL([`site`,`minImpressions`,`limit`,`includeBrand`,`refresh`]),site:D.string().trim().min(1),minImpressions:D.number().int().min(1).max(1e9).optional(),limit:D.number().int().min(1).max(100).optional(),brandTerms:D.array(D.string().trim().min(1).max(200)).max(20).optional()}},async({site:e,minImpressions:n,limit:r,brandTerms:i,includeBrand:a,refresh:o})=>{try{let s=await(t.ctrUnderperformersReport??gE)({site:e,minImpressions:n,limit:r,brandTerms:i,includeBrand:a,refresh:o});return $(s.summary.verdict,s)}catch(e){return Q(e)}}),e.registerTool(`seo_query_cluster`,{description:`Cluster queries by token overlap`,inputSchema:{...aL([`site`,`includeBrand`,`minImpressions`,`limit`,`refresh`]),site:D.string().trim().min(1),scope:D.string().trim().min(1).max(2e3).optional(),minImpressions:D.number().int().min(1).max(1e9).optional(),limit:D.number().int().min(1).max(100).optional(),brandTerms:D.array(D.string().trim().min(1).max(200)).max(20).optional()}},async({site:e,scope:n,brandTerms:r,includeBrand:i,minImpressions:a,limit:o,refresh:s})=>{try{let c=await(t.queryClusterReport??vP)({site:e,scope:n,brand:r?.[0],brandTerms:r,includeBrand:i,minImpressions:a,limit:o,refresh:s});return $(c.summary.verdict,c)}catch(e){return Q(e)}}),e.registerTool(`semrush_call`,{description:`Raw-ish Semrush passthrough for supported keyword endpoints`,inputSchema:{endpoint:D.enum([`phrase_this`,`phrase_related`,`phrase_questions`]),phrase:D.string()}},async({endpoint:e,phrase:t})=>{try{let n=$I(`authoritative`);if(!n)throw Error(`No keyword provider configured.`);let r=e===`phrase_this`?await n.keywordOverview(t):e===`phrase_related`?await n.relatedKeywords?.(t):await n.questions?.(t);if(!r)throw Error(`Endpoint ${e} is not supported by the active provider.`);return $(oL(r.data),r)}catch(e){return Q(e)}})}function yL(e){return{schemaVersion:e.schemaVersion,methodology:e.methodology,site:e.site,generatedAt:e.generatedAt,range:e.range,dataStatus:e.dataStatus,summary:e.summary,source:e.source,selection:e.selection,caveats:e.caveats,warnings:e.warnings,templates:e.templates.map(e=>({signature:e.signature,urlCount:e.urlCount,sampleUrls:e.sampleUrls,population:e.population,verdict:e.verdict,confidence:e.confidence,metrics:e.metrics,crawl:{requested:e.crawl.requested,attempted:e.crawl.attempted,usable:e.crawl.usable,blockedOrFailed:e.crawl.blockedOrFailed},inspection:{requested:e.inspection.requested,attempted:e.inspection.attempted,indexed:e.inspection.indexed,notIndexed:e.inspection.notIndexed,unknown:e.inspection.unknown},evidence:e.evidence,recommendation:e.recommendation}))}}function bL(e){e.registerTool(`seo_pseo_audit`,{description:`Audit pSEO templates with first-party GSC, optional crawl, and optional URL Inspection evidence`,inputSchema:{site:D.string().min(1),days:D.number().int().min(1).max(548).optional(),sitemaps:D.array(D.string().url()).max(20).optional(),maxSitemapUrls:D.number().int().min(1).max(1e5).optional(),templateLimit:D.number().int().min(1).max(100).optional(),minimumTemplateUrls:D.number().int().min(2).max(100).optional(),minimumTemplateShare:D.number().min(0).max(1).optional(),minimumTemplateImpressions:D.number().min(0).max(1e9).optional(),crawlSamples:D.number().int().min(0).max(10).optional(),inspectSamples:D.number().int().min(0).max(10).optional(),brandTerms:D.array(D.string().min(1).max(200)).max(20).optional(),includeBrand:D.boolean().optional(),refresh:D.boolean().optional(),js:D.boolean().optional(),fetchConcurrency:D.number().int().min(1).max(20).optional(),fetchIntervalCap:D.number().int().min(1).max(100).optional(),fetchIntervalMs:D.number().int().min(100).max(6e4).optional(),detail:D.enum([`summary`,`full`]).optional()}},async({site:e,days:t,sitemaps:n,templateLimit:r,maxSitemapUrls:i,minimumTemplateUrls:a,minimumTemplateShare:o,minimumTemplateImpressions:s,crawlSamples:c,inspectSamples:l,brandTerms:u,includeBrand:d,refresh:f,js:p,fetchConcurrency:m,fetchIntervalCap:h,fetchIntervalMs:g,detail:_})=>{try{let v=await GN({site:e,days:t,sitemaps:n,maxSitemapUrls:i,templateLimit:r,minimumTemplateUrls:a,minimumTemplateShare:o,minimumTemplateImpressions:s,crawlSamples:c,inspectSamples:l,brandTerms:u,includeBrand:d,refresh:f,js:rL(p,`auto`),rate:{concurrency:m,intervalCap:h,intervalMs:g}}),y=_===`full`;return $(`${Z(v.summary.templates,`pSEO template`)} audited from ${Z(v.summary.gscPages,`GSC page`)}.`,y?{report:v,presentation:ZN(v)}:{report:yL(v)},y?{markdown:QN(v)}:void 0)}catch(e){return Q(e)}})}const xL={"affected-urls":{readOrder:[`dataSources for provider availability`,`selection totalMatchedRows, returnedRows, and truncated`,`the requested rule, category, and severity filters`,`each row rule, category, severity, and crawler evidence`,`warnings and caveats`],doNotClaim:[`A capped or partial crawl only supports "affected among evaluated pages", not sitewide.`,`Unavailable clicks, impressions, or sessions are not a real zero.`,`truncated true means more matching issue rows exist than were returned.`],verify:`Re-run the same bounded crawl after deployment and confirm the rule no longer triggers on the affected URLs.`,related:[{id:`explain-crawl-issue`,reason:`Explains the rule before fixes.`},{id:`top-fixes`,reason:`Ranks issues when there are too many.`},{id:`crawl-report`,reason:`Opens the source crawl snapshot.`}]},"agent-readiness":{readOrder:[`profile, profileApplicability, dataStatus, and crawl caveats`,`summary and topActions`,`HTML and Markdown representation checks with affected URLs`,`Agent Skills, llms.txt, route manifest, crawler access, and identity evidence`],doNotClaim:[`A clean content profile is not proof of indexing, rankings, AI mentions, citations, or selection.`,`API, application, and commerce checks marked notApplicable were outside this content-site run. They did not fail.`,`An allowed crawler token does not prove that a service fetched or used the page.`,`Optional discovery files are not Google ranking requirements.`],verify:`Run the same content profile against the same public route scope after deployment and confirm each failed or warning check changed.`,related:[{id:`ai-readiness`,reason:`Checks AI search technical eligibility.`},{id:`llms-txt-audit`,reason:`Inspects llms.txt in more detail.`},{id:`entity-readiness`,reason:`Reviews identity evidence.`},{id:`site-crawl`,reason:`Creates a reusable crawl baseline.`}]},"ai-readiness":{readOrder:[`dataStatus and crawl caveats`,`each check evaluated flag`,`hard access, indexability, and snippet conflicts`,`semantic structure and answerable content as observations`],doNotClaim:[`unknown means evidence was unavailable, not a pass or a fail.`,`Valid JSON-LD is syntax evidence, not entity recognition or rich-result eligibility.`,`A clean report is not indexing, ranking, AI visibility, selection, or citations.`],verify:`Re-crawl at the same scope after a change and confirm the access or indexability signal resolved.`,related:[{id:`geo-gaps`,reason:`Lists affected URLs for access rules.`},{id:`entity-readiness`,reason:`Reviews identity signals.`},{id:`ai-search-scorecard`,reason:`Scores the same evidence.`}]},"ai-referrals":{readOrder:[`dataStatus, range, methodology, and dataSource partialReasons`,`possiblyTruncated and per-query statuses`,`selection.landingPages before treating landingPages as complete`,`sources observedSessionSources for attribution evidence`],doNotClaim:[`An empty result is not proof that no AI system sent or cited the site.`,`Referral sessions did not necessarily cause conversions and are not all AI traffic.`,`totalUsers is not additive and must not be reconstructed by summing rows.`],verify:`Compare complete like-for-like GA4 periods and check whether high-session landing pages answer the referring context.`,related:[{id:`seo-to-ai-query`,reason:`Builds monitoring prompts.`},{id:`ai-readiness`,reason:`Checks technical AI access.`}]},"ai-search-scorecard":{readOrder:[`partial, crawlComplete, and excluded before the number`,`score, which is null when no check had known evidence`,`each check status, observed evidence, and finding`,`methodology weights, statusCredit, and formula`],doNotClaim:[`The score is a heuristic over this tool's own checks, not a Google or AI-engine requirement, eligibility verdict, or ranking prediction.`,`unknown checks are excluded from the score and are never failures.`,`A blocked AI crawler token can be an intentional publisher choice.`],verify:`Re-crawl at the same scope after a change and confirm the per-check evidence moved.`,related:[{id:`ai-readiness`,reason:`Gives the per-page evidence.`},{id:`geo-gaps`,reason:`Lists affected URLs.`},{id:`entity-readiness`,reason:`Reviews identity signals.`}]},"audit-page":{readOrder:[`fetchDiagnostics, warnings, and the final fetched URL`,`observed page fields and structured-data findings`,`issues and recommendations`],doNotClaim:[`One page cannot support a sitewide conclusion.`,`Title pixel width is a review estimator, not a Google limit.`,`The fetched version is not proof that Google indexed that version.`],verify:`Re-fetch the URL after the edit and confirm the observed field changed.`,related:[{id:`redirect-trace`,reason:`Debugs the redirect chain.`},{id:`performance-audit`,reason:`Adds page speed evidence.`},{id:`content-optimization`,reason:`Builds a query-led brief.`}]},"audit-urls":{readOrder:[`requested versus attempted, fetched, failed, retained, and omitted counts`,`response evidence and issue summaries`,`requestEvidenceStatus, warnings, and caveats`],doNotClaim:[`A successful sample does not prove the same template works everywhere.`,`A failed request may be transient network, not a persistent defect.`,`A bounded audit is never complete site coverage.`],verify:`Re-run the identical URL list after deployment; a URL dropped from the request is not a verified fix.`,related:[{id:`audit-page`,reason:`Inspects one page in depth.`},{id:`site-crawl`,reason:`Discovers pages instead.`},{id:`redirect-trace`,reason:`Traces a redirecting URL.`}]},cannibalisation:{readOrder:[`dataStatus and both source.pageExposure and source.propertyDemand with validation counts and completeness`,`selection to see why groups were excluded or suppressed`,`per-page clicks, impressions, position, impression share, reviewContext, and HHI`,`priority.score and suggestedOwnerUrl`],doNotClaim:[`URL overlap is a review candidate, not proven harmful cannibalisation.`,`suggestedOwnerUrl is a low-confidence mechanical pick that requires intent review.`,`priority.score is a heuristic, not estimated click lift, and a partial source cannot support an all-clear.`],verify:`Compare intent and live SERPs for the overlapping URLs before consolidating anything.`,related:[{id:`query-clusters`,reason:`Groups related query themes.`},{id:`internal-links`,reason:`Clarifies link ownership.`},{id:`content-optimization`,reason:`Differentiates page intent.`}]},"community-intent":{readOrder:[`dataStatus, source.completeness, warnings, and caveats`,`selection.classifiedRows for every match and selection.returnedRows for the limited set`,`per-item intent, signals, and matchedTerms`],doNotClaim:[`Intent labels are low-confidence English language heuristics, not verified intent.`,`The classifier does not inspect the live SERP or identify the ranking URL.`,`A capped or partial zero is inconclusive, not absence of demand.`],verify:`Retrieve GSC query and page rows to find the ranking URL and check its content against the observed query language.`,related:[{id:`content-optimization`,reason:`Turns intent into a brief.`},{id:`query-clusters`,reason:`Groups the demand themes.`},{id:`page-opportunities`,reason:`Reviews one ranking URL.`}]},"compare-crawls":{readOrder:[`comparability.status and its matching flags`,`before and after preserved config, requestScope, and caps`,`top-level completeness and truncated reason codes`,`summary, pageChanges, and issueChanges`],doNotClaim:[`An absent page from a capped, skipped, failed, or differently scoped crawl is not proven removed.`,`Source row caps limit joined GSC or GA4 evidence even when the document crawl completed.`,`A change between snapshots is not proof the release caused it.`],verify:`Re-run the same definition and request scope after deployment and confirm the delta.`,related:[{id:`crawl-diff`,reason:`Compares against the previous run.`},{id:`crawl-history`,reason:`Finds saved report ids.`},{id:`affected-urls`,reason:`Lists URLs for a changed rule.`}]},"content-optimization":{readOrder:[`sourceReport dataStatus, selection, benchmark.possiblyTruncated, and verification.status`,`summary.score and primaryIntent as heuristics`,`topActions separating technical-check and unverified from verified observations`,`intentMix and the source query metrics`],doNotClaim:[`estimatedClickLift and score are prioritisation heuristics, not forecasts or quality grades.`,`Query wording is not a content mandate.`,`Adding suggested wording is not guaranteed traffic.`],verify:`Fetch the page after the edit and confirm the missing answer block or clearer framing is present.`,related:[{id:`page-opportunities`,reason:`Adds query-level opportunities.`},{id:`internal-links`,reason:`Finds contextual links.`},{id:`quick-wins`,reason:`Ranks CTR opportunities.`}]},"crawl-diff":{readOrder:[`run versus previousRun, where a missing previous run means a baseline`,`start URL, limit, counts, timestamps, rendering, and warnings for both runs`,`summary counts traced into items before, after, and changes`,`recommendations as review actions`],doNotClaim:[`A removed URL may be outside the current discovered set or limit rather than deleted.`,`A fetch or extraction failure cannot support a resolved issue.`,`A bounded diff is not an inventory-wide audit and does not show ranking impact.`],verify:`Repeat with comparable scope after a fix and confirm the changed item.`,related:[{id:`compare-crawls`,reason:`Compares two explicit snapshots.`},{id:`technical-watch`,reason:`Runs a recurring watch.`},{id:`affected-urls`,reason:`Lists URLs for a rule.`}]},"crawl-history":{readOrder:[`each report id, site, start URL, creation time, and status`,`total pages and issue count`,`configuration hash and storage version`],doNotClaim:[`Counts are orientation metadata, not severity or completeness.`,`A smaller page count could reflect a scope change, cap, or failure, not a real site change.`,`A saved list can expose stale data.`],verify:`Load a chosen id with crawl-report and inspect its caveats before analysis.`,related:[{id:`crawl-report`,reason:`Loads a saved snapshot.`},{id:`compare-crawls`,reason:`Compares two snapshots.`},{id:`site-crawl`,reason:`Creates a new crawl.`}]},"crawl-report":{readOrder:[`id, source URL and site, generatedAt, definition id, config hash, status, and requestEvidenceStatus`,`summary counts and pageLimitReached`,`data sources, warnings, and caveats`],doNotClaim:[`A report can be internally valid yet stale, capped, or partial and cannot prove the current live state.`,`Loading a report is a local storage read, not a fresh check.`],verify:`Run a new crawl with the same configuration and retain both report ids to confirm a change.`,related:[{id:`top-fixes`,reason:`Ranks the issue queue.`},{id:`affected-urls`,reason:`Lists URLs for a rule.`},{id:`compare-crawls`,reason:`Compares two snapshots.`}]},"crawler-rules":{readOrder:[`each rule id, title, and category`,`default severity as triage metadata`,`guidance metadata`],doNotClaim:[`Listing a rule does not mean the site triggered it or that the convention is a search-engine requirement.`,`Default severity is generic triage metadata, not actual priority.`,`Similarly named rules may not share source semantics.`],verify:`Pass the exact rule id to explain-crawl-issue or affected-urls to confirm it applies to real evidence.`,related:[{id:`explain-crawl-issue`,reason:`Returns the rule contract.`},{id:`affected-urls`,reason:`Returns crawl evidence for a rule.`}]},"ctr-underperformers":{readOrder:[`dataStatus, range, source.completeness, possiblyTruncated, and validation counts`,`selection before the queue`,`per-item query, URL, impressions, position, observed CTR, target CTR, and benchmark source`,`estimated shortfall`],doNotClaim:[`The shortfall is directional prioritisation math, not a traffic forecast.`,`Page-one average position hides day, device, geography, and result-feature variation.`,`A partial empty result is not an all-clear.`],verify:`Inspect the current SERP and displayed title link, then compare a complete later period after testing framing.`,related:[{id:`quick-wins`,reason:`Ranks position 4-10 opportunities.`},{id:`page-opportunities`,reason:`Reviews one URL.`},{id:`content-optimization`,reason:`Builds a page brief.`}]},"decaying-pages":{readOrder:[`dataStatus, ranges, and source.completeness`,`current and previous fetched rows, caps, truncation, and invalid rows in selection`,`summary.observedRetainedQueryClickLoss versus the returned subset`,`per-item clicks, impressions, CTR, position, and signals`],doNotClaim:[`A diagnosis such as lost_position or lost_ctr labels correlated movement, not a cause.`,`Missing retained rows are not zero traffic.`,`Do not sum capped rows into a property-wide loss.`],verify:`Check deployments, live indexability, and SERP composition for the affected URLs before editing.`,related:[{id:`refresh-priorities`,reason:`Combines decline signals.`},{id:`segment-impact`,reason:`Shows where movement concentrates.`},{id:`measure-change`,reason:`Evaluates a fix later.`}]},"entity-readiness":{readOrder:[`dataStatus, evaluatedPages versus crawlPages, and caveats`,`entities.schemaTypes`,`sameAs, sameAsByType, and socialProfiles`,`authors`],doNotClaim:[`Checks are informational observations with coverage, not pass or fail requirements.`,`Skipped or non-indexable pages are not evidence of absence.`,`These signals do not prove Knowledge Graph inclusion, ownership, expertise, rankings, or citations.`],verify:`Confirm names, URLs, authorship, and dates with the publisher, then re-crawl the same scope.`,related:[{id:`ai-readiness`,reason:`Checks technical AI access.`},{id:`ai-search-scorecard`,reason:`Scores the evidence.`},{id:`geo-gaps`,reason:`Lists affected URLs.`}]},"explain-crawl-issue":{readOrder:[`category, default severity, and why it matters`,`fix steps and ignored impact`,`verification method and agent hints`],doNotClaim:[`Guidance describes the checked condition, not a search-engine penalty or guaranteed ranking effect.`,`Default severity is generic prioritization metadata, not actual urgency.`,`The rule definition alone does not prove any page triggered it.`],verify:`Pair the definition with affected-urls from the same saved crawl and confirm the per-URL evidence.`,related:[{id:`affected-urls`,reason:`Returns URLs for the rule.`},{id:`crawler-rules`,reason:`Finds a rule id.`},{id:`top-fixes`,reason:`Ranks the fix queue.`}]},"generate-llms-txt":{readOrder:[`content, includedUrls, estimatedTokens, and sections`,`the generated titles and descriptions against the source pages`,`the source crawl date, cap, failures, and caveats`],doNotClaim:[`Reaching maxUrls or the token budget is possible truncation, not a complete inventory.`,`A valid draft does not prove search or AI benefit, selection, indexing, or citations.`,`The crawler cannot determine publisher intent.`],verify:`After human review, fetch the published file and validate its links with llms-txt-audit.`,related:[{id:`llms-txt-audit`,reason:`Audits the published file.`},{id:`crawl-report`,reason:`Inspects the source crawl.`},{id:`okf-build`,reason:`Builds a richer knowledge pack.`}]},"geo-gaps":{readOrder:[`dataStatus, source.partialReasons, and selection evaluated, matched, returned, and truncated counts`,`per-URL issues separated from searchEligibility`,`access states, where null means unknown`],doNotClaim:[`A null access state means unknown, not allowed.`,`Semantic HTML and structured data do not override a hard access restriction or create eligibility.`,`An empty gap list does not prove indexing, visibility, selection, or complete coverage; confirm intent before treating noindex, robots, canonicals, or snippet limits as defects.`],verify:`Fix unintended response or access conflicts, then re-check the exact URL and directive after deployment.`,related:[{id:`ai-readiness`,reason:`Gives the readiness overview.`},{id:`affected-urls`,reason:`Lists URLs for a rule.`},{id:`index-coverage`,reason:`Checks index signals.`}]},"index-coverage":{readOrder:[`sources completeness, rowLimit, rowLimitReached, date range, and invalid rows`,`retainedSearchVisibility and crawlableWithoutRetainedSearchVisibility`,`blockedOrNonIndexable, sitemapOnly, searchConsoleOnly, and templateReview`,`each group count, returned, and omitted`],doNotClaim:[`A page is not indexed because it had Search Analytics visibility, nor unindexed because no row appeared.`,`A missing Search Analytics row is not proof a page is unindexed.`,`Do not turn a limited or incomplete source into a whole-site coverage percentage.`],verify:`Run index-watch on representative URLs to collect exact URL Inspection verdicts.`,related:[{id:`index-watch`,reason:`Collects URL Inspection evidence.`},{id:`index-coverage-plan`,reason:`Plans quota when large.`},{id:`site-crawl`,reason:`Provides the source crawl.`}]},"index-coverage-plan":{readOrder:[`summary sitemapUrls, urlCount, property count, daily capacity, and estimated versus target cycle days`,`properties allocations and sample URLs`,`suggestions and every warning`],doNotClaim:[`Estimated cycle time is arithmetic over discovered URLs and capacity, not a promise Google will process or index them.`,`Sitemap fetch failures or maxUrls truncation make the plan incomplete.`,`This plan does not report an index coverage percentage or an all-clear.`],verify:`Use the plan to set a realistic recurring index-monitor limit and confirm allocations against properties you control.`,related:[{id:`index-monitor`,reason:`Collects the snapshots.`},{id:`index-coverage`,reason:`Reviews index signals first.`},{id:`index-watch`,reason:`Reviews index changes.`}]},"index-monitor":{readOrder:[`dataStatus and source sitemap and inventory limits, discovered and invalid URLs, and truncation`,`summary inventory states kept distinct`,`properties allocation and execution`,`items inspectionStatus, indexStatus, regressions, recoveries, and errors`],doNotClaim:[`A due URL that was not selected has no new inspection result.`,`Failed, deferred, or quota-blocked checks are operational outcomes, not SEO defects.`,`A complete selected batch does not prove inventory-wide indexing or future inclusion.`],verify:`Investigate regressions with a live page audit and the exact inspection evidence, and retry failed checks per retryAt.`,related:[{id:`index-watch`,reason:`Reviews index changes.`},{id:`index-coverage-plan`,reason:`Plans quota allocation.`},{id:`index-coverage`,reason:`Reviews coverage signals.`}]},"index-watch":{readOrder:[`source.possiblyTruncated, dataStatus, warnings, and sitemap inventory states`,`currentIssues, regressions, recoveries, failed, quotaBlocked, and deferred separately`,`indexStatus, issueCodes, and typed changes`],doNotClaim:[`URL Inspection is Google's indexed snapshot for one URL, not a live crawl or current search appearance.`,`A NEUTRAL verdict or canonical difference may be intentional, not a defect.`,`Do not infer inventory-wide health from the bounded selected sample; a provider 429 is authoritative over the local quota ledger.`],verify:`Audit the live page, sitemap, and exact inspection evidence for any regression or current issue.`,related:[{id:`index-monitor`,reason:`Collects the snapshots.`},{id:`index-coverage`,reason:`Reviews coverage signals.`},{id:`index-coverage-plan`,reason:`Plans quota allocation.`}]},"internal-links":{readOrder:[`dataStatus, target verification, aliases, canonical, and technical signals`,`source completeness and selection attempted, checked, failed, unchecked, and exclusions`,`per-item exact versus lexical matches and linkEvidence`],doNotClaim:[`A low checkLimit can leave plausible candidates unchecked, so absence is not proven.`,`Query overlap does not prove a link belongs or that adding one improves rankings.`,`Priority is a heuristic, not estimated impact.`],verify:`Read the source passage, add one contextual link if the target is genuinely useful, and re-crawl to confirm it.`,related:[{id:`cannibalisation`,reason:`Checks URL overlap.`},{id:`content-optimization`,reason:`Builds a page brief.`},{id:`page-opportunities`,reason:`Reviews one URL.`}]},"link-recovery":{readOrder:[`range and summary.checked with severity totals`,`per-item original and final URL, GSC metrics, issue lists, and redirect trace`,`chain status, final-page indexability, canonicals, and warnings`],doNotClaim:[`clicksAtRisk and impressionsAtRisk describe the checked period, not a forecast of loss or recovery.`,`Prior visibility orders the queue but does not prove fixing a URL restores traffic.`,`Failed checks or selection limits constrain coverage.`],verify:`Add one direct 301 to the closest equivalent destination only after confirming the page should still exist, then re-trace.`,related:[{id:`redirect-trace`,reason:`Traces one URL chain.`},{id:`decaying-pages`,reason:`Shows click declines.`},{id:`audit-urls`,reason:`Runs bounded checks.`}]},"llms-txt-audit":{readOrder:[`exists, resolved file URL, response status, headline, and issues`,`recommendedPages`,`the source crawl date, cap, partial state, failed requests, and caveats`],doNotClaim:[`llms.txt presence is not a ranking signal and its absence is not a technical defect.`,`A short recommendation list from a capped crawl is not sitewide evidence.`,`Adding the file does not improve indexing, rankings, traffic, AI selection, or citations.`],verify:`If the file exists, confirm its URLs are current, canonical, public, and aligned with publisher intent.`,related:[{id:`generate-llms-txt`,reason:`Drafts a new file.`},{id:`crawl-report`,reason:`Inspects the source crawl.`},{id:`okf-build`,reason:`Builds a knowledge pack.`}]},"measure-change":{readOrder:[`dataStatus, window, source, warnings, and caveats`,`window.effectiveDays versus requestedDays and afterWindowTruncated`,`GSC before and after metrics`,`GA4 source.analytics.status and timezone and any control adjusted deltas`],doNotClaim:[`Timing does not prove the recorded change caused the movement.`,`not-enough-data is never positive, negative, or flat, and the delta must not be annualized or forecast.`,`clickPct null means the before value was zero, not an infinite gain.`],verify:`Use equal whole-day before and after windows with at least 7 finalized days each before reading a directional verdict.`,related:[{id:`segment-impact`,reason:`Shows where movement sits.`},{id:`decaying-pages`,reason:`Shows click declines.`},{id:`update-postmortem`,reason:`Frames movement around an update.`}]},"monthly-action-plan":{readOrder:[`workflow summary, steps, actions, and nested output`,`requested month and final-data window`,`provider status and skipped sections`,`warnings, caveats, and retained caps`],doNotClaim:[`A skipped section is not a zero and a successful workflow does not make every nested source complete.`,`Actions are a review queue, not automatic edit instructions.`,`Do not invent click, traffic, ranking, or revenue forecasts or attribute movement to an update from timing alone.`],verify:`Trace each narrative claim back to its structured diagnosis or measurement and confirm affected URLs.`,related:[{id:`monthly-report`,reason:`Produces the human brief.`},{id:`refresh-priorities`,reason:`Builds a refresh queue.`},{id:`search-performance-overview`,reason:`Gives a baseline.`}]},"monthly-report":{readOrder:[`month and the underlying date windows`,`dataStatus and source completeness`,`skipped sections, warnings, and caveats`,`the narrative and its referenced diagnosis, changes, and priorities`],doNotClaim:[`A zero from a complete source differs from an unavailable, partial, filtered, or capped section.`,`verifyLimit is a bounded subset, not full-site validation.`,`The report cannot establish why traffic changed or forecast clicks, rankings, or revenue.`],verify:`Follow a narrative reference into the structured evidence and validate the affected URLs before acting.`,related:[{id:`monthly-action-plan`,reason:`Sequences the actions.`},{id:`narrative-report`,reason:`Assembles an evidence-linked briefing.`},{id:`search-performance-overview`,reason:`Gives a baseline.`}]},"narrative-report":{readOrder:[`top-level caveats and headline`,`sections and priorities`,`diagnosis, changeMeasurements, and changeMeasurementAttempts`,`monitoring`],doNotClaim:[`A failed or skipped measurement stays visible and is not a zero.`,`Update timing, template groupings, and opportunity scores are investigation context, not ranking causes.`,`The narrative summarizes evidence and does not strengthen it or prove causation.`],verify:`Reproduce a technical failure or inspect the affected URLs before treating a narrative claim as a defect.`,related:[{id:`monthly-report`,reason:`Fixes a calendar-month brief.`},{id:`refresh-priorities`,reason:`Builds a refresh queue.`},{id:`search-performance-overview`,reason:`Gives a baseline.`}]},"okf-build":{readOrder:[`manifest schema version, report id, source URL, generation time, crawl status, title, concept count, and caveats`,`selection page counts and deterministic ordering`,`the returned validation before using files`],doNotClaim:[`If selected pages equal the limit the bundle is bounded, not exhaustive.`,`A successful structural validation does not establish ownership, freshness, factual accuracy, or completeness.`,`The builder does not make extracted claims true or current.`],verify:`Inspect citations, canonical URLs, and summaries against source pages, especially for time-dependent claims.`,related:[{id:`okf-validate`,reason:`Validates the bundle.`},{id:`crawl-report`,reason:`Inspects the source crawl.`},{id:`generate-llms-txt`,reason:`Builds agent navigation.`}]},"okf-validate":{readOrder:[`validation.valid and file and concept counts`,`each issue level, path, and message`,`explanation.nextActions as ordering guidance`],doNotClaim:[`Validation checks the file contract only and does not browse cited sources or determine whether the knowledge is true, current, owned, or complete.`,`Warnings still require review even when valid is true.`,`Never report valid true as fact checked.`],verify:`After structural success, separately verify cited URLs, claims, dates, and the recorded crawl snapshot.`,related:[{id:`okf-build`,reason:`Builds the bundle to validate.`}]},"page-opportunities":{readOrder:[`dataStatus, range, source.targetRowsFetched, selection, and benchmark`,`benchmark.possiblyTruncated`,`verification.status and reason`,`per-item query metrics, opportunity type, and benchmark and verification evidence`],doNotClaim:[`Only verified results support live-page coverage observations.`,`estimatedCtrClickShortfall is a directional calculation, not a click forecast.`,`summary.opportunities is a review queue, not a defect count, and partial data is not an all-clear.`],verify:`Inspect the live SERP and resolve any status or indexability contradiction before editing.`,related:[{id:`content-optimization`,reason:`Builds a page brief.`},{id:`quick-wins`,reason:`Ranks CTR opportunities.`},{id:`internal-links`,reason:`Finds contextual links.`}]},"performance-audit":{readOrder:[`dataStatus, source, labDataStatus, and fieldDataStatus`,`Lighthouse score, lab metrics, and labInsights`,`CrUX fieldData.metrics with scope, formFactor, and collectionPeriod`,`fetch-fallback fields when source is fetch-fallback`],doNotClaim:[`Origin-level CrUX data is not page-level evidence.`,`A fetch-fallback duration is the local fetch workflow, not TTFB, LCP, CLS, TBT, or INP, and never earns a score.`,`Do not combine lab and field values into a new score, compare mobile with desktop as equal populations, or claim a ranking outcome.`],verify:`Reproduce the relevant lab issue and re-run the audit after the change.`,related:[{id:`audit-page`,reason:`Runs an on-page audit.`},{id:`site-crawl`,reason:`Builds the site inventory.`}]},"pseo-audit":{readOrder:[`dataStatus, source, selection, warnings, and caveats`,`population separating discovered, GSC-visible, sampled, and untested URLs`,`the template verdict confirmed against crawl, inspection, and evidence`,`metrics.topQueries and queryPatterns`],doNotClaim:[`PASS is the only indexed URL Inspection verdict and coverageState is diagnostic text that must not be parsed for index state.`,`A sitemap URL with no GSC row does not prove zero demand.`,`Literal term coverage and path entity fit are review heuristics, not ranking factors, and word count is descriptive.`],verify:`For an index-risk or crawl-risk verdict, inspect the sampled URLs directly and state the sample count.`,related:[{id:`index-coverage`,reason:`Reviews coverage signals.`},{id:`affected-urls`,reason:`Lists URLs for a rule.`},{id:`content-optimization`,reason:`Builds a page brief.`}]},"query-clusters":{readOrder:[`summary cluster and query counts, clicks, impressions, threshold, limit, and brand filtering`,`per-cluster member queries, pages, demand, and performance`,`each cluster recommendation`],doNotClaim:[`Token overlap does not prove shared intent, justify a new page, or establish that current URLs compete.`,`Page-two clusters do not claim a CTR-only click lift.`,`Queries below the threshold or outside the cap are outside the conclusion, not absent demand.`],verify:`Check whether a cluster maps to one intent before changing section structure or navigation.`,related:[{id:`cannibalisation`,reason:`Checks URL overlap.`},{id:`content-optimization`,reason:`Builds a page brief.`},{id:`community-intent`,reason:`Reviews intent language.`}]},"quick-wins":{readOrder:[`dataStatus, date range, source row cap, possiblyTruncated, selection.invalidRows, and filters`,`methodology and each item benchmark confidence before using targetCtr`,`estimatedCtrClickShortfall and priority`,`verification requested, attempted, verified, failed, and technical counts`],doNotClaim:[`estimatedCtrClickShortfall and priority score are heuristics, explicitly not estimated lift.`,`Average position is not a stable rank, and an unverified item cannot support a content-coverage conclusion.`,`Filtered or capped evidence is not an all-clear.`],verify:`Inspect the live SERP, test clearer snippet framing, then compare a later complete window.`,related:[{id:`ctr-underperformers`,reason:`Adds CTR peer evidence.`},{id:`page-opportunities`,reason:`Reviews one URL.`},{id:`striking-distance`,reason:`Finds rows near page one.`}]},"redirect-trace":{readOrder:[`chain in order with requested URL, status, Location, resolved next, and duration`,`finalUrl versus finalPage and summary.finalStatus`,`summary.issues`,`metaRobots, xRobotsTag, canonical, and warnings`],doNotClaim:[`The trace is one observation from the machine and time that ran it, and CDNs, geography, cookies, or later deploys can differ.`,`A canonical elsewhere or a 302 can be intentional.`,`JavaScript rendering affects final-page extraction, not the HTTP chain.`],verify:`Re-run the trace after deployment and confirm the intended destination and status.`,related:[{id:`link-recovery`,reason:`Prioritises search-value URLs.`},{id:`audit-page`,reason:`Audits the final page.`},{id:`audit-urls`,reason:`Runs bounded checks.`}]},"refresh-priorities":{readOrder:[`workflow steps and skipped states`,`per-item source, impactKind, score breakdown, date window, completeness, verification status, and rationale`,`the top few URLs manually`],doNotClaim:[`A click decline, low CTR, second-page position, and GA4 session signal have different semantics and are not interchangeable.`,`Missing GA4 or failed fetches stay unavailable, not zero-weight proof.`,`The score is not expected clicks, revenue, ranking lift, or causal confidence.`],verify:`Confirm query intent, current content, and internal links for the top URLs before choosing a bounded edit.`,related:[{id:`decaying-pages`,reason:`Shows click declines.`},{id:`quick-wins`,reason:`Ranks CTR opportunities.`},{id:`second-page`,reason:`Finds rows near page one.`}]},"search-performance-overview":{readOrder:[`workflow envelope with summary, steps, actions, and output`,`report dataStatus, skipped sections, and partial reasons`,`priorities`,`nested anomaly, update, segment, decay, cannibalisation, striking-distance, and quick-win results`],doNotClaim:[`One complete section must not hide a failed or capped neighbour.`,`Sparse data may legitimately skip a section.`,`Update overlap, opportunity scores, average position, and timing are investigation signals, not causation or forecasts.`],verify:`Run the focused report named in actions and confirm affected URLs and provider dates before editing.`,related:[{id:`segment-impact`,reason:`Shows where movement sits.`},{id:`quick-wins`,reason:`Ranks CTR opportunities.`},{id:`striking-distance`,reason:`Finds rows near page one.`}]},"second-page":{readOrder:[`source completeness, date range, selection, methodology, provenance, and brand filtering`,`per-item impressions, clicks, CTR, and average position`,`the priority value`],doNotClaim:[`Average position is aggregated across searches, locations, devices, and time and is not an exact rank.`,`Retained limits mean the output is not a complete inventory.`,`Priority is a transparent heuristic, not expected click lift, and one row is not cannibalisation.`],verify:`Inspect the live page and search intent and confirm the query suits the URL before one defensible change.`,related:[{id:`striking-distance`,reason:`Covers positions 11-20.`},{id:`quick-wins`,reason:`Ranks CTR opportunities.`},{id:`page-opportunities`,reason:`Reviews one URL.`}]},"segment-impact":{readOrder:[`before, after, and rangeDays`,`dataStatus and source.before, source.after, and completeness for caps and truncation`,`selection invalid, duplicate, conflicting, matched, unmatched, returned, and limited rows`,`per-item clickDelta, impressionDelta, and positionDelta, then unmatchedSegments`],doNotClaim:[`A positive positionDelta is worse because a larger average position is lower in results.`,`unmatchedSegments are retained in one window only and are not gains from or losses to zero.`,`A limited or truncated result cannot support an inventory-wide winner or loser.`],verify:`Audit the top matched movers pages and query intent before drawing a segment conclusion.`,related:[{id:`traffic-anomaly`,reason:`Confirms movement first.`},{id:`decaying-pages`,reason:`Shows click declines.`},{id:`update-postmortem`,reason:`Frames movement around an update.`}]},"seo-to-ai-query":{readOrder:[`dataStatus states empty, filtered, partial, and available`,`dateRange, source.possiblyTruncated, filters, selection.invalidRows, and limited rows`,`methodology.observedAiPromptData and estimatedTrafficLift, both false`,`per-item evidenceScope`],doNotClaim:[`Generated prompt count is not AI demand and prompts do not prove anyone entered them or that an answer mentioned the site.`,`No returned rows is not an all-clear when the source is partial or filtered.`,`Each prompt inherits the narrow retained-GSC-query scope.`],verify:`Store the source query and date range beside later monitoring observations and refresh on a controlled schedule.`,related:[{id:`ai-referrals`,reason:`Adds GA4 referral evidence.`},{id:`community-intent`,reason:`Reviews intent language.`},{id:`ai-readiness`,reason:`Checks technical access.`}]},"setup-check":{readOrder:[`ok`,`each checks entry status, distinguishing fail from warn, with detail and fix`,`generatedAt`],doNotClaim:[`A passing result means local prerequisites look present, not that a token is still accepted by Google or that the account can access a property.`,`Missing saved defaults are usually warnings because explicit parameters can replace them.`,`A setup pass is not evidence about rankings, indexing, or site health.`],verify:`Apply the fix on a failed check, re-run setup-check, then run a small read-only report for the intended property.`,related:[{id:`search-performance-overview`,reason:`First report once setup passes.`}]},"site-crawl":{readOrder:[`status, requestEvidenceStatus, attempted and fetched counts, failures, retained pages, and pageLimitReached`,`data-source states`,`warnings and caveats, then opt-in pages, requests, and issues`],doNotClaim:[`A partial or capped crawl cannot support an all-clear or a definitive zero.`,`A crawl is not an index, traffic source, or guarantee of complete site coverage.`,`If JavaScript rendering was disabled, state that discovery boundary.`],verify:`Preserve the report id and settings, then re-run the same configuration to confirm a change.`,related:[{id:`top-fixes`,reason:`Ranks the fix queue.`},{id:`affected-urls`,reason:`Lists URLs for a rule.`},{id:`index-coverage`,reason:`Checks coverage signals.`}]},"striking-distance":{readOrder:[`range, rangeDays, dataStatus, and source`,`source.possiblyTruncated and retained-row completeness`,`selection invalid, out-of-range, branded, below-threshold, eligible, returned, and limited`,`methodology, then per-item metrics and contentVerification`],doNotClaim:[`The 11-20 band is a heuristic boundary and average position is not a literal second results page.`,`Unverified or failed pages keep lower confidence.`,`The priority score is not expected traffic or ranking lift.`],verify:`Audit a small number of high-impression candidates and fix verified technical problems first.`,related:[{id:`second-page`,reason:`Covers positions 10-20.`},{id:`quick-wins`,reason:`Ranks CTR opportunities.`},{id:`page-opportunities`,reason:`Reviews one URL.`}]},"technical-watch":{readOrder:[`each workflow step and nested crawl, index, and recovery output independently`,`crawl run ids, limits, failures, and changed items`,`inspection selected, unselected-due, attempted, failed, quota-blocked, deferred, and regression states`,`recovery clicks-at-risk as history`],doNotClaim:[`A successful run does not prove every URL is crawlable, indexed, unchanged, or issue-free.`,`URL Inspection is a provider snapshot and sitemap discovery is not proof of indexing.`,`Partial crawls, capped inventories, quotas, and failed subqueries must stay visible in any all-clear.`],verify:`Reproduce new failures and confirmed regressions, then schedule another bounded run with a rollback path.`,related:[{id:`crawl-diff`,reason:`Isolates crawl change.`},{id:`index-watch`,reason:`Isolates index change.`},{id:`link-recovery`,reason:`Recovers search-value URLs.`}]},"top-fixes":{readOrder:[`dataSources, summary, warnings, and caveats`,`per-item rule id, affected count, sample URLs, scoreFactors, and whyThisRanks`,`effort, fix guidance, and verification`],doNotClaim:[`Rank means highest among retained eligible findings, not globally highest, because the crawl or category may be capped.`,`Missing provider joins stay unavailable and a numeric zero is meaningful only when the source is complete.`,`This is not a forecast of rankings, traffic, or revenue.`],verify:`Run affected-urls for the same rule and report id, then repeat the crawl configuration after implementation.`,related:[{id:`affected-urls`,reason:`Lists URLs for a rule.`},{id:`explain-crawl-issue`,reason:`Explains the rule.`},{id:`site-crawl`,reason:`Provides the source crawl.`}]},"traffic-anomaly":{readOrder:[`coverage with requested dates, expected versus observed days, missing days, invalid rows, and status`,`anomalies with metric, baseline and comparison dates, percentChange, zScore, and significanceMethod`,`direction and significant`],doNotClaim:[`Statistical unusualness does not identify a cause, diagnose a defect, or prove an external event affected the property.`,`A null percentage from a zero baseline differs from zero change.`,`Partial coverage can change the baseline.`],verify:`If movement is significant and coverage is adequate, run segment-impact for pages and queries.`,related:[{id:`segment-impact`,reason:`Shows where movement sits.`},{id:`update-correlation`,reason:`Adds update timing.`},{id:`decaying-pages`,reason:`Shows click declines.`}]},"update-correlation":{readOrder:[`classification with attribution and confidence, returned as not-established and none`,`anomalies, overlappingUpdates, and confounders`,`evidence, caveats, and source`],doNotClaim:[`Overlap cannot show the update caused a gain or loss even with large movement and no other recorded change.`,`update-overlap-without-significant-movement is not an impact result and no-update-overlap does not explain remaining movement.`,`Temporal overlap is not a penalty, reward, or confirmed effect.`],verify:`Test saved deploys, redirects, and content changes that overlap the window before attributing movement.`,related:[{id:`update-postmortem`,reason:`Maps winners and losers.`},{id:`segment-impact`,reason:`Shows where movement sits.`},{id:`traffic-anomaly`,reason:`Confirms movement first.`}]},"update-postmortem":{readOrder:[`workflow step statuses, finalized date windows, and source completeness`,`failed measurements, retained limits, and caveats`,`update, insights, templateMovement, and segments`],doNotClaim:[`The cause remains not established even when dates overlap.`,`Retained winners and losers are not a complete inventory when sources are capped or filtered.`,`Do not claim update causation, recovery probability, or future traffic.`],verify:`Inspect representative URLs from affected segments and reproduce technical changes before any rollback.`,related:[{id:`update-correlation`,reason:`Adds update timing.`},{id:`segment-impact`,reason:`Shows where movement sits.`},{id:`measure-change`,reason:`Evaluates a fix.`}]}},SL={"affected-urls":{name:`URLs affected by a crawl issue`,description:`Open the exact pages behind a crawl finding so you can review or fix the right URLs.`,useWhen:[`A crawl summary shows an issue that needs a closer look.`,`You need the affected URLs for one rule, category, or severity.`],avoidWhen:[`You have not run or selected a crawl yet.`,`You need an overview of the most important crawl findings first.`],outcome:`A focused list of affected URLs with the evidence for each match.`},"agent-readiness":{name:`AI agent readiness`,description:`Check whether a content site gives agents clean, stable, machine-readable pages and discovery files.`,useWhen:[`You want to test Markdown alternatives, content negotiation, Agent Skills, llms.txt, identity, and crawler access together.`,`You need evidence that the agent-facing version of a content site matches its public HTML routes.`],avoidWhen:[`You need an AI visibility, mention, citation, or ranking report.`,`You are auditing a public API, interactive application, or commerce flow rather than a content site.`],outcome:`An unscored content-site assessment with failed routes, evidence, and clear next actions.`},"ai-readiness":{name:`AI search technical readiness`,description:`Check whether crawl, index, or snippet controls could block pages from Google AI search features.`,useWhen:[`You want to check technical access before investigating AI search visibility.`,`You need page-level evidence for robots, indexability, or snippet restrictions.`],avoidWhen:[`You want a visibility score or a prediction of AI citations.`,`You need evidence of visits from AI products.`],outcome:`A technical blocker list with affected pages and clear limits.`},"ai-referrals":{name:`AI referral traffic`,description:`Find visits from known AI products in GA4 and see a ranked, bounded set of landing pages that received them.`,useWhen:[`You want measured AI referral traffic from GA4.`,`You need the landing pages and sources behind those visits.`],avoidWhen:[`GA4 is not connected or does not track the site.`,`You want to measure citations or unclicked mentions inside AI answers.`],outcome:`A source and landing-page breakdown of identifiable AI referrals.`},"ai-search-scorecard":{name:`AI search scorecard`,description:`Score AI-search technical readiness 0-100 from one crawl using this tool's own weighted checks, with observed evidence kept separate.`,useWhen:[`You want a single scored summary of the AI-search technical evidence this tool already collects.`,`You need per-check pass, warn, fail, or unknown states with the exact weights and formula.`],avoidWhen:[`You want a Google or AI-engine eligibility verdict, a ranking prediction, or proof of citations.`,`You need per-page fixes rather than a scored overview.`],outcome:`A 0-100 heuristic score with per-check evidence, unknown exclusions, and a partial flag.`},"audit-page":{name:`Single-page SEO audit`,description:`Check one URL for response, indexability, metadata, headings, links, and page content evidence.`,useWhen:[`One page needs a technical review before you change it.`,`A broader report points to a specific URL.`],avoidWhen:[`You need to discover issues across a whole site.`,`The page requires a logged-in browser session.`],outcome:`A page-level audit that separates observed evidence from review advice.`},"audit-urls":{name:`Selected URL audit`,description:`Audit a specific list of pages without crawling the rest of the site.`,useWhen:[`You already know which URLs need checking.`,`You want a repeatable pre-release or post-release check for key pages.`],avoidWhen:[`You need the crawler to discover pages for you.`,`You only need a deep review of one page.`],outcome:`Consistent technical checks for each supplied URL.`},cannibalisation:{name:`Query and URL overlap`,description:`Find search queries that appear across several URLs and decide whether the overlap needs attention.`,useWhen:[`Several pages may be competing for the same search intent.`,`You want query-level evidence before merging or redirecting pages.`],avoidWhen:[`You plan to treat every multi-URL query as a problem.`,`The site has too little Search Console data for meaningful overlap.`],outcome:`A ranked review list of queries, URLs, and their share of search exposure.`},"community-intent":{name:`Community and review intent`,description:`Find Search Console queries that explicitly ask for reviews, comparisons, recommendations, or real experience.`,useWhen:[`You want to see where searchers ask for proof, opinions, or comparisons.`,`You are planning content that answers community-style questions.`],avoidWhen:[`You want the tool to guess intent without explicit query wording.`,`You need proof that a new page will rank.`],outcome:`A review list of real queries grouped by the intent language they contain.`},"compare-crawls":{name:`Compare saved crawls`,description:`Compare two saved crawl snapshots to see which pages and technical findings changed.`,useWhen:[`You want to check what changed after a release or migration.`,`You need evidence that a technical fix appeared in a later crawl.`],avoidWhen:[`The two crawls used materially different scope or settings.`,`You have only one saved crawl.`],outcome:`A before-and-after view of added, removed, fixed, and new findings.`},"content-optimization":{name:`Search-led content brief`,description:`Turn a page and its Search Console queries into a practical brief for improving relevance and coverage.`,useWhen:[`An existing page has search demand but needs a clearer improvement plan.`,`You want first-party query evidence beside the current page content.`],avoidWhen:[`The URL has no useful Search Console history.`,`You want generated copy or a traffic forecast.`],outcome:`A page brief with query evidence, content checks, and specific review points.`},"crawl-diff":{name:`Monitor crawl changes`,description:`Crawl a site and compare it with the previous matching run to catch technical changes.`,useWhen:[`You want a repeatable technical check after deployments.`,`You need newly introduced or resolved crawl findings.`],avoidWhen:[`There is no comparable earlier crawl.`,`You need to compare two specific saved crawl IDs.`],outcome:`A fresh crawl plus a clear list of technical changes since the previous run.`},"crawl-history":{name:`Saved crawl history`,description:`List local crawl snapshots so you can choose the right report by site and date.`,useWhen:[`You need to find a crawl ID for review or comparison.`,`You want to confirm which crawl snapshots exist locally.`],avoidWhen:[`You need the findings inside a crawl rather than its saved metadata.`],outcome:`A compact list of saved crawls with IDs, sites, dates, and scope.`},"crawl-report":{name:`Open a saved crawl`,description:`Load a saved crawl by ID, or open the latest local crawl for a site.`,useWhen:[`You already know which saved crawl you want to inspect.`,`Another report asks for crawl evidence by report ID.`],avoidWhen:[`You need to create a new crawl.`,`You only need a filtered list for one issue.`],outcome:`The selected crawl summary with optional page and issue detail.`},"crawler-rules":{name:`Crawler rule catalog`,description:`Find the available technical check IDs and the evidence each crawler rule looks for.`,useWhen:[`You need a valid rule ID for another crawl report.`,`You want to see which technical checks are available by category.`],avoidWhen:[`You need affected pages or live crawl results.`],outcome:`A searchable catalog of crawler rules, severity, and guidance.`},"ctr-underperformers":{name:`CTR underperformers`,description:`Find high-impression queries whose click-through rate trails comparable queries at similar positions.`,useWhen:[`You want title or snippet review candidates backed by Search Console data.`,`You need to compare CTR fairly across similar average positions.`],avoidWhen:[`You want a promised click uplift.`,`The comparison groups have too little data.`],outcome:`A ranked list of query and page pairs with CTR comparison evidence.`},"decaying-pages":{name:`Pages losing search clicks`,description:`Find pages and queries with meaningful click declines across comparable Search Console periods.`,useWhen:[`You want to investigate a sustained organic search decline.`,`You need the pages and queries behind lost clicks.`],avoidWhen:[`The newer period is incomplete or not comparable.`,`You want to assume every decline is caused by old content.`],outcome:`A ranked investigation list with before-and-after search evidence.`},"entity-readiness":{name:`Entity signal review`,description:`Check names, authors, dates, structured data, and profile links that help identify who created a page.`,useWhen:[`You want to review publisher, organization, or author signals across crawled pages.`,`You need examples of missing or inconsistent entity evidence.`],avoidWhen:[`You want a knowledge graph or ranking guarantee.`,`You have not saved or supplied a crawl.`],outcome:`Page-level entity observations with examples that need human review.`},"explain-crawl-issue":{name:`Explain a crawl issue`,description:`Explain what one crawler rule means, why it may matter, and how to verify a fix.`,useWhen:[`A crawl returned a rule ID you do not recognize.`,`You need implementation and verification guidance before creating work.`],avoidWhen:[`You need the URLs affected by the rule.`,`You have not selected a valid crawler rule ID.`],outcome:`A plain-English rule explanation with fix and verification steps.`},"generate-llms-txt":{name:`Generate an llms.txt draft`,description:`Create a concise llms.txt draft from selected public pages in a crawl report.`,useWhen:[`You want a human-reviewed navigation file for useful public pages.`,`You have a current crawl that identifies suitable canonical URLs.`],avoidWhen:[`You expect the file to improve rankings, indexing, or AI citations.`,`The source crawl is old, partial, or aimed at the wrong site section.`],outcome:`An editable llms.txt draft with source and size limits kept visible.`},"geo-gaps":{name:`Google AI search controls`,description:`Find crawl, index, and snippet settings that could restrict pages in Google AI search features.`,useWhen:[`You want to find technical restrictions that apply to Google AI search.`,`You need affected pages for a crawl, index, or preview control.`],avoidWhen:[`You want a generic AI visibility score.`,`You need referral traffic or citation monitoring.`],outcome:`A focused list of technical restrictions and the pages where they appear.`},"index-coverage-plan":{name:`URL Inspection coverage plan`,description:`Choose which sitemap URLs and Search Console properties should use limited URL Inspection checks.`,useWhen:[`A large site needs a practical sampling plan for URL Inspection.`,`You want to spread checks across useful URL-prefix properties.`],avoidWhen:[`You need current Google index results rather than a plan.`,`You want complete coverage beyond the available quota.`],outcome:`A quota-aware list of URLs and properties to inspect next.`},"index-coverage":{name:`Index coverage signals`,description:`Compare the pages your site exposes with pages that had Google Search visibility, then choose representative URLs for URL Inspection.`,useWhen:[`You need to find crawlable pages that did not appear in the Search Console results returned for the selected period.`,`You want a focused review list before spending URL Inspection quota.`],avoidWhen:[`You need proof that a URL is indexed or excluded. Use URL Inspection for that URL.`,`You do not have a saved crawl for the selected site.`],outcome:`Cross-source URL groups with clear evidence limits and a representative review queue for URL Inspection.`},"index-monitor":{name:`Collect index snapshots`,description:`Check selected sitemap URLs with Google URL Inspection and save the results locally for later comparison.`,useWhen:[`You want current Google index evidence for a controlled URL set.`,`You need repeatable snapshots for future monitoring.`],avoidWhen:[`You expect every URL in a large site to be checked at once.`,`You only need to review previously saved snapshots.`],outcome:`Saved URL Inspection results with quota skips and provider failures separated.`},"index-watch":{name:`Review index changes`,description:`Compare current Google index evidence with saved snapshots to find regressions and recoveries.`,useWhen:[`You want to see which monitored URLs changed index state.`,`You need to separate page issues from quota or provider failures.`],avoidWhen:[`No earlier index snapshots exist.`,`You want a complete index count for the whole site.`],outcome:`A review queue of current issues, regressions, recoveries, and failed checks.`},"internal-links":{name:`Internal link opportunities`,description:`Find relevant pages that could add a useful internal link to a target URL.`,useWhen:[`A target page needs stronger internal discovery or contextual links.`,`You want candidate source pages checked against their current content.`],avoidWhen:[`You want links added automatically.`,`The target and candidate pages do not share a clear topic or user need.`],outcome:`Verified source-page candidates with query and page evidence for review.`},"link-recovery":{name:`Recover broken search-value URLs`,description:`Find URLs that previously earned search clicks but now fail, redirect poorly, or block indexing.`,useWhen:[`You suspect valuable URLs broke during a migration or release.`,`You want to prioritize technical recovery using Search Console evidence.`],avoidWhen:[`You want a backlink report.`,`You have no Search Console page history for the site.`],outcome:`A ranked recovery list with current URL checks and prior search value.`},"llms-txt-audit":{name:`Audit llms.txt`,description:`Check whether llms.txt exists, whether its links work, and how well it reflects useful public pages.`,useWhen:[`The site publishes llms.txt and you want to verify it.`,`You are deciding whether a concise navigation file would help agents.`],avoidWhen:[`You expect llms.txt to improve Google rankings or indexing.`,`You need to generate a new draft rather than audit the current file.`],outcome:`File, link, and page coverage findings with no visibility claims.`},"measure-change":{name:`Measure an SEO change`,description:`Compare equal Search Console periods before and after a known site change.`,useWhen:[`A release, migration, or content change has a clear date.`,`Both comparison periods contain final and comparable data.`],avoidWhen:[`The after period is incomplete.`,`You need proof that the change caused the movement.`],outcome:`A before-and-after search comparison with confounders and data limits shown.`},"monthly-action-plan":{name:`Monthly SEO action plan`,description:`Review a calendar month of search performance and turn the evidence into next actions.`,useWhen:[`You need a recurring monthly review with clear priorities.`,`The selected month has final Search Console data.`],avoidWhen:[`You only need a client-ready summary without an action queue.`,`The month is still incomplete.`],outcome:`A monthly performance summary followed by a focused action plan.`},"monthly-report":{name:`Monthly SEO report`,description:`Turn one calendar month of search evidence into a concise report for clients or teams.`,useWhen:[`You need a readable monthly summary of organic search performance.`,`The selected month has final Search Console data.`],avoidWhen:[`You need a rolling period rather than a calendar month.`,`You want a detailed technical site audit.`],outcome:`A clear monthly narrative with wins, losses, caveats, and next steps.`},"narrative-report":{name:`Client-ready SEO narrative`,description:`Turn search, change, and monitoring evidence into a readable update without hiding caveats.`,useWhen:[`You need to explain recent SEO performance to a client or team.`,`You want one narrative built from several available evidence sources.`],avoidWhen:[`You need raw rows for further analysis.`,`You want unsupported forecasts or causal claims.`],outcome:`A concise written report with evidence, limits, and practical next steps.`},"okf-build":{name:`Build site knowledge for agents`,description:`Create a compact, cited site knowledge pack from public pages in a crawl.`,useWhen:[`An agent needs reliable site facts with links back to source pages.`,`You want a portable knowledge pack from selected crawl content.`],avoidWhen:[`The crawl contains private, stale, or irrelevant pages.`,`You want the tool to invent missing facts.`],outcome:`An OKF manifest and optional markdown files with source links.`},"okf-validate":{name:`Validate a site knowledge pack`,description:`Check supplied OKF markdown files for structure, references, and broken links before use.`,useWhen:[`You created or edited an OKF pack and want to check it.`,`An agent needs to verify files before loading them.`],avoidWhen:[`You need to build the pack from a crawl first.`],outcome:`A validation result with exact file, reference, and link errors.`},"page-opportunities":{name:`Opportunities for one page`,description:`Find Search Console queries and page evidence that point to useful improvements for one URL.`,useWhen:[`You want to improve an existing page using its own search demand.`,`You need query themes and page checks in one result.`],avoidWhen:[`The page has no useful Search Console data.`,`You want a guaranteed traffic gain.`],outcome:`A ranked page improvement list tied to real queries and current content.`},"performance-audit":{name:`Page performance audit`,description:`Run Lighthouse lab checks and add CrUX field Core Web Vitals when that data is available.`,useWhen:[`You need actionable performance diagnostics for a page.`,`You want lab findings kept separate from real-user field data.`],avoidWhen:[`You want Lighthouse scores presented as field Core Web Vitals.`,`The page cannot be fetched from the local machine.`],outcome:`Lab diagnostics plus clearly labelled field metrics when available.`},"pseo-audit":{name:`Programmatic SEO audit`,description:`Review repeated page templates using search demand, crawl evidence, and optional Google index checks.`,useWhen:[`A site publishes many pages from repeatable templates.`,`You need to compare template groups before expanding or pruning them.`],avoidWhen:[`You want every repeated path labelled as spam or thin content.`,`There are too few pages to compare as a template group.`],outcome:`Template-level demand, quality, and index evidence with representative URLs.`},"query-clusters":{name:`Search query clusters`,description:`Group related Search Console queries so repeated demand themes are easier to review.`,useWhen:[`A large query list needs a clearer topic structure.`,`You want to find repeated wording within a site section.`],avoidWhen:[`You need semantic or intent classification beyond shared query terms.`,`You want clusters turned into new pages automatically.`],outcome:`Stable query groups with the search demand behind each theme.`},"quick-wins":{name:`Page-one CTR opportunities`,description:`Find page-one query and URL pairs whose CTR deserves a closer look.`,useWhen:[`You want high-impression title and snippet review candidates.`,`You need page-one opportunities ranked from Search Console data.`],avoidWhen:[`You want a promised click gain.`,`You plan to treat a CTR heuristic as a Google requirement.`],outcome:`A ranked review queue with CTR, position, impressions, and comparison evidence.`},"redirect-trace":{name:`Redirect trace`,description:`Follow every redirect hop and check the final page, indexability, and canonical signals.`,useWhen:[`A URL redirects unexpectedly or through several hops.`,`You need to verify the final destination after a migration.`],avoidWhen:[`You need to discover redirect issues across a whole site.`,`The URL requires a signed-in browser session.`],outcome:`The complete redirect chain with final response and page checks.`},"refresh-priorities":{name:`Content refresh priorities`,description:`Combine search declines and near-term opportunities into a ranked content review queue.`,useWhen:[`You need to decide which existing pages to review first.`,`You want several Search Console signals ranked in one queue.`],avoidWhen:[`You need a technical crawl plan.`,`You want the ranking treated as a traffic forecast.`],outcome:`A focused list of pages and queries to investigate next, with reasons.`},"search-performance-overview":{name:`Search performance overview`,description:`Find where Google Search performance changed and choose the next focused report to inspect it.`,useWhen:[`You are starting an investigation and do not yet know which pages or queries moved.`,`You want one view of declines, gains, overlap, and near-term opportunities.`],avoidWhen:[`You already know the exact question and can run a focused report.`,`You need a technical crawl without Search Console analysis.`],outcome:`A clear search performance summary with a short list of next reports.`},"second-page":{name:`Second-page opportunities`,description:`Find URLs averaging positions 11 to 20 and see the queries behind each opportunity.`,useWhen:[`You want existing pages that may deserve closer review near page one.`,`You need page-level evidence across all matching queries.`],avoidWhen:[`You want average position treated as a fixed rank.`,`You need proof that a change will move the page to page one.`],outcome:`A ranked URL list with query, click, impression, and position evidence.`},"segment-impact":{name:`Search impact by segment`,description:`Compare page or query groups across equal periods to find which parts of a site moved.`,useWhen:[`A traffic change may be concentrated in one directory, page type, or query group.`,`You need to separate broad movement from a local segment issue.`],avoidWhen:[`The periods are incomplete or not comparable.`,`Missing Search Console rows would be mistaken for zero activity.`],outcome:`A segment comparison showing where clicks and impressions changed.`},"seo-to-ai-query":{name:`AI monitoring prompt ideas`,description:`Turn real Search Console query wording into a repeatable set of prompts for external AI monitoring.`,useWhen:[`You need prompt ideas grounded in the way people already find the site.`,`You want a stable prompt set for manual or third-party monitoring.`],avoidWhen:[`You want proof of demand inside AI products.`,`You want citation or visibility results from this report.`],outcome:`A prompt set linked back to its source queries and search metrics.`},"setup-check":{name:`Check local setup`,description:`Check Google login, scopes, local configuration, and saved defaults before running reports.`,useWhen:[`Authentication or site selection is failing.`,`You want to verify the local setup before investigating report errors.`],avoidWhen:[`You need SEO findings rather than a setup check.`],outcome:`A local setup status with the exact problems that need fixing.`},"site-crawl":{name:`Technical SEO site crawl`,description:`Crawl a site, check technical SEO signals, and save a local baseline for follow-up reports.`,useWhen:[`You need to discover technical issues across linked pages.`,`You want a crawl snapshot for later filtering or comparison.`],avoidWhen:[`You only need to check one page or a known URL list.`,`Important content requires browser rendering that is not enabled.`],outcome:`A crawl summary with page evidence, technical findings, and saved report ID.`},"striking-distance":{name:`Queries near page one`,description:`Find Search Console query and page pairs averaging positions 11 to 20.`,useWhen:[`You want existing search demand close to page one.`,`You need a compact list of queries and URLs for content review.`],avoidWhen:[`You want average position treated as an exact daily rank.`,`You need a promised ranking or click gain.`],outcome:`A ranked list of near-page-one query and URL pairs with search evidence.`},"technical-watch":{name:`Technical SEO watch`,description:`Run crawl and index checks together to catch technical regressions on a schedule.`,useWhen:[`You want recurring checks after releases or site changes.`,`You need crawl changes and selected Google index checks in one run.`],avoidWhen:[`You have not chosen any crawl, index, or link recovery input.`,`You need a one-off deep audit rather than ongoing monitoring.`],outcome:`A combined technical change report with failures and skipped checks kept separate.`},"top-fixes":{name:`Top technical SEO fixes`,description:`Turn crawl findings into a short, ranked queue of technical issues to investigate first.`,useWhen:[`A crawl contains too many findings to review at once.`,`You need representative URLs and reasons for the highest priorities.`],avoidWhen:[`You need every affected URL for one rule.`,`You plan to treat rank as proof of business impact.`],outcome:`A focused technical fix queue with issue evidence and example URLs.`},"traffic-anomaly":{name:`Unusual search traffic changes`,description:`Find recent clicks or impressions that moved beyond the site's normal Search Console pattern.`,useWhen:[`You want an early signal of an unusual search change.`,`You need to know whether recent movement stands out from normal variation.`],avoidWhen:[`There is too little history for a useful baseline.`,`You want the report to identify the cause of the change.`],outcome:`A statistical movement signal with the dates, baseline, and limits shown.`},"update-correlation":{name:`Google update correlation`,description:`Compare search movement dates with official Google ranking update windows.`,useWhen:[`A traffic change overlaps a confirmed Google ranking update.`,`You need a timeline before investigating affected pages and queries.`],avoidWhen:[`You want overlap presented as proof of causation.`,`The traffic window does not cover the update dates.`],outcome:`A traffic and update timeline with overlaps clearly labelled as correlation.`},"update-postmortem":{name:`Google update impact review`,description:`Find the pages and queries that gained or lost around a confirmed Google ranking update.`,useWhen:[`You need winners and losers around a known update window.`,`You want to compare affected segments and known site changes.`],avoidWhen:[`You want the report to claim the update caused every change.`,`The comparison periods are incomplete or contain major unrecorded changes.`],outcome:`A post-update review of winners, losers, confounders, and next checks.`}};function CL(e){let t=SL[e],n=xL[e];if(!(!t||!n))return{...t,...n}}function wL(e){e.registerTool(`seo_audit_page`,{description:`Run a single-page technical and content audit`,inputSchema:{url:D.string().url(),site:D.string().optional(),js:D.boolean().optional(),refresh:D.boolean().optional()}},async({url:e,site:t,js:n,refresh:r})=>{try{let i=await S_({url:e,site:t,js:rL(n,`auto`),refresh:r});return $(`Audit complete for ${e}. Found ${i.issues.length} issues.`,i)}catch(e){return Q(e)}})}const TL=aL([`verifyContent`,`verifyLimit`,`js`,`fetchConcurrency`,`fetchIntervalCap`,`fetchIntervalMs`,`refresh`]);function EL(e){return{verifyContent:e.verifyContent,verifyLimit:e.verifyLimit,js:rL(e.js,void 0),rate:uL({fetchConcurrency:e.fetchConcurrency,fetchIntervalCap:e.fetchIntervalCap,fetchIntervalMs:e.fetchIntervalMs}),refresh:e.refresh}}function DL(e){e.registerTool(`seo_monthly_report`,{description:`Generate a monthly SEO report narrative`,inputSchema:{...aL([`site`,`limit`,`includeBrand`]),month:nL.optional(),...TL}},async({site:e,month:t,limit:n,includeBrand:r,...i})=>{try{let a=await JP({site:e,month:t,limit:n,includeBrand:r,...EL(i)});return $(`Monthly report generated for ${a.month}. ${a.headline}`,{...a,presentation:YP(a)},{markdown:a.markdown})}catch(e){return Q(e)}})}function OL(e){e.registerTool(`seo_report_narrative`,{description:`Generate a client-ready SEO narrative across diagnosis, changes, and monitoring`,inputSchema:{...aL([`site`,`days`,`recentDays`,`limit`,`includeBrand`]),startDate:tL.optional(),endDate:tL.optional(),changeLimit:D.number().optional(),...TL}},async({site:e,days:t,recentDays:n,startDate:r,endDate:i,limit:a,changeLimit:o,includeBrand:s,...c})=>{try{let l=await qP({site:e,days:t,recentDays:n,startDate:r,endDate:i,limit:a,changeLimit:o,includeBrand:s,...EL(c)});return $(l.headline,{...l,presentation:YP(l)},{markdown:l.markdown})}catch(e){return Q(e)}})}function kL(e){e.registerTool(`seo_second_page`,{description:`Find URLs with GSC average position above 10 through 20 and return evidence-grounded investigation prompts`,inputSchema:{...aL([`site`,`range`,`minImpressions`,`limit`,`includeBrand`]),...TL,range:D.number().int().min(1).max(548).optional(),minImpressions:D.number().int().min(0).max(1e9).optional(),limit:D.number().int().min(1).max(100).optional(),verifyLimit:D.number().int().min(0).max(100).optional(),brandTerms:D.array(D.string().trim().min(1).max(200)).max(20).optional(),fetchConcurrency:D.number().int().min(1).max(16).optional(),fetchIntervalCap:D.number().int().min(1).max(60).optional(),fetchIntervalMs:D.number().int().min(100).max(6e4).optional()}},async({site:e,range:t,minImpressions:n,limit:r,includeBrand:i,brandTerms:a,...o})=>{try{let s=await gF({site:e,range:t,minImpressions:n,limit:r,includeBrand:i,brandTerms:a,...EL(o)});return $(s.summary.verdict,s)}catch(e){return Q(e)}})}function AL(e){OL(e),DL(e),wL(e),kL(e)}const jL=D.strictObject({site:D.string().trim().min(1).max(2048),startUrl:D.string().url().optional(),urls:D.array(D.string().url()).min(1).max(100).optional(),sitemaps:D.array(D.string().url()).min(1).max(20).optional(),properties:D.array(D.string().trim().min(1).max(2048)).min(1).max(1e3).optional(),limit:D.number().int().min(1).max(25e4).optional(),refresh:D.boolean().optional(),js:D.boolean().optional(),languageCode:D.string().trim().min(1).max(35).optional(),dailyLimit:D.number().int().min(1).max(2e3).optional(),inspectLimit:D.number().int().min(1).max(100).optional(),maxUrls:D.number().int().min(1).max(25e4).optional(),recoverLinks:D.boolean().optional(),recoverDays:D.number().int().min(1).max(548).optional(),recoverLimit:D.number().int().min(1).max(100).optional(),recoverMinClicks:D.number().int().min(0).max(1e9).optional(),recoverMinImpressions:D.number().int().min(0).max(1e9).optional()}).superRefine((e,t)=>{let n=!!(e.urls?.length||e.sitemaps?.length);!e.startUrl&&!n&&e.recoverLinks===!1&&t.addIssue({code:`custom`,path:[`recoverLinks`],message:`Pass startUrl, urls, sitemaps, or enable link recovery for technical-watch.`})});function ML(e){return $(e.summary,{...e,presentation:tI(e)},{markdown:nI(e)})}function NL(e){e.registerTool(`seo_workflow_diagnose_property`,{description:`Run the full agent workflow for property diagnosis and next actions`,inputSchema:{...aL([`site`,`days`,`recentDays`,`limit`,`includeBrand`,`refresh`])}},async({site:e,days:t,recentDays:n,limit:r,includeBrand:i,refresh:a})=>{try{return ML(await YF({site:e,days:t,recentDays:n,limit:r,includeBrand:i,refresh:a}))}catch(e){return Q(e)}}),e.registerTool(`seo_workflow_monthly_report`,{description:`Run the monthly reporting workflow with next actions`,inputSchema:{...aL([`site`,`limit`,`includeBrand`,`refresh`]),month:nL.optional()}},async({site:e,month:t,limit:n,includeBrand:r,refresh:i})=>{try{return ML(await rI({site:e,month:t,limit:n,includeBrand:r,refresh:i}))}catch(e){return Q(e)}}),e.registerTool(`seo_workflow_update_postmortem`,{description:`Run winner/loser postmortem analysis for recent Google update exposure`,inputSchema:{...aL([`site`,`days`,`recentDays`,`limit`,`includeBrand`,`refresh`]),knownConfounders:D.array(D.string()).optional(),includeChangeLog:D.boolean().optional()}},async({site:e,days:t,recentDays:n,limit:r,includeBrand:i,knownConfounders:a,includeChangeLog:o,refresh:s})=>{try{return ML(await FI({site:e,days:t,recentDays:n,limit:r,includeBrand:i,knownConfounders:a,includeChangeLog:o,refresh:s}))}catch(e){return Q(e)}}),e.registerTool(`seo_workflow_technical_watch`,{description:`Run scheduled-style crawl-diff and index-watch monitoring in one workflow`,inputSchema:jL},async({site:e,startUrl:t,urls:n,sitemaps:r,properties:i,limit:a,refresh:o,js:s,languageCode:c,dailyLimit:l,inspectLimit:u,maxUrls:d,recoverLinks:f,recoverDays:p,recoverLimit:m,recoverMinClicks:h,recoverMinImpressions:g})=>{try{return ML(await vI({site:e,startUrl:t,urls:n,sitemaps:r,properties:i,limit:a,refresh:o,js:rL(s,`auto`),languageCode:c,dailyLimit:l,inspectLimit:u,maxUrls:d,recoverLinks:f,recoverDays:p,recoverLimit:m,recoverMinClicks:h,recoverMinImpressions:g}))}catch(e){return Q(e)}}),e.registerTool(`seo_workflow_refresh_priorities`,{description:`Rank decay, striking-distance, quick-win, cannibalisation, and diagnosis signals into an action queue`,inputSchema:{...aL([`site`,`days`,`recentDays`,`limit`,`includeBrand`,`verifyContent`,`verifyLimit`,`refresh`]),ga4PropertyId:D.string().optional()}},async({site:e,days:t,recentDays:n,limit:r,includeBrand:i,ga4PropertyId:a,verifyContent:o,verifyLimit:s,refresh:c})=>{try{return ML(await gI({site:e,days:t,recentDays:n,limit:r,includeBrand:i,ga4PropertyId:a,verifyContent:o,verifyLimit:s,refresh:c}))}catch(e){return Q(e)}})}const PL=[`ai-search`,`crawl`,`diagnosis`,`experiments`,`monitoring`,`opportunities`,`reporting`,`setup`,`workflows`],FL=[{category:`setup`,register:hL,names:[`seo_doctor`]},{category:`diagnosis`,register:hL,names:[`seo_segment_impact`,`seo_striking_distance`,`seo_traffic_anomaly`,`seo_update_correlate`]},{category:`opportunities`,register:vL,names:[`seo_cannibal`,`seo_ctr_underperformers`,`seo_decaying`,`seo_internal_links`,`seo_query_cluster`,`seo_quick_wins`]},{category:`ai-search`,register:lL,names:[`seo_ai_referrals`,`seo_community_intent`,`seo_content_optimization`,`seo_page_opportunities`,`seo_performance_audit`,`seo_to_ai_query`]},{category:`ai-search`,register:mL,names:[`seo_ai_search_scorecard`]},{category:`crawl`,register:mL,names:[`seo_affected_urls`,`seo_agent_readiness`,`seo_ai_readiness`,`seo_audit_urls`,`seo_compare_crawl_reports`,`seo_crawl_site`,`seo_entity_readiness`,`seo_explain_issue`,`seo_geo_gaps`,`seo_get_crawl_report`,`seo_list_crawl_reports`,`seo_list_rules`,`seo_llms_txt_audit`,`seo_llms_txt_generate`,`seo_okf_build`,`seo_okf_validate`,`seo_top_fixes`]},{category:`monitoring`,register:_L,names:[`seo_crawl_diff`,`seo_index_coverage`,`seo_index_coverage_plan`,`seo_index_monitor`,`seo_index_watch`,`seo_link_recover`,`seo_redirect_trace`]},{category:`reporting`,register:AL,names:[`seo_audit_page`,`seo_monthly_report`,`seo_report_narrative`,`seo_second_page`]},{category:`experiments`,register:gL,names:[`seo_measure_change`]},{category:`reporting`,register:bL,names:[`seo_pseo_audit`]},{category:`workflows`,register:NL,names:[`seo_workflow_diagnose_property`,`seo_workflow_monthly_report`,`seo_workflow_refresh_priorities`,`seo_workflow_technical_watch`,`seo_workflow_update_postmortem`]}],IL={cannibal:`cannibalisation`,decaying:`decaying-pages`,"crawl-site":`site-crawl`,"link-recover":`link-recovery`,"llms-txt-generate":`generate-llms-txt`,"query-cluster":`query-clusters`,"report-narrative":`narrative-report`,"to-ai-query":`seo-to-ai-query`,"workflow-refresh-priorities":`refresh-priorities`,"workflow-technical-watch":`technical-watch`,"workflow-update-postmortem":`update-postmortem`,"workflow-diagnose-property":`search-performance-overview`,"workflow-monthly-report":`monthly-action-plan`,doctor:`setup-check`,"update-correlate":`update-correlation`,"compare-crawl-reports":`compare-crawls`,"list-crawl-reports":`crawl-history`,"list-rules":`crawler-rules`,"get-crawl-report":`crawl-report`,"explain-issue":`explain-crawl-issue`};function LL(e){let t=e.replace(/^seo_/,``).replaceAll(`_`,`-`);return IL[t]??t}function RL(e,t){return e.id<t.id?-1:+(e.id>t.id)}function zL(e){let t=new Map;return e({registerTool(e,n,r){t.set(e,{inputSchema:n.inputSchema,handler:r})}}),t}function BL(e){return e instanceof D.ZodObject?e:D.strictObject(e??{})}function VL(){let e=new Map,t=[];for(let n of FL){let r=e.get(n.register);r||(r=zL(n.register),e.set(n.register,r));for(let e of n.names){let i=r.get(e);if(!i)throw Error(`MCP report registry references missing tool ${e}.`);let a=LL(e),o=CL(a);if(!o)throw Error(`MCP report registry is missing guidance for ${a}.`);t.push({id:a,category:n.category,...o,inputSchema:BL(i.inputSchema),handler:i.handler})}}return t.sort(RL)}const HL=VL(),UL=new Set(HL.map(e=>e.id)),WL=Object.keys(SL).filter(e=>!UL.has(e));if(WL.length>0)throw Error(`MCP report guidance references unknown reports: ${WL.join(`, `)}.`);const GL=new Map(HL.map(e=>[e.id,e]));function KL(e){return HL.filter(t=>!e||t.category===e).map(({id:e,name:t,description:n,category:r})=>({id:e,category:r,name:t,description:n}))}function qL(e){return GL.get(e)}function JL(e){return e.issues.map(e=>`${e.path.length>0?e.path.join(`.`):`input`}: ${e.message}`).join(`; `)}function YL(e){return KL(e)}function XL(e){let t=qL(e);if(!t)throw new k(`INVALID_INPUT`,`Unknown report: ${e}.`);return{id:t.id,category:t.category,name:t.name,description:t.description,useWhen:t.useWhen,avoidWhen:t.avoidWhen,outcome:t.outcome,readOrder:t.readOrder,doNotClaim:t.doNotClaim,verify:t.verify,related:t.related,inputSchema:D.toJSONSchema(t.inputSchema)}}async function ZL(e,t={}){let n=qL(e);if(!n)throw new k(`INVALID_INPUT`,`Unknown report: ${e}.`);let r=n.inputSchema.safeParse(t);if(!r.success)throw new k(`INVALID_INPUT`,`Invalid parameters for ${e}: ${JL(r.error)}`);return n.handler(r.data)}async function QL(e,t={}){try{return await ZL(e,t)}catch(e){return Q(e)}}const $L=D.looseObject({});function eR(e){e.registerTool(`seo_list_reports`,{description:`List compact SEO report ids, names, and purposes, optionally by category`,inputSchema:{category:D.enum(PL).optional()},outputSchema:$L,annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0}},async({category:e})=>{let t=YL(e),n=[...new Set(t.map(e=>e.category))];return $(`${t.length} SEO reports available across ${n.length} categories.`,{reports:t,categories:n})}),e.registerTool(`seo_describe_report`,{description:`Explain when to use one SEO report, what it returns, and which parameters it accepts`,inputSchema:{id:D.string().trim().min(1).max(100)},outputSchema:$L,annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0}},async({id:e})=>{try{let t=XL(e);return $(`${t.id}: ${t.description}`,{report:t})}catch(e){return Q(e)}}),e.registerTool(`seo_run_report`,{description:`Run one SEO report by id with parameters from seo_describe_report`,inputSchema:{id:D.string().trim().min(1).max(100),params:D.record(D.string(),D.unknown()).optional()},outputSchema:$L,annotations:{destructiveHint:!1}},async({id:e,params:t})=>QL(e,t))}function tR(){let e=new le({name:`seo`,version:eL},{capabilities:{logging:{}}});return eR(e),e}async function nR(e={}){let t=tR();if(e.test){process.stdout.write(`seo MCP server constructed successfully.
711
+ VALUES (?, ?, ?, ?, ?, ?, ?)`).run(e,u,JSON.stringify(c),JSON.stringify(g),_,Date.now(),Date.now()+i),{data:g,usage:{provider:`Semrush`,units:_,unitLabel:`units`,estimatedUsd:JI(_),calls:1}}}function XI(e){let[t,n]=e;if(!t||!n)return{phrase:``};let r=Object.fromEntries(t.map((e,t)=>[e,n[t]]));return{phrase:r.Ph??``,volume:Number(r.Nq??0)||void 0,cpc:Number(r.Cp??0)||void 0,competition:Number(r.Co??0)||void 0,difficulty:Number(r.Kd??0)||void 0,results:Number(r.Nr??0)||void 0}}function ZI(e){let[t,...n]=e;return t?n.map(e=>{let n=Object.fromEntries(t.map((t,n)=>[t,e[n]]));return{phrase:n.Ph??``,volume:Number(n.Nq??0)||void 0,difficulty:Number(n.Kd??0)||void 0,cpc:Number(n.Cp??0)||void 0,competition:Number(n.Co??0)||void 0,url:n.Ur,domain:n.Dn,position:Number(n.Po??0)||void 0}}):[]}var QI=class{name=`semrush`;capabilities={overview:!0,batchOverview:!0,related:!0,broadMatch:!0,questions:!0,difficulty:!0,urlKeywords:!0,domainKeywords:!0,maxBatchSize:100};async keywordOverview(e,t={}){return YI(`phrase_this`,{phrase:e,database:t.database??`us`,export_columns:`Ph,Nq,Cp,Co,Nr,Td,Kd`},XI,7*864e5,10,t.refresh)}async batchKeywordOverview(e,t={}){return YI(`phrase_these`,{phrase:e.join(`;`),database:t.database??`us`,export_columns:`Ph,Nq,Cp,Co,Nr,Td,Kd`},e=>ZI(e).map(e=>({...e,phrase:e.phrase})),7*864e5,10,t.refresh)}async relatedKeywords(e,t={}){return YI(`phrase_related`,{phrase:e,database:t.database??`us`,display_limit:20,export_columns:`Ph,Nq,Kd,Cp,Co`},ZI,14*864e5,40,t.refresh)}async questions(e,t={}){return YI(`phrase_questions`,{phrase:e,database:t.database??`us`,display_limit:20,export_columns:`Ph,Nq,Kd,Cp,Co`},ZI,14*864e5,40,t.refresh)}async keywordDifficulty(e,t={}){return YI(`phrase_kdi`,{phrase:e.join(`;`),database:t.database??`us`,export_columns:`Ph,Kd`},e=>ZI(e).map(e=>({phrase:e.phrase,kd:e.difficulty??0})),7*864e5,50,t.refresh)}async domainKeywords(e,t={}){return YI(`domain_organic`,{domain:e,database:t.database??`us`,display_limit:100,export_columns:`Ph,Nq,Cp,Co,Kd,Po,Ur,Dn`},ZI,7*864e5,10,t.refresh)}async urlKeywords(e,t={}){return YI(`url_organic`,{url:e,database:t.database??`us`,display_limit:100,export_columns:`Ph,Nq,Cp,Co,Kd,Po,Ur,Dn`},ZI,7*864e5,10,t.refresh)}};function $I(e){let t=$e(),n=e??t.providers.prefer;if(n===`authoritative`&&t.providers.semrushApiKey)return new QI;if(n===`cheap`&&t.providers.dataForSeoLogin&&t.providers.dataForSeoPassword)return new KI;if(t.providers.semrushApiKey)return new QI;if(t.providers.dataForSeoLogin&&t.providers.dataForSeoPassword)return new KI}new class{getGoogleOAuthClient(){let e=pt();if(e)return{clientId:e.clientId,clientSecret:e.clientSecret,source:`byo`};let t=$e(),n=process.env.SEO_GOOGLE_CLIENT_ID??process.env.GSC_CLIENT_ID??_t.clientId??t.auth.sharedClientId,r=process.env.SEO_GOOGLE_CLIENT_SECRET??process.env.GSC_CLIENT_SECRET??_t.clientSecret??t.auth.sharedClientSecret;if(!(!n||!r))return{clientId:n,clientSecret:r,source:`shared`}}readGoogleTokens(){return ct()}writeGoogleTokens(e){return lt(e)}deleteGoogleTokens(){return ut()}};const eL=`0.2.4`,tL=D.iso.date(`Use a YYYY-MM-DD date.`),nL=D.string().regex(/^\d{4}-(?:0[1-9]|1[0-2])$/,`Use a YYYY-MM month.`);function rL(e,t){return e===void 0?t:e}const iL={site:{mcp:`site`,schema:D.string()},days:{mcp:`days`,schema:D.number().optional()},recentDays:{mcp:`recentDays`,schema:D.number().optional()},range:{mcp:`range`,schema:D.number().optional()},limit:{mcp:`limit`,schema:D.number().optional()},checkLimit:{mcp:`checkLimit`,schema:D.number().optional()},minImpressions:{mcp:`minImpressions`,schema:D.number().optional()},includeBrand:{mcp:`includeBrand`,schema:D.boolean().optional()},verifyContent:{mcp:`verifyContent`,schema:D.boolean().optional()},verifyLimit:{mcp:`verifyLimit`,schema:D.number().optional()},js:{mcp:`js`,schema:D.boolean().optional()},fetchConcurrency:{mcp:`fetchConcurrency`,schema:D.number().optional()},fetchIntervalCap:{mcp:`fetchIntervalCap`,schema:D.number().optional()},fetchIntervalMs:{mcp:`fetchIntervalMs`,schema:D.number().optional()},refresh:{mcp:`refresh`,schema:D.boolean().optional()}};function aL(e){return Object.fromEntries(e.map(e=>{let t=iL[e];return[t.mcp,t.schema]}))}function oL(e){return JSON.stringify(e,null,2)}function Q(e){let t=Te(e);return{content:[{type:`text`,text:`Error: ${t.error.message}`}],structuredContent:t,isError:!0}}function $(e,t,n={}){return{content:[{type:`text`,text:n.markdown??e}],structuredContent:t}}const sL=D.union([tL,D.string().regex(/^(?:today|yesterday|\d+daysAgo)$/)]).describe(`Use YYYY-MM-DD or a GA4 relative date.`),cL={site:D.string().trim().min(1),days:D.number().int().min(1).max(548).optional(),startDate:tL.optional(),endDate:tL.optional(),limit:D.number().int().min(1).max(100).optional(),minImpressions:D.number().int().min(0).max(1e9).optional(),maxRows:D.number().int().min(1).max(5e4).optional(),brandTerms:D.array(D.string().trim().min(1)).max(100).optional(),includeBrand:D.boolean().optional(),refresh:D.boolean().optional()};function lL(e,t={}){e.registerTool(`seo_ai_referrals`,{description:`Find AI referral traffic detected in GA4. maxRows bounds retained provider rows; resultLimit bounds returned landing pages.`,inputSchema:{property:D.string().trim().min(1),startDate:sL.optional(),endDate:sL.optional(),maxRows:D.number().int().min(1).max(1e5).describe(`Maximum retained GA4 rows. If limit is also supplied, both values must match.`).optional(),limit:D.number().int().min(1).max(1e5).describe(`Legacy alias for maxRows. If maxRows is also supplied, both values must match.`).optional(),resultLimit:D.number().int().min(1).max(1e3).describe(`Maximum ranked landing pages returned. Defaults to 25.`).optional(),refresh:D.boolean().optional()}},async({property:e,startDate:n,endDate:r,maxRows:i,limit:a,resultLimit:o,refresh:s})=>{try{if(i!==void 0&&a!==void 0&&i!==a)throw new k(`INVALID_INPUT`,`maxRows and the legacy limit option must match when both are provided.`);let c=await(t.aiReferralsReport??Un)({property:e,startDate:n,endDate:r,maxRows:i,limit:a,resultLimit:o,refresh:s});return $(`${Z(c.summary.sessions,`AI referral session`)} detected across ${Z(c.summary.sources,`source`)}; evidence is ${c.dataStatus}.`,c)}catch(e){return Q(e)}}),e.registerTool(`seo_page_opportunities`,{description:`Find first-party traffic growth opportunities for one URL from GSC and page content`,inputSchema:{...aL([`site`,`days`,`limit`,`minImpressions`,`includeBrand`,`verifyContent`,`refresh`,`js`]),days:D.number().int().min(1).max(548).optional(),limit:D.number().int().min(1).max(100).optional(),minImpressions:D.number().int().positive().optional(),url:D.string().url()}},async({site:e,url:n,days:r,limit:i,minImpressions:a,includeBrand:o,verifyContent:s,refresh:c,js:l})=>{try{let u=await(t.pageOpportunitiesReport??dy)({site:e,url:n,days:r,limit:i,minImpressions:a,includeBrand:o,verifyContent:s,refresh:c,js:rL(l,`auto`)});return $(u.summary.verdict,u)}catch(e){return Q(e)}}),e.registerTool(`seo_content_optimization`,{description:`Build a content optimization brief for one URL from first-party GSC queries and page content checks`,inputSchema:{...aL([`site`,`days`,`limit`,`minImpressions`,`includeBrand`,`verifyContent`,`refresh`,`js`]),days:D.number().int().min(1).max(548).optional(),limit:D.number().int().min(1).max(100).optional(),minImpressions:D.number().int().positive().optional(),url:D.string().url()}},async({site:e,url:n,days:r,limit:i,minImpressions:a,includeBrand:o,verifyContent:s,refresh:c,js:l})=>{try{let u=await(t.contentOptimizationReport??xy)({site:e,url:n,days:r,limit:i,minImpressions:a,includeBrand:o,verifyContent:s,refresh:c,js:rL(l,`auto`)});return $(u.summary.verdict,u)}catch(e){return Q(e)}}),e.registerTool(`seo_performance_audit`,{description:`Run a local Lighthouse lab audit and optional device-specific CrUX field Core Web Vitals. Configure CrUX with SEO_CRUX_API_KEY.`,inputSchema:{url:D.string().url(),strategy:D.enum([`mobile`,`desktop`]).optional(),refresh:D.boolean().optional()}},async({url:e,strategy:t,refresh:n})=>{try{let r=await YM({url:e,strategy:t,refresh:n});return $(r.headline,r)}catch(e){return Q(e)}}),e.registerTool(`seo_to_ai_query`,{description:`Convert retained GSC queries into deterministic AI monitoring-prompt suggestions with source completeness and heuristic caveats`,inputSchema:cL},async({site:e,days:n,startDate:r,endDate:i,limit:a,minImpressions:o,maxRows:s,brandTerms:c,includeBrand:l,refresh:u})=>{try{let d=await(t.seoToAiQueryReport??kF)({site:e,days:n,startDate:r,endDate:i,limit:a,minImpressions:o,maxRows:s,brandTerms:c,includeBrand:l,refresh:u});return $(`${Z(d.summary.prompts,`monitoring-prompt suggestion`)} generated from ${Z(d.summary.returnedQueries,`retained GSC query`)}; evidence is ${d.dataStatus}.`,d)}catch(e){return Q(e)}}),e.registerTool(`seo_community_intent`,{description:`Classify explicit forum, review, comparison, experience, and recommendation language in retained GSC queries as review hypotheses`,inputSchema:cL},async({site:e,days:n,startDate:r,endDate:i,limit:a,minImpressions:o,maxRows:s,brandTerms:c,includeBrand:l,refresh:u})=>{try{let d=await(t.communityIntentReport??rv)({site:e,days:n,startDate:r,endDate:i,limit:a,minImpressions:o,maxRows:s,brandTerms:c,includeBrand:l,refresh:u});return $(`Evidence status: ${d.dataStatus}. ${d.summary.verdict}`,d)}catch(e){return Q(e)}})}function uL(e){if(!(e.fetchConcurrency===void 0&&e.fetchIntervalCap===void 0&&e.fetchIntervalMs===void 0))return{concurrency:e.fetchConcurrency,intervalCap:e.fetchIntervalCap,intervalMs:e.fetchIntervalMs}}function dL(e,t={}){let n=e.requestEvidenceStatus===`available`?`Processed ${e.requests.length} URL requests into ${e.summary.totalPages} unique documents.`:e.requestEvidenceStatus===`partial`?`Observed ${e.requests.length} URL requests and retained ${e.summary.totalPages} unique documents; some started requests were still in flight when the crawl stopped.`:`Loaded ${e.summary.totalPages} unique documents; request evidence is unavailable for this legacy report.`,r={id:e.id,definitionId:e.definitionId,headline:`${n} Found ${e.summary.highIssues} high, ${e.summary.mediumIssues} medium, and ${e.summary.lowIssues} low issues.`,status:e.status,requestEvidenceStatus:e.requestEvidenceStatus,configHash:e.configHash,summary:e.summary,dataSources:e.dataSources,ai:e.ai?{robotsTxt:e.ai.robotsTxt,llmsTxt:e.ai.llmsTxt,agentResources:e.ai.agentResources}:void 0,topFixes:Mw(e,{limit:10}),reviewObservations:Nw(e,{limit:10}),warnings:e.warnings,caveats:e.caveats};return t.includeIssues&&(r.issues=e.issues),t.includePages&&(r.requests=e.requests,r.pages=e.pages),r}function fL(e){if(!e.value||e.value===`latest`||e.value===`previous`){let t=vS({site:e.site,limit:20}).filter(t=>t.id!==e.skipId),n=e.value===`previous`?t[1]:t[0];return n?yS(n.id):void 0}return yS(e.value)}function pL(e,t){if(e&&t)throw new k(`INVALID_INPUT`,`Use either url or reportId, not both.`)}function mL(e){e.registerTool(`seo_crawl_site`,{description:`Crawl a site and run technical SEO checks. The compact result separates prioritised fixes from review observations. Set includePages/includeIssues for raw data.`,inputSchema:{url:D.string().url(),site:D.string().optional(),ga4PropertyId:D.string().optional(),maxPages:D.number().int().positive().optional(),maxDepth:D.number().int().nonnegative().optional(),concurrency:D.number().int().positive().optional(),fetchIntervalCap:D.number().int().positive().optional(),fetchIntervalMs:D.number().int().positive().optional(),refresh:D.boolean().optional(),include:D.array(D.string()).optional(),exclude:D.array(D.string()).optional(),useSitemap:D.boolean().optional(),respectRobots:D.boolean().optional(),js:D.boolean().optional(),includePages:D.boolean().optional(),includeIssues:D.boolean().optional(),saveReport:D.boolean().optional()}},async({url:e,site:t,ga4PropertyId:n,maxPages:r,maxDepth:i,concurrency:a,fetchIntervalCap:o,fetchIntervalMs:s,refresh:c,include:l,exclude:u,useSitemap:d,respectRobots:f,js:p,includePages:m,includeIssues:h,saveReport:g})=>{try{let _=await YC({url:e,site:t,ga4PropertyId:n,maxPages:r,maxDepth:i,concurrency:a,refresh:c,fetchRate:uL({fetchConcurrency:a,fetchIntervalCap:o,fetchIntervalMs:s}),include:l,exclude:u,useSitemap:d,respectRobots:f,js:!!p}),v=g?_S(_):void 0;return $(`Crawl complete for ${e}. Found ${_.issues.length} issues across ${_.summary.totalPages} pages.`,{...dL(_,{includePages:m,includeIssues:h}),...v?{saved:v}:{}})}catch(e){return Q(e)}}),e.registerTool(`seo_audit_urls`,{description:`Audit an explicit list of URLs with technical SEO checks. The compact result separates prioritised fixes from review observations. Set includePages/includeIssues for raw data.`,inputSchema:{urls:D.array(D.string().url()).min(1),site:D.string().optional(),ga4PropertyId:D.string().optional(),maxPages:D.number().int().positive().optional(),concurrency:D.number().int().positive().optional(),fetchIntervalCap:D.number().int().positive().optional(),fetchIntervalMs:D.number().int().positive().optional(),refresh:D.boolean().optional(),includePages:D.boolean().optional(),includeIssues:D.boolean().optional(),saveReport:D.boolean().optional()}},async({urls:e,site:t,ga4PropertyId:n,maxPages:r,concurrency:i,fetchIntervalCap:a,fetchIntervalMs:o,refresh:s,includePages:c,includeIssues:l,saveReport:u})=>{try{let d=await YC({url:e[0]??``,urls:e,mode:`list`,site:t,ga4PropertyId:n,maxPages:r??e.length,concurrency:i,refresh:s,fetchRate:uL({fetchConcurrency:i,fetchIntervalCap:a,fetchIntervalMs:o}),useSitemap:!1}),f=u?_S(d):void 0;return $(`URL audit complete. Found ${d.issues.length} issues across ${d.summary.totalPages} pages.`,{...dL(d,{includePages:c,includeIssues:l}),...f?{saved:f}:{}})}catch(e){return Q(e)}}),e.registerTool(`seo_top_fixes`,{description:`Return top technical SEO fixes for a URL by running a compact crawl.`,inputSchema:{url:D.string().url().optional(),reportId:D.string().optional(),site:D.string().optional(),maxPages:D.number().int().positive().optional(),maxDepth:D.number().int().nonnegative().optional(),fetchIntervalCap:D.number().int().positive().optional(),fetchIntervalMs:D.number().int().positive().optional(),refresh:D.boolean().optional(),category:D.string().optional(),limit:D.number().int().min(1).max(1e3).optional()}},async({url:e,reportId:t,site:n,maxPages:r,maxDepth:i,fetchIntervalCap:a,fetchIntervalMs:o,refresh:s,category:c,limit:l})=>{try{pL(e,t);let u=e?await YC({url:e,site:n,maxPages:r,maxDepth:i,refresh:s,fetchRate:uL({fetchIntervalCap:a,fetchIntervalMs:o})}):t?yS(t):xS(n);if(!u)return Q(`No crawl report found. Pass url, reportId, or run seo_crawl_site with saveReport first.`);let d=Mw(u,{category:c,limit:l});return $(`Found ${d.length} top fix groups for ${u.config.url}.`,{url:u.config.url,reportId:u.id,summary:u.summary,dataSources:u.dataSources,topFixes:d,warnings:u.warnings,caveats:u.caveats})}catch(e){return Q(e)}}),e.registerTool(`seo_explain_issue`,{description:`Explain a crawler rule in plain English: why it matters, how to fix it, and how to verify it.`,inputSchema:{ruleId:D.string()}},async({ruleId:e})=>{let t=tx(e);return t?$(`Rule guidance for ${e}.`,t):Q(`Unknown rule: ${e}`)}),e.registerTool(`seo_list_rules`,{description:`List crawler rule ids and guidance metadata. Use this before the explain-crawl-issue report when you need valid rule ids.`,inputSchema:{category:OT.optional()}},async({category:e})=>{let t=ex().filter(t=>!e||t.category===e);return $(`Found ${t.length} crawler rules.`,{rules:t})}),e.registerTool(`seo_affected_urls`,{description:`Return affected URLs for a saved or freshly crawled report, filtered by rule, category, or severity.`,inputSchema:{url:D.string().url().optional(),reportId:D.string().optional(),site:D.string().optional(),ruleId:D.string().optional(),category:D.string().optional(),severity:D.string().optional(),maxPages:D.number().int().positive().optional(),fetchIntervalCap:D.number().int().positive().optional(),fetchIntervalMs:D.number().int().positive().optional(),refresh:D.boolean().optional(),limit:D.number().int().positive().optional()}},async({url:e,reportId:t,site:n,ruleId:r,category:i,severity:a,maxPages:o,fetchIntervalCap:s,fetchIntervalMs:c,refresh:l,limit:u})=>{try{let d=e?await YC({url:e,site:n,maxPages:o,refresh:l,fetchRate:uL({fetchIntervalCap:s,fetchIntervalMs:c})}):t?yS(t):xS(n);if(!d)return Q(`No crawl report found. Pass url, reportId, or run seo_crawl_site with saveReport first.`);let f=Cy(d,{ruleId:r,category:i,severity:a,limit:u});return $(`Returned ${f.selection.returnedRows} of ${f.selection.totalMatchedRows} affected URLs for ${d.config.url}.`,{url:d.config.url,reportId:d.id,dataSources:d.dataSources,affectedUrls:f.affectedUrls,selection:f.selection,warnings:d.warnings,caveats:d.caveats})}catch(e){return Q(e)}}),e.registerTool(`seo_geo_gaps`,{description:`Return crawl, indexability, and page-level snippet controls for Google AI Search eligibility, with optional page observations kept separate. No detected restriction does not guarantee selection or visibility.`,inputSchema:{url:D.string().url().optional(),reportId:D.string().optional(),site:D.string().optional(),maxPages:D.number().int().positive().optional(),fetchIntervalCap:D.number().int().positive().optional(),fetchIntervalMs:D.number().int().positive().optional(),refresh:D.boolean().optional(),limit:D.number().int().positive().optional()}},async({url:e,reportId:t,site:n,maxPages:r,fetchIntervalCap:i,fetchIntervalMs:a,refresh:o,limit:s})=>{try{let c=e?await YC({url:e,site:n,maxPages:r,refresh:o,fetchRate:uL({fetchIntervalCap:i,fetchIntervalMs:a})}):t?yS(t):xS(n);if(!c)return Q(`No crawl report found. Pass url, reportId, or run seo_crawl_site with saveReport first.`);let l=wb(c,{limit:s});return $(`Returned ${l.selection.returnedPages} of ${l.selection.totalMatchedPages} AI Search technical eligibility gap pages for ${c.config.url}; evidence is ${l.dataStatus}.`,{url:c.config.url,reportId:c.id,...l})}catch(e){return Q(e)}}),e.registerTool(`seo_agent_readiness`,{description:`Check a content site for stable Markdown alternatives, agent discovery, crawler access, and identity evidence without an aggregate score.`,inputSchema:{url:D.string().url().optional(),reportId:D.string().optional(),site:D.string().optional(),maxPages:D.number().int().positive().optional(),fetchIntervalCap:D.number().int().positive().optional(),fetchIntervalMs:D.number().int().positive().optional(),refresh:D.boolean().optional()}},async({url:e,reportId:t,site:n,maxPages:r,fetchIntervalCap:i,fetchIntervalMs:a,refresh:o})=>{try{pL(e,t);let s=e?await YC({url:e,site:n,maxPages:r,refresh:o,checkAgentDiscovery:!0,useSitemap:!0,checkExternal:!1,fetchRate:uL({fetchIntervalCap:i,fetchIntervalMs:a})}):t?yS(t):xS(n);if(!s)return Q(`No crawl report found. Pass url, reportId, or run seo_crawl_site with saveReport first.`);let c=rb(s);return $(c.headline,c)}catch(e){return Q(e)}}),e.registerTool(`seo_ai_readiness`,{description:`Return AI-search technical evidence and optional observations without an aggregate score or citation prediction.`,inputSchema:{url:D.string().url().optional(),reportId:D.string().optional(),site:D.string().optional(),maxPages:D.number().int().positive().optional(),fetchIntervalCap:D.number().int().positive().optional(),fetchIntervalMs:D.number().int().positive().optional(),refresh:D.boolean().optional()}},async({url:e,reportId:t,site:n,maxPages:r,fetchIntervalCap:i,fetchIntervalMs:a,refresh:o})=>{try{let s=e?await YC({url:e,site:n,maxPages:r,refresh:o,fetchRate:uL({fetchIntervalCap:i,fetchIntervalMs:a})}):t?yS(t):xS(n);if(!s)return Q(`No crawl report found. Pass url, reportId, or run seo_crawl_site with saveReport first.`);let c=bb(s);return $(c.headline,c)}catch(e){return Q(e)}}),e.registerTool(`seo_ai_search_scorecard`,{description:`Score AI-search technical readiness 0-100 from one crawl using this tool's own weighted checks. The score is a heuristic summary, not a Google or AI-engine requirement, eligibility verdict, or ranking predictor. Unknown checks are excluded and a partial crawl cannot reach 100.`,inputSchema:{url:D.string().url().optional(),reportId:D.string().optional(),site:D.string().optional(),maxPages:D.number().int().positive().optional(),fetchIntervalCap:D.number().int().positive().optional(),fetchIntervalMs:D.number().int().positive().optional(),refresh:D.boolean().optional()}},async({url:e,reportId:t,site:n,maxPages:r,fetchIntervalCap:i,fetchIntervalMs:a,refresh:o})=>{try{let s=e?await YC({url:e,site:n,maxPages:r,refresh:o,fetchRate:uL({fetchIntervalCap:i,fetchIntervalMs:a})}):t?yS(t):xS(n);if(!s)return Q(`No crawl report found. Pass url, reportId, or run seo_crawl_site with saveReport first.`);let c=Xb(s);return $(c.headline,c)}catch(e){return Q(e)}}),e.registerTool(`seo_llms_txt_audit`,{description:`Inspect optional llms.txt presence from a saved or freshly crawled report and return current Google guidance plus candidate pages.`,inputSchema:{url:D.string().url().optional(),reportId:D.string().optional(),site:D.string().optional(),maxPages:D.number().int().positive().optional(),fetchIntervalCap:D.number().int().positive().optional(),fetchIntervalMs:D.number().int().positive().optional(),refresh:D.boolean().optional()}},async({url:e,reportId:t,site:n,maxPages:r,fetchIntervalCap:i,fetchIntervalMs:a,refresh:o})=>{try{let s=e?await YC({url:e,site:n,maxPages:r,refresh:o,fetchRate:uL({fetchIntervalCap:i,fetchIntervalMs:a})}):t?yS(t):xS(n);if(!s)return Q(`No crawl report found. Pass url, reportId, or run seo_crawl_site with saveReport first.`);let c=Mb(s);return $(c.headline,c)}catch(e){return Q(e)}}),e.registerTool(`seo_llms_txt_generate`,{description:`Generate an llms.txt draft from a saved or freshly crawled report. Returns content and metadata.`,inputSchema:{url:D.string().url().optional(),reportId:D.string().optional(),site:D.string().optional(),maxPages:D.number().int().positive().optional(),maxUrls:D.number().int().positive().optional(),tokenBudget:D.number().int().positive().optional(),exclude:D.array(D.string()).optional(),title:D.string().optional(),description:D.string().optional(),fetchIntervalCap:D.number().int().positive().optional(),fetchIntervalMs:D.number().int().positive().optional(),refresh:D.boolean().optional()}},async({url:e,reportId:t,site:n,maxPages:r,maxUrls:i,tokenBudget:a,exclude:o,title:s,description:c,fetchIntervalCap:l,fetchIntervalMs:u,refresh:d})=>{try{let f=e?await YC({url:e,site:n,maxPages:r,refresh:d,fetchRate:uL({fetchIntervalCap:l,fetchIntervalMs:u})}):t?yS(t):xS(n);if(!f)return Q(`No crawl report found. Pass url, reportId, or run seo_crawl_site with saveReport first.`);let p=Nb(f,{maxUrls:i,tokenBudget:a,exclude:o,title:s,description:c});return $(`Generated llms.txt with ${p.includedUrls} URLs.`,p)}catch(e){return Q(e)}}),e.registerTool(`seo_entity_readiness`,{description:`Check schema, sameAs, social profile, author/date, and naming signals from a saved or freshly crawled report.`,inputSchema:{url:D.string().url().optional(),reportId:D.string().optional(),site:D.string().optional(),maxPages:D.number().int().positive().optional(),fetchIntervalCap:D.number().int().positive().optional(),fetchIntervalMs:D.number().int().positive().optional(),refresh:D.boolean().optional()}},async({url:e,reportId:t,site:n,maxPages:r,fetchIntervalCap:i,fetchIntervalMs:a,refresh:o})=>{try{let s=e?await YC({url:e,site:n,maxPages:r,refresh:o,fetchRate:uL({fetchIntervalCap:i,fetchIntervalMs:a})}):t?yS(t):xS(n);if(!s)return Q(`No crawl report found. Pass url, reportId, or run seo_crawl_site with saveReport first.`);let c=fb(s);return $(c.headline,c)}catch(e){return Q(e)}}),e.registerTool(`seo_okf_build`,{description:`Build a compact OKF site knowledge manifest from a saved or freshly crawled report. Set includeFiles for bounded inline markdown.`,inputSchema:{url:D.string().url().optional(),reportId:D.string().optional(),site:D.string().optional(),maxPages:D.number().int().positive().optional(),maxConcepts:D.number().int().min(1).max(100).optional(),includeFiles:D.boolean().optional(),title:D.string().min(1).max(200).optional(),fetchIntervalCap:D.number().int().positive().optional(),fetchIntervalMs:D.number().int().positive().optional(),refresh:D.boolean().optional()}},async({url:e,reportId:t,site:n,maxPages:r,maxConcepts:i,includeFiles:a,title:o,fetchIntervalCap:s,fetchIntervalMs:c,refresh:l})=>{try{let u=e?await YC({url:e,site:n,maxPages:r,refresh:l,fetchRate:uL({fetchIntervalCap:s,fetchIntervalMs:c})}):t?yS(t):xS(n);if(!u)return Q(`No crawl report found. Pass url, reportId, or run seo_crawl_site with saveReport first.`);let d=_T(u,{maxConcepts:i??(a?25:100),title:o}),f=ST(d.files),{files:p,...m}=d;return $(`Built OKF bundle with ${d.conceptCount} concepts.`,{manifest:{...m,filePaths:p.map(e=>e.path)},...a?{files:p}:{},validation:f})}catch(e){return Q(e)}}),e.registerTool(`seo_okf_validate`,{description:`Validate OKF markdown files supplied by an agent. Use the okf-build report to generate files from a crawl.`,inputSchema:{files:D.array(D.object({path:D.string().min(1).max(512),content:D.string().max(2e6)})).min(1).max(5006)}},async({files:e})=>{let t=ST(e);return $(t.valid?`Bundle passes seo OKF checks.`:`Bundle has seo OKF issues.`,{validation:t,explanation:CT(t)})}),e.registerTool(`seo_get_crawl_report`,{description:`Return a saved crawl report by id, or the latest saved report for an optional site. Compact by default.`,inputSchema:{id:D.string().optional(),site:D.string().optional(),includePages:D.boolean().optional(),includeIssues:D.boolean().optional()}},async({id:e,site:t,includePages:n,includeIssues:r})=>{try{let i=e?yS(e):xS(t);return i?$(`Saved crawl report ${i.id}.`,{...dL(i,{includePages:n,includeIssues:r}),url:i.config.url,site:i.site,generatedAt:i.generatedAt}):Q(e?`No saved crawl report found for ${e}.`:`No saved crawl reports found.`)}catch(e){return Q(e)}}),e.registerTool(`seo_list_crawl_reports`,{description:`List saved crawl report metadata, optionally filtered by GSC site/property.`,inputSchema:{site:D.string().optional(),limit:D.number().int().positive().optional()}},async({site:e,limit:t})=>{try{let n=vS({site:e,limit:t});return $(`Found ${n.length} saved crawl reports.`,{reports:n})}catch(e){return Q(e)}}),e.registerTool(`seo_compare_crawl_reports`,{description:`Compare two saved crawl reports and return page, issue, score, and plain-English change summaries.`,inputSchema:{after:D.string().optional(),before:D.string().optional(),site:D.string().optional()}},async({after:e,before:t,site:n})=>{try{let r=fL({value:e??`latest`,site:n});if(!r)return Q(`No newer crawl report found.`);let i=fL({value:t??`previous`,site:r.site??n,skipId:r.id});if(!i)return Q(`No baseline crawl report found. Save at least two reports or pass before.`);let a=oT({before:i,after:r});return $(a.headline,a)}catch(e){return Q(e)}})}function hL(e,t={}){e.registerTool(`seo_doctor`,{description:`Check local auth, scopes, config, and defaults`,inputSchema:{}},async()=>{try{let e=await GI();return $(e.ok?`Local seo setup is ready.`:`Local seo setup needs attention.`,e)}catch(e){return Q(e)}}),e.registerTool(`seo_diagnose_property`,{description:`Run end-to-end property diagnosis across anomaly, update, segment, decay, cannibalisation, and opportunity signals`,inputSchema:{...aL([`site`,`days`,`recentDays`,`limit`,`includeBrand`,`refresh`])}},async({site:e,days:t,recentDays:n,limit:r,includeBrand:i,refresh:a})=>{try{let o=await xk({site:e,days:t,recentDays:n,limit:r,includeBrand:i,refresh:a});return $(`Diagnosis ${o.dataStatus}. Update attribution: ${o.summary.updateAttribution}.`,o)}catch(e){return Q(e)}}),e.registerTool(`seo_segment_impact`,{description:`Compare matched retained GSC segments across adjacent equal-length periods without treating missing rows as zero`,inputSchema:{site:D.string().trim().min(1),days:D.number().int().min(1).max(240).optional(),limit:D.number().int().min(1).max(100).optional(),refresh:D.boolean().optional(),dimension:D.enum([`page`,`query`,`country`,`device`]).optional(),compareDays:D.number().int().min(1).max(240).optional(),startDate:tL.optional(),endDate:tL.optional(),maxRows:D.number().int().min(1).max(25e4).optional(),unmatchedLimit:D.number().int().min(0).max(100).optional()}},async({site:e,dimension:n,days:r,compareDays:i,startDate:a,endDate:o,limit:s,maxRows:c,unmatchedLimit:l,refresh:u})=>{try{let d=await(t.segmentImpact??qE)({site:e,dimension:n,days:r,compareDays:i,startDate:a,endDate:o,limit:s,maxRows:c,unmatchedLimit:l,refresh:u});return $(d.summary.verdict,d)}catch(e){return Q(e)}}),e.registerTool(`seo_striking_distance`,{description:`Find position 11-20 query/page opportunities from GSC`,inputSchema:{...aL([`site`,`days`,`minImpressions`,`limit`,`verifyContent`,`verifyLimit`,`includeBrand`,`js`,`fetchConcurrency`,`fetchIntervalCap`,`fetchIntervalMs`,`refresh`]),days:D.number().int().min(1).max(548).optional(),minImpressions:D.number().int().min(0).max(1e9).optional(),limit:D.number().int().min(1).max(100).optional(),verifyLimit:D.number().int().min(0).max(100).optional(),brandTerms:D.array(D.string().trim().min(1).max(200)).max(20).optional(),fetchConcurrency:D.number().int().min(1).max(16).optional(),fetchIntervalCap:D.number().int().min(1).max(60).optional(),fetchIntervalMs:D.number().int().min(100).max(6e4).optional()}},async({site:e,days:t,minImpressions:n,limit:r,verifyContent:i,verifyLimit:a,includeBrand:o,brandTerms:s,js:c,fetchConcurrency:l,fetchIntervalCap:u,fetchIntervalMs:d,refresh:f})=>{try{let p=await jO({site:e,days:t,minImpressions:n,limit:r,verifyContent:i,verifyLimit:a,includeBrand:o,brandTerms:s,js:rL(c,void 0),rate:uL({fetchConcurrency:l,fetchIntervalCap:u,fetchIntervalMs:d}),refresh:f});return $(p.summary.verdict,p)}catch(e){return Q(e)}}),e.registerTool(`seo_traffic_anomaly`,{description:`Detect statistically significant recent GSC traffic movement`,inputSchema:{...aL([`site`,`days`,`recentDays`,`refresh`])}},async({site:e,days:t,recentDays:n,refresh:r})=>{try{let i=await ck({site:e,days:t,recentDays:n,refresh:r});return $(`${i.anomalies.filter(e=>e.significant).length} significant anomalies found.`,i)}catch(e){return Q(e)}}),e.registerTool(`seo_update_correlate`,{description:`Overlay recent traffic anomalies against official Google ranking update windows`,inputSchema:{...aL([`site`,`days`,`recentDays`,`refresh`]),paddingDays:D.number().optional()}},async({site:e,days:t,recentDays:n,paddingDays:r,refresh:i})=>{try{let a=await lk({site:e,days:t,recentDays:n,paddingDays:r,refresh:i});return $(`Classification: ${a.classification}.`,a)}catch(e){return Q(e)}})}function gL(e){e.registerTool(`seo_content_groups`,{description:`List, create, or delete reusable page/query groups`,inputSchema:{action:D.enum([`list`,`add`,`delete`]),site:D.string().optional(),id:D.string().optional(),name:D.string().optional(),dimension:D.enum([`page`,`query`]).optional(),matchType:D.enum([`equals`,`contains`,`regex`]).optional(),pattern:D.string().optional()}},async({action:e,site:t,id:n,name:r,dimension:i,matchType:a,pattern:o})=>{try{if(e===`list`){let e=Ck(t);return $(`${e.length} content groups found.`,{groups:e})}if(e===`delete`){if(!n)throw Error(`Pass id to delete a content group.`);let e=Tk(n);return $(e?`Content group deleted.`:`Not found.`,{id:n,deleted:e})}if(!t||!r||!o)throw Error(`Pass site, name, and pattern to add a content group.`);let s=Sk({site:t,name:r,dimension:i,matchType:a,pattern:o});return $(`Content group created: ${s.name}.`,s)}catch(e){return Q(e)}}),e.registerTool(`seo_change_log`,{description:`List or record SEO annotations and site changes`,inputSchema:{action:D.enum([`list`,`add`,`delete`]),site:D.string().optional(),id:D.string().optional(),limit:D.number().optional(),scope:D.enum([`site`,`page`,`query`,`group`]).optional(),target:D.string().optional(),title:D.string().optional(),description:D.string().optional(),changedAt:D.string().optional()}},async({action:e,site:t,id:n,limit:r,scope:i,target:a,title:o,description:s,changedAt:c})=>{try{if(e===`list`){let e=BO({site:t,limit:r});return $(`${e.length} changes found.`,{changes:e})}if(e===`delete`){if(!n)throw Error(`Pass id to delete a change.`);let e=HO(n);return $(e?`Change deleted.`:`Not found.`,{id:n,deleted:e})}if(!t||!i||!a||!o)throw Error(`Pass site, scope, target, and title to add a change.`);let l=zO({site:t,scope:i,target:a,title:o,description:s,changedAt:c??new Date().toISOString().slice(0,10)});return $(`Change recorded: ${l.title}.`,l)}catch(e){return Q(e)}}),e.registerTool(`seo_measure_change`,{description:`Measure a saved or ad hoc SEO change with equal finalized GSC windows and optional GA4/control evidence`,inputSchema:{id:D.string().min(1).optional(),site:D.string().min(1).optional(),scope:D.enum([`site`,`page`,`query`,`group`]).optional(),target:D.string().min(1).optional(),title:D.string().min(1).optional(),changedAt:tL.optional(),ga4PropertyId:D.string().min(1).optional(),controlScope:D.enum([`site`,`page`,`query`,`group`]).optional(),controlTarget:D.string().min(1).optional(),controlTitle:D.string().min(1).optional(),beforeDays:D.number().int().min(1).max(548).optional(),afterDays:D.number().int().min(1).max(548).optional(),refresh:D.boolean().optional()}},async({id:e,site:t,scope:n,target:r,title:i,changedAt:a,ga4PropertyId:o,controlScope:s,controlTarget:c,controlTitle:l,beforeDays:u,afterDays:d,refresh:f})=>{try{let p=await $k({id:e,site:t,scope:n,target:r,title:i,changedAt:a,ga4PropertyId:o,controlScope:s,controlTarget:c,controlTitle:l,beforeDays:u,afterDays:d,refresh:f});return $(`Measurement ${p.dataStatus}. Verdict: ${p.verdict}. Compared ${p.window.effectiveDays}/${p.window.requestedDays} finalized days.`,p)}catch(e){return Q(e)}})}function _L(e){e.registerTool(`seo_crawl_diff`,{description:`Crawl a bounded same-origin URL set and compare technical/page changes with the previous run`,inputSchema:{startUrl:D.string().url(),site:D.string().optional(),limit:D.number().optional(),refresh:D.boolean().optional(),js:D.boolean().optional()}},async({startUrl:e,site:t,limit:n,refresh:r,js:i})=>{try{let a=await GA({startUrl:e,site:t,limit:n,refresh:r,js:rL(i,`auto`)});return $(`Crawled ${a.summary.crawled} URLs. ${a.summary.changed} changed vs previous run.`,a)}catch(e){return Q(e)}}),e.registerTool(`seo_index_watch`,{description:`Inspect a bounded URL set against Google's indexed snapshot and separate current issues, regressions, recoveries, and operational failures`,inputSchema:{site:D.string(),urls:D.array(D.string().url()).min(1).max(100),languageCode:D.string().min(1).max(35).optional(),dailyLimit:D.number().int().min(1).max(2e3).optional()}},async({site:e,urls:t,languageCode:n,dailyLimit:r})=>{try{let i=await Zj({site:e,urls:t,languageCode:n,dailyLimit:r});return $(`Inspected ${i.summary.inspected} URLs. ${i.summary.currentIssues} current reviews, ${i.summary.regressions} regressions, ${i.summary.failed} failed, ${i.summary.quotaBlocked} quota-blocked, ${i.summary.deferred} deferred.`,i)}catch(e){return Q(e)}}),e.registerTool(`seo_index_coverage_plan`,{description:`Plan sitemap URL allocation across GSC properties and suggest URL-prefix properties for better URL Inspection coverage`,inputSchema:{site:D.string(),sitemaps:D.array(D.string().url()).min(1).max(20),properties:D.array(D.string()).optional(),dailyLimit:D.number().int().min(1).max(2e3).optional(),targetCycleDays:D.number().int().min(1).max(365).optional(),maxUrls:D.number().int().min(1).max(25e4).optional()}},async({site:e,sitemaps:t,properties:n,dailyLimit:r,targetCycleDays:i,maxUrls:a})=>{try{let o=await Fj({site:e,sitemaps:t,properties:n,dailyLimit:r,targetCycleDays:i,maxUrls:a});return $(`Planned ${o.summary.urlCount} sitemap URLs across ${o.summary.properties} properties. ${o.summary.suggestedProperties} suggested properties.`,o)}catch(e){return Q(e)}}),e.registerTool(`seo_index_coverage`,{description:`Compare a saved crawl and sitemap with Search Console page results, then choose representative URLs for URL Inspection`,inputSchema:{site:D.string().min(1),crawlReportId:D.string().min(1).optional(),sitemaps:D.array(D.string().url()).min(1).max(20).optional(),days:D.number().int().min(1).max(548).optional(),rowLimit:D.number().int().min(1).max(25e4).optional(),maxSitemapUrls:D.number().int().min(1).max(25e4).optional(),itemsPerSection:D.number().int().min(1).max(1e3).optional(),templateClusters:D.number().int().min(1).max(200).optional(),templateSamples:D.number().int().min(1).max(25).optional(),refresh:D.boolean().optional()}},async({site:e,crawlReportId:t,sitemaps:n,days:r,rowLimit:i,maxSitemapUrls:a,itemsPerSection:o,templateClusters:s,templateSamples:c,refresh:l})=>{try{let u=await Cj({site:e,crawlReportId:t,sitemaps:n,days:r,rowLimit:i,maxSitemapUrls:a,itemsPerSection:o,templateClusters:s,templateSamples:c,refresh:l});return $(`${u.summary.retainedSearchVisibleUrls} URLs appeared in the returned Search Console data. ${u.summary.crawlableCandidatesWithoutRetainedSearchVisibility} crawlable URLs need evidence review before choosing representative URL Inspection checks.`,u)}catch(e){return Q(e)}}),e.registerTool(`seo_index_monitor`,{description:`Run bounded, locally quota-enforced URL Inspection monitoring from XML sitemaps and store Google's indexed snapshots`,inputSchema:{site:D.string(),sitemaps:D.array(D.string().url()).min(1).max(20),properties:D.array(D.string()).optional(),dailyLimit:D.number().int().min(1).max(2e3).optional(),inspectLimit:D.number().int().min(1).max(100).optional(),maxUrls:D.number().int().min(1).max(25e4).optional(),languageCode:D.string().min(1).max(35).optional()}},async({site:e,sitemaps:t,properties:n,dailyLimit:r,inspectLimit:i,maxUrls:a,languageCode:o})=>{try{let s=await rM({site:e,sitemaps:t,properties:n,dailyLimit:r,inspectLimit:i,maxUrls:a,languageCode:o});return $(`Inventory ${s.summary.inventoryUrls}; ${s.summary.due} due, ${s.summary.selected} selected, ${s.summary.inspected} inspected, ${s.summary.unselectedDue} due but not selected. ${s.summary.currentIssues} current reviews in selected results, ${s.summary.regressions} regressions, ${s.summary.failed} failed, ${s.summary.quotaBlocked} quota-blocked, ${s.summary.deferred} deferred.`,s)}catch(e){return Q(e)}}),e.registerTool(`seo_redirect_trace`,{description:`Trace redirects and report final indexability/canonical issues for a URL`,inputSchema:{url:D.string().url(),maxHops:D.number().optional(),refresh:D.boolean().optional(),js:D.boolean().optional()}},async({url:e,maxHops:t,refresh:n,js:r})=>{try{let i=await uM({url:e,maxHops:t,refresh:n,js:rL(r,`auto`)});return $(`Traced ${i.summary.hops} hops. Final status ${i.summary.finalStatus}. ${i.summary.issues.length} issues.`,i)}catch(e){return Q(e)}}),e.registerTool(`seo_link_recover`,{description:`Find GSC search-value URLs that are now broken, blocked, or poorly redirected`,inputSchema:{...aL([`site`,`days`,`limit`,`minImpressions`,`refresh`,`js`]),minClicks:D.number().optional()}},async({site:e,days:t,limit:n,minClicks:r,minImpressions:i,refresh:a,js:o})=>{try{let s=await hM({site:e,days:t,limit:n,minClicks:r,minImpressions:i,refresh:a,js:rL(o,`auto`)});return $(`Checked ${s.summary.checked} search-value URLs. ${s.summary.recoverable} recoverable issues.`,s)}catch(e){return Q(e)}})}function vL(e,t={}){e.registerTool(`seo_cannibal`,{description:`Find multi-URL query exposure candidates for intent and technical review`,inputSchema:{...aL([`site`,`days`,`limit`,`minImpressions`,`includeBrand`,`refresh`]),days:D.number().int().min(1).max(548).optional(),limit:D.number().int().min(1).max(100).optional(),minImpressions:D.number().int().min(0).max(1e9).optional(),brandTerms:D.array(D.string().trim().min(1).max(200)).max(20).optional()}},async({site:e,days:t,limit:n,minImpressions:r,brandTerms:i,includeBrand:a,refresh:o})=>{try{let s=await TD({site:e,days:t,limit:n,minImpressions:r,brandTerms:i,includeBrand:a,refresh:o});return $(s.summary.verdict,s)}catch(e){return Q(e)}}),e.registerTool(`seo_decaying`,{description:`Find query/page click declines observed in both retained GSC windows and investigation signals`,inputSchema:{...aL([`site`,`days`,`limit`,`includeBrand`,`refresh`]),days:D.number().int().min(1).max(548).optional(),limit:D.number().int().min(1).max(100).optional(),comparison:D.enum([`previous-period`,`year-over-year`]).optional(),minDropPct:D.number().min(0).max(100).optional(),minPreviousClicks:D.number().min(0).max(1e9).optional(),minClickLoss:D.number().min(0).max(1e9).optional(),brandTerms:D.array(D.string().trim().min(1).max(200)).max(20).optional()}},async({site:e,days:t,limit:n,comparison:r,minDropPct:i,minPreviousClicks:a,minClickLoss:o,brandTerms:s,includeBrand:c,refresh:l})=>{try{let u=await VD({site:e,days:t,limit:n,comparison:r,minDropPct:i,minPreviousClicks:a,minClickLoss:o,brandTerms:s,includeBrand:c,refresh:l});return $(u.summary.verdict,u)}catch(e){return Q(e)}}),e.registerTool(`seo_quick_wins`,{description:`Find quick-win CTR/position opportunities`,inputSchema:{...aL([`site`,`days`,`limit`,`minImpressions`,`verifyContent`,`verifyLimit`,`includeBrand`,`js`,`fetchConcurrency`,`fetchIntervalCap`,`fetchIntervalMs`,`refresh`]),days:D.number().int().min(1).max(548).optional(),limit:D.number().int().min(1).max(100).optional(),minImpressions:D.number().int().min(0).max(1e9).optional(),verifyLimit:D.number().int().min(0).max(100).optional(),brandTerms:D.array(D.string().trim().min(1).max(200)).max(20).optional(),fetchConcurrency:D.number().int().min(1).max(16).optional(),fetchIntervalCap:D.number().int().min(1).max(60).optional(),fetchIntervalMs:D.number().int().min(100).max(6e4).optional()}},async({site:e,days:n,limit:r,minImpressions:i,brandTerms:a,verifyContent:o,verifyLimit:s,includeBrand:c,js:l,fetchConcurrency:u,fetchIntervalCap:d,fetchIntervalMs:f,refresh:p})=>{try{let m=await(t.quickWinsReport??dO)({site:e,days:n,limit:r,minImpressions:i,brandTerms:a,verifyContent:o,verifyLimit:s,includeBrand:c,js:rL(l,void 0),rate:uL({fetchConcurrency:u,fetchIntervalCap:d,fetchIntervalMs:f}),refresh:p});return $(m.summary.verdict,m)}catch(e){return Q(e)}}),e.registerTool(`seo_internal_links`,{description:`Find verified internal-link review candidates for a target URL`,inputSchema:{...aL([`site`,`days`,`limit`,`checkLimit`,`minImpressions`,`includeBrand`,`js`,`fetchConcurrency`,`fetchIntervalCap`,`fetchIntervalMs`,`refresh`]),targetUrl:D.string().url().refine(e=>/^https?:\/\//.test(e),`Use an HTTP(S) URL.`),days:D.number().int().min(1).max(548).optional(),limit:D.number().int().min(1).max(100).optional(),checkLimit:D.number().int().min(1).max(200).optional(),minImpressions:D.number().min(0).max(1e9).optional(),brandTerms:D.array(D.string().trim().min(1).max(200)).max(20).optional(),fetchConcurrency:D.number().int().min(1).max(16).optional(),fetchIntervalCap:D.number().int().min(1).max(60).optional(),fetchIntervalMs:D.number().int().min(100).max(6e4).optional()}},async({site:e,targetUrl:n,days:r,limit:i,checkLimit:a,minImpressions:o,brandTerms:s,includeBrand:c,js:l,fetchConcurrency:u,fetchIntervalCap:d,fetchIntervalMs:f,refresh:p})=>{try{let m=await(t.internalLinksReport??kA)({site:e,targetUrl:n,days:r,limit:i,checkLimit:a,minImpressions:o,brandTerms:s,includeBrand:c,js:rL(l,void 0),rate:uL({fetchConcurrency:u,fetchIntervalCap:d,fetchIntervalMs:f}),refresh:p});return $(m.summary.verdict,m)}catch(e){return Q(e)}}),e.registerTool(`seo_ctr_underperformers`,{description:`Find high-impression queries underperforming CTR expectations`,inputSchema:{...aL([`site`,`minImpressions`,`limit`,`includeBrand`,`refresh`]),site:D.string().trim().min(1),minImpressions:D.number().int().min(1).max(1e9).optional(),limit:D.number().int().min(1).max(100).optional(),brandTerms:D.array(D.string().trim().min(1).max(200)).max(20).optional()}},async({site:e,minImpressions:n,limit:r,brandTerms:i,includeBrand:a,refresh:o})=>{try{let s=await(t.ctrUnderperformersReport??gE)({site:e,minImpressions:n,limit:r,brandTerms:i,includeBrand:a,refresh:o});return $(s.summary.verdict,s)}catch(e){return Q(e)}}),e.registerTool(`seo_query_cluster`,{description:`Cluster queries by token overlap`,inputSchema:{...aL([`site`,`includeBrand`,`minImpressions`,`limit`,`refresh`]),site:D.string().trim().min(1),scope:D.string().trim().min(1).max(2e3).optional(),minImpressions:D.number().int().min(1).max(1e9).optional(),limit:D.number().int().min(1).max(100).optional(),brandTerms:D.array(D.string().trim().min(1).max(200)).max(20).optional()}},async({site:e,scope:n,brandTerms:r,includeBrand:i,minImpressions:a,limit:o,refresh:s})=>{try{let c=await(t.queryClusterReport??vP)({site:e,scope:n,brand:r?.[0],brandTerms:r,includeBrand:i,minImpressions:a,limit:o,refresh:s});return $(c.summary.verdict,c)}catch(e){return Q(e)}}),e.registerTool(`semrush_call`,{description:`Raw-ish Semrush passthrough for supported keyword endpoints`,inputSchema:{endpoint:D.enum([`phrase_this`,`phrase_related`,`phrase_questions`]),phrase:D.string()}},async({endpoint:e,phrase:t})=>{try{let n=$I(`authoritative`);if(!n)throw Error(`No keyword provider configured.`);let r=e===`phrase_this`?await n.keywordOverview(t):e===`phrase_related`?await n.relatedKeywords?.(t):await n.questions?.(t);if(!r)throw Error(`Endpoint ${e} is not supported by the active provider.`);return $(oL(r.data),r)}catch(e){return Q(e)}})}function yL(e){return{schemaVersion:e.schemaVersion,methodology:e.methodology,site:e.site,generatedAt:e.generatedAt,range:e.range,dataStatus:e.dataStatus,summary:e.summary,source:e.source,selection:e.selection,caveats:e.caveats,warnings:e.warnings,templates:e.templates.map(e=>({signature:e.signature,urlCount:e.urlCount,sampleUrls:e.sampleUrls,population:e.population,verdict:e.verdict,confidence:e.confidence,metrics:e.metrics,crawl:{requested:e.crawl.requested,attempted:e.crawl.attempted,usable:e.crawl.usable,blockedOrFailed:e.crawl.blockedOrFailed},inspection:{requested:e.inspection.requested,attempted:e.inspection.attempted,indexed:e.inspection.indexed,notIndexed:e.inspection.notIndexed,unknown:e.inspection.unknown},evidence:e.evidence,recommendation:e.recommendation}))}}function bL(e){e.registerTool(`seo_pseo_audit`,{description:`Audit pSEO templates with first-party GSC, optional crawl, and optional URL Inspection evidence`,inputSchema:{site:D.string().min(1),days:D.number().int().min(1).max(548).optional(),sitemaps:D.array(D.string().url()).max(20).optional(),maxSitemapUrls:D.number().int().min(1).max(1e5).optional(),templateLimit:D.number().int().min(1).max(100).optional(),minimumTemplateUrls:D.number().int().min(2).max(100).optional(),minimumTemplateShare:D.number().min(0).max(1).optional(),minimumTemplateImpressions:D.number().min(0).max(1e9).optional(),crawlSamples:D.number().int().min(0).max(10).optional(),inspectSamples:D.number().int().min(0).max(10).optional(),brandTerms:D.array(D.string().min(1).max(200)).max(20).optional(),includeBrand:D.boolean().optional(),refresh:D.boolean().optional(),js:D.boolean().optional(),fetchConcurrency:D.number().int().min(1).max(20).optional(),fetchIntervalCap:D.number().int().min(1).max(100).optional(),fetchIntervalMs:D.number().int().min(100).max(6e4).optional(),detail:D.enum([`summary`,`full`]).optional()}},async({site:e,days:t,sitemaps:n,templateLimit:r,maxSitemapUrls:i,minimumTemplateUrls:a,minimumTemplateShare:o,minimumTemplateImpressions:s,crawlSamples:c,inspectSamples:l,brandTerms:u,includeBrand:d,refresh:f,js:p,fetchConcurrency:m,fetchIntervalCap:h,fetchIntervalMs:g,detail:_})=>{try{let v=await GN({site:e,days:t,sitemaps:n,maxSitemapUrls:i,templateLimit:r,minimumTemplateUrls:a,minimumTemplateShare:o,minimumTemplateImpressions:s,crawlSamples:c,inspectSamples:l,brandTerms:u,includeBrand:d,refresh:f,js:rL(p,`auto`),rate:{concurrency:m,intervalCap:h,intervalMs:g}}),y=_===`full`;return $(`${Z(v.summary.templates,`pSEO template`)} audited from ${Z(v.summary.gscPages,`GSC page`)}.`,y?{report:v,presentation:ZN(v)}:{report:yL(v)},y?{markdown:QN(v)}:void 0)}catch(e){return Q(e)}})}const xL={"affected-urls":{readOrder:[`dataSources for provider availability`,`selection totalMatchedRows, returnedRows, and truncated`,`the requested rule, category, and severity filters`,`each row rule, category, severity, and crawler evidence`,`warnings and caveats`],doNotClaim:[`A capped or partial crawl only supports "affected among evaluated pages", not sitewide.`,`Unavailable clicks, impressions, or sessions are not a real zero.`,`truncated true means more matching issue rows exist than were returned.`],verify:`Re-run the same bounded crawl after deployment and confirm the rule no longer triggers on the affected URLs.`,related:[{id:`explain-crawl-issue`,reason:`Explains the rule before fixes.`},{id:`top-fixes`,reason:`Ranks issues when there are too many.`},{id:`crawl-report`,reason:`Opens the source crawl snapshot.`}]},"agent-readiness":{readOrder:[`profile, profileApplicability, dataStatus, and crawl caveats`,`summary and topActions`,`HTML and Markdown representation checks with affected URLs`,`Agent Skills, llms.txt, route manifest, crawler access, and identity evidence`],doNotClaim:[`A clean content profile is not proof of indexing, rankings, AI mentions, citations, or selection.`,`API, application, and commerce checks marked notApplicable were outside this content-site run. They did not fail.`,`An allowed crawler token does not prove that a service fetched or used the page.`,`Optional discovery files are not Google ranking requirements.`],verify:`Run the same content profile against the same public route scope after deployment and confirm each failed or warning check changed.`,related:[{id:`ai-readiness`,reason:`Checks AI search technical eligibility.`},{id:`llms-txt-audit`,reason:`Inspects llms.txt in more detail.`},{id:`entity-readiness`,reason:`Reviews identity evidence.`},{id:`site-crawl`,reason:`Creates a reusable crawl baseline.`}]},"ai-readiness":{readOrder:[`dataStatus and crawl caveats`,`each check evaluated flag`,`hard access, indexability, and snippet conflicts`,`semantic structure and answerable content as observations`],doNotClaim:[`unknown means evidence was unavailable, not a pass or a fail.`,`Valid JSON-LD is syntax evidence, not entity recognition or rich-result eligibility.`,`A clean report is not indexing, ranking, AI visibility, selection, or citations.`],verify:`Re-crawl at the same scope after a change and confirm the access or indexability signal resolved.`,related:[{id:`geo-gaps`,reason:`Lists affected URLs for access rules.`},{id:`entity-readiness`,reason:`Reviews identity signals.`},{id:`ai-search-scorecard`,reason:`Scores the same evidence.`}]},"ai-referrals":{readOrder:[`dataStatus, range, methodology, and dataSource partialReasons`,`possiblyTruncated and per-query statuses`,`selection.landingPages before treating landingPages as complete`,`sources observedSessionSources for attribution evidence`],doNotClaim:[`An empty result is not proof that no AI system sent or cited the site.`,`Referral sessions did not necessarily cause conversions and are not all AI traffic.`,`totalUsers is not additive and must not be reconstructed by summing rows.`],verify:`Compare complete like-for-like GA4 periods and check whether high-session landing pages answer the referring context.`,related:[{id:`seo-to-ai-query`,reason:`Builds monitoring prompts.`},{id:`ai-readiness`,reason:`Checks technical AI access.`}]},"ai-search-scorecard":{readOrder:[`partial, crawlComplete, and excluded before the number`,`score, which is null when no check had known evidence`,`each check status, observed evidence, and finding`,`methodology weights, statusCredit, and formula`],doNotClaim:[`The score is a heuristic over this tool's own checks, not a Google or AI-engine requirement, eligibility verdict, or ranking prediction.`,`unknown checks are excluded from the score and are never failures.`,`A blocked AI crawler token can be an intentional publisher choice.`],verify:`Re-crawl at the same scope after a change and confirm the per-check evidence moved.`,related:[{id:`ai-readiness`,reason:`Gives the per-page evidence.`},{id:`geo-gaps`,reason:`Lists affected URLs.`},{id:`entity-readiness`,reason:`Reviews identity signals.`}]},"audit-page":{readOrder:[`fetchDiagnostics, warnings, and the final fetched URL`,`observed page fields and structured-data findings`,`issues and recommendations`],doNotClaim:[`One page cannot support a sitewide conclusion.`,`Title pixel width is a review estimator, not a Google limit.`,`The fetched version is not proof that Google indexed that version.`],verify:`Re-fetch the URL after the edit and confirm the observed field changed.`,related:[{id:`redirect-trace`,reason:`Debugs the redirect chain.`},{id:`performance-audit`,reason:`Adds page speed evidence.`},{id:`content-optimization`,reason:`Builds a query-led brief.`}]},"audit-urls":{readOrder:[`requested versus attempted, fetched, failed, retained, and omitted counts`,`response evidence and issue summaries`,`requestEvidenceStatus, warnings, and caveats`],doNotClaim:[`A successful sample does not prove the same template works everywhere.`,`A failed request may be transient network, not a persistent defect.`,`A bounded audit is never complete site coverage.`],verify:`Re-run the identical URL list after deployment; a URL dropped from the request is not a verified fix.`,related:[{id:`audit-page`,reason:`Inspects one page in depth.`},{id:`site-crawl`,reason:`Discovers pages instead.`},{id:`redirect-trace`,reason:`Traces a redirecting URL.`}]},cannibalisation:{readOrder:[`dataStatus and both source.pageExposure and source.propertyDemand with validation counts and completeness`,`selection to see why groups were excluded or suppressed`,`per-page clicks, impressions, position, impression share, reviewContext, and HHI`,`priority.score and suggestedOwnerUrl`],doNotClaim:[`URL overlap is a review candidate, not proven harmful cannibalisation.`,`suggestedOwnerUrl is a low-confidence mechanical pick that requires intent review.`,`priority.score is a heuristic, not estimated click lift, and a partial source cannot support an all-clear.`],verify:`Compare intent and live SERPs for the overlapping URLs before consolidating anything.`,related:[{id:`query-clusters`,reason:`Groups related query themes.`},{id:`internal-links`,reason:`Clarifies link ownership.`},{id:`content-optimization`,reason:`Differentiates page intent.`}]},"community-intent":{readOrder:[`dataStatus, source.completeness, warnings, and caveats`,`selection.classifiedRows for every match and selection.returnedRows for the limited set`,`per-item intent, signals, and matchedTerms`],doNotClaim:[`Intent labels are low-confidence English language heuristics, not verified intent.`,`The classifier does not inspect the live SERP or identify the ranking URL.`,`A capped or partial zero is inconclusive, not absence of demand.`],verify:`Retrieve GSC query and page rows to find the ranking URL and check its content against the observed query language.`,related:[{id:`content-optimization`,reason:`Turns intent into a brief.`},{id:`query-clusters`,reason:`Groups the demand themes.`},{id:`page-opportunities`,reason:`Reviews one ranking URL.`}]},"compare-crawls":{readOrder:[`comparability.status and its matching flags`,`before and after preserved config, requestScope, and caps`,`top-level completeness and truncated reason codes`,`summary, pageChanges, and issueChanges`],doNotClaim:[`An absent page from a capped, skipped, failed, or differently scoped crawl is not proven removed.`,`Source row caps limit joined GSC or GA4 evidence even when the document crawl completed.`,`A change between snapshots is not proof the release caused it.`],verify:`Re-run the same definition and request scope after deployment and confirm the delta.`,related:[{id:`crawl-diff`,reason:`Compares against the previous run.`},{id:`crawl-history`,reason:`Finds saved report ids.`},{id:`affected-urls`,reason:`Lists URLs for a changed rule.`}]},"content-optimization":{readOrder:[`sourceReport dataStatus, selection, benchmark.possiblyTruncated, and verification.status`,`summary.score and primaryIntent as heuristics`,`topActions separating technical-check and unverified from verified observations`,`intentMix and the source query metrics`],doNotClaim:[`estimatedClickLift and score are prioritisation heuristics, not forecasts or quality grades.`,`Query wording is not a content mandate.`,`Adding suggested wording is not guaranteed traffic.`],verify:`Fetch the page after the edit and confirm the missing answer block or clearer framing is present.`,related:[{id:`page-opportunities`,reason:`Adds query-level opportunities.`},{id:`internal-links`,reason:`Finds contextual links.`},{id:`quick-wins`,reason:`Ranks CTR opportunities.`}]},"crawl-diff":{readOrder:[`run versus previousRun, where a missing previous run means a baseline`,`start URL, limit, counts, timestamps, rendering, and warnings for both runs`,`summary counts traced into items before, after, and changes`,`recommendations as review actions`],doNotClaim:[`A removed URL may be outside the current discovered set or limit rather than deleted.`,`A fetch or extraction failure cannot support a resolved issue.`,`A bounded diff is not an inventory-wide audit and does not show ranking impact.`],verify:`Repeat with comparable scope after a fix and confirm the changed item.`,related:[{id:`compare-crawls`,reason:`Compares two explicit snapshots.`},{id:`technical-watch`,reason:`Runs a recurring watch.`},{id:`affected-urls`,reason:`Lists URLs for a rule.`}]},"crawl-history":{readOrder:[`each report id, site, start URL, creation time, and status`,`total pages and issue count`,`configuration hash and storage version`],doNotClaim:[`Counts are orientation metadata, not severity or completeness.`,`A smaller page count could reflect a scope change, cap, or failure, not a real site change.`,`A saved list can expose stale data.`],verify:`Load a chosen id with crawl-report and inspect its caveats before analysis.`,related:[{id:`crawl-report`,reason:`Loads a saved snapshot.`},{id:`compare-crawls`,reason:`Compares two snapshots.`},{id:`site-crawl`,reason:`Creates a new crawl.`}]},"crawl-report":{readOrder:[`id, source URL and site, generatedAt, definition id, config hash, status, and requestEvidenceStatus`,`summary counts and pageLimitReached`,`data sources, warnings, and caveats`],doNotClaim:[`A report can be internally valid yet stale, capped, or partial and cannot prove the current live state.`,`Loading a report is a local storage read, not a fresh check.`],verify:`Run a new crawl with the same configuration and retain both report ids to confirm a change.`,related:[{id:`top-fixes`,reason:`Ranks the issue queue.`},{id:`affected-urls`,reason:`Lists URLs for a rule.`},{id:`compare-crawls`,reason:`Compares two snapshots.`}]},"crawler-rules":{readOrder:[`each rule id, title, and category`,`default severity as triage metadata`,`guidance metadata`],doNotClaim:[`Listing a rule does not mean the site triggered it or that the convention is a search-engine requirement.`,`Default severity is generic triage metadata, not actual priority.`,`Similarly named rules may not share source semantics.`],verify:`Pass the exact rule id to explain-crawl-issue or affected-urls to confirm it applies to real evidence.`,related:[{id:`explain-crawl-issue`,reason:`Returns the rule contract.`},{id:`affected-urls`,reason:`Returns crawl evidence for a rule.`}]},"ctr-underperformers":{readOrder:[`dataStatus, range, source.completeness, possiblyTruncated, and validation counts`,`selection before the queue`,`per-item query, URL, impressions, position, observed CTR, target CTR, and benchmark source`,`estimated shortfall`],doNotClaim:[`The shortfall is directional prioritisation math, not a traffic forecast.`,`Page-one average position hides day, device, geography, and result-feature variation.`,`A partial empty result is not an all-clear.`],verify:`Inspect the current SERP and displayed title link, then compare a complete later period after testing framing.`,related:[{id:`quick-wins`,reason:`Ranks position 4-10 opportunities.`},{id:`page-opportunities`,reason:`Reviews one URL.`},{id:`content-optimization`,reason:`Builds a page brief.`}]},"decaying-pages":{readOrder:[`dataStatus, ranges, and source.completeness`,`current and previous fetched rows, caps, truncation, and invalid rows in selection`,`summary.observedRetainedQueryClickLoss versus the returned subset`,`per-item clicks, impressions, CTR, position, and signals`],doNotClaim:[`A diagnosis such as lost_position or lost_ctr labels correlated movement, not a cause.`,`Missing retained rows are not zero traffic.`,`Do not sum capped rows into a property-wide loss.`],verify:`Check deployments, live indexability, and SERP composition for the affected URLs before editing.`,related:[{id:`refresh-priorities`,reason:`Combines decline signals.`},{id:`segment-impact`,reason:`Shows where movement concentrates.`},{id:`measure-change`,reason:`Evaluates a fix later.`}]},"entity-readiness":{readOrder:[`dataStatus, evaluatedPages versus crawlPages, and caveats`,`entities.schemaTypes`,`sameAs, sameAsByType, and socialProfiles`,`authors`],doNotClaim:[`Checks are informational observations with coverage, not pass or fail requirements.`,`Skipped or non-indexable pages are not evidence of absence.`,`These signals do not prove Knowledge Graph inclusion, ownership, expertise, rankings, or citations.`],verify:`Confirm names, URLs, authorship, and dates with the publisher, then re-crawl the same scope.`,related:[{id:`ai-readiness`,reason:`Checks technical AI access.`},{id:`ai-search-scorecard`,reason:`Scores the evidence.`},{id:`geo-gaps`,reason:`Lists affected URLs.`}]},"explain-crawl-issue":{readOrder:[`category, default severity, and why it matters`,`fix steps and ignored impact`,`verification method and agent hints`],doNotClaim:[`Guidance describes the checked condition, not a search-engine penalty or guaranteed ranking effect.`,`Default severity is generic prioritization metadata, not actual urgency.`,`The rule definition alone does not prove any page triggered it.`],verify:`Pair the definition with affected-urls from the same saved crawl and confirm the per-URL evidence.`,related:[{id:`affected-urls`,reason:`Returns URLs for the rule.`},{id:`crawler-rules`,reason:`Finds a rule id.`},{id:`top-fixes`,reason:`Ranks the fix queue.`}]},"generate-llms-txt":{readOrder:[`content, includedUrls, estimatedTokens, and sections`,`the generated titles and descriptions against the source pages`,`the source crawl date, cap, failures, and caveats`],doNotClaim:[`Reaching maxUrls or the token budget is possible truncation, not a complete inventory.`,`A valid draft does not prove search or AI benefit, selection, indexing, or citations.`,`The crawler cannot determine publisher intent.`],verify:`After human review, fetch the published file and validate its links with llms-txt-audit.`,related:[{id:`llms-txt-audit`,reason:`Audits the published file.`},{id:`crawl-report`,reason:`Inspects the source crawl.`},{id:`okf-build`,reason:`Builds a richer knowledge pack.`}]},"geo-gaps":{readOrder:[`dataStatus, source.partialReasons, and selection evaluated, matched, returned, and truncated counts`,`per-URL issues separated from searchEligibility`,`access states, where null means unknown`],doNotClaim:[`A null access state means unknown, not allowed.`,`Semantic HTML and structured data do not override a hard access restriction or create eligibility.`,`An empty gap list does not prove indexing, visibility, selection, or complete coverage; confirm intent before treating noindex, robots, canonicals, or snippet limits as defects.`],verify:`Fix unintended response or access conflicts, then re-check the exact URL and directive after deployment.`,related:[{id:`ai-readiness`,reason:`Gives the readiness overview.`},{id:`affected-urls`,reason:`Lists URLs for a rule.`},{id:`index-coverage`,reason:`Checks index signals.`}]},"index-coverage":{readOrder:[`sources completeness, rowLimit, rowLimitReached, date range, and invalid rows`,`retainedSearchVisibility and crawlableWithoutRetainedSearchVisibility`,`blockedOrNonIndexable, sitemapOnly, searchConsoleOnly, and templateReview`,`each group count, returned, and omitted`],doNotClaim:[`A page is not indexed because it had Search Analytics visibility, nor unindexed because no row appeared.`,`A missing Search Analytics row is not proof a page is unindexed.`,`Do not turn a limited or incomplete source into a whole-site coverage percentage.`],verify:`Run index-watch on representative URLs to collect exact URL Inspection verdicts.`,related:[{id:`index-watch`,reason:`Collects URL Inspection evidence.`},{id:`index-coverage-plan`,reason:`Plans quota when large.`},{id:`site-crawl`,reason:`Provides the source crawl.`}]},"index-coverage-plan":{readOrder:[`summary sitemapUrls, urlCount, property count, daily capacity, and estimated versus target cycle days`,`properties allocations and sample URLs`,`suggestions and every warning`],doNotClaim:[`Estimated cycle time is arithmetic over discovered URLs and capacity, not a promise Google will process or index them.`,`Sitemap fetch failures or maxUrls truncation make the plan incomplete.`,`This plan does not report an index coverage percentage or an all-clear.`],verify:`Use the plan to set a realistic recurring index-monitor limit and confirm allocations against properties you control.`,related:[{id:`index-monitor`,reason:`Collects the snapshots.`},{id:`index-coverage`,reason:`Reviews index signals first.`},{id:`index-watch`,reason:`Reviews index changes.`}]},"index-monitor":{readOrder:[`dataStatus and source sitemap and inventory limits, discovered and invalid URLs, and truncation`,`summary inventory states kept distinct`,`properties allocation and execution`,`items inspectionStatus, indexStatus, regressions, recoveries, and errors`],doNotClaim:[`A due URL that was not selected has no new inspection result.`,`Failed, deferred, or quota-blocked checks are operational outcomes, not SEO defects.`,`A complete selected batch does not prove inventory-wide indexing or future inclusion.`],verify:`Investigate regressions with a live page audit and the exact inspection evidence, and retry failed checks per retryAt.`,related:[{id:`index-watch`,reason:`Reviews index changes.`},{id:`index-coverage-plan`,reason:`Plans quota allocation.`},{id:`index-coverage`,reason:`Reviews coverage signals.`}]},"index-watch":{readOrder:[`source.possiblyTruncated, dataStatus, warnings, and sitemap inventory states`,`currentIssues, regressions, recoveries, failed, quotaBlocked, and deferred separately`,`indexStatus, issueCodes, and typed changes`],doNotClaim:[`URL Inspection is Google's indexed snapshot for one URL, not a live crawl or current search appearance.`,`A NEUTRAL verdict or canonical difference may be intentional, not a defect.`,`Do not infer inventory-wide health from the bounded selected sample; a provider 429 is authoritative over the local quota ledger.`],verify:`Audit the live page, sitemap, and exact inspection evidence for any regression or current issue.`,related:[{id:`index-monitor`,reason:`Collects the snapshots.`},{id:`index-coverage`,reason:`Reviews coverage signals.`},{id:`index-coverage-plan`,reason:`Plans quota allocation.`}]},"internal-links":{readOrder:[`dataStatus, target verification, aliases, canonical, and technical signals`,`source completeness and selection attempted, checked, failed, unchecked, and exclusions`,`per-item exact versus lexical matches and linkEvidence`],doNotClaim:[`A low checkLimit can leave plausible candidates unchecked, so absence is not proven.`,`Query overlap does not prove a link belongs or that adding one improves rankings.`,`Priority is a heuristic, not estimated impact.`],verify:`Read the source passage, add one contextual link if the target is genuinely useful, and re-crawl to confirm it.`,related:[{id:`cannibalisation`,reason:`Checks URL overlap.`},{id:`content-optimization`,reason:`Builds a page brief.`},{id:`page-opportunities`,reason:`Reviews one URL.`}]},"link-recovery":{readOrder:[`range and summary.checked with severity totals`,`per-item original and final URL, GSC metrics, issue lists, and redirect trace`,`chain status, final-page indexability, canonicals, and warnings`],doNotClaim:[`clicksAtRisk and impressionsAtRisk describe the checked period, not a forecast of loss or recovery.`,`Prior visibility orders the queue but does not prove fixing a URL restores traffic.`,`Failed checks or selection limits constrain coverage.`],verify:`Add one direct 301 to the closest equivalent destination only after confirming the page should still exist, then re-trace.`,related:[{id:`redirect-trace`,reason:`Traces one URL chain.`},{id:`decaying-pages`,reason:`Shows click declines.`},{id:`audit-urls`,reason:`Runs bounded checks.`}]},"llms-txt-audit":{readOrder:[`exists, resolved file URL, response status, headline, and issues`,`recommendedPages`,`the source crawl date, cap, partial state, failed requests, and caveats`],doNotClaim:[`llms.txt presence is not a ranking signal and its absence is not a technical defect.`,`A short recommendation list from a capped crawl is not sitewide evidence.`,`Adding the file does not improve indexing, rankings, traffic, AI selection, or citations.`],verify:`If the file exists, confirm its URLs are current, canonical, public, and aligned with publisher intent.`,related:[{id:`generate-llms-txt`,reason:`Drafts a new file.`},{id:`crawl-report`,reason:`Inspects the source crawl.`},{id:`okf-build`,reason:`Builds a knowledge pack.`}]},"measure-change":{readOrder:[`dataStatus, window, source, warnings, and caveats`,`window.effectiveDays versus requestedDays and afterWindowTruncated`,`GSC before and after metrics`,`GA4 source.analytics.status and timezone and any control adjusted deltas`],doNotClaim:[`Timing does not prove the recorded change caused the movement.`,`not-enough-data is never positive, negative, or flat, and the delta must not be annualized or forecast.`,`clickPct null means the before value was zero, not an infinite gain.`],verify:`Use equal whole-day before and after windows with at least 7 finalized days each before reading a directional verdict.`,related:[{id:`segment-impact`,reason:`Shows where movement sits.`},{id:`decaying-pages`,reason:`Shows click declines.`},{id:`update-postmortem`,reason:`Frames movement around an update.`}]},"monthly-action-plan":{readOrder:[`workflow summary, steps, actions, and nested output`,`requested month and final-data window`,`provider status and skipped sections`,`warnings, caveats, and retained caps`],doNotClaim:[`A skipped section is not a zero and a successful workflow does not make every nested source complete.`,`Actions are a review queue, not automatic edit instructions.`,`Do not invent click, traffic, ranking, or revenue forecasts or attribute movement to an update from timing alone.`],verify:`Trace each narrative claim back to its structured diagnosis or measurement and confirm affected URLs.`,related:[{id:`monthly-report`,reason:`Produces the human brief.`},{id:`refresh-priorities`,reason:`Builds a refresh queue.`},{id:`search-performance-overview`,reason:`Gives a baseline.`}]},"monthly-report":{readOrder:[`month and the underlying date windows`,`dataStatus and source completeness`,`skipped sections, warnings, and caveats`,`the narrative and its referenced diagnosis, changes, and priorities`],doNotClaim:[`A zero from a complete source differs from an unavailable, partial, filtered, or capped section.`,`verifyLimit is a bounded subset, not full-site validation.`,`The report cannot establish why traffic changed or forecast clicks, rankings, or revenue.`],verify:`Follow a narrative reference into the structured evidence and validate the affected URLs before acting.`,related:[{id:`monthly-action-plan`,reason:`Sequences the actions.`},{id:`narrative-report`,reason:`Assembles an evidence-linked briefing.`},{id:`search-performance-overview`,reason:`Gives a baseline.`}]},"narrative-report":{readOrder:[`top-level caveats and headline`,`sections and priorities`,`diagnosis, changeMeasurements, and changeMeasurementAttempts`,`monitoring`],doNotClaim:[`A failed or skipped measurement stays visible and is not a zero.`,`Update timing, template groupings, and opportunity scores are investigation context, not ranking causes.`,`The narrative summarizes evidence and does not strengthen it or prove causation.`],verify:`Reproduce a technical failure or inspect the affected URLs before treating a narrative claim as a defect.`,related:[{id:`monthly-report`,reason:`Fixes a calendar-month brief.`},{id:`refresh-priorities`,reason:`Builds a refresh queue.`},{id:`search-performance-overview`,reason:`Gives a baseline.`}]},"okf-build":{readOrder:[`manifest schema version, report id, source URL, generation time, crawl status, title, concept count, and caveats`,`selection page counts and deterministic ordering`,`the returned validation before using files`],doNotClaim:[`If selected pages equal the limit the bundle is bounded, not exhaustive.`,`A successful structural validation does not establish ownership, freshness, factual accuracy, or completeness.`,`The builder does not make extracted claims true or current.`],verify:`Inspect citations, canonical URLs, and summaries against source pages, especially for time-dependent claims.`,related:[{id:`okf-validate`,reason:`Validates the bundle.`},{id:`crawl-report`,reason:`Inspects the source crawl.`},{id:`generate-llms-txt`,reason:`Builds agent navigation.`}]},"okf-validate":{readOrder:[`validation.valid and file and concept counts`,`each issue level, path, and message`,`explanation.nextActions as ordering guidance`],doNotClaim:[`Validation checks the file contract only and does not browse cited sources or determine whether the knowledge is true, current, owned, or complete.`,`Warnings still require review even when valid is true.`,`Never report valid true as fact checked.`],verify:`After structural success, separately verify cited URLs, claims, dates, and the recorded crawl snapshot.`,related:[{id:`okf-build`,reason:`Builds the bundle to validate.`}]},"page-opportunities":{readOrder:[`dataStatus, range, source.targetRowsFetched, selection, and benchmark`,`benchmark.possiblyTruncated`,`verification.status and reason`,`per-item query metrics, opportunity type, and benchmark and verification evidence`],doNotClaim:[`Only verified results support live-page coverage observations.`,`estimatedCtrClickShortfall is a directional calculation, not a click forecast.`,`summary.opportunities is a review queue, not a defect count, and partial data is not an all-clear.`],verify:`Inspect the live SERP and resolve any status or indexability contradiction before editing.`,related:[{id:`content-optimization`,reason:`Builds a page brief.`},{id:`quick-wins`,reason:`Ranks CTR opportunities.`},{id:`internal-links`,reason:`Finds contextual links.`}]},"performance-audit":{readOrder:[`dataStatus, source, labDataStatus, and fieldDataStatus`,`Lighthouse score, lab metrics, and labInsights`,`CrUX fieldData.metrics with scope, formFactor, and collectionPeriod`,`fetch-fallback fields when source is fetch-fallback`],doNotClaim:[`Origin-level CrUX data is not page-level evidence.`,`A fetch-fallback duration is the local fetch workflow, not TTFB, LCP, CLS, TBT, or INP, and never earns a score.`,`Do not combine lab and field values into a new score, compare mobile with desktop as equal populations, or claim a ranking outcome.`],verify:`Reproduce the relevant lab issue and re-run the audit after the change.`,related:[{id:`audit-page`,reason:`Runs an on-page audit.`},{id:`site-crawl`,reason:`Builds the site inventory.`}]},"pseo-audit":{readOrder:[`dataStatus, source, selection, warnings, and caveats`,`population separating discovered, GSC-visible, sampled, and untested URLs`,`the template verdict confirmed against crawl, inspection, and evidence`,`metrics.topQueries and queryPatterns`],doNotClaim:[`PASS is the only indexed URL Inspection verdict and coverageState is diagnostic text that must not be parsed for index state.`,`A sitemap URL with no GSC row does not prove zero demand.`,`Literal term coverage and path entity fit are review heuristics, not ranking factors, and word count is descriptive.`],verify:`For an index-risk or crawl-risk verdict, inspect the sampled URLs directly and state the sample count.`,related:[{id:`index-coverage`,reason:`Reviews coverage signals.`},{id:`affected-urls`,reason:`Lists URLs for a rule.`},{id:`content-optimization`,reason:`Builds a page brief.`}]},"query-clusters":{readOrder:[`summary cluster and query counts, clicks, impressions, threshold, limit, and brand filtering`,`per-cluster member queries, pages, demand, and performance`,`each cluster recommendation`],doNotClaim:[`Token overlap does not prove shared intent, justify a new page, or establish that current URLs compete.`,`Page-two clusters do not claim a CTR-only click lift.`,`Queries below the threshold or outside the cap are outside the conclusion, not absent demand.`],verify:`Check whether a cluster maps to one intent before changing section structure or navigation.`,related:[{id:`cannibalisation`,reason:`Checks URL overlap.`},{id:`content-optimization`,reason:`Builds a page brief.`},{id:`community-intent`,reason:`Reviews intent language.`}]},"quick-wins":{readOrder:[`dataStatus, date range, source row cap, possiblyTruncated, selection.invalidRows, and filters`,`methodology and each item benchmark confidence before using targetCtr`,`estimatedCtrClickShortfall and priority`,`verification requested, attempted, verified, failed, and technical counts`],doNotClaim:[`estimatedCtrClickShortfall and priority score are heuristics, explicitly not estimated lift.`,`Average position is not a stable rank, and an unverified item cannot support a content-coverage conclusion.`,`Filtered or capped evidence is not an all-clear.`],verify:`Inspect the live SERP, test clearer snippet framing, then compare a later complete window.`,related:[{id:`ctr-underperformers`,reason:`Adds CTR peer evidence.`},{id:`page-opportunities`,reason:`Reviews one URL.`},{id:`striking-distance`,reason:`Finds rows near page one.`}]},"redirect-trace":{readOrder:[`chain in order with requested URL, status, Location, resolved next, and duration`,`finalUrl versus finalPage and summary.finalStatus`,`summary.issues`,`metaRobots, xRobotsTag, canonical, and warnings`],doNotClaim:[`The trace is one observation from the machine and time that ran it, and CDNs, geography, cookies, or later deploys can differ.`,`A canonical elsewhere or a 302 can be intentional.`,`JavaScript rendering affects final-page extraction, not the HTTP chain.`],verify:`Re-run the trace after deployment and confirm the intended destination and status.`,related:[{id:`link-recovery`,reason:`Prioritises search-value URLs.`},{id:`audit-page`,reason:`Audits the final page.`},{id:`audit-urls`,reason:`Runs bounded checks.`}]},"refresh-priorities":{readOrder:[`workflow steps and skipped states`,`per-item source, impactKind, score breakdown, date window, completeness, verification status, and rationale`,`the top few URLs manually`],doNotClaim:[`A click decline, low CTR, second-page position, and GA4 session signal have different semantics and are not interchangeable.`,`Missing GA4 or failed fetches stay unavailable, not zero-weight proof.`,`The score is not expected clicks, revenue, ranking lift, or causal confidence.`],verify:`Confirm query intent, current content, and internal links for the top URLs before choosing a bounded edit.`,related:[{id:`decaying-pages`,reason:`Shows click declines.`},{id:`quick-wins`,reason:`Ranks CTR opportunities.`},{id:`second-page`,reason:`Finds rows near page one.`}]},"search-performance-overview":{readOrder:[`workflow envelope with summary, steps, actions, and output`,`report dataStatus, skipped sections, and partial reasons`,`priorities`,`nested anomaly, update, segment, decay, cannibalisation, striking-distance, and quick-win results`],doNotClaim:[`One complete section must not hide a failed or capped neighbour.`,`Sparse data may legitimately skip a section.`,`Update overlap, opportunity scores, average position, and timing are investigation signals, not causation or forecasts.`],verify:`Run the focused report named in actions and confirm affected URLs and provider dates before editing.`,related:[{id:`segment-impact`,reason:`Shows where movement sits.`},{id:`quick-wins`,reason:`Ranks CTR opportunities.`},{id:`striking-distance`,reason:`Finds rows near page one.`}]},"second-page":{readOrder:[`source completeness, date range, selection, methodology, provenance, and brand filtering`,`per-item impressions, clicks, CTR, and average position`,`the priority value`],doNotClaim:[`Average position is aggregated across searches, locations, devices, and time and is not an exact rank.`,`Retained limits mean the output is not a complete inventory.`,`Priority is a transparent heuristic, not expected click lift, and one row is not cannibalisation.`],verify:`Inspect the live page and search intent and confirm the query suits the URL before one defensible change.`,related:[{id:`striking-distance`,reason:`Covers positions 11-20.`},{id:`quick-wins`,reason:`Ranks CTR opportunities.`},{id:`page-opportunities`,reason:`Reviews one URL.`}]},"segment-impact":{readOrder:[`before, after, and rangeDays`,`dataStatus and source.before, source.after, and completeness for caps and truncation`,`selection invalid, duplicate, conflicting, matched, unmatched, returned, and limited rows`,`per-item clickDelta, impressionDelta, and positionDelta, then unmatchedSegments`],doNotClaim:[`A positive positionDelta is worse because a larger average position is lower in results.`,`unmatchedSegments are retained in one window only and are not gains from or losses to zero.`,`A limited or truncated result cannot support an inventory-wide winner or loser.`],verify:`Audit the top matched movers pages and query intent before drawing a segment conclusion.`,related:[{id:`traffic-anomaly`,reason:`Confirms movement first.`},{id:`decaying-pages`,reason:`Shows click declines.`},{id:`update-postmortem`,reason:`Frames movement around an update.`}]},"seo-to-ai-query":{readOrder:[`dataStatus states empty, filtered, partial, and available`,`dateRange, source.possiblyTruncated, filters, selection.invalidRows, and limited rows`,`methodology.observedAiPromptData and estimatedTrafficLift, both false`,`per-item evidenceScope`],doNotClaim:[`Generated prompt count is not AI demand and prompts do not prove anyone entered them or that an answer mentioned the site.`,`No returned rows is not an all-clear when the source is partial or filtered.`,`Each prompt inherits the narrow retained-GSC-query scope.`],verify:`Store the source query and date range beside later monitoring observations and refresh on a controlled schedule.`,related:[{id:`ai-referrals`,reason:`Adds GA4 referral evidence.`},{id:`community-intent`,reason:`Reviews intent language.`},{id:`ai-readiness`,reason:`Checks technical access.`}]},"setup-check":{readOrder:[`ok`,`each checks entry status, distinguishing fail from warn, with detail and fix`,`generatedAt`],doNotClaim:[`A passing result means local prerequisites look present, not that a token is still accepted by Google or that the account can access a property.`,`Missing saved defaults are usually warnings because explicit parameters can replace them.`,`A setup pass is not evidence about rankings, indexing, or site health.`],verify:`Apply the fix on a failed check, re-run setup-check, then run a small read-only report for the intended property.`,related:[{id:`search-performance-overview`,reason:`First report once setup passes.`}]},"site-crawl":{readOrder:[`status, requestEvidenceStatus, attempted and fetched counts, failures, retained pages, and pageLimitReached`,`data-source states`,`warnings and caveats, then opt-in pages, requests, and issues`],doNotClaim:[`A partial or capped crawl cannot support an all-clear or a definitive zero.`,`A crawl is not an index, traffic source, or guarantee of complete site coverage.`,`If JavaScript rendering was disabled, state that discovery boundary.`],verify:`Preserve the report id and settings, then re-run the same configuration to confirm a change.`,related:[{id:`top-fixes`,reason:`Ranks the fix queue.`},{id:`affected-urls`,reason:`Lists URLs for a rule.`},{id:`index-coverage`,reason:`Checks coverage signals.`}]},"striking-distance":{readOrder:[`range, rangeDays, dataStatus, and source`,`source.possiblyTruncated and retained-row completeness`,`selection invalid, out-of-range, branded, below-threshold, eligible, returned, and limited`,`methodology, then per-item metrics and contentVerification`],doNotClaim:[`The 11-20 band is a heuristic boundary and average position is not a literal second results page.`,`Unverified or failed pages keep lower confidence.`,`The priority score is not expected traffic or ranking lift.`],verify:`Audit a small number of high-impression candidates and fix verified technical problems first.`,related:[{id:`second-page`,reason:`Covers positions 10-20.`},{id:`quick-wins`,reason:`Ranks CTR opportunities.`},{id:`page-opportunities`,reason:`Reviews one URL.`}]},"technical-watch":{readOrder:[`each workflow step and nested crawl, index, and recovery output independently`,`crawl run ids, limits, failures, and changed items`,`inspection selected, unselected-due, attempted, failed, quota-blocked, deferred, and regression states`,`recovery clicks-at-risk as history`],doNotClaim:[`A successful run does not prove every URL is crawlable, indexed, unchanged, or issue-free.`,`URL Inspection is a provider snapshot and sitemap discovery is not proof of indexing.`,`Partial crawls, capped inventories, quotas, and failed subqueries must stay visible in any all-clear.`],verify:`Reproduce new failures and confirmed regressions, then schedule another bounded run with a rollback path.`,related:[{id:`crawl-diff`,reason:`Isolates crawl change.`},{id:`index-watch`,reason:`Isolates index change.`},{id:`link-recovery`,reason:`Recovers search-value URLs.`}]},"top-fixes":{readOrder:[`dataSources, summary, warnings, and caveats`,`per-item rule id, affected count, sample URLs, scoreFactors, and whyThisRanks`,`effort, fix guidance, and verification`],doNotClaim:[`Rank means highest among retained eligible findings, not globally highest, because the crawl or category may be capped.`,`Missing provider joins stay unavailable and a numeric zero is meaningful only when the source is complete.`,`This is not a forecast of rankings, traffic, or revenue.`],verify:`Run affected-urls for the same rule and report id, then repeat the crawl configuration after implementation.`,related:[{id:`affected-urls`,reason:`Lists URLs for a rule.`},{id:`explain-crawl-issue`,reason:`Explains the rule.`},{id:`site-crawl`,reason:`Provides the source crawl.`}]},"traffic-anomaly":{readOrder:[`coverage with requested dates, expected versus observed days, missing days, invalid rows, and status`,`anomalies with metric, baseline and comparison dates, percentChange, zScore, and significanceMethod`,`direction and significant`],doNotClaim:[`Statistical unusualness does not identify a cause, diagnose a defect, or prove an external event affected the property.`,`A null percentage from a zero baseline differs from zero change.`,`Partial coverage can change the baseline.`],verify:`If movement is significant and coverage is adequate, run segment-impact for pages and queries.`,related:[{id:`segment-impact`,reason:`Shows where movement sits.`},{id:`update-correlation`,reason:`Adds update timing.`},{id:`decaying-pages`,reason:`Shows click declines.`}]},"update-correlation":{readOrder:[`classification with attribution and confidence, returned as not-established and none`,`anomalies, overlappingUpdates, and confounders`,`evidence, caveats, and source`],doNotClaim:[`Overlap cannot show the update caused a gain or loss even with large movement and no other recorded change.`,`update-overlap-without-significant-movement is not an impact result and no-update-overlap does not explain remaining movement.`,`Temporal overlap is not a penalty, reward, or confirmed effect.`],verify:`Test saved deploys, redirects, and content changes that overlap the window before attributing movement.`,related:[{id:`update-postmortem`,reason:`Maps winners and losers.`},{id:`segment-impact`,reason:`Shows where movement sits.`},{id:`traffic-anomaly`,reason:`Confirms movement first.`}]},"update-postmortem":{readOrder:[`workflow step statuses, finalized date windows, and source completeness`,`failed measurements, retained limits, and caveats`,`update, insights, templateMovement, and segments`],doNotClaim:[`The cause remains not established even when dates overlap.`,`Retained winners and losers are not a complete inventory when sources are capped or filtered.`,`Do not claim update causation, recovery probability, or future traffic.`],verify:`Inspect representative URLs from affected segments and reproduce technical changes before any rollback.`,related:[{id:`update-correlation`,reason:`Adds update timing.`},{id:`segment-impact`,reason:`Shows where movement sits.`},{id:`measure-change`,reason:`Evaluates a fix.`}]}},SL={"affected-urls":{name:`URLs affected by a crawl issue`,description:`Open the exact pages behind a crawl finding so you can review or fix the right URLs.`,useWhen:[`A crawl summary shows an issue that needs a closer look.`,`You need the affected URLs for one rule, category, or severity.`],avoidWhen:[`You have not run or selected a crawl yet.`,`You need an overview of the most important crawl findings first.`],outcome:`A focused list of affected URLs with the evidence for each match.`},"agent-readiness":{name:`AI agent readiness`,description:`Check whether a content site gives agents clean, stable, machine-readable pages and discovery files.`,useWhen:[`You want to test Markdown alternatives, content negotiation, Agent Skills, llms.txt, identity, and crawler access together.`,`You need evidence that the agent-facing version of a content site matches its public HTML routes.`],avoidWhen:[`You need an AI visibility, mention, citation, or ranking report.`,`You are auditing a public API, interactive application, or commerce flow rather than a content site.`],outcome:`An unscored content-site assessment with failed routes, evidence, and clear next actions.`},"ai-readiness":{name:`AI search technical readiness`,description:`Check whether crawl, index, or snippet controls could block pages from Google AI search features.`,useWhen:[`You want to check technical access before investigating AI search visibility.`,`You need page-level evidence for robots, indexability, or snippet restrictions.`],avoidWhen:[`You want a visibility score or a prediction of AI citations.`,`You need evidence of visits from AI products.`],outcome:`A technical blocker list with affected pages and clear limits.`},"ai-referrals":{name:`AI referral traffic`,description:`Find visits from known AI products in GA4 and see a ranked, bounded set of landing pages that received them.`,useWhen:[`You want measured AI referral traffic from GA4.`,`You need the landing pages and sources behind those visits.`],avoidWhen:[`GA4 is not connected or does not track the site.`,`You want to measure citations or unclicked mentions inside AI answers.`],outcome:`A source and landing-page breakdown of identifiable AI referrals.`},"ai-search-scorecard":{name:`AI search scorecard`,description:`Score AI-search technical readiness 0-100 from one crawl using this tool's own weighted checks, with observed evidence kept separate.`,useWhen:[`You want a single scored summary of the AI-search technical evidence this tool already collects.`,`You need per-check pass, warn, fail, or unknown states with the exact weights and formula.`],avoidWhen:[`You want a Google or AI-engine eligibility verdict, a ranking prediction, or proof of citations.`,`You need per-page fixes rather than a scored overview.`],outcome:`A 0-100 heuristic score with per-check evidence, unknown exclusions, and a partial flag.`},"audit-page":{name:`Single-page SEO audit`,description:`Check one URL for response, indexability, metadata, headings, links, and page content evidence.`,useWhen:[`One page needs a technical review before you change it.`,`A broader report points to a specific URL.`],avoidWhen:[`You need to discover issues across a whole site.`,`The page requires a logged-in browser session.`],outcome:`A page-level audit that separates observed evidence from review advice.`},"audit-urls":{name:`Selected URL audit`,description:`Audit a specific list of pages without crawling the rest of the site.`,useWhen:[`You already know which URLs need checking.`,`You want a repeatable pre-release or post-release check for key pages.`],avoidWhen:[`You need the crawler to discover pages for you.`,`You only need a deep review of one page.`],outcome:`Consistent technical checks for each supplied URL.`},cannibalisation:{name:`Query and URL overlap`,description:`Find search queries that appear across several URLs and decide whether the overlap needs attention.`,useWhen:[`Several pages may be competing for the same search intent.`,`You want query-level evidence before merging or redirecting pages.`],avoidWhen:[`You plan to treat every multi-URL query as a problem.`,`The site has too little Search Console data for meaningful overlap.`],outcome:`A ranked review list of queries, URLs, and their share of search exposure.`},"community-intent":{name:`Community and review intent`,description:`Find Search Console queries that explicitly ask for reviews, comparisons, recommendations, or real experience.`,useWhen:[`You want to see where searchers ask for proof, opinions, or comparisons.`,`You are planning content that answers community-style questions.`],avoidWhen:[`You want the tool to guess intent without explicit query wording.`,`You need proof that a new page will rank.`],outcome:`A review list of real queries grouped by the intent language they contain.`},"compare-crawls":{name:`Compare saved crawls`,description:`Compare two saved crawl snapshots to see which pages and technical findings changed.`,useWhen:[`You want to check what changed after a release or migration.`,`You need evidence that a technical fix appeared in a later crawl.`],avoidWhen:[`The two crawls used materially different scope or settings.`,`You have only one saved crawl.`],outcome:`A before-and-after view of added, removed, fixed, and new findings.`},"content-optimization":{name:`Search-led content brief`,description:`Turn a page and its Search Console queries into a practical brief for improving relevance and coverage.`,useWhen:[`An existing page has search demand but needs a clearer improvement plan.`,`You want first-party query evidence beside the current page content.`],avoidWhen:[`The URL has no useful Search Console history.`,`You want generated copy or a traffic forecast.`],outcome:`A page brief with query evidence, content checks, and specific review points.`},"crawl-diff":{name:`Monitor crawl changes`,description:`Crawl a site and compare it with the previous matching run to catch technical changes.`,useWhen:[`You want a repeatable technical check after deployments.`,`You need newly introduced or resolved crawl findings.`],avoidWhen:[`There is no comparable earlier crawl.`,`You need to compare two specific saved crawl IDs.`],outcome:`A fresh crawl plus a clear list of technical changes since the previous run.`},"crawl-history":{name:`Saved crawl history`,description:`List local crawl snapshots so you can choose the right report by site and date.`,useWhen:[`You need to find a crawl ID for review or comparison.`,`You want to confirm which crawl snapshots exist locally.`],avoidWhen:[`You need the findings inside a crawl rather than its saved metadata.`],outcome:`A compact list of saved crawls with IDs, sites, dates, and scope.`},"crawl-report":{name:`Open a saved crawl`,description:`Load a saved crawl by ID, or open the latest local crawl for a site.`,useWhen:[`You already know which saved crawl you want to inspect.`,`Another report asks for crawl evidence by report ID.`],avoidWhen:[`You need to create a new crawl.`,`You only need a filtered list for one issue.`],outcome:`The selected crawl summary with optional page and issue detail.`},"crawler-rules":{name:`Crawler rule catalog`,description:`Find the available technical check IDs and the evidence each crawler rule looks for.`,useWhen:[`You need a valid rule ID for another crawl report.`,`You want to see which technical checks are available by category.`],avoidWhen:[`You need affected pages or live crawl results.`],outcome:`A searchable catalog of crawler rules, severity, and guidance.`},"ctr-underperformers":{name:`CTR underperformers`,description:`Find high-impression queries whose click-through rate trails comparable queries at similar positions.`,useWhen:[`You want title or snippet review candidates backed by Search Console data.`,`You need to compare CTR fairly across similar average positions.`],avoidWhen:[`You want a promised click uplift.`,`The comparison groups have too little data.`],outcome:`A ranked list of query and page pairs with CTR comparison evidence.`},"decaying-pages":{name:`Pages losing search clicks`,description:`Find pages and queries with meaningful click declines across comparable Search Console periods.`,useWhen:[`You want to investigate a sustained organic search decline.`,`You need the pages and queries behind lost clicks.`],avoidWhen:[`The newer period is incomplete or not comparable.`,`You want to assume every decline is caused by old content.`],outcome:`A ranked investigation list with before-and-after search evidence.`},"entity-readiness":{name:`Entity signal review`,description:`Check names, authors, dates, structured data, and profile links that help identify who created a page.`,useWhen:[`You want to review publisher, organization, or author signals across crawled pages.`,`You need examples of missing or inconsistent entity evidence.`],avoidWhen:[`You want a knowledge graph or ranking guarantee.`,`You have not saved or supplied a crawl.`],outcome:`Page-level entity observations with examples that need human review.`},"explain-crawl-issue":{name:`Explain a crawl issue`,description:`Explain what one crawler rule means, why it may matter, and how to verify a fix.`,useWhen:[`A crawl returned a rule ID you do not recognize.`,`You need implementation and verification guidance before creating work.`],avoidWhen:[`You need the URLs affected by the rule.`,`You have not selected a valid crawler rule ID.`],outcome:`A plain-English rule explanation with fix and verification steps.`},"generate-llms-txt":{name:`Generate an llms.txt draft`,description:`Create a concise llms.txt draft from selected public pages in a crawl report.`,useWhen:[`You want a human-reviewed navigation file for useful public pages.`,`You have a current crawl that identifies suitable canonical URLs.`],avoidWhen:[`You expect the file to improve rankings, indexing, or AI citations.`,`The source crawl is old, partial, or aimed at the wrong site section.`],outcome:`An editable llms.txt draft with source and size limits kept visible.`},"geo-gaps":{name:`Google AI search controls`,description:`Find crawl, index, and snippet settings that could restrict pages in Google AI search features.`,useWhen:[`You want to find technical restrictions that apply to Google AI search.`,`You need affected pages for a crawl, index, or preview control.`],avoidWhen:[`You want a generic AI visibility score.`,`You need referral traffic or citation monitoring.`],outcome:`A focused list of technical restrictions and the pages where they appear.`},"index-coverage-plan":{name:`URL Inspection coverage plan`,description:`Choose which sitemap URLs and Search Console properties should use limited URL Inspection checks.`,useWhen:[`A large site needs a practical sampling plan for URL Inspection.`,`You want to spread checks across useful URL-prefix properties.`],avoidWhen:[`You need current Google index results rather than a plan.`,`You want complete coverage beyond the available quota.`],outcome:`A quota-aware list of URLs and properties to inspect next.`},"index-coverage":{name:`Index coverage signals`,description:`Compare the pages your site exposes with pages that had Google Search visibility, then choose representative URLs for URL Inspection.`,useWhen:[`You need to find crawlable pages that did not appear in the Search Console results returned for the selected period.`,`You want a focused review list before spending URL Inspection quota.`],avoidWhen:[`You need proof that a URL is indexed or excluded. Use URL Inspection for that URL.`,`You do not have a saved crawl for the selected site.`],outcome:`Cross-source URL groups with clear evidence limits and a representative review queue for URL Inspection.`},"index-monitor":{name:`Collect index snapshots`,description:`Check selected sitemap URLs with Google URL Inspection and save the results locally for later comparison.`,useWhen:[`You want current Google index evidence for a controlled URL set.`,`You need repeatable snapshots for future monitoring.`],avoidWhen:[`You expect every URL in a large site to be checked at once.`,`You only need to review previously saved snapshots.`],outcome:`Saved URL Inspection results with quota skips and provider failures separated.`},"index-watch":{name:`Review index changes`,description:`Compare current Google index evidence with saved snapshots to find regressions and recoveries.`,useWhen:[`You want to see which monitored URLs changed index state.`,`You need to separate page issues from quota or provider failures.`],avoidWhen:[`No earlier index snapshots exist.`,`You want a complete index count for the whole site.`],outcome:`A review queue of current issues, regressions, recoveries, and failed checks.`},"internal-links":{name:`Internal link opportunities`,description:`Find relevant pages that could add a useful internal link to a target URL.`,useWhen:[`A target page needs stronger internal discovery or contextual links.`,`You want candidate source pages checked against their current content.`],avoidWhen:[`You want links added automatically.`,`The target and candidate pages do not share a clear topic or user need.`],outcome:`Verified source-page candidates with query and page evidence for review.`},"link-recovery":{name:`Recover broken search-value URLs`,description:`Find URLs that previously earned search clicks but now fail, redirect poorly, or block indexing.`,useWhen:[`You suspect valuable URLs broke during a migration or release.`,`You want to prioritize technical recovery using Search Console evidence.`],avoidWhen:[`You want a backlink report.`,`You have no Search Console page history for the site.`],outcome:`A ranked recovery list with current URL checks and prior search value.`},"llms-txt-audit":{name:`Audit llms.txt`,description:`Check whether llms.txt exists, whether its links work, and how well it reflects useful public pages.`,useWhen:[`The site publishes llms.txt and you want to verify it.`,`You are deciding whether a concise navigation file would help agents.`],avoidWhen:[`You expect llms.txt to improve Google rankings or indexing.`,`You need to generate a new draft rather than audit the current file.`],outcome:`File, link, and page coverage findings with no visibility claims.`},"measure-change":{name:`Measure an SEO change`,description:`Compare equal Search Console periods before and after a known site change.`,useWhen:[`A release, migration, or content change has a clear date.`,`Both comparison periods contain final and comparable data.`],avoidWhen:[`The after period is incomplete.`,`You need proof that the change caused the movement.`],outcome:`A before-and-after search comparison with confounders and data limits shown.`},"monthly-action-plan":{name:`Monthly SEO action plan`,description:`Review a calendar month of search performance and turn the evidence into next actions.`,useWhen:[`You need a recurring monthly review with clear priorities.`,`The selected month has final Search Console data.`],avoidWhen:[`You only need a client-ready summary without an action queue.`,`The month is still incomplete.`],outcome:`A monthly performance summary followed by a focused action plan.`},"monthly-report":{name:`Monthly SEO report`,description:`Turn one calendar month of search evidence into a concise report for clients or teams.`,useWhen:[`You need a readable monthly summary of organic search performance.`,`The selected month has final Search Console data.`],avoidWhen:[`You need a rolling period rather than a calendar month.`,`You want a detailed technical site audit.`],outcome:`A clear monthly narrative with wins, losses, caveats, and next steps.`},"narrative-report":{name:`Client-ready SEO narrative`,description:`Turn search, change, and monitoring evidence into a readable update without hiding caveats.`,useWhen:[`You need to explain recent SEO performance to a client or team.`,`You want one narrative built from several available evidence sources.`],avoidWhen:[`You need raw rows for further analysis.`,`You want unsupported forecasts or causal claims.`],outcome:`A concise written report with evidence, limits, and practical next steps.`},"okf-build":{name:`Build site knowledge for agents`,description:`Create a compact, cited site knowledge pack from public pages in a crawl.`,useWhen:[`An agent needs reliable site facts with links back to source pages.`,`You want a portable knowledge pack from selected crawl content.`],avoidWhen:[`The crawl contains private, stale, or irrelevant pages.`,`You want the tool to invent missing facts.`],outcome:`An OKF manifest and optional markdown files with source links.`},"okf-validate":{name:`Validate a site knowledge pack`,description:`Check supplied OKF markdown files for structure, references, and broken links before use.`,useWhen:[`You created or edited an OKF pack and want to check it.`,`An agent needs to verify files before loading them.`],avoidWhen:[`You need to build the pack from a crawl first.`],outcome:`A validation result with exact file, reference, and link errors.`},"page-opportunities":{name:`Opportunities for one page`,description:`Find Search Console queries and page evidence that point to useful improvements for one URL.`,useWhen:[`You want to improve an existing page using its own search demand.`,`You need query themes and page checks in one result.`],avoidWhen:[`The page has no useful Search Console data.`,`You want a guaranteed traffic gain.`],outcome:`A ranked page improvement list tied to real queries and current content.`},"performance-audit":{name:`Page performance audit`,description:`Run Lighthouse lab checks and add CrUX field Core Web Vitals when that data is available.`,useWhen:[`You need actionable performance diagnostics for a page.`,`You want lab findings kept separate from real-user field data.`],avoidWhen:[`You want Lighthouse scores presented as field Core Web Vitals.`,`The page cannot be fetched from the local machine.`],outcome:`Lab diagnostics plus clearly labelled field metrics when available.`},"pseo-audit":{name:`Programmatic SEO audit`,description:`Review repeated page templates using search demand, crawl evidence, and optional Google index checks.`,useWhen:[`A site publishes many pages from repeatable templates.`,`You need to compare template groups before expanding or pruning them.`],avoidWhen:[`You want every repeated path labelled as spam or thin content.`,`There are too few pages to compare as a template group.`],outcome:`Template-level demand, quality, and index evidence with representative URLs.`},"query-clusters":{name:`Search query clusters`,description:`Group related Search Console queries so repeated demand themes are easier to review.`,useWhen:[`A large query list needs a clearer topic structure.`,`You want to find repeated wording within a site section.`],avoidWhen:[`You need semantic or intent classification beyond shared query terms.`,`You want clusters turned into new pages automatically.`],outcome:`Stable query groups with the search demand behind each theme.`},"quick-wins":{name:`Page-one CTR opportunities`,description:`Find page-one query and URL pairs whose CTR deserves a closer look.`,useWhen:[`You want high-impression title and snippet review candidates.`,`You need page-one opportunities ranked from Search Console data.`],avoidWhen:[`You want a promised click gain.`,`You plan to treat a CTR heuristic as a Google requirement.`],outcome:`A ranked review queue with CTR, position, impressions, and comparison evidence.`},"redirect-trace":{name:`Redirect trace`,description:`Follow every redirect hop and check the final page, indexability, and canonical signals.`,useWhen:[`A URL redirects unexpectedly or through several hops.`,`You need to verify the final destination after a migration.`],avoidWhen:[`You need to discover redirect issues across a whole site.`,`The URL requires a signed-in browser session.`],outcome:`The complete redirect chain with final response and page checks.`},"refresh-priorities":{name:`Content refresh priorities`,description:`Combine search declines and near-term opportunities into a ranked content review queue.`,useWhen:[`You need to decide which existing pages to review first.`,`You want several Search Console signals ranked in one queue.`],avoidWhen:[`You need a technical crawl plan.`,`You want the ranking treated as a traffic forecast.`],outcome:`A focused list of pages and queries to investigate next, with reasons.`},"search-performance-overview":{name:`Search performance overview`,description:`Find where Google Search performance changed and choose the next focused report to inspect it.`,useWhen:[`You are starting an investigation and do not yet know which pages or queries moved.`,`You want one view of declines, gains, overlap, and near-term opportunities.`],avoidWhen:[`You already know the exact question and can run a focused report.`,`You need a technical crawl without Search Console analysis.`],outcome:`A clear search performance summary with a short list of next reports.`},"second-page":{name:`Second-page opportunities`,description:`Find URLs averaging positions 11 to 20 and see the queries behind each opportunity.`,useWhen:[`You want existing pages that may deserve closer review near page one.`,`You need page-level evidence across all matching queries.`],avoidWhen:[`You want average position treated as a fixed rank.`,`You need proof that a change will move the page to page one.`],outcome:`A ranked URL list with query, click, impression, and position evidence.`},"segment-impact":{name:`Search impact by segment`,description:`Compare page or query groups across equal periods to find which parts of a site moved.`,useWhen:[`A traffic change may be concentrated in one directory, page type, or query group.`,`You need to separate broad movement from a local segment issue.`],avoidWhen:[`The periods are incomplete or not comparable.`,`Missing Search Console rows would be mistaken for zero activity.`],outcome:`A segment comparison showing where clicks and impressions changed.`},"seo-to-ai-query":{name:`AI monitoring prompt ideas`,description:`Turn real Search Console query wording into a repeatable set of prompts for external AI monitoring.`,useWhen:[`You need prompt ideas grounded in the way people already find the site.`,`You want a stable prompt set for manual or third-party monitoring.`],avoidWhen:[`You want proof of demand inside AI products.`,`You want citation or visibility results from this report.`],outcome:`A prompt set linked back to its source queries and search metrics.`},"setup-check":{name:`Check local setup`,description:`Check Google login, scopes, local configuration, and saved defaults before running reports.`,useWhen:[`Authentication or site selection is failing.`,`You want to verify the local setup before investigating report errors.`],avoidWhen:[`You need SEO findings rather than a setup check.`],outcome:`A local setup status with the exact problems that need fixing.`},"site-crawl":{name:`Technical SEO site crawl`,description:`Crawl a site, check technical SEO signals, and save a local baseline for follow-up reports.`,useWhen:[`You need to discover technical issues across linked pages.`,`You want a crawl snapshot for later filtering or comparison.`],avoidWhen:[`You only need to check one page or a known URL list.`,`Important content requires browser rendering that is not enabled.`],outcome:`A crawl summary with page evidence, technical findings, and saved report ID.`},"striking-distance":{name:`Queries near page one`,description:`Find Search Console query and page pairs averaging positions 11 to 20.`,useWhen:[`You want existing search demand close to page one.`,`You need a compact list of queries and URLs for content review.`],avoidWhen:[`You want average position treated as an exact daily rank.`,`You need a promised ranking or click gain.`],outcome:`A ranked list of near-page-one query and URL pairs with search evidence.`},"technical-watch":{name:`Technical SEO watch`,description:`Run crawl and index checks together to catch technical regressions on a schedule.`,useWhen:[`You want recurring checks after releases or site changes.`,`You need crawl changes and selected Google index checks in one run.`],avoidWhen:[`You have not chosen any crawl, index, or link recovery input.`,`You need a one-off deep audit rather than ongoing monitoring.`],outcome:`A combined technical change report with failures and skipped checks kept separate.`},"top-fixes":{name:`Top technical SEO fixes`,description:`Turn crawl findings into a short, ranked queue of technical issues to investigate first.`,useWhen:[`A crawl contains too many findings to review at once.`,`You need representative URLs and reasons for the highest priorities.`],avoidWhen:[`You need every affected URL for one rule.`,`You plan to treat rank as proof of business impact.`],outcome:`A focused technical fix queue with issue evidence and example URLs.`},"traffic-anomaly":{name:`Unusual search traffic changes`,description:`Find recent clicks or impressions that moved beyond the site's normal Search Console pattern.`,useWhen:[`You want an early signal of an unusual search change.`,`You need to know whether recent movement stands out from normal variation.`],avoidWhen:[`There is too little history for a useful baseline.`,`You want the report to identify the cause of the change.`],outcome:`A statistical movement signal with the dates, baseline, and limits shown.`},"update-correlation":{name:`Google update correlation`,description:`Compare search movement dates with official Google ranking update windows.`,useWhen:[`A traffic change overlaps a confirmed Google ranking update.`,`You need a timeline before investigating affected pages and queries.`],avoidWhen:[`You want overlap presented as proof of causation.`,`The traffic window does not cover the update dates.`],outcome:`A traffic and update timeline with overlaps clearly labelled as correlation.`},"update-postmortem":{name:`Google update impact review`,description:`Find the pages and queries that gained or lost around a confirmed Google ranking update.`,useWhen:[`You need winners and losers around a known update window.`,`You want to compare affected segments and known site changes.`],avoidWhen:[`You want the report to claim the update caused every change.`,`The comparison periods are incomplete or contain major unrecorded changes.`],outcome:`A post-update review of winners, losers, confounders, and next checks.`}};function CL(e){let t=SL[e],n=xL[e];if(!(!t||!n))return{...t,...n}}function wL(e){e.registerTool(`seo_audit_page`,{description:`Run a single-page technical and content audit`,inputSchema:{url:D.string().url(),site:D.string().optional(),js:D.boolean().optional(),refresh:D.boolean().optional()}},async({url:e,site:t,js:n,refresh:r})=>{try{let i=await S_({url:e,site:t,js:rL(n,`auto`),refresh:r});return $(`Audit complete for ${e}. Found ${i.issues.length} issues.`,i)}catch(e){return Q(e)}})}const TL=aL([`verifyContent`,`verifyLimit`,`js`,`fetchConcurrency`,`fetchIntervalCap`,`fetchIntervalMs`,`refresh`]);function EL(e){return{verifyContent:e.verifyContent,verifyLimit:e.verifyLimit,js:rL(e.js,void 0),rate:uL({fetchConcurrency:e.fetchConcurrency,fetchIntervalCap:e.fetchIntervalCap,fetchIntervalMs:e.fetchIntervalMs}),refresh:e.refresh}}function DL(e){e.registerTool(`seo_monthly_report`,{description:`Generate a monthly SEO report narrative`,inputSchema:{...aL([`site`,`limit`,`includeBrand`]),month:nL.optional(),...TL}},async({site:e,month:t,limit:n,includeBrand:r,...i})=>{try{let a=await JP({site:e,month:t,limit:n,includeBrand:r,...EL(i)});return $(`Monthly report generated for ${a.month}. ${a.headline}`,{...a,presentation:YP(a)},{markdown:a.markdown})}catch(e){return Q(e)}})}function OL(e){e.registerTool(`seo_report_narrative`,{description:`Generate a client-ready SEO narrative across diagnosis, changes, and monitoring`,inputSchema:{...aL([`site`,`days`,`recentDays`,`limit`,`includeBrand`]),startDate:tL.optional(),endDate:tL.optional(),changeLimit:D.number().optional(),...TL}},async({site:e,days:t,recentDays:n,startDate:r,endDate:i,limit:a,changeLimit:o,includeBrand:s,...c})=>{try{let l=await qP({site:e,days:t,recentDays:n,startDate:r,endDate:i,limit:a,changeLimit:o,includeBrand:s,...EL(c)});return $(l.headline,{...l,presentation:YP(l)},{markdown:l.markdown})}catch(e){return Q(e)}})}function kL(e){e.registerTool(`seo_second_page`,{description:`Find URLs with GSC average position above 10 through 20 and return evidence-grounded investigation prompts`,inputSchema:{...aL([`site`,`range`,`minImpressions`,`limit`,`includeBrand`]),...TL,range:D.number().int().min(1).max(548).optional(),minImpressions:D.number().int().min(0).max(1e9).optional(),limit:D.number().int().min(1).max(100).optional(),verifyLimit:D.number().int().min(0).max(100).optional(),brandTerms:D.array(D.string().trim().min(1).max(200)).max(20).optional(),fetchConcurrency:D.number().int().min(1).max(16).optional(),fetchIntervalCap:D.number().int().min(1).max(60).optional(),fetchIntervalMs:D.number().int().min(100).max(6e4).optional()}},async({site:e,range:t,minImpressions:n,limit:r,includeBrand:i,brandTerms:a,...o})=>{try{let s=await gF({site:e,range:t,minImpressions:n,limit:r,includeBrand:i,brandTerms:a,...EL(o)});return $(s.summary.verdict,s)}catch(e){return Q(e)}})}function AL(e){OL(e),DL(e),wL(e),kL(e)}const jL=D.strictObject({site:D.string().trim().min(1).max(2048),startUrl:D.string().url().optional(),urls:D.array(D.string().url()).min(1).max(100).optional(),sitemaps:D.array(D.string().url()).min(1).max(20).optional(),properties:D.array(D.string().trim().min(1).max(2048)).min(1).max(1e3).optional(),limit:D.number().int().min(1).max(25e4).optional(),refresh:D.boolean().optional(),js:D.boolean().optional(),languageCode:D.string().trim().min(1).max(35).optional(),dailyLimit:D.number().int().min(1).max(2e3).optional(),inspectLimit:D.number().int().min(1).max(100).optional(),maxUrls:D.number().int().min(1).max(25e4).optional(),recoverLinks:D.boolean().optional(),recoverDays:D.number().int().min(1).max(548).optional(),recoverLimit:D.number().int().min(1).max(100).optional(),recoverMinClicks:D.number().int().min(0).max(1e9).optional(),recoverMinImpressions:D.number().int().min(0).max(1e9).optional()}).superRefine((e,t)=>{let n=!!(e.urls?.length||e.sitemaps?.length);!e.startUrl&&!n&&e.recoverLinks===!1&&t.addIssue({code:`custom`,path:[`recoverLinks`],message:`Pass startUrl, urls, sitemaps, or enable link recovery for technical-watch.`})});function ML(e){return $(e.summary,{...e,presentation:tI(e)},{markdown:nI(e)})}function NL(e){e.registerTool(`seo_workflow_diagnose_property`,{description:`Run the full agent workflow for property diagnosis and next actions`,inputSchema:{...aL([`site`,`days`,`recentDays`,`limit`,`includeBrand`,`refresh`])}},async({site:e,days:t,recentDays:n,limit:r,includeBrand:i,refresh:a})=>{try{return ML(await YF({site:e,days:t,recentDays:n,limit:r,includeBrand:i,refresh:a}))}catch(e){return Q(e)}}),e.registerTool(`seo_workflow_monthly_report`,{description:`Run the monthly reporting workflow with next actions`,inputSchema:{...aL([`site`,`limit`,`includeBrand`,`refresh`]),month:nL.optional()}},async({site:e,month:t,limit:n,includeBrand:r,refresh:i})=>{try{return ML(await rI({site:e,month:t,limit:n,includeBrand:r,refresh:i}))}catch(e){return Q(e)}}),e.registerTool(`seo_workflow_update_postmortem`,{description:`Run winner/loser postmortem analysis for recent Google update exposure`,inputSchema:{...aL([`site`,`days`,`recentDays`,`limit`,`includeBrand`,`refresh`]),knownConfounders:D.array(D.string()).optional(),includeChangeLog:D.boolean().optional()}},async({site:e,days:t,recentDays:n,limit:r,includeBrand:i,knownConfounders:a,includeChangeLog:o,refresh:s})=>{try{return ML(await FI({site:e,days:t,recentDays:n,limit:r,includeBrand:i,knownConfounders:a,includeChangeLog:o,refresh:s}))}catch(e){return Q(e)}}),e.registerTool(`seo_workflow_technical_watch`,{description:`Run scheduled-style crawl-diff and index-watch monitoring in one workflow`,inputSchema:jL},async({site:e,startUrl:t,urls:n,sitemaps:r,properties:i,limit:a,refresh:o,js:s,languageCode:c,dailyLimit:l,inspectLimit:u,maxUrls:d,recoverLinks:f,recoverDays:p,recoverLimit:m,recoverMinClicks:h,recoverMinImpressions:g})=>{try{return ML(await vI({site:e,startUrl:t,urls:n,sitemaps:r,properties:i,limit:a,refresh:o,js:rL(s,`auto`),languageCode:c,dailyLimit:l,inspectLimit:u,maxUrls:d,recoverLinks:f,recoverDays:p,recoverLimit:m,recoverMinClicks:h,recoverMinImpressions:g}))}catch(e){return Q(e)}}),e.registerTool(`seo_workflow_refresh_priorities`,{description:`Rank decay, striking-distance, quick-win, cannibalisation, and diagnosis signals into an action queue`,inputSchema:{...aL([`site`,`days`,`recentDays`,`limit`,`includeBrand`,`verifyContent`,`verifyLimit`,`refresh`]),ga4PropertyId:D.string().optional()}},async({site:e,days:t,recentDays:n,limit:r,includeBrand:i,ga4PropertyId:a,verifyContent:o,verifyLimit:s,refresh:c})=>{try{return ML(await gI({site:e,days:t,recentDays:n,limit:r,includeBrand:i,ga4PropertyId:a,verifyContent:o,verifyLimit:s,refresh:c}))}catch(e){return Q(e)}})}const PL=[`ai-search`,`crawl`,`diagnosis`,`experiments`,`monitoring`,`opportunities`,`reporting`,`setup`,`workflows`],FL=[{category:`setup`,register:hL,names:[`seo_doctor`]},{category:`diagnosis`,register:hL,names:[`seo_segment_impact`,`seo_striking_distance`,`seo_traffic_anomaly`,`seo_update_correlate`]},{category:`opportunities`,register:vL,names:[`seo_cannibal`,`seo_ctr_underperformers`,`seo_decaying`,`seo_internal_links`,`seo_query_cluster`,`seo_quick_wins`]},{category:`ai-search`,register:lL,names:[`seo_ai_referrals`,`seo_community_intent`,`seo_content_optimization`,`seo_page_opportunities`,`seo_performance_audit`,`seo_to_ai_query`]},{category:`ai-search`,register:mL,names:[`seo_ai_search_scorecard`]},{category:`crawl`,register:mL,names:[`seo_affected_urls`,`seo_agent_readiness`,`seo_ai_readiness`,`seo_audit_urls`,`seo_compare_crawl_reports`,`seo_crawl_site`,`seo_entity_readiness`,`seo_explain_issue`,`seo_geo_gaps`,`seo_get_crawl_report`,`seo_list_crawl_reports`,`seo_list_rules`,`seo_llms_txt_audit`,`seo_llms_txt_generate`,`seo_okf_build`,`seo_okf_validate`,`seo_top_fixes`]},{category:`monitoring`,register:_L,names:[`seo_crawl_diff`,`seo_index_coverage`,`seo_index_coverage_plan`,`seo_index_monitor`,`seo_index_watch`,`seo_link_recover`,`seo_redirect_trace`]},{category:`reporting`,register:AL,names:[`seo_audit_page`,`seo_monthly_report`,`seo_report_narrative`,`seo_second_page`]},{category:`experiments`,register:gL,names:[`seo_measure_change`]},{category:`reporting`,register:bL,names:[`seo_pseo_audit`]},{category:`workflows`,register:NL,names:[`seo_workflow_diagnose_property`,`seo_workflow_monthly_report`,`seo_workflow_refresh_priorities`,`seo_workflow_technical_watch`,`seo_workflow_update_postmortem`]}],IL={cannibal:`cannibalisation`,decaying:`decaying-pages`,"crawl-site":`site-crawl`,"link-recover":`link-recovery`,"llms-txt-generate":`generate-llms-txt`,"query-cluster":`query-clusters`,"report-narrative":`narrative-report`,"to-ai-query":`seo-to-ai-query`,"workflow-refresh-priorities":`refresh-priorities`,"workflow-technical-watch":`technical-watch`,"workflow-update-postmortem":`update-postmortem`,"workflow-diagnose-property":`search-performance-overview`,"workflow-monthly-report":`monthly-action-plan`,doctor:`setup-check`,"update-correlate":`update-correlation`,"compare-crawl-reports":`compare-crawls`,"list-crawl-reports":`crawl-history`,"list-rules":`crawler-rules`,"get-crawl-report":`crawl-report`,"explain-issue":`explain-crawl-issue`};function LL(e){let t=e.replace(/^seo_/,``).replaceAll(`_`,`-`);return IL[t]??t}function RL(e,t){return e.id<t.id?-1:+(e.id>t.id)}function zL(e){let t=new Map;return e({registerTool(e,n,r){t.set(e,{inputSchema:n.inputSchema,handler:r})}}),t}function BL(e){return e instanceof D.ZodObject?e:D.strictObject(e??{})}function VL(){let e=new Map,t=[];for(let n of FL){let r=e.get(n.register);r||(r=zL(n.register),e.set(n.register,r));for(let e of n.names){let i=r.get(e);if(!i)throw Error(`MCP report registry references missing tool ${e}.`);let a=LL(e),o=CL(a);if(!o)throw Error(`MCP report registry is missing guidance for ${a}.`);t.push({id:a,category:n.category,...o,inputSchema:BL(i.inputSchema),handler:i.handler})}}return t.sort(RL)}const HL=VL(),UL=new Set(HL.map(e=>e.id)),WL=Object.keys(SL).filter(e=>!UL.has(e));if(WL.length>0)throw Error(`MCP report guidance references unknown reports: ${WL.join(`, `)}.`);const GL=new Map(HL.map(e=>[e.id,e]));function KL(e){return HL.filter(t=>!e||t.category===e).map(({id:e,name:t,description:n,category:r})=>({id:e,category:r,name:t,description:n}))}function qL(e){return GL.get(e)}function JL(e){return e.issues.map(e=>`${e.path.length>0?e.path.join(`.`):`input`}: ${e.message}`).join(`; `)}function YL(e){return KL(e)}function XL(e){let t=qL(e);if(!t)throw new k(`INVALID_INPUT`,`Unknown report: ${e}.`);return{id:t.id,category:t.category,name:t.name,description:t.description,useWhen:t.useWhen,avoidWhen:t.avoidWhen,outcome:t.outcome,readOrder:t.readOrder,doNotClaim:t.doNotClaim,verify:t.verify,related:t.related,inputSchema:D.toJSONSchema(t.inputSchema)}}async function ZL(e,t={}){let n=qL(e);if(!n)throw new k(`INVALID_INPUT`,`Unknown report: ${e}.`);let r=n.inputSchema.safeParse(t);if(!r.success)throw new k(`INVALID_INPUT`,`Invalid parameters for ${e}: ${JL(r.error)}`);return n.handler(r.data)}async function QL(e,t={}){try{return await ZL(e,t)}catch(e){return Q(e)}}const $L=D.looseObject({});function eR(e){e.registerTool(`seo_list_reports`,{description:`List compact SEO report ids, names, and purposes, optionally by category`,inputSchema:{category:D.enum(PL).optional()},outputSchema:$L,annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0}},async({category:e})=>{let t=YL(e),n=[...new Set(t.map(e=>e.category))];return $(`${t.length} SEO reports available across ${n.length} categories.`,{reports:t,categories:n})}),e.registerTool(`seo_describe_report`,{description:`Explain when to use one SEO report, what it returns, and which parameters it accepts`,inputSchema:{id:D.string().trim().min(1).max(100)},outputSchema:$L,annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0}},async({id:e})=>{try{let t=XL(e);return $(`${t.id}: ${t.description}`,{report:t})}catch(e){return Q(e)}}),e.registerTool(`seo_run_report`,{description:`Run one SEO report by id with parameters from seo_describe_report`,inputSchema:{id:D.string().trim().min(1).max(100),params:D.record(D.string(),D.unknown()).optional()},outputSchema:$L,annotations:{destructiveHint:!1}},async({id:e,params:t})=>QL(e,t))}function tR(){let e=new le({name:`seo`,version:eL},{capabilities:{logging:{}}});return eR(e),e}async function nR(e={}){let t=tR();if(e.test){process.stdout.write(`seo MCP server constructed successfully.
712
712
  `);return}let n=new ue;await t.connect(n)}export{Z as $,ht as $t,uM as A,fb as At,Ck as B,qg as Bt,qP as C,yS as Ct,YM as D,Mb as Dt,GN as E,ex as Et,GA as F,A_ as Ft,BO as G,mn as Gt,ck as H,hn as Ht,kA as I,I_ as It,jO as J,cn as Jt,zO as K,fn as Kt,$k as L,S_ as Lt,Zj as M,xy as Mt,Fj as N,dy as Nt,vM as O,Nb as Ot,Cj as P,rv as Pt,qE as Q,ut as Qt,Sk as R,s_ as Rt,JP as S,vS as St,RN as T,tx as Tt,lk as U,gn as Ut,xk as V,Un as Vt,HO as W,pn as Wt,VD as X,Yt as Xt,dO as Y,en as Yt,TD as Z,qt as Zt,gI as _,pw as _t,YL as a,Fe as an,oT as at,kF as b,bS as bt,eL as c,Te as cn,Bw as ct,BI as d,Nw as dt,dt as en,gE as et,zI as f,Mw as ft,vI as g,yw as gt,FI as h,bw as ht,ZL as i,mt as in,ST as it,rM as j,rb as jt,hM as k,bb as kt,GI as l,Ce as ln,Lw as lt,UI as m,vw as mt,tR as n,ft as nn,CT as nt,QL as o,Pe as on,Iw as ot,VI as p,gw as pt,PO as q,ln as qt,XL as r,et as rn,mT as rt,nR as s,k as sn,zw as st,PL as t,$e as tn,_T as tt,HI as u,Rw as ut,YF as v,rw as vt,vP as w,_S as wt,gF as x,xS as xt,JF as y,YC as yt,Tk as z,e_ as zt};
713
- //# sourceMappingURL=dist-BnXs3Jen.js.map
713
+ //# sourceMappingURL=dist-DMSBrZYd.js.map