@rivi-software/agentic-seo-check 0.6.0 → 0.7.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 (62) hide show
  1. package/README.md +46 -7
  2. package/dist/brand.d.ts +1 -0
  3. package/dist/brand.js +1 -1
  4. package/dist/brand.js.map +1 -1
  5. package/dist/core/aeo/audit.js +31 -5
  6. package/dist/core/aeo/audit.js.map +1 -1
  7. package/dist/core/aeo/probe.d.ts +2 -0
  8. package/dist/core/aeo/probe.js +7 -2
  9. package/dist/core/aeo/probe.js.map +1 -1
  10. package/dist/core/aeo/site-rules.d.ts +2 -0
  11. package/dist/core/aeo/site-rules.js +6 -0
  12. package/dist/core/aeo/site-rules.js.map +1 -1
  13. package/dist/core/audit.d.ts +4 -0
  14. package/dist/core/audit.js +3 -0
  15. package/dist/core/audit.js.map +1 -1
  16. package/dist/core/crawl.d.ts +11 -2
  17. package/dist/core/crawl.js +176 -90
  18. package/dist/core/crawl.js.map +1 -1
  19. package/dist/core/fetch.d.ts +40 -0
  20. package/dist/core/fetch.js +74 -15
  21. package/dist/core/fetch.js.map +1 -1
  22. package/dist/core/hosted.d.ts +54 -0
  23. package/dist/core/hosted.js +134 -0
  24. package/dist/core/hosted.js.map +1 -0
  25. package/dist/core/refusals.d.ts +27 -0
  26. package/dist/core/refusals.js +56 -0
  27. package/dist/core/refusals.js.map +1 -0
  28. package/dist/core/robots.d.ts +12 -3
  29. package/dist/core/robots.js +20 -15
  30. package/dist/core/robots.js.map +1 -1
  31. package/dist/core/rules/aeo.js +11 -0
  32. package/dist/core/rules/aeo.js.map +1 -1
  33. package/dist/core/rules/response.js +13 -0
  34. package/dist/core/rules/response.js.map +1 -1
  35. package/dist/core/site-probes.d.ts +3 -0
  36. package/dist/core/site-probes.js +10 -2
  37. package/dist/core/site-probes.js.map +1 -1
  38. package/dist/core/site-rules.d.ts +6 -0
  39. package/dist/core/site-rules.js +125 -29
  40. package/dist/core/site-rules.js.map +1 -1
  41. package/dist/core/sitemap.d.ts +22 -3
  42. package/dist/core/sitemap.js +29 -10
  43. package/dist/core/sitemap.js.map +1 -1
  44. package/dist/core/types.d.ts +2 -0
  45. package/dist/core/types.js.map +1 -1
  46. package/dist/index.d.ts +3 -2
  47. package/dist/index.js +1 -1
  48. package/dist/index.js.map +1 -1
  49. package/dist/mcp-hosted.d.ts +47 -0
  50. package/dist/mcp-hosted.js +347 -0
  51. package/dist/mcp-hosted.js.map +1 -0
  52. package/dist/mcp-shared.d.ts +135 -0
  53. package/dist/mcp-shared.js +122 -0
  54. package/dist/mcp-shared.js.map +1 -0
  55. package/dist/mcp.d.ts +10 -1
  56. package/dist/mcp.js +11 -118
  57. package/dist/mcp.js.map +1 -1
  58. package/dist/version.d.ts +8 -0
  59. package/dist/version.js +6 -3
  60. package/dist/version.js.map +1 -1
  61. package/package.json +10 -3
  62. package/skills/seo-audit/SKILL.md +2 -0
@@ -13,6 +13,26 @@ const KEPT_HEADERS = [
13
13
  "vary",
14
14
  "content-security-policy",
15
15
  ];
