turva-llms-txt-validator 0.3.6 → 0.3.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/bin/cli.mjs +2 -2
- package/package.json +1 -1
- package/src/index.mjs +33 -1
package/README.md
CHANGED
|
@@ -14,7 +14,7 @@ Requires **Node.js 18.17 or newer**. Run it without a global install:
|
|
|
14
14
|
npx --yes turva-llms-txt-validator example.com
|
|
15
15
|
```
|
|
16
16
|
|
|
17
|
-
Replace `example.com` with the domain you want to check. A domain, HTTP URL or HTTPS URL is accepted. Validation uses the host's `/llms.txt` and home page over HTTPS, regardless of the supplied path.
|
|
17
|
+
Replace `example.com` with the domain you want to check. A domain, HTTP URL or HTTPS URL is accepted. Validation uses the host's `/llms.txt` and home page over HTTPS, regardless of the supplied path. If the address has a path other than `/` or `/llms.txt`, the result names that path first in an `info` line.
|
|
18
18
|
|
|
19
19
|
For repeated use, install the CLI globally:
|
|
20
20
|
|
|
@@ -65,7 +65,7 @@ Completed validation returns `{ target, summary, checks }`. Each check contains
|
|
|
65
65
|
| 1 | `/llms.txt` returns HTTP 200 | Non-200 response, rejected or excessive redirects | none |
|
|
66
66
|
| 2 | Response is plain text | Body looks like an HTML page | Content type is neither `text/plain` nor `text/markdown` |
|
|
67
67
|
| 3 | First non-empty line is a Markdown H1 | Missing H1, including a title indented as a code block | none |
|
|
68
|
-
| 4 | Blockquote summary follows the title | none | Missing summary |
|
|
68
|
+
| 4 | Blockquote summary follows the title | none | Missing summary, or a blockquote after other text but before the first H2, named by its line |
|
|
69
69
|
| 5 | H2 sections group the content | none | No H2 sections, or no section contains a Markdown link list |
|
|
70
70
|
| 6 | Markdown links have names and absolute HTTP or HTTPS targets | none | Missing links, empty names, relative targets or unsupported targets |
|
|
71
71
|
| 7 | File is small enough to read cheaply | none | Over 50 KB, or truncated at the 256 KB read limit |
|
package/bin/cli.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// CLI for turva-llms-txt-validator. Exit codes: 0 = valid (or valid with
|
|
3
3
|
// warnings), 1 = not valid (or warnings with --strict), 2 = could not fetch
|
|
4
|
-
// or bad input. The two v2 discovery checks
|
|
5
|
-
// exit code, by design. Same checks and JSON shape as the hosted validator:
|
|
4
|
+
// or bad input. The two v2 discovery checks and the input-path note carry status
|
|
5
|
+
// "info" and move no exit code, by design. Same checks and JSON shape as the hosted validator:
|
|
6
6
|
// curl -H "Accept: application/json" "https://turva.dev/llms-txt-validator?url=example.com"
|
|
7
7
|
import { validateHost } from "../src/index.mjs";
|
|
8
8
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "turva-llms-txt-validator",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.7",
|
|
4
4
|
"description": "Validate a site's llms.txt structure: eight checks as pass/warn/fail plus two llms.txt v2 discovery checks that read pass or info and never move the summary, JSON output. Same checks as the hosted validator at turva.dev/llms-txt-validator.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.mjs",
|
package/src/index.mjs
CHANGED
|
@@ -86,6 +86,21 @@ export function normalizeHostInput(raw) {
|
|
|
86
86
|
return typedHost.length > 1 && typedHost.endsWith(".") ? typedHost.slice(0, -1) : typedHost;
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
+
// The path of the address as typed, when it is neither the root nor /llms.txt. validateHost keeps its
|
|
90
|
+
// reads pinned to /llms.txt and the home page, because a fixed path is part of the guard against use
|
|
91
|
+
// as a fetch proxy, so a typed /docs/llms.txt used to be dropped without a word while the result
|
|
92
|
+
// described the root file (outside retest 2026-09-26). The result now names the unused path in an
|
|
93
|
+
// info check. The case of the path is kept, and the query and fragment are never returned.
|
|
94
|
+
// Mirrors worker.js enteredPath() (v3.176.0).
|
|
95
|
+
export function enteredPath(raw) {
|
|
96
|
+
let s = String(raw || "").trim();
|
|
97
|
+
if (!/^[a-z][a-z0-9+.-]*:\/\//i.test(s)) s = "https://" + s;
|
|
98
|
+
let u;
|
|
99
|
+
try { u = new URL(s); } catch { return ""; }
|
|
100
|
+
const path = u.pathname;
|
|
101
|
+
return path === "/" || path === "" || path === "/llms.txt" ? "" : path;
|
|
102
|
+
}
|
|
103
|
+
|
|
89
104
|
export function isValidPublicHost(host) {
|
|
90
105
|
if (!host || host.length > 253) return false;
|
|
91
106
|
if (host.startsWith("[") || /^\d+\.\d+\.\d+\.\d+$/.test(host)) return false;
|
|
@@ -316,7 +331,20 @@ export function validateLlmsTxt(f) {
|
|
|
316
331
|
if (afterH1.trim().startsWith("> ")) {
|
|
317
332
|
add("summary", "pass", "Blockquote summary after the title", JSON.stringify(cut(afterH1.trim(), 80)));
|
|
318
333
|
} else {
|
|
319
|
-
|
|
334
|
+
// A blockquote further down, before the first H2, is a summary in the wrong place and not a
|
|
335
|
+
// missing one, and the detail says which (outside retest 2026-09-26, mirrored from worker.js
|
|
336
|
+
// v3.176.0). The status is warn either way, because the format places the summary directly
|
|
337
|
+
// after the title.
|
|
338
|
+
const fence = fenceMask(lines);
|
|
339
|
+
let late = -1;
|
|
340
|
+
for (let i = firstIdx + 1; i < lines.length; i++) {
|
|
341
|
+
if (fence[i]) continue;
|
|
342
|
+
if (/^ {0,3}## /.test(lines[i])) break;
|
|
343
|
+
if (/^ {0,3}> /.test(lines[i])) { late = i; break; }
|
|
344
|
+
}
|
|
345
|
+
add("summary", "warn", "Blockquote summary after the title", late === -1
|
|
346
|
+
? "recommended by the format (> one-line summary), not required"
|
|
347
|
+
: "the blockquote at line " + (late + 1) + " comes after other text, and the format places the summary directly after the title");
|
|
320
348
|
}
|
|
321
349
|
// Headings are read outside fences only, and with the same indentation the H1 check and
|
|
322
350
|
// listItemHasLink have allowed since 2026-08-29. Until 2026-09-10 this one line still
|
|
@@ -1270,6 +1298,10 @@ export async function validateHost(input, opts = {}) {
|
|
|
1270
1298
|
discovery = validateV2Discovery(null, "the home page could not be read, so this was not measured");
|
|
1271
1299
|
}
|
|
1272
1300
|
const checks = validateLlmsTxt(fetched).concat(discovery);
|
|
1301
|
+
// A path in the typed address is not used, and the result says so first (mirrored from worker.js
|
|
1302
|
+
// v3.176.0). Information moves no summary and no exit code.
|
|
1303
|
+
const unusedPath = enteredPath(input);
|
|
1304
|
+
if (unusedPath) checks.unshift({ id: "input-path", status: "info", label: "Path in the address you entered", detail: cut(unusedPath, 120) + " is not used, because the validator always reads /llms.txt at the root of the host" });
|
|
1273
1305
|
// Text quoted from the fetched file leaves without bidirectional controls (round 19,
|
|
1274
1306
|
// V6-U1, mirrored from worker.js v3.164.0): a site's own H1 or summary could otherwise
|
|
1275
1307
|
// read in another order here than in the file itself.
|