@rankcli/agent-runtime 0.0.9 → 0.0.11

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.
Files changed (47) hide show
  1. package/README.md +90 -196
  2. package/dist/analyzer-GMURJADU.mjs +7 -0
  3. package/dist/chunk-2JADKV3Z.mjs +244 -0
  4. package/dist/chunk-3ZSCLNTW.mjs +557 -0
  5. package/dist/chunk-4E4MQOSP.mjs +374 -0
  6. package/dist/chunk-6BWS3CLP.mjs +16 -0
  7. package/dist/chunk-AK2IC22C.mjs +206 -0
  8. package/dist/chunk-K6VSXDD6.mjs +293 -0
  9. package/dist/chunk-M27NQCWW.mjs +303 -0
  10. package/dist/{chunk-YNZYHEYM.mjs → chunk-PJLNXOLN.mjs} +0 -14
  11. package/dist/chunk-VSQD74I7.mjs +474 -0
  12. package/dist/core-web-vitals-analyzer-TE6LQJMS.mjs +7 -0
  13. package/dist/geo-analyzer-D47LTMMA.mjs +25 -0
  14. package/dist/image-optimization-analyzer-XP4OQGRP.mjs +9 -0
  15. package/dist/index.d.mts +612 -17
  16. package/dist/index.d.ts +612 -17
  17. package/dist/index.js +9020 -2686
  18. package/dist/index.mjs +4177 -328
  19. package/dist/internal-linking-analyzer-MRMBV7NM.mjs +9 -0
  20. package/dist/mobile-seo-analyzer-67HNQ7IO.mjs +7 -0
  21. package/dist/security-headers-analyzer-3ZUQARS5.mjs +9 -0
  22. package/dist/structured-data-analyzer-2I4NQAUP.mjs +9 -0
  23. package/package.json +2 -2
  24. package/src/analyzers/core-web-vitals-analyzer.test.ts +236 -0
  25. package/src/analyzers/core-web-vitals-analyzer.ts +557 -0
  26. package/src/analyzers/geo-analyzer.test.ts +310 -0
  27. package/src/analyzers/geo-analyzer.ts +814 -0
  28. package/src/analyzers/image-optimization-analyzer.test.ts +145 -0
  29. package/src/analyzers/image-optimization-analyzer.ts +348 -0
  30. package/src/analyzers/index.ts +233 -0
  31. package/src/analyzers/internal-linking-analyzer.test.ts +141 -0
  32. package/src/analyzers/internal-linking-analyzer.ts +419 -0
  33. package/src/analyzers/mobile-seo-analyzer.test.ts +140 -0
  34. package/src/analyzers/mobile-seo-analyzer.ts +455 -0
  35. package/src/analyzers/security-headers-analyzer.test.ts +115 -0
  36. package/src/analyzers/security-headers-analyzer.ts +318 -0
  37. package/src/analyzers/structured-data-analyzer.test.ts +210 -0
  38. package/src/analyzers/structured-data-analyzer.ts +590 -0
  39. package/src/audit/engine.ts +3 -3
  40. package/src/audit/types.ts +3 -2
  41. package/src/fixer/framework-fixes.test.ts +489 -0
  42. package/src/fixer/framework-fixes.ts +3418 -0
  43. package/src/frameworks/detector.ts +642 -114
  44. package/src/frameworks/suggestion-engine.ts +38 -1
  45. package/src/index.ts +3 -0
  46. package/src/types.ts +15 -1
  47. package/dist/analyzer-2CSWIQGD.mjs +0 -6
