seo 0.2.37 → 0.2.38

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.
@@ -603,7 +603,7 @@ CREATE TABLE IF NOT EXISTS provider_spend_notices (
603
603
  provider, credential_scope, operation, request_hash, request_json,
604
604
  response_json, row_count, source_cost_micros, task_ids_json,
605
605
  fetched_at, expires_at
606
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(e.provider,e.credentialScope,e.operation,Br(e,o),o,s,t.rowCount,t.sourceCostMicros,l,i,a),Pt(Buffer.byteLength(o)+Buffer.byteLength(s)+Buffer.byteLength(l),r),{data:t.data,storedAt:new Date(i).toISOString(),expiresAt:new Date(a).toISOString(),rowCount:t.rowCount,sourceCostMicros:t.sourceCostMicros,taskIds:c}}const Wr=`0.2.37`,Gr=`SEO-Skill`,Kr=`${Gr}/${Wr} (+https://seoskill.dev)`,qr={name:`SEO Skill`,robotsToken:Gr,version:Wr,userAgent:Kr,documentationUrl:`https://seoskill.dev/docs/crawler/`};var Jr=class extends Error{maxBytes;constructor(e,t=`Response`){super(`${t} exceeds the ${e}-byte response limit.`),this.maxBytes=e,this.name=`ResponseSizeLimitError`}};function Yr(e){return e===`bot`?new ie({accept:`text/plain,*/*;q=0.8`,"accept-language":`en-US,en;q=0.9`,connection:`close`,"user-agent":Kr}):new ie({accept:`text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8`,"accept-language":`en-US,en;q=0.9`,connection:`close`,"sec-ch-ua-mobile":`?0`,"sec-ch-ua-platform":`"macOS"`,"upgrade-insecure-requests":`1`,"user-agent":Kr})}function Xr(e,t={}){let{profile:n=`browser`,headers:r,...i}=t,a=Yr(n);if(r instanceof ie)for(let[e,t]of r.entries())a.set(e,t);else if(Array.isArray(r))for(let[e,t]of r)a.set(e,t);else if(r)for(let[e,t]of Object.entries(r))t!==void 0&&a.set(e,t);return ae(e,{...i,headers:a})}async function Zr(e,t,n=`Response`){let r=Number(e.headers.get(`content-length`));if(Number.isFinite(r)&&r>t)throw await e.body?.cancel().catch(()=>void 0),new Jr(t,n);let i=e.body?.getReader();if(!i)return``;let a=[],o=0;try{for(;;){let{done:e,value:r}=await i.read();if(e)break;if(o+=r.byteLength,o>t)throw await i.cancel().catch(()=>void 0),new Jr(t,n);a.push(r)}}finally{i.releaseLock()}return Buffer.concat(a,o).toString(`utf8`)}function Qr(e){let t=e.issues.slice(0,3).map(e=>`${e.path.flatMap(e=>typeof e==`number`&&Number.isSafeInteger(e)?[`[${e}]`]:typeof e==`string`&&e.length<=64&&/^[a-zA-Z0-9_-]+$/u.test(e)?[e]:[]).reduce((e,t)=>t.startsWith(`[`)?`${e}${t}`:e?`${e}.${t}`:t,``)||`<root>`} (${e.code})`);return[...new Set(t)].join(`, `)}function $r(e,t){return t===401||t===403?new N({provider:e.provider,operation:e.operation,code:`authentication`,message:`${e.provider} rejected the configured credentials.`,status:t}):t===429?new N({provider:e.provider,operation:e.operation,code:`rate-limit`,message:`${e.provider} rate limited the request.`,status:t,retryable:!0}):new N({provider:e.provider,operation:e.operation,code:`remote-error`,message:`${e.provider} returned HTTP ${t}.`,status:t,retryable:t>=500})}function ei(e,t){return t instanceof N?t:t instanceof Jr?new N({provider:e.provider,operation:e.operation,code:`response-too-large`,message:t.message,cause:t}):t instanceof DOMException&&(t.name===`AbortError`||t.name===`TimeoutError`)?new N({provider:e.provider,operation:e.operation,code:`timeout`,message:`${e.provider} did not respond within ${e.timeoutMs}ms.`,retryable:!0,cause:t}):new N({provider:e.provider,operation:e.operation,code:`remote-error`,message:`${e.provider} request failed before a valid response arrived.`,retryable:!0,cause:t})}async function ti(e){try{let t=await e.fetch(e.url,{...e.init,signal:e.init?.signal??AbortSignal.timeout(e.timeoutMs)});if(!t.ok)throw await t.body?.cancel().catch(()=>void 0),$r(e,t.status);return e.onResponse?.(t),await Zr(t,e.maxResponseBytes,`${e.provider} response`)}catch(t){throw ei(e,t)}}async function ni(e){let t=e.retry===`safe`?2:1,n;for(let r=1;r<=t;r+=1)try{return await ti(e)}catch(i){if(n=ei(e,i),!n.retryable||r===t)throw n;await oe(e.retryDelayMs??250)}throw n}async function ri(e){let t=await ni(e),n;try{n=JSON.parse(t)}catch(t){throw new N({provider:e.provider,operation:e.operation,code:`invalid-response`,message:`${e.provider} returned malformed JSON.`,cause:t})}let r=e.schema.safeParse(n);if(!r.success){let t=Qr(r.error);throw new N({provider:e.provider,operation:e.operation,code:`invalid-response`,message:`${e.provider} returned data that does not match the expected response schema${t?` at ${t}`:``}.`,cause:r.error})}return r.data}function ii(e){let t=e??[],n=e=>{let n=t.filter(t=>t.cost_type===e);return n.length?n.reduce((e,t)=>e+Math.round(t.cost*1e6),0):t.length?0:null};return{perRequestMicros:n(`per_request`),perResultMicros:n(`per_result`)}}function ai(e){return{aiPromptObservationPrice:ii(e.price?.ai_optimization?.llm_responses?.live?.priority_normal),aiMentionPrices:{targetMetrics:ii(e.price?.ai_optimization?.llm_mentions?.target_metrics?.live?.priority_normal),multiTargetMetrics:ii(e.price?.ai_optimization?.llm_mentions?.multi_target_metrics?.live?.priority_normal),searchMentions:ii(e.price?.ai_optimization?.llm_mentions?.search_mentions?.live?.priority_normal)},keywordOverviewPrice:ii(e.price?.dataforseo_labs?.keyword_overview?.live?.priority_normal),keywordDiscoveryPrices:{ideas:ii(e.price?.dataforseo_labs?.keyword_ideas?.live?.priority_normal),related:ii(e.price?.dataforseo_labs?.related_keywords?.live?.priority_normal),suggestions:ii(e.price?.dataforseo_labs?.keyword_suggestions?.live?.priority_normal)},domainResearchPrices:{domainOverview:ii(e.price?.dataforseo_labs?.domain_rank_overview?.live?.priority_normal),rankedKeywords:ii(e.price?.dataforseo_labs?.ranked_keywords?.live?.priority_normal),rankingPages:ii(e.price?.dataforseo_labs?.relevant_pages?.live?.priority_normal),serpCompetitors:ii(e.price?.dataforseo_labs?.serp_competitors?.live?.priority_normal)},linkPrices:{summary:ii(e.price?.backlinks?.summary?.live?.priority_normal),backlinks:ii(e.price?.backlinks?.backlinks?.live?.priority_normal),referringDomains:ii(e.price?.backlinks?.referring_domains?.live?.priority_normal)},serpLiveAdvancedPrice:ii(e.price?.serp?.live?.advanced?.priority_normal),serpTaskPostPrice:ii(e.price?.serp?.task_post?.priority_normal)}}const oi=D.object({total:D.number().finite().nonnegative().optional(),balance:D.number().finite().optional(),limits:D.object({day:D.object({total:D.number().finite().nonnegative().optional()}).passthrough().optional()}).passthrough().optional(),statistics:D.object({day:D.object({total:D.number().finite().nonnegative().optional(),value:D.string().max(100).optional()}).passthrough().optional()}).passthrough().optional()}).passthrough(),si=D.object({cost_type:D.string().min(1).max(100),cost:D.number().finite().nonnegative()}).passthrough(),ci=D.object({priority_normal:D.array(si).max(20).optional()}).passthrough(),li=D.object({live:ci.optional()}).passthrough(),ui=D.object({ai_optimization:D.object({llm_responses:D.object({live:ci.optional()}).passthrough().optional(),llm_mentions:D.object({target_metrics:li.optional(),multi_target_metrics:li.optional(),search_mentions:li.optional()}).passthrough().optional()}).passthrough().optional(),backlinks:D.object({summary:li.optional(),backlinks:li.optional(),referring_domains:li.optional()}).passthrough().optional(),dataforseo_labs:D.object({keyword_overview:li.optional(),keyword_ideas:li.optional(),keyword_suggestions:li.optional(),related_keywords:li.optional(),domain_rank_overview:li.optional(),ranked_keywords:li.optional(),relevant_pages:li.optional(),serp_competitors:li.optional()}).passthrough().optional(),serp:D.object({task_post:ci.optional(),live:D.object({advanced:ci.optional()}).passthrough().optional()}).passthrough().optional()}).passthrough(),di=D.object({login:D.string().min(1).max(320),timezone:D.string().min(1).max(100).optional(),money:oi.optional(),price:ui.optional(),backlinks_subscription_expiry_date:D.string().nullable().optional(),llm_mentions_subscription_expiry_date:D.string().nullable().optional()}).passthrough(),fi=D.object({id:D.string().min(1).max(100).optional(),status_code:D.number().int(),status_message:D.string().max(1e3),cost:D.number().finite().nonnegative().optional(),result_count:D.number().int().nonnegative().optional(),result:D.array(di).max(10).nullable()}).passthrough(),pi=D.object({version:D.string().min(1).max(100).optional(),status_code:D.number().int(),status_message:D.string().max(1e3),cost:D.number().finite().nonnegative().optional(),tasks_count:D.number().int().nonnegative(),tasks_error:D.number().int().nonnegative(),tasks:D.array(fi).max(10)}).passthrough(),mi=D.object({key:D.union([D.string().max(500),D.number().int()]),mentions:D.number().int().nonnegative().optional(),ai_search_volume:D.number().int().nonnegative().optional()}).passthrough(),hi=D.object({key:D.string().min(1).max(250).optional(),sources_domain:D.array(mi).max(100).nullish(),total:D.object({mentions:D.number().int().nonnegative().optional(),ai_search_volume:D.number().int().nonnegative().optional()}).passthrough().nullish()}).passthrough(),gi=D.object({total_count:D.number().int().nonnegative().optional(),offset:D.number().int().nonnegative().optional(),items_count:D.number().int().nonnegative().optional(),aggregated_metrics:hi.nullish(),items:D.array(hi).max(10).nullish()}).passthrough(),_i=D.object({rank:D.number().int().positive().optional(),domain:D.string().max(500).nullish(),url:D.string().max(8192).nullish(),title:D.string().max(1e4).nullish(),source_name:D.string().max(1e3).nullish()}).passthrough(),vi=D.object({platform:D.string().max(100).optional(),model_name:D.string().max(250).nullish(),location_code:D.number().int().positive().optional(),language_code:D.string().max(35).optional(),question:D.string().max(1e4).nullish(),answer:D.string().max(1e6).nullish(),sources:D.array(_i).max(250).nullish(),ai_search_volume:D.number().int().nonnegative().nullish(),first_response_at:D.string().max(100).nullish(),last_response_at:D.string().max(100).nullish(),is_web_search_based:D.boolean().nullish()}).passthrough(),yi=D.object({total_count:D.number().int().nonnegative().optional(),offset:D.number().int().nonnegative().optional(),items_count:D.number().int().nonnegative().optional(),search_after_token:D.string().max(1e5).nullish(),items:D.array(vi).max(1e3).nullish()}).passthrough();function bi(e){return D.object({version:D.string().max(100).optional(),status_code:D.number().int(),status_message:D.string().max(1e3),cost:D.number().finite().nonnegative().optional(),tasks_count:D.number().int().nonnegative(),tasks_error:D.number().int().nonnegative(),tasks:D.array(D.object({id:D.string().max(100).optional(),status_code:D.number().int(),status_message:D.string().max(1e3),cost:D.number().finite().nonnegative().optional(),result_count:D.number().int().nonnegative().optional(),result:D.array(e).max(10).nullable()}).passthrough()).max(10)}).passthrough()}const xi=bi(gi),Si=bi(yi),Ci=125e3,wi={targetMetrics:`v3/ai_optimization/llm_mentions/target_metrics/live`,multiTargetMetrics:`v3/ai_optimization/llm_mentions/multi_target_metrics/live`,searchMentions:`v3/ai_optimization/llm_mentions/search_mentions/live`};function Ti(e){if(!/^[a-z]{2}$/.test(e.languageCode))throw new N({provider:`dataforseo`,operation:e.operation,code:`configuration`,message:`AI mention research needs a two-letter language code.`});let t=e.locationName?.trim();if(e.locationCode!==void 0==!!t)throw new N({provider:`dataforseo`,operation:e.operation,code:`configuration`,message:`AI mention research needs exactly one location code or canonical location name.`});if(e.locationCode!==void 0&&(!Number.isSafeInteger(e.locationCode)||e.locationCode<=0))throw new N({provider:`dataforseo`,operation:e.operation,code:`configuration`,message:`AI mention location code must be a positive integer.`});return e.locationCode===void 0?{location_name:t}:{location_code:e.locationCode}}function Ei(e){return e.aliases.map(e=>({keyword:e,match_type:`word_match`,search_scope:[`answer`],search_filter:`include`}))}function Di(e,t){if(!e.key.trim()||e.key.length>250||!e.label.trim()||e.label.length>250||e.aliases.length<1||e.aliases.length>5||e.aliases.some(e=>!e.trim()||e.length>250))throw new N({provider:`dataforseo`,operation:t,code:`configuration`,message:`Each AI mention target needs a key, label, and 1 to 5 aliases of at most 250 characters.`})}function Oi(e){return e.tasks.reduce((e,t)=>e+(t.result??[]).reduce((e,t)=>e+(t.items?.length??+!!t.aggregated_metrics),0),0)}function ki(e){return e.tasks.reduce((e,t)=>e+(t.result??[]).reduce((e,t)=>e+(t.items?.length??0),0),0)}function Ai(e,t){let n=[e.target,...e.competitors];if(n.length<1||n.length>6)throw new N({provider:`dataforseo`,operation:`ai-mention-metrics`,code:`configuration`,message:`AI mention metrics supports 1 to 6 targets.`});for(let e of n)Di(e,`ai-mention-metrics`);if(new Set(n.map(e=>e.key)).size!==n.length)throw new N({provider:`dataforseo`,operation:`ai-mention-metrics`,code:`configuration`,message:`AI mention target keys must be unique.`});let r=Ti({...e,operation:`ai-mention-metrics`}),i=n.length>1;return{operation:`ai-mention-metrics`,capability:`ai-mentions`,endpoint:i?wi.multiTargetMetrics:wi.targetMetrics,request:{language_code:e.languageCode,...r,platform:e.platform,...i?{targets:n.map(e=>({key:e.key,target:Ei(e)})),order_by:[`total.mentions,desc`],limit:n.length}:{target:Ei(e.target)},internal_list_limit:10},schema:xi,requestedRows:n.length,price:e=>i?e.aiMentionPrices.multiTargetMetrics:e.aiMentionPrices.targetMetrics,context:e.context,ttlMs:t,timeoutMs:Ci,refresh:e.refresh,rowCount:Oi}}function ji(e,t){if(Di(e.target,`ai-mention-samples`),!Number.isSafeInteger(e.limit)||e.limit<1||e.limit>25)throw new N({provider:`dataforseo`,operation:`ai-mention-samples`,code:`configuration`,message:`AI mention sample limit must be from 1 to 25.`});let n=Ti({...e,operation:`ai-mention-samples`});return{operation:`ai-mention-samples`,capability:`ai-mentions`,endpoint:wi.searchMentions,request:{language_code:e.languageCode,...n,platform:e.platform,target:Ei(e.target),order_by:[`ai_search_volume,desc`],offset:0,limit:e.limit},schema:Si,requestedRows:e.limit,price:e=>e.aiMentionPrices.searchMentions,context:e.context,ttlMs:t,timeoutMs:Ci,refresh:e.refresh,rowCount:ki}}const Mi=D.object({id:D.string().min(1).max(100).optional(),status_code:D.number().int(),status_message:D.string().max(1e3).optional(),cost:D.number().finite().nonnegative().optional()}).passthrough(),Ni=D.object({model_name:D.string().min(1).max(200),reasoning:D.boolean().optional(),web_search_supported:D.boolean().optional(),task_post_supported:D.boolean().optional()}).passthrough(),Pi=D.object({status_code:D.number().int(),status_message:D.string().max(1e3).optional(),cost:D.number().finite().nonnegative().optional(),tasks_error:D.number().int().nonnegative(),tasks:D.array(Mi.extend({result:D.array(Ni).max(500).nullable().optional()})).max(10)}).passthrough(),Fi=D.object({title:D.string().max(4e3).nullable().optional(),url:D.string().max(16384).nullable().optional()}).passthrough(),Ii=D.object({type:D.string().max(100).nullable().optional(),text:D.string().max(1e5).nullable().optional(),annotations:D.array(Fi).max(100).nullable().optional()}).passthrough(),Li=D.object({type:D.string().max(100).nullable().optional(),sections:D.array(Ii).max(100).nullable().optional()}).passthrough(),Ri=D.object({model_name:D.string().min(1).max(200),input_tokens:D.number().int().nonnegative().nullable().optional(),output_tokens:D.number().int().nonnegative().nullable().optional(),reasoning_tokens:D.number().int().nonnegative().nullable().optional(),web_search:D.boolean().nullable().optional(),money_spent:D.number().finite().nonnegative().nullable().optional(),datetime:D.string().max(100).nullable().optional(),items:D.array(Li).max(100).nullable().optional(),fan_out_queries:D.array(D.string().min(1).max(1e3)).max(100).nullable().optional()}).passthrough(),zi=D.object({status_code:D.number().int(),status_message:D.string().max(1e3).optional(),cost:D.number().finite().nonnegative().optional(),tasks_error:D.number().int().nonnegative(),tasks:D.array(Mi.extend({result:D.array(Ri).max(10).nullable().optional()})).max(10)}).passthrough();function Bi(e){return e===void 0?null:Math.round(e*1e6)}function Vi(e){return e>=40100&&e<40200?`authentication`:e===40202?`rate-limit`:`remote-error`}function Hi(e){return e.cost===void 0?e.tasks.some(e=>e.cost===void 0)?null:e.tasks.reduce((e,t)=>e+(Bi(t.cost)??0),0):Bi(e.cost)}const Ui={chatgpt:`chat_gpt`,claude:`claude`,gemini:`gemini`,perplexity:`perplexity`};function Wi(e){return`v3/ai_optimization/${Ui[e]}/llm_responses/models`}function Gi(e){return`v3/ai_optimization/${Ui[e]}/llm_responses/live`}async function Ki(e){let t=await ri({provider:`dataforseo`,operation:`ai-prompt-models`,url:new URL(Wi(e.surface),e.baseUrl),fetch:e.fetch,maxResponseBytes:e.maxResponseBytes,timeoutMs:e.timeoutMs,retry:`safe`,schema:Pi,init:{method:`GET`,headers:{authorization:e.authorization}}}),n=t.tasks.find(e=>e.status_code!==2e4);if(t.status_code!==2e4||t.tasks_error>0||n){let r=n?.status_code??t.status_code;throw new N({provider:`dataforseo`,operation:`ai-prompt-models`,code:Vi(r),message:`DataForSEO could not return the ${e.surface} model catalog (${r}).`,retryable:r===40202})}return t}function qi(e){if(!e.prompt.trim()||e.prompt.length>500)throw new N({provider:`dataforseo`,operation:`ai-prompt-observation`,code:`configuration`,message:`Each AI prompt must contain 1 to 500 characters.`});if(!e.model.trim()||e.model.length>200)throw new N({provider:`dataforseo`,operation:`ai-prompt-observation`,code:`configuration`,message:`Choose a model name returned by the provider model catalog.`});if(!/^[A-Z]{2}$/u.test(e.countryCode))throw new N({provider:`dataforseo`,operation:`ai-prompt-observation`,code:`configuration`,message:`AI prompt observations need a two-letter country code.`});if(!Number.isSafeInteger(e.maxOutputTokens)||e.maxOutputTokens<1||e.maxOutputTokens>4096)throw new N({provider:`dataforseo`,operation:`ai-prompt-observation`,code:`configuration`,message:`AI prompt output tokens must be from 1 to 4096.`});if(e.surface===`perplexity`&&!e.webSearch)throw new N({provider:`dataforseo`,operation:`ai-prompt-observation`,code:`configuration`,message:`Perplexity prompt observations require web search because its supported models use web search by default.`})}function Ji(e,t){qi(e);let n=e.surface!==`gemini`,r=e.surface!==`perplexity`;return{operation:`ai-prompt-observation-${e.surface}`,capability:`ai-prompt-observation`,endpoint:Gi(e.surface),request:{user_prompt:e.prompt.trim().replace(/\s+/gu,` `),model_name:e.model.trim(),max_output_tokens:e.maxOutputTokens,...r?{web_search:e.webSearch}:{},...n&&e.webSearch?{web_search_country_iso_code:e.countryCode}:{}},schema:zi,requestedRows:1,price:e=>e.aiPromptObservationPrice,context:e.context,ttlMs:t,timeoutMs:125e3,refresh:e.refresh,rowCount:e=>e.tasks.reduce((e,t)=>e+(t.result?.length??0),0)}}function Yi(e){let t=e.models.find(t=>t.name===e.request.model.trim());if(!t)throw new N({provider:`dataforseo`,operation:`ai-prompt-observation`,code:`configuration`,message:`Model ${e.request.model} is not in the current ${e.request.surface} model catalog.`});if(e.request.webSearch&&!t.webSearchSupported)throw new N({provider:`dataforseo`,operation:`ai-prompt-observation`,code:`configuration`,message:`Model ${t.name} does not support web search.`});let n=e.request.surface===`claude`&&t.reasoning?1025:t.reasoning?1024:1;if(e.request.maxOutputTokens<n)throw new N({provider:`dataforseo`,operation:`ai-prompt-observation`,code:`configuration`,message:`Model ${t.name} needs at least ${n} output tokens.`})}const Xi={ahrefsApiKey:`ahrefs-api-key`,bingApiKey:`bing-api-key`,clickySiteKeys:`clicky-sitekeys`,dataForSeoCredentials:`dataforseo-credentials`,indexNowKeys:`indexnow-keys`,providerExtensionCredentials:`provider-extension-credentials`,semrushApiKey:`semrush-api-key`,serpBaseApiKey:`serpbase-api-key`},Zi=Object.freeze(Object.values(Xi)),Qi=D.object({version:D.literal(1),secrets:D.record(D.string(),D.string())}).strict();function $i(e){return`provider:${e}`}function ea(e){try{o(e,384)}catch(e){if(e.code!==`ENOENT`)throw e}}function ta(){let e=ot().providerSecretsFile;ea(e);let t=Hn(e);if(t===void 0&&!c(e))return{version:1,secrets:{}};let n=Qi.safeParse(t);if(!n.success)throw Error(`Saved provider credentials are invalid. Remove the local provider secrets file, then reconnect the affected providers.`);return n.data}function na(e){let t=ot().providerSecretsFile;if(Object.keys(e.secrets).length===0){Gn(t);return}Un(t,e,384)}function ra(e){let t=ta();e in t.secrets&&(delete t.secrets[e],na(t))}async function ia(e){let t=e.env??process.env,n=e.envVar?t[e.envVar]?.trim():void 0;if(n)return{value:n,source:`environment`};if(Qn().security.useKeychain)try{let t=await Jn(`seo`,$i(e.name));if(t)return{value:t,source:`keychain`}}catch{}let r=ta().secrets[e.name];return r?{value:r,source:`file`}:void 0}async function aa(e,t){let n=t.trim();if(!n)throw Error(`Provider secret cannot be empty.`);if(Qn().security.useKeychain)try{return await Yn(`seo`,$i(e),n),ra(e),`keychain`}catch{}let r=ta();return r.secrets[e]=n,na(r),`file`}async function oa(e){let t=await ia({name:e}),n;try{await Xn(`seo`,$i(e))}catch(e){n=e}if(n&&t?.source===`keychain`)throw Error(`The saved provider credential could not be removed from the system keychain. Unlock the keychain and try again.`,{cause:n});ra(e)}async function sa(){let e=await Promise.allSettled(Zi.map(e=>oa(e))),t=e.flatMap((e,t)=>e.status===`rejected`?[Zi[t]]:[]);if(t.length>0)throw Error(`Reset could not remove ${t.length} saved provider credential record${t.length===1?``:`s`}. Unlock the keychain or repair the local provider secrets file, then run \`seo reset --yes\` again.`,{cause:AggregateError(e.flatMap(e=>e.status===`rejected`?[e.reason]:[]))})}const ca=`SEO_DATAFORSEO_LOGIN`,la=`SEO_DATAFORSEO_PASSWORD`,ua=Xi.dataForSeoCredentials,da=D.object({version:D.literal(1),login:D.string().trim().min(1).max(320),password:D.string().min(1).max(2048)}).strict();function fa(e){return new N({provider:`dataforseo`,operation:`credentials`,code:`configuration`,message:e})}function pa(e){let t=da.safeParse({version:1,login:e.login,password:e.password});if(!t.success)throw fa(`DataForSEO credentials need both an API login and API password.`);return{login:t.data.login,password:t.data.password}}function ma(){let e=Qn();return!e.providers.dataForSeoLogin&&!e.providers.dataForSeoPassword?!1:($n({...e,providers:{...e.providers,dataForSeoLogin:void 0,dataForSeoPassword:void 0}}),!0)}function ha(e){let t=e[ca]?.trim(),n=e[la];if(!(!t&&!n)){if(!t||!n)throw fa(`Set both ${ca} and ${la}.`);return pa({login:t,password:n})}}function ga(e){let t;try{t=JSON.parse(e)}catch{throw fa("Saved DataForSEO credentials are invalid. Run `seo providers dataforseo disconnect`, then connect again.")}let n=da.safeParse(t);if(!n.success)throw fa("Saved DataForSEO credentials are invalid. Run `seo providers dataforseo disconnect`, then connect again.");return{login:n.data.login,password:n.data.password}}async function _a(e){let t=pa(e),n=await aa(ua,JSON.stringify({version:1,...t}));return ma(),n}async function va(){let e=Qn(),t=e.providers.dataForSeoLogin,n=e.providers.dataForSeoPassword;if(!t&&!n)return;if(!t||!n)throw fa("Legacy DataForSEO config contains only part of the credential pair. Run `seo providers dataforseo disconnect`, then connect again.");let r=pa({login:t,password:n}),i=await _a(r);return{...r,source:i,migrated:!0}}async function ya(e={}){let t=ha(e.env??process.env);if(t)return{...t,source:`environment`,migrated:!1};let n=await ia({name:ua});if(n){let e=ga(n.value),t=ma();return{...e,source:n.source,migrated:t}}return va()}async function ba(){await oa(ua),ma()}function xa(e){return e.tasks.reduce((e,t)=>e+(t.result??[]).reduce((e,t)=>e+(t.items?.length??0),0),0)}function Sa(e){let t=e.tasks.flatMap(e=>(e.result??[]).flatMap(e=>typeof e.total_count==`number`?[e.total_count]:[]));return t.length?t.reduce((e,t)=>e+t,0):null}function Ca(e){let t=e.tasks.flatMap(e=>(e.result??[]).flatMap(e=>e.offset_token?[e.offset_token]:[]));return t.length===1?t[0]??null:null}const wa=D.number().finite().nullable().optional(),Ta=D.union([D.number().finite(),D.string().trim().min(1)]).nullable().optional(),Ea=D.object({year:D.number().int(),month:D.number().int(),search_volume:D.number().finite().nullable().optional()}).passthrough(),Da=D.object({keyword:D.string().trim().min(1),keyword_info:D.object({search_volume:wa,cpc:wa,competition:wa,monthly_searches:D.array(Ea).max(120).nullish(),last_updated_time:D.string().trim().min(1).nullable().optional()}).nullish(),keyword_properties:D.object({keyword_difficulty:wa}).nullish(),serp_info:D.object({se_results_count:Ta}).nullish(),search_intent_info:D.object({main_intent:D.enum([`informational`,`navigational`,`commercial`,`transactional`]).nullable().optional()}).nullish()}).passthrough(),Oa=D.object({items_count:D.number().int().nonnegative().optional(),items:D.array(Da).nullable().optional()}).passthrough(),ka=D.object({id:D.string().trim().min(1).optional(),status_code:D.number().int(),status_message:D.string(),cost:D.number().finite().nonnegative().optional(),result_count:D.number().int().nonnegative().optional(),result:D.array(Oa).nullable().optional()}).passthrough(),Aa=D.object({status_code:D.number().int(),status_message:D.string(),cost:D.number().finite().nonnegative().optional(),tasks_count:D.number().int().nonnegative(),tasks_error:D.number().int().nonnegative(),tasks:D.array(ka)}).passthrough();function ja(e){if(e==null||e===``)return;let t=Number(e);return Number.isFinite(t)?t:void 0}const Ma=Da,Na=D.object({keyword_data:Da,depth:D.number().int().nonnegative().optional(),related_keywords:D.array(D.string().trim().min(1)).max(100).optional()}).passthrough(),Pa=D.union([Ma,Na]),Fa=D.object({seed_keyword:D.string().trim().min(1).optional(),seed_keywords:D.array(D.string().trim().min(1)).max(20).optional(),total_count:D.number().int().nonnegative().nullable().optional(),items_count:D.number().int().nonnegative().optional(),offset_token:D.string().max(2e4).nullable().optional(),items:D.array(Pa).max(500).nullable().optional()}).passthrough(),Ia=D.object({id:D.string().trim().min(1).optional(),status_code:D.number().int(),status_message:D.string().max(1e3),cost:D.number().finite().nonnegative().optional(),result_count:D.number().int().nonnegative().optional(),result:D.array(Fa).max(10).nullable().optional()}).passthrough(),La=D.object({status_code:D.number().int(),status_message:D.string().max(1e3),cost:D.number().finite().nonnegative().optional(),tasks_count:D.number().int().nonnegative(),tasks_error:D.number().int().nonnegative(),tasks:D.array(Ia).max(10)}).passthrough(),Ra=D.number().finite().nullable().optional(),za=D.number().finite().nonnegative().nullable().optional(),Ba=D.number().int().nonnegative().nullable().optional(),Va=D.object({pos_1:Ba,pos_2_3:Ba,pos_4_10:Ba,pos_11_20:Ba,pos_21_30:Ba,pos_31_40:Ba,pos_41_50:Ba,pos_51_60:Ba,pos_61_70:Ba,pos_71_80:Ba,pos_81_90:Ba,pos_91_100:Ba,etv:za,count:Ba,estimated_paid_traffic_cost:za,is_new:Ba,is_up:Ba,is_down:Ba,is_lost:Ba}).passthrough(),Ha=D.object({organic:Va.nullable().optional()}).passthrough(),Ua=D.object({status_code:D.number().int(),status_message:D.string().max(1e3),cost:D.number().finite().nonnegative().optional(),tasks_count:D.number().int().nonnegative(),tasks_error:D.number().int().nonnegative()}).passthrough(),Wa={id:D.string().trim().min(1).max(100).optional(),status_code:D.number().int(),status_message:D.string().max(1e3),cost:D.number().finite().nonnegative().optional(),result_count:D.number().int().nonnegative().optional()},Ga=D.object({location_code:D.number().int().positive().nullable().optional(),language_code:D.string().trim().min(1).max(35).nullable().optional(),metrics:Ha.nullable().optional()}).passthrough(),Ka=D.object({target:D.string().trim().min(1).max(2048).optional(),location_code:D.number().int().positive().nullable().optional(),language_code:D.string().trim().min(1).max(35).nullable().optional(),total_count:D.number().int().nonnegative().nullable().optional(),items_count:D.number().int().nonnegative().optional(),items:D.array(Ga).max(1e3).nullable().optional()}).passthrough(),qa=Ua.extend({tasks:D.array(D.object({...Wa,result:D.array(Ka).max(10).nullable().optional()}).passthrough()).max(10)}),Ja=D.object({type:D.string().trim().min(1).max(100).optional(),rank_group:D.number().int().positive().nullable().optional(),rank_absolute:D.number().int().positive().nullable().optional(),url:D.string().trim().min(1).max(2e4).nullable().optional(),etv:za}).passthrough(),Ya=D.object({keyword_data:Da.nullable().optional(),ranked_serp_element:D.object({serp_item:Ja.nullable().optional()}).passthrough().nullable().optional()}).passthrough(),_ee=D.object({target:D.string().trim().min(1).max(2048).optional(),total_count:D.number().int().nonnegative().nullable().optional(),items_count:D.number().int().nonnegative().optional(),items:D.array(Ya).max(1e3).nullable().optional()}).passthrough(),vee=Ua.extend({tasks:D.array(D.object({...Wa,result:D.array(_ee).max(10).nullable().optional()}).passthrough()).max(10)}),yee=D.object({page_address:D.string().trim().min(1).max(2e4).nullable().optional(),metrics:Ha.nullable().optional()}).passthrough(),bee=D.object({target:D.string().trim().min(1).max(2048).optional(),total_count:D.number().int().nonnegative().nullable().optional(),items_count:D.number().int().nonnegative().optional(),items:D.array(yee).max(1e3).nullable().optional()}).passthrough(),xee=Ua.extend({tasks:D.array(D.object({...Wa,result:D.array(bee).max(10).nullable().optional()}).passthrough()).max(10)}),See=D.object({domain:D.string().trim().min(1).max(2048).nullable().optional(),avg_position:Ra,median_position:Ra,etv:za,keywords_count:Ba,visibility:za,relevant_serp_items:Ba,keywords_positions:D.record(D.string().trim().min(1).max(80),D.array(D.number().int().positive()).max(100)).nullable().optional()}).passthrough(),Cee=D.object({seed_keywords:D.array(D.string().trim().min(1).max(80)).max(200).optional(),total_count:D.number().int().nonnegative().nullable().optional(),items_count:D.number().int().nonnegative().optional(),items:D.array(See).max(1e3).nullable().optional()}).passthrough(),wee=Ua.extend({tasks:D.array(D.object({...Wa,result:D.array(Cee).max(10).nullable().optional()}).passthrough()).max(10)}),Xa=1e3,Za=1e5,Qa=new Set([`organic`,`paid`,`featured_snippet`,`local_pack`,`ai_overview_reference`]);function $a(e,t){if(!/^[a-z]{2}$/.test(e.languageCode))throw new N({provider:`dataforseo`,operation:t,code:`configuration`,message:`DataForSEO language code must contain two lowercase letters.`});let n=e.locationName?.trim();if(e.locationCode!==void 0==!!n)throw new N({provider:`dataforseo`,operation:t,code:`configuration`,message:`DataForSEO domain research requires exactly one location code or location name.`});if(e.locationCode!==void 0&&(!Number.isSafeInteger(e.locationCode)||e.locationCode<=0))throw new N({provider:`dataforseo`,operation:t,code:`configuration`,message:`DataForSEO location code must be a positive integer.`});if(n&&n.length>500)throw new N({provider:`dataforseo`,operation:t,code:`configuration`,message:`DataForSEO location name must be at most 500 characters.`});if(e.limit!==void 0&&(!Number.isSafeInteger(e.limit)||e.limit<1||e.limit>Xa))throw new N({provider:`dataforseo`,operation:t,code:`configuration`,message:`DataForSEO ${t} limit must be from 1 to ${Xa}.`});if(e.offset!==void 0&&(!Number.isSafeInteger(e.offset)||e.offset<0||e.offset>Za))throw new N({provider:`dataforseo`,operation:t,code:`configuration`,message:`DataForSEO ${t} offset must be from 0 to ${Za}.`});return{locationName:n}}function eo(e,t,n){let r=n?Qa:new Set([...Qa].filter(e=>e!==`ai_overview_reference`));if(e.length<1||e.length>r.size||new Set(e).size!==e.length||e.some(e=>!r.has(e)))throw new N({provider:`dataforseo`,operation:t,code:`configuration`,message:`DataForSEO ${t} received unsupported result types.`})}function to(e){return e.locationCode===void 0?{location_name:e.locationName}:{location_code:e.locationCode}}function no(e){return e.tasks.reduce((e,t)=>e+(t.result??[]).reduce((e,t)=>e+(t.items?.length??0),0),0)}function Tee(e,t){let{locationName:n}=$a(e,`domain-overview`);return{operation:`domain-overview`,capability:`domain-overview`,endpoint:`v3/dataforseo_labs/google/domain_rank_overview/live`,request:{target:e.target,language_code:e.languageCode,...to({locationCode:e.locationCode,locationName:n})},schema:qa,requestedRows:1,price:e=>e.domainResearchPrices.domainOverview,context:e.context,ttlMs:t,refresh:e.refresh,rowCount:no}}function Eee(e,t){let{locationName:n}=$a(e,`ranked-keywords`);return eo(e.resultTypes,`ranked-keywords`,!0),{operation:`ranked-keywords`,capability:`ranked-keywords`,endpoint:`v3/dataforseo_labs/google/ranked_keywords/live`,request:{target:e.target,language_code:e.languageCode,...to({locationCode:e.locationCode,locationName:n}),include_subdomains:e.includeSubdomains,item_types:e.resultTypes,limit:e.limit,offset:e.offset??0,order_by:e.orderBy,...e.filters?.length?{filters:e.filters}:{}},schema:vee,requestedRows:e.limit,price:e=>e.domainResearchPrices.rankedKeywords,context:e.context,ttlMs:t,refresh:e.refresh,rowCount:no}}function Dee(e,t){let{locationName:n}=$a(e,`ranking-pages`);return{operation:`ranking-pages`,capability:`relevant-pages`,endpoint:`v3/dataforseo_labs/google/relevant_pages/live`,request:{target:e.target,language_code:e.languageCode,...to({locationCode:e.locationCode,locationName:n}),limit:e.limit,offset:e.offset??0,order_by:e.orderBy,...e.filters?.length?{filters:e.filters}:{}},schema:xee,requestedRows:e.limit,price:e=>e.domainResearchPrices.rankingPages,context:e.context,ttlMs:t,refresh:e.refresh,rowCount:no}}function Oee(e,t){let{locationName:n}=$a(e,`serp-competitors`);if(eo(e.resultTypes,`serp-competitors`,!1),e.keywords.length<1||e.keywords.length>200||e.keywords.some(e=>e.length<1||e.length>80||e.split(/\s+/u).length>10))throw new N({provider:`dataforseo`,operation:`serp-competitors`,code:`configuration`,message:`SERP competitors requires 1 to 200 keywords of at most 80 characters and 10 words.`});return{operation:`serp-competitors`,capability:`serp-competitors`,endpoint:`v3/dataforseo_labs/google/serp_competitors/live`,request:{keywords:e.keywords,language_code:e.languageCode,...to({locationCode:e.locationCode,locationName:n}),include_subdomains:e.includeSubdomains,item_types:e.resultTypes,limit:e.limit,offset:e.offset??0,order_by:e.orderBy},schema:wee,requestedRows:e.limit,price:e=>e.domainResearchPrices.serpCompetitors,context:e.context,ttlMs:t,refresh:e.refresh,rowCount:no}}const ro=D.number().int().nonnegative().nullable().optional(),io=D.number().finite().nonnegative().nullable().optional(),ao=D.string().max(2e4).nullable().optional(),oo=D.object({status_code:D.number().int(),status_message:D.string().max(1e3),cost:D.number().finite().nonnegative().optional(),tasks_count:D.number().int().nonnegative(),tasks_error:D.number().int().nonnegative()}).passthrough(),so={id:D.string().trim().min(1).max(100).optional(),status_code:D.number().int(),status_message:D.string().max(1e3),cost:D.number().finite().nonnegative().optional(),result_count:D.number().int().nonnegative().optional()},kee=D.object({target:D.string().trim().min(1).max(2048).optional(),rank:io,backlinks:ro,referring_pages:ro,referring_domains:ro,broken_backlinks:ro,broken_pages:ro,backlinks_spam_score:io,info:D.object({target_spam_score:io}).passthrough().nullable().optional()}).passthrough(),Aee=oo.extend({tasks:D.array(D.object({...so,result:D.array(kee).max(10).nullable().optional()}).passthrough()).max(10)}),jee=D.object({domain_from:ao,url_from:ao,url_to:ao,anchor:ao,item_type:D.string().max(100).nullable().optional(),dofollow:D.boolean().nullable().optional(),rank:io,domain_from_rank:io,page_from_rank:io,backlink_spam_score:io,backlinks_spam_score:io,first_seen:D.string().max(100).nullable().optional(),last_visited:D.string().max(100).nullable().optional(),lost_date:D.string().max(100).nullable().optional(),is_lost:D.boolean().nullable().optional(),is_indirect_link:D.boolean().nullable().optional(),links_count:ro,group_count:ro,rel_attributes:D.array(D.string().trim().min(1).max(100)).max(100).nullable().optional(),attributes:D.array(D.string().trim().min(1).max(100)).max(100).nullable().optional()}).passthrough(),Mee=D.object({target:D.string().trim().min(1).max(2048).optional(),total_count:ro,items_count:ro,search_after_token:D.string().max(2e4).nullable().optional(),items:D.array(jee).max(1e3).nullable().optional()}).passthrough(),Nee=oo.extend({tasks:D.array(D.object({...so,result:D.array(Mee).max(10).nullable().optional()}).passthrough()).max(10)}),Pee=D.object({domain:D.string().trim().min(1).max(2048).nullable().optional(),backlinks:ro,referring_pages:ro,broken_backlinks:ro,broken_pages:ro,rank:io,backlinks_spam_score:io,first_seen:D.string().max(100).nullable().optional()}).passthrough(),Fee=D.object({target:D.string().trim().min(1).max(2048).optional(),total_count:ro,items_count:ro,items:D.array(Pee).max(1e3).nullable().optional()}).passthrough(),Iee=oo.extend({tasks:D.array(D.object({...so,result:D.array(Fee).max(10).nullable().optional()}).passthrough()).max(10)}),co=1e3,lo=2e4,uo={summary:`v3/backlinks/summary/live`,backlinks:`v3/backlinks/backlinks/live`,referringDomains:`v3/backlinks/referring_domains/live`};function fo(e){return e.tasks.reduce((e,t)=>e+(t.result??[]).reduce((e,t)=>e+(t.items?.length??0),0),0)}function Lee(e){return e.tasks.reduce((e,t)=>e+(t.result?.length??0),0)}function po(e){return{target:e.target,include_subdomains:e.scope===`domain`&&e.includeSubdomains,include_indirect_links:!0,exclude_internal_backlinks:!0,backlinks_status_type:`live`,rank_scale:`one_hundred`}}function mo(e){if(!Number.isSafeInteger(e.limit)||e.limit<1||e.limit>1e3)throw new N({provider:`dataforseo`,operation:e.operation,code:`configuration`,message:`Link row limit must be from 1 to ${co}.`});if(!Number.isSafeInteger(e.offset)||e.offset<0||e.offset>2e4)throw new N({provider:`dataforseo`,operation:e.operation,code:`configuration`,message:`Link row offset must be from 0 to ${lo}.`})}function Ree(e,t){return{operation:`link-summary`,capability:`link-summary`,endpoint:uo.summary,request:po(e),schema:Aee,requestedRows:1,price:e=>e.linkPrices.summary,context:e.context,ttlMs:t,refresh:e.refresh,rowCount:Lee}}function zee(e,t){if(mo({limit:e.limit,offset:e.offset,operation:`backlinks`}),e.orderBy.length<1||e.orderBy.length>3)throw new N({provider:`dataforseo`,operation:`backlinks`,code:`configuration`,message:`Backlink rows need from 1 to 3 sort rules.`});return{operation:`backlinks`,capability:`backlinks`,endpoint:uo.backlinks,request:{...po(e),mode:e.mode,backlinks_status_type:e.status,limit:e.limit,offset:e.offset,order_by:e.orderBy},schema:Nee,requestedRows:e.limit,price:e=>e.linkPrices.backlinks,context:e.context,ttlMs:t,refresh:e.refresh,rowCount:fo}}function Bee(e,t){if(mo({limit:e.limit,offset:e.offset,operation:`referring-domains`}),e.orderBy.length<1||e.orderBy.length>3)throw new N({provider:`dataforseo`,operation:`referring-domains`,code:`configuration`,message:`Referring-domain rows need from 1 to 3 sort rules.`});return{operation:`referring-domains`,capability:`referring-domains`,endpoint:uo.referringDomains,request:{...po(e),limit:e.limit,offset:e.offset,order_by:e.orderBy},schema:Iee,requestedRows:e.limit,price:e=>e.linkPrices.referringDomains,context:e.context,ttlMs:t,refresh:e.refresh,rowCount:fo}}function ho(e){return e===void 0?null:Math.round(e*1e6)}function Vee(e){return[...new Set(e.tasks.flatMap(e=>e.id?[e.id]:[]))].sort((e,t)=>e<t?-1:+(e>t))}function Hee(e){return e.cost===void 0?e.tasks.some(e=>e.cost===void 0)?null:e.tasks.reduce((e,t)=>e+(ho(t.cost)??0),0):ho(e.cost)}function Uee(e){return e>=40100&&e<40200?`authentication`:e===40202?`rate-limit`:`remote-error`}async function Wee(e){let t={provider:`dataforseo`,credentialScope:e.credentialScope,operation:e.operation,request:e.request},n=e.refresh?null:Hr(t,e.schema,{database:e.database,now:e.now().getTime()});if(n)return{response:n.data,observedAt:n.storedAt,returnedRows:n.rowCount??e.rowCount(n.data),cache:{status:`hit`,storedAt:n.storedAt,expiresAt:n.expiresAt},cost:{currency:`USD`,estimatedMicros:0,actualMicros:0,taskIds:n.taskIds},spendNotice:null,warnings:[]};let r=await e.price();if(r.perRequestMicros===null||r.perResultMicros===null)throw new N({provider:`dataforseo`,operation:e.operation,code:`invalid-response`,message:`DataForSEO account pricing for ${e.operation} is unavailable, so the paid request was not started.`});let i=r.perRequestMicros*(e.estimatedRequestUnits??1)+r.perResultMicros*e.requestedRows,a=Pr({provider:`dataforseo`,capability:e.capability,endpoint:e.endpoint,projectId:e.context.projectId,reportId:e.context.reportId,reportRunId:e.context.reportRunId,requestedRows:e.requestedRows,estimatedCostMicros:i},{database:e.database,limits:e.spendLimits,now:e.now().getTime()}),o;try{o=await ri({provider:`dataforseo`,operation:e.operation,url:new URL(e.endpoint,e.baseUrl),fetch:e.fetch,maxResponseBytes:e.maxResponseBytes,timeoutMs:e.timeoutMs,retry:`never`,schema:e.schema,init:{method:`POST`,headers:{authorization:`Basic ${Buffer.from(`${e.credentials.login}:${e.credentials.password}`).toString(`base64`)}`,"content-type":`application/json`},body:JSON.stringify([e.request])}})}catch(t){throw Fr(a.id,{provider:`dataforseo`,state:`failed`,actualCostMicros:null,returnedRows:null,taskIds:[]},{database:e.database,limits:e.spendLimits,now:e.now().getTime()}),t}let s=Hee(o),c=e.rowCount(o),l=Vee(o),u=o.tasks.filter(e=>e.status_code!==2e4),d=o.status_code!==2e4||o.tasks_error>0||u.length>0,f=d?u.length<o.tasks.length?`partial`:`failed`:`succeeded`,p=Fr(a.id,{provider:`dataforseo`,state:f,actualCostMicros:s,returnedRows:c,taskIds:l},{database:e.database,limits:e.spendLimits,now:e.now().getTime()});if(d){let t=u[0],n=t?.status_code??o.status_code,r=t?.status_message?.trim()||o.status_message?.trim();throw new N({provider:`dataforseo`,operation:e.operation,code:Uee(n),message:`DataForSEO could not complete ${e.operation} (${n}${r?`: ${r}`:``}).`,retryable:n===40202})}let m=[];try{Ur(t,{data:o,ttlMs:e.ttlMs,rowCount:c,sourceCostMicros:s,taskIds:l},{database:e.database,now:e.now().getTime()})}catch{m.push({code:`cache-write-failed`,message:`The provider result is valid, but it could not be saved to the local cache.`})}return{response:o,observedAt:e.now().toISOString(),returnedRows:c,cache:{status:e.refresh?`bypass`:`miss`,storedAt:null,expiresAt:null},cost:{currency:`USD`,estimatedMicros:i,actualMicros:s,taskIds:l},spendNotice:p,warnings:m}}function go(e){let t=e.keyword.trim().replace(/\s+/gu,` `);if(t.length<1||t.length>80||t.split(/\s+/u).length>10)throw new N({provider:`dataforseo`,operation:`serp-snapshot`,code:`configuration`,message:`SERP snapshots require a keyword of at most 80 characters and 10 words.`});if(/(?:^|\s)(?:allinanchor|allintext|allintitle|allinurl|cache|define|definition|filetype|id|inanchor|info|intext|intitle|inurl|link|site):/iu.test(t))throw new N({provider:`dataforseo`,operation:`serp-snapshot`,code:`configuration`,message:`SERP snapshot keywords cannot contain search operators with multiplied provider pricing.`});if(!Number.isSafeInteger(e.depth)||e.depth<1||e.depth>100)throw new N({provider:`dataforseo`,operation:`serp-snapshot`,code:`configuration`,message:`SERP depth must be from 1 to 100.`});if(!/^[a-z]{2}$/.test(e.languageCode))throw new N({provider:`dataforseo`,operation:`serp-snapshot`,code:`configuration`,message:`DataForSEO language code must contain two lowercase letters.`});let n=e.locationName?.trim();if(e.locationCode!==void 0==!!n)throw new N({provider:`dataforseo`,operation:`serp-snapshot`,code:`configuration`,message:`DataForSEO SERP snapshots require exactly one location code or location name.`});return{keyword:t,locationName:n}}const Gee=D.object({type:D.string().trim().min(1).max(100),rank_group:D.number().int().positive().nullable().optional(),rank_absolute:D.number().int().positive().nullable().optional(),page:D.number().int().positive().nullable().optional(),domain:D.string().trim().max(500).nullable().optional(),url:D.string().trim().max(1e4).nullable().optional(),title:D.string().max(1e4).nullable().optional(),description:D.string().max(5e4).nullable().optional(),is_featured_snippet:D.boolean().nullable().optional(),cid:D.unknown().optional(),phone:D.unknown().optional(),is_paid:D.unknown().optional(),rating:D.unknown().optional()}).passthrough(),_o=D.object({keyword:D.string().trim().min(1).max(700),se_domain:D.string().trim().min(1).max(500).nullable().optional(),check_url:D.string().trim().min(1).max(1e4).nullable().optional(),datetime:D.string().trim().min(1).max(100),spell:D.object({keyword:D.string().trim().min(1).max(700).optional(),type:D.string().trim().min(1).max(100).optional()}).passthrough().nullable().optional(),item_types:D.array(D.string().trim().min(1).max(100)).max(200).optional(),se_results_count:D.number().finite().nonnegative().nullable().optional(),pages_count:D.number().int().nonnegative().nullable().optional(),items_count:D.number().int().nonnegative().optional(),items:D.array(Gee).max(500).optional()}).passthrough(),vo=D.object({id:D.string().trim().min(1).max(100).optional(),status_code:D.number().int(),status_message:D.string().max(1e3),cost:D.number().finite().nonnegative().optional(),result_count:D.number().int().nonnegative().optional(),result:D.array(_o).max(10).nullable().optional()}).passthrough(),yo=D.object({status_code:D.number().int(),status_message:D.string().max(1e3),cost:D.number().finite().nonnegative().optional(),tasks_count:D.number().int().nonnegative(),tasks_error:D.number().int().nonnegative(),tasks:D.array(vo).max(10)}).passthrough(),Kee=D.object({id:D.string().trim().min(1).max(100).optional(),status_code:D.number().int(),status_message:D.string().max(1e3),cost:D.number().finite().nonnegative().optional(),result_count:D.number().int().nonnegative().optional(),data:D.object({tag:D.string().trim().min(1).max(255).optional()}).passthrough().optional()}).passthrough(),bo=D.object({status_code:D.number().int(),status_message:D.string().max(1e3),cost:D.number().finite().nonnegative().optional(),tasks_count:D.number().int().nonnegative(),tasks_error:D.number().int().nonnegative(),tasks:D.array(Kee).max(100)}).passthrough(),qee=D.object({id:D.string().trim().min(1).max(100),tag:D.string().trim().max(255).nullable().optional(),se:D.string().trim().max(100).optional(),se_type:D.string().trim().max(100).optional(),endpoint_advanced:D.string().trim().max(2048).nullable().optional()}).passthrough(),xo=D.object({id:D.string().trim().min(1).max(100).optional(),status_code:D.number().int(),status_message:D.string().max(1e3),cost:D.number().finite().nonnegative().optional(),result:D.array(qee).max(1e3).nullable().optional()}).passthrough(),So=D.object({status_code:D.number().int(),status_message:D.string().max(1e3),cost:D.number().finite().nonnegative().optional(),tasks_count:D.number().int().nonnegative(),tasks_error:D.number().int().nonnegative(),tasks:D.array(xo).max(10)}).passthrough(),Co=`v3/serp/google/organic/task_post`;function wo(e){return e.tasks.reduce((e,t)=>e+(t.result??[]).reduce((e,t)=>e+(t.items?.length??0),0),0)}function To(e){if(!/^[a-zA-Z0-9-]{1,100}$/u.test(e))throw new N({provider:`dataforseo`,operation:`serp-task-get`,code:`configuration`,message:`Use a valid queued SERP task id.`})}async function Eo(e,t){let{price:n,requests:r}=e;if(n.perRequestMicros===null||n.perResultMicros===null)throw new N({provider:`dataforseo`,operation:`serp-task-post`,code:`invalid-response`,message:`DataForSEO Standard SERP pricing is unavailable, so queued tasks were not posted.`});let i=r.reduce((e,t)=>e+Math.ceil(t.depth/10),0),a=n.perRequestMicros*i+n.perResultMicros*r.length,o=Pr({provider:`dataforseo`,capability:`serp-snapshot`,endpoint:Co,projectId:e.context.projectId,reportId:e.context.reportId,reportRunId:e.context.reportRunId,requestedRows:r.length,estimatedCostMicros:a},{database:t.database,limits:t.spendLimits,now:t.now().getTime()}),s;try{s=await ri({provider:`dataforseo`,operation:`serp-task-post`,url:new URL(Co,t.baseUrl),fetch:t.fetch,maxResponseBytes:t.maxResponseBytes,timeoutMs:t.timeoutMs,retry:`never`,schema:bo,init:{method:`POST`,headers:{authorization:t.authorization,"content-type":`application/json`},body:JSON.stringify(r)}})}catch(e){throw Fr(o.id,{provider:`dataforseo`,state:`failed`,actualCostMicros:null,returnedRows:null,taskIds:[]},{database:t.database,limits:t.spendLimits,now:t.now().getTime()}),e}let c=s.tasks.flatMap(e=>e.id&&e.data?.tag?[{providerTaskId:e.id,tag:e.data.tag}]:[]),l=c.map(e=>e.providerTaskId),u=new Set(r.map(e=>e.tag)),d=new Set(c.map(e=>e.tag)),f=u.size===r.length&&d.size===u.size&&[...u].every(e=>d.has(e)),p=s.tasks.filter(e=>e.status_code!==20100||!e.id||!e.data?.tag),m=Hi(s),h=p.length===0&&s.tasks_error===0?`succeeded`:p.length<s.tasks.length?`partial`:`failed`,g=Fr(o.id,{provider:`dataforseo`,state:h,actualCostMicros:m,returnedRows:l.length,taskIds:l},{database:t.database,limits:t.spendLimits,now:t.now().getTime()});if(s.status_code!==2e4||p.length>0||l.length!==r.length||!f){let e=p[0]?.status_code??s.status_code;throw new N({provider:`dataforseo`,operation:`serp-task-post`,code:Vi(e),message:`DataForSEO accepted ${l.length} of ${r.length} queued SERP tasks (${e}).`})}return{taskIds:l,taskReceipts:c,estimatedCostMicros:a,actualCostMicros:m,spendNotice:g,warnings:[],observedAt:t.now().toISOString()}}async function Do(e){let t=await ri({provider:`dataforseo`,operation:`serp-tasks-ready`,url:new URL(`v3/serp/google/organic/tasks_ready`,e.baseUrl),fetch:e.fetch,maxResponseBytes:e.maxResponseBytes,timeoutMs:e.timeoutMs,retry:`safe`,schema:So,init:{method:`GET`,headers:{authorization:e.authorization}}}),n=t.tasks.find(e=>e.status_code!==2e4);if(t.status_code!==2e4||n){let e=n?.status_code??t.status_code;throw new N({provider:`dataforseo`,operation:`serp-tasks-ready`,code:Vi(e),message:`DataForSEO could not list ready SERP tasks (${e}).`,retryable:e===40202})}return t.tasks.flatMap(e=>(e.result??[]).map(e=>({providerTaskId:e.id,tag:e.tag??null})))}async function Oo(e,t){let n=await ri({provider:`dataforseo`,operation:`serp-task-get`,url:new URL(`v3/serp/google/organic/task_get/advanced/${e}`,t.baseUrl),fetch:t.fetch,maxResponseBytes:t.maxResponseBytes,timeoutMs:t.timeoutMs,retry:`safe`,schema:yo,init:{method:`GET`,headers:{authorization:t.authorization}}}),r=n.tasks.find(e=>e.status_code!==2e4);if(n.status_code!==2e4||r){let t=r?.status_code??n.status_code;throw new N({provider:`dataforseo`,operation:`serp-task-get`,code:Vi(t),message:`DataForSEO could not collect queued SERP task ${e} (${t}).`,retryable:t===40202})}return{response:n,observedAt:t.now().toISOString(),returnedRows:wo(n),cache:{status:`bypass`,storedAt:null,expiresAt:null},cost:{currency:`USD`,estimatedMicros:0,actualMicros:Hi(n)??0,taskIds:[e]},spendNotice:null,warnings:[]}}const ko=`v3/dataforseo_labs/google/keyword_overview/live`,Jee={ideas:`v3/dataforseo_labs/google/keyword_ideas/live`,related:`v3/dataforseo_labs/google/related_keywords/live`,suggestions:`v3/dataforseo_labs/google/keyword_suggestions/live`};function Yee(e){return[...new Set(e.tasks.flatMap(e=>e.id?[e.id]:[]))].sort((e,t)=>e<t?-1:+(e>t))}function Ao(e){return e.tasks.reduce((e,t)=>e+(t.result??[]).reduce((e,t)=>e+(t.items?.length??0),0),0)}var jo=class{fetch;credentials;baseUrl;timeoutMs;maxResponseBytes;now;database;keywordOverviewTtlMs;keywordDiscoveryTtlMs;serpTtlMs;accountPricingTtlMs;domainResearchTtlMs;linkTtlMs;aiMentionTtlMs;aiPromptObservationTtlMs;spendLimits;accountPricing;accountPricingRequest;constructor(e={}){this.fetch=e.fetch??ae,this.credentials=e.credentials??ya,this.baseUrl=e.baseUrl??`https://api.dataforseo.com/`,this.timeoutMs=e.timeoutMs??15e3,this.maxResponseBytes=e.maxResponseBytes??5242880,this.now=e.now??(()=>new Date),this.database=e.database,this.keywordOverviewTtlMs=e.keywordOverviewTtlMs??6048e5,this.keywordDiscoveryTtlMs=e.keywordDiscoveryTtlMs??6048e5,this.serpTtlMs=e.serpTtlMs??864e5,this.accountPricingTtlMs=e.accountPricingTtlMs??3e5,this.domainResearchTtlMs=e.domainResearchTtlMs??6048e5,this.linkTtlMs=e.linkTtlMs??864e5,this.aiMentionTtlMs=e.aiMentionTtlMs??6048e5,this.aiPromptObservationTtlMs=e.aiPromptObservationTtlMs??6048e5,this.spendLimits=e.spendLimits}async getCredentials(e){let t=await this.credentials();if(!t)throw new N({provider:`dataforseo`,operation:e,code:`configuration`,message:"DataForSEO is not connected. Run `seo providers dataforseo connect`, or set SEO_DATAFORSEO_LOGIN and SEO_DATAFORSEO_PASSWORD."});return t}authorization(e){return`Basic ${Buffer.from(`${e.login}:${e.password}`).toString(`base64`)}`}serpTaskTransport(e){return{authorization:this.authorization(e),baseUrl:this.baseUrl,fetch:this.fetch,maxResponseBytes:this.maxResponseBytes,timeoutMs:this.timeoutMs,now:this.now,database:this.database,spendLimits:this.spendLimits}}async userDataForCredentials(e){let t;try{t=await ri({provider:`dataforseo`,operation:`account-status`,url:new URL(`v3/appendix/user_data`,this.baseUrl),fetch:this.fetch,maxResponseBytes:this.maxResponseBytes,timeoutMs:this.timeoutMs,retry:`safe`,schema:pi,init:{method:`GET`,headers:{authorization:this.authorization(e)}}})}catch(e){throw e instanceof N&&e.code===`authentication`?new N({provider:`dataforseo`,operation:`account-status`,code:`authentication`,status:e.status??void 0,message:"DataForSEO rejected the API login or API password. Run `seo providers dataforseo connect` again, or check SEO_DATAFORSEO_LOGIN and SEO_DATAFORSEO_PASSWORD.",cause:e}):e}let n=t.tasks.find(e=>e.status_code!==2e4);if(t.status_code!==2e4||t.tasks_error>0||n){let e=n?.status_code??t.status_code;throw new N({provider:`dataforseo`,operation:`account-status`,code:Vi(e),message:`DataForSEO could not return account status (${e}).`,retryable:e===40202})}let r=t.tasks[0]?.result?.[0];if(!r)throw new N({provider:`dataforseo`,operation:`account-status`,code:`invalid-response`,message:`DataForSEO returned account status without an account row.`});return{provider:`dataforseo`,login:r.login,timezone:r.timezone??null,balanceMicros:Bi(r.money?.balance),depositedMicros:Bi(r.money?.total),accountDailySpendMicros:Bi(r.money?.statistics?.day?.total),accountDailySpendPeriod:r.money?.statistics?.day?.value??null,accountDailyLimitMicros:Bi(r.money?.limits?.day?.total),...ai(r),backlinksSubscriptionExpiresAt:r.backlinks_subscription_expiry_date??null,aiMentionsSubscriptionExpiresAt:r.llm_mentions_subscription_expiry_date??null,apiVersion:t.version??null,requestCostMicros:Bi(t.cost)??0,taskIds:t.tasks.flatMap(e=>e.id?[e.id]:[]),observedAt:this.now().toISOString()}}async userData(){return this.userDataForCredentials(await this.getCredentials(`account-status`))}async pricingForCredentials(e,t){let n=this.now().getTime();if(this.accountPricing?.credentialScope===t&&this.accountPricing.expiresAt>n)return this.accountPricing.snapshot;if(this.accountPricingRequest?.credentialScope===t)return this.accountPricingRequest.promise;let r=this.userDataForCredentials(e).then(e=>(this.accountPricing={credentialScope:t,expiresAt:this.now().getTime()+this.accountPricingTtlMs,snapshot:e},e));this.accountPricingRequest={credentialScope:t,promise:r};try{return await r}finally{this.accountPricingRequest?.promise===r&&(this.accountPricingRequest=void 0)}}async paidPost(e){let t=await this.getCredentials(e.operation),n=zr(`dataforseo`,t.login);return Wee({...e,credentials:t,credentialScope:n,price:async()=>e.price(await this.pricingForCredentials(t,n)),baseUrl:this.baseUrl,fetch:this.fetch,maxResponseBytes:this.maxResponseBytes,timeoutMs:e.timeoutMs??this.timeoutMs,now:this.now,database:this.database,spendLimits:this.spendLimits})}async domainOverview(e){return this.paidPost(Tee(e,this.domainResearchTtlMs))}async aiMentionMetrics(e){return this.paidPost(Ai(e,this.aiMentionTtlMs))}async aiMentionSearch(e){return this.paidPost(ji(e,this.aiMentionTtlMs))}async aiPromptModels(e){let t=await this.getCredentials(`ai-prompt-models`);return Ki({surface:e,authorization:this.authorization(t),baseUrl:this.baseUrl,fetch:this.fetch,maxResponseBytes:this.maxResponseBytes,timeoutMs:this.timeoutMs})}async aiPromptObservation(e){return this.paidPost(Ji(e,this.aiPromptObservationTtlMs))}async rankedKeywords(e){return this.paidPost(Eee(e,this.domainResearchTtlMs))}async rankingPages(e){return this.paidPost(Dee(e,this.domainResearchTtlMs))}async serpCompetitors(e){return this.paidPost(Oee(e,this.domainResearchTtlMs))}async linkSummary(e){return this.paidPost(Ree(e,this.linkTtlMs))}async backlinks(e){return this.paidPost(zee(e,this.linkTtlMs))}async referringDomains(e){return this.paidPost(Bee(e,this.linkTtlMs))}async keywordOverview(e){let t=e.keywords.map(e=>e.trim());if(t.length<1||t.length>100||t.some(e=>e.length<1||e.length>80||e.split(/\s+/u).length>10))throw new N({provider:`dataforseo`,operation:`keyword-metrics`,code:`configuration`,message:`Keyword metrics requires 1 to 100 non-empty keywords of at most 80 characters and 10 words.`});if(!/^[a-z]{2}$/.test(e.languageCode))throw new N({provider:`dataforseo`,operation:`keyword-metrics`,code:`configuration`,message:`DataForSEO language code must contain two lowercase letters.`});let n=e.locationCode!==void 0,r=e.locationName?.trim();if(n===!!r)throw new N({provider:`dataforseo`,operation:`keyword-metrics`,code:`configuration`,message:`DataForSEO keyword metrics requires exactly one location code or location name.`});if(n&&(!Number.isSafeInteger(e.locationCode)||(e.locationCode??0)<=0))throw new N({provider:`dataforseo`,operation:`keyword-metrics`,code:`configuration`,message:`DataForSEO location code must be a positive integer.`});if(r&&r.length>500)throw new N({provider:`dataforseo`,operation:`keyword-metrics`,code:`configuration`,message:`DataForSEO location name must be at most 500 characters.`});let i=await this.getCredentials(`keyword-metrics`),a=zr(`dataforseo`,i.login),o={keywords:t,language_code:e.languageCode,...e.locationCode===void 0?{location_name:r}:{location_code:e.locationCode},...e.includeSerpInfo?{include_serp_info:!0}:{},...e.includeClickstreamData?{include_clickstream_data:!0}:{}},s={provider:`dataforseo`,credentialScope:a,operation:`keyword-metrics`,request:o},c=e.refresh?null:Hr(s,Aa,{database:this.database,now:this.now().getTime()});if(c)return{response:c.data,observedAt:c.storedAt,returnedRows:c.rowCount??Ao(c.data),cache:{status:`hit`,storedAt:c.storedAt,expiresAt:c.expiresAt},cost:{currency:`USD`,estimatedMicros:0,actualMicros:0,taskIds:c.taskIds},spendNotice:null,warnings:[]};let{perRequestMicros:l,perResultMicros:u}=(await this.pricingForCredentials(i,a)).keywordOverviewPrice;if(l===null||u===null)throw new N({provider:`dataforseo`,operation:`keyword-metrics`,code:`invalid-response`,message:`DataForSEO account pricing is unavailable, so the paid request was not started.`});let d=e.includeClickstreamData?2:1,f=(l+u*t.length)*d,p=this.now().getTime(),m=Pr({provider:`dataforseo`,capability:`keyword-metrics`,endpoint:ko,projectId:e.projectId,reportId:e.reportId,reportRunId:e.reportRunId,requestedRows:t.length,estimatedCostMicros:f},{database:this.database,limits:this.spendLimits,now:p}),h;try{h=await ri({provider:`dataforseo`,operation:`keyword-metrics`,url:new URL(ko,this.baseUrl),fetch:this.fetch,maxResponseBytes:this.maxResponseBytes,timeoutMs:this.timeoutMs,retry:`never`,schema:Aa,init:{method:`POST`,headers:{authorization:this.authorization(i),"content-type":`application/json`},body:JSON.stringify([o])}})}catch(e){throw Fr(m.id,{provider:`dataforseo`,state:`failed`,actualCostMicros:null,returnedRows:null,taskIds:[]},{database:this.database,limits:this.spendLimits,now:this.now().getTime()}),e}let g=Hi(h),_=Ao(h),v=Yee(h),y=h.tasks.filter(e=>e.status_code!==2e4),b=h.status_code!==2e4||h.tasks_error>0||y.length>0,x=b?y.length<h.tasks.length?`partial`:`failed`:`succeeded`,S=Fr(m.id,{provider:`dataforseo`,state:x,actualCostMicros:g,returnedRows:_,taskIds:v},{database:this.database,limits:this.spendLimits,now:this.now().getTime()});if(b){let e=y[0]?.status_code??h.status_code;throw new N({provider:`dataforseo`,operation:`keyword-metrics`,code:Vi(e),message:`DataForSEO could not complete the keyword metrics task (${e}).`,retryable:e===40202})}let C=[];try{Ur(s,{data:h,ttlMs:this.keywordOverviewTtlMs,rowCount:_,sourceCostMicros:g,taskIds:v},{database:this.database,now:this.now().getTime()})}catch{C.push({code:`cache-write-failed`,message:`The provider result is valid, but it could not be saved to the local cache.`})}return{response:h,observedAt:this.now().toISOString(),returnedRows:_,cache:{status:e.refresh?`bypass`:`miss`,storedAt:null,expiresAt:null},cost:{currency:`USD`,estimatedMicros:f,actualMicros:g,taskIds:v},spendNotice:S,warnings:C}}async keywordDiscovery(e){let t=e.seeds.map(e=>e.trim());if(t.length<1||t.length>5||t.some(e=>e.length<1||e.length>80||e.split(/\s+/u).length>10))throw new N({provider:`dataforseo`,operation:`keyword-discovery`,code:`configuration`,message:`Keyword discovery requires 1 to 5 seeds of at most 80 characters and 10 words.`});if(e.source!==`ideas`&&(t.length!==1||t[0]===void 0))throw new N({provider:`dataforseo`,operation:`keyword-discovery`,code:`configuration`,message:`${e.source} discovery requires exactly one seed per provider request.`});if(!Number.isSafeInteger(e.limit)||e.limit<1||e.limit>100)throw new N({provider:`dataforseo`,operation:`keyword-discovery`,code:`configuration`,message:`Keyword discovery limit must be from 1 to 100.`});if(!/^[a-z]{2}$/.test(e.languageCode))throw new N({provider:`dataforseo`,operation:`keyword-discovery`,code:`configuration`,message:`DataForSEO language code must contain two lowercase letters.`});let n=e.locationName?.trim();if(e.locationCode!==void 0==!!n)throw new N({provider:`dataforseo`,operation:`keyword-discovery`,code:`configuration`,message:`DataForSEO keyword discovery requires exactly one location code or location name.`});let r=e.locationCode===void 0?{location_name:n}:{location_code:e.locationCode},i=e.source===`ideas`?{keywords:t,language_code:e.languageCode,...r,include_serp_info:!0,limit:e.limit}:{keyword:t[0],language_code:e.languageCode,...r,include_serp_info:!0,include_seed_keyword:!1,...e.source===`related`?{depth:3}:{},limit:e.limit,order_by:[`${e.source===`related`?`keyword_data.`:``}keyword_info.search_volume,desc`]},a=await this.paidPost({operation:`keyword-discovery-${e.source}`,capability:`keyword-discovery`,endpoint:Jee[e.source],request:i,schema:La,requestedRows:e.limit,price:t=>t.keywordDiscoveryPrices[e.source],context:e.context,ttlMs:this.keywordDiscoveryTtlMs,refresh:e.refresh,rowCount:xa});return{...a,providerTotalRows:Sa(a.response),nextCursor:Ca(a.response)}}async serpLive(e){let{keyword:t,locationName:n}=go(e),r={keyword:t,language_code:e.languageCode,...e.locationCode===void 0?{location_name:n}:{location_code:e.locationCode},device:e.device,depth:e.depth,remove_from_url:[`srsltid`]};return this.paidPost({operation:`serp-snapshot`,capability:`serp-snapshot`,endpoint:`v3/serp/google/organic/live/advanced`,request:r,schema:yo,requestedRows:e.depth,estimatedRequestUnits:Math.ceil(e.depth/10),price:e=>e.serpLiveAdvancedPrice,context:e.context,ttlMs:this.serpTtlMs,refresh:e.refresh,rowCount:wo})}async serpTaskPost(e){if(e.tasks.length<1||e.tasks.length>100)throw new N({provider:`dataforseo`,operation:`serp-task-post`,code:`configuration`,message:`A queued SERP post requires 1 to 100 tasks.`});let t=e.tasks.map(e=>{let t=go(e);return{keyword:t.keyword,language_code:e.languageCode,...e.locationCode===void 0?{location_name:t.locationName}:{location_code:e.locationCode},device:e.device,depth:e.depth,tag:e.tag,priority:1,remove_from_url:[`srsltid`]}}),n=await this.getCredentials(`serp-task-post`),r=zr(`dataforseo`,n.login),i=(await this.pricingForCredentials(n,r)).serpTaskPostPrice;return Eo({requests:t,context:e.context,price:i},{authorization:this.authorization(n),baseUrl:this.baseUrl,fetch:this.fetch,maxResponseBytes:this.maxResponseBytes,timeoutMs:this.timeoutMs,now:this.now,database:this.database,spendLimits:this.spendLimits})}async serpTasksReady(){let e=await this.getCredentials(`serp-tasks-ready`);return Do(this.serpTaskTransport(e))}async serpTaskGet(e){To(e);let t=await this.getCredentials(`serp-task-get`);return Oo(e,this.serpTaskTransport(t))}};const Mo={single:`v3/ai_optimization/llm_mentions/target_metrics/live`,multi:`v3/ai_optimization/llm_mentions/multi_target_metrics/live`},No=2e3;function Po(e,t){return e<t?-1:+(e>t)}function Fo(e){return e===void 0?{state:`missing`,value:null,reason:`The provider did not return this metric.`}:{state:`observed`,value:e}}function Io(e){if(!e)return{state:`missing`,value:null,reason:`The provider did not return an observation time.`};let t=Date.parse(e);return Number.isFinite(t)?{state:`observed`,value:new Date(t).toISOString()}:{state:`invalid`,value:null,reason:`The provider returned an invalid observation time.`}}function Lo(e){return e==null?{state:`missing`,value:null,reason:`The provider did not return this flag.`}:{state:`observed`,value:e}}function Ro(e){let t=e.trim().toLowerCase();if(!t)return null;try{let e=new URL(t.includes(`://`)?t:`https://${t}`).hostname.replace(/^www\./u,``).replace(/\.$/u,``);return e.includes(`.`)&&e.length<=253?e:null}catch{return null}}function zo(e){if(!e)return null;try{let t=new URL(e);return[`http:`,`https:`].includes(t.protocol)?(t.username=``,t.password=``,t.toString()):null}catch{return null}}function Bo(e){let t=0,n=new Map;for(let r of e?.sources_domain??[]){let e=Ro(String(r.key));if(!e||r.mentions===void 0||r.ai_search_volume===void 0){t+=1;continue}let i=n.get(e)??{mentions:0,aiSearchVolume:0};i.mentions+=r.mentions,i.aiSearchVolume+=r.ai_search_volume,n.set(e,i)}return{rows:[...n.entries()].map(([e,t])=>({domain:e,...t})).sort((e,t)=>t.mentions-e.mentions||t.aiSearchVolume-e.aiSearchVolume||Po(e.domain,t.domain)).slice(0,10),invalidRows:t}}function Vo(e){return e.market.surface===`chatgpt`?`chat_gpt`:`google`}function Ho(e){return e.market.location.code===void 0?{locationName:e.market.location.name}:{locationCode:e.market.location.code}}function Uo(e){let t=jn.safeParse(e.market);if(!t.success)throw new N({provider:`dataforseo`,operation:`ai-mentions`,code:`configuration`,message:`Use a valid AI mention market and surface.`});if(t.data.surface===`chatgpt`&&(t.data.countryCode!==`US`||t.data.languageCode!==`en`||t.data.location.code!==void 0&&t.data.location.code!==2840))throw new N({provider:`dataforseo`,operation:`ai-mentions`,code:`configuration`,message:`DataForSEO ChatGPT mention data currently supports United States English only.`});return{...e,market:t.data}}function Wo(e){return e.response.tasks.flatMap(e=>e.result??[])[0]}function Go(e,t){let n=Wo(t),r=[e.target,...e.competitors],i=r.length>1,a=i?n?.items??[]:n?.aggregated_metrics?[{...n.aggregated_metrics,key:e.target.key}]:[],o=new Map,s=0;for(let e of a){if(!e.key||o.has(e.key)){s+=1;continue}o.set(e.key,e)}let c=r.map(e=>{let t=o.get(e.key),n=Bo(t);return s+=n.invalidRows,{target:e,mentions:Fo(t?.total?.mentions),aiSearchVolume:Fo(t?.total?.ai_search_volume),sourceDomains:n.rows}}),l=n?.aggregated_metrics,u=Bo(l??void 0);s+=u.invalidRows;let d=[...t.warnings];return s>0&&d.push({code:`invalid-ai-mention-metric-rows`,message:`The provider returned ${s} duplicate or invalid AI mention metric row${s===1?``:`s`} that could not be retained.`}),{schemaVersion:1,provider:`dataforseo`,capability:`ai-mentions`,data:{targets:c,combined:{mentions:Fo(l?.total?.mentions),aiSearchVolume:Fo(l?.total?.ai_search_volume),sourceDomains:u.rows}},observedAt:t.observedAt,market:e.market,coverage:{requestedRows:r.length,returnedRows:t.returnedRows,retainedRows:c.filter(e=>e.mentions.state===`observed`||e.aiSearchVolume.state===`observed`).length,invalidRows:s,providerTotalRows:n?.total_count??null,completeness:s>0?`partial`:`complete`,nextCursor:null},cache:t.cache,cost:t.cost,request:{operation:`ai-mention-metrics`,endpoint:i?Mo.multi:Mo.single,limit:r.length,filters:{surface:e.market.surface,countryCode:e.market.countryCode,languageCode:e.market.languageCode,aliases:r.reduce((e,t)=>e+t.aliases.length,0)},sort:[`mentions:descending`,`target.key:codepoint-ascending`]},warnings:d}}function Ko(e,t){let n=zo(e.url),r=Ro(e.domain??``);return!n||!r?null:{rank:e.rank??t,domain:r,url:n,title:e.title?.trim()||null,sourceName:e.source_name?.trim()||null}}function qo(e,t){let n=t.response.tasks.flatMap(e=>e.result??[])[0],r=[...t.warnings],i=0,a=0,o=0,s=new Set,c=(n?.items??[]).flatMap(e=>{let t=e.question?.trim();if(!t||s.has(t.toLowerCase()))return i+=1,[];s.add(t.toLowerCase());let n=e.answer?.trim()??``,r=n.length>No;r&&(a+=1);let c=(e.sources??[]).flatMap((e,t)=>{let n=Ko(e,t+1);return n||(i+=1),n?[n]:[]}),l=c.sort((e,t)=>e.rank-t.rank||Po(e.url,t.url)).slice(0,10);return c.length>l.length&&(o+=1),[{question:t,answerExcerpt:n.slice(0,No),answerTruncated:r,model:e.model_name?.trim()||null,aiSearchVolume:Fo(e.ai_search_volume??void 0),firstObservedAt:Io(e.first_response_at),lastObservedAt:Io(e.last_response_at),isWebSearchBased:Lo(e.is_web_search_based),sources:l}]}).sort((e,t)=>(t.aiSearchVolume.state===`observed`?t.aiSearchVolume.value:-1)-(e.aiSearchVolume.state===`observed`?e.aiSearchVolume.value:-1)||Po(e.question,t.question)).slice(0,e.sampleLimit);i>0&&r.push({code:`invalid-ai-mention-sample-rows`,message:`The provider returned ${i} duplicate or invalid AI mention sample value${i===1?``:`s`} that could not be retained.`}),(a>0||o>0)&&r.push({code:`bounded-ai-mention-sample-detail`,message:`${a} answer excerpt${a===1?``:`s`} and ${o} source list${o===1?``:`s`} were shortened to keep structured output bounded.`});let l=n?.total_count??null,u=l!==null&&l>c.length;return{schemaVersion:1,provider:`dataforseo`,capability:`ai-mentions`,data:c,observedAt:t.observedAt,market:e.market,coverage:{requestedRows:e.sampleLimit,returnedRows:t.returnedRows,retainedRows:c.length,invalidRows:i,providerTotalRows:l,completeness:i>0?`partial`:u?`capped`:`complete`,nextCursor:n?.search_after_token??null},cache:t.cache,cost:t.cost,request:{operation:`ai-mention-samples`,endpoint:`v3/ai_optimization/llm_mentions/search_mentions/live`,limit:e.sampleLimit,filters:{surface:e.market.surface,countryCode:e.market.countryCode,languageCode:e.market.languageCode,aliases:e.target.aliases.length},sort:[`aiSearchVolume:descending`,`question:codepoint-ascending`]},warnings:r}}var Jo=class{provider=`dataforseo`;capabilitySupport=[{capability:`ai-mentions`,status:`available`,markets:`all`}];client;constructor(e={}){this.client=e.client??new jo(e)}async aiMentionMetrics(e){let t=Uo(e),n=t.context??{reportId:`ai-mention-research`,reportRunId:i()};return Go(t,await this.client.aiMentionMetrics({target:t.target,competitors:t.competitors,platform:Vo(t),languageCode:t.market.languageCode.split(`-`)[0],...Ho(t),refresh:t.refresh,context:n}))}async aiMentionSamples(e){let t=Uo(e),n=t.context??{reportId:`ai-mention-research`,reportRunId:i()};return qo(t,await this.client.aiMentionSearch({target:t.target,platform:Vo(t),languageCode:t.market.languageCode.split(`-`)[0],...Ho(t),limit:t.sampleLimit,refresh:t.refresh,context:n}))}};function Yo(e,t){return!e||e.some(e=>e===t)}function Xo(e,t){return!(!Yo(e.searchEngines,t.searchEngine)||!Yo(e.countryCodes,t.countryCode)||!Yo(e.languageCodes,t.languageCode)||t.device&&!Yo(e.devices,t.device)||e.location===`country-only`&&t.location||e.location===`canonical`&&!t.location)}function Zo(e,t){return e.adapter.capabilitySupport.find(e=>e.capability===t&&e.status===`available`)}function Xee(e,t){return e.markets===`all`||e.markets.some(e=>Xo(e,t))}function Zee(e){return[...e].sort((e,t)=>e.priority-t.priority||e.adapter.provider.localeCompare(t.adapter.provider))}function Qo(e){let t=Zee(e.candidates),n=t.map(e=>e.adapter.provider),r=e.provider?t.filter(t=>t.adapter.provider===e.provider):t;if(e.provider&&r.length===0)return{status:`unavailable`,reason:`provider-not-registered`,considered:n};let i=r.map(t=>({candidate:t,support:Zo(t,e.capability)})).filter(e=>!!e.support);if(i.length===0)return{status:`unavailable`,reason:`capability-not-supported`,considered:n};let a=i.filter(t=>Xee(t.support,e.market));if(a.length===0)return{status:`unavailable`,reason:`market-not-supported`,considered:n};let o=a.find(e=>e.candidate.connected);return o?{status:`available`,provider:o.candidate.adapter,considered:n}:{status:`unavailable`,reason:`provider-not-connected`,considered:n}}const $o=new se;function Qee(e){$o.enterWith(e??{})}function es(e){return $o.getStore()?.[e]}function ts(e,t){let n=[...new Map(e.filter(e=>!!e).map(e=>[e.toLowerCase(),e])).values()];if(n.length<=1)return n[0];throw new j(`INVALID_INPUT`,`Saved projects use different Google accounts for ${t}. Select a project-specific account instead of relying on resource matching.`)}function ns(e){return es(`searchConsole`)||ts(Qn().clients.filter(t=>t.siteUrl===e).map(e=>e.googleAccounts?.searchConsole),e)}function rs(e){return es(`googleAnalytics`)||ts(Qn().clients.filter(t=>t.analytics.google?.propertyId===e).map(e=>e.googleAccounts?.googleAnalytics),`Google Analytics property ${e}`)}function is(e){throw new j(`INVALID_INPUT`,e)}function as(e){let t;try{t=new URL(e.trim())}catch{return is(`Invalid URL: ${e}`)}return t.protocol!==`http:`&&t.protocol!==`https:`?is(`The report URL must use http:// or https://.`):t.username||t.password?is(`The report URL must not contain embedded credentials.`):(t.hash=``,t.toString())}function os(e,t){let n=as(t),r=new URL(n);if(e.startsWith(`sc-domain:`)){let t=e.slice(10).trim().toLowerCase();if(!t||/[/:]/.test(t))return is(`Invalid Search Console domain property: ${e}`);let i=r.hostname.toLowerCase();return i!==t&&!i.endsWith(`.${t}`)?is(`${n} is outside the Search Console property ${e}.`):n}let i=as(e);return n.startsWith(i)?n:is(`${n} is outside the Search Console property ${e}.`)}async function $ee(e,t){Vn(e);let n=process.env.SEO_LOCK_FAST===`1`,r=await ce.lock(e,{stale:n?2e3:6e4,update:n?1e3:5e3,retries:n?{retries:5,factor:1.2,minTimeout:50,maxTimeout:200}:{retries:60,factor:1.2,minTimeout:200,maxTimeout:1e3},realpath:!1}).catch(t=>{let n=t instanceof Error?t.message:String(t);throw Error(`Could not acquire local file lock for ${e}. Another seo process may still be writing auth/config state. Wait a few seconds and retry. ${n}`)});try{return await t()}finally{await r()}}const ss={clientId:`548418788053-q7p964rqvjiv8ooamciprku6bpu34u9d.apps.googleusercontent.com`,clientSecret:`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`};function cs(){let e=yr();return{sharedConfigured:!!ls(),byoConfigured:!!(e?.clientId&&e?.clientSecret)}}function ls(){let e=Qn(),t=process.env.SEO_GOOGLE_CLIENT_ID??process.env.GSC_CLIENT_ID??ss.clientId??e.auth.sharedClientId,n=process.env.SEO_GOOGLE_CLIENT_SECRET??process.env.GSC_CLIENT_SECRET??ss.clientSecret??e.auth.sharedClientSecret;if(!(!t||!n))return{clientId:t,clientSecret:n,source:`shared`}}function us(e){let t=yr();if(e!==`shared`&&t)return{clientId:t.clientId,clientSecret:t.clientSecret,source:`byo`};if(e!==`byo`)return ls()}function ete(e){return e===`shared`?"The stored Google login uses the shared seo app, but this build is missing that OAuth client. Report this package issue at https://github.com/iannuttall/seo/issues, or use `seo auth setup-client` with your own desktop OAuth client.":"The stored Google login uses a BYO OAuth client that is no longer configured. Restore the same client with `seo auth setup-client`, or run `seo auth logout` before signing in with a different client."}const ds=[`https://www.googleapis.com/auth/webmasters.readonly`,`https://www.googleapis.com/auth/analytics.readonly`],fs={"https://www.googleapis.com/auth/webmasters.readonly":`Search Console`,"https://www.googleapis.com/auth/analytics.readonly":`Google Analytics`};function ps(e){let t=new Set(e.split(/\s+/u).filter(Boolean));return ds.filter(e=>!t.has(e))}const ms=[...ds,`openid`,`email`].join(` `),hs=`https://oauth2.googleapis.com/token`;function gs(e){return new j(`AUTH_CONFIG_REQUIRED`,e)}function _s(e){return e===`environment-json`?`SEO_GOOGLE_SERVICE_ACCOUNT_JSON`:e===`environment-file`?`SEO_GOOGLE_SERVICE_ACCOUNT_FILE`:`GOOGLE_APPLICATION_CREDENTIALS`}function vs(e){let t=[],n=e.SEO_GOOGLE_SERVICE_ACCOUNT_JSON?.trim();n&&t.push({source:`environment-json`,value:n});let r=e.SEO_GOOGLE_SERVICE_ACCOUNT_FILE?.trim();r&&t.push({source:`environment-file`,value:r});let i=e.GOOGLE_APPLICATION_CREDENTIALS?.trim();return i&&t.push({source:`application-default-file`,value:i}),t}function ys(e,t){let n;try{n=JSON.parse(e)}catch{throw gs(_s(t)+` must contain a valid service account JSON object.`)}if(!n||typeof n!=`object`||Array.isArray(n))throw gs(`${_s(t)} must contain a service account JSON object.`);let r=n;if(r.type!==void 0&&r.type!==`service_account`)throw gs(_s(t)+` must contain a Google service account credential.`);if(typeof r.client_email!=`string`||!r.client_email||typeof r.private_key!=`string`||!r.private_key)throw gs(_s(t)+` must include service account client_email and private_key fields.`);if(r.private_key_id!==void 0&&typeof r.private_key_id!=`string`)throw gs(`${_s(t)} has an invalid service account private_key_id.`);return{clientEmail:r.client_email,privateKey:r.private_key,privateKeyId:r.private_key_id,source:t}}function bs(e,t){try{return u(e,`utf8`)}catch{throw gs(`Could not read the service account file named by `+_s(t)+`.`)}}function xs(e=process.env){let t=vs(e);if(!t.length)return;if(t.length>1)throw gs(`Set only one service account credential source: SEO_GOOGLE_SERVICE_ACCOUNT_JSON, SEO_GOOGLE_SERVICE_ACCOUNT_FILE, or GOOGLE_APPLICATION_CREDENTIALS.`);let n=t[0];if(n)return ys(n.source===`environment-json`?n.value:bs(n.value,n.source),n.source)}function Ss(e=process.env){try{let t=xs(e);return t?{configured:!0,identity:t.clientEmail,source:t.source}:{configured:!1}}catch(t){let n=vs(e);return{configured:!1,...n.length===1&&n[0]?{source:n[0].source}:{},error:t instanceof Error?t.message:`Service account credentials are invalid.`}}}function Cs(e){return Buffer.from(e).toString(`base64url`)}function ws(e,t){let n={alg:`RS256`,typ:`JWT`,...e.privateKeyId?{kid:e.privateKeyId}:{}},r={iss:e.clientEmail,scope:ds.join(` `),aud:hs,iat:t,exp:t+3600},i=`${Cs(JSON.stringify(n))}.${Cs(JSON.stringify(r))}`;try{return i+`.`+a(`RSA-SHA256`,Buffer.from(i),e.privateKey).toString(`base64url`)}catch{throw gs(`The service account private key could not sign a Google access request.`)}}async function Ts(e,t=fetch){let n=ws(e,Math.floor(Date.now()/1e3)),r=await t(hs,{method:`POST`,headers:{"content-type":`application/x-www-form-urlencoded`},body:new URLSearchParams({grant_type:`urn:ietf:params:oauth:grant-type:jwt-bearer`,assertion:n})});if(!r.ok){let e=await r.json().catch(()=>void 0),t=typeof e?.error==`string`?` (${e.error})`:``;throw r.status===400||r.status===401||r.status===403?gs(`Google rejected the service account access request with `+r.status+t+`. Check the service account, private key, and system clock.`):new j(`PROVIDER_UNAVAILABLE`,`Google token service is unavailable (`+r.status+`). Try again later.`)}let i=await r.json();if(typeof i.access_token!=`string`||!i.access_token||typeof i.expires_in!=`number`||!Number.isFinite(i.expires_in)||i.expires_in<=0)throw new j(`PROVIDER_UNAVAILABLE`,`Google token service returned an invalid service account response.`);return{accessToken:i.access_token,expiresIn:i.expires_in}}var Es=class{config;fetchImpl;token;constructor(e,t=fetch){this.config=e,this.fetchImpl=t}async getAccessToken(){if(this.token&&this.token.expiresAt-Date.now()>=6e4)return this.token.value;let e=await Ts(this.config,this.fetchImpl);return this.token={value:e.accessToken,expiresAt:Date.now()+e.expiresIn*1e3},this.token.value}};const Ds=`https://oauth2.googleapis.com/token`;var Os=class extends Error{status;oauthError;constructor(e,t){super(`Google token refresh failed with ${e}${t?` (${t})`:``}.`),this.status=e,this.oauthError=t,this.name=`GoogleTokenEndpointError`}};async function ks(e,t=fetch){let n=await t(Ds,{method:`POST`,headers:{"content-type":`application/x-www-form-urlencoded`},body:new URLSearchParams({grant_type:`refresh_token`,client_id:e.clientConfig.clientId,client_secret:e.clientConfig.clientSecret,refresh_token:e.refreshToken})});if(!n.ok){let e=await n.json().catch(()=>void 0);throw new Os(n.status,typeof e?.error==`string`?e.error:void 0)}let r=await n.json();if(typeof r.access_token!=`string`||!r.access_token||typeof r.expires_in!=`number`||!Number.isFinite(r.expires_in)||r.expires_in<=0||r.refresh_token!==void 0&&typeof r.refresh_token!=`string`)throw Error(`Google token refresh returned an invalid response.`);return{accessToken:r.access_token,expiresIn:r.expires_in,refreshToken:r.refresh_token}}function As(e){return new j(`AUTH_REQUIRED`,e?`Google account ${e} is not connected. Run \`seo auth login\` first.`:"Not logged in. Run `seo auth login` first.")}function js(){return new j(`AUTH_EXPIRED`,"Google refresh token is no longer valid. Run `seo auth login` again.")}function Ms(e){let t=ps(e.scope);if(t.length!==0)throw new j(`ACCESS_DENIED`,`Google access is missing ${t.map(e=>fs[e]).join(` and `)}. Run \`seo auth login\` again, choose Select all, and approve both permission boxes.`)}function Ns(e,t){return!e.access_token||e.expires_at-t<6e4}function Ps(e){let t=us(e.client_source);if(!t)throw new j(`AUTH_CONFIG_REQUIRED`,ete(e.client_source));return t}function Fs(e){return e instanceof Os&&(e.status===401||e.oauthError===`invalid_grant`)||/invalid_grant|401/.test(e instanceof Error?e.message:String(e))}async function Is(e){return $ee(ot().tokensFile,async()=>{let t=await pr(e.accountEmail);if(!t)throw As(e.accountEmail);let n=Ps(t),r=Date.now();if(e.onlyIfExpiring&&!Ns(t,r))return t;if(!t.refresh_token)throw await _r(t.account_email),js();try{let e=await ks({clientConfig:n,refreshToken:t.refresh_token}),i={...t,access_token:e.accessToken,refresh_token:e.refreshToken??t.refresh_token,expires_at:r+e.expiresIn*1e3};return await gr(i,{makeActive:!1}),i}catch(e){throw Fs(e)?(await _r(t.account_email),js()):e}})}var Ls=class{tokens;constructor(e){this.tokens=e}async getAccessToken(){if(Ns(this.tokens,Date.now())&&(this.tokens=await Is({onlyIfExpiring:!0,accountEmail:this.tokens.account_email})),!this.tokens.access_token)throw js();return this.tokens.access_token}};async function Rs(e){let t=await pr(e);if(!t)throw As(e);return Ps(t),Ms(t),Ns(t,Date.now())&&(t=await Is({onlyIfExpiring:!0,accountEmail:t.account_email})),{client:new Ls(t),tokens:t}}async function zs(e){return Is({onlyIfExpiring:!1,accountEmail:e})}async function Bs(e){let t=xs();if(t)return{client:new Es(t),mode:`service-account`,identity:t.clientEmail,quotaIdentity:{clientId:`service-account`,accountEmail:t.clientEmail}};let{client:n,tokens:r}=await Rs(e),i=Ps(r);return{client:n,mode:`oauth`,identity:r.account_email,quotaIdentity:{clientId:i.clientId,accountEmail:r.account_email},tokens:r}}async function Vs(){let e=cs(),t=Ss(),n=mr();if(t.configured||t.error)return{configured:t.configured,accounts:n,sharedConfigured:e.sharedConfigured,byoConfigured:e.byoConfigured,activeMode:`service-account`,identity:t.identity,serviceAccount:t};let r=await pr();return{tokens:r,accounts:n,configured:!!us(r?.client_source),sharedConfigured:e.sharedConfigured,byoConfigured:e.byoConfigured,activeMode:r?`oauth`:`none`,identity:r?.account_email,serviceAccount:t}}function Hs(e){return e.status===`connected`?{title:`Google account connected`,heading:`Google account connected.`,detail:`This tab can be closed.`}:e.status===`permissions-missing`?{title:`Google permissions not granted`,heading:`Google permissions were not granted.`,detail:`Return to your terminal and run seo auth login again. Select all permission boxes for ${e.missing.join(` and `)}.`}:{title:`Google connection failed`,heading:`Google connection failed.`,detail:`Return to your terminal for the error and try again.`}}function Us(e={status:`connected`}){let t=Hs(e);return`<!doctype html>
606
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(e.provider,e.credentialScope,e.operation,Br(e,o),o,s,t.rowCount,t.sourceCostMicros,l,i,a),Pt(Buffer.byteLength(o)+Buffer.byteLength(s)+Buffer.byteLength(l),r),{data:t.data,storedAt:new Date(i).toISOString(),expiresAt:new Date(a).toISOString(),rowCount:t.rowCount,sourceCostMicros:t.sourceCostMicros,taskIds:c}}const Wr=`0.2.38`,Gr=`SEO-Skill`,Kr=`${Gr}/${Wr} (+https://seoskill.dev)`,qr={name:`SEO Skill`,robotsToken:Gr,version:Wr,userAgent:Kr,documentationUrl:`https://seoskill.dev/docs/crawler/`};var Jr=class extends Error{maxBytes;constructor(e,t=`Response`){super(`${t} exceeds the ${e}-byte response limit.`),this.maxBytes=e,this.name=`ResponseSizeLimitError`}};function Yr(e){return e===`bot`?new ie({accept:`text/plain,*/*;q=0.8`,"accept-language":`en-US,en;q=0.9`,connection:`close`,"user-agent":Kr}):new ie({accept:`text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8`,"accept-language":`en-US,en;q=0.9`,connection:`close`,"sec-ch-ua-mobile":`?0`,"sec-ch-ua-platform":`"macOS"`,"upgrade-insecure-requests":`1`,"user-agent":Kr})}function Xr(e,t={}){let{profile:n=`browser`,headers:r,...i}=t,a=Yr(n);if(r instanceof ie)for(let[e,t]of r.entries())a.set(e,t);else if(Array.isArray(r))for(let[e,t]of r)a.set(e,t);else if(r)for(let[e,t]of Object.entries(r))t!==void 0&&a.set(e,t);return ae(e,{...i,headers:a})}async function Zr(e,t,n=`Response`){let r=Number(e.headers.get(`content-length`));if(Number.isFinite(r)&&r>t)throw await e.body?.cancel().catch(()=>void 0),new Jr(t,n);let i=e.body?.getReader();if(!i)return``;let a=[],o=0;try{for(;;){let{done:e,value:r}=await i.read();if(e)break;if(o+=r.byteLength,o>t)throw await i.cancel().catch(()=>void 0),new Jr(t,n);a.push(r)}}finally{i.releaseLock()}return Buffer.concat(a,o).toString(`utf8`)}function Qr(e){let t=e.issues.slice(0,3).map(e=>`${e.path.flatMap(e=>typeof e==`number`&&Number.isSafeInteger(e)?[`[${e}]`]:typeof e==`string`&&e.length<=64&&/^[a-zA-Z0-9_-]+$/u.test(e)?[e]:[]).reduce((e,t)=>t.startsWith(`[`)?`${e}${t}`:e?`${e}.${t}`:t,``)||`<root>`} (${e.code})`);return[...new Set(t)].join(`, `)}function $r(e,t){return t===401||t===403?new N({provider:e.provider,operation:e.operation,code:`authentication`,message:`${e.provider} rejected the configured credentials.`,status:t}):t===429?new N({provider:e.provider,operation:e.operation,code:`rate-limit`,message:`${e.provider} rate limited the request.`,status:t,retryable:!0}):new N({provider:e.provider,operation:e.operation,code:`remote-error`,message:`${e.provider} returned HTTP ${t}.`,status:t,retryable:t>=500})}function ei(e,t){return t instanceof N?t:t instanceof Jr?new N({provider:e.provider,operation:e.operation,code:`response-too-large`,message:t.message,cause:t}):t instanceof DOMException&&(t.name===`AbortError`||t.name===`TimeoutError`)?new N({provider:e.provider,operation:e.operation,code:`timeout`,message:`${e.provider} did not respond within ${e.timeoutMs}ms.`,retryable:!0,cause:t}):new N({provider:e.provider,operation:e.operation,code:`remote-error`,message:`${e.provider} request failed before a valid response arrived.`,retryable:!0,cause:t})}async function ti(e){try{let t=await e.fetch(e.url,{...e.init,signal:e.init?.signal??AbortSignal.timeout(e.timeoutMs)});if(!t.ok)throw await t.body?.cancel().catch(()=>void 0),$r(e,t.status);return e.onResponse?.(t),await Zr(t,e.maxResponseBytes,`${e.provider} response`)}catch(t){throw ei(e,t)}}async function ni(e){let t=e.retry===`safe`?2:1,n;for(let r=1;r<=t;r+=1)try{return await ti(e)}catch(i){if(n=ei(e,i),!n.retryable||r===t)throw n;await oe(e.retryDelayMs??250)}throw n}async function ri(e){let t=await ni(e),n;try{n=JSON.parse(t)}catch(t){throw new N({provider:e.provider,operation:e.operation,code:`invalid-response`,message:`${e.provider} returned malformed JSON.`,cause:t})}let r=e.schema.safeParse(n);if(!r.success){let t=Qr(r.error);throw new N({provider:e.provider,operation:e.operation,code:`invalid-response`,message:`${e.provider} returned data that does not match the expected response schema${t?` at ${t}`:``}.`,cause:r.error})}return r.data}function ii(e){let t=e??[],n=e=>{let n=t.filter(t=>t.cost_type===e);return n.length?n.reduce((e,t)=>e+Math.round(t.cost*1e6),0):t.length?0:null};return{perRequestMicros:n(`per_request`),perResultMicros:n(`per_result`)}}function ai(e){return{aiPromptObservationPrice:ii(e.price?.ai_optimization?.llm_responses?.live?.priority_normal),aiMentionPrices:{targetMetrics:ii(e.price?.ai_optimization?.llm_mentions?.target_metrics?.live?.priority_normal),multiTargetMetrics:ii(e.price?.ai_optimization?.llm_mentions?.multi_target_metrics?.live?.priority_normal),searchMentions:ii(e.price?.ai_optimization?.llm_mentions?.search_mentions?.live?.priority_normal)},keywordOverviewPrice:ii(e.price?.dataforseo_labs?.keyword_overview?.live?.priority_normal),keywordDiscoveryPrices:{ideas:ii(e.price?.dataforseo_labs?.keyword_ideas?.live?.priority_normal),related:ii(e.price?.dataforseo_labs?.related_keywords?.live?.priority_normal),suggestions:ii(e.price?.dataforseo_labs?.keyword_suggestions?.live?.priority_normal)},domainResearchPrices:{domainOverview:ii(e.price?.dataforseo_labs?.domain_rank_overview?.live?.priority_normal),rankedKeywords:ii(e.price?.dataforseo_labs?.ranked_keywords?.live?.priority_normal),rankingPages:ii(e.price?.dataforseo_labs?.relevant_pages?.live?.priority_normal),serpCompetitors:ii(e.price?.dataforseo_labs?.serp_competitors?.live?.priority_normal)},linkPrices:{summary:ii(e.price?.backlinks?.summary?.live?.priority_normal),backlinks:ii(e.price?.backlinks?.backlinks?.live?.priority_normal),referringDomains:ii(e.price?.backlinks?.referring_domains?.live?.priority_normal)},serpLiveAdvancedPrice:ii(e.price?.serp?.live?.advanced?.priority_normal),serpTaskPostPrice:ii(e.price?.serp?.task_post?.priority_normal)}}const oi=D.object({total:D.number().finite().nonnegative().optional(),balance:D.number().finite().optional(),limits:D.object({day:D.object({total:D.number().finite().nonnegative().optional()}).passthrough().optional()}).passthrough().optional(),statistics:D.object({day:D.object({total:D.number().finite().nonnegative().optional(),value:D.string().max(100).optional()}).passthrough().optional()}).passthrough().optional()}).passthrough(),si=D.object({cost_type:D.string().min(1).max(100),cost:D.number().finite().nonnegative()}).passthrough(),ci=D.object({priority_normal:D.array(si).max(20).optional()}).passthrough(),li=D.object({live:ci.optional()}).passthrough(),ui=D.object({ai_optimization:D.object({llm_responses:D.object({live:ci.optional()}).passthrough().optional(),llm_mentions:D.object({target_metrics:li.optional(),multi_target_metrics:li.optional(),search_mentions:li.optional()}).passthrough().optional()}).passthrough().optional(),backlinks:D.object({summary:li.optional(),backlinks:li.optional(),referring_domains:li.optional()}).passthrough().optional(),dataforseo_labs:D.object({keyword_overview:li.optional(),keyword_ideas:li.optional(),keyword_suggestions:li.optional(),related_keywords:li.optional(),domain_rank_overview:li.optional(),ranked_keywords:li.optional(),relevant_pages:li.optional(),serp_competitors:li.optional()}).passthrough().optional(),serp:D.object({task_post:ci.optional(),live:D.object({advanced:ci.optional()}).passthrough().optional()}).passthrough().optional()}).passthrough(),di=D.object({login:D.string().min(1).max(320),timezone:D.string().min(1).max(100).optional(),money:oi.optional(),price:ui.optional(),backlinks_subscription_expiry_date:D.string().nullable().optional(),llm_mentions_subscription_expiry_date:D.string().nullable().optional()}).passthrough(),fi=D.object({id:D.string().min(1).max(100).optional(),status_code:D.number().int(),status_message:D.string().max(1e3),cost:D.number().finite().nonnegative().optional(),result_count:D.number().int().nonnegative().optional(),result:D.array(di).max(10).nullable()}).passthrough(),pi=D.object({version:D.string().min(1).max(100).optional(),status_code:D.number().int(),status_message:D.string().max(1e3),cost:D.number().finite().nonnegative().optional(),tasks_count:D.number().int().nonnegative(),tasks_error:D.number().int().nonnegative(),tasks:D.array(fi).max(10)}).passthrough(),mi=D.object({key:D.union([D.string().max(500),D.number().int()]),mentions:D.number().int().nonnegative().optional(),ai_search_volume:D.number().int().nonnegative().optional()}).passthrough(),hi=D.object({key:D.string().min(1).max(250).optional(),sources_domain:D.array(mi).max(100).nullish(),total:D.object({mentions:D.number().int().nonnegative().optional(),ai_search_volume:D.number().int().nonnegative().optional()}).passthrough().nullish()}).passthrough(),gi=D.object({total_count:D.number().int().nonnegative().optional(),offset:D.number().int().nonnegative().optional(),items_count:D.number().int().nonnegative().optional(),aggregated_metrics:hi.nullish(),items:D.array(hi).max(10).nullish()}).passthrough(),_i=D.object({rank:D.number().int().positive().optional(),domain:D.string().max(500).nullish(),url:D.string().max(8192).nullish(),title:D.string().max(1e4).nullish(),source_name:D.string().max(1e3).nullish()}).passthrough(),vi=D.object({platform:D.string().max(100).optional(),model_name:D.string().max(250).nullish(),location_code:D.number().int().positive().optional(),language_code:D.string().max(35).optional(),question:D.string().max(1e4).nullish(),answer:D.string().max(1e6).nullish(),sources:D.array(_i).max(250).nullish(),ai_search_volume:D.number().int().nonnegative().nullish(),first_response_at:D.string().max(100).nullish(),last_response_at:D.string().max(100).nullish(),is_web_search_based:D.boolean().nullish()}).passthrough(),yi=D.object({total_count:D.number().int().nonnegative().optional(),offset:D.number().int().nonnegative().optional(),items_count:D.number().int().nonnegative().optional(),search_after_token:D.string().max(1e5).nullish(),items:D.array(vi).max(1e3).nullish()}).passthrough();function bi(e){return D.object({version:D.string().max(100).optional(),status_code:D.number().int(),status_message:D.string().max(1e3),cost:D.number().finite().nonnegative().optional(),tasks_count:D.number().int().nonnegative(),tasks_error:D.number().int().nonnegative(),tasks:D.array(D.object({id:D.string().max(100).optional(),status_code:D.number().int(),status_message:D.string().max(1e3),cost:D.number().finite().nonnegative().optional(),result_count:D.number().int().nonnegative().optional(),result:D.array(e).max(10).nullable()}).passthrough()).max(10)}).passthrough()}const xi=bi(gi),Si=bi(yi),Ci=125e3,wi={targetMetrics:`v3/ai_optimization/llm_mentions/target_metrics/live`,multiTargetMetrics:`v3/ai_optimization/llm_mentions/multi_target_metrics/live`,searchMentions:`v3/ai_optimization/llm_mentions/search_mentions/live`};function Ti(e){if(!/^[a-z]{2}$/.test(e.languageCode))throw new N({provider:`dataforseo`,operation:e.operation,code:`configuration`,message:`AI mention research needs a two-letter language code.`});let t=e.locationName?.trim();if(e.locationCode!==void 0==!!t)throw new N({provider:`dataforseo`,operation:e.operation,code:`configuration`,message:`AI mention research needs exactly one location code or canonical location name.`});if(e.locationCode!==void 0&&(!Number.isSafeInteger(e.locationCode)||e.locationCode<=0))throw new N({provider:`dataforseo`,operation:e.operation,code:`configuration`,message:`AI mention location code must be a positive integer.`});return e.locationCode===void 0?{location_name:t}:{location_code:e.locationCode}}function Ei(e){return e.aliases.map(e=>({keyword:e,match_type:`word_match`,search_scope:[`answer`],search_filter:`include`}))}function Di(e,t){if(!e.key.trim()||e.key.length>250||!e.label.trim()||e.label.length>250||e.aliases.length<1||e.aliases.length>5||e.aliases.some(e=>!e.trim()||e.length>250))throw new N({provider:`dataforseo`,operation:t,code:`configuration`,message:`Each AI mention target needs a key, label, and 1 to 5 aliases of at most 250 characters.`})}function Oi(e){return e.tasks.reduce((e,t)=>e+(t.result??[]).reduce((e,t)=>e+(t.items?.length??+!!t.aggregated_metrics),0),0)}function ki(e){return e.tasks.reduce((e,t)=>e+(t.result??[]).reduce((e,t)=>e+(t.items?.length??0),0),0)}function Ai(e,t){let n=[e.target,...e.competitors];if(n.length<1||n.length>6)throw new N({provider:`dataforseo`,operation:`ai-mention-metrics`,code:`configuration`,message:`AI mention metrics supports 1 to 6 targets.`});for(let e of n)Di(e,`ai-mention-metrics`);if(new Set(n.map(e=>e.key)).size!==n.length)throw new N({provider:`dataforseo`,operation:`ai-mention-metrics`,code:`configuration`,message:`AI mention target keys must be unique.`});let r=Ti({...e,operation:`ai-mention-metrics`}),i=n.length>1;return{operation:`ai-mention-metrics`,capability:`ai-mentions`,endpoint:i?wi.multiTargetMetrics:wi.targetMetrics,request:{language_code:e.languageCode,...r,platform:e.platform,...i?{targets:n.map(e=>({key:e.key,target:Ei(e)})),order_by:[`total.mentions,desc`],limit:n.length}:{target:Ei(e.target)},internal_list_limit:10},schema:xi,requestedRows:n.length,price:e=>i?e.aiMentionPrices.multiTargetMetrics:e.aiMentionPrices.targetMetrics,context:e.context,ttlMs:t,timeoutMs:Ci,refresh:e.refresh,rowCount:Oi}}function ji(e,t){if(Di(e.target,`ai-mention-samples`),!Number.isSafeInteger(e.limit)||e.limit<1||e.limit>25)throw new N({provider:`dataforseo`,operation:`ai-mention-samples`,code:`configuration`,message:`AI mention sample limit must be from 1 to 25.`});let n=Ti({...e,operation:`ai-mention-samples`});return{operation:`ai-mention-samples`,capability:`ai-mentions`,endpoint:wi.searchMentions,request:{language_code:e.languageCode,...n,platform:e.platform,target:Ei(e.target),order_by:[`ai_search_volume,desc`],offset:0,limit:e.limit},schema:Si,requestedRows:e.limit,price:e=>e.aiMentionPrices.searchMentions,context:e.context,ttlMs:t,timeoutMs:Ci,refresh:e.refresh,rowCount:ki}}const Mi=D.object({id:D.string().min(1).max(100).optional(),status_code:D.number().int(),status_message:D.string().max(1e3).optional(),cost:D.number().finite().nonnegative().optional()}).passthrough(),Ni=D.object({model_name:D.string().min(1).max(200),reasoning:D.boolean().optional(),web_search_supported:D.boolean().optional(),task_post_supported:D.boolean().optional()}).passthrough(),Pi=D.object({status_code:D.number().int(),status_message:D.string().max(1e3).optional(),cost:D.number().finite().nonnegative().optional(),tasks_error:D.number().int().nonnegative(),tasks:D.array(Mi.extend({result:D.array(Ni).max(500).nullable().optional()})).max(10)}).passthrough(),Fi=D.object({title:D.string().max(4e3).nullable().optional(),url:D.string().max(16384).nullable().optional()}).passthrough(),Ii=D.object({type:D.string().max(100).nullable().optional(),text:D.string().max(1e5).nullable().optional(),annotations:D.array(Fi).max(100).nullable().optional()}).passthrough(),Li=D.object({type:D.string().max(100).nullable().optional(),sections:D.array(Ii).max(100).nullable().optional()}).passthrough(),Ri=D.object({model_name:D.string().min(1).max(200),input_tokens:D.number().int().nonnegative().nullable().optional(),output_tokens:D.number().int().nonnegative().nullable().optional(),reasoning_tokens:D.number().int().nonnegative().nullable().optional(),web_search:D.boolean().nullable().optional(),money_spent:D.number().finite().nonnegative().nullable().optional(),datetime:D.string().max(100).nullable().optional(),items:D.array(Li).max(100).nullable().optional(),fan_out_queries:D.array(D.string().min(1).max(1e3)).max(100).nullable().optional()}).passthrough(),zi=D.object({status_code:D.number().int(),status_message:D.string().max(1e3).optional(),cost:D.number().finite().nonnegative().optional(),tasks_error:D.number().int().nonnegative(),tasks:D.array(Mi.extend({result:D.array(Ri).max(10).nullable().optional()})).max(10)}).passthrough();function Bi(e){return e===void 0?null:Math.round(e*1e6)}function Vi(e){return e>=40100&&e<40200?`authentication`:e===40202?`rate-limit`:`remote-error`}function Hi(e){return e.cost===void 0?e.tasks.some(e=>e.cost===void 0)?null:e.tasks.reduce((e,t)=>e+(Bi(t.cost)??0),0):Bi(e.cost)}const Ui={chatgpt:`chat_gpt`,claude:`claude`,gemini:`gemini`,perplexity:`perplexity`};function Wi(e){return`v3/ai_optimization/${Ui[e]}/llm_responses/models`}function Gi(e){return`v3/ai_optimization/${Ui[e]}/llm_responses/live`}async function Ki(e){let t=await ri({provider:`dataforseo`,operation:`ai-prompt-models`,url:new URL(Wi(e.surface),e.baseUrl),fetch:e.fetch,maxResponseBytes:e.maxResponseBytes,timeoutMs:e.timeoutMs,retry:`safe`,schema:Pi,init:{method:`GET`,headers:{authorization:e.authorization}}}),n=t.tasks.find(e=>e.status_code!==2e4);if(t.status_code!==2e4||t.tasks_error>0||n){let r=n?.status_code??t.status_code;throw new N({provider:`dataforseo`,operation:`ai-prompt-models`,code:Vi(r),message:`DataForSEO could not return the ${e.surface} model catalog (${r}).`,retryable:r===40202})}return t}function qi(e){if(!e.prompt.trim()||e.prompt.length>500)throw new N({provider:`dataforseo`,operation:`ai-prompt-observation`,code:`configuration`,message:`Each AI prompt must contain 1 to 500 characters.`});if(!e.model.trim()||e.model.length>200)throw new N({provider:`dataforseo`,operation:`ai-prompt-observation`,code:`configuration`,message:`Choose a model name returned by the provider model catalog.`});if(!/^[A-Z]{2}$/u.test(e.countryCode))throw new N({provider:`dataforseo`,operation:`ai-prompt-observation`,code:`configuration`,message:`AI prompt observations need a two-letter country code.`});if(!Number.isSafeInteger(e.maxOutputTokens)||e.maxOutputTokens<1||e.maxOutputTokens>4096)throw new N({provider:`dataforseo`,operation:`ai-prompt-observation`,code:`configuration`,message:`AI prompt output tokens must be from 1 to 4096.`});if(e.surface===`perplexity`&&!e.webSearch)throw new N({provider:`dataforseo`,operation:`ai-prompt-observation`,code:`configuration`,message:`Perplexity prompt observations require web search because its supported models use web search by default.`})}function Ji(e,t){qi(e);let n=e.surface!==`gemini`,r=e.surface!==`perplexity`;return{operation:`ai-prompt-observation-${e.surface}`,capability:`ai-prompt-observation`,endpoint:Gi(e.surface),request:{user_prompt:e.prompt.trim().replace(/\s+/gu,` `),model_name:e.model.trim(),max_output_tokens:e.maxOutputTokens,...r?{web_search:e.webSearch}:{},...n&&e.webSearch?{web_search_country_iso_code:e.countryCode}:{}},schema:zi,requestedRows:1,price:e=>e.aiPromptObservationPrice,context:e.context,ttlMs:t,timeoutMs:125e3,refresh:e.refresh,rowCount:e=>e.tasks.reduce((e,t)=>e+(t.result?.length??0),0)}}function Yi(e){let t=e.models.find(t=>t.name===e.request.model.trim());if(!t)throw new N({provider:`dataforseo`,operation:`ai-prompt-observation`,code:`configuration`,message:`Model ${e.request.model} is not in the current ${e.request.surface} model catalog.`});if(e.request.webSearch&&!t.webSearchSupported)throw new N({provider:`dataforseo`,operation:`ai-prompt-observation`,code:`configuration`,message:`Model ${t.name} does not support web search.`});let n=e.request.surface===`claude`&&t.reasoning?1025:t.reasoning?1024:1;if(e.request.maxOutputTokens<n)throw new N({provider:`dataforseo`,operation:`ai-prompt-observation`,code:`configuration`,message:`Model ${t.name} needs at least ${n} output tokens.`})}const Xi={ahrefsApiKey:`ahrefs-api-key`,bingApiKey:`bing-api-key`,clickySiteKeys:`clicky-sitekeys`,dataForSeoCredentials:`dataforseo-credentials`,indexNowKeys:`indexnow-keys`,providerExtensionCredentials:`provider-extension-credentials`,semrushApiKey:`semrush-api-key`,serpBaseApiKey:`serpbase-api-key`},Zi=Object.freeze(Object.values(Xi)),Qi=D.object({version:D.literal(1),secrets:D.record(D.string(),D.string())}).strict();function $i(e){return`provider:${e}`}function ea(e){try{o(e,384)}catch(e){if(e.code!==`ENOENT`)throw e}}function ta(){let e=ot().providerSecretsFile;ea(e);let t=Hn(e);if(t===void 0&&!c(e))return{version:1,secrets:{}};let n=Qi.safeParse(t);if(!n.success)throw Error(`Saved provider credentials are invalid. Remove the local provider secrets file, then reconnect the affected providers.`);return n.data}function na(e){let t=ot().providerSecretsFile;if(Object.keys(e.secrets).length===0){Gn(t);return}Un(t,e,384)}function ra(e){let t=ta();e in t.secrets&&(delete t.secrets[e],na(t))}async function ia(e){let t=e.env??process.env,n=e.envVar?t[e.envVar]?.trim():void 0;if(n)return{value:n,source:`environment`};if(Qn().security.useKeychain)try{let t=await Jn(`seo`,$i(e.name));if(t)return{value:t,source:`keychain`}}catch{}let r=ta().secrets[e.name];return r?{value:r,source:`file`}:void 0}async function aa(e,t){let n=t.trim();if(!n)throw Error(`Provider secret cannot be empty.`);if(Qn().security.useKeychain)try{return await Yn(`seo`,$i(e),n),ra(e),`keychain`}catch{}let r=ta();return r.secrets[e]=n,na(r),`file`}async function oa(e){let t=await ia({name:e}),n;try{await Xn(`seo`,$i(e))}catch(e){n=e}if(n&&t?.source===`keychain`)throw Error(`The saved provider credential could not be removed from the system keychain. Unlock the keychain and try again.`,{cause:n});ra(e)}async function sa(){let e=await Promise.allSettled(Zi.map(e=>oa(e))),t=e.flatMap((e,t)=>e.status===`rejected`?[Zi[t]]:[]);if(t.length>0)throw Error(`Reset could not remove ${t.length} saved provider credential record${t.length===1?``:`s`}. Unlock the keychain or repair the local provider secrets file, then run \`seo reset --yes\` again.`,{cause:AggregateError(e.flatMap(e=>e.status===`rejected`?[e.reason]:[]))})}const ca=`SEO_DATAFORSEO_LOGIN`,la=`SEO_DATAFORSEO_PASSWORD`,ua=Xi.dataForSeoCredentials,da=D.object({version:D.literal(1),login:D.string().trim().min(1).max(320),password:D.string().min(1).max(2048)}).strict();function fa(e){return new N({provider:`dataforseo`,operation:`credentials`,code:`configuration`,message:e})}function pa(e){let t=da.safeParse({version:1,login:e.login,password:e.password});if(!t.success)throw fa(`DataForSEO credentials need both an API login and API password.`);return{login:t.data.login,password:t.data.password}}function ma(){let e=Qn();return!e.providers.dataForSeoLogin&&!e.providers.dataForSeoPassword?!1:($n({...e,providers:{...e.providers,dataForSeoLogin:void 0,dataForSeoPassword:void 0}}),!0)}function ha(e){let t=e[ca]?.trim(),n=e[la];if(!(!t&&!n)){if(!t||!n)throw fa(`Set both ${ca} and ${la}.`);return pa({login:t,password:n})}}function ga(e){let t;try{t=JSON.parse(e)}catch{throw fa("Saved DataForSEO credentials are invalid. Run `seo providers dataforseo disconnect`, then connect again.")}let n=da.safeParse(t);if(!n.success)throw fa("Saved DataForSEO credentials are invalid. Run `seo providers dataforseo disconnect`, then connect again.");return{login:n.data.login,password:n.data.password}}async function _a(e){let t=pa(e),n=await aa(ua,JSON.stringify({version:1,...t}));return ma(),n}async function va(){let e=Qn(),t=e.providers.dataForSeoLogin,n=e.providers.dataForSeoPassword;if(!t&&!n)return;if(!t||!n)throw fa("Legacy DataForSEO config contains only part of the credential pair. Run `seo providers dataforseo disconnect`, then connect again.");let r=pa({login:t,password:n}),i=await _a(r);return{...r,source:i,migrated:!0}}async function ya(e={}){let t=ha(e.env??process.env);if(t)return{...t,source:`environment`,migrated:!1};let n=await ia({name:ua});if(n){let e=ga(n.value),t=ma();return{...e,source:n.source,migrated:t}}return va()}async function ba(){await oa(ua),ma()}function xa(e){return e.tasks.reduce((e,t)=>e+(t.result??[]).reduce((e,t)=>e+(t.items?.length??0),0),0)}function Sa(e){let t=e.tasks.flatMap(e=>(e.result??[]).flatMap(e=>typeof e.total_count==`number`?[e.total_count]:[]));return t.length?t.reduce((e,t)=>e+t,0):null}function Ca(e){let t=e.tasks.flatMap(e=>(e.result??[]).flatMap(e=>e.offset_token?[e.offset_token]:[]));return t.length===1?t[0]??null:null}const wa=D.number().finite().nullable().optional(),Ta=D.union([D.number().finite(),D.string().trim().min(1)]).nullable().optional(),Ea=D.object({year:D.number().int(),month:D.number().int(),search_volume:D.number().finite().nullable().optional()}).passthrough(),Da=D.object({keyword:D.string().trim().min(1),keyword_info:D.object({search_volume:wa,cpc:wa,competition:wa,monthly_searches:D.array(Ea).max(120).nullish(),last_updated_time:D.string().trim().min(1).nullable().optional()}).nullish(),keyword_properties:D.object({keyword_difficulty:wa}).nullish(),serp_info:D.object({se_results_count:Ta}).nullish(),search_intent_info:D.object({main_intent:D.enum([`informational`,`navigational`,`commercial`,`transactional`]).nullable().optional()}).nullish()}).passthrough(),Oa=D.object({items_count:D.number().int().nonnegative().optional(),items:D.array(Da).nullable().optional()}).passthrough(),ka=D.object({id:D.string().trim().min(1).optional(),status_code:D.number().int(),status_message:D.string(),cost:D.number().finite().nonnegative().optional(),result_count:D.number().int().nonnegative().optional(),result:D.array(Oa).nullable().optional()}).passthrough(),Aa=D.object({status_code:D.number().int(),status_message:D.string(),cost:D.number().finite().nonnegative().optional(),tasks_count:D.number().int().nonnegative(),tasks_error:D.number().int().nonnegative(),tasks:D.array(ka)}).passthrough();function ja(e){if(e==null||e===``)return;let t=Number(e);return Number.isFinite(t)?t:void 0}const Ma=Da,Na=D.object({keyword_data:Da,depth:D.number().int().nonnegative().optional(),related_keywords:D.array(D.string().trim().min(1)).max(100).optional()}).passthrough(),Pa=D.union([Ma,Na]),Fa=D.object({seed_keyword:D.string().trim().min(1).optional(),seed_keywords:D.array(D.string().trim().min(1)).max(20).optional(),total_count:D.number().int().nonnegative().nullable().optional(),items_count:D.number().int().nonnegative().optional(),offset_token:D.string().max(2e4).nullable().optional(),items:D.array(Pa).max(500).nullable().optional()}).passthrough(),Ia=D.object({id:D.string().trim().min(1).optional(),status_code:D.number().int(),status_message:D.string().max(1e3),cost:D.number().finite().nonnegative().optional(),result_count:D.number().int().nonnegative().optional(),result:D.array(Fa).max(10).nullable().optional()}).passthrough(),La=D.object({status_code:D.number().int(),status_message:D.string().max(1e3),cost:D.number().finite().nonnegative().optional(),tasks_count:D.number().int().nonnegative(),tasks_error:D.number().int().nonnegative(),tasks:D.array(Ia).max(10)}).passthrough(),Ra=D.number().finite().nullable().optional(),za=D.number().finite().nonnegative().nullable().optional(),Ba=D.number().int().nonnegative().nullable().optional(),Va=D.object({pos_1:Ba,pos_2_3:Ba,pos_4_10:Ba,pos_11_20:Ba,pos_21_30:Ba,pos_31_40:Ba,pos_41_50:Ba,pos_51_60:Ba,pos_61_70:Ba,pos_71_80:Ba,pos_81_90:Ba,pos_91_100:Ba,etv:za,count:Ba,estimated_paid_traffic_cost:za,is_new:Ba,is_up:Ba,is_down:Ba,is_lost:Ba}).passthrough(),Ha=D.object({organic:Va.nullable().optional()}).passthrough(),Ua=D.object({status_code:D.number().int(),status_message:D.string().max(1e3),cost:D.number().finite().nonnegative().optional(),tasks_count:D.number().int().nonnegative(),tasks_error:D.number().int().nonnegative()}).passthrough(),Wa={id:D.string().trim().min(1).max(100).optional(),status_code:D.number().int(),status_message:D.string().max(1e3),cost:D.number().finite().nonnegative().optional(),result_count:D.number().int().nonnegative().optional()},Ga=D.object({location_code:D.number().int().positive().nullable().optional(),language_code:D.string().trim().min(1).max(35).nullable().optional(),metrics:Ha.nullable().optional()}).passthrough(),Ka=D.object({target:D.string().trim().min(1).max(2048).optional(),location_code:D.number().int().positive().nullable().optional(),language_code:D.string().trim().min(1).max(35).nullable().optional(),total_count:D.number().int().nonnegative().nullable().optional(),items_count:D.number().int().nonnegative().optional(),items:D.array(Ga).max(1e3).nullable().optional()}).passthrough(),qa=Ua.extend({tasks:D.array(D.object({...Wa,result:D.array(Ka).max(10).nullable().optional()}).passthrough()).max(10)}),Ja=D.object({type:D.string().trim().min(1).max(100).optional(),rank_group:D.number().int().positive().nullable().optional(),rank_absolute:D.number().int().positive().nullable().optional(),url:D.string().trim().min(1).max(2e4).nullable().optional(),etv:za}).passthrough(),Ya=D.object({keyword_data:Da.nullable().optional(),ranked_serp_element:D.object({serp_item:Ja.nullable().optional()}).passthrough().nullable().optional()}).passthrough(),_ee=D.object({target:D.string().trim().min(1).max(2048).optional(),total_count:D.number().int().nonnegative().nullable().optional(),items_count:D.number().int().nonnegative().optional(),items:D.array(Ya).max(1e3).nullable().optional()}).passthrough(),vee=Ua.extend({tasks:D.array(D.object({...Wa,result:D.array(_ee).max(10).nullable().optional()}).passthrough()).max(10)}),yee=D.object({page_address:D.string().trim().min(1).max(2e4).nullable().optional(),metrics:Ha.nullable().optional()}).passthrough(),bee=D.object({target:D.string().trim().min(1).max(2048).optional(),total_count:D.number().int().nonnegative().nullable().optional(),items_count:D.number().int().nonnegative().optional(),items:D.array(yee).max(1e3).nullable().optional()}).passthrough(),xee=Ua.extend({tasks:D.array(D.object({...Wa,result:D.array(bee).max(10).nullable().optional()}).passthrough()).max(10)}),See=D.object({domain:D.string().trim().min(1).max(2048).nullable().optional(),avg_position:Ra,median_position:Ra,etv:za,keywords_count:Ba,visibility:za,relevant_serp_items:Ba,keywords_positions:D.record(D.string().trim().min(1).max(80),D.array(D.number().int().positive()).max(100)).nullable().optional()}).passthrough(),Cee=D.object({seed_keywords:D.array(D.string().trim().min(1).max(80)).max(200).optional(),total_count:D.number().int().nonnegative().nullable().optional(),items_count:D.number().int().nonnegative().optional(),items:D.array(See).max(1e3).nullable().optional()}).passthrough(),wee=Ua.extend({tasks:D.array(D.object({...Wa,result:D.array(Cee).max(10).nullable().optional()}).passthrough()).max(10)}),Xa=1e3,Za=1e5,Qa=new Set([`organic`,`paid`,`featured_snippet`,`local_pack`,`ai_overview_reference`]);function $a(e,t){if(!/^[a-z]{2}$/.test(e.languageCode))throw new N({provider:`dataforseo`,operation:t,code:`configuration`,message:`DataForSEO language code must contain two lowercase letters.`});let n=e.locationName?.trim();if(e.locationCode!==void 0==!!n)throw new N({provider:`dataforseo`,operation:t,code:`configuration`,message:`DataForSEO domain research requires exactly one location code or location name.`});if(e.locationCode!==void 0&&(!Number.isSafeInteger(e.locationCode)||e.locationCode<=0))throw new N({provider:`dataforseo`,operation:t,code:`configuration`,message:`DataForSEO location code must be a positive integer.`});if(n&&n.length>500)throw new N({provider:`dataforseo`,operation:t,code:`configuration`,message:`DataForSEO location name must be at most 500 characters.`});if(e.limit!==void 0&&(!Number.isSafeInteger(e.limit)||e.limit<1||e.limit>Xa))throw new N({provider:`dataforseo`,operation:t,code:`configuration`,message:`DataForSEO ${t} limit must be from 1 to ${Xa}.`});if(e.offset!==void 0&&(!Number.isSafeInteger(e.offset)||e.offset<0||e.offset>Za))throw new N({provider:`dataforseo`,operation:t,code:`configuration`,message:`DataForSEO ${t} offset must be from 0 to ${Za}.`});return{locationName:n}}function eo(e,t,n){let r=n?Qa:new Set([...Qa].filter(e=>e!==`ai_overview_reference`));if(e.length<1||e.length>r.size||new Set(e).size!==e.length||e.some(e=>!r.has(e)))throw new N({provider:`dataforseo`,operation:t,code:`configuration`,message:`DataForSEO ${t} received unsupported result types.`})}function to(e){return e.locationCode===void 0?{location_name:e.locationName}:{location_code:e.locationCode}}function no(e){return e.tasks.reduce((e,t)=>e+(t.result??[]).reduce((e,t)=>e+(t.items?.length??0),0),0)}function Tee(e,t){let{locationName:n}=$a(e,`domain-overview`);return{operation:`domain-overview`,capability:`domain-overview`,endpoint:`v3/dataforseo_labs/google/domain_rank_overview/live`,request:{target:e.target,language_code:e.languageCode,...to({locationCode:e.locationCode,locationName:n})},schema:qa,requestedRows:1,price:e=>e.domainResearchPrices.domainOverview,context:e.context,ttlMs:t,refresh:e.refresh,rowCount:no}}function Eee(e,t){let{locationName:n}=$a(e,`ranked-keywords`);return eo(e.resultTypes,`ranked-keywords`,!0),{operation:`ranked-keywords`,capability:`ranked-keywords`,endpoint:`v3/dataforseo_labs/google/ranked_keywords/live`,request:{target:e.target,language_code:e.languageCode,...to({locationCode:e.locationCode,locationName:n}),include_subdomains:e.includeSubdomains,item_types:e.resultTypes,limit:e.limit,offset:e.offset??0,order_by:e.orderBy,...e.filters?.length?{filters:e.filters}:{}},schema:vee,requestedRows:e.limit,price:e=>e.domainResearchPrices.rankedKeywords,context:e.context,ttlMs:t,refresh:e.refresh,rowCount:no}}function Dee(e,t){let{locationName:n}=$a(e,`ranking-pages`);return{operation:`ranking-pages`,capability:`relevant-pages`,endpoint:`v3/dataforseo_labs/google/relevant_pages/live`,request:{target:e.target,language_code:e.languageCode,...to({locationCode:e.locationCode,locationName:n}),limit:e.limit,offset:e.offset??0,order_by:e.orderBy,...e.filters?.length?{filters:e.filters}:{}},schema:xee,requestedRows:e.limit,price:e=>e.domainResearchPrices.rankingPages,context:e.context,ttlMs:t,refresh:e.refresh,rowCount:no}}function Oee(e,t){let{locationName:n}=$a(e,`serp-competitors`);if(eo(e.resultTypes,`serp-competitors`,!1),e.keywords.length<1||e.keywords.length>200||e.keywords.some(e=>e.length<1||e.length>80||e.split(/\s+/u).length>10))throw new N({provider:`dataforseo`,operation:`serp-competitors`,code:`configuration`,message:`SERP competitors requires 1 to 200 keywords of at most 80 characters and 10 words.`});return{operation:`serp-competitors`,capability:`serp-competitors`,endpoint:`v3/dataforseo_labs/google/serp_competitors/live`,request:{keywords:e.keywords,language_code:e.languageCode,...to({locationCode:e.locationCode,locationName:n}),include_subdomains:e.includeSubdomains,item_types:e.resultTypes,limit:e.limit,offset:e.offset??0,order_by:e.orderBy},schema:wee,requestedRows:e.limit,price:e=>e.domainResearchPrices.serpCompetitors,context:e.context,ttlMs:t,refresh:e.refresh,rowCount:no}}const ro=D.number().int().nonnegative().nullable().optional(),io=D.number().finite().nonnegative().nullable().optional(),ao=D.string().max(2e4).nullable().optional(),oo=D.object({status_code:D.number().int(),status_message:D.string().max(1e3),cost:D.number().finite().nonnegative().optional(),tasks_count:D.number().int().nonnegative(),tasks_error:D.number().int().nonnegative()}).passthrough(),so={id:D.string().trim().min(1).max(100).optional(),status_code:D.number().int(),status_message:D.string().max(1e3),cost:D.number().finite().nonnegative().optional(),result_count:D.number().int().nonnegative().optional()},kee=D.object({target:D.string().trim().min(1).max(2048).optional(),rank:io,backlinks:ro,referring_pages:ro,referring_domains:ro,broken_backlinks:ro,broken_pages:ro,backlinks_spam_score:io,info:D.object({target_spam_score:io}).passthrough().nullable().optional()}).passthrough(),Aee=oo.extend({tasks:D.array(D.object({...so,result:D.array(kee).max(10).nullable().optional()}).passthrough()).max(10)}),jee=D.object({domain_from:ao,url_from:ao,url_to:ao,anchor:ao,item_type:D.string().max(100).nullable().optional(),dofollow:D.boolean().nullable().optional(),rank:io,domain_from_rank:io,page_from_rank:io,backlink_spam_score:io,backlinks_spam_score:io,first_seen:D.string().max(100).nullable().optional(),last_visited:D.string().max(100).nullable().optional(),lost_date:D.string().max(100).nullable().optional(),is_lost:D.boolean().nullable().optional(),is_indirect_link:D.boolean().nullable().optional(),links_count:ro,group_count:ro,rel_attributes:D.array(D.string().trim().min(1).max(100)).max(100).nullable().optional(),attributes:D.array(D.string().trim().min(1).max(100)).max(100).nullable().optional()}).passthrough(),Mee=D.object({target:D.string().trim().min(1).max(2048).optional(),total_count:ro,items_count:ro,search_after_token:D.string().max(2e4).nullable().optional(),items:D.array(jee).max(1e3).nullable().optional()}).passthrough(),Nee=oo.extend({tasks:D.array(D.object({...so,result:D.array(Mee).max(10).nullable().optional()}).passthrough()).max(10)}),Pee=D.object({domain:D.string().trim().min(1).max(2048).nullable().optional(),backlinks:ro,referring_pages:ro,broken_backlinks:ro,broken_pages:ro,rank:io,backlinks_spam_score:io,first_seen:D.string().max(100).nullable().optional()}).passthrough(),Fee=D.object({target:D.string().trim().min(1).max(2048).optional(),total_count:ro,items_count:ro,items:D.array(Pee).max(1e3).nullable().optional()}).passthrough(),Iee=oo.extend({tasks:D.array(D.object({...so,result:D.array(Fee).max(10).nullable().optional()}).passthrough()).max(10)}),co=1e3,lo=2e4,uo={summary:`v3/backlinks/summary/live`,backlinks:`v3/backlinks/backlinks/live`,referringDomains:`v3/backlinks/referring_domains/live`};function fo(e){return e.tasks.reduce((e,t)=>e+(t.result??[]).reduce((e,t)=>e+(t.items?.length??0),0),0)}function Lee(e){return e.tasks.reduce((e,t)=>e+(t.result?.length??0),0)}function po(e){return{target:e.target,include_subdomains:e.scope===`domain`&&e.includeSubdomains,include_indirect_links:!0,exclude_internal_backlinks:!0,backlinks_status_type:`live`,rank_scale:`one_hundred`}}function mo(e){if(!Number.isSafeInteger(e.limit)||e.limit<1||e.limit>1e3)throw new N({provider:`dataforseo`,operation:e.operation,code:`configuration`,message:`Link row limit must be from 1 to ${co}.`});if(!Number.isSafeInteger(e.offset)||e.offset<0||e.offset>2e4)throw new N({provider:`dataforseo`,operation:e.operation,code:`configuration`,message:`Link row offset must be from 0 to ${lo}.`})}function Ree(e,t){return{operation:`link-summary`,capability:`link-summary`,endpoint:uo.summary,request:po(e),schema:Aee,requestedRows:1,price:e=>e.linkPrices.summary,context:e.context,ttlMs:t,refresh:e.refresh,rowCount:Lee}}function zee(e,t){if(mo({limit:e.limit,offset:e.offset,operation:`backlinks`}),e.orderBy.length<1||e.orderBy.length>3)throw new N({provider:`dataforseo`,operation:`backlinks`,code:`configuration`,message:`Backlink rows need from 1 to 3 sort rules.`});return{operation:`backlinks`,capability:`backlinks`,endpoint:uo.backlinks,request:{...po(e),mode:e.mode,backlinks_status_type:e.status,limit:e.limit,offset:e.offset,order_by:e.orderBy},schema:Nee,requestedRows:e.limit,price:e=>e.linkPrices.backlinks,context:e.context,ttlMs:t,refresh:e.refresh,rowCount:fo}}function Bee(e,t){if(mo({limit:e.limit,offset:e.offset,operation:`referring-domains`}),e.orderBy.length<1||e.orderBy.length>3)throw new N({provider:`dataforseo`,operation:`referring-domains`,code:`configuration`,message:`Referring-domain rows need from 1 to 3 sort rules.`});return{operation:`referring-domains`,capability:`referring-domains`,endpoint:uo.referringDomains,request:{...po(e),limit:e.limit,offset:e.offset,order_by:e.orderBy},schema:Iee,requestedRows:e.limit,price:e=>e.linkPrices.referringDomains,context:e.context,ttlMs:t,refresh:e.refresh,rowCount:fo}}function ho(e){return e===void 0?null:Math.round(e*1e6)}function Vee(e){return[...new Set(e.tasks.flatMap(e=>e.id?[e.id]:[]))].sort((e,t)=>e<t?-1:+(e>t))}function Hee(e){return e.cost===void 0?e.tasks.some(e=>e.cost===void 0)?null:e.tasks.reduce((e,t)=>e+(ho(t.cost)??0),0):ho(e.cost)}function Uee(e){return e>=40100&&e<40200?`authentication`:e===40202?`rate-limit`:`remote-error`}async function Wee(e){let t={provider:`dataforseo`,credentialScope:e.credentialScope,operation:e.operation,request:e.request},n=e.refresh?null:Hr(t,e.schema,{database:e.database,now:e.now().getTime()});if(n)return{response:n.data,observedAt:n.storedAt,returnedRows:n.rowCount??e.rowCount(n.data),cache:{status:`hit`,storedAt:n.storedAt,expiresAt:n.expiresAt},cost:{currency:`USD`,estimatedMicros:0,actualMicros:0,taskIds:n.taskIds},spendNotice:null,warnings:[]};let r=await e.price();if(r.perRequestMicros===null||r.perResultMicros===null)throw new N({provider:`dataforseo`,operation:e.operation,code:`invalid-response`,message:`DataForSEO account pricing for ${e.operation} is unavailable, so the paid request was not started.`});let i=r.perRequestMicros*(e.estimatedRequestUnits??1)+r.perResultMicros*e.requestedRows,a=Pr({provider:`dataforseo`,capability:e.capability,endpoint:e.endpoint,projectId:e.context.projectId,reportId:e.context.reportId,reportRunId:e.context.reportRunId,requestedRows:e.requestedRows,estimatedCostMicros:i},{database:e.database,limits:e.spendLimits,now:e.now().getTime()}),o;try{o=await ri({provider:`dataforseo`,operation:e.operation,url:new URL(e.endpoint,e.baseUrl),fetch:e.fetch,maxResponseBytes:e.maxResponseBytes,timeoutMs:e.timeoutMs,retry:`never`,schema:e.schema,init:{method:`POST`,headers:{authorization:`Basic ${Buffer.from(`${e.credentials.login}:${e.credentials.password}`).toString(`base64`)}`,"content-type":`application/json`},body:JSON.stringify([e.request])}})}catch(t){throw Fr(a.id,{provider:`dataforseo`,state:`failed`,actualCostMicros:null,returnedRows:null,taskIds:[]},{database:e.database,limits:e.spendLimits,now:e.now().getTime()}),t}let s=Hee(o),c=e.rowCount(o),l=Vee(o),u=o.tasks.filter(e=>e.status_code!==2e4),d=o.status_code!==2e4||o.tasks_error>0||u.length>0,f=d?u.length<o.tasks.length?`partial`:`failed`:`succeeded`,p=Fr(a.id,{provider:`dataforseo`,state:f,actualCostMicros:s,returnedRows:c,taskIds:l},{database:e.database,limits:e.spendLimits,now:e.now().getTime()});if(d){let t=u[0],n=t?.status_code??o.status_code,r=t?.status_message?.trim()||o.status_message?.trim();throw new N({provider:`dataforseo`,operation:e.operation,code:Uee(n),message:`DataForSEO could not complete ${e.operation} (${n}${r?`: ${r}`:``}).`,retryable:n===40202})}let m=[];try{Ur(t,{data:o,ttlMs:e.ttlMs,rowCount:c,sourceCostMicros:s,taskIds:l},{database:e.database,now:e.now().getTime()})}catch{m.push({code:`cache-write-failed`,message:`The provider result is valid, but it could not be saved to the local cache.`})}return{response:o,observedAt:e.now().toISOString(),returnedRows:c,cache:{status:e.refresh?`bypass`:`miss`,storedAt:null,expiresAt:null},cost:{currency:`USD`,estimatedMicros:i,actualMicros:s,taskIds:l},spendNotice:p,warnings:m}}function go(e){let t=e.keyword.trim().replace(/\s+/gu,` `);if(t.length<1||t.length>80||t.split(/\s+/u).length>10)throw new N({provider:`dataforseo`,operation:`serp-snapshot`,code:`configuration`,message:`SERP snapshots require a keyword of at most 80 characters and 10 words.`});if(/(?:^|\s)(?:allinanchor|allintext|allintitle|allinurl|cache|define|definition|filetype|id|inanchor|info|intext|intitle|inurl|link|site):/iu.test(t))throw new N({provider:`dataforseo`,operation:`serp-snapshot`,code:`configuration`,message:`SERP snapshot keywords cannot contain search operators with multiplied provider pricing.`});if(!Number.isSafeInteger(e.depth)||e.depth<1||e.depth>100)throw new N({provider:`dataforseo`,operation:`serp-snapshot`,code:`configuration`,message:`SERP depth must be from 1 to 100.`});if(!/^[a-z]{2}$/.test(e.languageCode))throw new N({provider:`dataforseo`,operation:`serp-snapshot`,code:`configuration`,message:`DataForSEO language code must contain two lowercase letters.`});let n=e.locationName?.trim();if(e.locationCode!==void 0==!!n)throw new N({provider:`dataforseo`,operation:`serp-snapshot`,code:`configuration`,message:`DataForSEO SERP snapshots require exactly one location code or location name.`});return{keyword:t,locationName:n}}const Gee=D.object({type:D.string().trim().min(1).max(100),rank_group:D.number().int().positive().nullable().optional(),rank_absolute:D.number().int().positive().nullable().optional(),page:D.number().int().positive().nullable().optional(),domain:D.string().trim().max(500).nullable().optional(),url:D.string().trim().max(1e4).nullable().optional(),title:D.string().max(1e4).nullable().optional(),description:D.string().max(5e4).nullable().optional(),is_featured_snippet:D.boolean().nullable().optional(),cid:D.unknown().optional(),phone:D.unknown().optional(),is_paid:D.unknown().optional(),rating:D.unknown().optional()}).passthrough(),_o=D.object({keyword:D.string().trim().min(1).max(700),se_domain:D.string().trim().min(1).max(500).nullable().optional(),check_url:D.string().trim().min(1).max(1e4).nullable().optional(),datetime:D.string().trim().min(1).max(100),spell:D.object({keyword:D.string().trim().min(1).max(700).optional(),type:D.string().trim().min(1).max(100).optional()}).passthrough().nullable().optional(),item_types:D.array(D.string().trim().min(1).max(100)).max(200).optional(),se_results_count:D.number().finite().nonnegative().nullable().optional(),pages_count:D.number().int().nonnegative().nullable().optional(),items_count:D.number().int().nonnegative().optional(),items:D.array(Gee).max(500).optional()}).passthrough(),vo=D.object({id:D.string().trim().min(1).max(100).optional(),status_code:D.number().int(),status_message:D.string().max(1e3),cost:D.number().finite().nonnegative().optional(),result_count:D.number().int().nonnegative().optional(),result:D.array(_o).max(10).nullable().optional()}).passthrough(),yo=D.object({status_code:D.number().int(),status_message:D.string().max(1e3),cost:D.number().finite().nonnegative().optional(),tasks_count:D.number().int().nonnegative(),tasks_error:D.number().int().nonnegative(),tasks:D.array(vo).max(10)}).passthrough(),Kee=D.object({id:D.string().trim().min(1).max(100).optional(),status_code:D.number().int(),status_message:D.string().max(1e3),cost:D.number().finite().nonnegative().optional(),result_count:D.number().int().nonnegative().optional(),data:D.object({tag:D.string().trim().min(1).max(255).optional()}).passthrough().optional()}).passthrough(),bo=D.object({status_code:D.number().int(),status_message:D.string().max(1e3),cost:D.number().finite().nonnegative().optional(),tasks_count:D.number().int().nonnegative(),tasks_error:D.number().int().nonnegative(),tasks:D.array(Kee).max(100)}).passthrough(),qee=D.object({id:D.string().trim().min(1).max(100),tag:D.string().trim().max(255).nullable().optional(),se:D.string().trim().max(100).optional(),se_type:D.string().trim().max(100).optional(),endpoint_advanced:D.string().trim().max(2048).nullable().optional()}).passthrough(),xo=D.object({id:D.string().trim().min(1).max(100).optional(),status_code:D.number().int(),status_message:D.string().max(1e3),cost:D.number().finite().nonnegative().optional(),result:D.array(qee).max(1e3).nullable().optional()}).passthrough(),So=D.object({status_code:D.number().int(),status_message:D.string().max(1e3),cost:D.number().finite().nonnegative().optional(),tasks_count:D.number().int().nonnegative(),tasks_error:D.number().int().nonnegative(),tasks:D.array(xo).max(10)}).passthrough(),Co=`v3/serp/google/organic/task_post`;function wo(e){return e.tasks.reduce((e,t)=>e+(t.result??[]).reduce((e,t)=>e+(t.items?.length??0),0),0)}function To(e){if(!/^[a-zA-Z0-9-]{1,100}$/u.test(e))throw new N({provider:`dataforseo`,operation:`serp-task-get`,code:`configuration`,message:`Use a valid queued SERP task id.`})}async function Eo(e,t){let{price:n,requests:r}=e;if(n.perRequestMicros===null||n.perResultMicros===null)throw new N({provider:`dataforseo`,operation:`serp-task-post`,code:`invalid-response`,message:`DataForSEO Standard SERP pricing is unavailable, so queued tasks were not posted.`});let i=r.reduce((e,t)=>e+Math.ceil(t.depth/10),0),a=n.perRequestMicros*i+n.perResultMicros*r.length,o=Pr({provider:`dataforseo`,capability:`serp-snapshot`,endpoint:Co,projectId:e.context.projectId,reportId:e.context.reportId,reportRunId:e.context.reportRunId,requestedRows:r.length,estimatedCostMicros:a},{database:t.database,limits:t.spendLimits,now:t.now().getTime()}),s;try{s=await ri({provider:`dataforseo`,operation:`serp-task-post`,url:new URL(Co,t.baseUrl),fetch:t.fetch,maxResponseBytes:t.maxResponseBytes,timeoutMs:t.timeoutMs,retry:`never`,schema:bo,init:{method:`POST`,headers:{authorization:t.authorization,"content-type":`application/json`},body:JSON.stringify(r)}})}catch(e){throw Fr(o.id,{provider:`dataforseo`,state:`failed`,actualCostMicros:null,returnedRows:null,taskIds:[]},{database:t.database,limits:t.spendLimits,now:t.now().getTime()}),e}let c=s.tasks.flatMap(e=>e.id&&e.data?.tag?[{providerTaskId:e.id,tag:e.data.tag}]:[]),l=c.map(e=>e.providerTaskId),u=new Set(r.map(e=>e.tag)),d=new Set(c.map(e=>e.tag)),f=u.size===r.length&&d.size===u.size&&[...u].every(e=>d.has(e)),p=s.tasks.filter(e=>e.status_code!==20100||!e.id||!e.data?.tag),m=Hi(s),h=p.length===0&&s.tasks_error===0?`succeeded`:p.length<s.tasks.length?`partial`:`failed`,g=Fr(o.id,{provider:`dataforseo`,state:h,actualCostMicros:m,returnedRows:l.length,taskIds:l},{database:t.database,limits:t.spendLimits,now:t.now().getTime()});if(s.status_code!==2e4||p.length>0||l.length!==r.length||!f){let e=p[0]?.status_code??s.status_code;throw new N({provider:`dataforseo`,operation:`serp-task-post`,code:Vi(e),message:`DataForSEO accepted ${l.length} of ${r.length} queued SERP tasks (${e}).`})}return{taskIds:l,taskReceipts:c,estimatedCostMicros:a,actualCostMicros:m,spendNotice:g,warnings:[],observedAt:t.now().toISOString()}}async function Do(e){let t=await ri({provider:`dataforseo`,operation:`serp-tasks-ready`,url:new URL(`v3/serp/google/organic/tasks_ready`,e.baseUrl),fetch:e.fetch,maxResponseBytes:e.maxResponseBytes,timeoutMs:e.timeoutMs,retry:`safe`,schema:So,init:{method:`GET`,headers:{authorization:e.authorization}}}),n=t.tasks.find(e=>e.status_code!==2e4);if(t.status_code!==2e4||n){let e=n?.status_code??t.status_code;throw new N({provider:`dataforseo`,operation:`serp-tasks-ready`,code:Vi(e),message:`DataForSEO could not list ready SERP tasks (${e}).`,retryable:e===40202})}return t.tasks.flatMap(e=>(e.result??[]).map(e=>({providerTaskId:e.id,tag:e.tag??null})))}async function Oo(e,t){let n=await ri({provider:`dataforseo`,operation:`serp-task-get`,url:new URL(`v3/serp/google/organic/task_get/advanced/${e}`,t.baseUrl),fetch:t.fetch,maxResponseBytes:t.maxResponseBytes,timeoutMs:t.timeoutMs,retry:`safe`,schema:yo,init:{method:`GET`,headers:{authorization:t.authorization}}}),r=n.tasks.find(e=>e.status_code!==2e4);if(n.status_code!==2e4||r){let t=r?.status_code??n.status_code;throw new N({provider:`dataforseo`,operation:`serp-task-get`,code:Vi(t),message:`DataForSEO could not collect queued SERP task ${e} (${t}).`,retryable:t===40202})}return{response:n,observedAt:t.now().toISOString(),returnedRows:wo(n),cache:{status:`bypass`,storedAt:null,expiresAt:null},cost:{currency:`USD`,estimatedMicros:0,actualMicros:Hi(n)??0,taskIds:[e]},spendNotice:null,warnings:[]}}const ko=`v3/dataforseo_labs/google/keyword_overview/live`,Jee={ideas:`v3/dataforseo_labs/google/keyword_ideas/live`,related:`v3/dataforseo_labs/google/related_keywords/live`,suggestions:`v3/dataforseo_labs/google/keyword_suggestions/live`};function Yee(e){return[...new Set(e.tasks.flatMap(e=>e.id?[e.id]:[]))].sort((e,t)=>e<t?-1:+(e>t))}function Ao(e){return e.tasks.reduce((e,t)=>e+(t.result??[]).reduce((e,t)=>e+(t.items?.length??0),0),0)}var jo=class{fetch;credentials;baseUrl;timeoutMs;maxResponseBytes;now;database;keywordOverviewTtlMs;keywordDiscoveryTtlMs;serpTtlMs;accountPricingTtlMs;domainResearchTtlMs;linkTtlMs;aiMentionTtlMs;aiPromptObservationTtlMs;spendLimits;accountPricing;accountPricingRequest;constructor(e={}){this.fetch=e.fetch??ae,this.credentials=e.credentials??ya,this.baseUrl=e.baseUrl??`https://api.dataforseo.com/`,this.timeoutMs=e.timeoutMs??15e3,this.maxResponseBytes=e.maxResponseBytes??5242880,this.now=e.now??(()=>new Date),this.database=e.database,this.keywordOverviewTtlMs=e.keywordOverviewTtlMs??6048e5,this.keywordDiscoveryTtlMs=e.keywordDiscoveryTtlMs??6048e5,this.serpTtlMs=e.serpTtlMs??864e5,this.accountPricingTtlMs=e.accountPricingTtlMs??3e5,this.domainResearchTtlMs=e.domainResearchTtlMs??6048e5,this.linkTtlMs=e.linkTtlMs??864e5,this.aiMentionTtlMs=e.aiMentionTtlMs??6048e5,this.aiPromptObservationTtlMs=e.aiPromptObservationTtlMs??6048e5,this.spendLimits=e.spendLimits}async getCredentials(e){let t=await this.credentials();if(!t)throw new N({provider:`dataforseo`,operation:e,code:`configuration`,message:"DataForSEO is not connected. Run `seo providers dataforseo connect`, or set SEO_DATAFORSEO_LOGIN and SEO_DATAFORSEO_PASSWORD."});return t}authorization(e){return`Basic ${Buffer.from(`${e.login}:${e.password}`).toString(`base64`)}`}serpTaskTransport(e){return{authorization:this.authorization(e),baseUrl:this.baseUrl,fetch:this.fetch,maxResponseBytes:this.maxResponseBytes,timeoutMs:this.timeoutMs,now:this.now,database:this.database,spendLimits:this.spendLimits}}async userDataForCredentials(e){let t;try{t=await ri({provider:`dataforseo`,operation:`account-status`,url:new URL(`v3/appendix/user_data`,this.baseUrl),fetch:this.fetch,maxResponseBytes:this.maxResponseBytes,timeoutMs:this.timeoutMs,retry:`safe`,schema:pi,init:{method:`GET`,headers:{authorization:this.authorization(e)}}})}catch(e){throw e instanceof N&&e.code===`authentication`?new N({provider:`dataforseo`,operation:`account-status`,code:`authentication`,status:e.status??void 0,message:"DataForSEO rejected the API login or API password. Run `seo providers dataforseo connect` again, or check SEO_DATAFORSEO_LOGIN and SEO_DATAFORSEO_PASSWORD.",cause:e}):e}let n=t.tasks.find(e=>e.status_code!==2e4);if(t.status_code!==2e4||t.tasks_error>0||n){let e=n?.status_code??t.status_code;throw new N({provider:`dataforseo`,operation:`account-status`,code:Vi(e),message:`DataForSEO could not return account status (${e}).`,retryable:e===40202})}let r=t.tasks[0]?.result?.[0];if(!r)throw new N({provider:`dataforseo`,operation:`account-status`,code:`invalid-response`,message:`DataForSEO returned account status without an account row.`});return{provider:`dataforseo`,login:r.login,timezone:r.timezone??null,balanceMicros:Bi(r.money?.balance),depositedMicros:Bi(r.money?.total),accountDailySpendMicros:Bi(r.money?.statistics?.day?.total),accountDailySpendPeriod:r.money?.statistics?.day?.value??null,accountDailyLimitMicros:Bi(r.money?.limits?.day?.total),...ai(r),backlinksSubscriptionExpiresAt:r.backlinks_subscription_expiry_date??null,aiMentionsSubscriptionExpiresAt:r.llm_mentions_subscription_expiry_date??null,apiVersion:t.version??null,requestCostMicros:Bi(t.cost)??0,taskIds:t.tasks.flatMap(e=>e.id?[e.id]:[]),observedAt:this.now().toISOString()}}async userData(){return this.userDataForCredentials(await this.getCredentials(`account-status`))}async pricingForCredentials(e,t){let n=this.now().getTime();if(this.accountPricing?.credentialScope===t&&this.accountPricing.expiresAt>n)return this.accountPricing.snapshot;if(this.accountPricingRequest?.credentialScope===t)return this.accountPricingRequest.promise;let r=this.userDataForCredentials(e).then(e=>(this.accountPricing={credentialScope:t,expiresAt:this.now().getTime()+this.accountPricingTtlMs,snapshot:e},e));this.accountPricingRequest={credentialScope:t,promise:r};try{return await r}finally{this.accountPricingRequest?.promise===r&&(this.accountPricingRequest=void 0)}}async paidPost(e){let t=await this.getCredentials(e.operation),n=zr(`dataforseo`,t.login);return Wee({...e,credentials:t,credentialScope:n,price:async()=>e.price(await this.pricingForCredentials(t,n)),baseUrl:this.baseUrl,fetch:this.fetch,maxResponseBytes:this.maxResponseBytes,timeoutMs:e.timeoutMs??this.timeoutMs,now:this.now,database:this.database,spendLimits:this.spendLimits})}async domainOverview(e){return this.paidPost(Tee(e,this.domainResearchTtlMs))}async aiMentionMetrics(e){return this.paidPost(Ai(e,this.aiMentionTtlMs))}async aiMentionSearch(e){return this.paidPost(ji(e,this.aiMentionTtlMs))}async aiPromptModels(e){let t=await this.getCredentials(`ai-prompt-models`);return Ki({surface:e,authorization:this.authorization(t),baseUrl:this.baseUrl,fetch:this.fetch,maxResponseBytes:this.maxResponseBytes,timeoutMs:this.timeoutMs})}async aiPromptObservation(e){return this.paidPost(Ji(e,this.aiPromptObservationTtlMs))}async rankedKeywords(e){return this.paidPost(Eee(e,this.domainResearchTtlMs))}async rankingPages(e){return this.paidPost(Dee(e,this.domainResearchTtlMs))}async serpCompetitors(e){return this.paidPost(Oee(e,this.domainResearchTtlMs))}async linkSummary(e){return this.paidPost(Ree(e,this.linkTtlMs))}async backlinks(e){return this.paidPost(zee(e,this.linkTtlMs))}async referringDomains(e){return this.paidPost(Bee(e,this.linkTtlMs))}async keywordOverview(e){let t=e.keywords.map(e=>e.trim());if(t.length<1||t.length>100||t.some(e=>e.length<1||e.length>80||e.split(/\s+/u).length>10))throw new N({provider:`dataforseo`,operation:`keyword-metrics`,code:`configuration`,message:`Keyword metrics requires 1 to 100 non-empty keywords of at most 80 characters and 10 words.`});if(!/^[a-z]{2}$/.test(e.languageCode))throw new N({provider:`dataforseo`,operation:`keyword-metrics`,code:`configuration`,message:`DataForSEO language code must contain two lowercase letters.`});let n=e.locationCode!==void 0,r=e.locationName?.trim();if(n===!!r)throw new N({provider:`dataforseo`,operation:`keyword-metrics`,code:`configuration`,message:`DataForSEO keyword metrics requires exactly one location code or location name.`});if(n&&(!Number.isSafeInteger(e.locationCode)||(e.locationCode??0)<=0))throw new N({provider:`dataforseo`,operation:`keyword-metrics`,code:`configuration`,message:`DataForSEO location code must be a positive integer.`});if(r&&r.length>500)throw new N({provider:`dataforseo`,operation:`keyword-metrics`,code:`configuration`,message:`DataForSEO location name must be at most 500 characters.`});let i=await this.getCredentials(`keyword-metrics`),a=zr(`dataforseo`,i.login),o={keywords:t,language_code:e.languageCode,...e.locationCode===void 0?{location_name:r}:{location_code:e.locationCode},...e.includeSerpInfo?{include_serp_info:!0}:{},...e.includeClickstreamData?{include_clickstream_data:!0}:{}},s={provider:`dataforseo`,credentialScope:a,operation:`keyword-metrics`,request:o},c=e.refresh?null:Hr(s,Aa,{database:this.database,now:this.now().getTime()});if(c)return{response:c.data,observedAt:c.storedAt,returnedRows:c.rowCount??Ao(c.data),cache:{status:`hit`,storedAt:c.storedAt,expiresAt:c.expiresAt},cost:{currency:`USD`,estimatedMicros:0,actualMicros:0,taskIds:c.taskIds},spendNotice:null,warnings:[]};let{perRequestMicros:l,perResultMicros:u}=(await this.pricingForCredentials(i,a)).keywordOverviewPrice;if(l===null||u===null)throw new N({provider:`dataforseo`,operation:`keyword-metrics`,code:`invalid-response`,message:`DataForSEO account pricing is unavailable, so the paid request was not started.`});let d=e.includeClickstreamData?2:1,f=(l+u*t.length)*d,p=this.now().getTime(),m=Pr({provider:`dataforseo`,capability:`keyword-metrics`,endpoint:ko,projectId:e.projectId,reportId:e.reportId,reportRunId:e.reportRunId,requestedRows:t.length,estimatedCostMicros:f},{database:this.database,limits:this.spendLimits,now:p}),h;try{h=await ri({provider:`dataforseo`,operation:`keyword-metrics`,url:new URL(ko,this.baseUrl),fetch:this.fetch,maxResponseBytes:this.maxResponseBytes,timeoutMs:this.timeoutMs,retry:`never`,schema:Aa,init:{method:`POST`,headers:{authorization:this.authorization(i),"content-type":`application/json`},body:JSON.stringify([o])}})}catch(e){throw Fr(m.id,{provider:`dataforseo`,state:`failed`,actualCostMicros:null,returnedRows:null,taskIds:[]},{database:this.database,limits:this.spendLimits,now:this.now().getTime()}),e}let g=Hi(h),_=Ao(h),v=Yee(h),y=h.tasks.filter(e=>e.status_code!==2e4),b=h.status_code!==2e4||h.tasks_error>0||y.length>0,x=b?y.length<h.tasks.length?`partial`:`failed`:`succeeded`,S=Fr(m.id,{provider:`dataforseo`,state:x,actualCostMicros:g,returnedRows:_,taskIds:v},{database:this.database,limits:this.spendLimits,now:this.now().getTime()});if(b){let e=y[0]?.status_code??h.status_code;throw new N({provider:`dataforseo`,operation:`keyword-metrics`,code:Vi(e),message:`DataForSEO could not complete the keyword metrics task (${e}).`,retryable:e===40202})}let C=[];try{Ur(s,{data:h,ttlMs:this.keywordOverviewTtlMs,rowCount:_,sourceCostMicros:g,taskIds:v},{database:this.database,now:this.now().getTime()})}catch{C.push({code:`cache-write-failed`,message:`The provider result is valid, but it could not be saved to the local cache.`})}return{response:h,observedAt:this.now().toISOString(),returnedRows:_,cache:{status:e.refresh?`bypass`:`miss`,storedAt:null,expiresAt:null},cost:{currency:`USD`,estimatedMicros:f,actualMicros:g,taskIds:v},spendNotice:S,warnings:C}}async keywordDiscovery(e){let t=e.seeds.map(e=>e.trim());if(t.length<1||t.length>5||t.some(e=>e.length<1||e.length>80||e.split(/\s+/u).length>10))throw new N({provider:`dataforseo`,operation:`keyword-discovery`,code:`configuration`,message:`Keyword discovery requires 1 to 5 seeds of at most 80 characters and 10 words.`});if(e.source!==`ideas`&&(t.length!==1||t[0]===void 0))throw new N({provider:`dataforseo`,operation:`keyword-discovery`,code:`configuration`,message:`${e.source} discovery requires exactly one seed per provider request.`});if(!Number.isSafeInteger(e.limit)||e.limit<1||e.limit>100)throw new N({provider:`dataforseo`,operation:`keyword-discovery`,code:`configuration`,message:`Keyword discovery limit must be from 1 to 100.`});if(!/^[a-z]{2}$/.test(e.languageCode))throw new N({provider:`dataforseo`,operation:`keyword-discovery`,code:`configuration`,message:`DataForSEO language code must contain two lowercase letters.`});let n=e.locationName?.trim();if(e.locationCode!==void 0==!!n)throw new N({provider:`dataforseo`,operation:`keyword-discovery`,code:`configuration`,message:`DataForSEO keyword discovery requires exactly one location code or location name.`});let r=e.locationCode===void 0?{location_name:n}:{location_code:e.locationCode},i=e.source===`ideas`?{keywords:t,language_code:e.languageCode,...r,include_serp_info:!0,limit:e.limit}:{keyword:t[0],language_code:e.languageCode,...r,include_serp_info:!0,include_seed_keyword:!1,...e.source===`related`?{depth:3}:{},limit:e.limit,order_by:[`${e.source===`related`?`keyword_data.`:``}keyword_info.search_volume,desc`]},a=await this.paidPost({operation:`keyword-discovery-${e.source}`,capability:`keyword-discovery`,endpoint:Jee[e.source],request:i,schema:La,requestedRows:e.limit,price:t=>t.keywordDiscoveryPrices[e.source],context:e.context,ttlMs:this.keywordDiscoveryTtlMs,refresh:e.refresh,rowCount:xa});return{...a,providerTotalRows:Sa(a.response),nextCursor:Ca(a.response)}}async serpLive(e){let{keyword:t,locationName:n}=go(e),r={keyword:t,language_code:e.languageCode,...e.locationCode===void 0?{location_name:n}:{location_code:e.locationCode},device:e.device,depth:e.depth,remove_from_url:[`srsltid`]};return this.paidPost({operation:`serp-snapshot`,capability:`serp-snapshot`,endpoint:`v3/serp/google/organic/live/advanced`,request:r,schema:yo,requestedRows:e.depth,estimatedRequestUnits:Math.ceil(e.depth/10),price:e=>e.serpLiveAdvancedPrice,context:e.context,ttlMs:this.serpTtlMs,refresh:e.refresh,rowCount:wo})}async serpTaskPost(e){if(e.tasks.length<1||e.tasks.length>100)throw new N({provider:`dataforseo`,operation:`serp-task-post`,code:`configuration`,message:`A queued SERP post requires 1 to 100 tasks.`});let t=e.tasks.map(e=>{let t=go(e);return{keyword:t.keyword,language_code:e.languageCode,...e.locationCode===void 0?{location_name:t.locationName}:{location_code:e.locationCode},device:e.device,depth:e.depth,tag:e.tag,priority:1,remove_from_url:[`srsltid`]}}),n=await this.getCredentials(`serp-task-post`),r=zr(`dataforseo`,n.login),i=(await this.pricingForCredentials(n,r)).serpTaskPostPrice;return Eo({requests:t,context:e.context,price:i},{authorization:this.authorization(n),baseUrl:this.baseUrl,fetch:this.fetch,maxResponseBytes:this.maxResponseBytes,timeoutMs:this.timeoutMs,now:this.now,database:this.database,spendLimits:this.spendLimits})}async serpTasksReady(){let e=await this.getCredentials(`serp-tasks-ready`);return Do(this.serpTaskTransport(e))}async serpTaskGet(e){To(e);let t=await this.getCredentials(`serp-task-get`);return Oo(e,this.serpTaskTransport(t))}};const Mo={single:`v3/ai_optimization/llm_mentions/target_metrics/live`,multi:`v3/ai_optimization/llm_mentions/multi_target_metrics/live`},No=2e3;function Po(e,t){return e<t?-1:+(e>t)}function Fo(e){return e===void 0?{state:`missing`,value:null,reason:`The provider did not return this metric.`}:{state:`observed`,value:e}}function Io(e){if(!e)return{state:`missing`,value:null,reason:`The provider did not return an observation time.`};let t=Date.parse(e);return Number.isFinite(t)?{state:`observed`,value:new Date(t).toISOString()}:{state:`invalid`,value:null,reason:`The provider returned an invalid observation time.`}}function Lo(e){return e==null?{state:`missing`,value:null,reason:`The provider did not return this flag.`}:{state:`observed`,value:e}}function Ro(e){let t=e.trim().toLowerCase();if(!t)return null;try{let e=new URL(t.includes(`://`)?t:`https://${t}`).hostname.replace(/^www\./u,``).replace(/\.$/u,``);return e.includes(`.`)&&e.length<=253?e:null}catch{return null}}function zo(e){if(!e)return null;try{let t=new URL(e);return[`http:`,`https:`].includes(t.protocol)?(t.username=``,t.password=``,t.toString()):null}catch{return null}}function Bo(e){let t=0,n=new Map;for(let r of e?.sources_domain??[]){let e=Ro(String(r.key));if(!e||r.mentions===void 0||r.ai_search_volume===void 0){t+=1;continue}let i=n.get(e)??{mentions:0,aiSearchVolume:0};i.mentions+=r.mentions,i.aiSearchVolume+=r.ai_search_volume,n.set(e,i)}return{rows:[...n.entries()].map(([e,t])=>({domain:e,...t})).sort((e,t)=>t.mentions-e.mentions||t.aiSearchVolume-e.aiSearchVolume||Po(e.domain,t.domain)).slice(0,10),invalidRows:t}}function Vo(e){return e.market.surface===`chatgpt`?`chat_gpt`:`google`}function Ho(e){return e.market.location.code===void 0?{locationName:e.market.location.name}:{locationCode:e.market.location.code}}function Uo(e){let t=jn.safeParse(e.market);if(!t.success)throw new N({provider:`dataforseo`,operation:`ai-mentions`,code:`configuration`,message:`Use a valid AI mention market and surface.`});if(t.data.surface===`chatgpt`&&(t.data.countryCode!==`US`||t.data.languageCode!==`en`||t.data.location.code!==void 0&&t.data.location.code!==2840))throw new N({provider:`dataforseo`,operation:`ai-mentions`,code:`configuration`,message:`DataForSEO ChatGPT mention data currently supports United States English only.`});return{...e,market:t.data}}function Wo(e){return e.response.tasks.flatMap(e=>e.result??[])[0]}function Go(e,t){let n=Wo(t),r=[e.target,...e.competitors],i=r.length>1,a=i?n?.items??[]:n?.aggregated_metrics?[{...n.aggregated_metrics,key:e.target.key}]:[],o=new Map,s=0;for(let e of a){if(!e.key||o.has(e.key)){s+=1;continue}o.set(e.key,e)}let c=r.map(e=>{let t=o.get(e.key),n=Bo(t);return s+=n.invalidRows,{target:e,mentions:Fo(t?.total?.mentions),aiSearchVolume:Fo(t?.total?.ai_search_volume),sourceDomains:n.rows}}),l=n?.aggregated_metrics,u=Bo(l??void 0);s+=u.invalidRows;let d=[...t.warnings];return s>0&&d.push({code:`invalid-ai-mention-metric-rows`,message:`The provider returned ${s} duplicate or invalid AI mention metric row${s===1?``:`s`} that could not be retained.`}),{schemaVersion:1,provider:`dataforseo`,capability:`ai-mentions`,data:{targets:c,combined:{mentions:Fo(l?.total?.mentions),aiSearchVolume:Fo(l?.total?.ai_search_volume),sourceDomains:u.rows}},observedAt:t.observedAt,market:e.market,coverage:{requestedRows:r.length,returnedRows:t.returnedRows,retainedRows:c.filter(e=>e.mentions.state===`observed`||e.aiSearchVolume.state===`observed`).length,invalidRows:s,providerTotalRows:n?.total_count??null,completeness:s>0?`partial`:`complete`,nextCursor:null},cache:t.cache,cost:t.cost,request:{operation:`ai-mention-metrics`,endpoint:i?Mo.multi:Mo.single,limit:r.length,filters:{surface:e.market.surface,countryCode:e.market.countryCode,languageCode:e.market.languageCode,aliases:r.reduce((e,t)=>e+t.aliases.length,0)},sort:[`mentions:descending`,`target.key:codepoint-ascending`]},warnings:d}}function Ko(e,t){let n=zo(e.url),r=Ro(e.domain??``);return!n||!r?null:{rank:e.rank??t,domain:r,url:n,title:e.title?.trim()||null,sourceName:e.source_name?.trim()||null}}function qo(e,t){let n=t.response.tasks.flatMap(e=>e.result??[])[0],r=[...t.warnings],i=0,a=0,o=0,s=new Set,c=(n?.items??[]).flatMap(e=>{let t=e.question?.trim();if(!t||s.has(t.toLowerCase()))return i+=1,[];s.add(t.toLowerCase());let n=e.answer?.trim()??``,r=n.length>No;r&&(a+=1);let c=(e.sources??[]).flatMap((e,t)=>{let n=Ko(e,t+1);return n||(i+=1),n?[n]:[]}),l=c.sort((e,t)=>e.rank-t.rank||Po(e.url,t.url)).slice(0,10);return c.length>l.length&&(o+=1),[{question:t,answerExcerpt:n.slice(0,No),answerTruncated:r,model:e.model_name?.trim()||null,aiSearchVolume:Fo(e.ai_search_volume??void 0),firstObservedAt:Io(e.first_response_at),lastObservedAt:Io(e.last_response_at),isWebSearchBased:Lo(e.is_web_search_based),sources:l}]}).sort((e,t)=>(t.aiSearchVolume.state===`observed`?t.aiSearchVolume.value:-1)-(e.aiSearchVolume.state===`observed`?e.aiSearchVolume.value:-1)||Po(e.question,t.question)).slice(0,e.sampleLimit);i>0&&r.push({code:`invalid-ai-mention-sample-rows`,message:`The provider returned ${i} duplicate or invalid AI mention sample value${i===1?``:`s`} that could not be retained.`}),(a>0||o>0)&&r.push({code:`bounded-ai-mention-sample-detail`,message:`${a} answer excerpt${a===1?``:`s`} and ${o} source list${o===1?``:`s`} were shortened to keep structured output bounded.`});let l=n?.total_count??null,u=l!==null&&l>c.length;return{schemaVersion:1,provider:`dataforseo`,capability:`ai-mentions`,data:c,observedAt:t.observedAt,market:e.market,coverage:{requestedRows:e.sampleLimit,returnedRows:t.returnedRows,retainedRows:c.length,invalidRows:i,providerTotalRows:l,completeness:i>0?`partial`:u?`capped`:`complete`,nextCursor:n?.search_after_token??null},cache:t.cache,cost:t.cost,request:{operation:`ai-mention-samples`,endpoint:`v3/ai_optimization/llm_mentions/search_mentions/live`,limit:e.sampleLimit,filters:{surface:e.market.surface,countryCode:e.market.countryCode,languageCode:e.market.languageCode,aliases:e.target.aliases.length},sort:[`aiSearchVolume:descending`,`question:codepoint-ascending`]},warnings:r}}var Jo=class{provider=`dataforseo`;capabilitySupport=[{capability:`ai-mentions`,status:`available`,markets:`all`}];client;constructor(e={}){this.client=e.client??new jo(e)}async aiMentionMetrics(e){let t=Uo(e),n=t.context??{reportId:`ai-mention-research`,reportRunId:i()};return Go(t,await this.client.aiMentionMetrics({target:t.target,competitors:t.competitors,platform:Vo(t),languageCode:t.market.languageCode.split(`-`)[0],...Ho(t),refresh:t.refresh,context:n}))}async aiMentionSamples(e){let t=Uo(e),n=t.context??{reportId:`ai-mention-research`,reportRunId:i()};return qo(t,await this.client.aiMentionSearch({target:t.target,platform:Vo(t),languageCode:t.market.languageCode.split(`-`)[0],...Ho(t),limit:t.sampleLimit,refresh:t.refresh,context:n}))}};function Yo(e,t){return!e||e.some(e=>e===t)}function Xo(e,t){return!(!Yo(e.searchEngines,t.searchEngine)||!Yo(e.countryCodes,t.countryCode)||!Yo(e.languageCodes,t.languageCode)||t.device&&!Yo(e.devices,t.device)||e.location===`country-only`&&t.location||e.location===`canonical`&&!t.location)}function Zo(e,t){return e.adapter.capabilitySupport.find(e=>e.capability===t&&e.status===`available`)}function Xee(e,t){return e.markets===`all`||e.markets.some(e=>Xo(e,t))}function Zee(e){return[...e].sort((e,t)=>e.priority-t.priority||e.adapter.provider.localeCompare(t.adapter.provider))}function Qo(e){let t=Zee(e.candidates),n=t.map(e=>e.adapter.provider),r=e.provider?t.filter(t=>t.adapter.provider===e.provider):t;if(e.provider&&r.length===0)return{status:`unavailable`,reason:`provider-not-registered`,considered:n};let i=r.map(t=>({candidate:t,support:Zo(t,e.capability)})).filter(e=>!!e.support);if(i.length===0)return{status:`unavailable`,reason:`capability-not-supported`,considered:n};let a=i.filter(t=>Xee(t.support,e.market));if(a.length===0)return{status:`unavailable`,reason:`market-not-supported`,considered:n};let o=a.find(e=>e.candidate.connected);return o?{status:`available`,provider:o.candidate.adapter,considered:n}:{status:`unavailable`,reason:`provider-not-connected`,considered:n}}const $o=new se;function Qee(e){$o.enterWith(e??{})}function es(e){return $o.getStore()?.[e]}function ts(e,t){let n=[...new Map(e.filter(e=>!!e).map(e=>[e.toLowerCase(),e])).values()];if(n.length<=1)return n[0];throw new j(`INVALID_INPUT`,`Saved projects use different Google accounts for ${t}. Select a project-specific account instead of relying on resource matching.`)}function ns(e){return es(`searchConsole`)||ts(Qn().clients.filter(t=>t.siteUrl===e).map(e=>e.googleAccounts?.searchConsole),e)}function rs(e){return es(`googleAnalytics`)||ts(Qn().clients.filter(t=>t.analytics.google?.propertyId===e).map(e=>e.googleAccounts?.googleAnalytics),`Google Analytics property ${e}`)}function is(e){throw new j(`INVALID_INPUT`,e)}function as(e){let t;try{t=new URL(e.trim())}catch{return is(`Invalid URL: ${e}`)}return t.protocol!==`http:`&&t.protocol!==`https:`?is(`The report URL must use http:// or https://.`):t.username||t.password?is(`The report URL must not contain embedded credentials.`):(t.hash=``,t.toString())}function os(e,t){let n=as(t),r=new URL(n);if(e.startsWith(`sc-domain:`)){let t=e.slice(10).trim().toLowerCase();if(!t||/[/:]/.test(t))return is(`Invalid Search Console domain property: ${e}`);let i=r.hostname.toLowerCase();return i!==t&&!i.endsWith(`.${t}`)?is(`${n} is outside the Search Console property ${e}.`):n}let i=as(e);return n.startsWith(i)?n:is(`${n} is outside the Search Console property ${e}.`)}async function $ee(e,t){Vn(e);let n=process.env.SEO_LOCK_FAST===`1`,r=await ce.lock(e,{stale:n?2e3:6e4,update:n?1e3:5e3,retries:n?{retries:5,factor:1.2,minTimeout:50,maxTimeout:200}:{retries:60,factor:1.2,minTimeout:200,maxTimeout:1e3},realpath:!1}).catch(t=>{let n=t instanceof Error?t.message:String(t);throw Error(`Could not acquire local file lock for ${e}. Another seo process may still be writing auth/config state. Wait a few seconds and retry. ${n}`)});try{return await t()}finally{await r()}}const ss={clientId:`548418788053-q7p964rqvjiv8ooamciprku6bpu34u9d.apps.googleusercontent.com`,clientSecret:`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`};function cs(){let e=yr();return{sharedConfigured:!!ls(),byoConfigured:!!(e?.clientId&&e?.clientSecret)}}function ls(){let e=Qn(),t=process.env.SEO_GOOGLE_CLIENT_ID??process.env.GSC_CLIENT_ID??ss.clientId??e.auth.sharedClientId,n=process.env.SEO_GOOGLE_CLIENT_SECRET??process.env.GSC_CLIENT_SECRET??ss.clientSecret??e.auth.sharedClientSecret;if(!(!t||!n))return{clientId:t,clientSecret:n,source:`shared`}}function us(e){let t=yr();if(e!==`shared`&&t)return{clientId:t.clientId,clientSecret:t.clientSecret,source:`byo`};if(e!==`byo`)return ls()}function ete(e){return e===`shared`?"The stored Google login uses the shared seo app, but this build is missing that OAuth client. Report this package issue at https://github.com/iannuttall/seo/issues, or use `seo auth setup-client` with your own desktop OAuth client.":"The stored Google login uses a BYO OAuth client that is no longer configured. Restore the same client with `seo auth setup-client`, or run `seo auth logout` before signing in with a different client."}const ds=[`https://www.googleapis.com/auth/webmasters.readonly`,`https://www.googleapis.com/auth/analytics.readonly`],fs={"https://www.googleapis.com/auth/webmasters.readonly":`Search Console`,"https://www.googleapis.com/auth/analytics.readonly":`Google Analytics`};function ps(e){let t=new Set(e.split(/\s+/u).filter(Boolean));return ds.filter(e=>!t.has(e))}const ms=[...ds,`openid`,`email`].join(` `),hs=`https://oauth2.googleapis.com/token`;function gs(e){return new j(`AUTH_CONFIG_REQUIRED`,e)}function _s(e){return e===`environment-json`?`SEO_GOOGLE_SERVICE_ACCOUNT_JSON`:e===`environment-file`?`SEO_GOOGLE_SERVICE_ACCOUNT_FILE`:`GOOGLE_APPLICATION_CREDENTIALS`}function vs(e){let t=[],n=e.SEO_GOOGLE_SERVICE_ACCOUNT_JSON?.trim();n&&t.push({source:`environment-json`,value:n});let r=e.SEO_GOOGLE_SERVICE_ACCOUNT_FILE?.trim();r&&t.push({source:`environment-file`,value:r});let i=e.GOOGLE_APPLICATION_CREDENTIALS?.trim();return i&&t.push({source:`application-default-file`,value:i}),t}function ys(e,t){let n;try{n=JSON.parse(e)}catch{throw gs(_s(t)+` must contain a valid service account JSON object.`)}if(!n||typeof n!=`object`||Array.isArray(n))throw gs(`${_s(t)} must contain a service account JSON object.`);let r=n;if(r.type!==void 0&&r.type!==`service_account`)throw gs(_s(t)+` must contain a Google service account credential.`);if(typeof r.client_email!=`string`||!r.client_email||typeof r.private_key!=`string`||!r.private_key)throw gs(_s(t)+` must include service account client_email and private_key fields.`);if(r.private_key_id!==void 0&&typeof r.private_key_id!=`string`)throw gs(`${_s(t)} has an invalid service account private_key_id.`);return{clientEmail:r.client_email,privateKey:r.private_key,privateKeyId:r.private_key_id,source:t}}function bs(e,t){try{return u(e,`utf8`)}catch{throw gs(`Could not read the service account file named by `+_s(t)+`.`)}}function xs(e=process.env){let t=vs(e);if(!t.length)return;if(t.length>1)throw gs(`Set only one service account credential source: SEO_GOOGLE_SERVICE_ACCOUNT_JSON, SEO_GOOGLE_SERVICE_ACCOUNT_FILE, or GOOGLE_APPLICATION_CREDENTIALS.`);let n=t[0];if(n)return ys(n.source===`environment-json`?n.value:bs(n.value,n.source),n.source)}function Ss(e=process.env){try{let t=xs(e);return t?{configured:!0,identity:t.clientEmail,source:t.source}:{configured:!1}}catch(t){let n=vs(e);return{configured:!1,...n.length===1&&n[0]?{source:n[0].source}:{},error:t instanceof Error?t.message:`Service account credentials are invalid.`}}}function Cs(e){return Buffer.from(e).toString(`base64url`)}function ws(e,t){let n={alg:`RS256`,typ:`JWT`,...e.privateKeyId?{kid:e.privateKeyId}:{}},r={iss:e.clientEmail,scope:ds.join(` `),aud:hs,iat:t,exp:t+3600},i=`${Cs(JSON.stringify(n))}.${Cs(JSON.stringify(r))}`;try{return i+`.`+a(`RSA-SHA256`,Buffer.from(i),e.privateKey).toString(`base64url`)}catch{throw gs(`The service account private key could not sign a Google access request.`)}}async function Ts(e,t=fetch){let n=ws(e,Math.floor(Date.now()/1e3)),r=await t(hs,{method:`POST`,headers:{"content-type":`application/x-www-form-urlencoded`},body:new URLSearchParams({grant_type:`urn:ietf:params:oauth:grant-type:jwt-bearer`,assertion:n})});if(!r.ok){let e=await r.json().catch(()=>void 0),t=typeof e?.error==`string`?` (${e.error})`:``;throw r.status===400||r.status===401||r.status===403?gs(`Google rejected the service account access request with `+r.status+t+`. Check the service account, private key, and system clock.`):new j(`PROVIDER_UNAVAILABLE`,`Google token service is unavailable (`+r.status+`). Try again later.`)}let i=await r.json();if(typeof i.access_token!=`string`||!i.access_token||typeof i.expires_in!=`number`||!Number.isFinite(i.expires_in)||i.expires_in<=0)throw new j(`PROVIDER_UNAVAILABLE`,`Google token service returned an invalid service account response.`);return{accessToken:i.access_token,expiresIn:i.expires_in}}var Es=class{config;fetchImpl;token;constructor(e,t=fetch){this.config=e,this.fetchImpl=t}async getAccessToken(){if(this.token&&this.token.expiresAt-Date.now()>=6e4)return this.token.value;let e=await Ts(this.config,this.fetchImpl);return this.token={value:e.accessToken,expiresAt:Date.now()+e.expiresIn*1e3},this.token.value}};const Ds=`https://oauth2.googleapis.com/token`;var Os=class extends Error{status;oauthError;constructor(e,t){super(`Google token refresh failed with ${e}${t?` (${t})`:``}.`),this.status=e,this.oauthError=t,this.name=`GoogleTokenEndpointError`}};async function ks(e,t=fetch){let n=await t(Ds,{method:`POST`,headers:{"content-type":`application/x-www-form-urlencoded`},body:new URLSearchParams({grant_type:`refresh_token`,client_id:e.clientConfig.clientId,client_secret:e.clientConfig.clientSecret,refresh_token:e.refreshToken})});if(!n.ok){let e=await n.json().catch(()=>void 0);throw new Os(n.status,typeof e?.error==`string`?e.error:void 0)}let r=await n.json();if(typeof r.access_token!=`string`||!r.access_token||typeof r.expires_in!=`number`||!Number.isFinite(r.expires_in)||r.expires_in<=0||r.refresh_token!==void 0&&typeof r.refresh_token!=`string`)throw Error(`Google token refresh returned an invalid response.`);return{accessToken:r.access_token,expiresIn:r.expires_in,refreshToken:r.refresh_token}}function As(e){return new j(`AUTH_REQUIRED`,e?`Google account ${e} is not connected. Run \`seo auth login\` first.`:"Not logged in. Run `seo auth login` first.")}function js(){return new j(`AUTH_EXPIRED`,"Google refresh token is no longer valid. Run `seo auth login` again.")}function Ms(e){let t=ps(e.scope);if(t.length!==0)throw new j(`ACCESS_DENIED`,`Google access is missing ${t.map(e=>fs[e]).join(` and `)}. Run \`seo auth login\` again, choose Select all, and approve both permission boxes.`)}function Ns(e,t){return!e.access_token||e.expires_at-t<6e4}function Ps(e){let t=us(e.client_source);if(!t)throw new j(`AUTH_CONFIG_REQUIRED`,ete(e.client_source));return t}function Fs(e){return e instanceof Os&&(e.status===401||e.oauthError===`invalid_grant`)||/invalid_grant|401/.test(e instanceof Error?e.message:String(e))}async function Is(e){return $ee(ot().tokensFile,async()=>{let t=await pr(e.accountEmail);if(!t)throw As(e.accountEmail);let n=Ps(t),r=Date.now();if(e.onlyIfExpiring&&!Ns(t,r))return t;if(!t.refresh_token)throw await _r(t.account_email),js();try{let e=await ks({clientConfig:n,refreshToken:t.refresh_token}),i={...t,access_token:e.accessToken,refresh_token:e.refreshToken??t.refresh_token,expires_at:r+e.expiresIn*1e3};return await gr(i,{makeActive:!1}),i}catch(e){throw Fs(e)?(await _r(t.account_email),js()):e}})}var Ls=class{tokens;constructor(e){this.tokens=e}async getAccessToken(){if(Ns(this.tokens,Date.now())&&(this.tokens=await Is({onlyIfExpiring:!0,accountEmail:this.tokens.account_email})),!this.tokens.access_token)throw js();return this.tokens.access_token}};async function Rs(e){let t=await pr(e);if(!t)throw As(e);return Ps(t),Ms(t),Ns(t,Date.now())&&(t=await Is({onlyIfExpiring:!0,accountEmail:t.account_email})),{client:new Ls(t),tokens:t}}async function zs(e){return Is({onlyIfExpiring:!1,accountEmail:e})}async function Bs(e){let t=xs();if(t)return{client:new Es(t),mode:`service-account`,identity:t.clientEmail,quotaIdentity:{clientId:`service-account`,accountEmail:t.clientEmail}};let{client:n,tokens:r}=await Rs(e),i=Ps(r);return{client:n,mode:`oauth`,identity:r.account_email,quotaIdentity:{clientId:i.clientId,accountEmail:r.account_email},tokens:r}}async function Vs(){let e=cs(),t=Ss(),n=mr();if(t.configured||t.error)return{configured:t.configured,accounts:n,sharedConfigured:e.sharedConfigured,byoConfigured:e.byoConfigured,activeMode:`service-account`,identity:t.identity,serviceAccount:t};let r=await pr();return{tokens:r,accounts:n,configured:!!us(r?.client_source),sharedConfigured:e.sharedConfigured,byoConfigured:e.byoConfigured,activeMode:r?`oauth`:`none`,identity:r?.account_email,serviceAccount:t}}function Hs(e){return e.status===`connected`?{title:`Google account connected`,heading:`Google account connected.`,detail:`This tab can be closed.`}:e.status===`permissions-missing`?{title:`Google permissions not granted`,heading:`Google permissions were not granted.`,detail:`Return to your terminal and run seo auth login again. Select all permission boxes for ${e.missing.join(` and `)}.`}:{title:`Google connection failed`,heading:`Google connection failed.`,detail:`Return to your terminal for the error and try again.`}}function Us(e={status:`connected`}){let t=Hs(e);return`<!doctype html>
607
607
  <html lang="en">
608
608
  <head>
609
609
  <meta charset="utf-8">
@@ -1126,4 +1126,4 @@ Disable: seo telemetry disable (or set DO_NOT_TRACK=1)
1126
1126
  `);if(e===-1)return null;let t=this._buffer.toString(`utf8`,0,e).replace(/\r$/,``);return this._buffer=this._buffer.subarray(e+1),Gce(t)}clear(){this._buffer=void 0}};function Gce(e){return _ae.parse(JSON.parse(e))}function Kce(e){return JSON.stringify(e)+`
1127
1127
  `}var qce=class{constructor(e=Me.stdin,t=Me.stdout){this._stdin=e,this._stdout=t,this._readBuffer=new Wce,this._started=!1,this._ondata=e=>{this._readBuffer.append(e),this.processReadBuffer()},this._onerror=e=>{this.onerror?.(e)}}async start(){if(this._started)throw Error(`StdioServerTransport already started! If using Server class, note that connect() calls start() automatically.`);this._started=!0,this._stdin.on(`data`,this._ondata),this._stdin.on(`error`,this._onerror)}processReadBuffer(){for(;;)try{let e=this._readBuffer.readMessage();if(e===null)break;this.onmessage?.(e)}catch(e){this.onerror?.(e)}}async close(){this._stdin.off(`data`,this._ondata),this._stdin.off(`error`,this._onerror),this._stdin.listenerCount(`data`)===0&&this._stdin.pause(),this._readBuffer.clear(),this.onclose?.()}send(e){return new Promise(t=>{let n=Kce(e);this._stdout.write(n)?t():this._stdout.once(`drain`,t)})}};const W7=O.iso.date(`Use a YYYY-MM-DD date.`),G7=O.string().regex(/^\d{4}-(?:0[1-9]|1[0-2])$/,`Use a YYYY-MM month.`);function K7(e,t){return e===void 0?t:e}const Jce={site:{mcp:`site`,schema:O.string()},days:{mcp:`days`,schema:O.number().optional()},recentDays:{mcp:`recentDays`,schema:O.number().optional()},range:{mcp:`range`,schema:O.number().optional()},limit:{mcp:`limit`,schema:O.number().optional()},checkLimit:{mcp:`checkLimit`,schema:O.number().optional()},minImpressions:{mcp:`minImpressions`,schema:O.number().optional()},includeBrand:{mcp:`includeBrand`,schema:O.boolean().optional()},verifyContent:{mcp:`verifyContent`,schema:O.boolean().optional()},verifyLimit:{mcp:`verifyLimit`,schema:O.number().optional()},js:{mcp:`js`,schema:O.boolean().optional()},fetchConcurrency:{mcp:`fetchConcurrency`,schema:O.number().optional()},fetchIntervalCap:{mcp:`fetchIntervalCap`,schema:O.number().optional()},fetchIntervalMs:{mcp:`fetchIntervalMs`,schema:O.number().optional()},refresh:{mcp:`refresh`,schema:O.boolean().optional()}};function q7(e){return Object.fromEntries(e.map(e=>{let t=Jce[e];return[t.mcp,t.schema]}))}function Q(e){let t=qe(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 J7=O.union([W7,O.string().regex(/^(?:today|yesterday|\d+daysAgo)$/)]).describe(`Use YYYY-MM-DD or a Google Analytics relative date.`),Y7={site:O.string().trim().min(1),days:O.number().int().min(1).max(548).optional(),startDate:W7.optional(),endDate:W7.optional(),limit:O.number().int().min(1).max(100).optional(),minImpressions:O.number().int().min(0).max(1e9).optional(),maxRows:O.number().int().min(1).max(5e4).optional(),brandTerms:O.array(O.string().trim().min(1)).max(100).optional(),includeBrand:O.boolean().optional(),refresh:O.boolean().optional()};function Yce(e,t={}){e.registerTool(`seo_ai_referrals`,{description:`Find AI referral traffic detected in Google Analytics. maxRows bounds retained provider rows; resultLimit bounds returned landing pages.`,inputSchema:{property:O.string().trim().min(1),startDate:J7.optional(),endDate:J7.optional(),maxRows:O.number().int().min(1).max(1e5).describe(`Maximum retained Google Analytics rows. If limit is also supplied, both values must match.`).optional(),limit:O.number().int().min(1).max(1e5).describe(`Legacy alias for maxRows. If maxRows is also supplied, both values must match.`).optional(),resultLimit:O.number().int().min(1).max(1e3).describe(`Maximum ranked landing pages returned. Defaults to 25.`).optional(),refresh:O.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 j(`INVALID_INPUT`,`maxRows and the legacy limit option must match when both are provided.`);let c=await(t.aiReferralsReport??mh)({property:e,startDate:n,endDate:r,maxRows:i,limit:a,resultLimit:o,refresh:s});return $(`${Y(c.summary.sessions,`AI referral session`)} detected across ${Y(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:{...q7([`site`,`days`,`limit`,`minImpressions`,`includeBrand`,`verifyContent`,`refresh`,`js`]),days:O.number().int().min(1).max(548).optional(),limit:O.number().int().min(1).max(100).optional(),minImpressions:O.number().int().positive().optional(),url:O.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??zM)({site:e,url:n,days:r,limit:i,minImpressions:a,includeBrand:o,verifyContent:s,refresh:c,js:K7(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:{...q7([`site`,`days`,`limit`,`minImpressions`,`includeBrand`,`verifyContent`,`refresh`,`js`]),days:O.number().int().min(1).max(548).optional(),limit:O.number().int().min(1).max(100).optional(),minImpressions:O.number().int().positive().optional(),url:O.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??YM)({site:e,url:n,days:r,limit:i,minImpressions:a,includeBrand:o,verifyContent:s,refresh:c,js:K7(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:O.string().url(),strategy:O.enum([`mobile`,`desktop`]).optional(),refresh:O.boolean().optional()}},async({url:e,strategy:t,refresh:n})=>{try{let r=await RX({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:Y7},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??Y0)({site:e,days:n,startDate:r,endDate:i,limit:a,minImpressions:o,maxRows:s,brandTerms:c,includeBrand:l,refresh:u});return $(`${Y(d.summary.prompts,`monitoring-prompt suggestion`)} generated from ${Y(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:Y7},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??wO)({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 X7(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,config:e.config,access:e.access,summary:e.summary,dataSources:e.dataSources,ai:e.ai?{robotsTxt:e.ai.robotsTxt,llmsTxt:e.ai.llmsTxt,agentResources:e.ai.agentResources}:void 0,topFixes:az(e,{limit:1e3}),reviewObservations:oz(e,{limit:1e3}),issueGroupsComplete:!0,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 Z7(e){if(!e.value||e.value===`latest`||e.value===`previous`){let t=DI({site:e.site,limit:20}).filter(t=>t.id!==e.skipId),n=e.value===`previous`?t[1]:t[0];return n?OI(n.id):void 0}return OI(e.value)}function Q7(e,t){if(e&&t)throw new j(`INVALID_INPUT`,`Use either url or reportId, not both.`)}const $7=O.number().int().min(1).max(RF).optional(),e9=O.number().int().min(0).max(64).optional(),t9=O.number().int().min(1).max(16).optional(),n9=O.string().regex(/^\d{1,30}$/u).optional();function r9(e){if(e.googleAnalyticsPropertyId&&e.clickySiteId)throw new j(`INVALID_INPUT`,`Pass either googleAnalyticsPropertyId or clickySiteId, not both.`);return e.clickySiteId?{provider:`clicky`,siteId:e.clickySiteId}:void 0}function i9(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 Xce(e){e.registerTool(`seo_llms_txt_audit`,{description:`Fetch and check an optional llms.txt file body and links. The file does not affect Google rankings.`,inputSchema:{url:O.string().url().optional(),reportId:O.string().optional(),site:O.string().optional(),maxPages:$7,fetchIntervalCap:O.number().int().positive().optional(),fetchIntervalMs:O.number().int().positive().optional(),refresh:O.boolean().optional()}},async({url:e,reportId:t,site:n,maxPages:r,fetchIntervalCap:i,fetchIntervalMs:a,refresh:o})=>{try{Q7(e,t);let s=e?await SR({url:e,site:n,maxPages:r,refresh:o,useSitemap:!0,checkExternal:!1,fetchRate:i9({fetchIntervalCap:i,fetchIntervalMs:a})}):t?OI(t):AI(n);if(!s)return Q(`No crawl report found. Pass url, reportId, or run seo_crawl_site with saveReport first.`);let c=await zP(s);return $(c.headline,c)}catch(e){return Q(e)}}),e.registerTool(`seo_llms_txt_generate`,{description:`Generate a valid llms.txt v2 draft from saved or fresh crawl data. Returns content and metadata.`,inputSchema:{url:O.string().url().optional(),reportId:O.string().optional(),site:O.string().optional(),maxPages:$7,maxUrls:O.number().int().min(1).max(OP).optional(),tokenBudget:O.number().int().min(1).max(16e3).optional(),exclude:O.array(O.string()).optional(),title:O.string().optional(),description:O.string().optional(),fetchIntervalCap:O.number().int().positive().optional(),fetchIntervalMs:O.number().int().positive().optional(),refresh:O.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 SR({url:e,site:n,maxPages:r,refresh:d,fetchRate:i9({fetchIntervalCap:l,fetchIntervalMs:u})}):t?OI(t):AI(n);if(!f)return Q(`No crawl report found. Pass url, reportId, or run seo_crawl_site with saveReport first.`);let p=BP(f,{maxUrls:i,tokenBudget:a,maxBytes:65536,exclude:o,title:s,description:c});return $(`Generated llms.txt with ${p.includedUrls} URLs.`,p)}catch(e){return Q(e)}})}function a9(e){e.registerTool(`seo_crawl_site`,{description:`Run a site crawl. Start with health=true for a fast uncached sitemap status and redirect pass, especially on large or unknown sites. Run the full crawl second when content-level evidence is needed. The compact result includes crawler identity and access-block guidance.`,inputSchema:{url:O.string().url().optional().describe(`Site start URL. Optional when sitemapUrl supplies the health-pass scope.`),health:O.boolean().optional().describe(`Run the fast sitemap health pass before a full crawl. Downloads no page bodies and writes no page cache entries.`),sitemapUrl:O.string().url().optional().describe(`Explicit sitemap or sitemap index URL for the health pass.`),site:O.string().optional(),googleAnalyticsPropertyId:O.string().optional(),clickySiteId:n9,maxPages:$7,maxDepth:e9,concurrency:t9,fetchIntervalCap:O.number().int().positive().optional(),fetchIntervalMs:O.number().int().positive().optional(),refresh:O.boolean().optional(),include:O.array(O.string()).optional(),exclude:O.array(O.string()).optional(),useSitemap:O.boolean().optional(),respectRobots:O.boolean().optional(),js:O.boolean().optional(),includePages:O.boolean().optional(),includeIssues:O.boolean().optional(),saveReport:O.boolean().optional()}},async({url:e,health:t,sitemapUrl:n,site:r,googleAnalyticsPropertyId:i,clickySiteId:a,maxPages:o,maxDepth:s,concurrency:c,fetchIntervalCap:l,fetchIntervalMs:u,refresh:d,include:f,exclude:p,useSitemap:m,respectRobots:h,js:g,includePages:_,includeIssues:v,saveReport:y})=>{try{let b=r9({googleAnalyticsPropertyId:i,clickySiteId:a}),x=e??(n?new URL(`/`,n).toString():void 0);if(!x)throw new j(`INVALID_INPUT`,`Pass url or an explicit sitemapUrl.`);let S=await SR({url:x,mode:t||n?`sitemap`:`site`,strategy:t?`health`:`full`,sitemapUrl:n,site:r,googleAnalyticsPropertyId:i,analyticsConnection:b,maxPages:o,maxDepth:s,concurrency:c,refresh:d,fetchRate:i9({fetchConcurrency:c,fetchIntervalCap:l,fetchIntervalMs:u}),include:f,exclude:p,useSitemap:m,respectRobots:h,js:!!g}),C=y?EI(S):void 0;return $(`${t?`Sitemap health pass`:`Crawl`} complete for ${x}. Found ${S.issues.length} issues across ${S.summary.totalPages} pages.`,{...X7(S,{includePages:_,includeIssues:v}),...C?{saved:C}:{}})}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:O.array(O.string().url()).min(1).max(RF),site:O.string().optional(),googleAnalyticsPropertyId:O.string().optional(),clickySiteId:n9,maxPages:$7,concurrency:t9,fetchIntervalCap:O.number().int().positive().optional(),fetchIntervalMs:O.number().int().positive().optional(),refresh:O.boolean().optional(),includePages:O.boolean().optional(),includeIssues:O.boolean().optional(),saveReport:O.boolean().optional()}},async({urls:e,site:t,googleAnalyticsPropertyId:n,clickySiteId:r,maxPages:i,concurrency:a,fetchIntervalCap:o,fetchIntervalMs:s,refresh:c,includePages:l,includeIssues:u,saveReport:d})=>{try{let f=r9({googleAnalyticsPropertyId:n,clickySiteId:r}),p=await SR({url:e[0]??``,urls:e,mode:`list`,site:t,googleAnalyticsPropertyId:n,analyticsConnection:f,maxPages:i??e.length,concurrency:a,refresh:c,fetchRate:i9({fetchConcurrency:a,fetchIntervalCap:o,fetchIntervalMs:s}),useSitemap:!1}),m=d?EI(p):void 0;return $(`URL audit complete. Found ${p.issues.length} issues across ${p.summary.totalPages} pages.`,{...X7(p,{includePages:l,includeIssues:u}),...m?{saved:m}:{}})}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:O.string().url().optional(),reportId:O.string().optional(),site:O.string().optional(),maxPages:$7,maxDepth:e9,fetchIntervalCap:O.number().int().positive().optional(),fetchIntervalMs:O.number().int().positive().optional(),refresh:O.boolean().optional(),category:O.string().optional(),limit:O.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{Q7(e,t);let u=e?await SR({url:e,site:n,maxPages:r,maxDepth:i,refresh:s,fetchRate:i9({fetchIntervalCap:a,fetchIntervalMs:o})}):t?OI(t):AI(n);if(!u)return Q(`No crawl report found. Pass url, reportId, or run seo_crawl_site with saveReport first.`);let d=az(u,{category:c,limit:l}),f=u.status===`completed`?``:` Coverage is ${u.status}: ${Y(u.summary.crawledUrls,`URL`)} crawled from ${Y(u.summary.discoveredUrls,`discovered URL`)}. Review the returned warnings and caveats before treating this as sitewide.`;return $(`Found ${Y(d.length,`top fix group`)} for ${u.config.url}.${f}`,{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:O.string()}},async({ruleId:e})=>{let t=tn(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:IB.optional()}},async({category:e})=>{let t=en().filter(t=>!e||t.category===e);return $(`Found ${Y(t.length,`crawler rule`)}.`,{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:O.string().url().optional(),reportId:O.string().optional(),site:O.string().optional(),ruleId:O.string().optional(),category:O.string().optional(),severity:O.string().optional(),maxPages:$7,fetchIntervalCap:O.number().int().positive().optional(),fetchIntervalMs:O.number().int().positive().optional(),refresh:O.boolean().optional(),limit:O.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 SR({url:e,site:n,maxPages:o,refresh:l,fetchRate:i9({fetchIntervalCap:s,fetchIntervalMs:c})}):t?OI(t):AI(n);if(!d)return Q(`No crawl report found. Pass url, reportId, or run seo_crawl_site with saveReport first.`);let f=ZM(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:O.string().url().optional(),reportId:O.string().optional(),site:O.string().optional(),maxPages:$7,fetchIntervalCap:O.number().int().positive().optional(),fetchIntervalMs:O.number().int().positive().optional(),refresh:O.boolean().optional(),limit:O.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 SR({url:e,site:n,maxPages:r,refresh:o,fetchRate:i9({fetchIntervalCap:i,fetchIntervalMs:a})}):t?OI(t):AI(n);if(!c)return Q(`No crawl report found. Pass url, reportId, or run seo_crawl_site with saveReport first.`);let l=DP(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:O.string().url().optional(),reportId:O.string().optional(),site:O.string().optional(),maxPages:$7,fetchIntervalCap:O.number().int().positive().optional(),fetchIntervalMs:O.number().int().positive().optional(),refresh:O.boolean().optional()}},async({url:e,reportId:t,site:n,maxPages:r,fetchIntervalCap:i,fetchIntervalMs:a,refresh:o})=>{try{Q7(e,t);let s=e?await SR({url:e,site:n,maxPages:r,refresh:o,checkAgentDiscovery:!0,useSitemap:!0,checkExternal:!1,fetchRate:i9({fetchIntervalCap:i,fetchIntervalMs:a})}):t?OI(t):AI(n);if(!s)return Q(`No crawl report found. Pass url, reportId, or run seo_crawl_site with saveReport first.`);let c=aP(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:O.string().url().optional(),reportId:O.string().optional(),site:O.string().optional(),maxPages:$7,fetchIntervalCap:O.number().int().positive().optional(),fetchIntervalMs:O.number().int().positive().optional(),refresh:O.boolean().optional()}},async({url:e,reportId:t,site:n,maxPages:r,fetchIntervalCap:i,fetchIntervalMs:a,refresh:o})=>{try{let s=e?await SR({url:e,site:n,maxPages:r,refresh:o,fetchRate:i9({fetchIntervalCap:i,fetchIntervalMs:a})}):t?OI(t):AI(n);if(!s)return Q(`No crawl report found. Pass url, reportId, or run seo_crawl_site with saveReport first.`);let c=CP(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:O.string().url().optional(),reportId:O.string().optional(),site:O.string().optional(),maxPages:$7,fetchIntervalCap:O.number().int().positive().optional(),fetchIntervalMs:O.number().int().positive().optional(),refresh:O.boolean().optional()}},async({url:e,reportId:t,site:n,maxPages:r,fetchIntervalCap:i,fetchIntervalMs:a,refresh:o})=>{try{let s=e?await SR({url:e,site:n,maxPages:r,refresh:o,fetchRate:i9({fetchIntervalCap:i,fetchIntervalMs:a})}):t?OI(t):AI(n);if(!s)return Q(`No crawl report found. Pass url, reportId, or run seo_crawl_site with saveReport first.`);let c=rF(s);return $(c.headline,c)}catch(e){return Q(e)}}),Xce(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:O.string().url().optional(),reportId:O.string().optional(),site:O.string().optional(),maxPages:$7,fetchIntervalCap:O.number().int().positive().optional(),fetchIntervalMs:O.number().int().positive().optional(),refresh:O.boolean().optional()}},async({url:e,reportId:t,site:n,maxPages:r,fetchIntervalCap:i,fetchIntervalMs:a,refresh:o})=>{try{let s=e?await SR({url:e,site:n,maxPages:r,refresh:o,fetchRate:i9({fetchIntervalCap:i,fetchIntervalMs:a})}):t?OI(t):AI(n);if(!s)return Q(`No crawl report found. Pass url, reportId, or run seo_crawl_site with saveReport first.`);let c=hP(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:O.string().url().optional(),reportId:O.string().optional(),site:O.string().optional(),maxPages:$7,maxConcepts:O.number().int().min(1).max(100).optional(),includeFiles:O.boolean().optional(),title:O.string().min(1).max(200).optional(),fetchIntervalCap:O.number().int().positive().optional(),fetchIntervalMs:O.number().int().positive().optional(),refresh:O.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 SR({url:e,site:n,maxPages:r,refresh:l,fetchRate:i9({fetchIntervalCap:s,fetchIntervalMs:c})}):t?OI(t):AI(n);if(!u)return Q(`No crawl report found. Pass url, reportId, or run seo_crawl_site with saveReport first.`);let d=AB(u,{maxConcepts:i??(a?25:100),title:o}),f=pB(d.files,{profile:`seo-export`,now:u.generatedAt}),{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. Missing Markdown targets remain warnings under the generic OKF profile. Use the okf-build report to generate files from a crawl.`,inputSchema:{files:O.array(O.object({path:O.string().min(1).max(512),content:O.string().max(2e6)})).min(1).max(5006),profile:O.enum([`okf`,`seo-export`]).optional()}},async({files:e,profile:t})=>{let n=pB(e,{profile:t??`okf`});return $(n.valid?`Bundle passes OKF validation.`:`Bundle has OKF validation errors.`,{validation:n,explanation:mB(n)})}),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:O.string().optional(),site:O.string().optional(),includePages:O.boolean().optional(),includeIssues:O.boolean().optional()}},async({id:e,site:t,includePages:n,includeIssues:r})=>{try{let i=e?OI(e):AI(t);return i?$(`Saved crawl report ${i.id}.`,{...X7(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:O.string().optional(),limit:O.number().int().positive().optional()}},async({site:e,limit:t})=>{try{let n=DI({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:O.string().optional(),before:O.string().optional(),site:O.string().optional()}},async({after:e,before:t,site:n})=>{try{let r=Z7({value:e??`latest`,site:n});if(!r)return Q(`No newer crawl report found.`);let i=Z7({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=Fz({before:i,after:r});return $(a.headline,a)}catch(e){return Q(e)}})}function o9(e,t={}){e.registerTool(`seo_doctor`,{description:`Check the local database, auth, scopes, config, and defaults`,inputSchema:{}},async()=>{try{let e=await q4();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:{...q7([`site`,`days`,`recentDays`,`limit`,`includeBrand`,`refresh`])}},async({site:e,days:t,recentDays:n,limit:r,includeBrand:i,refresh:a})=>{try{let o=await HW({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:O.string().trim().min(1),days:O.number().int().min(1).max(240).optional(),limit:O.number().int().min(1).max(100).optional(),refresh:O.boolean().optional(),dimension:O.enum([`page`,`query`,`country`,`device`]).optional(),compareDays:O.number().int().min(1).max(240).optional(),startDate:W7.optional(),endDate:W7.optional(),maxRows:O.number().int().min(1).max(25e4).optional(),unmatchedLimit:O.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??rH)({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:{...q7([`site`,`days`,`minImpressions`,`limit`,`verifyContent`,`verifyLimit`,`includeBrand`,`js`,`fetchConcurrency`,`fetchIntervalCap`,`fetchIntervalMs`,`refresh`]),days:O.number().int().min(1).max(548).optional(),minImpressions:O.number().int().min(0).max(1e9).optional(),limit:O.number().int().min(1).max(100).optional(),verifyLimit:O.number().int().min(0).max(100).optional(),brandTerms:O.array(O.string().trim().min(1).max(200)).max(20).optional(),fetchConcurrency:O.number().int().min(1).max(16).optional(),fetchIntervalCap:O.number().int().min(1).max(60).optional(),fetchIntervalMs:O.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 QU({site:e,days:t,minImpressions:n,limit:r,verifyContent:i,verifyLimit:a,includeBrand:o,brandTerms:s,js:K7(c,void 0),rate:i9({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:{...q7([`site`,`days`,`recentDays`,`refresh`])}},async({site:e,days:t,recentDays:n,refresh:r})=>{try{let i=await kW({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:{...q7([`site`,`days`,`recentDays`,`refresh`]),paddingDays:O.number().optional()}},async({site:e,days:t,recentDays:n,paddingDays:r,refresh:i})=>{try{let a=await AW({site:e,days:t,recentDays:n,paddingDays:r,refresh:i});return $(`Classification: ${a.classification}.`,a)}catch(e){return Q(e)}})}function s9(e){return Buffer.byteLength(JSON.stringify(e))}function c9(e){let t=s9(e);for(;e.outputBudget.returnedBytes!==t;)e.outputBudget.returnedBytes=t,t=s9(e);return t}function l9(e,t,n,r){if(typeof e==`string`)return e.length<=n.stringLimit?e:(r.push({path:t,kind:`string`,available:e.length,returned:n.stringLimit}),`${e.slice(0,Math.max(1,n.stringLimit-3)).trimEnd()}...`);if(Array.isArray(e))return e.length>n.arrayLimit&&r.push({path:t,kind:`array`,available:e.length,returned:n.arrayLimit}),e.slice(0,n.arrayLimit).map((e,i)=>l9(e,`${t}[${i}]`,n,r));if(!e||typeof e!=`object`)return e;let i={};for(let[a,o]of Object.entries(e)){let e=t?`${t}.${a}`:a;if(!t&&n.preserveAgentContract&&(a===`findings`||a===`inventories`)){i[a]=o;continue}if(a===`markdown`){r.push({path:e,kind:`field`});continue}i[a]=l9(o,e,n,r)}return i}function u9(e,t={}){let n=t.maxBytes??98304,r=e,i=e.findings,a=i?.schemaVersion===1&&i.scope===`returned-report`&&Array.isArray(i.items),o=s9(r),s={...r,outputBudget:{schemaVersion:1,maxBytes:n,originalBytes:o,returnedBytes:0,truncated:!1,omissions:[],omissionsTruncated:!1,detail:`The complete structured report fits within the agent output budget.`}};if(c9(s)<=n)return s;let c=[{arrayLimit:10,stringLimit:4e3,preserveAgentContract:a},{arrayLimit:5,stringLimit:2e3,preserveAgentContract:a},{arrayLimit:3,stringLimit:1e3,preserveAgentContract:a},{arrayLimit:1,stringLimit:500,preserveAgentContract:a}];for(let e of c){let t=[],i={...l9(r,``,e,t),outputBudget:{schemaVersion:1,maxBytes:n,originalBytes:o,returnedBytes:0,truncated:t.length>0,arrayLimit:e.arrayLimit,stringLimit:e.stringLimit,omissions:t.slice(0,100),omissionsTruncated:t.length>100,detail:`Counts, provenance, caveats, and compact evidence are retained. Run the related focused report when more rows are needed.`}};if(c9(i)<=n)return i}let l={...Tn(r),outputBudget:{schemaVersion:1,maxBytes:n,originalBytes:o,returnedBytes:0,truncated:!0,fallback:!0,omissionsTruncated:!1,omissions:[{path:`reportBody`,kind:`field`}],detail:`The detailed report exceeded the budget, so the complete findings and retained inventories are returned instead.`}};if(c9(l)<=n)return l;throw Error(`The complete findings and inventories exceed the ${n}-byte agent output budget. Run a focused report with a narrower bounded scope; actionable rows were not truncated.`)}const d9=O.string().trim().min(1).max(80).refine(e=>e.split(/\s+/u).length<=10,`Use at most 10 words per keyword.`),f9=O.strictObject({code:O.number().int().positive().optional(),name:O.string().trim().min(1).max(500).optional()}).refine(e=>e.code!==void 0||e.name!==void 0,{message:`A location needs a code or name.`}),p9=O.string().trim().regex(/^[a-z]{2}$/i),m9=O.string().trim().min(2).max(35).regex(/^[a-z]{2,3}(?:-[a-z0-9]{2,8})*$/i),h9=O.enum([`dataforseo`,`semrush`,`ahrefs`,`serpbase`]),Zce=O.enum([`dataforseo`,`semrush`,`ahrefs`]),g9=O.enum([`google`,`bing`]).default(`google`),_9=O.enum([`desktop`,`mobile`]),v9=e=>O.string().trim().min(1).max(500).describe(e).optional(),Qce=O.strictObject({keyword:v9(`Source column containing the search query.`),url:v9(`Source column containing the ranking URL.`),position:v9(`Source column containing grouped position.`),absolutePosition:v9(`Source column containing absolute result position.`),searchVolume:v9(`Source column containing monthly search volume.`),keywordDifficulty:v9(`Source column containing keyword difficulty.`),cpc:v9(`Source column containing cost per click.`),paidCompetition:v9(`Source column containing paid competition.`),intent:v9(`Source column containing search intent.`),resultCount:v9(`Source column containing estimated result count.`),estimatedTraffic:v9(`Source column containing estimated monthly visits.`),resultType:v9(`Source column containing result type.`),searchVolumeUpdatedAt:v9(`Source column containing the search volume update date.`)}).superRefine((e,t)=>{let n=new Map;for(let[r,i]of Object.entries(e)){if(!i)continue;let e=Yu(i),a=n.get(e);a?t.addIssue({code:`custom`,message:`Source column "${i}" is already mapped to "${a}".`,path:[r]}):n.set(e,r)}}).describe(`Optional canonical field to source column mapping. Named fields override automatic header matching.`),y9=O.array(O.strictObject({dataset:O.literal(`ranked-keywords`),file:O.string().trim().min(1).max(4096),provider:Zce,exportedAt:O.string().trim().min(1).max(100),format:O.enum([`csv`,`json`,`jsonl`]).optional(),rowLimit:O.number().int().min(1).max(1e5).optional(),columns:Qce.optional()})).min(1).max(4).describe(`One to four local ranked-keyword exports from the same provider and market.`),b9=O.string().trim().min(3).max(253),x9=O.string().trim().min(1).max(2048),$ce=O.string().trim().min(1).max(80).optional(),S9=O.array(O.enum([`organic`,`paid`,`featured_snippet`,`local_pack`,`ai_overview_reference`])).min(1).max(5).optional(),C9={countryCode:p9,languageCode:m9,searchEngine:O.literal(`google`).default(`google`),provider:h9.optional(),projectId:$ce,refresh:O.boolean().optional()},ele=O.strictObject({domain:b9,siteType:O.enum([`business`,`publisher`,`directory`,`community`,`marketplace`,`unknown`])}),tle=O.strictObject({domain:b9,siteType:O.enum([`business`,`publisher`,`directory`,`community`,`marketplace`])}),nle=O.strictObject({domain:b9,site:x9.optional(),days:O.number().int().min(1).max(548).optional(),...C9}),rle=O.strictObject({target:O.string().trim().min(3).max(2048),site:x9.optional(),days:O.number().int().min(1).max(548).optional(),includeSubdomains:O.boolean().optional(),resultTypes:S9,minSearchVolume:O.number().int().min(0).optional(),maxRank:O.number().int().min(1).max(100).optional(),excludeTerms:O.array(O.string().trim().min(1).max(80)).max(5).optional(),limit:O.number().int().min(1).max(100).default(50),offset:O.number().int().min(0).max(1e5).default(0),researchFiles:y9.optional(),...C9}),ile=O.strictObject({domain:b9,site:x9.optional(),days:O.number().int().min(1).max(548).optional(),minEstimatedTraffic:O.number().min(0).optional(),minRankedKeywords:O.number().int().min(0).optional(),limit:O.number().int().min(1).max(100).default(50),offset:O.number().int().min(0).max(1e5).default(0),researchFiles:y9.optional(),...C9}),ale=O.strictObject({keywords:O.array(d9).min(2).max(200),targetDomain:b9.optional(),declaredCompetitors:O.array(ele).max(10).optional(),resultTypes:S9,limit:O.number().int().min(1).max(100).default(25),offset:O.number().int().min(0).max(1e5).default(0),researchFiles:y9.optional(),...C9}),ole=O.strictObject({site:x9,competitors:O.array(tle).min(1).max(3),days:O.number().int().min(1).max(548).optional(),limitPerDomain:O.number().int().min(1).max(250).default(100),candidateLimit:O.number().int().min(1).max(100).default(50),minSearchVolume:O.number().int().min(0).optional(),maxRank:O.number().int().min(1).max(100).optional(),includeSubdomains:O.boolean().optional(),researchFiles:y9.optional(),...C9});function w9(e){return{countryCode:e.countryCode,languageCode:e.languageCode,searchEngine:e.searchEngine}}function T9(e){return $(e.summary.verdict,u9(e))}function sle(e,t={}){e.registerTool(`seo_domain_overview`,{description:`Compare a bounded country-level domain footprint with optional owner-verified Search Console totals`,inputSchema:nle},async e=>{try{return T9(await(t.domainOverviewReport??wG)({...e,market:w9(e)}))}catch(e){return Q(e)}}),e.registerTool(`seo_ranked_keywords`,{description:`Review bounded provider keyword and ranking-page evidence with an optional matching Search Console comparison`,inputSchema:rle},async e=>{try{return T9(await(t.rankedKeywordsReport??TG)({...e,market:w9(e)}))}catch(e){return Q(e)}}),e.registerTool(`seo_ranking_pages`,{description:`Find a domain's bounded ranking-page footprint, repeated URL patterns, and optional matching Search Console pages`,inputSchema:ile},async e=>{try{return T9(await(t.rankingPagesReport??DG)({...e,market:w9(e)}))}catch(e){return Q(e)}}),e.registerTool(`seo_serp_competitors`,{description:`Identify repeated search competitors across an explicit bounded keyword set without guessing their business type`,inputSchema:ale},async e=>{try{return T9(await(t.serpCompetitorsReport??AG)({...e,market:w9(e)}))}catch(e){return Q(e)}}),e.registerTool(`seo_competitor_keyword_gap`,{description:`Compare up to three explicit domains with bounded provider keywords, Search Console themes, and programmatic page patterns`,inputSchema:ole},async e=>{try{return T9(await(t.competitorKeywordGapReport??SG)({...e,market:w9(e)}))}catch(e){return Q(e)}})}function cle(e){e.registerTool(`seo_content_groups`,{description:`List, create, or delete reusable page/query groups`,inputSchema:{action:O.enum([`list`,`add`,`delete`]),site:O.string().optional(),id:O.string().optional(),name:O.string().optional(),dimension:O.enum([`page`,`query`]).optional(),matchType:O.enum([`equals`,`contains`,`regex`]).optional(),pattern:O.string().optional()}},async({action:e,site:t,id:n,name:r,dimension:i,matchType:a,pattern:o})=>{try{if(e===`list`){let e=MG(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=PG(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=jG({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:O.enum([`list`,`add`,`delete`]),site:O.string().optional(),id:O.string().optional(),limit:O.number().optional(),scope:O.enum([`site`,`page`,`query`,`group`]).optional(),target:O.string().optional(),title:O.string().optional(),description:O.string().optional(),changedAt:O.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=sW({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=lW(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=oW({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 Google Analytics/control evidence`,inputSchema:{id:O.string().min(1).optional(),site:O.string().min(1).optional(),scope:O.enum([`site`,`page`,`query`,`group`]).optional(),target:O.string().min(1).optional(),title:O.string().min(1).optional(),changedAt:W7.optional(),googleAnalyticsPropertyId:O.string().min(1).optional(),controlScope:O.enum([`site`,`page`,`query`,`group`]).optional(),controlTarget:O.string().min(1).optional(),controlTitle:O.string().min(1).optional(),beforeDays:O.number().int().min(1).max(548).optional(),afterDays:O.number().int().min(1).max(548).optional(),refresh:O.boolean().optional()}},async({id:e,site:t,scope:n,target:r,title:i,changedAt:a,googleAnalyticsPropertyId:o,controlScope:s,controlTarget:c,controlTitle:l,beforeDays:u,afterDays:d,refresh:f})=>{try{let p=await cK({id:e,site:t,scope:n,target:r,title:i,changedAt:a,googleAnalyticsPropertyId: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)}})}const lle=O.strictObject({seeds:O.array(d9).min(1).max(5),sources:O.array(O.enum([`ideas`,`related`,`suggestions`])).min(1).max(3).default([`ideas`,`related`,`suggestions`]),countryCode:p9,languageCode:m9,searchEngine:g9,location:f9.optional(),device:_9.optional(),limit:O.number().int().min(1).max(100).default(50),provider:h9.optional(),refresh:O.boolean().optional()}).superRefine((e,t)=>{let n=new Set(e.seeds.map(e=>e.trim().replace(/\s+/gu,` `).toLowerCase())).size,r=new Set(e.sources).size*n;e.limit<r&&t.addIssue({code:`custom`,path:[`limit`],message:`Use a limit of at least ${r} to sample every requested source and seed.`})}),ule=O.strictObject({keyword:d9,countryCode:p9,languageCode:m9,searchEngine:g9,location:f9.optional(),device:_9.default(`desktop`),depth:O.number().int().min(1).max(100).default(10),provider:h9.optional(),refresh:O.boolean().optional()}),dle=O.strictObject({site:O.string().trim().min(1).max(2048),days:O.number().int().min(1).max(548).optional(),minImpressions:O.number().int().min(0).max(1e9).optional(),limit:O.number().int().min(1).max(25).optional(),keywordLimit:O.number().int().min(1).max(50).optional(),queriesPerPage:O.number().int().min(1).max(5).optional(),clusterLimit:O.number().int().min(1).max(20).optional(),brandTerms:O.array(O.string().trim().min(1).max(200)).max(20).optional(),includeBrand:O.boolean().optional(),includeExternal:O.boolean().default(!1),countryCode:p9.optional(),languageCode:m9.optional(),searchEngine:g9,location:f9.optional(),device:_9.optional(),provider:h9.optional(),refresh:O.boolean().optional()}).superRefine((e,t)=>{let n=!!(e.countryCode||e.languageCode||e.location||e.device||e.provider);!e.includeExternal&&n&&t.addIssue({code:`custom`,path:[`includeExternal`],message:`Set includeExternal to true before passing market or provider options.`}),e.includeExternal&&(!e.countryCode||!e.languageCode)&&t.addIssue({code:`custom`,path:[`includeExternal`],message:`External keyword context requires countryCode and languageCode.`})}),fle=O.strictObject({projectId:O.string().trim().min(1).max(80),set:O.string().trim().min(1).max(80),tag:O.string().trim().min(1).max(40).optional(),limit:O.number().int().min(1).max(1e3).default(100),offset:O.number().int().min(0).max(1e5).default(0),staleDays:O.number().int().min(1).max(365).default(45)}),ple=O.strictObject({projectId:O.string().trim().min(1).max(80),set:O.string().trim().min(1).max(80),targetDomain:O.string().trim().min(1).max(253),tag:O.string().trim().min(1).max(40).optional(),devices:O.array(O.enum([`desktop`,`mobile`])).min(1).max(2).refine(e=>new Set(e).size===e.length,{message:`Choose each device once.`}).optional(),provider:h9.optional(),collectionMethod:O.enum([`live`,`queued`]).optional(),cadence:O.enum([`manual`,`daily`,`weekly`,`monthly`]).default(`manual`),depth:O.number().int().min(1).max(100).optional(),keywordLimit:O.number().int().min(1).max(1e3).optional(),start:O.boolean().default(!0),outputLimit:O.number().int().min(1).max(250).default(100)});function mle(e,t={}){e.registerTool(`seo_saved_keywords`,{description:`Review one bounded local keyword set with metric freshness, tags, page mappings, and evidence limits`,inputSchema:fle},async({projectId:e,set:n,tag:r,limit:i,offset:a,staleDays:o})=>{try{let s=(t.savedKeywordSetReport??C0)({projectId:e,idOrName:n,tag:r,limit:i,offset:a,staleDays:o});return $(s.summary.verdict,u9(s))}catch(e){return Q(e)}}),e.registerTool(`seo_rank_tracking`,{description:`Collect and compare exact market and device-specific ranks for one bounded saved keyword set, with local history, queued recovery, coverage, and cost evidence`,inputSchema:ple},async e=>{try{let n=await(t.rankTrackingReport??N1)(e);return $(n.summary.verdict,u9(n))}catch(e){return Q(e)}}),e.registerTool(`seo_keyword_metrics`,{description:`Compare bounded third-party keyword demand, cost, competition, difficulty, intent, result-count, and trend evidence`,inputSchema:{keywords:O.array(d9).min(1).max(50),countryCode:p9,languageCode:m9,searchEngine:g9,location:f9.optional(),device:_9.optional(),provider:h9.optional(),refresh:O.boolean().optional()}},async({keywords:e,countryCode:n,languageCode:r,searchEngine:i,location:a,device:o,provider:s,refresh:c})=>{try{let l=await(t.keywordMetricsReport??ZO)({keywords:e,market:{countryCode:n,languageCode:r,searchEngine:i,location:a,device:o},provider:s,refresh:c});return $(l.summary.verdict,u9(l))}catch(e){return Q(e)}}),e.registerTool(`seo_keyword_research`,{description:`Discover bounded keyword ideas with typed source, market, metric, coverage, cache, and cost evidence`,inputSchema:lle},async({seeds:e,sources:n,countryCode:r,languageCode:i,searchEngine:a,location:o,device:s,limit:c,provider:l,refresh:u})=>{try{let d=await(t.keywordResearchReport??ek)({seeds:e,sources:n,market:{countryCode:r,languageCode:i,searchEngine:a,location:o,device:s},limit:c,provider:l,refresh:u});return $(d.summary.verdict,u9(d))}catch(e){return Q(e)}}),e.registerTool(`seo_serp_results`,{description:`Inspect one bounded live result snapshot with exact organic ranks, domains, features, market, cache, and cost evidence`,inputSchema:ule},async({keyword:e,countryCode:n,languageCode:r,searchEngine:i,location:a,device:o,depth:s,provider:c,refresh:l})=>{try{let u=await(t.serpResultsReport??lj)({keyword:e,market:{countryCode:n,languageCode:r,searchEngine:i,location:a,device:o},depth:s,provider:c,refresh:l});return $(u.summary.verdict,u9(u))}catch(e){return Q(e)}}),e.registerTool(`seo_keyword_opportunities`,{description:`Combine bounded Search Console opportunity evidence with optional provider-neutral keyword context and programmatic SEO clusters`,inputSchema:dle},async({site:e,days:n,minImpressions:r,limit:i,keywordLimit:a,queriesPerPage:o,clusterLimit:s,brandTerms:c,includeBrand:l,includeExternal:u,countryCode:d,languageCode:f,searchEngine:p,location:m,device:h,provider:g,refresh:_})=>{try{let v=await(t.keywordOpportunitiesReport??Rq)({site:e,days:n,minImpressions:r,limit:i,keywordLimit:a,queriesPerPage:o,clusterLimit:s,brandTerms:c,includeBrand:l,includeExternal:u,...u&&d&&f?{market:{countryCode:d,languageCode:f,searchEngine:p,location:m,device:h}}:{},provider:g,refresh:_});return $(v.summary.verdict,u9(v))}catch(e){return Q(e)}})}function hle(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:O.string().url(),site:O.string().optional(),limit:O.number().optional(),refresh:O.boolean().optional(),js:O.boolean().optional()}},async({startUrl:e,site:t,limit:n,refresh:r,js:i})=>{try{let a=await GJ({startUrl:e,site:t,limit:n,refresh:r,js:K7(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:O.string(),urls:O.array(O.string().url()).min(1).max(100),languageCode:O.string().min(1).max(35).optional(),dailyLimit:O.number().int().min(1).max(2e3).optional()}},async({site:e,urls:t,languageCode:n,dailyLimit:r})=>{try{let i=await BY({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:O.string(),sitemaps:O.array(O.string().url()).min(1).max(20),properties:O.array(O.string()).optional(),dailyLimit:O.number().int().min(1).max(2e3).optional(),targetCycleDays:O.number().int().min(1).max(365).optional(),maxUrls:O.number().int().min(1).max(25e4).optional()}},async({site:e,sitemaps:t,properties:n,dailyLimit:r,targetCycleDays:i,maxUrls:a})=>{try{let o=await CY({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:O.string().min(1),crawlReportId:O.string().min(1).optional(),sitemaps:O.array(O.string().url()).min(1).max(20).optional(),days:O.number().int().min(1).max(548).optional(),rowLimit:O.number().int().min(1).max(25e4).optional(),maxSitemapUrls:O.number().int().min(1).max(25e4).optional(),itemsPerSection:O.number().int().min(1).max(1e3).optional(),templateClusters:O.number().int().min(1).max(200).optional(),templateSamples:O.number().int().min(1).max(25).optional(),refresh:O.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 dY({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:O.string(),sitemaps:O.array(O.string().url()).min(1).max(20),properties:O.array(O.string()).optional(),dailyLimit:O.number().int().min(1).max(2e3).optional(),inspectLimit:O.number().int().min(1).max(100).optional(),maxUrls:O.number().int().min(1).max(25e4).optional(),languageCode:O.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 KY({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:O.string().url(),maxHops:O.number().optional(),refresh:O.boolean().optional(),js:O.boolean().optional()}},async({url:e,maxHops:t,refresh:n,js:r})=>{try{let i=await $Y({url:e,maxHops:t,refresh:n,js:K7(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:{...q7([`site`,`days`,`limit`,`minImpressions`,`refresh`,`js`]),minClicks:O.number().optional()}},async({site:e,days:t,limit:n,minClicks:r,minImpressions:i,refresh:a,js:o})=>{try{let s=await iX({site:e,days:t,limit:n,minClicks:r,minImpressions:i,refresh:a,js:K7(o,`auto`)});return $(`Checked ${s.summary.checked} search-value URLs. ${s.summary.recoverable} recoverable issues.`,s)}catch(e){return Q(e)}})}function gle(e,t={}){e.registerTool(`seo_cannibal`,{description:`Find multi-URL query exposure candidates for intent and technical review`,inputSchema:{...q7([`site`,`days`,`limit`,`minImpressions`,`includeBrand`,`refresh`]),days:O.number().int().min(1).max(548).optional(),limit:O.number().int().min(1).max(100).optional(),minImpressions:O.number().int().min(0).max(1e9).optional(),brandTerms:O.array(O.string().trim().min(1).max(200)).max(20).optional(),researchFiles:y9.optional()}},async({site:e,days:t,limit:n,minImpressions:r,brandTerms:i,includeBrand:a,refresh:o,researchFiles:s})=>{try{let c=s?await KH({site:e,days:t,limit:n,minImpressions:r,brandTerms:i,includeBrand:a,refresh:o,researchFiles:s}):await KH({site:e,days:t,limit:n,minImpressions:r,brandTerms:i,includeBrand:a,refresh:o});return $(c.summary.verdict,c)}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:{...q7([`site`,`days`,`limit`,`includeBrand`,`refresh`]),days:O.number().int().min(1).max(548).optional(),limit:O.number().int().min(1).max(100).optional(),comparison:O.enum([`previous-period`,`year-over-year`]).optional(),minDropPct:O.number().min(0).max(100).optional(),minPreviousClicks:O.number().min(0).max(1e9).optional(),minClickLoss:O.number().min(0).max(1e9).optional(),brandTerms:O.array(O.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 cU({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:{...q7([`site`,`days`,`limit`,`minImpressions`,`verifyContent`,`verifyLimit`,`includeBrand`,`js`,`fetchConcurrency`,`fetchIntervalCap`,`fetchIntervalMs`,`refresh`]),days:O.number().int().min(1).max(548).optional(),limit:O.number().int().min(1).max(100).optional(),minImpressions:O.number().int().min(0).max(1e9).optional(),verifyLimit:O.number().int().min(0).max(100).optional(),brandTerms:O.array(O.string().trim().min(1).max(200)).max(20).optional(),fetchConcurrency:O.number().int().min(1).max(16).optional(),fetchIntervalCap:O.number().int().min(1).max(60).optional(),fetchIntervalMs:O.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??MU)({site:e,days:n,limit:r,minImpressions:i,brandTerms:a,verifyContent:o,verifyLimit:s,includeBrand:c,js:K7(l,void 0),rate:i9({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:{...q7([`site`,`days`,`limit`,`checkLimit`,`minImpressions`,`includeBrand`,`js`,`fetchConcurrency`,`fetchIntervalCap`,`fetchIntervalMs`,`refresh`]),targetUrl:O.string().url().refine(e=>/^https?:\/\//.test(e),`Use an HTTP(S) URL.`),days:O.number().int().min(1).max(548).optional(),limit:O.number().int().min(1).max(100).optional(),checkLimit:O.number().int().min(1).max(200).optional(),minImpressions:O.number().min(0).max(1e9).optional(),brandTerms:O.array(O.string().trim().min(1).max(200)).max(20).optional(),fetchConcurrency:O.number().int().min(1).max(16).optional(),fetchIntervalCap:O.number().int().min(1).max(60).optional(),fetchIntervalMs:O.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??RK)({site:e,targetUrl:n,days:r,limit:i,checkLimit:a,minImpressions:o,brandTerms:s,includeBrand:c,js:K7(l,void 0),rate:i9({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:{...q7([`site`,`minImpressions`,`limit`,`includeBrand`,`refresh`]),site:O.string().trim().min(1),minImpressions:O.number().int().min(1).max(1e9).optional(),limit:O.number().int().min(1).max(100).optional(),brandTerms:O.array(O.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??DV)({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:{...q7([`site`,`includeBrand`,`minImpressions`,`limit`,`refresh`]),site:O.string().trim().min(1),scope:O.string().trim().min(1).max(2e3).optional(),minImpressions:O.number().int().min(1).max(1e9).optional(),limit:O.number().int().min(1).max(100).optional(),brandTerms:O.array(O.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??XZ)({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)}})}function E9(e){if(e.rows.length<=14&&e.outputSelection)return e;let t=e.rows.slice(-14);return{...e,rows:t,outputSelection:{strategy:`most-recent`,availableRows:e.rows.length,returnedRows:t.length,omittedRows:Math.max(0,e.rows.length-t.length)}}}function _le(e){return{...e,traffic:e.traffic.status===`unavailable`?e.traffic:{...e.traffic,data:E9(e.traffic.data)},crawl:e.crawl.status===`unavailable`?e.crawl:{...e.crawl,data:E9(e.crawl.data)},outputBudget:e.outputBudget}}function vle(e){e.registerTool(`seo_bing_webmaster_overview`,{description:`Find bounded Bing traffic, crawl, query, and page insights for one verified site`,inputSchema:{site:O.string().url().max(2e3)}},async({site:e})=>{try{let t=await A4({site:e}),n=t.findings[0]?.title;return $(`Bing evidence is ${t.dataStatus} with ${t.summary.findings} findings.${n?` Review first: ${n}.`:``}`,_le(t))}catch(e){return Q(e)}}),e.registerTool(`seo_link_evidence`,{description:`Review bounded referring-link evidence from Ahrefs, DataForSEO, Bing Webmaster or a local export`,inputSchema:{site:O.string().url().max(2e3).optional(),provider:O.enum([`ahrefs`,`dataforseo`,`bing`]).optional(),target:O.string().trim().min(1).max(2048).optional(),scope:O.enum([`domain`,`page`]).optional(),includeSubdomains:O.boolean().optional(),searchConsoleSite:O.string().trim().min(1).max(2048).optional(),file:O.string().min(1).max(4096).optional(),format:O.enum([`csv`,`json`,`jsonl`]).optional(),rowLimit:O.number().int().min(1).max(1e5).optional(),targetLimit:O.number().int().min(1).max(50).optional(),detailPagesPerTarget:O.number().int().min(1).max(3).optional(),limit:O.number().int().min(1).max(500).optional(),days:O.number().int().min(1).max(548).optional(),refresh:O.boolean().optional()}},async({site:e,provider:t,target:n,scope:r,includeSubdomains:i,searchConsoleSite:a,file:o,format:s,rowLimit:c,targetLimit:l,detailPagesPerTarget:u,limit:d,days:f,refresh:p})=>{try{let m=t??(n?`dataforseo`:`bing`);if(Number(!!o)+Number(!!e)+Number(!!n)!==1)throw new j(`INVALID_INPUT`,`Pass one link source: file, site for Bing, or target for Ahrefs or DataForSEO.`);if(o&&t)throw new j(`INVALID_INPUT`,`Do not pass provider with a local link file.`);if([`ahrefs`,`dataforseo`].includes(m)&&!n)throw new j(`INVALID_INPUT`,`Pass target for Ahrefs or DataForSEO link evidence.`);if(m===`bing`&&!e&&!o)throw new j(`INVALID_INPUT`,`Pass site for Bing link evidence.`);let h=o?await q3({file:o,format:s,rowLimit:c}):m===`ahrefs`?await g3({target:n??``,scope:r,includeSubdomains:i,rowLimit:c,refresh:p}):m===`dataforseo`?await U3({target:n??``,scope:r,includeSubdomains:i,rowLimit:c,refresh:p}):await k3({site:e??``,rowLimit:c,targetLimit:l,detailPagesPerTarget:u}),g=X3({evidence:h,limit:d,targetContext:a||h.externalProvider?await H3({evidence:h,searchConsoleSite:a,crawlSite:a??n,days:f,refresh:p}):void 0});return $(`${g.summary.observedLinks} referring links were retained from ${g.provenance.provider}.`,g)}catch(e){return Q(e)}})}const yle=O.strictObject({site:O.string().trim().min(1).max(2048),days:O.number().int().min(1).max(548).optional(),sitemaps:O.array(O.string().url()).max(20).optional(),maxSitemapUrls:O.number().int().min(1).max(1e5).optional(),templateLimit:O.number().int().min(1).max(25).optional(),clusterLimit:O.number().int().min(1).max(25).optional(),minimumTemplateUrls:O.number().int().min(2).max(100).optional(),minimumTemplateShare:O.number().min(0).max(1).optional(),minimumTemplateImpressions:O.number().min(0).max(1e9).optional(),brandTerms:O.array(O.string().trim().min(1).max(200)).max(20).optional(),includeBrand:O.boolean().optional(),includeExternal:O.boolean().default(!1),countryCode:p9.optional(),languageCode:m9.optional(),searchEngine:g9,location:f9.optional(),device:_9.optional(),provider:h9.optional(),discoverySources:O.array(O.enum([`ideas`,`related`,`suggestions`])).min(1).max(3).default([`suggestions`]),discoveryLimit:O.number().int().min(1).max(100).optional(),candidateLimit:O.number().int().min(1).max(25).optional(),serpLimit:O.number().int().min(0).max(3).default(0),serpDepth:O.number().int().min(1).max(20).optional(),refresh:O.boolean().optional()}).superRefine((e,t)=>{let n=!!(e.countryCode||e.languageCode||e.location||e.device||e.provider||e.serpLimit);!e.includeExternal&&n&&t.addIssue({code:`custom`,path:[`includeExternal`],message:`Set includeExternal to true before passing market, provider, or SERP options.`}),e.includeExternal&&(!e.countryCode||!e.languageCode)&&t.addIssue({code:`custom`,path:[`includeExternal`],message:`External pSEO research requires countryCode and languageCode.`})});function ble(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 xle(e,t={}){e.registerTool(`seo_pseo_audit`,{description:`Audit pSEO templates with first-party GSC, optional crawl, and optional URL Inspection evidence`,inputSchema:{site:O.string().min(1),days:O.number().int().min(1).max(548).optional(),sitemaps:O.array(O.string().url()).max(20).optional(),maxSitemapUrls:O.number().int().min(1).max(1e5).optional(),templateLimit:O.number().int().min(1).max(100).optional(),minimumTemplateUrls:O.number().int().min(2).max(100).optional(),minimumTemplateShare:O.number().min(0).max(1).optional(),minimumTemplateImpressions:O.number().min(0).max(1e9).optional(),crawlSamples:O.number().int().min(0).max(10).optional(),inspectSamples:O.number().int().min(0).max(10).optional(),brandTerms:O.array(O.string().min(1).max(200)).max(20).optional(),includeBrand:O.boolean().optional(),refresh:O.boolean().optional(),js:O.boolean().optional(),fetchConcurrency:O.number().int().min(1).max(16).optional(),fetchIntervalCap:O.number().int().min(1).max(100).optional(),fetchIntervalMs:O.number().int().min(100).max(6e4).optional(),detail:O.enum([`summary`,`full`]).optional()}},async({site:e,days:n,sitemaps:r,templateLimit:i,maxSitemapUrls:a,minimumTemplateUrls:o,minimumTemplateShare:s,minimumTemplateImpressions:c,crawlSamples:l,inspectSamples:u,brandTerms:d,includeBrand:f,refresh:p,js:m,fetchConcurrency:h,fetchIntervalCap:g,fetchIntervalMs:_,detail:v})=>{try{let y=await(t.pseoAuditReport??TZ)({site:e,days:n,sitemaps:r,maxSitemapUrls:a,templateLimit:i,minimumTemplateUrls:o,minimumTemplateShare:s,minimumTemplateImpressions:c,crawlSamples:l,inspectSamples:u,brandTerms:d,includeBrand:f,refresh:p,js:K7(m,`auto`),rate:{concurrency:h,intervalCap:g,intervalMs:_}}),b=v===`full`;return $(`${Y(y.summary.templates,`pSEO template`)} audited from ${Y(y.summary.gscPages,`GSC page`)}.`,b?{report:y,presentation:jZ(y)}:{report:ble(y)},b?{markdown:MZ(y)}:void 0)}catch(e){return Q(e)}}),e.registerTool(`seo_pseo_opportunities`,{description:`Combine bounded template, Search Console cluster, optional keyword discovery, live SERP, competitor pattern, cost, and data-source evidence for programmatic SEO research`,inputSchema:yle},async({site:e,days:n,sitemaps:r,maxSitemapUrls:i,templateLimit:a,clusterLimit:o,minimumTemplateUrls:s,minimumTemplateShare:c,minimumTemplateImpressions:l,brandTerms:u,includeBrand:d,includeExternal:f,countryCode:p,languageCode:m,searchEngine:h,location:g,device:_,provider:v,discoverySources:y,discoveryLimit:b,candidateLimit:x,serpLimit:S,serpDepth:C,refresh:ee})=>{try{let w=await(t.pseoOpportunitiesReport??oQ)({site:e,days:n,sitemaps:r,maxSitemapUrls:i,templateLimit:a,clusterLimit:o,minimumTemplateUrls:s,minimumTemplateShare:c,minimumTemplateImpressions:l,brandTerms:u,includeBrand:d,includeExternal:f,market:p&&m?{countryCode:p,languageCode:m,searchEngine:h,location:g,device:_}:void 0,provider:v,discoverySources:y,discoveryLimit:b,candidateLimit:x,serpLimit:S,serpDepth:C,refresh:ee});return $(w.summary.verdict,u9(w))}catch(e){return Q(e)}})}const D9=O.strictObject({label:O.string().trim().min(1).max(250),aliases:O.array(O.string().trim().min(1).max(250)).max(5).optional()}),O9=O.strictObject({target:D9,competitors:O.array(D9).max(5).optional(),domain:O.string().trim().min(1).max(2048).optional(),surface:O.enum([`google-ai-overview`,`chatgpt`]),countryCode:p9,languageCode:m9,location:f9,provider:h9.optional(),includeSamples:O.boolean().default(!0),sampleLimit:O.number().int().min(1).max(25).default(10),site:O.string().trim().min(1).max(2048).optional(),days:O.number().int().min(1).max(548).optional(),refresh:O.boolean().optional()});function Sle(e={}){return async t=>{let{target:n,competitors:r,domain:i,surface:a,countryCode:o,languageCode:s,location:c,provider:l,includeSamples:u,sampleLimit:d,site:f,days:p,refresh:m}=O9.parse(t);try{let t=await(e.aiMentionResearchReport??Qp)({target:n,competitors:r,domain:i,market:{surface:a,countryCode:o,languageCode:s,location:c},provider:l,includeSamples:u,sampleLimit:d,site:f,days:p,refresh:m});return $(t.summary.verdict,u9(t))}catch(e){return Q(e)}}}const Cle=O.strictObject({id:O.string().trim().min(1).max(100).optional().describe(`Stable local id used to match this prompt over time.`),group:O.string().trim().min(1).max(100).optional().describe(`Optional local label for related prompts.`),prompt:O.string().trim().min(1).max(500).describe(`Exact question or instruction sent to the selected models.`)}),wle=O.strictObject({surface:O.enum([`chatgpt`,`claude`,`gemini`,`perplexity`]).describe(`AI product to observe.`),model:O.string().trim().min(1).max(200).describe(`Exact current model name from the provider catalog. Stale names fail before paid work.`)}),k9=O.strictObject({label:O.string().trim().min(1).max(250).describe(`Name shown for this target in the report.`),aliases:O.array(O.string().trim().min(1).max(250)).max(5).optional().describe(`Other exact names that identify this target in an answer.`),domains:O.array(O.string().trim().min(1).max(2048)).max(5).optional().describe(`Domains that identify this target in returned citations.`)}),A9=O.strictObject({prompts:O.array(Cle).min(1).max(5).describe(`One to five fixed prompts. Start with the smallest useful set.`),models:O.array(wle).min(1).max(4).describe(`One to four exact surface and model pairs. Every prompt and model pair starts one request.`),target:k9.describe(`Primary brand, product, person, or site to find.`),competitors:O.array(k9).max(5).optional().describe(`Optional named comparisons checked in the same retained answers.`),countryCode:p9.describe(`Two-letter country label for this observation set. It is not a precise user location filter.`),languageCode:m9.describe(`Language label for this observation set. Write the prompt in the language you need.`),provider:O.literal(`dataforseo`).optional().describe(`Live answer provider. DataForSEO is the supported provider today.`),webSearch:O.boolean().default(!0).describe(`Ask supported models to use web search. Perplexity always uses web search.`),maxOutputTokens:O.number().int().min(1).max(4096).default(2048).describe(`Maximum generated tokens requested for each answer.`),site:O.string().trim().min(1).max(2048).optional().describe(`Optional Search Console property used for first-party query context.`),days:O.number().int().min(1).max(548).optional().describe(`Optional Search Console lookback window. Defaults to 90 days.`),refresh:O.boolean().optional().describe(`Bypass cached provider answers and request fresh observations.`)});function Tle(e={}){return async t=>{let{prompts:n,models:r,target:i,competitors:a,countryCode:o,languageCode:s,provider:c,webSearch:l,maxOutputTokens:u,site:d,days:f,refresh:p}=A9.parse(t);try{let t=await(e.aiPromptObservationsReport??Am)({prompts:n,models:r,target:i,competitors:a,market:{countryCode:o,languageCode:s},provider:c,webSearch:l,maxOutputTokens:u,site:d,days:f,refresh:p});return $(t.summary.verdict,u9(t))}catch(e){return Q(e)}}}const Ele=O.enum([`ideas`,`related`,`suggestions`]),j9=O.strictObject({target:O.string().trim().min(3).max(253),seeds:O.array(d9).min(1).max(5),countryCode:p9,languageCode:m9,searchEngine:O.literal(`google`).default(`google`),device:_9.default(`desktop`),keywordProvider:h9.optional(),discoverySources:O.array(Ele).min(1).max(3).default([`ideas`,`related`,`suggestions`]),discoveryLimit:O.number().int().min(1).max(100).default(30),keywordLimit:O.number().int().min(1).max(10).default(5),serpProvider:h9.optional(),serpDepth:O.number().int().min(1).max(20).default(10),competitorLimit:O.number().int().min(1).max(10).default(5),competitionEvidence:O.enum([`serp`,`domain-rating`,`link-summary`]).default(`domain-rating`),linkProvider:O.enum([`ahrefs`,`dataforseo`]).optional(),projectId:O.string().trim().min(1).max(80).optional(),refresh:O.boolean().optional()}).superRefine((e,t)=>{let n=new Set(e.seeds.map(e=>e.trim().replace(/\s+/gu,` `).toLowerCase())).size*new Set(e.discoverySources).size;e.discoveryLimit<n&&t.addIssue({code:`custom`,path:[`discoveryLimit`],message:`Use a discoveryLimit of at least ${n} to sample every requested source and seed.`}),e.linkProvider!==void 0&&e.competitionEvidence!==`link-summary`&&t.addIssue({code:`custom`,path:[`linkProvider`],message:`Set competitionEvidence to link-summary before choosing a link provider.`})});function Dle(e={}){return async t=>{let n=j9.parse(t);try{let t=await(e.competitiveOpportunitiesReport??xj)({target:n.target,seeds:n.seeds,market:{countryCode:n.countryCode,languageCode:n.languageCode,searchEngine:n.searchEngine,device:n.device},keywordProvider:n.keywordProvider,discoverySources:n.discoverySources,discoveryLimit:n.discoveryLimit,keywordLimit:n.keywordLimit,serpProvider:n.serpProvider,serpDepth:n.serpDepth,competitorLimit:n.competitorLimit,competitionEvidence:n.competitionEvidence,linkProvider:n.linkProvider,projectId:n.projectId,refresh:n.refresh});return $(t.summary.verdict,u9(t))}catch(e){return Q(e)}}}const M9=O.strictObject({target:O.string().trim().min(3).max(2048),targetMode:O.enum([`domain`,`url`]).default(`domain`),provider:h9.optional(),refresh:O.boolean().optional()});function Ole(e={}){return async t=>{let n=M9.parse(t);try{let t=await(e.domainRatingReport??Lk)(n);return $(t.summary.verdict,u9(t))}catch(e){return Q(e)}}}const N9=O.strictObject({site:O.string().trim().min(1).max(2048),days:O.number().int().min(1).max(548).optional(),locationTerms:O.array(O.string().trim().min(1).max(100)).max(100).optional(),minImpressions:O.number().int().min(0).max(1e9).optional(),limit:O.number().int().min(1).max(100).optional(),maxRows:O.number().int().min(1).max(5e4).optional(),brandTerms:O.array(O.string().trim().min(1).max(200)).max(20).optional(),includeBrand:O.boolean().optional(),includeSerps:O.boolean().default(!1),countryCode:p9.optional(),languageCode:m9.optional(),searchEngine:g9,location:f9.optional(),device:_9.optional(),provider:h9.optional(),serpLimit:O.number().int().min(1).max(3).optional(),serpDepth:O.number().int().min(1).max(20).optional(),googleAnalyticsPropertyId:O.string().trim().regex(/^(?:properties\/)?\d{1,30}$/u).describe(`Optional numeric Google Analytics property id for landing-page geography evidence.`).optional(),analyticsLimit:O.number().int().min(1).max(1e4).describe(`Maximum Google Analytics geography rows to request.`).optional(),refresh:O.boolean().optional()}).superRefine((e,t)=>{let n=!!(e.countryCode||e.languageCode||e.location||e.device||e.provider||e.serpLimit||e.serpDepth);!e.includeSerps&&n&&t.addIssue({code:`custom`,path:[`includeSerps`],message:`Set includeSerps to true before passing market, provider, or SERP options.`}),e.includeSerps&&(!e.countryCode||!e.languageCode||!e.location)&&t.addIssue({code:`custom`,path:[`includeSerps`],message:`Local SERP evidence requires countryCode, languageCode, and a canonical location.`}),e.analyticsLimit&&!e.googleAnalyticsPropertyId&&t.addIssue({code:`custom`,path:[`analyticsLimit`],message:`Pass googleAnalyticsPropertyId before setting analyticsLimit.`})});function kle(e={}){return async t=>{let{site:n,days:r,locationTerms:i,minImpressions:a,limit:o,maxRows:s,brandTerms:c,includeBrand:l,includeSerps:u,countryCode:d,languageCode:f,searchEngine:p,location:m,device:h,provider:g,serpLimit:_,serpDepth:v,googleAnalyticsPropertyId:y,analyticsLimit:b,refresh:x}=N9.parse(t);try{let t=await(e.localSearchReport??OJ)({site:n,days:r,locationTerms:i,minImpressions:a,limit:o,maxRows:s,brandTerms:c,includeBrand:l,includeSerps:u,...u&&d&&f&&m?{market:{countryCode:d,languageCode:f,searchEngine:p,location:m,device:h}}:{},provider:g,serpLimit:_,serpDepth:v,googleAnalyticsPropertyId:y,analyticsLimit:b,refresh:x});return $(t.summary.verdict,u9(t))}catch(e){return Q(e)}}}const Ale=O.enum(sQ),jle=O.enum([`evidence-led`,`complete-set`]).default(`evidence-led`),P9=O.union([O.string().trim().min(1).max(80),O.strictObject({id:O.string().trim().min(1).max(80).regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/u),label:O.string().trim().min(1).max(80)})]),Mle=O.array(O.string().trim().min(1).max(120)).min(1).max(SQ.queryTemplates),F9={id:O.string().trim().min(1).max(80).regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/u),kind:Ale,coveragePolicy:jle,queryTemplates:Mle,pathTemplate:O.string().trim().min(1).max(200).startsWith(`/`).optional()},Nle=O.strictObject({...F9,shape:O.literal(`terms`),values:O.array(P9).min(1).max(SQ.valuesPerSet)}),Ple=O.strictObject({...F9,shape:O.literal(`pairs`),values:O.array(P9).min(2).max(SQ.valuesPerSet),pairing:O.enum([`anchor`,`all-pairs`,`explicit`]),anchor:O.string().trim().min(1).max(80).optional(),pairs:O.array(O.strictObject({left:O.string().trim().min(1).max(80),right:O.string().trim().min(1).max(80)})).min(1).max(SQ.explicitPairs).optional()}).superRefine((e,t)=>{e.pairing===`anchor`&&!e.anchor&&t.addIssue({code:`custom`,path:[`anchor`],message:`Anchor pairing requires anchor.`}),e.pairing!==`anchor`&&e.anchor!==void 0&&t.addIssue({code:`custom`,path:[`anchor`],message:`anchor is only valid for anchor pairing.`}),e.pairing===`explicit`&&!e.pairs&&t.addIssue({code:`custom`,path:[`pairs`],message:`Explicit pairing requires pairs.`}),e.pairing!==`explicit`&&e.pairs!==void 0&&t.addIssue({code:`custom`,path:[`pairs`],message:`pairs is only valid for explicit pairing.`})}),Fle=O.strictObject({...F9,shape:O.literal(`matrix`),axes:O.array(O.strictObject({id:O.string().trim().min(1).max(40).regex(/^[a-z][a-z0-9-]*$/u),values:O.array(P9).min(1).max(SQ.valuesPerSet)})).min(1).max(SQ.matrixAxes)}),Ile=O.discriminatedUnion(`shape`,[Nle,Ple,Fle]).superRefine((e,t)=>{cQ.find(t=>t.kind===e.kind)?.shapes.includes(e.shape)||t.addIssue({code:`custom`,path:[`shape`],message:`${e.kind} does not support ${e.shape}.`})}),I9=O.strictObject({site:O.string().trim().min(1).max(2048),days:O.number().int().min(1).max(548).optional(),sitemaps:O.array(O.string().url()).max(20).optional(),maxSitemapUrls:O.number().int().min(1).max(1e5).optional(),templateLimit:O.number().int().min(1).max(25).optional(),minimumTemplateUrls:O.number().int().min(2).max(100).optional(),minimumTemplateShare:O.number().min(0).max(1).optional(),minimumTemplateImpressions:O.number().min(0).max(1e9).optional(),brandTerms:O.array(O.string().trim().min(1).max(200)).max(20).optional(),includeBrand:O.boolean().default(!0),patternSets:O.array(Ile).max(SQ.patternSets).default([]),candidateLimit:O.number().int().min(1).max(SQ.candidates).default(100),observedQueryLimit:O.number().int().min(1).max(SQ.observedQueries).default(100),includeExternal:O.boolean().default(!1),countryCode:p9.optional(),languageCode:m9.optional(),searchEngine:g9,location:f9.optional(),device:_9.optional(),provider:h9.optional(),keywordLimit:O.number().int().min(0).max(SQ.keywordMetrics).optional(),serpLimit:O.number().int().min(0).max(SQ.serps).default(0),serpDepth:O.number().int().min(1).max(SQ.serpDepth).default(10),projectId:O.string().trim().min(1).max(80).optional(),refresh:O.boolean().optional()}).superRefine((e,t)=>{let n=!!(e.countryCode||e.languageCode||e.location||e.device||e.provider||(e.keywordLimit??0)>0||e.serpLimit);!e.includeExternal&&n&&t.addIssue({code:`custom`,path:[`includeExternal`],message:`Set includeExternal to true before passing market, provider, keyword, or SERP options.`}),e.includeExternal&&(!e.countryCode||!e.languageCode)&&t.addIssue({code:`custom`,path:[`includeExternal`],message:`External pSEO pattern research requires countryCode and languageCode.`})});function Lle(e={}){return async t=>{try{let n=I9.parse(t),r=await(e.pseoPatternsReport??c$)({site:n.site,days:n.days,sitemaps:n.sitemaps,maxSitemapUrls:n.maxSitemapUrls,templateLimit:n.templateLimit,minimumTemplateUrls:n.minimumTemplateUrls,minimumTemplateShare:n.minimumTemplateShare,minimumTemplateImpressions:n.minimumTemplateImpressions,brandTerms:n.brandTerms,includeBrand:n.includeBrand,patternSets:n.patternSets,candidateLimit:n.candidateLimit,observedQueryLimit:n.observedQueryLimit,includeExternal:n.includeExternal,market:n.countryCode&&n.languageCode?{countryCode:n.countryCode,languageCode:n.languageCode,searchEngine:n.searchEngine,location:n.location,device:n.device}:void 0,provider:n.provider,keywordLimit:n.keywordLimit,serpLimit:n.serpLimit,serpDepth:n.serpDepth,projectId:n.projectId,refresh:n.refresh});return $(r.summary.verdict,u9(r))}catch(e){return Q(e)}}}const Rle={"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`,`endpoint checks for Link headers and optional well-known files, where info means absent and optional`,`for any failed or warning check, request seo_describe_report with the check id to get fix guidance`],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 and emerging endpoints such as MCP server cards, A2A agent cards, OAuth metadata, and auth.md are observations, not requirements or defects.`],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 Google Analytics 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`,`topFixes AND reviewObservations together; the two lists are the complete grouped issue inventory, so carry every group from both into findings`,`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.`}]},"bing-webmaster-overview":{readOrder:[`dataStatus and provenance`,`summary and findings, including verification steps`,`traffic analysis current and previous periods, then changes`,`crawl analysis current and previous snapshots, then changes`,`query and page coverage before movements or opportunities`,`outputBudget, per-section outputSelection, and caveats`],doNotClaim:[`Bing evidence does not describe Google or the whole search market, and inIndex is not URL-level proof of indexing.`,`A missing weekly top-list entry is not zero traffic, and movements require observation in every week of both periods.`,`Opportunity thresholds prioritize review and are not Bing requirements.`,`Partial, capped, invalid, or unavailable rows cannot support an all-clear.`],verify:`Check the same verified site in Bing Webmaster Tools and investigate the exact dates behind any crawl or traffic change.`,related:[{id:`search-performance-overview`,reason:`Adds Search Console evidence.`},{id:`site-crawl`,reason:`Checks the current site directly.`},{id:`link-recovery`,reason:`Finds broken search-value URLs.`}]},"link-evidence":{readOrder:[`dataStatus, provenance, warnings, and caveats`,`providerSummary and providerEvidence coverage, cache, cost, filters, and observation time when present`,`selection, outputBudget, and retained summary counts`,`findings and targetContext with crawl and Search Console evidence states`,`targetCounts, then the bounded representative links list and provider-native metrics`],doNotClaim:[`The retained rows are not a complete backlink index, and missing rows do not prove a link does not exist.`,`Observed links do not establish quality, authority, value, or ranking impact.`,`Provider rank and spam metrics keep their provider-specific scales. They are not search-engine ranking factors or interchangeable authority scores.`,`A missing Search Console row is not zero, and a saved crawl finding does not prove the target still has that state.`],verify:`Open a selected referring URL to confirm the link, then check any flagged target live with redirect-trace or audit-page before changing it.`,related:[{id:`link-recovery`,reason:`Finds broken search-value URLs.`},{id:`redirect-trace`,reason:`Checks a target redirect chain.`},{id:`site-crawl`,reason:`Checks internal links and live pages.`},{id:`search-performance-overview`,reason:`Adds broader first-party search context.`}]},"server-log-analysis":{readOrder:[`dataStatus, provenance coverage, warnings, and caveats`,`summary and statusCodes`,`crawlers, then the bounded crawlerPaths list and selection`],doNotClaim:[`A user-agent match does not verify the crawler identity or source IP.`,`The supplied file does not prove that every server or time period is represented.`,`Path rankings are partial when path tracking reached its limit.`],verify:`Check important crawler errors against the original log line and deployment or server configuration before changing the site.`,related:[{id:`site-crawl`,reason:`Checks the current site directly.`},{id:`crawler-rules`,reason:`Explains current crawl checks.`},{id:`redirect-trace`,reason:`Traces one failing or redirecting path.`}]},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`,`dataSource plus evidence.imports file dates, hashes, and row quality when the run used researchFiles`],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.`,`Research-import rows are provider estimates that support multiple-ranking-URL claims only, not traffic or Search Console-verified cannibalisation.`],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 Google Analytics 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, the token budget, or the output byte 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`,`Google Analytics 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.`}]},"ai-mention-research":{readOrder:[`dataStatus, exact surface and market, source statuses, coverage, cache, cost, and warnings`,`summary and comparison provider values before targetComparisonShare`,`samples with observation times, model, answerTruncated, sources, and ownedSources`,`firstParty source limits and each sample lexical overlap state`,`questionPatterns, dataSourceBriefs, findings, caveats, and nextSteps`],doNotClaim:[`Provider-indexed records are not live prompt observations, a complete census, or proof of current AI visibility.`,`Comparison share is limited to the supplied targets and returned provider dataset. It is not a universal score.`,`Mention count, AI search volume, and citations do not prove referral traffic, sentiment, authority, or future performance.`,`Lexical overlap and repeated question terms do not prove shared intent or justify a new page or programmatic template.`],verify:`Inspect cited pages and current results, then run fixed prompt observations for a small stable question set before changing content or templates.`,related:[{id:`ai-prompt-observations`,reason:`Records current answers for fixed prompts.`},{id:`ai-referrals`,reason:`Measures identifiable referral visits.`},{id:`ai-readiness`,reason:`Checks technical access controls.`},{id:`keyword-opportunities`,reason:`Starts from first-party demand.`}]},"ai-prompt-observations":{readOrder:[`dataStatus, exact prompt and model combinations, market label, web-search setting, and request count`,`each observation evidence coverage, cache state, requested and effective model, checkedAt, answerTruncated, cost, and warnings`,`answer text, citations, target matches, and comparison status`,`firstParty source limits and each supporting-search word-match state`,`citedDomains, fanOutThemes, findings, caveats, and nextSteps`],doNotClaim:[`A generated answer is one sample, not a census of what every user sees or proof of universal AI visibility.`,`not-observed applies only to the retained answer and supplied aliases. It is not proof of absence.`,`A citation does not prove that the source supports every answer claim, caused traffic, or will recur. Model changes, cache hits, partial evidence, and truncated answers are not comparable trend points.`,`Supporting-search and Search Console word matches do not prove shared intent, independent demand, or that a new page or programmatic template should be built.`],verify:`Inspect the answer and cited pages, validate research leads with keyword and current result evidence, and repeat only decision-critical prompts with the same effective model and complete evidence.`,related:[{id:`ai-mention-research`,reason:`Adds broader provider-indexed mention evidence.`},{id:`ai-referrals`,reason:`Measures identifiable referral visits.`},{id:`keyword-opportunities`,reason:`Validates first-party demand and content coverage.`},{id:`pseo-opportunities`,reason:`Researches viable template ideas.`}]},"competitive-opportunities":{readOrder:[`dataStatus, market, selection bounds, detailBudget, source coverage, cache, costs, warnings, and failed requests`,`summary and processing counts before the retained opportunity order`,`each opportunity current result status, target ranks, competitors, reviewReasons, and evidenceRefs`,`source SERP rows, Domain Rating observations, and link summaries behind decision-critical comparisons`,`findings, caveats, and nextSteps`],doNotClaim:[`The investigation order is not a keyword-difficulty, authority, ranking-feasibility, or traffic forecast.`,`A lower Domain Rating or referring-domain count does not by itself make a result easy to outrank.`,`Missing, failed, capped, or partial provider evidence is not zero.`,`A recurring result domain is not automatically a relevant business competitor.`],verify:`Inspect the current ranking pages, their search intent, existing site coverage, Search Console evidence, and URL-level links before choosing a page or keyword.`,related:[{id:`competitor-keyword-gap`,reason:`Compares a small classified competitor set in depth.`},{id:`serp-results`,reason:`Refreshes one current result snapshot.`},{id:`keyword-research`,reason:`Expands seeds without competitor enrichment.`},{id:`link-evidence`,reason:`Inspects decision-critical page links.`}]},"competitor-keyword-gap":{readOrder:[`dataStatus, source statuses, market, and selection bounds`,`source evidence.imports file dates, hashes, fields, row quality, and caps when present`,`summary classification counts before the returned candidate subset`,`processing counts for first-party rows, token postings, competitor rows, and candidate terms`,`candidate classification, relevance state, firstParty, ownProviderRank, and competitor rows`,`pseo proposal, repeatedCompetitorPatterns, dataSourceBriefs, findings, and caveats`],doNotClaim:[`A provider-only competitor term is not automatically relevant, missing from the site, or worth targeting.`,`not-in-retained Search Console evidence is not proof of zero impressions or no existing coverage.`,`Lexical overlap and repeated URL patterns do not prove intent, demand, page quality, or that a template should be built.`],verify:`Inspect current results, representative competitor pages, existing first-party pages, and an authoritative data source before approving a candidate.`,related:[{id:`serp-competitors`,reason:`Finds and classifies domains first.`},{id:`ranking-pages`,reason:`Inspects repeated competitor pages.`},{id:`keyword-opportunities`,reason:`Starts from first-party demand.`}]},"domain-overview":{readOrder:[`dataStatus, market, provider coverage, cache, cost, and warnings`,`summary and evidence.data.organic provider values`,`firstParty status, range, and measured values when requested`,`findings, caveats, and nextSteps`],doNotClaim:[`Provider traffic, keyword counts, ranking movements, and traffic cost are estimates, not measured site performance.`,`Do not subtract provider estimates from Search Console values or turn them into a percentage gap.`,`Empty, capped, partial, invalid, or unavailable evidence cannot support a zero footprint or all-clear.`],verify:`Use Search Console for actual site performance and inspect ranking pages or current results before acting on provider estimates.`,related:[{id:`ranking-pages`,reason:`Shows pages behind the footprint.`},{id:`ranked-keywords`,reason:`Shows bounded keyword rows.`},{id:`search-performance-overview`,reason:`Starts with first-party performance.`}]},"domain-rating":{readOrder:[`dataStatus, target, targetMode, observedAt, cache, coverage, and warnings`,`evidence.data.domainRating, licenseUrl, attribution, and attributionUrl`,`caveats and nextSteps before using the value in a comparison`],doNotClaim:[`Domain Rating is an Ahrefs backlink-profile estimate, not a Google metric, ranking factor, traffic estimate, or keyword-difficulty score.`,`A lower Domain Rating does not by itself mean a result, page, or keyword is easy to outrank.`,`Missing or unavailable Domain Rating is not zero.`],verify:`Inspect the current result page, page relevance, URL-level link evidence, and your own site evidence before making a competitive decision.`,related:[{id:`link-evidence`,reason:`Adds bounded referring-link evidence.`},{id:`serp-results`,reason:`Checks a current result snapshot.`},{id:`domain-overview`,reason:`Adds search-footprint estimates.`}]},"ranked-keywords":{readOrder:[`dataStatus, market, coverage, request filters, cache, cost, and warnings`,`evidence.imports file dates, hashes, fields, row quality, and caps when present`,`summary totals and returned evidence.data.rows`,`firstParty status, caps, and per-row match states when requested`,`findings, caveats, and nextSteps`],doNotClaim:[`Provider rank, traffic, volume, difficulty, and intent are not live result or first-party evidence.`,`A query absent from retained Search Console rows is not proof of no impressions or no ranking.`,`The returned page is not a full domain keyword inventory when coverage is filtered, capped, or paginated.`],verify:`Run a current result report for any keyword whose rank, result type, or intent would change the decision.`,related:[{id:`serp-results`,reason:`Checks a current result snapshot.`},{id:`ranking-pages`,reason:`Groups ranking pages and URL patterns.`},{id:`keyword-opportunities`,reason:`Adds first-party opportunity logic.`}]},"ranking-pages":{readOrder:[`dataStatus, market, coverage, request filters, cache, cost, and warnings`,`evidence.imports file dates, hashes, fields, row quality, and caps when present`,`summary and bounded evidence.data.rows`,`firstParty page matches when requested`,`repeatedPatterns, findings, caveats, and nextSteps`],doNotClaim:[`Estimated traffic and keyword counts are not measured page traffic.`,`A repeated path is a structural heuristic, not proof of shared intent, quality, or a programmatic generator.`,`Patterns from a filtered or paginated page sample may not describe the whole domain.`],verify:`Inspect representative pages, their ranked queries, current results, data sources, and first-party performance before changing or creating a template.`,related:[{id:`ranked-keywords`,reason:`Shows queries behind a page.`},{id:`pseo-audit`,reason:`Checks first-party template quality.`},{id:`competitor-keyword-gap`,reason:`Classifies possible gaps.`}]},"serp-competitors":{readOrder:[`dataStatus, market, querySetSize, coverage, request, cache, and cost`,`evidence.imports file dates, hashes, fields, row quality, and caps when present`,`competitors relationship, siteType, classificationSource, and keywordCoverage`,`findings, caveats, and nextSteps`],doNotClaim:[`A recurring result domain is a search competitor for this keyword set, not automatically a business competitor.`,`unknown site types must stay unknown until the user or agent classifies them.`,`Visibility and traffic estimates are not complete market share or measured traffic.`],verify:`Review unknown domains and current representative results before selecting a small explicit set for gap research.`,related:[{id:`ranking-pages`,reason:`Inspects a relevant domain.`},{id:`competitor-keyword-gap`,reason:`Compares explicit domains.`},{id:`serp-results`,reason:`Checks one current result set.`}]},"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, OKF version, report id, source URL, generation time, crawl status, title, total concept count, page concept count, and caveats`,`selection page counts and deterministic ordering`,`the returned validation profile, valid state, issue counts, provenance, generation, trust, lifecycle, freshness, and attestation 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, and the builder does not make extracted claims true or current.`,`Unverified means no supported verification event was supplied; it does not mean the concept is false.`,`Attestation validation checks the contract shape and referenced bundle paths. It does not run the computation, executor, or attester.`],verify:`Inspect source references, 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.profile, formatVersion, compatibility, valid state, file and concept counts, and issue counts`,`provenance, generation, trust, lifecycle, freshness, and attestation summaries`,`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, complete, or fact checked.`,`Warnings still require review even when valid is true.`,`Missing optional trust fields do not make a generic OKF bundle invalid.`,`Attestation warnings do not prove a computation failed. The validator does not execute computations or inspect runtime receipts.`],verify:`After structural success, separately verify source URLs, claims, dates, any recorded crawl snapshot, and runtime attestation receipts.`,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.`}]},"pseo-patterns":{readOrder:[`dataStatus, source states, selection limits, detailBudget, warnings, and caveats`,`observedPatterns and observedQueries from retained Search Console evidence`,`patternSets planned and returned cardinality before reviewing candidates`,`candidate review.state, firstParty, inventory, overlap, external evidenceRefs, and reasons`,`findings and nextSteps only after separating evidence-led and complete-set coverage`],doNotClaim:[`A recognized query phrase is a wording heuristic, not proof that a page template should be built.`,`A declared complete set is caller-supplied product strategy, not observed demand, and a strategic gap is not a search-volume claim.`,`A query absent from retained Search Console evidence is not zero demand, no ranking, or no existing page coverage.`,`Provider metrics and result snapshots are optional secondary evidence. They do not prove traffic, ranking feasibility, page quality, or the correct number of pages.`],verify:`Inspect representative current pages and results, confirm stable entities and source fields, resolve possible overlap, and test a small page set before expanding a pattern.`,related:[{id:`pseo-opportunities`,reason:`Expands observed templates with deeper external research.`},{id:`pseo-audit`,reason:`Audits existing template evidence.`},{id:`ranking-pages`,reason:`Inspects repeated competitor page structures.`},{id:`keyword-metrics`,reason:`Adds bounded market estimates when needed.`}]},"pseo-opportunities":{readOrder:[`dataStatus, summary, market, selection, and caveats`,`source.pseoAudit and source.queryClusters before external evidence`,`source.external.discovery coverage, cache, cost, warnings, and candidate value states`,`source.external.serps observations and competitors with exact evidence references`,`dataSourceBriefs and nextSteps only after checking the supporting evidence`],doNotClaim:[`A discovered term absent from retained Search Console rows does not prove zero first-party visibility.`,`Keyword estimates, category ideas, related-search terms, repeated domains, and repeated URL patterns do not prove authority, ranking feasibility, shared intent, page quality, or that an existing template should expand.`,`A data-source brief does not establish that a dataset exists, may be reused, or can support differentiated pages.`,`Do not recommend one page per keyword or combinatorial page generation from volume alone.`],verify:`Inspect the retained live results and representative existing pages, then validate identifiers, fields, freshness, usage rights, missing-value rules, crawl controls, and internal links before expanding a template.`,related:[{id:`pseo-patterns`,reason:`Maps broad observed and declared pattern sets first.`},{id:`pseo-audit`,reason:`Validates existing template evidence.`},{id:`serp-results`,reason:`Inspects one live result snapshot.`},{id:`query-clusters`,reason:`Reviews first-party query groups.`}]},"keyword-metrics":{readOrder:[`dataStatus, summary, market, and caveats`,`evidence coverage, warnings, cache status, request, and cost`,`each evidence value state before using its number`,`analysis trend methodology and findings evidenceRef`,`nextSteps for first-party and result-page verification`],doNotClaim:[`Search volume and result counts are provider estimates, not measured traffic or complete inventories.`,`Keyword difficulty is not a ranking probability and does not establish whether this site can rank.`,`A trend heuristic does not forecast future demand, and a missing value is not zero.`],verify:`Compare the term with first-party Search Console evidence and inspect a current result page in the same market before choosing work.`,related:[{id:`query-clusters`,reason:`Groups first-party query themes.`},{id:`quick-wins`,reason:`Finds first-party CTR candidates.`},{id:`striking-distance`,reason:`Finds near-page-one evidence.`}]},"keyword-opportunities":{readOrder:[`dataStatus, summary, range, methodology, and caveats`,`firstParty source calls, row cap, possiblyTruncated, and each existing analysis selection`,`external requested, status, selection, provider coverage, warnings, cache, and cost`,`combined firstParty evidence and external evidenceRef with every provider value state`,`candidateClusters, programmaticPatterns, findings, and dataSourcePrompts`,`nextSteps for the supplying report, programmatic audit, and current result inspection`],doNotClaim:[`Overlapping quick-win, second-page, and striking-distance signals are different views of retained rows, not independent confirmations.`,`External estimates do not change first-party scores, prove demand, predict traffic, or establish site-specific ranking feasibility.`,`Candidate clusters and template signatures do not prove shared intent or justify creating more pages.`,`Average position is not exact rank, and this report contains no live result or competitor-ranking evidence.`],verify:`Open the full supplying report for page-level evidence, validate representative template pages, and inspect a current result page in the same market before choosing work.`,related:[{id:`keyword-metrics`,reason:`Reviews a supplied keyword list.`},{id:`pseo-audit`,reason:`Validates repeated page templates.`},{id:`quick-wins`,reason:`Shows the full CTR evidence.`},{id:`striking-distance`,reason:`Shows the full query rows.`}]},"keyword-research":{readOrder:[`dataStatus, summary, market, caveats, and requested source count`,`evidence coverage, warnings, cache status, request, and cost`,`each keyword sources array and provider value state before using a metric`,`analysis trend methodology and findings evidenceRef`,`nextSteps for intent, result-page, and first-party verification`],doNotClaim:[`Appearing in several discovery sources is not independent confirmation of demand, shared intent, or ranking feasibility.`,`Provider metrics are estimates, a missing value is not zero, and an observed zero does not prove no demand.`,`A discovered keyword does not justify a new page or programmatic template without content-fit and result-page review.`],verify:`Check shared intent in a live result snapshot and compare relevant terms with first-party evidence before choosing work.`,related:[{id:`serp-results`,reason:`Shows the current result snapshot.`},{id:`keyword-metrics`,reason:`Reviews a supplied keyword list.`},{id:`query-clusters`,reason:`Groups first-party query themes.`},{id:`pseo-audit`,reason:`Validates repeated page templates.`}]},"local-search-demand":{readOrder:[`dataStatus, source row cap, possiblyTruncated, and selection exclusions`,`methodologyDetails supplied terms and automatic heuristic patterns`,`opportunities intent evidence, metrics, page coverage, action, and templates as repeated page review signals`,`analyticsEvidence status, source completeness, exact landing-page matches, location coverage, and template context`,`serpEvidence status, market, coverage, cache, cost and snapshots, then serpInsights observations, unclassified competitors, and retained listings`,`caveats and nextSteps`],doNotClaim:[`A place in a query is not evidence of the searcher location, and Google Analytics geography cannot prove the query.`,`Search Console average position is not an exact city rank, and one live snapshot is not rank history.`,`A retained local-pack listing does not prove ownership, complete Maps coverage, business visibility, or Google Business Profile performance, and a recurring result domain is not proof of a comparable business.`,`A repeated local URL pattern does not prove shared intent, page quality, unique source data, or permission to scale.`],verify:`Inspect representative landing pages, classify recurring result domains, and verify retained listings in current results for the same location, language, and device before changing or expanding local pages.`,related:[{id:`pseo-audit`,reason:`Validates repeated local page templates.`},{id:`serp-results`,reason:`Opens one local result snapshot.`},{id:`rank-tracking`,reason:`Measures exact local ranks over time.`},{id:`keyword-research`,reason:`Finds candidate terms beyond retained Search Console queries.`}]},"saved-keywords":{readOrder:[`dataStatus, summary, evidence pagination, and any tag filter`,`the saved set market, source, refresh time, and keyword count`,`each item metric value state, observation time, tags, and page mapping`,`analysis groups, findings, caveats, and nextSteps`],doNotClaim:[`User-managed tags do not prove shared intent or justify one page or template.`,`Provider metrics are estimates rather than first-party rankings or traffic, and a missing value differs from an observed zero.`,`A target or proposed page mapping does not prove that the page should exist, ranks, or is indexed.`,`A filtered, capped, or paginated view cannot support a conclusion about the complete set.`],verify:`Compare relevant terms with Search Console and inspect current results in the saved market before changing a page or template plan.`,related:[{id:`keyword-metrics`,reason:`Refreshes a supplied term list.`},{id:`keyword-research`,reason:`Discovers more candidate terms.`},{id:`keyword-opportunities`,reason:`Adds first-party opportunity evidence.`},{id:`pseo-opportunities`,reason:`Reviews repeatable programmatic patterns.`}]},"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 Google Analytics session signal have different semantics and are not interchangeable.`,`Missing Google Analytics 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.`}]},"serp-results":{readOrder:[`dataStatus, summary, market, checkedAt, caveats, and corrected query state`,`evidence coverage, warnings, cache status, request, and cost`,`organicResults rankAbsolute, rankGroup, page, domain, URL, title, and snippet evidence`,`domains and findings for repeated domains or query correction`,`nextSteps for page, intent, and repeat-snapshot review`],doNotClaim:[`One snapshot is not rank history, and it does not establish a stable or personalized ranking.`,`Repeated domains and result features do not prove authority, quality, weakness, or site-specific ranking feasibility.`,`Provider result counts are estimates, and a bounded depth is not a complete result inventory.`],verify:`Open the retained pages, confirm the query intent, and repeat the same market and device snapshot when stability matters.`,related:[{id:`keyword-research`,reason:`Discovers candidate terms.`},{id:`keyword-metrics`,reason:`Adds independent estimates.`},{id:`striking-distance`,reason:`Shows first-party position evidence.`},{id:`content-optimization`,reason:`Builds a page brief.`}]},"rank-tracking":{readOrder:[`dataStatus, configuration market, devices, targetDomain, depth, collection method, cadence, and run state`,`coverage, pending and failed counts, provider cost, warnings, and caveats`,`comparison prior run and per-item current and previous observation states`,`organic position change, absolute position, ranking URL changes, and checkedAt`,`nextSteps for first-party context, template review, or the next collection`],doNotClaim:[`Not observed within the configured depth is not proof that the domain does not rank.`,`Exact snapshots must not be substituted for Search Console average position or blended across markets and devices.`,`A ranking change alone does not establish causation, traffic impact, or future performance.`,`Pending or failed tasks cannot be treated as losses or zero rankings.`],verify:`Confirm the same market, device, depth, and target-domain scope, then inspect the ranking URL and Search Console evidence before acting.`,related:[{id:`serp-results`,reason:`Inspects the current result page.`},{id:`saved-keywords`,reason:`Reviews the tracked keyword set.`},{id:`quick-wins`,reason:`Adds first-party opportunity evidence.`},{id:`striking-distance`,reason:`Adds first-party average-position evidence.`}]},"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 Google Analytics 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:[`config.strategy first; use health before full on large or unknown sites`,`status, requestEvidenceStatus, attempted and fetched counts, failures, retained pages, and pageLimitReached`,`access crawler identity, blockedRequests, provider counts, and samples`,`data-source states`,`topFixes AND reviewObservations together; the two lists are the complete grouped issue inventory, so carry every group from both into findings`,`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.`,`A health pass checks response access, status, and redirects only. It does not download page bodies or evaluate content, metadata, canonicals, indexability directives, internal links, or rendered HTML. For full crawls, state when JavaScript rendering was disabled.`,`A User-Agent can be spoofed. Never ask the user for a broad firewall bypass based on crawler identity alone.`],verify:`Re-run the same health configuration first. Use a full crawl only when the health evidence is clean and the question needs page-body analysis.`,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.`}]}},zle={"competitive-opportunities":{name:`Competitive opportunities`,description:`Turn a topic into a focused keyword and competitor shortlist using demand, current rankings, Domain Rating, and optional link evidence.`,useWhen:[`You want to research a topic before choosing keywords or competitors.`,`You need one bounded investigation order across keyword demand, current results, and competitor evidence.`,`You want free Ahrefs Domain Rating context before deciding whether paid link research would help.`],avoidWhen:[`You already know the exact competitor domains and need their full keyword footprints.`,`You need local results for a specific city or precise location.`],outcome:`A focused keyword and competitor investigation order with current ranking pages, supporting evidence, costs, and clear follow-ups.`},"competitor-keyword-gap":{name:`Competitor keyword gaps`,description:`Compare a small explicit competitor set with Search Console themes, the site's provider footprint, and repeated ranking-page patterns.`,useWhen:[`You have already identified relevant competitors and want a bounded opportunity review.`,`You want to separate existing coverage from plausible new keyword or programmatic template research.`,`You have one ranked-keyword export containing the site and compared competitor domains but no connected provider API.`],avoidWhen:[`You have not classified whether the compared domains are relevant to the business and search intent.`,`You want every competitor keyword treated as a content gap.`],outcome:`A classified keyword set with first-party overlap, provider coverage, pSEO patterns, and bounded data-source briefs.`},"domain-overview":{name:`Domain search footprint`,description:`Review a country-level estimate of a domain's organic footprint beside optional owner-verified Search Console totals.`,useWhen:[`You need a quick independent footprint before deeper competitor or page research.`,`You want provider estimates and matching first-party performance shown with clear boundaries.`],avoidWhen:[`You need live rankings for a particular place or device.`,`You want estimated traffic presented as measured site traffic.`],outcome:`A bounded domain estimate with ranking distribution, source coverage, cost, and an optional Search Console comparison.`},"domain-rating":{name:`Ahrefs Domain Rating`,description:`Retrieve the current Ahrefs backlink-profile estimate for one domain or URL with its required license and attribution.`,useWhen:[`You need one provider-native backlink-profile metric as supporting evidence.`,`You want the free Ahrefs value before deciding whether deeper paid link research is useful.`],avoidWhen:[`You want a Google ranking factor, traffic estimate, keyword-difficulty score, or an easy-to-outrank verdict.`,`You need page relevance, current result positions, or complete backlink evidence.`],outcome:`One explicitly attributed Ahrefs Domain Rating observation with cache, coverage, license, and interpretation limits.`},"ranked-keywords":{name:`Ranked keyword footprint`,description:`Review a bounded set of provider-observed keywords, ranking pages, result types, and optional matching Search Console evidence.`,useWhen:[`You need the keyword rows behind a site, competitor, or page footprint.`,`You want to check provider-only rows against retained first-party query evidence for a site you own.`,`You have a DataForSEO, Semrush or Ahrefs ranked-keyword export instead of API access.`],avoidWhen:[`You need a current exact rank for one query, place, and device.`,`You want an absent Search Console row treated as proof of no impressions.`],outcome:`Bounded keyword and ranking-page rows with filters, pagination, estimates, and first-party match states.`},"ranking-pages":{name:`Ranking pages and patterns`,description:`Find a domain's bounded ranking-page footprint and repeated URL patterns, with optional matching Search Console page evidence.`,useWhen:[`You want to see which pages account for a domain's estimated organic footprint.`,`You are researching competitor or first-party programmatic page families.`,`You want to group a local ranked-keyword export into pages and repeated URL patterns.`],avoidWhen:[`You plan to infer page quality or intent from URL structure alone.`,`You need a complete inventory beyond the provider page cap.`],outcome:`Ranking-page estimates, repeated path patterns, representative URLs, and first-party page matches.`},"serp-competitors":{name:`Search competitors`,description:`Identify domains that repeatedly appear across an explicit keyword set while keeping unknown site types unclassified.`,useWhen:[`You want to discover search competitors before selecting domains for deeper research.`,`You need to separate a target, declared competitors, and other recurring result domains.`,`You have a multi-domain ranked-keyword export covering the supplied keyword set.`],avoidWhen:[`You have only one query or need a local, device-specific live snapshot.`,`You want recurring publishers or directories assumed to be business competitors.`],outcome:`A bounded competitor set with query coverage, visibility estimates, declared classifications, and unknowns left explicit.`}},Ble={"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.`},"pseo-patterns":{name:`Programmatic SEO patterns`,description:`Find repeatable search patterns in Search Console, then review bounded term, pair, or matrix sets supplied as product strategy.`,useWhen:[`You want to see which repeatable query patterns already appear in first-party search data.`,`You have a known comparison, utility, location, integration, template, or other structured set to check.`,`You need strategic coverage gaps kept separate from observed search demand.`],avoidWhen:[`You only need to audit the quality and index state of pages that already exist.`,`You want generated copy, automatic page creation, deployment, or traffic forecasts.`],outcome:`Observed query-pattern evidence and a bounded review queue for declared programmatic topics, with coverage, overlap, inventory, and optional provider evidence kept separate.`},"pseo-opportunities":{name:`Programmatic SEO opportunities`,description:`Combine repeated template and query-cluster evidence with optional keyword discovery, live results, competitor URL patterns, and data-source research briefs.`,useWhen:[`You want to expand a template that already earns impressions without treating every related keyword as a new page.`,`You need to separate extensions of an observed template from research for a possible new template.`,`Current result pages and repeated competitor URL patterns would change the programmatic SEO decision.`],avoidWhen:[`You only need to audit the current technical and index state of existing templates.`,`You want external provider calls made without explicit market, cost, and result limits.`],outcome:`A bounded research queue that keeps first-party templates, provider estimates, live results, competitor patterns, costs, and data-source checks separate.`}},L9={...zle,...Ble,"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 Google Analytics and see a ranked, bounded set of landing pages that received them.`,useWhen:[`You want measured AI referral traffic from Google Analytics.`,`You need the landing pages and sources behind those visits.`],avoidWhen:[`Google Analytics 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-mention-research":{name:`AI mention research`,description:`Compare provider-indexed mentions, source domains, and question samples for one AI surface, location, and language.`,useWhen:[`You need independent AI mention and citation research beyond referral traffic or technical readiness.`,`You want to compare a named target with a small explicit competitor set in the same provider dataset.`,`You want to check bounded question samples against existing Search Console demand before planning content.`],avoidWhen:[`You need a current answer from a fixed prompt. Use prompt observation rather than an indexed mention dataset.`,`You want a universal visibility score or proof that an AI product never mentions a target.`],outcome:`Provider-indexed comparison metrics, cited sources, bounded question evidence, optional first-party overlap, costs, and programmatic research prompts.`},"ai-prompt-observations":{name:`Fixed AI prompt observations`,description:`Run a small fixed set of prompts against explicit models, retain answer and citation evidence locally, and compare only like-for-like observations.`,useWhen:[`You need a current answer sample for an exact prompt, model, market label, and web-search setting.`,`You want to track target and competitor mentions or citations without turning samples into a universal visibility score.`,`You want to compare repeated supporting-search themes with retained Search Console demand before researching content or programmatic templates.`],avoidWhen:[`You need broad provider-indexed mention estimates rather than current fixed prompt samples.`,`You cannot supply current model names or do not want to start explicitly bounded paid requests.`],outcome:`Current answer samples, target and competitor observations, cited domains, supporting-search themes, optional first-party overlap, exact response costs, and defensible local change states.`},"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.`},"bing-webmaster-overview":{name:`Bing Webmaster overview`,description:`Review bounded Bing traffic trends, crawl changes, and query and page opportunities for one verified site.`,useWhen:[`You want processed search and crawl insights observed by Bing.`,`You need a second search-engine view beside Search Console.`],avoidWhen:[`Bing Webmaster is not connected for this installation.`,`You need URL-level backlink rows or proof that a page is indexed.`],outcome:`Prioritized Bing findings with traffic comparisons, crawl changes, query and page opportunities, provider coverage, and caveats.`},"link-evidence":{name:`Referring link evidence`,description:`Review bounded referring-link evidence from a live provider, Bing Webmaster or a local export.`,useWhen:[`You want concrete referring URLs and anchor text where available.`,`You want to compare a live provider summary with saved crawl and Search Console evidence for linked target pages.`,`You need one normalized view of DataForSEO, Bing or an exported CSV, JSON, or JSONL file.`],avoidWhen:[`You need a complete backlink index or a universal authority score.`,`You want to find broken internal links during a crawl.`],outcome:`Provider summary counts, representative links, target-page search and crawl context, and bounded technical findings with explicit source coverage.`},"server-log-analysis":{name:`Server log crawler analysis`,description:`Stream a local access log to see observed search and AI crawler requests, statuses, and paths.`,useWhen:[`You need evidence of crawler requests that reached the web server.`,`You want to find crawler-facing 4xx or 5xx responses in a local access log.`],avoidWhen:[`You do not have a combined or JSONL access log.`,`You need to prove crawler identity from a user-agent string alone.`],outcome:`Bounded crawler, status, and path aggregates with file coverage and parser limits kept visible.`},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 OKF v0.2 site knowledge pack with source provenance 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 v0.2 manifest and optional Markdown files with source, generation, and validation evidence.`},"okf-validate":{name:`Validate a site knowledge pack`,description:`Check supplied OKF Markdown files using the generic format or stricter SEO export profile.`,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 format compatibility, exact issues, provenance, trust, lifecycle, freshness, and attestation summaries.`},"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.`},"keyword-metrics":{name:`Keyword metrics`,description:`Compare market-specific keyword demand, trend, cost, competition, difficulty, intent, and result-count estimates.`,useWhen:[`You have a bounded keyword list and need independent market estimates.`,`You want recent demand history before deciding which terms deserve deeper research.`],avoidWhen:[`You need first-party evidence of how your own pages perform.`,`You need an exact current rank or a current result-page comparison.`],outcome:`Provider-neutral keyword evidence with missing values, costs, coverage, and trend heuristics kept visible.`},"keyword-opportunities":{name:`Enriched keyword opportunities`,description:`Combine existing Search Console opportunity evidence with optional market estimates and programmatic SEO cluster context.`,useWhen:[`You want quick-win, second-page, and striking-distance evidence from one bounded Search Console acquisition.`,`You want to add independent keyword estimates without replacing first-party evidence or its existing scores.`,`You need candidate query clusters and repeatable page patterns to validate before expanding a template.`],avoidWhen:[`You only need independent metrics for a supplied keyword list.`,`You need a current result snapshot, exact rank, competitor comparison, or external provider work without explicit paid-request intent.`],outcome:`Bounded first-party opportunity sections, optional typed provider evidence, discrepancy findings, and programmatic SEO validation prompts.`},"keyword-research":{name:`Keyword research`,description:`Expand a small seed list into market-specific keyword ideas with source, metric, coverage, and cost evidence.`,useWhen:[`You need candidate terms beyond the queries already visible in Search Console.`,`You want to compare several bounded provider discovery methods before deeper result-page research.`],avoidWhen:[`You need exact current rankings or pages in the live results.`,`You plan to turn every discovered term into a page without checking intent or fit.`],outcome:`A bounded keyword set with discovery sources, provider estimates, evidence states, and research follow-ups.`},"local-search-demand":{name:`Local search demand`,description:`Find location-specific Search Console demand and add optional landing-page geography and exact local result evidence.`,useWhen:[`You want to find retained queries that name a service area, use a proximity phrase, or contain a postcode or ZIP code.`,`You need to connect local demand with current landing pages and repeated programmatic page patterns.`,`You want optional Analytics landing-page geography or city and device-specific result evidence without replacing first-party performance data or assigning a location to a query.`],avoidWhen:[`You need Google Business Profile performance or a complete business listing audit.`,`You want the searcher location inferred from a place name in the query.`],outcome:`A bounded local-intent query and page queue, repeated local template signals, optional landing-page geography, and optional local competitor and listing evidence.`},"saved-keywords":{name:`Saved keyword set`,description:`Review one local keyword set with metric freshness, tags, page mappings, and explicit view limits.`,useWhen:[`You want to continue earlier keyword research from a saved project set.`,`You need to review stale, unmapped, or tagged terms before deeper research.`],avoidWhen:[`You need independent keyword discovery or a current result snapshot.`,`You plan to treat user-managed tags as proof of shared search intent.`],outcome:`A bounded local research view with saved evidence, freshness state, planning mappings, and next steps.`},"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.`},"serp-results":{name:`Live search results`,description:`Inspect one location and device-specific search snapshot with exact organic ranks, domains, result features, and provider evidence.`,useWhen:[`A keyword needs a current result-page review before content or targeting decisions.`,`You need exact snapshot ranks kept separate from Search Console average position.`],avoidWhen:[`You need rank history or scheduled tracking across many keywords.`,`You want a domain strength, content quality, or ranking feasibility verdict.`],outcome:`A bounded result snapshot with exact retained ranks, domain repetition, query corrections, features, cost, and coverage.`},"rank-tracking":{name:`Exact rank tracking`,description:`Collect and compare exact organic positions for a saved keyword set without mixing markets, devices, or Search Console averages.`,useWhen:[`You need repeatable exact-rank snapshots for a fixed market and device.`,`You want new, lost, improved, declined, or ranking URL change evidence over time.`,`A recurring set needs local history. Use queued collection only when the selected provider supports it.`],avoidWhen:[`You need first-party clicks, impressions, or average position; use a Search Console report.`,`One current query needs result-page inspection but no history; use live search results.`],outcome:`A bounded exact-rank comparison with explicit depth, pending and failed states, provider cost, and local retention 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:`Sitemap health pass and technical site crawl`,description:`Start with a lightweight sitemap health pass, then run a full technical crawl only when page bodies and content-level evidence are needed.`,useWhen:[`You need a fast first check of sitemap URLs, statuses, redirects, robots decisions, or access blocks.`,`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.`,`Do not run a full crawl on a large or unknown site before health=true, or when important content requires browser rendering that is not enabled.`],outcome:`A fast sitemap response gate or a full crawl summary with page evidence, technical findings, crawler identity, access guidance, and an optional 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 R9(e){let t=L9[e],n=Rle[e];if(!(!t||!n))return{...t,...n}}function Vle(e){e.registerTool(`seo_audit_page`,{description:`Run a single-page technical and content audit`,inputSchema:{url:O.string().url(),site:O.string().optional(),js:O.boolean().optional(),refresh:O.boolean().optional()}},async({url:e,site:t,js:n,refresh:r})=>{try{let i=await HD({url:e,site:t,js:K7(n,`auto`),refresh:r});return $(`Audit complete for ${e}. Found ${i.issues.length} issues.`,i)}catch(e){return Q(e)}})}const z9=q7([`verifyContent`,`verifyLimit`,`js`,`fetchConcurrency`,`fetchIntervalCap`,`fetchIntervalMs`,`refresh`]);function B9(e){return{verifyContent:e.verifyContent,verifyLimit:e.verifyLimit,js:K7(e.js,void 0),rate:i9({fetchConcurrency:e.fetchConcurrency,fetchIntervalCap:e.fetchIntervalCap,fetchIntervalMs:e.fetchIntervalMs}),refresh:e.refresh}}function Hle(e){e.registerTool(`seo_monthly_report`,{description:`Generate a monthly SEO report narrative`,inputSchema:{...q7([`site`,`limit`,`includeBrand`]),month:G7.optional(),...z9}},async({site:e,month:t,limit:n,includeBrand:r,...i})=>{try{let a=await l0({site:e,month:t,limit:n,includeBrand:r,...B9(i)});return $(`Monthly report generated for ${a.month}. ${a.headline}`,{...a,presentation:u0(a)},{markdown:a.markdown})}catch(e){return Q(e)}})}function Ule(e){e.registerTool(`seo_report_narrative`,{description:`Generate a client-ready SEO narrative across diagnosis, changes, and monitoring`,inputSchema:{...q7([`site`,`days`,`recentDays`,`limit`,`includeBrand`]),startDate:W7.optional(),endDate:W7.optional(),changeLimit:O.number().optional(),...z9}},async({site:e,days:t,recentDays:n,startDate:r,endDate:i,limit:a,changeLimit:o,includeBrand:s,...c})=>{try{let l=await c0({site:e,days:t,recentDays:n,startDate:r,endDate:i,limit:a,changeLimit:o,includeBrand:s,...B9(c)});return $(l.headline,{...l,presentation:u0(l)},{markdown:l.markdown})}catch(e){return Q(e)}})}function Wle(e){e.registerTool(`seo_second_page`,{description:`Find URLs with GSC average position above 10 through 20 and return evidence-grounded investigation prompts`,inputSchema:{...q7([`site`,`range`,`minImpressions`,`limit`,`includeBrand`]),...z9,range:O.number().int().min(1).max(548).optional(),minImpressions:O.number().int().min(0).max(1e9).optional(),limit:O.number().int().min(1).max(100).optional(),verifyLimit:O.number().int().min(0).max(100).optional(),brandTerms:O.array(O.string().trim().min(1).max(200)).max(20).optional(),fetchConcurrency:O.number().int().min(1).max(16).optional(),fetchIntervalCap:O.number().int().min(1).max(60).optional(),fetchIntervalMs:O.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 I0({site:e,range:t,minImpressions:n,limit:r,includeBrand:i,brandTerms:a,...B9(o)});return $(s.summary.verdict,s)}catch(e){return Q(e)}})}function Gle(e){Ule(e),Hle(e),Vle(e),Wle(e)}function Kle(e){e.registerTool(`seo_server_log_analysis`,{description:`Stream a local access log into bounded search and AI crawler evidence`,inputSchema:{file:O.string().min(1).max(4096),format:O.enum([`combined`,`jsonl`]).optional(),rowLimit:O.number().int().min(1).max(1e7).optional(),pathLimit:O.number().int().min(1).max(1e5).optional(),limit:O.number().int().min(1).max(500).optional()}},async({file:e,format:t,rowLimit:n,pathLimit:r,limit:i})=>{try{let a=X6({evidence:await Y6({file:e,format:t,rowLimit:n,pathLimit:r}),limit:i});return $(`${a.summary.crawlerRows} crawler requests were observed in ${a.summary.parsedRows} parsed log rows.`,a)}catch(e){return Q(e)}})}const qle=O.strictObject({site:O.string().trim().min(1).max(2048),startUrl:O.string().url().optional(),urls:O.array(O.string().url()).min(1).max(100).optional(),sitemaps:O.array(O.string().url()).min(1).max(20).optional(),properties:O.array(O.string().trim().min(1).max(2048)).min(1).max(1e3).optional(),limit:O.number().int().min(1).max(25e4).optional(),refresh:O.boolean().optional(),js:O.boolean().optional(),languageCode:O.string().trim().min(1).max(35).optional(),dailyLimit:O.number().int().min(1).max(2e3).optional(),inspectLimit:O.number().int().min(1).max(100).optional(),maxUrls:O.number().int().min(1).max(25e4).optional(),recoverLinks:O.boolean().optional(),recoverDays:O.number().int().min(1).max(548).optional(),recoverLimit:O.number().int().min(1).max(100).optional(),recoverMinClicks:O.number().int().min(0).max(1e9).optional(),recoverMinImpressions:O.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 V9(e){let t=x2(e),n=u9({...e,presentation:t});return $(e.summary,n,{markdown:S2(e)})}function Jle(e){e.registerTool(`seo_workflow_diagnose_property`,{description:`Run the full agent workflow for property diagnosis and next actions`,inputSchema:{...q7([`site`,`days`,`recentDays`,`limit`,`includeBrand`,`refresh`])}},async({site:e,days:t,recentDays:n,limit:r,includeBrand:i,refresh:a})=>{try{return V9(await h2({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:{...q7([`site`,`limit`,`includeBrand`,`refresh`]),month:G7.optional()}},async({site:e,month:t,limit:n,includeBrand:r,refresh:i})=>{try{return V9(await C2({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:{...q7([`site`,`days`,`recentDays`,`limit`,`includeBrand`,`refresh`]),knownConfounders:O.array(O.string()).optional(),includeChangeLog:O.boolean().optional()}},async({site:e,days:t,recentDays:n,limit:r,includeBrand:i,knownConfounders:a,includeChangeLog:o,refresh:s})=>{try{return V9(await t4({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:qle},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 V9(await R2({site:e,startUrl:t,urls:n,sitemaps:r,properties:i,limit:a,refresh:o,js:K7(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:{...q7([`site`,`days`,`recentDays`,`limit`,`includeBrand`,`verifyContent`,`verifyLimit`,`refresh`]),googleAnalyticsPropertyId:O.string().optional()}},async({site:e,days:t,recentDays:n,limit:r,includeBrand:i,googleAnalyticsPropertyId:a,verifyContent:o,verifyLimit:s,refresh:c})=>{try{return V9(await I2({site:e,days:t,recentDays:n,limit:r,includeBrand:i,googleAnalyticsPropertyId:a,verifyContent:o,verifyLimit:s,refresh:c}))}catch(e){return Q(e)}})}const H9=[`ai-search`,`crawl`,`diagnosis`,`experiments`,`monitoring`,`opportunities`,`reporting`,`setup`,`workflows`],Yle=[{id:`competitive-opportunities`,category:`opportunities`,inputSchema:j9,handler:Dle()},{id:`domain-rating`,category:`opportunities`,inputSchema:M9,handler:Ole()},{id:`ai-mention-research`,category:`ai-search`,inputSchema:O9,handler:Sle()},{id:`ai-prompt-observations`,category:`ai-search`,inputSchema:A9,handler:Tle()},{id:`local-search-demand`,category:`opportunities`,inputSchema:N9,handler:kle()},{id:`pseo-patterns`,category:`opportunities`,inputSchema:I9,handler:Lle()}],Xle=[{category:`setup`,register:o9,names:[`seo_doctor`]},{category:`diagnosis`,register:o9,names:[`seo_segment_impact`,`seo_striking_distance`,`seo_traffic_anomaly`,`seo_update_correlate`]},{category:`opportunities`,register:gle,names:[`seo_cannibal`,`seo_ctr_underperformers`,`seo_decaying`,`seo_internal_links`,`seo_query_cluster`,`seo_quick_wins`]},{category:`opportunities`,register:mle,names:[`seo_keyword_metrics`,`seo_keyword_opportunities`,`seo_keyword_research`,`seo_rank_tracking`,`seo_saved_keywords`,`seo_serp_results`]},{category:`opportunities`,register:sle,names:[`seo_competitor_keyword_gap`,`seo_domain_overview`,`seo_ranked_keywords`,`seo_ranking_pages`,`seo_serp_competitors`]},{category:`ai-search`,register:Yce,names:[`seo_ai_referrals`,`seo_community_intent`,`seo_content_optimization`,`seo_page_opportunities`,`seo_performance_audit`,`seo_to_ai_query`]},{category:`ai-search`,register:a9,names:[`seo_ai_search_scorecard`]},{category:`crawl`,register:a9,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:hle,names:[`seo_crawl_diff`,`seo_index_coverage`,`seo_index_coverage_plan`,`seo_index_monitor`,`seo_index_watch`,`seo_link_recover`,`seo_redirect_trace`]},{category:`monitoring`,register:vle,names:[`seo_bing_webmaster_overview`,`seo_link_evidence`]},{category:`monitoring`,register:Kle,names:[`seo_server_log_analysis`]},{category:`reporting`,register:Gle,names:[`seo_audit_page`,`seo_monthly_report`,`seo_report_narrative`,`seo_second_page`]},{category:`experiments`,register:cle,names:[`seo_measure_change`]},{category:`reporting`,register:xle,names:[`seo_pseo_audit`,`seo_pseo_opportunities`]},{category:`workflows`,register:Jle,names:[`seo_workflow_diagnose_property`,`seo_workflow_monthly_report`,`seo_workflow_refresh_priorities`,`seo_workflow_technical_watch`,`seo_workflow_update_postmortem`]}],Zle={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 Qle(e){let t=e.replace(/^seo_/,``).replaceAll(`_`,`-`);return Zle[t]??t}function $le(e,t){return e.id<t.id?-1:+(e.id>t.id)}function eue(e){let t=new Map;return e({registerTool(e,n,r){t.set(e,{inputSchema:n.inputSchema,handler:r})}}),t}function tue(e){return e instanceof O.ZodObject?e:O.strictObject(e??{})}function nue(){let e=new Map,t=Yle.map(e=>{let t=R9(e.id);if(!t)throw Error(`MCP report registry is missing guidance for ${e.id}.`);return{...e,...t}});for(let n of Xle){let r=e.get(n.register);r||(r=eue(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=Qle(e),o=R9(a);if(!o)throw Error(`MCP report registry is missing guidance for ${a}.`);t.push({id:a,category:n.category,...o,inputSchema:tue(i.inputSchema),handler:i.handler})}}return t.sort($le)}const U9=nue(),rue=new Set(U9.map(e=>e.id)),W9=Object.keys(L9).filter(e=>!rue.has(e));if(W9.length>0)throw Error(`MCP report guidance references unknown reports: ${W9.join(`, `)}.`);const iue=new Map(U9.map(e=>[e.id,e]));function aue(e){return U9.filter(t=>!e||t.category===e).map(({id:e,name:t,description:n,category:r})=>({id:e,category:r,name:t,description:n}))}function G9(e){return iue.get(e)}const K9={"agent-readiness":{"crawler-access":{goal:`Keep robots.txt policy for AI crawler tokens intentional.`,fix:`Review the blocked tokens in the check evidence. Remove accidental Disallow rules for crawlers you want, and keep deliberate blocks documented so nobody reverts them by mistake.`,prompt:`Open robots.txt and list every rule that affects the AI crawler tokens named in the agent-readiness crawler-access evidence. For each blocked token, decide with the site owner whether the block is intentional. Remove only the accidental rules, redeploy, then re-run the agent-readiness report and confirm the crawler-access check lists no unintended blocks.`,resources:[{title:`RFC 9309: Robots Exclusion Protocol`,url:`https://www.rfc-editor.org/rfc/rfc9309`}],verify:`Re-run the report and confirm the blocked list contains only intentional tokens. An allowed token still does not prove the service crawls the site.`},"protocol-canonicalization":{goal:`Serve one canonical HTTPS origin with a permanent redirect and HSTS.`,fix:`Redirect the HTTP variant to HTTPS with a 301 or 308, redirect the alternate www or bare host to the preferred host, and send Strict-Transport-Security once HTTPS is stable everywhere.`,prompt:`Configure the web server or CDN so http:// requests return one permanent redirect to the https:// start URL, and the non-preferred host redirects to the preferred host. Add a Strict-Transport-Security header on HTTPS responses. Then re-run the agent-readiness report and confirm protocol-canonicalization passes.`,resources:[{title:`MDN: Strict-Transport-Security`,url:`https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security`}],verify:`curl -I the HTTP and alternate-host variants and confirm one permanent redirect each, then re-run the report.`},"markdown-coverage":{goal:`Give every public HTML page a Markdown representation.`,fix:`Serve text/markdown through content negotiation or advertise one working Markdown alternative per page with a link rel="alternate" type="text/markdown" tag, generated from the same content source as the HTML.`,prompt:`For each URL listed in the markdown-coverage check, make the route return Markdown when requested with Accept: text/markdown, or publish a .md alternate advertised from the HTML head. Generate the Markdown from the same source as the HTML so the two cannot drift. Re-run the agent-readiness report and confirm markdown-coverage passes.`,resources:[{title:`Markdown for Agents`,url:`https://developers.cloudflare.com/agents/markdown-for-agents/`}],verify:`Request each affected URL with Accept: text/markdown and confirm a 200 text/markdown response.`},"markdown-negotiation":{goal:`Make Markdown content negotiation honest and cache-safe.`,fix:`Honour Accept q-values, send Vary: Accept on negotiated responses, and keep the explicit .md file and the negotiated response byte-identical.`,prompt:`Fix the routes listed in the markdown-negotiation check so that Accept: text/markdown returns Markdown, an Accept header that refuses Markdown returns HTML, every negotiated response carries Vary: Accept, and the explicit alternate matches the negotiated body byte for byte. Re-run the agent-readiness report and confirm markdown-negotiation passes.`,resources:[{title:`RFC 9110: Content negotiation`,url:`https://www.rfc-editor.org/rfc/rfc9110#name-content-negotiation`}],verify:`Request one affected URL twice, once with Accept: text/markdown and once with text/markdown;q=0, and confirm the content types differ while Vary: Accept is present.`},"markdown-determinism":{goal:`Return identical Markdown bytes for repeated requests.`,fix:`Remove timestamps, request ids, random ordering, or per-request rewriting from the Markdown pipeline so the same route always produces the same bytes.`,prompt:`Fetch one affected URL from the markdown-determinism check twice and diff the responses. Remove whatever changes between requests, usually timestamps or randomised output, from the generator. Re-run the agent-readiness report and confirm markdown-determinism passes.`,resources:[],verify:`Fetch the same Markdown URL twice and confirm the SHA-256 digests match.`},"markdown-token-estimates":{goal:`Publish a stable token estimate on Markdown responses.`,fix:`Add an X-Markdown-Tokens header with a deterministic estimate, and keep the value identical between the explicit and negotiated responses for the same document.`,prompt:`Add an X-Markdown-Tokens response header to the Markdown routes listed in the markdown-token-estimates check, computed deterministically from the response body. Re-run the agent-readiness report and confirm the check passes.`,resources:[],verify:`Request an affected URL and confirm the header is present and unchanged on a second request.`},"markdown-quality":{goal:`Keep Markdown alternatives clean, structured, and complete.`,fix:`Fix the shared HTML to Markdown converter rather than individual files. Keep one H1 and the frontmatter title, exclude layout components, and stop SVG, script, and style markup leaking into the output.`,prompt:`Open the Markdown for each URL in the markdown-quality check and identify the defects the evidence names, such as leaked markup, duplicate prose, or navigation-only output. Fix the converter or component exclusion rules that cause them, then re-run the agent-readiness report and confirm markdown-quality passes.`,resources:[],verify:`Read one regenerated Markdown file end to end and confirm the useful copy survived without presentation markup.`},"agent-skills":{goal:`Publish a valid, digest-verified Agent Skills index.`,fix:`Serve /.well-known/agent-skills/index.json with a $schema field and a skills array. Each skill needs a same-origin Markdown SKILL.md with name and description frontmatter, and a sha256 digest generated from the exact deployed bytes.`,prompt:`Publish or repair the Agent Skills discovery index at /.well-known/agent-skills/index.json. Regenerate every declared digest from the deployed SKILL.md bytes, keep skill URLs same-origin, serve them as text/markdown with an Access-Control-Allow-Origin header, and confirm each file has name and description frontmatter. Re-run the agent-readiness report and confirm agent-skills passes.`,resources:[{title:`Agent Skills discovery specification`,url:`https://agentskills.io/specification`}],verify:`Fetch the index and one skill file, recompute the SHA-256 digest, and confirm it matches the declared value.`},"llms-txt":{goal:`Keep llms.txt short, stable, and pointing at pages that resolve.`,fix:`Curate the file to useful entry points. Remove duplicate, redirected, broken, and non-indexable links, keep it under the size cap, and generate it deterministically so repeated fetches return identical bytes.`,prompt:`Fix the llms.txt issues listed in the check evidence: remove or repair every malformed, duplicate, redirected, broken, or non-indexable link, and make the file deterministic. The generate-llms-txt report can rebuild it from the crawl. Re-run the agent-readiness report and confirm llms-txt passes. Do not present llms.txt as a Google ranking factor.`,resources:[{title:`llms.txt proposal`,url:`https://llmstxt.org/`},{title:`Google guidance on llms.txt`,url:`https://developers.google.com/search/updates#clarifying-guidance-on-llms-txt-files`}],verify:`Fetch llms.txt twice and confirm identical bytes, then spot-check that each listed link returns 200 without redirecting.`},"content-signals":{goal:`Declare one deliberate Content Signals policy.`,fix:`Add a Content-Signal directive to robots.txt stating the search, ai-input, and ai-train preferences the site owner actually wants. If response headers also carry the policy, keep every surface identical.`,prompt:`Agree the content usage policy with the site owner, then add one Content-Signal line to robots.txt, for example Content-Signal: search=yes, ai-input=yes, ai-train=no. If HTML or Markdown responses also send a content-signal header, make the values match robots.txt exactly. Re-run the agent-readiness report and confirm content-signals passes.`,resources:[{title:`Content Signals policy`,url:`https://contentsignals.org/`}],verify:`Fetch robots.txt and one page and confirm every observed Content-Signal value is identical.`},"route-manifest":{goal:`Publish a deterministic route manifest when HTML and Markdown parity matters.`,fix:`Generate /agent-routes.json from the build with a pages array of htmlPath and markdownPath entries, so a crawl can prove no orphan or missing Markdown routes were deployed.`,prompt:`Generate /agent-routes.json at build time listing every public HTML route and its Markdown alternate. Fix any routes the route-manifest evidence lists as missing or orphaned, then re-run the agent-readiness report and confirm route-manifest passes.`,resources:[],verify:`Compare the manifest against the deployed routes and confirm no missing or orphan entries remain.`},"link-headers":{goal:`Advertise machine-readable resources with Link response headers.`,fix:`Add Link headers to the start page pointing at resources that exist. Use describedby for llms.txt, registered relation types such as api-catalog, service-desc, or service-doc, and agent-skills only when the target resolves.`,prompt:`Add Link response headers to the homepage that point at real machine-readable resources, for example Link: </.well-known/api-catalog>; rel="api-catalog" or Link: </llms.txt>; rel="describedby"; type="text/markdown". Confirm every advertised target returns 200, then re-run the agent-readiness report and confirm link-headers passes.`,resources:[{title:`RFC 8288: Web Linking`,url:`https://www.rfc-editor.org/rfc/rfc8288`},{title:`IANA link relation registry`,url:`https://www.iana.org/assignments/link-relations/link-relations.xhtml`}],verify:`curl -I the homepage and confirm the Link header lists the intended relation types and each target resolves.`},"mcp-server-card":{goal:`Describe a real public MCP endpoint with a server card.`,fix:`Serve /.well-known/mcp/server-card.json with serverInfo (name and version), the transport endpoint, and capabilities. Publish it only when the MCP endpoint actually exists.`,prompt:`If this site exposes a public MCP endpoint, publish /.well-known/mcp/server-card.json as JSON with serverInfo, the transport endpoint, and capabilities that match the deployed server. The format is a draft MCP proposal, so note that in any claims. Re-run the agent-readiness report and confirm mcp-server-card reflects the published card.`,resources:[{title:`MCP server card proposal (SEP-1649)`,url:`https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2127`}],verify:`Fetch the card, confirm it parses as JSON with serverInfo, and confirm the declared endpoint accepts an MCP connection.`},"a2a-agent-card":{goal:`Describe a real agent-to-agent endpoint with an agent card.`,fix:`Serve /.well-known/agent-card.json with the agent name, url, and capabilities from the A2A specification. Publish it only when the site hosts an A2A endpoint.`,prompt:`If this site hosts an agent-to-agent endpoint, publish /.well-known/agent-card.json as JSON with at least name, url, and capabilities matching the deployed endpoint. Re-run the agent-readiness report and confirm a2a-agent-card reflects the published card.`,resources:[{title:`A2A protocol specification`,url:`https://a2a-protocol.org/latest/specification/`}],verify:`Fetch the card, confirm it parses as JSON with a name, and confirm the declared endpoint responds.`},"oauth-discovery":{goal:`Let clients discover the authorization server for protected APIs.`,fix:`Publish /.well-known/openid-configuration for OpenID Connect or /.well-known/oauth-authorization-server for plain OAuth 2.0, including issuer, authorization_endpoint, token_endpoint, and jwks_uri.`,prompt:`If this origin has OAuth-protected APIs, publish authorization server metadata at /.well-known/oauth-authorization-server or /.well-known/openid-configuration with the issuer and endpoint URLs of the real authorization server. Re-run the agent-readiness report and confirm oauth-discovery reflects the published metadata.`,resources:[{title:`RFC 8414: OAuth 2.0 Authorization Server Metadata`,url:`https://www.rfc-editor.org/rfc/rfc8414`},{title:`OpenID Connect Discovery`,url:`https://openid.net/specs/openid-connect-discovery-1_0.html`}],verify:`Fetch the metadata, confirm the issuer matches the origin, and complete one token flow against the declared endpoints.`},"oauth-protected-resource":{goal:`Tell clients how to obtain tokens for protected resources.`,fix:`Publish /.well-known/oauth-protected-resource with the resource identifier, the authorization_servers list, and scopes_supported per RFC 9728.`,prompt:`If this origin serves OAuth-protected APIs, publish /.well-known/oauth-protected-resource as JSON with resource, authorization_servers, and scopes_supported values that match the real deployment. Re-run the agent-readiness report and confirm oauth-protected-resource reflects the published metadata.`,resources:[{title:`RFC 9728: OAuth 2.0 Protected Resource Metadata`,url:`https://www.rfc-editor.org/rfc/rfc9728`}],verify:`Fetch the metadata and confirm each listed authorization server issues tokens the resource accepts.`},"api-catalog":{goal:`Make published APIs discoverable through an API catalog.`,fix:`Serve /.well-known/api-catalog as application/linkset+json with a linkset array. Each entry needs an anchor URL plus service-desc and service-doc links for the API.`,prompt:`If this site publishes APIs, create /.well-known/api-catalog returning application/linkset+json with one linkset entry per API, each carrying an anchor and service-desc or service-doc links that resolve. Re-run the agent-readiness report and confirm api-catalog reflects the published catalog.`,resources:[{title:`RFC 9727: api-catalog well-known URI`,url:`https://www.rfc-editor.org/rfc/rfc9727`},{title:`RFC 9264: Linkset`,url:`https://www.rfc-editor.org/rfc/rfc9264`}],verify:`Fetch the catalog, confirm the linkset parses, and confirm each linked description resolves.`},"web-bot-auth":{goal:`Publish signing keys when this operator signs its own bot requests.`,fix:`Serve /.well-known/http-message-signatures-directory with the JSON Web Key Set used to sign outbound bot requests, per the Web Bot Auth draft.`,prompt:`If this operator runs bots that sign requests with Web Bot Auth, publish the signing keys as a JWKS at /.well-known/http-message-signatures-directory. Re-run the agent-readiness report and confirm web-bot-auth reflects the published directory.`,resources:[{title:`Web Bot Auth draft`,url:`https://datatracker.ietf.org/doc/draft-meunier-web-bot-auth-architecture/`}],verify:`Fetch the directory, confirm the keys array parses, and confirm a signed request validates against a published key.`},"auth-md":{goal:`Explain agent registration when agents can really sign up.`,fix:`Serve /auth.md as Markdown describing how an agent registers and authenticates, and pair it with real OAuth discovery metadata at the well-known paths.`,prompt:`If agents can register for and authenticate with this site, publish /auth.md as a Markdown guide covering registration, credential types, and token endpoints, consistent with the OAuth metadata the site publishes. Re-run the agent-readiness report and confirm auth-md reflects the published file.`,resources:[{title:`auth.md proposal`,url:`https://github.com/workos/auth.md`}],verify:`Fetch /auth.md, confirm a text or Markdown content type, and follow the documented registration steps once.`},"identity-graph":{goal:`Connect truthful WebSite, creator, and page-level structured data.`,fix:`Add JSON-LD that links the WebSite node, a Person or Organization creator, and page-level types. Use sameAs only for official profiles, and add SoftwareApplication only when the site represents software.`,prompt:`Add or repair the JSON-LD identity graph: one WebSite node, a truthful Person or Organization creator or publisher, and appropriate page-level types, all linked by id references. Use sameAs only for official profiles of the same entity. Re-run the agent-readiness report and confirm identity-graph passes.`,resources:[{title:`Schema.org WebSite`,url:`https://schema.org/WebSite`}],verify:`Validate the JSON-LD parses and confirm each claimed profile link belongs to the same entity.`}}};function q9(e){return Object.keys(K9[e]??{}).sort()}function oue(e,t){return K9[e]?.[t]}function sue(e){return e.issues.map(e=>`${e.path.length>0?e.path.join(`.`):`input`}: ${e.message}`).join(`; `)}function J9(e){return aue(e)}function Y9(e,t){let n=G9(e);if(!n)throw new j(`INVALID_INPUT`,`Unknown report: ${e}.`);let r=q9(n.id);if(r.length===0)throw new j(`INVALID_INPUT`,`Report ${n.id} has no per-check fix guidance yet.`);let i=oue(n.id,t);if(!i)throw new j(`INVALID_INPUT`,`Unknown check ${t} for ${n.id}. Fix guidance exists for: ${r.join(`, `)}.`);return{id:n.id,category:n.category,name:n.name,check:t,checkFix:i}}function X9(e){let t=G9(e);if(!t)throw new j(`INVALID_INPUT`,`Unknown report: ${e}.`);let n=q9(t.id);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,agentWorkflow:{actionView:{mcp:{id:t.id,view:`actions`},cli:`seo reports run ${t.id} --params '<json>' --actions-only --json`},readOrder:[`findings.coverage and report-level caveats`,`findings.items in returned priority order`,`inventories row by row when present`],completion:`Use each finding type and its allowed outcomes. Fix items support fixed, deferred, or not-needed. Review items support changed, no-change, or deferred. Verify changed items and rerun this report before closing them.`},...n.length>0?{fixableChecks:n}:{},inputSchema:O.toJSONSchema(t.inputSchema)}}async function Z9(e,t={},n={}){let r=G9(e);if(!r)throw new j(`INVALID_INPUT`,`Unknown report: ${e}.`);let i=r.inputSchema.safeParse(t);if(!i.success)throw new j(`INVALID_INPUT`,`Invalid parameters for ${e}: ${sue(i.error)}`);let a=await r.handler(i.data);if(a.isError||!a.structuredContent)return a;let o={reportId:r.id,verify:r.verify},s=n.view===`actions`?Tn(a.structuredContent,o):a.structuredContent;return{...a,structuredContent:u9(s)}}async function Q9(e,t={},n={}){try{return await Z9(e,t,n)}catch(e){return Q(e)}}const cue=new Set(J9().map(e=>e.id));function lue(e,t={}){e.registerTool(`seo_list_reports`,{description:`List compact SEO report ids, names, and purposes, optionally by category`,inputSchema:{category:O.enum(H9).optional()},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0}},async({category:e})=>{let t=J9(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. Pass check to get fix guidance for one failed check id.`,inputSchema:{id:O.string().trim().min(1).max(100),check:O.string().trim().min(1).max(100).optional()},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0}},async({id:e,check:t})=>{try{if(t!==void 0){let n=Y9(e,t);return $(`${n.id} ${n.check}: ${n.checkFix.goal}`,{report:n})}let n=X9(e);return $(`${n.id}: ${n.description}`,{report:n})}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:O.string().trim().min(1).max(100),params:O.record(O.string(),O.unknown()).optional(),view:O.enum([`full`,`actions`]).optional()},annotations:{destructiveHint:!1}},async({id:e,params:n,view:r})=>{let i=cue.has(e)?t.telemetry?.():void 0;i&&_8(e,i);try{let t=await Q9(e,n,{view:r});return i&&(t.isError?y8(e,t.structuredContent?.error,i):v8(e,i)),t}catch(t){throw i&&y8(e,t,i),t}})}function uue(e){e.registerTool(`seo_list_providers`,{description:`List provider packages installed through the seo CLI. Package installation requires human approval in a terminal.`,inputSchema:{},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0}},async()=>{try{let e=await o6();return $(`${e.installed.length} provider packages are installed.`,e)}catch(e){return Q(e)}}),e.registerTool(`seo_describe_provider`,{description:`Describe one installed provider, its shared capabilities, and its agent action schemas`,inputSchema:{id:O.string().trim().min(1).max(64)},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0}},async({id:e})=>{try{let t=await s6(e);return $(`${t.id}: ${t.description}`,t)}catch(e){return Q(e)}}),e.registerTool(`seo_run_provider`,{description:`Run one action from an installed provider after reading its schema with seo_describe_provider`,inputSchema:{id:O.string().trim().min(1).max(64),action:O.string().trim().min(1).max(64),account:O.record(O.string(),O.string()).optional(),params:O.record(O.string(),O.unknown()).optional(),refresh:O.boolean().optional()},annotations:{readOnlyHint:!1,destructiveHint:!1}},async({id:e,action:t,account:n,params:r,refresh:i})=>{try{let a=await a6({providerId:e,actionId:t,account:n??Yk(e),params:r??{},refresh:i});return $(`Provider ${e} completed action ${t}.`,u9(a))}catch(e){return Q(e)}})}function $9(e={}){let t=new Vce({name:`seo`,version:Wr},{capabilities:{logging:{}}}),n=()=>({clientName:t.server.getClientVersion()?.name});return lue(t,{telemetry:e.telemetry===!1?void 0:n}),uue(t),e.telemetry!==!1&&(t.server.oninitialized=()=>{h8(n())}),t}async function due(e={}){let t=$9();if(e.test){process.stdout.write(`seo MCP server constructed successfully.
1128
1128
  `);return}let n=new qce;await t.connect(n)}export{H4 as $,Xk as $n,Tn as $r,KH as $t,o6 as A,OI as An,jo as Ar,sX as At,d3 as B,kA as Bn,Ir as Br,jG as Bt,X6 as C,BR as Cn,Cc as Cr,B$ as Ct,S6 as D,kI as Dn,Vs as Dr,gZ as Dt,x6 as E,eR as En,Js as Er,XZ as Et,U3 as F,CP as Fn,_a as Fr,CY as Ft,o3 as G,pA as Gn,vr as Gr,AW as Gt,c3 as H,DA as Hn,gee as Hr,MG as Ht,H3 as I,hP as In,oa as Ir,dY as It,N4 as J,nA as Jn,pr as Jr,oW as Jt,q4 as K,Qk as Kn,mr as Kr,lW as Kt,k3 as L,aP as Ln,ia as Lr,GJ as Lt,$3 as M,RP as Mn,la as Mr,$Y as Mt,X3 as N,zP as Nn,ba as Nr,KY as Nt,C6 as O,AI as On,zs as Or,TZ as Ot,q3 as P,BP as Pn,ya as Pr,BY as Pt,L4 as Q,Yk as Qn,mee as Qr,cU as Qt,g3 as R,YM as Rn,aa as Rr,RK as Rt,Pie as S,KR as Sn,Oc as Sr,F$ as St,jie as T,SR as Tn,lc as Tr,I$ as Tt,l3 as U,mA as Un,_r as Ur,HW as Ut,t3 as V,MA as Vn,xr as Vr,PG as Vt,s3 as W,fA as Wn,hee as Wr,kW as Wt,R4 as X,sA as Xn,pee as Xr,QU as Xt,W4 as Y,oA as Yn,hr as Yr,tW as Yt,j4 as Z,Zk as Zn,$n as Zr,MU as Zt,y8 as _,oz as _n,Iu as _r,x0 as _t,Y9 as a,ree as ai,Wz as an,Bm as ar,A4 as at,e8 as b,GR as bn,Ac as br,l0 as bt,Q9 as c,j as ci,pB as cn,zm as cr,C4 as ct,rae as d,lz as dn,Ud as dr,R2 as dt,wn as ei,rH as en,wO as er,z4 as et,h8 as f,pz as fn,Qd as fr,I2 as ft,v8 as g,dz as gn,Hu as gr,I0 as gt,aae as h,uz as hn,Fu as hr,Y0 as ht,X9 as i,aee as ii,Uz as in,mh as ir,B4 as it,a6 as j,EI as jn,ca as jr,iX as jt,s6 as k,DI as kn,Qee as kr,RX as kt,due as l,qe as li,Y as ln,Lm as lr,y4 as lt,iae as m,mz as mn,Xd as mr,m2 as mt,u9 as n,en as ni,AB as nn,nO as nr,U4 as nt,Z9 as o,st as oi,Gz as on,Vm as or,S4 as ot,o8 as p,fz as pn,Zd as pr,h2 as pt,M4 as q,cA as qn,Qn as qr,sW as qt,$9 as r,Xt as ri,DB as rn,HD as rr,G4 as rt,J9 as s,ot as si,mB as sn,Rm as sr,x4 as st,H9 as t,tn as ti,DV as tn,XD as tr,V4 as tt,u8 as u,Ge as ui,Fz as un,ff as ur,t4 as ut,_8 as v,az as vn,Xc as vr,d0 as vt,Y6 as w,AR as wn,gc as wr,L$ as wt,t8 as x,qR as xn,kc as xr,c0 as xt,oae as y,UR as yn,Tc as yr,h0 as yt,p3 as z,zM as zn,Wr as zr,cK as zt};
1129
- //# sourceMappingURL=dist-DcFi5w-G.js.map
1129
+ //# sourceMappingURL=dist-CI75G5N6.js.map