@rivi-software/agentic-seo-check 0.1.0 → 0.2.0

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 (144) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +124 -18
  3. package/dist/brand.d.ts +11 -0
  4. package/dist/brand.js +22 -0
  5. package/dist/brand.js.map +1 -0
  6. package/dist/cli.js +174 -86
  7. package/dist/cli.js.map +1 -1
  8. package/dist/core/aeo/audit.d.ts +44 -0
  9. package/dist/core/aeo/audit.js +310 -0
  10. package/dist/core/aeo/audit.js.map +1 -0
  11. package/dist/core/aeo/bots.d.ts +30 -0
  12. package/dist/core/aeo/bots.js +299 -0
  13. package/dist/core/aeo/bots.js.map +1 -0
  14. package/dist/core/aeo/llms-txt.d.ts +8 -0
  15. package/dist/core/aeo/llms-txt.js +88 -0
  16. package/dist/core/aeo/llms-txt.js.map +1 -0
  17. package/dist/core/aeo/page.d.ts +53 -0
  18. package/dist/core/aeo/page.js +312 -0
  19. package/dist/core/aeo/page.js.map +1 -0
  20. package/dist/core/aeo/probe.d.ts +10 -0
  21. package/dist/core/aeo/probe.js +53 -0
  22. package/dist/core/aeo/probe.js.map +1 -0
  23. package/dist/core/aeo/site-rules.d.ts +13 -0
  24. package/dist/core/aeo/site-rules.js +178 -0
  25. package/dist/core/aeo/site-rules.js.map +1 -0
  26. package/dist/core/audit.d.ts +37 -16
  27. package/dist/core/audit.js +389 -249
  28. package/dist/core/audit.js.map +1 -1
  29. package/dist/core/concurrency.d.ts +4 -4
  30. package/dist/core/concurrency.js +33 -23
  31. package/dist/core/concurrency.js.map +1 -1
  32. package/dist/core/crawl.d.ts +9 -5
  33. package/dist/core/crawl.js +271 -123
  34. package/dist/core/crawl.js.map +1 -1
  35. package/dist/core/fetch.d.ts +5 -5
  36. package/dist/core/fetch.js +151 -100
  37. package/dist/core/fetch.js.map +1 -1
  38. package/dist/core/html/answer.d.ts +4 -0
  39. package/dist/core/html/answer.js +198 -0
  40. package/dist/core/html/answer.js.map +1 -0
  41. package/dist/core/html/dom.d.ts +17 -0
  42. package/dist/core/html/dom.js +83 -0
  43. package/dist/core/html/dom.js.map +1 -0
  44. package/dist/core/html/extract.d.ts +2 -2
  45. package/dist/core/html/extract.js +102 -111
  46. package/dist/core/html/extract.js.map +1 -1
  47. package/dist/core/html/structured-data.d.ts +16 -6
  48. package/dist/core/html/structured-data.js +180 -145
  49. package/dist/core/html/structured-data.js.map +1 -1
  50. package/dist/core/html/text.d.ts +14 -14
  51. package/dist/core/html/text.js +110 -107
  52. package/dist/core/html/text.js.map +1 -1
  53. package/dist/core/lighthouse.d.ts +26 -25
  54. package/dist/core/lighthouse.js +181 -105
  55. package/dist/core/lighthouse.js.map +1 -1
  56. package/dist/core/performance-rules.d.ts +2 -2
  57. package/dist/core/performance-rules.js +105 -33
  58. package/dist/core/performance-rules.js.map +1 -1
  59. package/dist/core/render-rules.d.ts +5 -4
  60. package/dist/core/render-rules.js +129 -62
  61. package/dist/core/render-rules.js.map +1 -1
  62. package/dist/core/render.d.ts +2 -2
  63. package/dist/core/render.js +29 -23
  64. package/dist/core/render.js.map +1 -1
  65. package/dist/core/report/markdown.d.ts +4 -2
  66. package/dist/core/report/markdown.js +210 -129
  67. package/dist/core/report/markdown.js.map +1 -1
  68. package/dist/core/robots.d.ts +12 -5
  69. package/dist/core/robots.js +43 -17
  70. package/dist/core/robots.js.map +1 -1
  71. package/dist/core/rules/aeo.d.ts +11 -0
  72. package/dist/core/rules/aeo.js +423 -0
  73. package/dist/core/rules/aeo.js.map +1 -0
  74. package/dist/core/rules/canonical.d.ts +1 -1
  75. package/dist/core/rules/canonical.js +93 -57
  76. package/dist/core/rules/canonical.js.map +1 -1
  77. package/dist/core/rules/content.js +40 -26
  78. package/dist/core/rules/content.js.map +1 -1
  79. package/dist/core/rules/document.js +64 -41
  80. package/dist/core/rules/document.js.map +1 -1
  81. package/dist/core/rules/headings.js +58 -38
  82. package/dist/core/rules/headings.js.map +1 -1
  83. package/dist/core/rules/hreflang.js +70 -38
  84. package/dist/core/rules/hreflang.js.map +1 -1
  85. package/dist/core/rules/images.js +44 -26
  86. package/dist/core/rules/images.js.map +1 -1
  87. package/dist/core/rules/index.d.ts +10 -6
  88. package/dist/core/rules/index.js +43 -36
  89. package/dist/core/rules/index.js.map +1 -1
  90. package/dist/core/rules/indexing.d.ts +2 -2
  91. package/dist/core/rules/indexing.js +48 -26
  92. package/dist/core/rules/indexing.js.map +1 -1
  93. package/dist/core/rules/keyword.js +84 -47
  94. package/dist/core/rules/keyword.js.map +1 -1
  95. package/dist/core/rules/links.js +72 -37
  96. package/dist/core/rules/links.js.map +1 -1
  97. package/dist/core/rules/meta.js +55 -34
  98. package/dist/core/rules/meta.js.map +1 -1
  99. package/dist/core/rules/response.js +75 -41
  100. package/dist/core/rules/response.js.map +1 -1
  101. package/dist/core/rules/rule.d.ts +6 -6
  102. package/dist/core/rules/rule.js +36 -36
  103. package/dist/core/rules/rule.js.map +1 -1
  104. package/dist/core/rules/social.js +86 -45
  105. package/dist/core/rules/social.js.map +1 -1
  106. package/dist/core/rules/structured-data.d.ts +6 -4
  107. package/dist/core/rules/structured-data.js +314 -102
  108. package/dist/core/rules/structured-data.js.map +1 -1
  109. package/dist/core/rules/title.js +48 -33
  110. package/dist/core/rules/title.js.map +1 -1
  111. package/dist/core/score.d.ts +5 -3
  112. package/dist/core/score.js +11 -9
  113. package/dist/core/score.js.map +1 -1
  114. package/dist/core/site-rules.d.ts +3 -3
  115. package/dist/core/site-rules.js +409 -198
  116. package/dist/core/site-rules.js.map +1 -1
  117. package/dist/core/sitemap.d.ts +5 -5
  118. package/dist/core/sitemap.js +109 -64
  119. package/dist/core/sitemap.js.map +1 -1
  120. package/dist/core/static.d.ts +2 -2
  121. package/dist/core/static.js +210 -112
  122. package/dist/core/static.js.map +1 -1
  123. package/dist/core/summary.d.ts +2 -2
  124. package/dist/core/summary.js +11 -6
  125. package/dist/core/summary.js.map +1 -1
  126. package/dist/core/types.d.ts +189 -14
  127. package/dist/core/types.js +15 -8
  128. package/dist/core/types.js.map +1 -1
  129. package/dist/core/url.d.ts +11 -11
  130. package/dist/core/url.js +122 -70
  131. package/dist/core/url.js.map +1 -1
  132. package/dist/index.d.ts +15 -11
  133. package/dist/index.js +11 -9
  134. package/dist/index.js.map +1 -1
  135. package/dist/mcp-bin.js +7 -4
  136. package/dist/mcp-bin.js.map +1 -1
  137. package/dist/mcp.d.ts +3 -3
  138. package/dist/mcp.js +437 -121
  139. package/dist/mcp.js.map +1 -1
  140. package/dist/version.d.ts +0 -1
  141. package/dist/version.js +4 -2
  142. package/dist/version.js.map +1 -1
  143. package/package.json +32 -10
  144. package/skills/seo-audit/SKILL.md +8 -2
