turva-llms-txt-validator 0.4.1 → 0.4.2

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 (2) hide show
  1. package/README.md +115 -36
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -1,41 +1,69 @@
1
1
  # turva-llms-txt-validator
2
2
 
3
- Check a website's `llms.txt` from the command line, Node or CI. Get a clear result for each check, with readable output or JSON.
3
+ A command-line tool and TypeScript library that checks a website's `llms.txt`. It runs eight structure checks and two informational discovery checks and prints the result as readable text or JSON.
4
4
 
5
- **Eight structure checks and two informational discovery checks.** The result describes the file's structure and the home page's discovery links. It does not measure overall agent readiness or predict AI-search citations.
5
+ The result describes the file's structure and the home page's discovery links. It does not measure overall agent readiness or predict AI-search citations.
6
6
 
7
- [npm package](https://www.npmjs.com/package/turva-llms-txt-validator) · [Try it in your browser](https://turva.dev/llms-txt-validator) · [llms.txt guide](https://turva.dev/guides/llms-txt) · [All turva.dev tools](https://turva.dev/tools)
7
+ Links: [npm package](https://www.npmjs.com/package/turva-llms-txt-validator), [try it in your browser](https://turva.dev/llms-txt-validator), [llms.txt guide](https://turva.dev/guides/llms-txt) and [all turva.dev tools](https://turva.dev/tools).
8
8
 
9
9
  ## Quick start
10
10
 
11
- Requires **Node.js 22 or newer**. Run it without a global install:
11
+ The package needs **Node.js 22 or newer**. It is MIT licensed and has no runtime dependencies.
12
+
13
+ Run it without installing anything:
12
14
 
13
15
  ```sh
14
16
  npx --yes turva-llms-txt-validator example.com
15
17
  ```
16
18
 
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
-
19
- For repeated use, install the CLI globally:
19
+ Install the CLI globally for repeated use:
20
20
 
21
21
  ```sh
22
22
  npm install -g turva-llms-txt-validator
23
23
  llms-txt-validate example.com
24
24
  ```
25
25
 
26
- ## Read the result
26
+ Add it to a project as a library:
27
+
28
+ ```sh
29
+ npm install turva-llms-txt-validator
30
+ ```
27
31
 
28
- The summary comes last. Each preceding line reports one check. This shortened example uses the hosted validator's turva.dev result observed on 2026-09-09, formatted as the CLI prints it. The remaining passing checks are omitted here:
32
+ ## Command line
33
+
34
+ ```text
35
+ llms-txt-validate <domain-or-url> [--json] [--strict]
36
+ llms-txt-validate --help
37
+ llms-txt-validate --version
38
+ ```
39
+
40
+ A domain, an HTTP URL or an HTTPS URL is accepted. Validation always 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.
41
+
42
+ | Option | Effect |
43
+ | --- | --- |
44
+ | `--json` | Print the result as JSON instead of text. |
45
+ | `--strict` | Exit with code `1` when the summary is `valid with warnings`. |
46
+ | `--help` | Print usage and exit with code `0`. |
47
+ | `--version`, `-V` | Print the installed version and exit with code `0`. |
48
+
49
+ ### Read the result
50
+
51
+ This is the first part of the CLI output for `turva.dev` on 2026-10-21. The remaining checks are omitted here:
29
52
 
30
53
  ```text
31
54
  https://turva.dev/llms.txt
32
55
  ok File exists at /llms.txt (HTTP 200)
33
56
  ok Response is plain text (text/plain)
34
57
  ok Starts with an H1 title ("# turva.dev")
35
- result: valid
58
+ ok Blockquote summary after the title ("> Agent-readiness audits and advisory for product teams.")
59
+ ok H2 sections group the content (9 sections, 5 carrying a file list)
60
+ ok Markdown links an agent can follow (83 links, all absolute URLs)
61
+ ok Small enough to be cheap to read (11358 bytes)
36
62
  ```
37
63
 
38
- A `warn` identifies something to review, such as a missing blockquote summary. It does not fail the command unless you add `--strict`. A `FAIL` means a structural requirement failed. Each line includes the observed detail.
64
+ The summary comes last, as `result: valid` for this run. Each preceding line reports one check. A `warn` identifies something to review, such as a missing blockquote summary. It does not fail the command unless you add `--strict`. A `FAIL` means a structural requirement failed. Each line includes the observed detail.
65
+
66
+ ### Exit codes
39
67
 
40
68
  The eight structure checks determine the summary:
41
69
 
@@ -48,13 +76,21 @@ The eight structure checks determine the summary:
48
76
 
49
77
  The two discovery checks return `pass` or `info`. They never change the summary or exit code, including in strict mode. If the home page cannot be read, those checks report `info`. The file's structure result still stands.
50
78
 
51
- Use JSON for automation and strict mode when warnings should fail a CI step:
79
+ ### JSON output
52
80
 
53
81
  ```sh
54
82
  npx --yes turva-llms-txt-validator example.com --json --strict
55
83
  ```
56
84
 
57
- Completed validation returns `{ target, summary, checks }`. Each check contains `{ id, status, label, detail }`. The `input-path` check, which reports a path given with the domain and then ignored, also carries `value`, that path cut to 120 characters. With `--json`, an invalid domain or a failed fetch returns `{ "error": "...", "target": "..." }` and exits with code `2`. The target is the address given on the command line, with any password and query value masked. A user name is masked too, except in a value such as `user:secret@example.com`, where the part before the colon reads as a URL scheme and is shown as `user:***@example.com`. A value with no scheme and no slash is read as user information when a colon comes before its last `@`, so `user_name:secret@example.com` is shown as `***@example.com`. Without that colon, as in `path@2x.png` or an e-mail address, the value is shown as given and holds no password. A network error adds `code`, such as `EAI_AGAIN`. An unknown option, a second domain and a missing domain return `{ "error": "..." }` only, with no target, and also exit with code `2`. `--help` exits with code `0`, and `--version` or `-V` prints the installed version and exits with code `0`.
85
+ Completed validation returns `{ target, summary, checks }`, where `target` is `https://<host>/llms.txt`. Each check contains `{ id, status, label, detail }`. The `input-path` check, which reports a path given with the domain and then ignored, also carries `value`, that path cut to 120 characters.
86
+
87
+ With `--json`, an invalid domain or a failed fetch returns `{ "error": "...", "target": "..." }` and exits with code `2`. A network error adds `code`, such as `EAI_AGAIN`. An unknown option, a second domain and a missing domain return `{ "error": "..." }` only, with no target, and also exit with code `2`.
88
+
89
+ In an error report the `target` is the address given on the command line. When it parses as a URL with a host, the user name and password are removed and each query value becomes `***`. Other values follow these cases:
90
+
91
+ - In a value such as `user:secret@example.com`, the part before the colon reads as a URL scheme and is shown as `user:***@example.com`.
92
+ - A value with no scheme and no slash is read as user information when a colon comes before its last `@`, so `user_name:secret@example.com` is shown as `***@example.com`.
93
+ - Without that colon, as in `path@2x.png` or an e-mail address, the value is shown as given and holds no password.
58
94
 
59
95
  ## What it checks
60
96
 
@@ -68,13 +104,35 @@ Completed validation returns `{ target, summary, checks }`. Each check contains
68
104
  | 4 | Blockquote summary follows the title | none | Missing summary, a blockquote block in which no line has text after the `>`, or a blockquote after other text but before the first H2, named by its line |
69
105
  | 5 | H2 sections group the content | none | No H2 sections, no section with a Markdown link list, a heading between the title and the first H2, or a second H1 |
70
106
  | 6 | Markdown links have names and absolute HTTP or HTTPS targets | none | Missing links, empty names, relative targets or targets that do not parse as HTTP or HTTPS URLs |
71
- | 7 | File is small enough to read cheaply | none | Over 50 KB, or truncated at the 256 KB read limit |
107
+ | 7 | File is small enough to read cheaply | none | Over 50 KiB, which is 51,200 bytes, or truncated at the 256 KiB read limit |
72
108
  | 8 | File contains no HTML markup | none | An opening tag with its closing `>`, a closing tag or an HTML comment is found anywhere in the file, including inside code spans and fenced code. An attribute name is any run of characters other than white space, quotes, `<`, `>`, `/` and `=` that holds a Unicode letter, a Unicode digit or the character `_`, so `<div @click="x">` warns. Autolinks such as `<https://example.com>` and prose such as `a <b + c` are not tags. A tag still open at the end of the file, such as `<div class="x"`, has no closing `>` and does not warn |
73
109
 
74
- Links are read the way CommonMark reads them for the forms listed here. A link inside inline code, after an escaped bracket or in an image does not count, and a link in an ordered list, a target in angle brackets and a target with a title in double or single quotes do. A title in parentheses is not read, so `[Guide](https://example.org/guide (Overview))` is not collected. An unbracketed target ends at its first closing parenthesis, so `https://example.org/docs_(v2)` is read only up to `https://example.org/docs_(v2`. Write a target that contains parentheses in angle brackets. A link reference definition or label that spans several lines, and an image used as a reference, are not handled. A quote escaped with a backslash inside a link title, as in `[A](https://example.org/a "Ti\"tle\"")`, is not recognised, so that link is dropped. A title that spans a line break is dropped the same way. Nested brackets in a link label, as in `[A [x] B](https://example.org/a)`, drop the link. A heading is an H2 when `##` is followed by a space or a tab, or when it is one line of text underlined with hyphens. The text line must follow a blank line or the start of the file. A list item above a line of hyphens is not an H2, and neither is a line of hyphens after a blank line. A link in a four-space indented code block counts as a link, but such a block does not count as a file list under an H2. A backslash escape in a target is resolved before the URL test, so `https\://example.org/guide` is read as `https://example.org/guide`. A link label cannot span a blank line. A link on a line that continues a list item, such as the line after a bare `-`, counts as that item's link. After a bare marker the next line counts only when it is indented to the item's content column, which is 2 for `-` and 3 for `1.`, and a blank line right after a bare marker ends the item. After a blank line inside an item, a line counts only when it is indented to the content column, where the item's text starts, so `- item` needs 4 columns. A code fence indented less than that column ends the item. A fence indented to it keeps the item open, and the line after the fence then counts only when it is indented to that column too. CR and CRLF line endings are read as LF. A setext title, which is a text line underlined with `=`, is read only when it is a single line.
75
-
76
110
  Some failures stop the file checks early. For example, an HTML response is reported as a failed plain-text check rather than parsed as Markdown.
77
111
 
112
+ ### How links, headings and list items are read
113
+
114
+ Links are read the way CommonMark reads them for the forms listed here.
115
+
116
+ - A link inside inline code, after an escaped bracket or in an image does not count.
117
+ - A link in an ordered list, a target in angle brackets and a target with a title in double or single quotes do count.
118
+ - A title in parentheses is not read, so `[Guide](https://example.org/guide (Overview))` is not collected.
119
+ - An unbracketed target ends at its first closing parenthesis, so `https://example.org/docs_(v2)` is read only up to `https://example.org/docs_(v2`. Write a target that contains parentheses in angle brackets.
120
+ - A link reference definition or label that spans several lines, and an image used as a reference, are not handled.
121
+ - A quote escaped with a backslash inside a link title, as in `[A](https://example.org/a "Ti\"tle\"")`, is not recognised, so that link is dropped. A title that spans a line break is dropped the same way.
122
+ - Nested brackets in a link label, as in `[A [x] B](https://example.org/a)`, drop the link. A link label cannot span a blank line.
123
+ - A backslash escape in a target is resolved before the URL test, so `https\://example.org/guide` is read as `https://example.org/guide`.
124
+ - CR and CRLF line endings are read as LF. A setext title, which is a text line underlined with `=`, is read only when it is a single line.
125
+
126
+ A heading is an H2 when `##` is followed by a space or a tab, or when it is one line of text underlined with hyphens. The text line must follow a blank line or the start of the file. A list item above a line of hyphens is not an H2, and neither is a line of hyphens after a blank line.
127
+
128
+ A link in a four-space indented code block counts as a link, but such a block does not count as a file list under an H2.
129
+
130
+ A link on a line that continues a list item, such as the line after a bare `-`, counts as that item's link:
131
+
132
+ - After a bare marker the next line counts only when it is indented to the item's content column, which is 2 for `-` and 3 for `1.`. A blank line right after a bare marker ends the item.
133
+ - After a blank line inside an item, a line counts only when it is indented to the content column, where the item's text starts, so `- item` needs 4 columns.
134
+ - A code fence indented less than that column ends the item. A fence indented to it keeps the item open, and the line after the fence then counts only when it is indented to that column too.
135
+
78
136
  ### Home-page discovery
79
137
 
80
138
  These checks are labelled **v2** in the output. They look for link relations in the home page's HTML head or HTTP `Link` header.
@@ -84,31 +142,34 @@ These checks are labelled **v2** in the output. They look for link relations in
84
142
  | 9 | Home page points to its llms.txt | `rel="describedby"` has a non-empty target | `info` |
85
143
  | 10 | Home page points to a Markdown version | `rel="alternate"` has media type `text/markdown` and a non-empty target | `info` |
86
144
 
87
- The validator detects these declarations without fetching their targets. It reads the first 65,536 UTF-16 code units of the head after comments and the contents of script, style, title and template elements have been set aside, and when a relation is missing from a longer head the result says that only that part was read. A target is shown with its credentials and fragment removed and its query values masked. It also does not follow the links inside `llms.txt`, crawl the site or test whether an AI agent can complete a task there.
88
-
89
- ## Use from Node
145
+ The validator detects these declarations without fetching their targets. It reads the first 65,536 UTF-16 code units of the head after comments and the contents of script, style, title, noscript, noframes and template elements have been set aside. When a relation is missing from a longer head, the result says that only that part was read. A target is shown with its credentials and fragment removed and its query values masked.
90
146
 
91
- Install the package in your project:
147
+ The validator does not follow the links inside `llms.txt`, crawl the site or test whether an AI agent can complete a task there.
92
148
 
93
- ```sh
94
- npm install turva-llms-txt-validator
95
- ```
149
+ ## Use from Node and TypeScript
96
150
 
97
- Use an ES module, for example `validate.mjs`:
151
+ The package is an ES module written in TypeScript. It ships its type declarations, so no `@types` package is needed.
98
152
 
99
- ```js
153
+ ```ts
100
154
  import { validateHost } from "turva-llms-txt-validator";
155
+ import type { ValidationResult, LlmsCheck } from "turva-llms-txt-validator";
101
156
 
102
- const result = await validateHost("example.com");
157
+ const result: ValidationResult = await validateHost("example.com");
103
158
  console.log(result.summary);
104
- for (const check of result.checks) {
159
+
160
+ const problems: LlmsCheck[] = result.checks.filter(
161
+ (check) => check.status === "warn" || check.status === "fail",
162
+ );
163
+ for (const check of problems) {
105
164
  console.log(check.status, check.label, check.detail);
106
165
  }
107
166
  ```
108
167
 
168
+ `validateHost(input, options?)` returns a `Promise<ValidationResult>`. The result has `target`, `summary` and `checks`. `summary` is one of `"valid"`, `"valid with warnings"` and `"not valid"`, typed as `ChecksSummary`. Each `LlmsCheck` has `id`, `status`, `label` and `detail`, and an optional `value`. `status` is a `CheckStatus`, one of `"pass"`, `"warn"`, `"fail"` and `"info"`. The optional second argument is typed as `ValidateHostOptions`.
169
+
109
170
  `validateHost` rejects on invalid input or a failed `/llms.txt` fetch. Structural failures are returned in `checks` with the summary `not valid`.
110
171
 
111
- The package is written in TypeScript and ships its type declarations.
172
+ The package also exports the helper functions and types behind `validateHost`, such as `fetchLlmsTxt`, `validateLlmsTxt` and `maskLocation`. The hosted validator keeps its own copies of the same logic. These exports are not documented here as a stable interface. Treat `validateHost` and the types above as the supported surface.
112
173
 
113
174
  ## Use in CI
114
175
 
@@ -119,14 +180,14 @@ Add this step to a GitHub Actions job that already has Node.js 22 or newer avail
119
180
  run: npx --yes turva-llms-txt-validator your-domain.com --strict
120
181
  ```
121
182
 
122
- The same command works in Woodpecker and other runners with Node installed. Omit `--strict` if warnings should remain advisory.
183
+ Any CI runner with Node.js 22 or newer can run the same command. Omit `--strict` if warnings should remain advisory.
123
184
 
124
185
  ## Fetch limits and security
125
186
 
126
187
  The default validation requests two documents: `https://<host>/llms.txt` and `https://<host>/`. Redirects may add requests, but linked pages and discovery targets are never fetched. The validator stores no results.
127
188
 
128
189
  - Timeout: eight seconds per document, shared across its redirect chain.
129
- - Read limit: 256 KB per response.
190
+ - Read limit: 256 KiB, which is 262,144 bytes, per response.
130
191
  - Redirects: up to four hops, over HTTPS, to the same host or its `www`/apex equivalent. Off-site redirects, embedded credentials and unsupported ports are rejected. A redirect target is reported with its credentials and fragment removed and its query values masked, whether the redirect was followed or rejected.
131
192
  - Host checks: IP literals, bracketed IPv6 addresses, localhost and the internal-use TLDs `localdomain`, `local`, `internal`, `home`, `lan`, `corp`, `test`, `invalid`, `arpa` and `onion` are rejected before a request is sent. An address that holds `@`, `?` or `#` is not repeated in the error message, because it can carry a user name, a password or a query value. One trailing dot on the host you give, as in `example.com.`, is removed first. Redirect targets are checked too.
132
193
 
@@ -134,21 +195,41 @@ The default validation requests two documents: `https://<host>/llms.txt` and `ht
134
195
 
135
196
  For vulnerability reporting and supported versions, see [SECURITY.md](https://github.com/erekola/llms-txt-validator/blob/main/SECURITY.md).
136
197
 
198
+ ## Release process
199
+
200
+ The source is TypeScript under `src/` and `bin/`, compiled with `tsc` to `dist/`. The tarball ships only `dist/src/index.js`, `dist/src/index.d.ts`, `dist/bin/cli.js`, the README, the license, the third-party notices and `package.json`, which npm always includes. `dist/` is generated, and `npm pack` builds it through the `prepack` script.
201
+
202
+ GitHub Actions tests Node.js 22 and 24 on Ubuntu and Node.js 22 on Windows. A version tag starts the release workflow, `.github/workflows/publish.yml`. It runs the type check and the tests, fails when the tag and `package.json` name different versions, publishes with npm trusted publishing over OIDC and creates the GitHub release from the CHANGELOG section. npm writes a provenance attestation. No npm token is stored in the repository or in the workflow.
203
+
204
+ The entry point moved to `dist/src/index.js` in 0.4.0, behind the same `exports` name. The CLI kept its flags, its output and its exit codes. See [CHANGELOG.md](https://github.com/erekola/llms-txt-validator/blob/main/CHANGELOG.md) for each release.
205
+
137
206
  ## Verify a release
138
207
 
139
208
  GitHub Actions publishes every version from 0.1.3 on with npm trusted publishing, and each one carries a provenance attestation. To check one, install it in an empty directory and ask npm to verify the signatures. Replace the version with the one you want to check.
140
209
 
141
210
  ```sh
142
- mkdir verify-llms && cd verify-llms && npm init -y && npm install turva-llms-txt-validator@0.4.1 --ignore-scripts && npm audit signatures
211
+ mkdir verify-llms && cd verify-llms && npm init -y && npm install turva-llms-txt-validator@0.4.2 --ignore-scripts && npm audit signatures
212
+ ```
213
+
214
+ `npm audit signatures` checks the registry signature and the provenance attestation of each installed package that has one. The attestation of this package names three things to compare with what you expect: the repository `github.com/erekola/llms-txt-validator`, the workflow file `.github/workflows/publish.yml` and the commit that produced the tarball. The npm version page shows them under Provenance. Tag `v0.4.2` must point at that same commit, and `gh api repos/erekola/llms-txt-validator/commits/v0.4.2 --jq .sha` prints it. A mismatch is a reason not to use that version. Provenance proves which workflow built a release and from which commit. It does not prove that the code is safe, so reading the source and the dependencies stays your job.
215
+
216
+ ## Development
217
+
218
+ ```sh
219
+ git clone https://github.com/erekola/llms-txt-validator.git
220
+ cd llms-txt-validator
221
+ npm ci --ignore-scripts
222
+ npm run typecheck
223
+ npm test
143
224
  ```
144
225
 
145
- `npm audit signatures` checks the registry signature and the provenance attestation of each installed package that has one. The attestation of this package names three things to compare with what you expect: the repository `github.com/erekola/llms-txt-validator`, the workflow file `.github/workflows/publish.yml` and the commit that produced the tarball. The npm version page shows them under Provenance. Tag `v0.4.1` must point at that same commit, and `gh api repos/erekola/llms-txt-validator/commits/v0.4.1 --jq .sha` prints it. A mismatch is a reason not to use that version. Provenance proves where a release was built and from which commit. It does not prove that the code is safe, so reading the source and the dependencies stays your job.
226
+ `npm test` builds `dist/` first and then runs the tests against the compiled output. `npm run build` compiles without testing. `npm run typecheck` runs `tsc --noEmit`. The npm page shows this README from the tarball, so a change to it reaches npm only with a new version.
146
227
 
147
228
  ## Hosted version and source
148
229
 
149
- The [hosted validator](https://turva.dev/llms-txt-validator) accepts any public domain in the browser. It requests the same two documents, `/llms.txt` and the home page, and runs the same checks. Its source is in the [turva.dev Cloudflare Worker](https://github.com/erekola/turva-worker). The hosted validator is the canonical implementation: if results diverge, this package is updated to match it.
230
+ The [hosted validator](https://turva.dev/llms-txt-validator) accepts any public domain in the browser. For any host other than turva.dev itself it requests the same two documents, `/llms.txt` and the home page. For turva.dev it validates the site's own files directly, without a request. It runs the same checks. Its source is in the [turva.dev Cloudflare Worker](https://github.com/erekola/turva-worker). The hosted validator is the canonical implementation: if results diverge, this package is updated to match it.
150
231
 
151
- The hosted version also returns JSON with the same result shape for a completed validation. Its errors are shaped differently: an `error` field with HTTP status 400 and no `target`. The site-wide rate limiter can also answer HTTP 429 as plain text, with a `Retry-After` header, before the validator runs, and that answer is not JSON.
232
+ The hosted version also returns JSON with the same result shape for a completed validation. Its errors are shaped differently: an `error` field, plus a `docs` link when the `url` parameter is missing, with HTTP status 400 and no `target`. The site-wide rate limiter can also answer HTTP 429 as plain text, with a `Retry-After` header, before the validator runs, and that answer is not JSON.
152
233
 
153
234
  The JSON request:
154
235
 
@@ -158,8 +239,6 @@ curl -H "Accept: application/json" "https://turva.dev/llms-txt-validator?url=exa
158
239
 
159
240
  In Windows PowerShell, use `curl.exe` if `curl` resolves to `Invoke-WebRequest`.
160
241
 
161
- The package has no runtime dependencies. The repository's release workflow uses npm trusted publishing with provenance. See [SECURITY.md](https://github.com/erekola/llms-txt-validator/blob/main/SECURITY.md) for details and [CHANGELOG.md](https://github.com/erekola/llms-txt-validator/blob/main/CHANGELOG.md) for releases.
162
-
163
242
  ## Check HTML and Markdown content
164
243
 
165
244
  [markdown-parity-check](https://github.com/erekola/markdown-parity-check) is a separate tool for a different question. This validator reads the structure of `llms.txt` and the home page's discovery links. The parity check compares the main content of one page's HTML and Markdown versions and reports changed text, numbers, links or blocks. It requires Node.js 22 or newer:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "turva-llms-txt-validator",
3
- "version": "0.4.1",
3
+ "version": "0.4.2",
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": "./dist/src/index.js",