16
+ /**
17
+ * A request the gate refused, thrown out of `fetchPage` so that no caller takes it for an answer:
18
+ * a link that was not requested is not checked, never broken.
19
+ */
20
+ export class RequestRefusedError extends Error {
21
+ scope;
22
+ /** The request that was refused: the URL asked for, or a redirect it led to. */
23
+ url;
24
+ constructor(refusal, url) {
25
+ super(refusal.message);
26
+ this.name = "RequestRefusedError";
27
+ this.scope = refusal.scope;
28
+ this.url = url;
29
+ }
30
+ get host() {
31
+ return new URL(this.url).hostname;
32
+ }
33
+ }
34
+ /** The most of a body an audit reads unless `maxBodyBytes` says otherwise. */
35
+ export const MAX_BODY_BYTES = 5_000_000;
16
36
  const REDIRECT_STATUSES = new Set([301, 302, 303, 307, 308]);
17
37
  const pickHeaders = (headers) => {
18
38
  const out = {};
@@ -55,8 +75,10 @@ const readLimited = async (res, maxBytes) => {
55
75
  break;
56
76
  }
57
77
  if (value) {
58
- chunks.push(value);
59
- total += value.byteLength;
78
+ // A chunk can be large: keep only what fits, so the cap holds to the byte.
79
+ const room = maxBytes - total;
80
+ chunks.push(value.byteLength > room ? value.subarray(0, room) : value);
81
+ total += Math.min(value.byteLength, room);
60
82
  if (total >= maxBytes) {
61
83
  truncated = true;
62
84
  break;
@@ -79,8 +101,49 @@ const decodeBody = (bytes, contentType) => {
79
101
  return new TextDecoder("utf-8").decode(bytes);
80
102
  }
81
103
  };
104
+ /** Asks the gate before a request, and throws its refusal. */
105
+ const askGate = async (gate, current) => {
106
+ // A URL that does not parse fails in fetch(), with an answer that says so.
107
+ if (!gate || !URL.canParse(current)) {
108
+ return;
109
+ }
110
+ const refusal = await gate.beforeRequest(new URL(current));
111
+ if (refusal) {
112
+ throw new RequestRefusedError(refusal, current);
113
+ }
114
+ };
115
+ /** Shows the gate an answer, and throws its refusal once the body is let go. */
116
+ const showGate = async (gate, current, res) => {
117
+ const refusal = gate?.afterResponse?.(new URL(current), res);
118
+ if (refusal) {
119
+ await res.body?.cancel().catch(() => null);
120
+ throw new RequestRefusedError(refusal, current);
121
+ }
122
+ };
123
+ /** The answer with its body read, up to `maxBodyBytes`. */
124
+ const readAnswer = async (walk, current, res, headers) => {
125
+ const { url, options, started, chain } = walk;
126
+ const read = await readLimited(res, options.maxBodyBytes ?? MAX_BODY_BYTES);
127
+ const done = result(url, current, res.status, headers,
128
+ // The caller reads `bytes`; decoding them too would hold the body a second time.
129
+ options.keepBytes
130
+ ? ""
131
+ : decodeBody(read.bytes, res.headers.get("content-type")), chain, started);
132
+ if (read.truncated) {
133
+ done.truncated = true;
134
+ }
135
+ if (options.keepBytes) {
136
+ done.bytes = read.bytes;
137
+ }
138
+ const refusal = options.gate?.afterBody?.(new URL(current), done);
139
+ if (refusal) {
140
+ throw new RequestRefusedError(refusal, current);
141
+ }
142
+ return done;
143
+ };
82
144
  const fetchHop = async (walk, current) => {
83
145
  const { url, options, readBody, timeoutMs, started, chain } = walk;
146
+ await askGate(options.gate, current);
84
147
  const controller = new AbortController();
85
148
  const timer = setTimeout(() => controller.abort(), timeoutMs);
86
149
  try {
@@ -95,6 +158,7 @@ const fetchHop = async (walk, current) => {
95
158
  ...options.headers,
96
159
  },
97
160
  });
161
+ await showGate(options.gate, current, res);
98
162
  const headers = pickHeaders(res.headers);
99
163
  if (REDIRECT_STATUSES.has(res.status) && headers["location"]) {
100
164
  chain.push({ url: current, status: res.status });
@@ -121,21 +185,13 @@ const fetchHop = async (walk, current) => {
121
185
  done: result(url, current, res.status, headers, null, chain, started),
122
186
  };
123
187
  }
124
- const read = await readLimited(res, options.maxBodyBytes ?? 5_000_000);
125
- const done = result(url, current, res.status, headers,
126
- // The caller reads `bytes`; decoding them too would hold the body a second time.
127
- options.keepBytes
128
- ? ""
129
- : decodeBody(read.bytes, res.headers.get("content-type")), chain, started);
130
- if (read.truncated) {
131
- done.truncated = true;
132
- }
133
- if (options.keepBytes) {
134
- done.bytes = read.bytes;
135
- }
136
- return { done };
188
+ return { done: await readAnswer(walk, current, res, headers) };
137
189
  }
138
190
  catch (error) {
191
+ // A refusal is no failure of the request: it goes to the caller as it is.
192
+ if (error instanceof RequestRefusedError) {
193
+ throw error;
194
+ }
139
195
  let message = String(error);
140
196
  if (error instanceof Error) {
141
197
  message =
@@ -187,6 +243,9 @@ export const toResponseInfo = (r) => {
187
243
  if (r.error) {
188
244
  info.error = r.error;
189
245
  }
246
+ if (r.truncated) {
247
+ info.truncated = true;
248
+ }
190
249
  return info;
191
250
  };
192
251
  export const isHtmlResponse = (r) => {
@@ -1 +1 @@
1
- {"version":3,"file":"fetch.js","sourceRoot":"","sources":["../../src/core/fetch.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"fetch.js","sourceRoot":"","sources":["../../src/core/fetch.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,54 @@
1
+ import type { Refusal, RequestGate } from "./fetch.js";
2
+ /** The local server, which none of these limits apply to: the way out of every refusal. */
3
+ export declare const LOCAL_SERVER = "npx -y @rivi-software/agentic-seo-check mcp";
4
+ /**
5
+ * What the hosted server reads and requests at most. A Worker isolate has 128 MB of memory: one
6
+ * 2 MB page takes about 80 MB of it to audit, a 5 MB sitemap about 64 MB to parse.
7
+ */
8
+ export declare const HOSTED_LIMITS: {
9
+ /** Bytes of a page (5 MB on the local server). Above document.html-too-large's 1.5 MB. */
10
+ readonly maxBodyBytes: 2000000;
11
+ /** Characters of inline `html`. */
12
+ readonly maxHtmlChars: 2000000;
13
+ /**
14
+ * Bytes of a request to the server, checked before it is parsed. 2,000,000 characters of HTML
15
+ * are up to 6 MB in UTF-8, and JSON escaping adds to that.
16
+ */
17
+ readonly maxRequestBytes: 8000000;
18
+ readonly maxPages: 25;
19
+ readonly maxDepth: 3;
20
+ /** Requests at a time; a Worker has at most 6 connections waiting for headers. */
21
+ readonly concurrency: 4;
22
+ /** Internal links, sitemap URLs and images a crawl checks, and as many external links. */
23
+ readonly maxLinkChecks: 50;
24
+ readonly timeoutMs: 15000;
25
+ readonly maxSitemaps: 10;
26
+ readonly maxSitemapBytes: 5000000;
27
+ /** `includePatterns` and `excludePatterns` are the caller's regular expressions, run here. */
28
+ readonly maxPatterns: 20;
29
+ readonly maxPatternChars: 200;
30
+ /** `rules` and `disableRules`. */
31
+ readonly maxRuleIds: 500;
32
+ readonly maxRuleIdChars: 100;
33
+ /** `baseline`: the finding ids of an earlier audit. */
34
+ readonly maxBaselineIds: 5000;
35
+ readonly maxBaselineIdChars: 300;
36
+ readonly maxKeywordChars: 200;
37
+ };
38
+ /**
39
+ * Why the hosted server will not request `url`, or undefined when it may: it requests http and
40
+ * https URLs on public hostnames only. A Worker reaches no private network anyway; the rules give
41
+ * a clear answer instead of a failed request.
42
+ */
43
+ export declare const hostedUrlProblem: (url: URL) => string | undefined;
44
+ /**
45
+ * Why an answer is the site refusing the hosted server rather than its page: a challenge, a rate
46
+ * limit or a 403. A 429 or a challenge holds for every later request to the host; a 403 for the
47
+ * URL alone, since a site may forbid one path to everyone. Undefined for a page.
48
+ */
49
+ export declare const siteRefusal: (url: URL, response: Pick<Response, "status" | "headers">) => Refusal | undefined;
50
+ /**
51
+ * The gate of a hosted tool call: the URL rules and the site refusals, then the Worker's own gate
52
+ * (its request limits), which only sees requests the rules allow.
53
+ */
54
+ export declare const hostedGate: (limits?: RequestGate) => RequestGate;
@@ -0,0 +1,134 @@
1
+ /**
2
+ * The hosted server's rules (hosted/, issue #35): what it reads at most, which URLs it requests,
3
+ * and which answers are a site turning it away rather than the site's page. The Worker adds its
4
+ * limits on top, through the gate it passes to `hostedGate`.
5
+ */
6
+ import { challengeTitle } from "./aeo/probe.js";
7
+ /** The local server, which none of these limits apply to: the way out of every refusal. */
8
+ export const LOCAL_SERVER = "npx -y @rivi-software/agentic-seo-check mcp";
9
+ const USE_LOCAL = `Use the local server for it, which sends the requests from your machine: ${LOCAL_SERVER}`;
10
+ /**
11
+ * What the hosted server reads and requests at most. A Worker isolate has 128 MB of memory: one
12
+ * 2 MB page takes about 80 MB of it to audit, a 5 MB sitemap about 64 MB to parse.
13
+ */
14
+ export const HOSTED_LIMITS = {
15
+ /** Bytes of a page (5 MB on the local server). Above document.html-too-large's 1.5 MB. */
16
+ maxBodyBytes: 2_000_000,
17
+ /** Characters of inline `html`. */
18
+ maxHtmlChars: 2_000_000,
19
+ /**
20
+ * Bytes of a request to the server, checked before it is parsed. 2,000,000 characters of HTML
21
+ * are up to 6 MB in UTF-8, and JSON escaping adds to that.
22
+ */
23
+ maxRequestBytes: 8_000_000,
24
+ maxPages: 25,
25
+ maxDepth: 3,
26
+ /** Requests at a time; a Worker has at most 6 connections waiting for headers. */
27
+ concurrency: 4,
28
+ /** Internal links, sitemap URLs and images a crawl checks, and as many external links. */
29
+ maxLinkChecks: 50,
30
+ timeoutMs: 15_000,
31
+ maxSitemaps: 10,
32
+ maxSitemapBytes: 5_000_000,
33
+ /** `includePatterns` and `excludePatterns` are the caller's regular expressions, run here. */
34
+ maxPatterns: 20,
35
+ maxPatternChars: 200,
36
+ /** `rules` and `disableRules`. */
37
+ maxRuleIds: 500,
38
+ maxRuleIdChars: 100,
39
+ /** `baseline`: the finding ids of an earlier audit. */
40
+ maxBaselineIds: 5000,
41
+ maxBaselineIdChars: 300,
42
+ maxKeywordChars: 200,
43
+ };
44
+ /** Names that only resolve on a local machine or network, never on the public internet. */
45
+ const LOCAL_SUFFIXES = [".localhost", ".local", ".internal"];
46
+ /** An IPv4 address as the URL parser writes it, which it does for every numeric form. */
47
+ const IPV4 = /^\d{1,3}(?:\.\d{1,3}){3}$/u;
48
+ /**
49
+ * Why the hosted server will not request `url`, or undefined when it may: it requests http and
50
+ * https URLs on public hostnames only. A Worker reaches no private network anyway; the rules give
51
+ * a clear answer instead of a failed request.
52
+ */
53
+ export const hostedUrlProblem = (url) => {
54
+ if (url.protocol !== "http:" && url.protocol !== "https:") {
55
+ return `The hosted server requests http and https URLs only, not ${url.protocol} ones (${url.href}).`;
56
+ }
57
+ if (url.username || url.password) {
58
+ return `The hosted server does not send credentials in a URL (${url.host}). A site behind a login is one for the local server, which can send your headers: ${LOCAL_SERVER}`;
59
+ }
60
+ const host = url.hostname.replace(/\.$/u, "");
61
+ if (IPV4.test(host) || host.startsWith("[")) {
62
+ return `The hosted server requests hostnames only, not IP addresses (${host}). ${USE_LOCAL}`;
63
+ }
64
+ if (host === "localhost" ||
65
+ !host.includes(".") ||
66
+ LOCAL_SUFFIXES.some((suffix) => host.endsWith(suffix))) {
67
+ return `The hosted server cannot reach ${host}: it runs on Cloudflare's network, not on your machine or your network. ${USE_LOCAL}`;
68
+ }
69
+ return undefined;
70
+ };
71
+ const REFUSED_BY_SITE = `Many sites turn away requests from Cloudflare Workers, which the hosted server runs on, while they serve the page to everyone else. ${USE_LOCAL}`;
72
+ /**
73
+ * Why an answer is the site refusing the hosted server rather than its page: a challenge, a rate
74
+ * limit or a 403. A 429 or a challenge holds for every later request to the host; a 403 for the
75
+ * URL alone, since a site may forbid one path to everyone. Undefined for a page.
76
+ */
77
+ export const siteRefusal = (url, response) => {
78
+ // Cloudflare marks every challenge page so (developers.cloudflare.com, "Detect a challenge page").
79
+ if (response.headers.get("cf-mitigated")?.toLowerCase() === "challenge") {
80
+ return {
81
+ scope: "host",
82
+ message: `${url.hostname} answered the hosted server with a challenge page (cf-mitigated: challenge). ${REFUSED_BY_SITE}`,
83
+ };
84
+ }
85
+ if (response.status === 429) {
86
+ return {
87
+ scope: "host",
88
+ message: `${url.hostname} answered the hosted server with HTTP 429 (Too Many Requests). A site that counts requests per address counts every Cloudflare Worker as one. Try again later, or use the local server, which sends the requests from your machine: ${LOCAL_SERVER}`,
89
+ };
90
+ }
91
+ if (response.status === 403) {
92
+ return {
93
+ scope: "url",
94
+ message: `${url.href} answered the hosted server with HTTP 403 (Forbidden). ${REFUSED_BY_SITE}`,
95
+ };
96
+ }
97
+ return undefined;
98
+ };
99
+ /**
100
+ * The statuses a challenge page comes with besides 403 and 429, which `siteRefusal` answers from
101
+ * the status alone: 503 (an "under attack" check) and 405 (AWS WAF's CAPTCHA). A 401 or 404 page
102
+ * titled "Access denied" is the site's own.
103
+ */
104
+ const CHALLENGE_STATUSES = new Set([405, 503]);
105
+ /** An error page that is a CDN's or bot protection's interstitial rather than the site's own. */
106
+ const challengeRefusal = (url, result) => {
107
+ if (result.status === null ||
108
+ !CHALLENGE_STATUSES.has(result.status) ||
109
+ !result.body) {
110
+ return undefined;
111
+ }
112
+ const title = challengeTitle(result.body.slice(0, 10_000));
113
+ return title === null
114
+ ? undefined
115
+ : {
116
+ scope: "host",
117
+ message: `${url.hostname} answered the hosted server with a challenge page ("${title}", HTTP ${result.status}). ${REFUSED_BY_SITE}`,
118
+ };
119
+ };
120
+ /**
121
+ * The gate of a hosted tool call: the URL rules and the site refusals, then the Worker's own gate
122
+ * (its request limits), which only sees requests the rules allow.
123
+ */
124
+ export const hostedGate = (limits) => ({
125
+ beforeRequest: (url) => {
126
+ const problem = hostedUrlProblem(url);
127
+ return problem
128
+ ? { scope: "url", message: problem }
129
+ : limits?.beforeRequest(url);
130
+ },
131
+ afterResponse: (url, response) => siteRefusal(url, response) ?? limits?.afterResponse?.(url, response),
132
+ afterBody: (url, result) => challengeRefusal(url, result) ?? limits?.afterBody?.(url, result),
133
+ });
134
+ //# sourceMappingURL=hosted.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hosted.js","sourceRoot":"","sources":["../../src/core/hosted.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,27 @@
1
+ import { RequestRefusedError } from "./fetch.js";
2
+ import type { FetchOptions } from "./fetch.js";
3
+ /**
4
+ * The requests of one audit that a gate refused (the hosted server's), and what each rules out:
5
+ * the URL alone, its host for the rest of the audit, or every later request. A request that was
6
+ * not sent says nothing about its URL, so callers leave it unchecked, never broken.
7
+ */
8
+ export interface Refusals {
9
+ /**
10
+ * Runs `request`, which fetches `url`, unless a refusal already rules it out. Null when it is
11
+ * refused, now or before; any other error goes to the caller.
12
+ */
13
+ run: <T>(url: string, request: () => Promise<T>) => Promise<T | null>;
14
+ /** The refusal that rules out `url`, if one does. */
15
+ of: (url: string) => RequestRefusedError | undefined;
16
+ /**
17
+ * The refusal that ended the requests to a host `isSite` accepts, or to every host: the reason a
18
+ * crawl of that site stopped early.
19
+ */
20
+ stopped: (isSite: (host: string) => boolean) => RequestRefusedError | undefined;
21
+ /**
22
+ * `options` with a gate that also refuses the hosts already refused, so a redirect to one of them
23
+ * is not sent again. Without a gate nothing is ever refused, and `options` stays as it is.
24
+ */
25
+ guard: (options: FetchOptions) => FetchOptions;
26
+ }
27
+ export declare const trackRefusals: () => Refusals;
@@ -0,0 +1,56 @@
1
+ import { RequestRefusedError } from "./fetch.js";
2
+ import { hostOf } from "./url.js";
3
+ export const trackRefusals = () => {
4
+ const urls = new Map();
5
+ const hosts = new Map();
6
+ let all;
7
+ const of = (url) => all ?? urls.get(url) ?? hosts.get(hostOf(url) ?? "");
8
+ const record = (url, error) => {
9
+ // The URL asked for, refused itself or through a redirect it led to.
10
+ urls.set(url, error);
11
+ if (error.scope === "all") {
12
+ all ??= error;
13
+ }
14
+ else if (error.scope === "host" && !hosts.has(error.host)) {
15
+ hosts.set(error.host, error);
16
+ }
17
+ };
18
+ return {
19
+ of,
20
+ stopped: (isSite) => all ?? [...hosts].find(([host]) => isSite(host))?.[1],
21
+ guard: (options) => {
22
+ const { gate } = options;
23
+ if (!gate) {
24
+ return options;
25
+ }
26
+ return {
27
+ ...options,
28
+ gate: {
29
+ ...gate,
30
+ beforeRequest: (url) => {
31
+ const known = all ?? hosts.get(url.hostname);
32
+ return known
33
+ ? { scope: known.scope, message: known.message }
34
+ : gate.beforeRequest(url);
35
+ },
36
+ },
37
+ };
38
+ },
39
+ run: async (url, request) => {
40
+ if (of(url)) {
41
+ return null;
42
+ }
43
+ try {
44
+ return await request();
45
+ }
46
+ catch (error) {
47
+ if (!(error instanceof RequestRefusedError)) {
48
+ throw error;
49
+ }
50
+ record(url, error);
51
+ return null;
52
+ }
53
+ },
54
+ };
55
+ };
56
+ //# sourceMappingURL=refusals.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"refusals.js","sourceRoot":"","sources":["../../src/core/refusals.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAEjD,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AA6BlC,MAAM,CAAC,MAAM,aAAa,GAAG,GAAa,EAAE;IAC1C,MAAM,IAAI,GAAG,IAAI,GAAG,EAA+B,CAAC;IACpD,MAAM,KAAK,GAAG,IAAI,GAAG,EAA+B,CAAC;IACrD,IAAI,GAAoC,CAAC;IAEzC,MAAM,EAAE,GAAG,CAAC,GAAW,EAAmC,EAAE,CAC1D,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC;IAEvD,MAAM,MAAM,GAAG,CAAC,GAAW,EAAE,KAA0B,EAAQ,EAAE;QAC/D,qEAAqE;QACrE,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;QAErB,IAAI,KAAK,CAAC,KAAK,KAAK,KAAK,EAAE,CAAC;YAC1B,GAAG,KAAK,KAAK,CAAC;QAChB,CAAC;aAAM,IAAI,KAAK,CAAC,KAAK,KAAK,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;YAC5D,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;QAC/B,CAAC;IACH,CAAC,CAAC;IAEF,OAAO;QACL,EAAE;QACF,OAAO,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,GAAG,IAAI,CAAC,GAAG,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QAC1E,KAAK,EAAE,CAAC,OAAO,EAAE,EAAE;YACjB,MAAM,EAAE,IAAI,EAAE,GAAG,OAAO,CAAC;YAEzB,IAAI,CAAC,IAAI,EAAE,CAAC;gBACV,OAAO,OAAO,CAAC;YACjB,CAAC;YAED,OAAO;gBACL,GAAG,OAAO;gBACV,IAAI,EAAE;oBACJ,GAAG,IAAI;oBACP,aAAa,EAAE,CAAC,GAAG,EAAE,EAAE;wBACrB,MAAM,KAAK,GAAG,GAAG,IAAI,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;wBAE7C,OAAO,KAAK;4BACV,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE;4BAChD,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC;oBAC9B,CAAC;iBACF;aACF,CAAC;QACJ,CAAC;QACD,GAAG,EAAE,KAAK,EAAE,GAAG,EAAE,OAAO,EAAE,EAAE;YAC1B,IAAI,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC;gBACZ,OAAO,IAAI,CAAC;YACd,CAAC;YAED,IAAI,CAAC;gBACH,OAAO,MAAM,OAAO,EAAE,CAAC;YACzB,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,IAAI,CAAC,CAAC,KAAK,YAAY,mBAAmB,CAAC,EAAE,CAAC;oBAC5C,MAAM,KAAK,CAAC;gBACd,CAAC;gBAED,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;gBAEnB,OAAO,IAAI,CAAC;YACd,CAAC;QACH,CAAC;KACF,CAAC;AACJ,CAAC,CAAC"}
@@ -2,11 +2,22 @@ import type { FetchOptions } from "./fetch.js";
2
2
  import type { RobotsTxtInfo } from "./types.js";
3
3
  export interface RobotsResult {
4
4
  info: RobotsTxtInfo;
5
- /** Returns true when crawling the URL is allowed (unknown/missing robots.txt allows everything). */
5
+ /**
6
+ * Returns true when robots.txt allows the URL for the audit's User-Agent as robots-parser reads
7
+ * it (unknown/missing robots.txt allows everything). The site rules judge blocked pages by this.
8
+ */
6
9
  allows: (url: string) => boolean;
10
+ /**
11
+ * Returns true when this crawler may request the URL: the group for its product token
12
+ * (`agentic-seo-check`), or `*` when there is none (RFC 9309, section 2.2.1). A site can keep
13
+ * the audit out without blocking anyone else.
14
+ */
15
+ crawlerAllows: (url: string) => boolean;
7
16
  /** The robots.txt text up to the size crawlers must read (RFC 9309: 500 KiB), or null when there is none. */
8
17
  text: string | null;
9
18
  }
19
+ /** Short bot token used for robots.txt matching, derived from the full UA string. */
20
+ export declare const robotsUserAgentToken: (userAgent: string) => string;
10
21
  /**
11
22
  * Whether robots.txt at `url` lets the crawler with product token `agent` fetch `target`. A URL
12
23
  * on another origin is allowed: this robots.txt has no say over it.
@@ -19,5 +30,3 @@ export declare const agentMatcher: (url: string, text: string) => ((target: stri
19
30
  export declare const ruleLineMatcher: (url: string, text: string) => ((target: string, agent: string) => number);
20
31
  export declare const buildRobots: (url: string, status: number | null, error: string | undefined, text: string | null, userAgent: string) => RobotsResult;
21
32
  export declare const loadRobotsTxt: (origin: string, userAgent: string, options?: FetchOptions) => Promise<RobotsResult>;
22
- /** Short bot token used for robots.txt matching, derived from the full UA string. */
23
- export declare const robotsUserAgentToken: (userAgent: string) => string;
@@ -1,11 +1,20 @@
1
- import { createRequire } from "node:module";
1
+ import robotsParserModule from "robots-parser";
2
2
  import { fetchPage } from "./fetch.js";
3
- // robots-parser is CommonJS; require() avoids ESM default-export interop mismatches.
4
- // SAFETY: require() returns the package's module.exports, which is the parser function that its
5
- // index.d.ts declares as the default export.
6
- const robotsParser = createRequire(import.meta.url)("robots-parser");
3
+ /**
4
+ * robots-parser is CommonJS, imported statically so a bundler (the hosted server's) takes it in.
5
+ * Node and bundlers hand a default import its module.exports, the parser function itself; its
6
+ * index.d.ts declares the parser as the module's `default`, so TypeScript's Node resolution types
7
+ * the import as the module and its bundler resolution as the parser. Either way, this is the parser.
8
+ */
9
+ const parserOf = (imported) => ("default" in imported ? imported.default : imported);
10
+ const robotsParser = parserOf(robotsParserModule);
7
11
  /** The part of a robots.txt file a crawler must read at least (RFC 9309 section 2.5). */
8
12
  const MAX_ROBOTS_BYTES = 512_000;
13
+ /** Short bot token used for robots.txt matching, derived from the full UA string. */
14
+ export const robotsUserAgentToken = (userAgent) => {
15
+ const match = /agentic-seo-check|Googlebot|bingbot/iu.exec(userAgent);
16
+ return match ? match[0] : userAgent;
17
+ };
9
18
  /**
10
19
  * Whether robots.txt at `url` lets the crawler with product token `agent` fetch `target`. A URL
11
20
  * on another origin is allowed: this robots.txt has no say over it.
@@ -35,17 +44,18 @@ export const buildRobots = (url, status, error, text, userAgent) => {
35
44
  info.error = error;
36
45
  }
37
46
  if (text === null) {
38
- return { info, allows: () => true, text: null };
47
+ return { info, allows: () => true, crawlerAllows: () => true, text: null };
39
48
  }
40
49
  const robot = robotsParser(url, text);
41
50
  info.sitemaps = robot.getSitemaps();
51
+ const token = robotsUserAgentToken(userAgent);
42
52
  return {
43
53
  info,
44
54
  text: text.slice(0, MAX_ROBOTS_BYTES),
45
- allows: (target) => {
46
- const allowed = robot.isAllowed(target, userAgent);
47
- return allowed === undefined ? true : allowed;
48
- },
55
+ allows: (target) => robot.isAllowed(target, userAgent) ?? true,
56
+ // robots-parser keeps what comes before the first "/" of a User-Agent ("Mozilla" for the
57
+ // audit's own), so the product token is passed on its own.
58
+ crawlerAllows: (target) => robot.isAllowed(target, token) ?? true,
49
59
  };
50
60
  };
51
61
  export const loadRobotsTxt = async (origin, userAgent, options = {}) => {
@@ -60,9 +70,4 @@ export const loadRobotsTxt = async (origin, userAgent, options = {}) => {
60
70
  !/<html/iu.test(res.body.slice(0, 500));
61
71
  return buildRobots(url, res.status, res.error, found ? res.body : null, userAgent);
62
72
  };
63
- /** Short bot token used for robots.txt matching, derived from the full UA string. */
64
- export const robotsUserAgentToken = (userAgent) => {
65
- const match = /agentic-seo-check|Googlebot|bingbot/iu.exec(userAgent);
66
- return match ? match[0] : userAgent;
67
- };
68
73
  //# sourceMappingURL=robots.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"robots.js","sourceRoot":"","sources":["../../src/core/robots.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"robots.js","sourceRoot":"","sources":["../../src/core/robots.ts"],"names":[],"mappings":"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"}
@@ -275,6 +275,17 @@ export const aeoRules = [
275
275
  })
276
276
  : null;
277
277
  }),
278
+ defineRule({
279
+ id: "aeo.page-truncated",
280
+ category: "aeo",
281
+ severity: "info",
282
+ description: "The page is larger than the audit reads (5 MB; 2 MB on the hosted server), so only its first part was audited.",
283
+ recommendation: "Findings about the rest of the page are missing. HTML this large is slow for crawlers too: move inline scripts, data and SVG into files the page loads. On the hosted server, the local server reads up to 5 MB.",
284
+ }, ({ page, response }, make) => response?.truncated
285
+ ? make(`The audit read the first ${(page.htmlBytes / 1_000_000).toFixed(1)} MB of the page and stopped there.`,
286
+ // About the audit, not the page.
287
+ { fix: [], evidence: { bytesRead: page.htmlBytes } })
288
+ : null),
278
289
  defineRule({
279
290
  id: "aeo.noindex",
280
291
  category: "aeo",