@@ -0,0 +1,299 @@
1
+ import { agentMatcher } from "../robots.js";
2
+ const OPENAI_DOCS = "https://developers.openai.com/api/docs/bots";
3
+ const ANTHROPIC_DOCS = "https://support.claude.com/en/articles/8896518-does-anthropic-crawl-data-from-the-web-and-how-can-site-owners-block-the-crawler";
4
+ const PERPLEXITY_DOCS = "https://docs.perplexity.ai/guides/bots";
5
+ const GOOGLE_CRAWLERS_DOCS = "https://developers.google.com/crawling/docs/crawlers-fetchers/google-common-crawlers";
6
+ /**
7
+ * The crawlers that decide whether a site can show up in AI answers, from the operators' own
8
+ * documentation. Order: search crawlers, answer bots, training bots.
9
+ */
10
+ export const AI_BOTS = [
11
+ {
12
+ name: "Googlebot",
13
+ operator: "Google",
14
+ purpose: "search",
15
+ usedFor: "Google Search, AI Overviews and AI Mode",
16
+ docs: "https://developers.google.com/search/docs/appearance/ai-features",
17
+ },
18
+ {
19
+ name: "Bingbot",
20
+ operator: "Microsoft",
21
+ purpose: "search",
22
+ usedFor: "Bing search and Microsoft Copilot",
23
+ docs: "https://www.bing.com/webmasters/help/webmaster-guidelines-30fba23a",
24
+ },
25
+ {
26
+ name: "Applebot",
27
+ operator: "Apple",
28
+ purpose: "search",
29
+ usedFor: "Siri, Spotlight and Safari search",
30
+ docs: "https://support.apple.com/en-us/119829",
31
+ },
32
+ {
33
+ name: "OAI-SearchBot",
34
+ operator: "OpenAI",
35
+ purpose: "answer",
36
+ usedFor: "ChatGPT search results",
37
+ docs: OPENAI_DOCS,
38
+ userAgent: "Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko); compatible; OAI-SearchBot/1.4; +https://openai.com/searchbot",
39
+ },
40
+ {
41
+ name: "ChatGPT-User",
42
+ operator: "OpenAI",
43
+ purpose: "answer",
44
+ usedFor: "pages ChatGPT fetches for a user; robots.txt may not apply",
45
+ docs: OPENAI_DOCS,
46
+ userAgent: "Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko); compatible; ChatGPT-User/1.0; +https://openai.com/bot",
47
+ },
48
+ {
49
+ name: "Claude-SearchBot",
50
+ operator: "Anthropic",
51
+ purpose: "answer",
52
+ usedFor: "Claude's search results",
53
+ docs: ANTHROPIC_DOCS,
54
+ userAgent: "Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko; compatible; Claude-SearchBot/1.0; +https://www.anthropic.com)",
55
+ },
56
+ {
57
+ name: "Claude-User",
58
+ operator: "Anthropic",
59
+ purpose: "answer",
60
+ usedFor: "pages Claude fetches for a user's question",
61
+ docs: ANTHROPIC_DOCS,
62
+ userAgent: "Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko; compatible; Claude-User/1.0; +https://www.anthropic.com)",
63
+ },
64
+ {
65
+ name: "PerplexityBot",
66
+ operator: "Perplexity",
67
+ purpose: "answer",
68
+ usedFor: "Perplexity search results",
69
+ docs: PERPLEXITY_DOCS,
70
+ userAgent: "Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko; compatible; PerplexityBot/1.0; +https://perplexity.ai/perplexitybot)",
71
+ },
72
+ {
73
+ name: "Perplexity-User",
74
+ operator: "Perplexity",
75
+ purpose: "answer",
76
+ usedFor: "pages Perplexity fetches for a user",
77
+ docs: PERPLEXITY_DOCS,
78
+ userAgent: "Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko; compatible; Perplexity-User/1.0; +https://perplexity.ai/perplexity-user)",
79
+ },
80
+ {
81
+ name: "Google-Extended",
82
+ operator: "Google",
83
+ purpose: "answer",
84
+ usedFor: "Gemini Apps grounding and Gemini training, as a robots.txt token; not AI Overviews",
85
+ docs: GOOGLE_CRAWLERS_DOCS,
86
+ },
87
+ {
88
+ name: "DuckAssistBot",
89
+ operator: "DuckDuckGo",
90
+ purpose: "answer",
91
+ usedFor: "DuckDuckGo's AI-assisted answers",
92
+ docs: "https://duckduckgo.com/duckduckgo-help-pages/results/duckassistbot",
93
+ userAgent: "DuckAssistBot/1.2; (+http://duckduckgo.com/duckassistbot.html)",
94
+ },
95
+ {
96
+ name: "MistralAI-User",
97
+ operator: "Mistral AI",
98
+ purpose: "answer",
99
+ usedFor: "pages Le Chat fetches for a user",
100
+ docs: "https://docs.mistral.ai/robots",
101
+ userAgent: "Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko; compatible; MistralAI-User/1.0; +https://docs.mistral.ai/robots)",
102
+ },
103
+ {
104
+ name: "GPTBot",
105
+ operator: "OpenAI",
106
+ purpose: "training",
107
+ usedFor: "training OpenAI's models",
108
+ docs: OPENAI_DOCS,
109
+ userAgent: "Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko); compatible; GPTBot/1.4; +https://openai.com/gptbot",
110
+ },
111
+ {
112
+ name: "ClaudeBot",
113
+ operator: "Anthropic",
114
+ purpose: "training",
115
+ usedFor: "training Anthropic's models",
116
+ docs: ANTHROPIC_DOCS,
117
+ userAgent: "Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko; compatible; ClaudeBot/1.0; +claudebot@anthropic.com)",
118
+ },
119
+ {
120
+ name: "Applebot-Extended",
121
+ operator: "Apple",
122
+ purpose: "training",
123
+ usedFor: "training Apple's models, as a robots.txt token",
124
+ docs: "https://support.apple.com/en-us/119829",
125
+ },
126
+ {
127
+ name: "CCBot",
128
+ operator: "Common Crawl",
129
+ purpose: "training",
130
+ usedFor: "Common Crawl's archive, which many models train on",
131
+ docs: "https://commoncrawl.org/ccbot",
132
+ userAgent: "CCBot/2.0 (https://commoncrawl.org/faq/)",
133
+ },
134
+ {
135
+ name: "meta-externalagent",
136
+ operator: "Meta",
137
+ purpose: "training",
138
+ usedFor: "training Meta's AI models and indexing for Meta products",
139
+ docs: "https://developers.facebook.com/docs/sharing/webmasters/web-crawlers",
140
+ userAgent: "meta-externalagent/1.1 (+https://developers.facebook.com/docs/sharing/webmasters/crawler)",
141
+ },
142
+ ];
143
+ /** The policy token of the shared AI training preference (draft-kelley-aipref-training-preference-signal). */
144
+ export const TRAINING_PREFERENCE_TOKEN = "__usage_ai-training";
145
+ /** Signals the Content Signals Policy defines, plus Cloudflare's experimental `use`. */
146
+ const KNOWN_SIGNALS = new Set(["search", "ai-input", "ai-train", "use"]);
147
+ /** How robots.txt groups match product tokens: lower-cased, without a version. */
148
+ const productToken = (agent) => agent.split("/")[0]?.trim().toLowerCase() ?? "";
149
+ /** The groups of a robots.txt file: every user agent it names, and each rule line with its group. */
150
+ const readGroups = (text) => {
151
+ const named = new Set();
152
+ const lines = [];
153
+ let agents = [];
154
+ let inAgentRun = false;
155
+ for (const raw of text.split(/\r\n|\r|\n/u)) {
156
+ const hash = raw.indexOf("#");
157
+ const line = (hash === -1 ? raw : raw.slice(0, hash)).trim();
158
+ const colon = line.indexOf(":");
159
+ if (colon === -1) {
160
+ continue;
161
+ }
162
+ const key = line.slice(0, colon).trim().toLowerCase();
163
+ const value = line.slice(colon + 1).trim();
164
+ if (key === "user-agent") {
165
+ // Consecutive user-agent lines share one group; a rule line in between starts a new one.
166
+ if (!inAgentRun) {
167
+ agents = [];
168
+ }
169
+ if (value) {
170
+ agents.push(value);
171
+ named.add(productToken(value));
172
+ }
173
+ inAgentRun = true;
174
+ continue;
175
+ }
176
+ inAgentRun = false;
177
+ lines.push({ agents, key, value });
178
+ }
179
+ return { named, lines };
180
+ };
181
+ /**
182
+ * Content-Signal lines: `Content-Signal: search=yes, ai-input=no` or, for one path,
183
+ * `Content-Signal: /blog/ ai-train=no`.
184
+ */
185
+ export const contentSignals = (text) => readGroups(text)
186
+ .lines.filter((l) => l.key === "content-signal" && l.value)
187
+ .slice(0, 50)
188
+ .map((l) => {
189
+ let rest = l.value;
190
+ let path = null;
191
+ if (rest.startsWith("/")) {
192
+ const space = rest.search(/\s/u);
193
+ path = space === -1 ? rest : rest.slice(0, space);
194
+ rest = space === -1 ? "" : rest.slice(space + 1);
195
+ }
196
+ const signals = {};
197
+ for (const pair of rest.split(",")) {
198
+ const eq = pair.indexOf("=");
199
+ const name = (eq === -1 ? pair : pair.slice(0, eq))
200
+ .trim()
201
+ .toLowerCase();
202
+ if (name) {
203
+ signals[name] =
204
+ eq === -1
205
+ ? ""
206
+ : pair
207
+ .slice(eq + 1)
208
+ .trim()
209
+ .toLowerCase();
210
+ }
211
+ }
212
+ return { userAgents: l.agents, path, signals };
213
+ });
214
+ /** Signals in a Content-Signal line that the policy does not define, or values other than yes and no. */
215
+ export const invalidSignals = (signal) => Object.entries(signal.signals).flatMap(([name, value]) => {
216
+ if (!KNOWN_SIGNALS.has(name)) {
217
+ return [`${name}=${value}`];
218
+ }
219
+ return name !== "use" && value !== "yes" && value !== "no"
220
+ ? [`${name}=${value}`]
221
+ : [];
222
+ });
223
+ /**
224
+ * robots.txt as RFC 9309 has crawlers read it: a file is "found" with status 200, "missing" with a
225
+ * 4xx (crawl everything), "unreachable" with a 5xx or no answer (crawl nothing).
226
+ */
227
+ const robotsStatus = (robots) => {
228
+ if (robots.found) {
229
+ return "found";
230
+ }
231
+ if (robots.status === null || robots.status >= 500) {
232
+ return "unreachable";
233
+ }
234
+ return "missing";
235
+ };
236
+ /** What robots.txt lets each AI bot do on the audited pages; `startUrl` is the page the summary describes. */
237
+ export const crawlerAccess = (robots, text, startUrl, pageUrls) => {
238
+ const status = robotsStatus(robots);
239
+ const found = status === "found" && text !== null;
240
+ const groups = found ? readGroups(text) : null;
241
+ const allows = found ? agentMatcher(robots.url, text) : null;
242
+ const urls = pageUrls.length ? pageUrls : [startUrl];
243
+ const bots = AI_BOTS.map((bot) => {
244
+ const token = productToken(bot.name);
245
+ let robotsGroup = "none";
246
+ let agent = bot.name;
247
+ if (groups?.named.has(token)) {
248
+ robotsGroup = "named";
249
+ }
250
+ else if (token === "applebot" && groups?.named.has("googlebot")) {
251
+ // Apple: without an Applebot group, Applebot follows the Googlebot group.
252
+ robotsGroup = "googlebot";
253
+ agent = "Googlebot";
254
+ }
255
+ else if (groups?.named.has("*")) {
256
+ robotsGroup = "wildcard";
257
+ }
258
+ if (status === "unreachable") {
259
+ return {
260
+ name: bot.name,
261
+ operator: bot.operator,
262
+ purpose: bot.purpose,
263
+ usedFor: bot.usedFor,
264
+ robotsGroup,
265
+ allowed: false,
266
+ blockedPages: urls.length,
267
+ };
268
+ }
269
+ return {
270
+ name: bot.name,
271
+ operator: bot.operator,
272
+ purpose: bot.purpose,
273
+ usedFor: bot.usedFor,
274
+ robotsGroup,
275
+ allowed: allows ? allows(startUrl, agent) : true,
276
+ // A path `User-agent: *` keeps every crawler from is a site-wide choice, which the SEO
277
+ // audit reports; only a page this bot may not fetch while other crawlers may counts here.
278
+ blockedPages: allows
279
+ ? urls.filter((u) => !allows(u, agent) && allows(u, "*")).length
280
+ : 0,
281
+ };
282
+ });
283
+ let trainingPreference = "none";
284
+ // `User-agent: *` never speaks for the policy token, so only a group that names it counts.
285
+ if (allows && groups?.named.has(TRAINING_PREFERENCE_TOKEN)) {
286
+ trainingPreference = allows(startUrl, TRAINING_PREFERENCE_TOKEN)
287
+ ? "allowed"
288
+ : "disallowed";
289
+ }
290
+ return {
291
+ robotsTxt: robots.url,
292
+ status,
293
+ httpStatus: robots.status,
294
+ trainingPreference,
295
+ contentSignals: found ? contentSignals(text) : [],
296
+ bots,
297
+ };
298
+ };
299
+ //# sourceMappingURL=bots.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bots.js","sourceRoot":"","sources":["../../../src/core/aeo/bots.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAuB5C,MAAM,WAAW,GAAG,6CAA6C,CAAC;AAElE,MAAM,cAAc,GAClB,iIAAiI,CAAC;AAEpI,MAAM,eAAe,GAAG,wCAAwC,CAAC;AAEjE,MAAM,oBAAoB,GACxB,sFAAsF,CAAC;AAEzF;;;GAGG;AACH,MAAM,CAAC,MAAM,OAAO,GAAY;IAC9B;QACE,IAAI,EAAE,WAAW;QACjB,QAAQ,EAAE,QAAQ;QAClB,OAAO,EAAE,QAAQ;QACjB,OAAO,EAAE,yCAAyC;QAClD,IAAI,EAAE,kEAAkE;KACzE;IACD;QACE,IAAI,EAAE,SAAS;QACf,QAAQ,EAAE,WAAW;QACrB,OAAO,EAAE,QAAQ;QACjB,OAAO,EAAE,mCAAmC;QAC5C,IAAI,EAAE,oEAAoE;KAC3E;IACD;QACE,IAAI,EAAE,UAAU;QAChB,QAAQ,EAAE,OAAO;QACjB,OAAO,EAAE,QAAQ;QACjB,OAAO,EAAE,mCAAmC;QAC5C,IAAI,EAAE,wCAAwC;KAC/C;IACD;QACE,IAAI,EAAE,eAAe;QACrB,QAAQ,EAAE,QAAQ;QAClB,OAAO,EAAE,QAAQ;QACjB,OAAO,EAAE,wBAAwB;QACjC,IAAI,EAAE,WAAW;QACjB,SAAS,EACP,kHAAkH;KACrH;IACD;QACE,IAAI,EAAE,cAAc;QACpB,QAAQ,EAAE,QAAQ;QAClB,OAAO,EAAE,QAAQ;QACjB,OAAO,EAAE,4DAA4D;QACrE,IAAI,EAAE,WAAW;QACjB,SAAS,EACP,2GAA2G;KAC9G;IACD;QACE,IAAI,EAAE,kBAAkB;QACxB,QAAQ,EAAE,WAAW;QACrB,OAAO,EAAE,QAAQ;QACjB,OAAO,EAAE,yBAAyB;QAClC,IAAI,EAAE,cAAc;QACpB,SAAS,EACP,kHAAkH;KACrH;IACD;QACE,IAAI,EAAE,aAAa;QACnB,QAAQ,EAAE,WAAW;QACrB,OAAO,EAAE,QAAQ;QACjB,OAAO,EAAE,4CAA4C;QACrD,IAAI,EAAE,cAAc;QACpB,SAAS,EACP,6GAA6G;KAChH;IACD;QACE,IAAI,EAAE,eAAe;QACrB,QAAQ,EAAE,YAAY;QACtB,OAAO,EAAE,QAAQ;QACjB,OAAO,EAAE,2BAA2B;QACpC,IAAI,EAAE,eAAe;QACrB,SAAS,EACP,yHAAyH;KAC5H;IACD;QACE,IAAI,EAAE,iBAAiB;QACvB,QAAQ,EAAE,YAAY;QACtB,OAAO,EAAE,QAAQ;QACjB,OAAO,EAAE,qCAAqC;QAC9C,IAAI,EAAE,eAAe;QACrB,SAAS,EACP,6HAA6H;KAChI;IACD;QACE,IAAI,EAAE,iBAAiB;QACvB,QAAQ,EAAE,QAAQ;QAClB,OAAO,EAAE,QAAQ;QACjB,OAAO,EACL,oFAAoF;QACtF,IAAI,EAAE,oBAAoB;KAC3B;IACD;QACE,IAAI,EAAE,eAAe;QACrB,QAAQ,EAAE,YAAY;QACtB,OAAO,EAAE,QAAQ;QACjB,OAAO,EAAE,kCAAkC;QAC3C,IAAI,EAAE,oEAAoE;QAC1E,SAAS,EAAE,gEAAgE;KAC5E;IACD;QACE,IAAI,EAAE,gBAAgB;QACtB,QAAQ,EAAE,YAAY;QACtB,OAAO,EAAE,QAAQ;QACjB,OAAO,EAAE,kCAAkC;QAC3C,IAAI,EAAE,gCAAgC;QACtC,SAAS,EACP,qHAAqH;KACxH;IACD;QACE,IAAI,EAAE,QAAQ;QACd,QAAQ,EAAE,QAAQ;QAClB,OAAO,EAAE,UAAU;QACnB,OAAO,EAAE,0BAA0B;QACnC,IAAI,EAAE,WAAW;QACjB,SAAS,EACP,wGAAwG;KAC3G;IACD;QACE,IAAI,EAAE,WAAW;QACjB,QAAQ,EAAE,WAAW;QACrB,OAAO,EAAE,UAAU;QACnB,OAAO,EAAE,6BAA6B;QACtC,IAAI,EAAE,cAAc;QACpB,SAAS,EACP,yGAAyG;KAC5G;IACD;QACE,IAAI,EAAE,mBAAmB;QACzB,QAAQ,EAAE,OAAO;QACjB,OAAO,EAAE,UAAU;QACnB,OAAO,EAAE,gDAAgD;QACzD,IAAI,EAAE,wCAAwC;KAC/C;IACD;QACE,IAAI,EAAE,OAAO;QACb,QAAQ,EAAE,cAAc;QACxB,OAAO,EAAE,UAAU;QACnB,OAAO,EAAE,oDAAoD;QAC7D,IAAI,EAAE,+BAA+B;QACrC,SAAS,EAAE,0CAA0C;KACtD;IACD;QACE,IAAI,EAAE,oBAAoB;QAC1B,QAAQ,EAAE,MAAM;QAChB,OAAO,EAAE,UAAU;QACnB,OAAO,EAAE,0DAA0D;QACnE,IAAI,EAAE,sEAAsE;QAC5E,SAAS,EACP,2FAA2F;KAC9F;CACF,CAAC;AAEF,8GAA8G;AAC9G,MAAM,CAAC,MAAM,yBAAyB,GAAG,qBAAqB,CAAC;AAE/D,wFAAwF;AACxF,MAAM,aAAa,GAAG,IAAI,GAAG,CAAC,CAAC,QAAQ,EAAE,UAAU,EAAE,UAAU,EAAE,KAAK,CAAC,CAAC,CAAC;AAEzE,kFAAkF;AAClF,MAAM,YAAY,GAAG,CAAC,KAAa,EAAU,EAAE,CAC7C,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,WAAW,EAAE,IAAI,EAAE,CAAC;AASlD,qGAAqG;AACrG,MAAM,UAAU,GAAG,CAAC,IAAY,EAAE,EAAE;IAClC,MAAM,KAAK,GAAG,IAAI,GAAG,EAAU,CAAC;IAChC,MAAM,KAAK,GAAiB,EAAE,CAAC;IAC/B,IAAI,MAAM,GAAa,EAAE,CAAC;IAC1B,IAAI,UAAU,GAAG,KAAK,CAAC;IAEvB,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC,EAAE,CAAC;QAC5C,MAAM,IAAI,GAAG,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QAC9B,MAAM,IAAI,GAAG,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QAC7D,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QAEhC,IAAI,KAAK,KAAK,CAAC,CAAC,EAAE,CAAC;YACjB,SAAS;QACX,CAAC;QAED,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;QACtD,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QAE3C,IAAI,GAAG,KAAK,YAAY,EAAE,CAAC;YACzB,yFAAyF;YACzF,IAAI,CAAC,UAAU,EAAE,CAAC;gBAChB,MAAM,GAAG,EAAE,CAAC;YACd,CAAC;YAED,IAAI,KAAK,EAAE,CAAC;gBACV,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;gBACnB,KAAK,CAAC,GAAG,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC;YACjC,CAAC;YAED,UAAU,GAAG,IAAI,CAAC;YAClB,SAAS;QACX,CAAC;QAED,UAAU,GAAG,KAAK,CAAC;QACnB,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC,CAAC;IACrC,CAAC;IAED,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;AAC1B,CAAC,CAAC;AAEF;;;GAGG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,IAAY,EAAmB,EAAE,CAC9D,UAAU,CAAC,IAAI,CAAC;KACb,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,KAAK,gBAAgB,IAAI,CAAC,CAAC,KAAK,CAAC;KAC1D,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC;KACZ,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;IACT,IAAI,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC;IACnB,IAAI,IAAI,GAAkB,IAAI,CAAC;IAE/B,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QACzB,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACjC,IAAI,GAAG,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;QAClD,IAAI,GAAG,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC;IACnD,CAAC;IAED,MAAM,OAAO,GAA2B,EAAE,CAAC;IAE3C,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;QACnC,MAAM,EAAE,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QAE7B,MAAM,IAAI,GAAG,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;aAChD,IAAI,EAAE;aACN,WAAW,EAAE,CAAC;QAEjB,IAAI,IAAI,EAAE,CAAC;YACT,OAAO,CAAC,IAAI,CAAC;gBACX,EAAE,KAAK,CAAC,CAAC;oBACP,CAAC,CAAC,EAAE;oBACJ,CAAC,CAAC,IAAI;yBACD,KAAK,CAAC,EAAE,GAAG,CAAC,CAAC;yBACb,IAAI,EAAE;yBACN,WAAW,EAAE,CAAC;QACzB,CAAC;IACH,CAAC;IAED,OAAO,EAAE,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;AACjD,CAAC,CAAC,CAAC;AAEP,yGAAyG;AACzG,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,MAAqB,EAAY,EAAE,CAChE,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,EAAE,KAAK,CAAC,EAAE,EAAE;IACvD,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;QAC7B,OAAO,CAAC,GAAG,IAAI,IAAI,KAAK,EAAE,CAAC,CAAC;IAC9B,CAAC;IAED,OAAO,IAAI,KAAK,KAAK,IAAI,KAAK,KAAK,KAAK,IAAI,KAAK,KAAK,IAAI;QACxD,CAAC,CAAC,CAAC,GAAG,IAAI,IAAI,KAAK,EAAE,CAAC;QACtB,CAAC,CAAC,EAAE,CAAC;AACT,CAAC,CAAC,CAAC;AAEL;;;GAGG;AACH,MAAM,YAAY,GAAG,CAAC,MAAqB,EAA6B,EAAE;IACxE,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;QACjB,OAAO,OAAO,CAAC;IACjB,CAAC;IAED,IAAI,MAAM,CAAC,MAAM,KAAK,IAAI,IAAI,MAAM,CAAC,MAAM,IAAI,GAAG,EAAE,CAAC;QACnD,OAAO,aAAa,CAAC;IACvB,CAAC;IAED,OAAO,SAAS,CAAC;AACnB,CAAC,CAAC;AAEF,8GAA8G;AAC9G,MAAM,CAAC,MAAM,aAAa,GAAG,CAC3B,MAAqB,EACrB,IAAmB,EACnB,QAAgB,EAChB,QAAkB,EACD,EAAE;IACnB,MAAM,MAAM,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC;IACpC,MAAM,KAAK,GAAG,MAAM,KAAK,OAAO,IAAI,IAAI,KAAK,IAAI,CAAC;IAClD,MAAM,MAAM,GAAG,KAAK,CAAC,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IAC/C,MAAM,MAAM,GAAG,KAAK,CAAC,CAAC,CAAC,YAAY,CAAC,MAAM,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IAC7D,MAAM,IAAI,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;IAErD,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,GAAG,EAAe,EAAE;QAC5C,MAAM,KAAK,GAAG,YAAY,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACrC,IAAI,WAAW,GAA+B,MAAM,CAAC;QACrD,IAAI,KAAK,GAAG,GAAG,CAAC,IAAI,CAAC;QAErB,IAAI,MAAM,EAAE,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;YAC7B,WAAW,GAAG,OAAO,CAAC;QACxB,CAAC;aAAM,IAAI,KAAK,KAAK,UAAU,IAAI,MAAM,EAAE,KAAK,CAAC,GAAG,CAAC,WAAW,CAAC,EAAE,CAAC;YAClE,0EAA0E;YAC1E,WAAW,GAAG,WAAW,CAAC;YAC1B,KAAK,GAAG,WAAW,CAAC;QACtB,CAAC;aAAM,IAAI,MAAM,EAAE,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;YAClC,WAAW,GAAG,UAAU,CAAC;QAC3B,CAAC;QAED,IAAI,MAAM,KAAK,aAAa,EAAE,CAAC;YAC7B,OAAO;gBACL,IAAI,EAAE,GAAG,CAAC,IAAI;gBACd,QAAQ,EAAE,GAAG,CAAC,QAAQ;gBACtB,OAAO,EAAE,GAAG,CAAC,OAAO;gBACpB,OAAO,EAAE,GAAG,CAAC,OAAO;gBACpB,WAAW;gBACX,OAAO,EAAE,KAAK;gBACd,YAAY,EAAE,IAAI,CAAC,MAAM;aAC1B,CAAC;QACJ,CAAC;QAED,OAAO;YACL,IAAI,EAAE,GAAG,CAAC,IAAI;YACd,QAAQ,EAAE,GAAG,CAAC,QAAQ;YACtB,OAAO,EAAE,GAAG,CAAC,OAAO;YACpB,OAAO,EAAE,GAAG,CAAC,OAAO;YACpB,WAAW;YACX,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI;YAChD,uFAAuF;YACvF,0FAA0F;YAC1F,YAAY,EAAE,MAAM;gBAClB,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,MAAM;gBAChE,CAAC,CAAC,CAAC;SACN,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,IAAI,kBAAkB,GAA0C,MAAM,CAAC;IAEvE,2FAA2F;IAC3F,IAAI,MAAM,IAAI,MAAM,EAAE,KAAK,CAAC,GAAG,CAAC,yBAAyB,CAAC,EAAE,CAAC;QAC3D,kBAAkB,GAAG,MAAM,CAAC,QAAQ,EAAE,yBAAyB,CAAC;YAC9D,CAAC,CAAC,SAAS;YACX,CAAC,CAAC,YAAY,CAAC;IACnB,CAAC;IAED,OAAO;QACL,SAAS,EAAE,MAAM,CAAC,GAAG;QACrB,MAAM;QACN,UAAU,EAAE,MAAM,CAAC,MAAM;QACzB,kBAAkB;QAClB,cAAc,EAAE,KAAK,CAAC,CAAC,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE;QACjD,IAAI;KACL,CAAC;AACJ,CAAC,CAAC"}
@@ -0,0 +1,8 @@
1
+ import type { FetchOptions } from "../fetch.js";
2
+ import type { LlmsTxtInfo } from "../types.js";
3
+ /** Reads an llms.txt file against the format at llmstxt.org: an H1 title is the one required part. */
4
+ export declare const parseLlmsTxt: (url: string, httpStatus: number | null, raw: string) => LlmsTxtInfo;
5
+ /** Fetches /llms.txt from an origin. An HTML answer is a single-page app's fallback route: no file. */
6
+ export declare const loadLlmsTxt: (origin: string, options?: FetchOptions) => Promise<LlmsTxtInfo>;
7
+ /** Reads llms.txt from the root of a build output directory. */
8
+ export declare const readLlmsTxt: (dir: string, baseUrl: string) => Promise<LlmsTxtInfo>;
@@ -0,0 +1,88 @@
1
+ import { promises as fs } from "node:fs";
2
+ import path from "node:path";
3
+ import { fetchPage } from "../fetch.js";
4
+ /** An llms.txt is an index of links; a file this large is not one, and is read no further. */
5
+ const MAX_LLMS_TXT_BYTES = 1_000_000;
6
+ /**
7
+ * `[name](url)`. Each negated class also stops at the bracket that would start the next attempt,
8
+ * so a line of "[[[[" or "](](](" costs one pass. A URL with parentheses in it is not counted.
9
+ */
10
+ const MARKDOWN_LINK = /\[[^[\]\n]*\]\([^()\s]+\)/gu;
11
+ const missing = (url, httpStatus) => ({
12
+ url,
13
+ status: "missing",
14
+ httpStatus,
15
+ title: null,
16
+ summary: null,
17
+ sections: [],
18
+ links: 0,
19
+ bytes: 0,
20
+ problems: [],
21
+ });
22
+ /** Reads an llms.txt file against the format at llmstxt.org: an H1 title is the one required part. */
23
+ export const parseLlmsTxt = (url, httpStatus, raw) => {
24
+ const text = raw.replace(/^\uFEFF/u, "").slice(0, MAX_LLMS_TXT_BYTES);
25
+ const lines = text.split(/\r\n|\r|\n/u).map((l) => l.trim());
26
+ const firstIndex = lines.findIndex(Boolean);
27
+ const first = firstIndex === -1 ? "" : (lines[firstIndex] ?? "");
28
+ const title = /^#\s+\S/u.test(first) ? first.replace(/^#\s+/u, "") : null;
29
+ const afterTitle = lines.slice(firstIndex + 1).find(Boolean) ?? "";
30
+ const problems = [];
31
+ if (!first) {
32
+ problems.push("the file is empty");
33
+ }
34
+ else if (!title) {
35
+ problems.push(`it does not start with an H1 title ("# Site name"); the first line is "${first.slice(0, 80)}"`);
36
+ }
37
+ return {
38
+ url,
39
+ status: problems.length ? "invalid" : "found",
40
+ httpStatus,
41
+ title: title ? title.slice(0, 200) : null,
42
+ summary: title && afterTitle.startsWith(">")
43
+ ? afterTitle.replace(/^>\s*/u, "").slice(0, 300)
44
+ : null,
45
+ sections: lines
46
+ .filter((l) => /^##\s+\S/u.test(l))
47
+ .slice(0, 20)
48
+ .map((l) => l.replace(/^##\s+/u, "").slice(0, 120)),
49
+ links: [...text.matchAll(MARKDOWN_LINK)].length,
50
+ bytes: Buffer.byteLength(text, "utf-8"),
51
+ problems,
52
+ };
53
+ };
54
+ /** Fetches /llms.txt from an origin. An HTML answer is a single-page app's fallback route: no file. */
55
+ export const loadLlmsTxt = async (origin, options = {}) => {
56
+ const url = new URL("/llms.txt", origin).href;
57
+ const res = await fetchPage(url, {
58
+ ...options,
59
+ timeoutMs: Math.min(options.timeoutMs ?? 10_000, 10_000),
60
+ maxBodyBytes: MAX_LLMS_TXT_BYTES,
61
+ });
62
+ const contentType = res.contentType?.toLowerCase() ?? "";
63
+ const body = res.body ?? "";
64
+ if (res.status !== 200 ||
65
+ res.error ||
66
+ contentType.includes("text/html") ||
67
+ /^\s*</u.test(body.slice(0, 200))) {
68
+ return missing(url, res.status);
69
+ }
70
+ return parseLlmsTxt(url, res.status, body);
71
+ };
72
+ /** Reads llms.txt from the root of a build output directory. */
73
+ export const readLlmsTxt = async (dir, baseUrl) => {
74
+ const url = new URL("/llms.txt", baseUrl).href;
75
+ const file = await fs.open(path.join(dir, "llms.txt")).catch(() => null);
76
+ if (!file) {
77
+ return missing(url, 404);
78
+ }
79
+ try {
80
+ const buffer = Buffer.alloc(MAX_LLMS_TXT_BYTES);
81
+ const { bytesRead } = await file.read(buffer, 0, MAX_LLMS_TXT_BYTES, 0);
82
+ return parseLlmsTxt(url, 200, buffer.subarray(0, bytesRead).toString("utf-8"));
83
+ }
84
+ finally {
85
+ await file.close();
86
+ }
87
+ };
88
+ //# sourceMappingURL=llms-txt.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"llms-txt.js","sourceRoot":"","sources":["../../../src/core/aeo/llms-txt.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,53 @@
1
+ import type { JsonObject, JsonValue } from "../html/structured-data.js";
2
+ import type { AeoPageSummary, ExtractedPage, ResponseInfo, StructuredDataItem } from "../types.js";
3
+ export type Engine = "google" | "bing";
4
+ export interface ScopedDirective {
5
+ /** The user agent the directive is for, or null when it is for every crawler. */
6
+ agent: string | null;
7
+ directive: string;
8
+ }
9
+ /**
10
+ * X-Robots-Tag values. "googlebot: nosnippet, noarchive" scopes every directive after the prefix
11
+ * to googlebot, up to the next prefix.
12
+ */
13
+ export declare const parseRobotsHeader: (value: string) => ScopedDirective[];
14
+ /** The robots directives one engine applies to the page, from its meta tags and X-Robots-Tag. */
15
+ export declare const directivesFor: (engine: Engine, page: ExtractedPage, response?: ResponseInfo) => string[];
16
+ /** The `max-snippet` limit, or null when none is set (-1 means no limit). */
17
+ export declare const maxSnippet: (directives: string[]) => number | null;
18
+ export declare const hasNoindex: (directives: string[]) => boolean;
19
+ export declare const hasNosnippet: (directives: string[]) => boolean;
20
+ export interface TypedNode {
21
+ types: string[];
22
+ node: JsonObject;
23
+ /** True for the item itself, false for an object nested in it (publisher, author, mainEntity). */
24
+ topLevel: boolean;
25
+ }
26
+ /** Every typed object in the page's structured data, nested ones included. */
27
+ export declare const typedNodes: (items: StructuredDataItem[]) => TypedNode[];
28
+ export declare const isArticleType: (type: string) => boolean;
29
+ /**
30
+ * Whether a typed object describes an organization or a person. LocalBusiness alone has over a
31
+ * hundred subtypes (Dentist, Bakery, ...), so an object with a name and a logo, address or phone
32
+ * number counts too.
33
+ */
34
+ export declare const isEntity: ({ types, node }: TypedNode) => boolean;
35
+ /** The text of a name-like value: a string, or the `name` of an object, or of the first in a list. */
36
+ export declare const nameOf: (value: JsonValue | undefined) => string | null;
37
+ export declare const articles: (page: ExtractedPage) => TypedNode[];
38
+ /** Lower-cased letters and digits only, so punctuation and spacing differences do not count. */
39
+ export declare const comparable: (value: string) => string;
40
+ /**
41
+ * Structured-data text may hold HTML: tags, and entities such as &amp;. `[^<>]` stops each attempt
42
+ * at the next bracket, so a run of "<" without ">" costs one pass, not one pass per bracket.
43
+ */
44
+ export declare const stripTags: (value: string) => string;
45
+ export interface MarkedUpText {
46
+ type: "Question" | "Answer" | "HowToStep";
47
+ text: string;
48
+ }
49
+ /** The questions, answers and steps FAQPage, QAPage and HowTo markup claims the page shows. */
50
+ export declare const markedUpTexts: (page: ExtractedPage) => MarkedUpText[];
51
+ /** Whether the URL is a site's home page (also /index.html in build output). */
52
+ export declare const isHomePage: (url: string) => boolean;
53
+ export declare const aeoPageSummary: (page: ExtractedPage, response?: ResponseInfo) => AeoPageSummary;