@rankcli/agent-runtime 0.0.8 → 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.
- package/README.md +90 -196
- package/dist/analyzer-GMURJADU.mjs +7 -0
- package/dist/chunk-2JADKV3Z.mjs +244 -0
- package/dist/chunk-3ZSCLNTW.mjs +557 -0
- package/dist/chunk-4E4MQOSP.mjs +374 -0
- package/dist/chunk-6BWS3CLP.mjs +16 -0
- package/dist/chunk-AK2IC22C.mjs +206 -0
- package/dist/chunk-K6VSXDD6.mjs +293 -0
- package/dist/chunk-M27NQCWW.mjs +303 -0
- package/dist/{chunk-YNZYHEYM.mjs → chunk-PJLNXOLN.mjs} +0 -14
- package/dist/chunk-VSQD74I7.mjs +474 -0
- package/dist/core-web-vitals-analyzer-TE6LQJMS.mjs +7 -0
- package/dist/geo-analyzer-D47LTMMA.mjs +25 -0
- package/dist/image-optimization-analyzer-XP4OQGRP.mjs +9 -0
- package/dist/index.d.mts +1523 -17
- package/dist/index.d.ts +1523 -17
- package/dist/index.js +9582 -2664
- package/dist/index.mjs +4812 -380
- package/dist/internal-linking-analyzer-MRMBV7NM.mjs +9 -0
- package/dist/mobile-seo-analyzer-67HNQ7IO.mjs +7 -0
- package/dist/security-headers-analyzer-3ZUQARS5.mjs +9 -0
- package/dist/structured-data-analyzer-2I4NQAUP.mjs +9 -0
- package/package.json +2 -2
- package/src/analyzers/core-web-vitals-analyzer.test.ts +236 -0
- package/src/analyzers/core-web-vitals-analyzer.ts +557 -0
- package/src/analyzers/geo-analyzer.test.ts +310 -0
- package/src/analyzers/geo-analyzer.ts +814 -0
- package/src/analyzers/image-optimization-analyzer.test.ts +145 -0
- package/src/analyzers/image-optimization-analyzer.ts +348 -0
- package/src/analyzers/index.ts +233 -0
- package/src/analyzers/internal-linking-analyzer.test.ts +141 -0
- package/src/analyzers/internal-linking-analyzer.ts +419 -0
- package/src/analyzers/mobile-seo-analyzer.test.ts +140 -0
- package/src/analyzers/mobile-seo-analyzer.ts +455 -0
- package/src/analyzers/security-headers-analyzer.test.ts +115 -0
- package/src/analyzers/security-headers-analyzer.ts +318 -0
- package/src/analyzers/structured-data-analyzer.test.ts +210 -0
- package/src/analyzers/structured-data-analyzer.ts +590 -0
- package/src/audit/engine.ts +3 -3
- package/src/audit/types.ts +3 -2
- package/src/fixer/framework-fixes.test.ts +489 -0
- package/src/fixer/framework-fixes.ts +3418 -0
- package/src/fixer/index.ts +1 -0
- package/src/fixer/schemas.ts +971 -0
- package/src/frameworks/detector.ts +642 -114
- package/src/frameworks/suggestion-engine.ts +38 -1
- package/src/index.ts +6 -0
- package/src/types.ts +15 -1
- 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
|
+
};
|