@automatelab/ai-seo-mcp 0.1.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.
- package/LICENSE +21 -0
- package/README.md +217 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +145 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/config.d.ts +8 -0
- package/dist/lib/config.d.ts.map +1 -0
- package/dist/lib/config.js +11 -0
- package/dist/lib/config.js.map +1 -0
- package/dist/lib/crawlers.json +6 -0
- package/dist/lib/entities.d.ts +17 -0
- package/dist/lib/entities.d.ts.map +1 -0
- package/dist/lib/entities.js +113 -0
- package/dist/lib/entities.js.map +1 -0
- package/dist/lib/fetch.d.ts +33 -0
- package/dist/lib/fetch.d.ts.map +1 -0
- package/dist/lib/fetch.js +190 -0
- package/dist/lib/fetch.js.map +1 -0
- package/dist/lib/html.d.ts +45 -0
- package/dist/lib/html.d.ts.map +1 -0
- package/dist/lib/html.js +166 -0
- package/dist/lib/html.js.map +1 -0
- package/dist/lib/llms-txt.d.ts +25 -0
- package/dist/lib/llms-txt.d.ts.map +1 -0
- package/dist/lib/llms-txt.js +149 -0
- package/dist/lib/llms-txt.js.map +1 -0
- package/dist/lib/robots.d.ts +26 -0
- package/dist/lib/robots.d.ts.map +1 -0
- package/dist/lib/robots.js +83 -0
- package/dist/lib/robots.js.map +1 -0
- package/dist/lib/schema-types.json +56 -0
- package/dist/lib/schema.d.ts +20 -0
- package/dist/lib/schema.d.ts.map +1 -0
- package/dist/lib/schema.js +311 -0
- package/dist/lib/schema.js.map +1 -0
- package/dist/lib/score.d.ts +32 -0
- package/dist/lib/score.d.ts.map +1 -0
- package/dist/lib/score.js +62 -0
- package/dist/lib/score.js.map +1 -0
- package/dist/tools/audit-canonical.d.ts +26 -0
- package/dist/tools/audit-canonical.d.ts.map +1 -0
- package/dist/tools/audit-canonical.js +122 -0
- package/dist/tools/audit-canonical.js.map +1 -0
- package/dist/tools/audit-page.d.ts +31 -0
- package/dist/tools/audit-page.d.ts.map +1 -0
- package/dist/tools/audit-page.js +221 -0
- package/dist/tools/audit-page.js.map +1 -0
- package/dist/tools/audit-schema.d.ts +34 -0
- package/dist/tools/audit-schema.d.ts.map +1 -0
- package/dist/tools/audit-schema.js +65 -0
- package/dist/tools/audit-schema.js.map +1 -0
- package/dist/tools/check-robots.d.ts +22 -0
- package/dist/tools/check-robots.d.ts.map +1 -0
- package/dist/tools/check-robots.js +142 -0
- package/dist/tools/check-robots.js.map +1 -0
- package/dist/tools/check-sitemap.d.ts +27 -0
- package/dist/tools/check-sitemap.d.ts.map +1 -0
- package/dist/tools/check-sitemap.js +214 -0
- package/dist/tools/check-sitemap.js.map +1 -0
- package/dist/tools/check-technical.d.ts +50 -0
- package/dist/tools/check-technical.d.ts.map +1 -0
- package/dist/tools/check-technical.js +209 -0
- package/dist/tools/check-technical.js.map +1 -0
- package/dist/tools/extract-entities.d.ts +35 -0
- package/dist/tools/extract-entities.d.ts.map +1 -0
- package/dist/tools/extract-entities.js +69 -0
- package/dist/tools/extract-entities.js.map +1 -0
- package/dist/tools/generate-llms-txt.d.ts +32 -0
- package/dist/tools/generate-llms-txt.d.ts.map +1 -0
- package/dist/tools/generate-llms-txt.js +157 -0
- package/dist/tools/generate-llms-txt.js.map +1 -0
- package/dist/tools/rewrite-for-aeo.d.ts +50 -0
- package/dist/tools/rewrite-for-aeo.d.ts.map +1 -0
- package/dist/tools/rewrite-for-aeo.js +119 -0
- package/dist/tools/rewrite-for-aeo.js.map +1 -0
- package/dist/tools/rewrite-for-geo.d.ts +51 -0
- package/dist/tools/rewrite-for-geo.d.ts.map +1 -0
- package/dist/tools/rewrite-for-geo.js +115 -0
- package/dist/tools/rewrite-for-geo.js.map +1 -0
- package/dist/tools/score-ai-overview-eligibility.d.ts +34 -0
- package/dist/tools/score-ai-overview-eligibility.d.ts.map +1 -0
- package/dist/tools/score-ai-overview-eligibility.js +186 -0
- package/dist/tools/score-ai-overview-eligibility.js.map +1 -0
- package/dist/tools/score-citation-worthiness.d.ts +51 -0
- package/dist/tools/score-citation-worthiness.d.ts.map +1 -0
- package/dist/tools/score-citation-worthiness.js +145 -0
- package/dist/tools/score-citation-worthiness.js.map +1 -0
- package/dist/tools/validate-llms-txt.d.ts +33 -0
- package/dist/tools/validate-llms-txt.d.ts.map +1 -0
- package/dist/tools/validate-llms-txt.js +62 -0
- package/dist/tools/validate-llms-txt.js.map +1 -0
- package/dist/types.d.ts +48 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/package.json +60 -0
- package/src/lib/crawlers.json +6 -0
- package/src/lib/schema-types.json +56 -0
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
// Polite fetch wrapper. Every HTTP request in the server goes through here.
|
|
2
|
+
// Enforces: User-Agent, timeout, byte cap, inter-request delay, robots.txt compliance.
|
|
3
|
+
import { fetch as undiciFetch } from "undici";
|
|
4
|
+
import { POLITE_FETCH } from "./config.js";
|
|
5
|
+
import { checkRobotsAllowed, fetchRobotsTxt } from "./robots.js";
|
|
6
|
+
export class ToolFetchError extends Error {
|
|
7
|
+
toolError;
|
|
8
|
+
constructor(toolError) {
|
|
9
|
+
super(toolError.message ??
|
|
10
|
+
toolError.url ??
|
|
11
|
+
"fetch error");
|
|
12
|
+
this.toolError = toolError;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
function sleep(ms) {
|
|
16
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
17
|
+
}
|
|
18
|
+
/** Extract hostname from a URL string. Returns empty string on failure. */
|
|
19
|
+
function getHostname(url) {
|
|
20
|
+
try {
|
|
21
|
+
return new URL(url).hostname;
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return "";
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Polite HTTP GET. Validates URL, checks robots.txt, enforces rate limits.
|
|
29
|
+
* Throws ToolFetchError on any error condition.
|
|
30
|
+
*/
|
|
31
|
+
export async function politeFetch(url, opts = {}) {
|
|
32
|
+
// Validate URL
|
|
33
|
+
let parsedUrl;
|
|
34
|
+
try {
|
|
35
|
+
parsedUrl = new URL(url);
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
throw new ToolFetchError({
|
|
39
|
+
type: "invalid_url",
|
|
40
|
+
message: `Invalid URL: ${url}`,
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
const hostname = parsedUrl.hostname;
|
|
44
|
+
const respectRobots = opts.respectRobots !== undefined ? opts.respectRobots : POLITE_FETCH.RESPECT_ROBOTS;
|
|
45
|
+
// Check inter-request delay
|
|
46
|
+
if (opts.hostDelays) {
|
|
47
|
+
const lastRequest = opts.hostDelays.get(hostname);
|
|
48
|
+
if (lastRequest !== undefined) {
|
|
49
|
+
const elapsed = Date.now() - lastRequest;
|
|
50
|
+
if (elapsed < POLITE_FETCH.INTER_REQUEST_DELAY_MS) {
|
|
51
|
+
await sleep(POLITE_FETCH.INTER_REQUEST_DELAY_MS - elapsed);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
// Check robots.txt (skip for robots.txt and sitemaps to avoid recursion)
|
|
56
|
+
const isRobotsTxt = parsedUrl.pathname.toLowerCase() === "/robots.txt";
|
|
57
|
+
const isSitemap = parsedUrl.pathname.toLowerCase().includes("sitemap") ||
|
|
58
|
+
parsedUrl.pathname.toLowerCase().includes("llms.txt");
|
|
59
|
+
if (respectRobots && !isRobotsTxt && !isSitemap) {
|
|
60
|
+
const robotsCache = opts.robotsCache ?? new Map();
|
|
61
|
+
if (!robotsCache.has(hostname)) {
|
|
62
|
+
const robotsUrl = `${parsedUrl.protocol}//${hostname}/robots.txt`;
|
|
63
|
+
try {
|
|
64
|
+
const robotsText = await fetchRobotsTxt(robotsUrl);
|
|
65
|
+
robotsCache.set(hostname, robotsText);
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
// robots.txt absent or fetch error - treat as allow-all
|
|
69
|
+
robotsCache.set(hostname, "");
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
const robotsContent = robotsCache.get(hostname) ?? "";
|
|
73
|
+
const allowed = checkRobotsAllowed(robotsContent, url, POLITE_FETCH.USER_AGENT);
|
|
74
|
+
if (!allowed) {
|
|
75
|
+
throw new ToolFetchError({
|
|
76
|
+
type: "robots_blocked",
|
|
77
|
+
url,
|
|
78
|
+
user_agent: POLITE_FETCH.USER_AGENT,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
// Update delay tracker
|
|
83
|
+
if (opts.hostDelays) {
|
|
84
|
+
opts.hostDelays.set(hostname, Date.now());
|
|
85
|
+
}
|
|
86
|
+
const startTime = Date.now();
|
|
87
|
+
console.error(`[fetch] GET ${url}`);
|
|
88
|
+
try {
|
|
89
|
+
const response = await undiciFetch(url, {
|
|
90
|
+
method: "GET",
|
|
91
|
+
headers: {
|
|
92
|
+
"User-Agent": POLITE_FETCH.USER_AGENT,
|
|
93
|
+
Accept: "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
|
|
94
|
+
"Accept-Language": "en-US,en;q=0.5",
|
|
95
|
+
},
|
|
96
|
+
signal: AbortSignal.timeout(POLITE_FETCH.TIMEOUT_MS),
|
|
97
|
+
redirect: "follow",
|
|
98
|
+
});
|
|
99
|
+
// Stream body with byte cap using arrayBuffer slicing
|
|
100
|
+
const arrayBuf = await response.arrayBuffer();
|
|
101
|
+
const fullBytes = Buffer.from(arrayBuf);
|
|
102
|
+
let truncated = false;
|
|
103
|
+
let bodyBuf;
|
|
104
|
+
if (fullBytes.length > POLITE_FETCH.MAX_BYTES) {
|
|
105
|
+
bodyBuf = fullBytes.subarray(0, POLITE_FETCH.MAX_BYTES);
|
|
106
|
+
truncated = true;
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
bodyBuf = fullBytes;
|
|
110
|
+
}
|
|
111
|
+
if (truncated) {
|
|
112
|
+
console.error(`[fetch] Response truncated at ${POLITE_FETCH.MAX_BYTES} bytes: ${url}`);
|
|
113
|
+
}
|
|
114
|
+
const body = bodyBuf.toString("utf-8");
|
|
115
|
+
const duration = Date.now() - startTime;
|
|
116
|
+
console.error(`[fetch] ${response.status} ${url} (${duration}ms, ${fullBytes.length}b)`);
|
|
117
|
+
if (response.status >= 400) {
|
|
118
|
+
throw new ToolFetchError({
|
|
119
|
+
type: "fetch_error",
|
|
120
|
+
url,
|
|
121
|
+
status: response.status,
|
|
122
|
+
message: `HTTP ${response.status}`,
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
// Collect headers as plain Record<string, string>
|
|
126
|
+
const headers = {};
|
|
127
|
+
response.headers.forEach((value, key) => {
|
|
128
|
+
headers[key] = value;
|
|
129
|
+
});
|
|
130
|
+
return {
|
|
131
|
+
body,
|
|
132
|
+
finalUrl: response.url || url,
|
|
133
|
+
statusCode: response.status,
|
|
134
|
+
headers,
|
|
135
|
+
redirected: response.redirected,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
catch (err) {
|
|
139
|
+
if (err instanceof ToolFetchError)
|
|
140
|
+
throw err;
|
|
141
|
+
const errObj = err;
|
|
142
|
+
if (errObj.name === "TimeoutError" || errObj.name === "AbortError") {
|
|
143
|
+
throw new ToolFetchError({
|
|
144
|
+
type: "fetch_timeout",
|
|
145
|
+
url,
|
|
146
|
+
timeout_ms: POLITE_FETCH.TIMEOUT_MS,
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
throw new ToolFetchError({
|
|
150
|
+
type: "fetch_error",
|
|
151
|
+
url,
|
|
152
|
+
message: errObj.message ?? String(err),
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* HTTP HEAD request for link health checks. Returns status code or null on error.
|
|
158
|
+
*/
|
|
159
|
+
export async function politeHead(url, opts = {}) {
|
|
160
|
+
const hostname = getHostname(url);
|
|
161
|
+
if (!hostname)
|
|
162
|
+
return null;
|
|
163
|
+
if (opts.hostDelays) {
|
|
164
|
+
const lastRequest = opts.hostDelays.get(hostname);
|
|
165
|
+
if (lastRequest !== undefined) {
|
|
166
|
+
const elapsed = Date.now() - lastRequest;
|
|
167
|
+
if (elapsed < POLITE_FETCH.INTER_REQUEST_DELAY_MS) {
|
|
168
|
+
await sleep(POLITE_FETCH.INTER_REQUEST_DELAY_MS - elapsed);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
try {
|
|
173
|
+
const response = await undiciFetch(url, {
|
|
174
|
+
method: "HEAD",
|
|
175
|
+
headers: { "User-Agent": POLITE_FETCH.USER_AGENT },
|
|
176
|
+
signal: AbortSignal.timeout(POLITE_FETCH.TIMEOUT_MS),
|
|
177
|
+
redirect: "follow",
|
|
178
|
+
});
|
|
179
|
+
// Consume body
|
|
180
|
+
await response.arrayBuffer();
|
|
181
|
+
if (opts.hostDelays)
|
|
182
|
+
opts.hostDelays.set(hostname, Date.now());
|
|
183
|
+
console.error(`[head] ${response.status} ${url}`);
|
|
184
|
+
return response.status;
|
|
185
|
+
}
|
|
186
|
+
catch {
|
|
187
|
+
return null;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
//# sourceMappingURL=fetch.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fetch.js","sourceRoot":"","sources":["../../src/lib/fetch.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAC5E,uFAAuF;AAEvF,OAAO,EAAE,KAAK,IAAI,WAAW,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAyBjE,MAAM,OAAO,cAAe,SAAQ,KAAK;IACX;IAA5B,YAA4B,SAAoB;QAC9C,KAAK,CACF,SAAkC,CAAC,OAAO;YAC1C,SAA8B,CAAC,GAAG;YACnC,aAAa,CACd,CAAC;QALwB,cAAS,GAAT,SAAS,CAAW;IAMhD,CAAC;CACF;AAED,SAAS,KAAK,CAAC,EAAU;IACvB,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC;AAC3D,CAAC;AAED,2EAA2E;AAC3E,SAAS,WAAW,CAAC,GAAW;IAC9B,IAAI,CAAC;QACH,OAAO,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC;IAC/B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,GAAW,EACX,OAAqB,EAAE;IAEvB,eAAe;IACf,IAAI,SAAc,CAAC;IACnB,IAAI,CAAC;QACH,SAAS,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;IAC3B,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,cAAc,CAAC;YACvB,IAAI,EAAE,aAAa;YACnB,OAAO,EAAE,gBAAgB,GAAG,EAAE;SAC/B,CAAC,CAAC;IACL,CAAC;IAED,MAAM,QAAQ,GAAG,SAAS,CAAC,QAAQ,CAAC;IACpC,MAAM,aAAa,GACjB,IAAI,CAAC,aAAa,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,YAAY,CAAC,cAAc,CAAC;IAEtF,4BAA4B;IAC5B,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;QACpB,MAAM,WAAW,GAAG,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QAClD,IAAI,WAAW,KAAK,SAAS,EAAE,CAAC;YAC9B,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,WAAW,CAAC;YACzC,IAAI,OAAO,GAAG,YAAY,CAAC,sBAAsB,EAAE,CAAC;gBAClD,MAAM,KAAK,CAAC,YAAY,CAAC,sBAAsB,GAAG,OAAO,CAAC,CAAC;YAC7D,CAAC;QACH,CAAC;IACH,CAAC;IAED,yEAAyE;IACzE,MAAM,WAAW,GAAG,SAAS,CAAC,QAAQ,CAAC,WAAW,EAAE,KAAK,aAAa,CAAC;IACvE,MAAM,SAAS,GACb,SAAS,CAAC,QAAQ,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,SAAS,CAAC;QACpD,SAAS,CAAC,QAAQ,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC;IAExD,IAAI,aAAa,IAAI,CAAC,WAAW,IAAI,CAAC,SAAS,EAAE,CAAC;QAChD,MAAM,WAAW,GAAG,IAAI,CAAC,WAAW,IAAI,IAAI,GAAG,EAAkB,CAAC;QAClE,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC/B,MAAM,SAAS,GAAG,GAAG,SAAS,CAAC,QAAQ,KAAK,QAAQ,aAAa,CAAC;YAClE,IAAI,CAAC;gBACH,MAAM,UAAU,GAAG,MAAM,cAAc,CAAC,SAAS,CAAC,CAAC;gBACnD,WAAW,CAAC,GAAG,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC;YACxC,CAAC;YAAC,MAAM,CAAC;gBACP,wDAAwD;gBACxD,WAAW,CAAC,GAAG,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;YAChC,CAAC;QACH,CAAC;QACD,MAAM,aAAa,GAAG,WAAW,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC;QACtD,MAAM,OAAO,GAAG,kBAAkB,CAAC,aAAa,EAAE,GAAG,EAAE,YAAY,CAAC,UAAU,CAAC,CAAC;QAChF,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,MAAM,IAAI,cAAc,CAAC;gBACvB,IAAI,EAAE,gBAAgB;gBACtB,GAAG;gBACH,UAAU,EAAE,YAAY,CAAC,UAAU;aACpC,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,uBAAuB;IACvB,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;QACpB,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,QAAQ,EAAE,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;IAC5C,CAAC;IAED,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAC7B,OAAO,CAAC,KAAK,CAAC,eAAe,GAAG,EAAE,CAAC,CAAC;IAEpC,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,MAAM,WAAW,CAAC,GAAG,EAAE;YACtC,MAAM,EAAE,KAAK;YACb,OAAO,EAAE;gBACP,YAAY,EAAE,YAAY,CAAC,UAAU;gBACrC,MAAM,EAAE,iEAAiE;gBACzE,iBAAiB,EAAE,gBAAgB;aACpC;YACD,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,YAAY,CAAC,UAAU,CAAC;YACpD,QAAQ,EAAE,QAAQ;SACnB,CAAC,CAAC;QAEH,sDAAsD;QACtD,MAAM,QAAQ,GAAG,MAAM,QAAQ,CAAC,WAAW,EAAE,CAAC;QAC9C,MAAM,SAAS,GAAG,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACxC,IAAI,SAAS,GAAG,KAAK,CAAC;QACtB,IAAI,OAAe,CAAC;QACpB,IAAI,SAAS,CAAC,MAAM,GAAG,YAAY,CAAC,SAAS,EAAE,CAAC;YAC9C,OAAO,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC,EAAE,YAAY,CAAC,SAAS,CAAC,CAAC;YACxD,SAAS,GAAG,IAAI,CAAC;QACnB,CAAC;aAAM,CAAC;YACN,OAAO,GAAG,SAAS,CAAC;QACtB,CAAC;QAED,IAAI,SAAS,EAAE,CAAC;YACd,OAAO,CAAC,KAAK,CAAC,iCAAiC,YAAY,CAAC,SAAS,WAAW,GAAG,EAAE,CAAC,CAAC;QACzF,CAAC;QAED,MAAM,IAAI,GAAG,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;QACvC,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC;QACxC,OAAO,CAAC,KAAK,CAAC,WAAW,QAAQ,CAAC,MAAM,IAAI,GAAG,KAAK,QAAQ,OAAO,SAAS,CAAC,MAAM,IAAI,CAAC,CAAC;QAEzF,IAAI,QAAQ,CAAC,MAAM,IAAI,GAAG,EAAE,CAAC;YAC3B,MAAM,IAAI,cAAc,CAAC;gBACvB,IAAI,EAAE,aAAa;gBACnB,GAAG;gBACH,MAAM,EAAE,QAAQ,CAAC,MAAM;gBACvB,OAAO,EAAE,QAAQ,QAAQ,CAAC,MAAM,EAAE;aACnC,CAAC,CAAC;QACL,CAAC;QAED,kDAAkD;QAClD,MAAM,OAAO,GAA2B,EAAE,CAAC;QAC3C,QAAQ,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE;YACtC,OAAO,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;QACvB,CAAC,CAAC,CAAC;QAEH,OAAO;YACL,IAAI;YACJ,QAAQ,EAAE,QAAQ,CAAC,GAAG,IAAI,GAAG;YAC7B,UAAU,EAAE,QAAQ,CAAC,MAAM;YAC3B,OAAO;YACP,UAAU,EAAE,QAAQ,CAAC,UAAU;SAChC,CAAC;IACJ,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,GAAG,YAAY,cAAc;YAAE,MAAM,GAAG,CAAC;QAC7C,MAAM,MAAM,GAAG,GAAY,CAAC;QAC5B,IAAI,MAAM,CAAC,IAAI,KAAK,cAAc,IAAI,MAAM,CAAC,IAAI,KAAK,YAAY,EAAE,CAAC;YACnE,MAAM,IAAI,cAAc,CAAC;gBACvB,IAAI,EAAE,eAAe;gBACrB,GAAG;gBACH,UAAU,EAAE,YAAY,CAAC,UAAU;aACpC,CAAC,CAAC;QACL,CAAC;QACD,MAAM,IAAI,cAAc,CAAC;YACvB,IAAI,EAAE,aAAa;YACnB,GAAG;YACH,OAAO,EAAE,MAAM,CAAC,OAAO,IAAI,MAAM,CAAC,GAAG,CAAC;SACvC,CAAC,CAAC;IACL,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAC9B,GAAW,EACX,OAAqB,EAAE;IAEvB,MAAM,QAAQ,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC;IAClC,IAAI,CAAC,QAAQ;QAAE,OAAO,IAAI,CAAC;IAE3B,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;QACpB,MAAM,WAAW,GAAG,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QAClD,IAAI,WAAW,KAAK,SAAS,EAAE,CAAC;YAC9B,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,WAAW,CAAC;YACzC,IAAI,OAAO,GAAG,YAAY,CAAC,sBAAsB,EAAE,CAAC;gBAClD,MAAM,KAAK,CAAC,YAAY,CAAC,sBAAsB,GAAG,OAAO,CAAC,CAAC;YAC7D,CAAC;QACH,CAAC;IACH,CAAC;IAED,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,MAAM,WAAW,CAAC,GAAG,EAAE;YACtC,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,EAAE,YAAY,EAAE,YAAY,CAAC,UAAU,EAAE;YAClD,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,YAAY,CAAC,UAAU,CAAC;YACpD,QAAQ,EAAE,QAAQ;SACnB,CAAC,CAAC;QACH,eAAe;QACf,MAAM,QAAQ,CAAC,WAAW,EAAE,CAAC;QAC7B,IAAI,IAAI,CAAC,UAAU;YAAE,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,QAAQ,EAAE,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;QAC/D,OAAO,CAAC,KAAK,CAAC,UAAU,QAAQ,CAAC,MAAM,IAAI,GAAG,EAAE,CAAC,CAAC;QAClD,OAAO,QAAQ,CAAC,MAAM,CAAC;IACzB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
export interface HeadData {
|
|
2
|
+
title: string | null;
|
|
3
|
+
metaDescription: string | null;
|
|
4
|
+
canonical: string | null;
|
|
5
|
+
ogTitle: string | null;
|
|
6
|
+
ogDescription: string | null;
|
|
7
|
+
ogImage: string | null;
|
|
8
|
+
ogUrl: string | null;
|
|
9
|
+
ogType: string | null;
|
|
10
|
+
twitterCard: string | null;
|
|
11
|
+
twitterTitle: string | null;
|
|
12
|
+
twitterDescription: string | null;
|
|
13
|
+
noindex: boolean;
|
|
14
|
+
noindexHeader: boolean;
|
|
15
|
+
hreflangTags: Array<{
|
|
16
|
+
lang: string;
|
|
17
|
+
href: string;
|
|
18
|
+
}>;
|
|
19
|
+
charset: string | null;
|
|
20
|
+
viewport: string | null;
|
|
21
|
+
}
|
|
22
|
+
export interface PageStructure {
|
|
23
|
+
h1s: string[];
|
|
24
|
+
h2s: string[];
|
|
25
|
+
h3s: string[];
|
|
26
|
+
h4s: string[];
|
|
27
|
+
paragraphs: string[];
|
|
28
|
+
orderedLists: number;
|
|
29
|
+
tables: number;
|
|
30
|
+
images: number;
|
|
31
|
+
videos: number;
|
|
32
|
+
externalLinks: string[];
|
|
33
|
+
internalLinks: string[];
|
|
34
|
+
wordCount: number;
|
|
35
|
+
bodyText: string;
|
|
36
|
+
}
|
|
37
|
+
/** Parse HTML and extract HEAD metadata. */
|
|
38
|
+
export declare function parseHead(html: string, xRobotsTag?: string | string[]): HeadData;
|
|
39
|
+
/** Extract page structure: headings, paragraphs, links, word count, body text. */
|
|
40
|
+
export declare function parseBody(html: string, baseUrl: string): PageStructure;
|
|
41
|
+
/** Extract all JSON-LD script blocks from HTML as raw strings. */
|
|
42
|
+
export declare function extractJsonLdBlocks(html: string): string[];
|
|
43
|
+
/** Simple Levenshtein distance between two strings. */
|
|
44
|
+
export declare function levenshtein(a: string, b: string): number;
|
|
45
|
+
//# sourceMappingURL=html.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"html.d.ts","sourceRoot":"","sources":["../../src/lib/html.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,QAAQ;IACvB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,OAAO,EAAE,OAAO,CAAC;IACjB,aAAa,EAAE,OAAO,CAAC;IACvB,YAAY,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACpD,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB;AAED,MAAM,WAAW,aAAa;IAC5B,GAAG,EAAE,MAAM,EAAE,CAAC;IACd,GAAG,EAAE,MAAM,EAAE,CAAC;IACd,GAAG,EAAE,MAAM,EAAE,CAAC;IACd,GAAG,EAAE,MAAM,EAAE,CAAC;IACd,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,4CAA4C;AAC5C,wBAAgB,SAAS,CACvB,IAAI,EAAE,MAAM,EACZ,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,GAC7B,QAAQ,CA4EV;AAED,kFAAkF;AAClF,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,aAAa,CAyEtE;AAED,kEAAkE;AAClE,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAQ1D;AAED,uDAAuD;AACvD,wBAAgB,WAAW,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAexD"}
|
package/dist/lib/html.js
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
// Cheerio-based DOM helpers for extracting page signals.
|
|
2
|
+
import * as cheerio from "cheerio";
|
|
3
|
+
/** Parse HTML and extract HEAD metadata. */
|
|
4
|
+
export function parseHead(html, xRobotsTag) {
|
|
5
|
+
const $ = cheerio.load(html);
|
|
6
|
+
const head = $("head");
|
|
7
|
+
const title = head.find("title").first().text().trim() || null;
|
|
8
|
+
const metaDescription = head
|
|
9
|
+
.find('meta[name="description"]')
|
|
10
|
+
.attr("content")
|
|
11
|
+
?.trim() ?? null;
|
|
12
|
+
const canonical = head.find('link[rel="canonical"]').attr("href")?.trim() ?? null;
|
|
13
|
+
const ogTitle = head.find('meta[property="og:title"]').attr("content")?.trim() ?? null;
|
|
14
|
+
const ogDescription = head.find('meta[property="og:description"]').attr("content")?.trim() ?? null;
|
|
15
|
+
const ogImage = head.find('meta[property="og:image"]').attr("content")?.trim() ?? null;
|
|
16
|
+
const ogUrl = head.find('meta[property="og:url"]').attr("content")?.trim() ?? null;
|
|
17
|
+
const ogType = head.find('meta[property="og:type"]').attr("content")?.trim() ?? null;
|
|
18
|
+
const twitterCard = head.find('meta[name="twitter:card"]').attr("content")?.trim() ?? null;
|
|
19
|
+
const twitterTitle = head.find('meta[name="twitter:title"]').attr("content")?.trim() ?? null;
|
|
20
|
+
const twitterDescription = head.find('meta[name="twitter:description"]').attr("content")?.trim() ?? null;
|
|
21
|
+
// Check noindex from meta tags
|
|
22
|
+
const robotsMeta = head
|
|
23
|
+
.find('meta[name="robots"]')
|
|
24
|
+
.attr("content")
|
|
25
|
+
?.toLowerCase() ?? "";
|
|
26
|
+
let noindex = robotsMeta.includes("noindex");
|
|
27
|
+
// Check X-Robots-Tag header
|
|
28
|
+
let noindexHeader = false;
|
|
29
|
+
if (xRobotsTag) {
|
|
30
|
+
const tags = Array.isArray(xRobotsTag) ? xRobotsTag : [xRobotsTag];
|
|
31
|
+
noindexHeader = tags.some((t) => t.toLowerCase().includes("noindex"));
|
|
32
|
+
if (noindexHeader)
|
|
33
|
+
noindex = true;
|
|
34
|
+
}
|
|
35
|
+
const hreflangTags = [];
|
|
36
|
+
head.find('link[rel="alternate"][hreflang]').each((_, el) => {
|
|
37
|
+
const lang = $(el).attr("hreflang") ?? "";
|
|
38
|
+
const href = $(el).attr("href") ?? "";
|
|
39
|
+
if (lang && href)
|
|
40
|
+
hreflangTags.push({ lang, href });
|
|
41
|
+
});
|
|
42
|
+
const charset = head.find('meta[charset]').attr("charset")?.trim() ??
|
|
43
|
+
head.find('meta[http-equiv="Content-Type"]').attr("content")?.match(/charset=([^;]+)/i)?.[1]?.trim() ??
|
|
44
|
+
null;
|
|
45
|
+
const viewport = head.find('meta[name="viewport"]').attr("content")?.trim() ?? null;
|
|
46
|
+
return {
|
|
47
|
+
title,
|
|
48
|
+
metaDescription,
|
|
49
|
+
canonical,
|
|
50
|
+
ogTitle,
|
|
51
|
+
ogDescription,
|
|
52
|
+
ogImage,
|
|
53
|
+
ogUrl,
|
|
54
|
+
ogType,
|
|
55
|
+
twitterCard,
|
|
56
|
+
twitterTitle,
|
|
57
|
+
twitterDescription,
|
|
58
|
+
noindex,
|
|
59
|
+
noindexHeader,
|
|
60
|
+
hreflangTags,
|
|
61
|
+
charset,
|
|
62
|
+
viewport,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
/** Extract page structure: headings, paragraphs, links, word count, body text. */
|
|
66
|
+
export function parseBody(html, baseUrl) {
|
|
67
|
+
const $ = cheerio.load(html);
|
|
68
|
+
const body = $("body");
|
|
69
|
+
const h1s = [];
|
|
70
|
+
const h2s = [];
|
|
71
|
+
const h3s = [];
|
|
72
|
+
const h4s = [];
|
|
73
|
+
body.find("h1").each((_, el) => { h1s.push($(el).text().trim()); });
|
|
74
|
+
body.find("h2").each((_, el) => { h2s.push($(el).text().trim()); });
|
|
75
|
+
body.find("h3").each((_, el) => { h3s.push($(el).text().trim()); });
|
|
76
|
+
body.find("h4").each((_, el) => { h4s.push($(el).text().trim()); });
|
|
77
|
+
const paragraphs = [];
|
|
78
|
+
body.find("p").each((_, el) => {
|
|
79
|
+
const text = $(el).text().trim();
|
|
80
|
+
if (text.length > 20)
|
|
81
|
+
paragraphs.push(text);
|
|
82
|
+
});
|
|
83
|
+
const orderedLists = body.find("ol").length;
|
|
84
|
+
const tables = body.find("table").length;
|
|
85
|
+
const images = body.find("img").length;
|
|
86
|
+
let videos = body.find("video").length;
|
|
87
|
+
body.find("iframe").each((_, el) => {
|
|
88
|
+
const src = $(el).attr("src") ?? "";
|
|
89
|
+
if (src.includes("youtube") || src.includes("youtu.be") || src.includes("vimeo")) {
|
|
90
|
+
videos++;
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
let baseParsed = null;
|
|
94
|
+
try {
|
|
95
|
+
baseParsed = new URL(baseUrl);
|
|
96
|
+
}
|
|
97
|
+
catch { /* ignore */ }
|
|
98
|
+
const externalLinks = [];
|
|
99
|
+
const internalLinks = [];
|
|
100
|
+
body.find("a[href]").each((_, el) => {
|
|
101
|
+
const href = $(el).attr("href") ?? "";
|
|
102
|
+
if (!href || href.startsWith("#") || href.startsWith("mailto:"))
|
|
103
|
+
return;
|
|
104
|
+
if (href.startsWith("http")) {
|
|
105
|
+
try {
|
|
106
|
+
const linkUrl = new URL(href);
|
|
107
|
+
if (baseParsed && linkUrl.hostname !== baseParsed.hostname) {
|
|
108
|
+
externalLinks.push(href);
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
internalLinks.push(href);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
catch { /* ignore invalid */ }
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
internalLinks.push(href);
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
// Strip scripts/styles/nav from body text
|
|
121
|
+
body.find("script, style, nav, header, footer, .nav, .menu").remove();
|
|
122
|
+
const bodyText = body.text().replace(/\s+/g, " ").trim();
|
|
123
|
+
const wordCount = bodyText.split(/\s+/).filter((w) => w.length > 0).length;
|
|
124
|
+
return {
|
|
125
|
+
h1s,
|
|
126
|
+
h2s,
|
|
127
|
+
h3s,
|
|
128
|
+
h4s,
|
|
129
|
+
paragraphs,
|
|
130
|
+
orderedLists,
|
|
131
|
+
tables,
|
|
132
|
+
images,
|
|
133
|
+
videos,
|
|
134
|
+
externalLinks,
|
|
135
|
+
internalLinks,
|
|
136
|
+
wordCount,
|
|
137
|
+
bodyText,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
/** Extract all JSON-LD script blocks from HTML as raw strings. */
|
|
141
|
+
export function extractJsonLdBlocks(html) {
|
|
142
|
+
const $ = cheerio.load(html);
|
|
143
|
+
const blocks = [];
|
|
144
|
+
$('script[type="application/ld+json"]').each((_, el) => {
|
|
145
|
+
const content = $(el).html();
|
|
146
|
+
if (content)
|
|
147
|
+
blocks.push(content.trim());
|
|
148
|
+
});
|
|
149
|
+
return blocks;
|
|
150
|
+
}
|
|
151
|
+
/** Simple Levenshtein distance between two strings. */
|
|
152
|
+
export function levenshtein(a, b) {
|
|
153
|
+
const m = a.length;
|
|
154
|
+
const n = b.length;
|
|
155
|
+
const dp = Array.from({ length: m + 1 }, (_, i) => Array.from({ length: n + 1 }, (_, j) => (i === 0 ? j : j === 0 ? i : 0)));
|
|
156
|
+
for (let i = 1; i <= m; i++) {
|
|
157
|
+
for (let j = 1; j <= n; j++) {
|
|
158
|
+
dp[i][j] =
|
|
159
|
+
a[i - 1] === b[j - 1]
|
|
160
|
+
? dp[i - 1][j - 1]
|
|
161
|
+
: 1 + Math.min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return dp[m][n];
|
|
165
|
+
}
|
|
166
|
+
//# sourceMappingURL=html.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"html.js","sourceRoot":"","sources":["../../src/lib/html.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { Finding } from "../types.js";
|
|
2
|
+
export interface LlmsPage {
|
|
3
|
+
url: string;
|
|
4
|
+
title: string;
|
|
5
|
+
description: string;
|
|
6
|
+
}
|
|
7
|
+
/** Group pages by path prefix into sections. */
|
|
8
|
+
export declare function groupPagesBySection(pages: LlmsPage[]): Map<string, LlmsPage[]>;
|
|
9
|
+
/** Generate llms.txt content from grouped pages. */
|
|
10
|
+
export declare function generateLlmsTxt(siteName: string, siteDescription: string, groups: Map<string, LlmsPage[]>): string;
|
|
11
|
+
/** Generate llms-full.txt by concatenating page full text. */
|
|
12
|
+
export declare function generateLlmsFullTxt(siteName: string, siteDescription: string, pages: Array<LlmsPage & {
|
|
13
|
+
fullText?: string;
|
|
14
|
+
}>, maxBytes?: number): {
|
|
15
|
+
content: string;
|
|
16
|
+
truncated: boolean;
|
|
17
|
+
};
|
|
18
|
+
/** Validate a llms.txt string and return findings. */
|
|
19
|
+
export declare function validateLlmsTxtContent(content: string): Finding[];
|
|
20
|
+
/** Extract all Markdown links from llms.txt content. Returns [{title, url}] */
|
|
21
|
+
export declare function extractLlmsLinks(content: string): Array<{
|
|
22
|
+
title: string;
|
|
23
|
+
url: string;
|
|
24
|
+
}>;
|
|
25
|
+
//# sourceMappingURL=llms-txt.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"llms-txt.d.ts","sourceRoot":"","sources":["../../src/lib/llms-txt.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAE3C,MAAM,WAAW,QAAQ;IACvB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,gDAAgD;AAChD,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,QAAQ,EAAE,GAAG,GAAG,CAAC,MAAM,EAAE,QAAQ,EAAE,CAAC,CAmB9E;AAED,oDAAoD;AACpD,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,MAAM,EAChB,eAAe,EAAE,MAAM,EACvB,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,QAAQ,EAAE,CAAC,GAC9B,MAAM,CA2BR;AAED,8DAA8D;AAC9D,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,MAAM,EAChB,eAAe,EAAE,MAAM,EACvB,KAAK,EAAE,KAAK,CAAC,QAAQ,GAAG;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,EAC9C,QAAQ,SAAa,GACpB;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,OAAO,CAAA;CAAE,CAiBzC;AAED,sDAAsD;AACtD,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,EAAE,CA2EjE;AAED,+EAA+E;AAC/E,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,KAAK,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC,CAQvF"}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
// llms.txt generator and validator helpers.
|
|
2
|
+
/** Group pages by path prefix into sections. */
|
|
3
|
+
export function groupPagesBySection(pages) {
|
|
4
|
+
const groups = new Map();
|
|
5
|
+
for (const page of pages) {
|
|
6
|
+
try {
|
|
7
|
+
const url = new URL(page.url);
|
|
8
|
+
const parts = url.pathname.split("/").filter(Boolean);
|
|
9
|
+
const group = parts.length >= 2 ? `/${parts[0]}/` : "Root";
|
|
10
|
+
const existing = groups.get(group) ?? [];
|
|
11
|
+
existing.push(page);
|
|
12
|
+
groups.set(group, existing);
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
const existing = groups.get("Root") ?? [];
|
|
16
|
+
existing.push(page);
|
|
17
|
+
groups.set("Root", existing);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return groups;
|
|
21
|
+
}
|
|
22
|
+
/** Generate llms.txt content from grouped pages. */
|
|
23
|
+
export function generateLlmsTxt(siteName, siteDescription, groups) {
|
|
24
|
+
const lines = [];
|
|
25
|
+
lines.push(`# ${siteName}`);
|
|
26
|
+
lines.push("");
|
|
27
|
+
lines.push(`> ${siteDescription}`);
|
|
28
|
+
lines.push("");
|
|
29
|
+
for (const [section, pages] of groups.entries()) {
|
|
30
|
+
const sectionTitle = section === "Root"
|
|
31
|
+
? "Pages"
|
|
32
|
+
: section.replace(/^\/|\/$/g, "").replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
33
|
+
lines.push(`## ${sectionTitle}`);
|
|
34
|
+
lines.push("");
|
|
35
|
+
for (const page of pages) {
|
|
36
|
+
const desc = page.description
|
|
37
|
+
? page.description.substring(0, 120)
|
|
38
|
+
: "";
|
|
39
|
+
if (desc) {
|
|
40
|
+
lines.push(`- [${page.title}](${page.url}): ${desc}`);
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
lines.push(`- [${page.title}](${page.url})`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
lines.push("");
|
|
47
|
+
}
|
|
48
|
+
return lines.join("\n").trim();
|
|
49
|
+
}
|
|
50
|
+
/** Generate llms-full.txt by concatenating page full text. */
|
|
51
|
+
export function generateLlmsFullTxt(siteName, siteDescription, pages, maxBytes = 500 * 1024) {
|
|
52
|
+
const header = `# ${siteName}\n\n> ${siteDescription}\n\n`;
|
|
53
|
+
const parts = [header];
|
|
54
|
+
let totalLen = header.length;
|
|
55
|
+
let truncated = false;
|
|
56
|
+
for (const page of pages) {
|
|
57
|
+
const section = `---\n# ${page.title}\nURL: ${page.url}\n\n${page.fullText ?? page.description}\n\n`;
|
|
58
|
+
if (totalLen + section.length > maxBytes) {
|
|
59
|
+
truncated = true;
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
62
|
+
parts.push(section);
|
|
63
|
+
totalLen += section.length;
|
|
64
|
+
}
|
|
65
|
+
return { content: parts.join(""), truncated };
|
|
66
|
+
}
|
|
67
|
+
/** Validate a llms.txt string and return findings. */
|
|
68
|
+
export function validateLlmsTxtContent(content) {
|
|
69
|
+
const findings = [];
|
|
70
|
+
const lines = content.split("\n");
|
|
71
|
+
// H1 must be first non-empty element
|
|
72
|
+
const firstMeaningfulLine = lines.find((l) => l.trim().length > 0);
|
|
73
|
+
if (!firstMeaningfulLine || !firstMeaningfulLine.startsWith("# ")) {
|
|
74
|
+
findings.push({
|
|
75
|
+
severity: "critical",
|
|
76
|
+
category: "llms_txt",
|
|
77
|
+
where: "line 1",
|
|
78
|
+
message: "llms.txt must start with an H1 heading (# Site Name).",
|
|
79
|
+
fix: "Add '# Your Site Name' as the first line of the file.",
|
|
80
|
+
estimated_impact: "high",
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
// Blockquote must be present
|
|
84
|
+
const hasBlockquote = lines.some((l) => l.trim().startsWith("> "));
|
|
85
|
+
if (!hasBlockquote) {
|
|
86
|
+
findings.push({
|
|
87
|
+
severity: "warning",
|
|
88
|
+
category: "llms_txt",
|
|
89
|
+
where: "file structure",
|
|
90
|
+
message: "llms.txt is missing a blockquote description after the H1.",
|
|
91
|
+
fix: "Add '> One to three sentence description of the site.' after the H1.",
|
|
92
|
+
estimated_impact: "medium",
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
// H2 sections
|
|
96
|
+
const hasH2 = lines.some((l) => l.startsWith("## "));
|
|
97
|
+
if (!hasH2) {
|
|
98
|
+
findings.push({
|
|
99
|
+
severity: "info",
|
|
100
|
+
category: "llms_txt",
|
|
101
|
+
where: "file structure",
|
|
102
|
+
message: "llms.txt has no H2 section headings.",
|
|
103
|
+
fix: "Group pages under H2 headings like '## Blog Posts' or '## Tools'.",
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
// All Markdown links must be absolute URLs
|
|
107
|
+
const linkPattern = /\[([^\]]+)\]\(([^)]+)\)/g;
|
|
108
|
+
let match;
|
|
109
|
+
let lineNum = 0;
|
|
110
|
+
for (const line of lines) {
|
|
111
|
+
lineNum++;
|
|
112
|
+
linkPattern.lastIndex = 0;
|
|
113
|
+
while ((match = linkPattern.exec(line)) !== null) {
|
|
114
|
+
const href = match[2];
|
|
115
|
+
const title = match[1];
|
|
116
|
+
if (!title.trim()) {
|
|
117
|
+
findings.push({
|
|
118
|
+
severity: "warning",
|
|
119
|
+
category: "llms_txt",
|
|
120
|
+
where: `line ${lineNum}`,
|
|
121
|
+
message: "Markdown link has an empty title.",
|
|
122
|
+
fix: "Add a descriptive title inside the square brackets.",
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
if (!href.startsWith("http://") && !href.startsWith("https://")) {
|
|
126
|
+
findings.push({
|
|
127
|
+
severity: "critical",
|
|
128
|
+
category: "llms_txt",
|
|
129
|
+
where: `line ${lineNum}`,
|
|
130
|
+
message: `Relative link "${href.substring(0, 60)}" found - all links must be absolute URLs.`,
|
|
131
|
+
fix: "Replace with the full absolute URL including https://.",
|
|
132
|
+
estimated_impact: "medium",
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return findings;
|
|
138
|
+
}
|
|
139
|
+
/** Extract all Markdown links from llms.txt content. Returns [{title, url}] */
|
|
140
|
+
export function extractLlmsLinks(content) {
|
|
141
|
+
const linkPattern = /\[([^\]]+)\]\((https?:\/\/[^)]+)\)/g;
|
|
142
|
+
const links = [];
|
|
143
|
+
let match;
|
|
144
|
+
while ((match = linkPattern.exec(content)) !== null) {
|
|
145
|
+
links.push({ title: match[1], url: match[2] });
|
|
146
|
+
}
|
|
147
|
+
return links;
|
|
148
|
+
}
|
|
149
|
+
//# sourceMappingURL=llms-txt.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"llms-txt.js","sourceRoot":"","sources":["../../src/lib/llms-txt.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export type CrawlerStatus = "allowed" | "disallowed" | "not-mentioned";
|
|
2
|
+
export interface CrawlerRegistry {
|
|
3
|
+
training: string[];
|
|
4
|
+
search: string[];
|
|
5
|
+
user_triggered: string[];
|
|
6
|
+
robots_token_only: string[];
|
|
7
|
+
}
|
|
8
|
+
export declare const CRAWLERS: CrawlerRegistry;
|
|
9
|
+
/** All crawler user-agents we check (excludes robots_token_only which have no HTTP UA). */
|
|
10
|
+
export declare function getAllCrawlerAgents(): string[];
|
|
11
|
+
/**
|
|
12
|
+
* Fetch raw robots.txt content. Does NOT use politeFetch to avoid recursion.
|
|
13
|
+
* Returns empty string if fetch fails.
|
|
14
|
+
*/
|
|
15
|
+
export declare function fetchRobotsTxt(robotsUrl: string): Promise<string>;
|
|
16
|
+
/**
|
|
17
|
+
* Parse robots.txt text and check if a URL is allowed for a given user-agent.
|
|
18
|
+
* Returns true (allowed) if robots.txt is empty or parse fails.
|
|
19
|
+
*/
|
|
20
|
+
export declare function checkRobotsAllowed(robotsText: string, url: string, userAgent: string): boolean;
|
|
21
|
+
/**
|
|
22
|
+
* Check the robots.txt posture for a specific crawler user-agent.
|
|
23
|
+
* Returns "allowed", "disallowed", or "not-mentioned".
|
|
24
|
+
*/
|
|
25
|
+
export declare function checkCrawlerStatus(robotsText: string, robotsUrl: string, userAgent: string): CrawlerStatus;
|
|
26
|
+
//# sourceMappingURL=robots.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"robots.d.ts","sourceRoot":"","sources":["../../src/lib/robots.ts"],"names":[],"mappings":"AAgBA,MAAM,MAAM,aAAa,GAAG,SAAS,GAAG,YAAY,GAAG,eAAe,CAAC;AAEvE,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,iBAAiB,EAAE,MAAM,EAAE,CAAC;CAC7B;AAED,eAAO,MAAM,QAAQ,EAAE,eAAiD,CAAC;AAEzE,2FAA2F;AAC3F,wBAAgB,mBAAmB,IAAI,MAAM,EAAE,CAM9C;AAED;;;GAGG;AACH,wBAAsB,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAevE;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,UAAU,EAAE,MAAM,EAClB,GAAG,EAAE,MAAM,EACX,SAAS,EAAE,MAAM,GAChB,OAAO,CAST;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,GAChB,aAAa,CAkBf"}
|