@@ -0,0 +1,557 @@
1
+ // src/analyzers/geo-analyzer.ts
2
+ import * as cheerio from "cheerio";
3
+ var AI_CRAWLERS = {
4
+ // OpenAI
5
+ GPTBot: { userAgent: "GPTBot", company: "OpenAI", purpose: "ChatGPT training & browsing" },
6
+ ChatGPTUser: { userAgent: "ChatGPT-User", company: "OpenAI", purpose: "ChatGPT browsing feature" },
7
+ OAI_SearchBot: { userAgent: "OAI-SearchBot", company: "OpenAI", purpose: "OpenAI search features" },
8
+ // Anthropic
9
+ ClaudeBot: { userAgent: "Claude-Web", company: "Anthropic", purpose: "Claude web access" },
10
+ anthropic_ai: { userAgent: "anthropic-ai", company: "Anthropic", purpose: "Claude training" },
11
+ // Perplexity
12
+ PerplexityBot: { userAgent: "PerplexityBot", company: "Perplexity", purpose: "Perplexity AI search" },
13
+ // Google AI
14
+ GoogleOther: { userAgent: "Google-Extended", company: "Google", purpose: "Bard/Gemini training" },
15
+ // Microsoft/Bing
16
+ Bingbot: { userAgent: "bingbot", company: "Microsoft", purpose: "Bing Chat/Copilot" },
17
+ // Meta
18
+ FacebookBot: { userAgent: "FacebookBot", company: "Meta", purpose: "Meta AI features" },
19
+ MetaAI: { userAgent: "meta-externalagent", company: "Meta", purpose: "Meta AI training" },
20
+ // Apple
21
+ Applebot: { userAgent: "Applebot-Extended", company: "Apple", purpose: "Apple Intelligence" },
22
+ // Amazon
23
+ Amazonbot: { userAgent: "Amazonbot", company: "Amazon", purpose: "Alexa/Amazon AI" },
24
+ // Others
25
+ YouBot: { userAgent: "YouBot", company: "You.com", purpose: "You.com AI search" },
26
+ CCBot: { userAgent: "CCBot", company: "Common Crawl", purpose: "Training data" },
27
+ cohere_ai: { userAgent: "cohere-ai", company: "Cohere", purpose: "Cohere training" }
28
+ };
29
+ function analyzeRobotsTxtForAI(robotsTxt) {
30
+ const allowed = [];
31
+ const blocked = [];
32
+ const recommendations = [];
33
+ const lines = robotsTxt.split("\n").map((l) => l.trim().toLowerCase());
34
+ let currentAgent = "*";
35
+ for (const line of lines) {
36
+ if (line.startsWith("user-agent:")) {
37
+ currentAgent = line.replace("user-agent:", "").trim();
38
+ } else if (line.startsWith("disallow:")) {
39
+ const path = line.replace("disallow:", "").trim();
40
+ if (path === "/" || path === "/*") {
41
+ for (const [name, info] of Object.entries(AI_CRAWLERS)) {
42
+ if (currentAgent === "*" || currentAgent.includes(info.userAgent.toLowerCase())) {
43
+ if (!blocked.includes(name)) blocked.push(name);
44
+ }
45
+ }
46
+ }
47
+ } else if (line.startsWith("allow:")) {
48
+ for (const [name, info] of Object.entries(AI_CRAWLERS)) {
49
+ if (currentAgent === "*" || currentAgent.includes(info.userAgent.toLowerCase())) {
50
+ if (!allowed.includes(name)) allowed.push(name);
51
+ }
52
+ }
53
+ }
54
+ }
55
+ for (const [name, info] of Object.entries(AI_CRAWLERS)) {
56
+ const hasExplicitRule = lines.some(
57
+ (l) => l.includes(info.userAgent.toLowerCase())
58
+ );
59
+ if (!hasExplicitRule && !blocked.includes(name) && !allowed.includes(name)) {
60
+ allowed.push(name);
61
+ }
62
+ }
63
+ if (blocked.includes("GPTBot")) {
64
+ recommendations.push("\u26A0\uFE0F GPTBot is blocked - your content won't appear in ChatGPT responses");
65
+ }
66
+ if (blocked.includes("PerplexityBot")) {
67
+ recommendations.push("\u26A0\uFE0F PerplexityBot is blocked - missing Perplexity AI search visibility");
68
+ }
69
+ if (blocked.includes("ClaudeBot")) {
70
+ recommendations.push("\u26A0\uFE0F Claude-Web is blocked - no visibility in Claude AI responses");
71
+ }
72
+ if (allowed.length === 0) {
73
+ recommendations.push("\u{1F6A8} All AI crawlers appear to be blocked - zero AI search visibility");
74
+ }
75
+ return { allowed, blocked, recommendations };
76
+ }
77
+ function detectRenderingMode(html) {
78
+ const $ = cheerio.load(html);
79
+ const signals = [];
80
+ const hasNextData = html.includes("__NEXT_DATA__");
81
+ const hasNuxtData = html.includes("__NUXT__");
82
+ const hasGatsbyData = html.includes("___gatsby");
83
+ const hasAstroIsland = html.includes("astro-island");
84
+ const bodyText = $("body").text().replace(/\s+/g, " ").trim();
85
+ const hasMinimalContent = bodyText.length < 200;
86
+ const hasRootDiv = $("#root, #app, #__next, #__nuxt").length > 0;
87
+ const hasReactRoot = $("[data-reactroot]").length > 0;
88
+ const hasPrerender = $('meta[name="prerender-status-code"]').length > 0 || $('meta[name="fragment"]').attr("content") === "!";
89
+ const paragraphs = $("p").length;
90
+ const headings = $("h1, h2, h3, h4, h5, h6").length;
91
+ const hasSubstantialContent = paragraphs > 2 || headings > 2;
92
+ let serverSideRendered = false;
93
+ let jsRenderingRequired = false;
94
+ if (hasNextData || hasNuxtData || hasGatsbyData || hasAstroIsland) {
95
+ serverSideRendered = true;
96
+ signals.push("Meta-framework SSR detected");
97
+ }
98
+ if (hasMinimalContent && hasRootDiv) {
99
+ jsRenderingRequired = true;
100
+ signals.push("SPA with minimal server content detected");
101
+ }
102
+ if (hasSubstantialContent) {
103
+ serverSideRendered = true;
104
+ signals.push("Substantial HTML content present");
105
+ }
106
+ if (hasReactRoot && !hasNextData && hasMinimalContent) {
107
+ jsRenderingRequired = true;
108
+ signals.push("Client-side React app detected");
109
+ }
110
+ return {
111
+ serverSideRendered,
112
+ jsRenderingRequired,
113
+ hasPrerendering: hasPrerender,
114
+ contentInHTML: hasSubstantialContent,
115
+ signals
116
+ };
117
+ }
118
+ function analyzeContentStructure(html) {
119
+ const $ = cheerio.load(html);
120
+ const jsonLdScripts = $('script[type="application/ld+json"]');
121
+ let hasStructuredData = jsonLdScripts.length > 0;
122
+ let hasFAQSchema = false;
123
+ let hasHowToSchema = false;
124
+ let hasArticleSchema = false;
125
+ let hasProductSchema = false;
126
+ jsonLdScripts.each((_, el) => {
127
+ try {
128
+ const content = $(el).html() || "";
129
+ if (content.includes('"FAQPage"') || content.includes('"@type":"FAQPage"')) {
130
+ hasFAQSchema = true;
131
+ }
132
+ if (content.includes('"HowTo"') || content.includes('"@type":"HowTo"')) {
133
+ hasHowToSchema = true;
134
+ }
135
+ if (content.includes('"Article"') || content.includes('"BlogPosting"') || content.includes('"NewsArticle"')) {
136
+ hasArticleSchema = true;
137
+ }
138
+ if (content.includes('"Product"')) {
139
+ hasProductSchema = true;
140
+ }
141
+ } catch {
142
+ }
143
+ });
144
+ const hasBreadcrumbs = $('[itemtype*="BreadcrumbList"], .breadcrumb, .breadcrumbs, nav[aria-label*="breadcrumb"]').length > 0 || html.includes('"BreadcrumbList"');
145
+ const h1Count = $("h1").length;
146
+ const h2Count = $("h2").length;
147
+ const h3Count = $("h3").length;
148
+ let headingHierarchy = "poor";
149
+ if (h1Count === 1 && h2Count >= 2 && h3Count >= 0) {
150
+ headingHierarchy = "good";
151
+ } else if (h1Count === 1 && h2Count >= 1) {
152
+ headingHierarchy = "needs-work";
153
+ }
154
+ const contentSections = $('section, article, .section, [class*="section"]').length;
155
+ const listsAndTables = $("ul, ol, table, dl").length;
156
+ return {
157
+ hasStructuredData,
158
+ hasFAQSchema,
159
+ hasHowToSchema,
160
+ hasArticleSchema,
161
+ hasProductSchema,
162
+ hasBreadcrumbs,
163
+ headingHierarchy,
164
+ contentSections,
165
+ listsAndTables
166
+ };
167
+ }
168
+ function analyzeCitationReadiness(html) {
169
+ const $ = cheerio.load(html);
170
+ const bodyText = $("body").text();
171
+ const hasCitations = $('cite, blockquote, [class*="citation"], [class*="reference"]').length > 0 || /\[\d+\]|\(\d{4}\)/.test(bodyText);
172
+ const externalLinks = $('a[href^="http"]').filter((_, el) => {
173
+ const href = $(el).attr("href") || "";
174
+ return !href.includes(new URL(href).hostname);
175
+ });
176
+ const hasExternalLinks = externalLinks.length > 0;
177
+ const hasStatistics = /\d+%|\d+\s*(million|billion|thousand)|increased by \d+|decreased by \d+/i.test(bodyText);
178
+ const hasQuotes = $("blockquote, q").length > 0 || /"[^"]{20,}"/.test(bodyText);
179
+ const hasAuthorInfo = $('[rel="author"], .author, [class*="author"], [itemtype*="Person"]').length > 0 || $('meta[name="author"]').length > 0;
180
+ const hasPublishDate = $('time[datetime], meta[property="article:published_time"], .publish-date, .date').length > 0;
181
+ const hasLastModified = $('meta[property="article:modified_time"], .updated, .modified').length > 0;
182
+ const trustSignals = [];
183
+ if (hasCitations) trustSignals.push("Citations present");
184
+ if (hasExternalLinks) trustSignals.push("External source links");
185
+ if (hasStatistics) trustSignals.push("Statistical data");
186
+ if (hasQuotes) trustSignals.push("Expert quotes");
187
+ if (hasAuthorInfo) trustSignals.push("Author attribution");
188
+ if (hasPublishDate) trustSignals.push("Publish date");
189
+ if (hasLastModified) trustSignals.push("Last modified date");
190
+ return {
191
+ hasCitations,
192
+ hasExternalLinks,
193
+ hasStatistics,
194
+ hasQuotes,
195
+ hasAuthorInfo,
196
+ hasPublishDate,
197
+ hasLastModified,
198
+ trustSignals
199
+ };
200
+ }
201
+ function analyzeEntityExtraction(html) {
202
+ const $ = cheerio.load(html);
203
+ const bodyText = $("body").text();
204
+ const definedTerms = [];
205
+ $("dfn, abbr[title]").each((_, el) => {
206
+ const term = $(el).text().trim();
207
+ if (term) definedTerms.push(term);
208
+ });
209
+ const hasDefinitions = /is defined as|refers to|means that|is the process of/i.test(bodyText) || $('dfn, .definition, [class*="definition"]').length > 0;
210
+ const hasComparisons = /compared to|versus|vs\.|unlike|similar to|difference between/i.test(bodyText) || $('table[class*="comparison"], .comparison, [class*="versus"]').length > 0;
211
+ let questionAnswerPairs = 0;
212
+ $('details, .faq-item, [class*="question"], [class*="accordion"]').each(() => {
213
+ questionAnswerPairs++;
214
+ });
215
+ $("h2, h3, h4").each((_, el) => {
216
+ const text = $(el).text();
217
+ if (/^(what|how|why|when|where|who|which|can|does|is|are|should|will)\s/i.test(text) || text.endsWith("?")) {
218
+ questionAnswerPairs++;
219
+ }
220
+ });
221
+ const namedEntities = [];
222
+ $("strong, b").each((_, el) => {
223
+ const text = $(el).text().trim();
224
+ if (text.length > 2 && text.length < 50 && /^[A-Z]/.test(text)) {
225
+ if (!namedEntities.includes(text)) namedEntities.push(text);
226
+ }
227
+ });
228
+ return {
229
+ namedEntities: namedEntities.slice(0, 20),
230
+ // Limit to top 20
231
+ definedTerms,
232
+ hasDefinitions,
233
+ hasComparisons,
234
+ questionAnswerPairs
235
+ };
236
+ }
237
+ function calculateLLMSignals(structure, citation, entity, html) {
238
+ const $ = cheerio.load(html);
239
+ let contentClarity = 0;
240
+ if (structure.headingHierarchy === "good") contentClarity += 30;
241
+ else if (structure.headingHierarchy === "needs-work") contentClarity += 15;
242
+ contentClarity += Math.min(structure.listsAndTables * 5, 25);
243
+ contentClarity += Math.min(structure.contentSections * 5, 20);
244
+ if (structure.hasBreadcrumbs) contentClarity += 10;
245
+ contentClarity += entity.hasDefinitions ? 15 : 0;
246
+ contentClarity = Math.min(contentClarity, 100);
247
+ let factDensity = 0;
248
+ if (citation.hasStatistics) factDensity += 25;
249
+ if (citation.hasCitations) factDensity += 20;
250
+ if (citation.hasExternalLinks) factDensity += 15;
251
+ if (citation.hasQuotes) factDensity += 15;
252
+ factDensity += Math.min(entity.questionAnswerPairs * 5, 25);
253
+ factDensity = Math.min(factDensity, 100);
254
+ let structureQuality = 0;
255
+ if (structure.hasStructuredData) structureQuality += 25;
256
+ if (structure.hasFAQSchema) structureQuality += 20;
257
+ if (structure.hasArticleSchema) structureQuality += 15;
258
+ if (structure.hasHowToSchema) structureQuality += 15;
259
+ if (structure.hasProductSchema) structureQuality += 10;
260
+ structureQuality += structure.headingHierarchy === "good" ? 15 : 0;
261
+ structureQuality = Math.min(structureQuality, 100);
262
+ let citationQuality = 0;
263
+ citationQuality += citation.trustSignals.length * 12;
264
+ if (citation.hasAuthorInfo) citationQuality += 15;
265
+ if (citation.hasPublishDate && citation.hasLastModified) citationQuality += 15;
266
+ citationQuality = Math.min(citationQuality, 100);
267
+ const overallLLMFriendliness = Math.round(
268
+ contentClarity * 0.25 + factDensity * 0.25 + structureQuality * 0.3 + citationQuality * 0.2
269
+ );
270
+ return {
271
+ contentClarity,
272
+ factDensity,
273
+ structureQuality,
274
+ citationQuality,
275
+ overallLLMFriendliness
276
+ };
277
+ }
278
+ function generateGEOIssues(aiAccess, structure, citation, entity, llmSignals, url) {
279
+ const issues = [];
280
+ if (aiAccess.blockedCrawlers.length > 0) {
281
+ issues.push({
282
+ code: "GEO_AI_CRAWLERS_BLOCKED",
283
+ severity: "critical",
284
+ category: "technical",
285
+ title: `${aiAccess.blockedCrawlers.length} AI crawlers blocked in robots.txt`,
286
+ description: `The following AI crawlers are blocked: ${aiAccess.blockedCrawlers.join(", ")}. This prevents your content from appearing in AI search results.`,
287
+ impact: "Your content will NOT appear in ChatGPT, Perplexity, or Claude responses",
288
+ howToFix: `Update robots.txt to allow AI crawlers:
289
+
290
+ User-agent: GPTBot
291
+ Allow: /
292
+
293
+ User-agent: PerplexityBot
294
+ Allow: /
295
+
296
+ User-agent: Claude-Web
297
+ Allow: /`,
298
+ affectedUrls: [url]
299
+ });
300
+ }
301
+ if (aiAccess.jsRenderingRequired && !aiAccess.hasPrerendering) {
302
+ issues.push({
303
+ code: "GEO_JS_RENDERING_REQUIRED",
304
+ severity: "critical",
305
+ category: "technical",
306
+ title: "JavaScript rendering required - AI crawlers see blank page",
307
+ description: "Your site requires JavaScript to render content. Most AI crawlers (GPTBot, PerplexityBot) do not execute JavaScript and will see a blank page.",
308
+ impact: "Zero visibility in AI search - crawlers cannot access your content",
309
+ howToFix: "Implement Server-Side Rendering (SSR) or Static Site Generation (SSG). For React: use Next.js. For Vue: use Nuxt. Alternatively, implement a prerendering service.",
310
+ affectedUrls: [url]
311
+ });
312
+ }
313
+ if (!structure.hasStructuredData) {
314
+ issues.push({
315
+ code: "GEO_NO_STRUCTURED_DATA",
316
+ severity: "warning",
317
+ category: "technical",
318
+ title: "No JSON-LD structured data found",
319
+ description: "Structured data helps AI systems understand your content's context, entities, and relationships.",
320
+ impact: "AI systems may misunderstand or skip your content",
321
+ howToFix: "Add JSON-LD structured data. At minimum, include Organization, WebPage, and Article/Product schemas.",
322
+ affectedUrls: [url]
323
+ });
324
+ }
325
+ if (!structure.hasFAQSchema && entity.questionAnswerPairs > 0) {
326
+ issues.push({
327
+ code: "GEO_MISSING_FAQ_SCHEMA",
328
+ severity: "warning",
329
+ category: "technical",
330
+ title: "Q&A content without FAQPage schema",
331
+ description: `Found ${entity.questionAnswerPairs} question-answer pairs but no FAQPage schema. FAQ schema is excellent for AI search visibility.`,
332
+ impact: "Missing opportunity for featured snippets and AI citations",
333
+ howToFix: "Add FAQPage schema markup for your Q&A content. This dramatically increases chances of being cited by AI.",
334
+ affectedUrls: [url]
335
+ });
336
+ }
337
+ if (structure.headingHierarchy === "poor") {
338
+ issues.push({
339
+ code: "GEO_POOR_HEADING_STRUCTURE",
340
+ severity: "warning",
341
+ category: "content",
342
+ title: "Poor heading hierarchy hurts AI understanding",
343
+ description: "AI systems use heading structure to understand content organization. Your page lacks a clear H1 \u2192 H2 \u2192 H3 hierarchy.",
344
+ impact: "AI may struggle to extract key topics and relationships",
345
+ howToFix: "Structure content with one H1 (main topic), multiple H2s (subtopics), and H3s (details). Use question-format headings where appropriate.",
346
+ affectedUrls: [url]
347
+ });
348
+ }
349
+ if (!citation.hasCitations && !citation.hasExternalLinks) {
350
+ issues.push({
351
+ code: "GEO_NO_CITATIONS",
352
+ severity: "warning",
353
+ category: "content",
354
+ title: "No citations or source references",
355
+ description: "AI systems prioritize content with verifiable sources and citations. Your page lacks external references.",
356
+ impact: "Lower trust score in AI ranking - may not be cited as authoritative",
357
+ howToFix: "Add citations, link to authoritative sources, include statistics with references, and add expert quotes.",
358
+ affectedUrls: [url]
359
+ });
360
+ }
361
+ if (!citation.hasAuthorInfo) {
362
+ issues.push({
363
+ code: "GEO_NO_AUTHOR_INFO",
364
+ severity: "info",
365
+ category: "content",
366
+ title: "Missing author attribution",
367
+ description: "Author information helps establish E-E-A-T (Experience, Expertise, Authoritativeness, Trustworthiness) for AI systems.",
368
+ impact: "Lower credibility score in AI ranking algorithms",
369
+ howToFix: "Add author byline, link to author bio/profile, and consider adding author schema markup.",
370
+ affectedUrls: [url]
371
+ });
372
+ }
373
+ if (!citation.hasPublishDate || !citation.hasLastModified) {
374
+ issues.push({
375
+ code: "GEO_NO_DATES",
376
+ severity: "info",
377
+ category: "content",
378
+ title: "Missing publish/update dates",
379
+ description: "AI systems consider content freshness. Pages without dates may be considered outdated.",
380
+ impact: "May be deprioritized for time-sensitive queries",
381
+ howToFix: 'Add visible publish date and "last updated" date. Include datePublished and dateModified in schema.',
382
+ affectedUrls: [url]
383
+ });
384
+ }
385
+ if (llmSignals.overallLLMFriendliness < 40) {
386
+ issues.push({
387
+ code: "GEO_LOW_LLM_SCORE",
388
+ severity: "warning",
389
+ category: "content",
390
+ title: `Low AI-friendliness score: ${llmSignals.overallLLMFriendliness}/100`,
391
+ description: "Your content structure and signals are not optimized for AI consumption. AI systems may struggle to extract and cite your content.",
392
+ impact: "Significantly reduced chances of appearing in AI search results",
393
+ howToFix: "Improve content structure (clear headings, lists, tables), add citations and statistics, include FAQ sections, and add comprehensive schema markup.",
394
+ affectedUrls: [url]
395
+ });
396
+ }
397
+ return issues;
398
+ }
399
+ async function analyzeGEO(html, url, robotsTxt) {
400
+ let aiCrawlerAccess = {
401
+ robotsTxtAllowsAI: true,
402
+ blockedCrawlers: [],
403
+ allowedCrawlers: Object.keys(AI_CRAWLERS),
404
+ hasPrerendering: false,
405
+ jsRenderingRequired: false,
406
+ serverSideRendered: true
407
+ };
408
+ if (robotsTxt) {
409
+ const robotsAnalysis = analyzeRobotsTxtForAI(robotsTxt);
410
+ aiCrawlerAccess.blockedCrawlers = robotsAnalysis.blocked;
411
+ aiCrawlerAccess.allowedCrawlers = robotsAnalysis.allowed;
412
+ aiCrawlerAccess.robotsTxtAllowsAI = robotsAnalysis.blocked.length === 0;
413
+ }
414
+ const renderingMode = detectRenderingMode(html);
415
+ aiCrawlerAccess.hasPrerendering = renderingMode.hasPrerendering;
416
+ aiCrawlerAccess.jsRenderingRequired = renderingMode.jsRenderingRequired;
417
+ aiCrawlerAccess.serverSideRendered = renderingMode.serverSideRendered;
418
+ const contentStructure = analyzeContentStructure(html);
419
+ const citationReadiness = analyzeCitationReadiness(html);
420
+ const entityExtraction = analyzeEntityExtraction(html);
421
+ const llmSignals = calculateLLMSignals(
422
+ contentStructure,
423
+ citationReadiness,
424
+ entityExtraction,
425
+ html
426
+ );
427
+ const issues = generateGEOIssues(
428
+ aiCrawlerAccess,
429
+ contentStructure,
430
+ citationReadiness,
431
+ entityExtraction,
432
+ llmSignals,
433
+ url
434
+ );
435
+ const recommendations = [];
436
+ if (aiCrawlerAccess.blockedCrawlers.length > 0) {
437
+ recommendations.push("\u{1F6A8} URGENT: Unblock AI crawlers in robots.txt");
438
+ }
439
+ if (aiCrawlerAccess.jsRenderingRequired) {
440
+ recommendations.push("\u{1F6A8} URGENT: Implement SSR/SSG for AI crawler access");
441
+ }
442
+ if (!contentStructure.hasFAQSchema && entityExtraction.questionAnswerPairs > 0) {
443
+ recommendations.push("Add FAQPage schema for your Q&A content");
444
+ }
445
+ if (!contentStructure.hasStructuredData) {
446
+ recommendations.push("Add JSON-LD structured data (Article, Organization)");
447
+ }
448
+ if (citationReadiness.trustSignals.length < 3) {
449
+ recommendations.push("Add more trust signals: citations, statistics, author info");
450
+ }
451
+ if (contentStructure.listsAndTables < 2) {
452
+ recommendations.push("Add lists and tables - AI loves structured content");
453
+ }
454
+ if (!entityExtraction.hasDefinitions) {
455
+ recommendations.push("Add clear definitions for key terms");
456
+ }
457
+ if (entityExtraction.questionAnswerPairs < 3) {
458
+ recommendations.push("Add FAQ section with common questions");
459
+ }
460
+ let score = llmSignals.overallLLMFriendliness;
461
+ if (aiCrawlerAccess.blockedCrawlers.length > 0) {
462
+ score -= aiCrawlerAccess.blockedCrawlers.length * 10;
463
+ }
464
+ if (aiCrawlerAccess.jsRenderingRequired && !aiCrawlerAccess.hasPrerendering) {
465
+ score -= 30;
466
+ }
467
+ if (contentStructure.hasFAQSchema) score += 10;
468
+ if (contentStructure.hasArticleSchema) score += 5;
469
+ if (aiCrawlerAccess.robotsTxtAllowsAI) score += 10;
470
+ score = Math.max(0, Math.min(100, score));
471
+ return {
472
+ score: Math.round(score),
473
+ aiCrawlerAccess,
474
+ contentStructure,
475
+ citationReadiness,
476
+ entityExtraction,
477
+ llmSignals,
478
+ issues,
479
+ recommendations
480
+ };
481
+ }
482
+ function generateAIFriendlyRobotsTxt(siteUrl) {
483
+ return `# AI-Optimized robots.txt
484
+ # Generated by RankCLI - https://rankcli.dev
485
+
486
+ # Allow all standard crawlers
487
+ User-agent: *
488
+ Allow: /
489
+ Disallow: /api/
490
+ Disallow: /admin/
491
+ Disallow: /_next/static/
492
+ Disallow: /private/
493
+
494
+ # === AI Search Crawlers ===
495
+ # OpenAI (ChatGPT)
496
+ User-agent: GPTBot
497
+ Allow: /
498
+
499
+ User-agent: ChatGPT-User
500
+ Allow: /
501
+
502
+ User-agent: OAI-SearchBot
503
+ Allow: /
504
+
505
+ # Anthropic (Claude)
506
+ User-agent: Claude-Web
507
+ Allow: /
508
+
509
+ User-agent: anthropic-ai
510
+ Allow: /
511
+
512
+ # Perplexity
513
+ User-agent: PerplexityBot
514
+ Allow: /
515
+
516
+ # Google AI (Gemini/Bard)
517
+ User-agent: Google-Extended
518
+ Allow: /
519
+
520
+ # Microsoft (Copilot)
521
+ User-agent: bingbot
522
+ Allow: /
523
+
524
+ # Apple Intelligence
525
+ User-agent: Applebot-Extended
526
+ Allow: /
527
+
528
+ # Meta AI
529
+ User-agent: meta-externalagent
530
+ Allow: /
531
+
532
+ # You.com
533
+ User-agent: YouBot
534
+ Allow: /
535
+
536
+ # Cohere
537
+ User-agent: cohere-ai
538
+ Allow: /
539
+
540
+ # Sitemap
541
+ Sitemap: ${siteUrl}/sitemap.xml
542
+ `;
543
+ }
544
+ var AI_CRAWLERS_INFO = AI_CRAWLERS;
545
+
546
+ export {
547
+ analyzeRobotsTxtForAI,
548
+ detectRenderingMode,
549
+ analyzeContentStructure,
550
+ analyzeCitationReadiness,
551
+ analyzeEntityExtraction,
552
+ calculateLLMSignals,
553
+ generateGEOIssues,
554
+ analyzeGEO,
555
+ generateAIFriendlyRobotsTxt,
556
+ AI_CRAWLERS_INFO
557
+ };