@anubhav-choubey/web-for-agents 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +74 -515
- package/dist/bin.js +4 -4
- package/dist/chunk-CGIIB6AF.js +127 -0
- package/dist/{chunk-AZG5MLNG.js → chunk-F7PN5R25.js} +2 -0
- package/dist/chunk-LCVPXUFN.js +245 -0
- package/dist/{chunk-IF32TPGR.js → chunk-P6JSPAA2.js} +169 -124
- package/dist/{chunk-5TNG4QMP.js → chunk-PGZH33B5.js} +20 -2
- package/dist/chunk-PHTV5NAY.js +44 -0
- package/dist/{chunk-ZYMNO5AO.js → chunk-PSNLS4GL.js} +93 -57
- package/dist/{chunk-HONWT6GK.js → chunk-SMFRAZAA.js} +66 -34
- package/dist/cli.js +4 -4
- package/dist/{happyDom-XVDHYJDM.js → happyDom-WTYQ5UAC.js} +3 -3
- package/dist/index.js +41 -235
- package/dist/overlay.js +7 -32
- package/dist/{renderChild-Y2RFSYY4.js → renderChild-H6TR4SGV.js} +27 -4
- package/dist/vite.d.ts +88 -2
- package/dist/vite.js +628 -146
- package/package.json +1 -1
- package/dist/chunk-CKXHTGGT.js +0 -25
- package/dist/chunk-LCVI2PRP.js +0 -13
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://github.com/LazyyVenom/Web-For-Agents/actions/workflows/ci.yml)
|
|
4
4
|
|
|
5
|
-
SEO and LLM-readiness helpers for React + Vite. At runtime it manages `<head>` metadata (title, description, canonical, Open Graph, Twitter) and emits typed JSON-LD
|
|
5
|
+
SEO and LLM-readiness helpers for React + Vite. At runtime it manages `<head>` metadata (title, description, canonical, Open Graph, Twitter) and emits typed JSON-LD. At build time a Vite plugin prerenders every route to static HTML and writes a markdown version of each page, `llms.txt`, `llms-full.txt`, an `agents.json` map of the actions agents can take, a `sitemap.xml`, and a `robots.txt` with an explicit AI-crawler policy, so you decide which bots may train on your content and which may use it for search.
|
|
6
6
|
|
|
7
7
|
## Install
|
|
8
8
|
|
|
@@ -10,31 +10,10 @@ SEO and LLM-readiness helpers for React + Vite. At runtime it manages `<head>` m
|
|
|
10
10
|
npm i @anubhav-choubey/web-for-agents
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
-
Requires Node 20.19 or later; Node 22.13 or later is recommended for builds, because prerendering is better isolated there (see [Security](
|
|
13
|
+
Requires Node 20.19 or later; Node 22.13 or later is recommended for builds, because prerendering is better isolated there (see [Security](https://github.com/LazyyVenom/Web-For-Agents/blob/master/docs/security.md)). The runtime needs `react >=18` and `react-dom >=18`, and the Vite plugin needs `vite >=5` (tested on Vite 5 to 8). All three are optional peers, so the CLI works without them.
|
|
14
14
|
|
|
15
15
|
## Quick start
|
|
16
16
|
|
|
17
|
-
```tsx
|
|
18
|
-
import { SeoProvider, Seo, JsonLd, schema } from "@anubhav-choubey/web-for-agents";
|
|
19
|
-
|
|
20
|
-
export function Root({ children }: { children: React.ReactNode }) {
|
|
21
|
-
return (
|
|
22
|
-
<SeoProvider defaults={{ siteName: "My Site", siteUrl: "https://example.com", titleTemplate: "%s | My Site" }}>
|
|
23
|
-
{children}
|
|
24
|
-
</SeoProvider>
|
|
25
|
-
);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export function Post() {
|
|
29
|
-
return (
|
|
30
|
-
<>
|
|
31
|
-
<Seo title="Hello world" description="My first post" canonical="/blog/hello" type="article" />
|
|
32
|
-
<JsonLd data={schema.article({ headline: "Hello world", datePublished: "2026-10-28" })} />
|
|
33
|
-
</>
|
|
34
|
-
);
|
|
35
|
-
}
|
|
36
|
-
```
|
|
37
|
-
|
|
38
17
|
```ts
|
|
39
18
|
// vite.config.ts
|
|
40
19
|
import { defineConfig } from "vite";
|
|
@@ -53,181 +32,49 @@ export default defineConfig({
|
|
|
53
32
|
});
|
|
54
33
|
```
|
|
55
34
|
|
|
56
|
-
`webForAgents()` returns an array of plugins (the build plugin and the dev-only overlay); put it in `plugins` as shown, and Vite flattens it.
|
|
57
|
-
|
|
58
|
-
`robots.ai` is required: the plugin throws if you do not state a policy for both `training` and `search`. Files you place in `public/` (for example your own `robots.txt`) are never overwritten; the plugin logs a warning and skips generation.
|
|
59
|
-
|
|
60
|
-
Use one `SeoProvider` per document. It owns the shared defaults and the base head tags; nested or multiple providers will overwrite each other.
|
|
61
|
-
|
|
62
|
-
## Prerendering
|
|
63
|
-
|
|
64
|
-
After `vite build`, the plugin serves your `dist` locally, starts at `/` plus any `routes`, follows same-origin links (crawl), and writes one file per route:
|
|
65
|
-
|
|
66
|
-
- `dist/<route>/index.html`: the rendered page with its `<head>` (title, Open Graph, JSON-LD) and a `data-wfa-prerendered` attribute on `<html>`. Your client bundle is kept, so the app still boots.
|
|
67
|
-
- `dist/<route>.md`: the markdown version (`dist/index.md` for `/`).
|
|
68
|
-
- `dist/200.html`: your original SPA shell (the built `index.html` before `/` is prerendered over it), for host fallbacks. See [SPA fallback](#spa-fallback).
|
|
69
|
-
- `llms.txt`, `llms-full.txt`, `agents.json`, `sitemap.xml`, `robots.txt`.
|
|
70
|
-
|
|
71
|
-
Non-page links (other origins, `mailto:`, files with an extension, `exclude`d routes) are not followed. A path whose last segment contains a dot (`/v1.2`, `/files/report.final`) is treated as a file and not crawled; list such pages in `routes` to prerender them. Routes marked `<Seo noindex>` are written but left out of the sitemap and `llms.txt`. Nothing is written until every route has rendered: if any route fails, the build rejects with one error listing each failing route and renderer.
|
|
72
|
-
|
|
73
|
-
The default renderer is [happy-dom](https://github.com/capricorn86/happy-dom), which needs no browser. Pages that need a real browser can use your installed Chrome or Edge:
|
|
74
|
-
|
|
75
|
-
```sh
|
|
76
|
-
npm i -D playwright-core
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
No browser is ever downloaded; the plugin launches the Chrome/Edge already on your machine (or `prerender.executablePath`). Use `renderer: "chrome"` for everything, `routeRenderers` for specific routes, or `fallback: "chrome"` to retry only routes that fail under happy-dom.
|
|
80
|
-
|
|
81
|
-
```ts
|
|
82
|
-
webForAgents({
|
|
83
|
-
siteUrl: "https://example.com",
|
|
84
|
-
robots: { ai: { training: "block", search: "allow" } },
|
|
85
|
-
prerender: {
|
|
86
|
-
fallback: "chrome",
|
|
87
|
-
routeRenderers: [{ match: "/charts/*", renderer: "chrome" }],
|
|
88
|
-
},
|
|
89
|
-
});
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
happy-dom renders in a separate, locked-down Node.js process, and pages can only load your build's own files unless you allow other origins with `prerender.allowOrigins`. See [Security](#security) for what that means for your pages.
|
|
93
|
-
|
|
94
|
-
A synchronous infinite loop in a page cannot be interrupted inside happy-dom. The render process is killed `prerender.timeoutMs` plus 10 seconds after the route started, which fails that route and any others rendering in the same process. Render such pages with the Chrome renderer (`routeRenderers` or `renderer: "chrome"`), which enforces the timeout per page.
|
|
95
|
-
|
|
96
|
-
Pressing Ctrl-C during prerendering reports `prerender interrupted` rather than a list of failed routes.
|
|
97
|
-
|
|
98
|
-
happy-dom 21 fails to load a module when a regex literal follows a character outside its narrow list of punctuation, such as `return`, `!`, `typeof`, `-`, whitespace or `)`. This is common in minified code, e.g. `return/^[a-z]+:/i.test(e)`. When any route may render under happy-dom, the local prerender server works around it by wrapping each regex literal in the JS it serves in parentheses. With mixed renderers, Chrome gets the same rewritten JS, which is harmless. Your `dist` files are not changed.
|
|
99
|
-
|
|
100
|
-
Set `prerender: false` to skip all of this; `markdown`, `llms` and `agentMap` then turn off too, with a warning if you set them.
|
|
101
|
-
|
|
102
|
-
### Readiness
|
|
103
|
-
|
|
104
|
-
A route is captured once the page has loaded, no `fetch`/XHR is in flight, and the DOM has been quiet for a moment (`prerender.idleMs`). If you load data some other way (timers, a custom client, a Suspense boundary), tell the renderer to wait:
|
|
105
|
-
|
|
106
35
|
```tsx
|
|
107
|
-
import {
|
|
108
|
-
|
|
109
|
-
useEffect(() => {
|
|
110
|
-
const release = holdPrerender();
|
|
111
|
-
load().then(setData).finally(release);
|
|
112
|
-
}, []);
|
|
113
|
-
```
|
|
114
|
-
|
|
115
|
-
`holdPrerender()` returns an idempotent release function. It sets `window.__WFA_READY__`, which only the prerenderer reads, so it is harmless in a normal browser session. A route that is not ready after `prerender.timeoutMs` (default 15000) fails the build.
|
|
116
|
-
|
|
117
|
-
Pages that never go idle (polling requests, a long-lived `fetch` stream, a DOM that keeps changing) time out the same way. Hold and release around the work the page actually needs, so the renderer captures it once that is done, or raise `prerender.timeoutMs` for slow routes.
|
|
118
|
-
|
|
119
|
-
### SPA fallback
|
|
120
|
-
|
|
121
|
-
After prerendering, `dist/index.html` is the rendered `/` page. If your host serves `index.html` for unknown URLs, deep links that were not prerendered would get the home page's HTML: it flashes before the app boots, and `hydrateApp` would hydrate the wrong markup. Point the fallback at `dist/200.html` instead, which is the untouched shell. If `public/200.html` exists, it is used as is and not generated.
|
|
122
|
-
|
|
123
|
-
Netlify, `public/_redirects`:
|
|
124
|
-
|
|
125
|
-
```
|
|
126
|
-
/* /200.html 200
|
|
127
|
-
```
|
|
128
|
-
|
|
129
|
-
Vercel, `vercel.json`:
|
|
130
|
-
|
|
131
|
-
```json
|
|
132
|
-
{ "rewrites": [{ "source": "/(.*)", "destination": "/200.html" }] }
|
|
133
|
-
```
|
|
134
|
-
|
|
135
|
-
Vercel serves existing files before applying rewrites, so prerendered pages are still served directly.
|
|
136
|
-
|
|
137
|
-
Cloudflare Pages: when the output has no top-level `404.html` it already serves the SPA shell, but that is `index.html`. Add `public/_redirects` with the same rule as Netlify:
|
|
138
|
-
|
|
139
|
-
```
|
|
140
|
-
/* /200.html 200
|
|
141
|
-
```
|
|
142
|
-
|
|
143
|
-
nginx (`$uri/` serves `<route>/index.html` through the default `index` directive):
|
|
36
|
+
import { SeoProvider, Seo, JsonLd, schema } from "@anubhav-choubey/web-for-agents";
|
|
144
37
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
38
|
+
export function Root({ children }: { children: React.ReactNode }) {
|
|
39
|
+
return (
|
|
40
|
+
<SeoProvider defaults={{ siteName: "My Site", siteUrl: "https://example.com", titleTemplate: "%s | My Site" }}>
|
|
41
|
+
{children}
|
|
42
|
+
</SeoProvider>
|
|
43
|
+
);
|
|
148
44
|
}
|
|
149
|
-
```
|
|
150
45
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
hydrateApp(document.getElementById("root")!, <App />);
|
|
161
|
-
```
|
|
162
|
-
|
|
163
|
-
## Content for agents
|
|
164
|
-
|
|
165
|
-
The markdown for a page comes from its `<main>` element (change it with `markdown.contentSelector`). Mark a different region explicitly with `ContentRegion`, and keep boilerplate such as newsletter forms out with `ExcludeFromContent`:
|
|
166
|
-
|
|
167
|
-
```tsx
|
|
168
|
-
import { ContentRegion, ExcludeFromContent } from "@anubhav-choubey/web-for-agents";
|
|
169
|
-
|
|
170
|
-
<ContentRegion as="article">
|
|
171
|
-
<h1>Pricing</h1>
|
|
172
|
-
<ExcludeFromContent>Subscribe to our newsletter</ExcludeFromContent>
|
|
173
|
-
</ContentRegion>
|
|
46
|
+
export function Post() {
|
|
47
|
+
return (
|
|
48
|
+
<>
|
|
49
|
+
<Seo title="Hello world" description="My first post" canonical="/blog/hello" type="article" />
|
|
50
|
+
<JsonLd data={schema.article({ headline: "Hello world", datePublished: "2026-10-28" })} />
|
|
51
|
+
</>
|
|
52
|
+
);
|
|
53
|
+
}
|
|
174
54
|
```
|
|
175
55
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
## Markdown URLs
|
|
179
|
-
|
|
180
|
-
Every prerendered page gets a markdown twin: `/about` is served as `/about.md` and `/` as `/index.md`. The HTML advertises it with `<link rel="alternate" type="text/markdown" href="https://example.com/about.md">`. Links in the markdown are absolute.
|
|
56
|
+
`robots.ai` is required: the plugin throws if you do not state a policy for both `training` and `search`. Use one `SeoProvider` per document. More in [Head tags and JSON-LD](https://github.com/LazyyVenom/Web-For-Agents/blob/master/docs/seo.md).
|
|
181
57
|
|
|
182
|
-
##
|
|
58
|
+
## What the build produces
|
|
183
59
|
|
|
184
|
-
|
|
60
|
+
After `vite build`, the plugin crawls from `/` plus `routes` and writes:
|
|
185
61
|
|
|
186
|
-
|
|
62
|
+
| File | Contents |
|
|
63
|
+
| --- | --- |
|
|
64
|
+
| `dist/<route>/index.html` | Prerendered HTML with the full `<head>`; your client bundle still boots. |
|
|
65
|
+
| `dist/<route>.md` | Markdown version of the page (`dist/index.md` for `/`). |
|
|
66
|
+
| `dist/200.html` | The untouched SPA shell, for host fallbacks. |
|
|
67
|
+
| `llms.txt`, `llms-full.txt` | Page index linking to the `.md` files, and all pages' markdown in one file. |
|
|
68
|
+
| `agents.json` | Every page and the agent actions on it. |
|
|
69
|
+
| `sitemap.xml`, `robots.txt` | Sitemap of discovered routes, and your AI-crawler policy. |
|
|
187
70
|
|
|
188
|
-
|
|
71
|
+
Already have your own prerender script? Skip the plugin's prerender and run `npx web-for-agents generate dist --site-url https://example.com --ai-training block --ai-search allow` after it. It adds the same `.md`, `llms.txt`, `robots.txt`, `sitemap.xml` and `agents.json` files to your HTML. See [Using your own prerender](https://github.com/LazyyVenom/Web-For-Agents/blob/master/docs/prerendering.md#using-your-own-prerender).
|
|
189
72
|
|
|
190
|
-
|
|
73
|
+
Files you place in `public/` are never overwritten. Content regions, `holdPrerender`, `hydrateApp`, host fallback configs and memory limits: see [Prerendering](https://github.com/LazyyVenom/Web-For-Agents/blob/master/docs/prerendering.md).
|
|
191
74
|
|
|
192
75
|
## Agent actions
|
|
193
76
|
|
|
194
|
-
Mark
|
|
195
|
-
|
|
196
|
-
### `AgentForm`
|
|
197
|
-
|
|
198
|
-
A `<form>` that is also an action. Without a `handler`, the input schema is derived from the form's fields and the agent's input is filled in and submitted through the form itself (the form fallback), so your existing `onSubmit` and validation run as for a user:
|
|
199
|
-
|
|
200
|
-
```tsx
|
|
201
|
-
import { AgentForm, agentParam } from "@anubhav-choubey/web-for-agents";
|
|
202
|
-
|
|
203
|
-
<AgentForm agent={{ name: "contact", description: "Send us a message" }} onSubmit={send}>
|
|
204
|
-
<input type="email" name="email" required {...agentParam("Your email address")} />
|
|
205
|
-
<select name="topic">
|
|
206
|
-
<option value="sales">Sales</option>
|
|
207
|
-
<option value="support">Support</option>
|
|
208
|
-
</select>
|
|
209
|
-
<button type="submit">Send</button>
|
|
210
|
-
</AgentForm>
|
|
211
|
-
```
|
|
212
|
-
|
|
213
|
-
The derived schema uses each field's `name`, type (`email`, `url`, `number`, `checkbox`, ...), `required`, `min`/`max`, `minlength`/`maxlength` and `pattern`; `<select>` options and radio groups become an `enum`. Some fields are never exposed: they do not appear in the schema or in `agents.json`, and an agent cannot fill them, even through another field with the same name. These are:
|
|
214
|
-
|
|
215
|
-
- disabled and `readonly` fields, and fields inside a `<fieldset disabled>` (except in its first `<legend>`);
|
|
216
|
-
- fields without a `name`;
|
|
217
|
-
- hidden, password and file inputs, and buttons;
|
|
218
|
-
- fields hidden by markup: inside a `hidden`, `inert` or `aria-hidden="true"` element, a closed `<dialog>`, or the content of a closed `<details>`.
|
|
219
|
-
|
|
220
|
-
Visibility is read from markup only, not CSS. One exception: a checkbox or radio with its own `hidden` attribute is still exposed when it has a visible, enabled `<label>` (the styled-label pattern). Options that are disabled, `hidden` or inside a disabled `<optgroup>` are left out of the `enum`.
|
|
221
|
-
|
|
222
|
-
A form action without a `handler` has exactly one schema: the one derived from the form. It is what the tool advertises and what the input is checked against, using the form's fields at the moment the action runs. If you also pass `inputSchema`, it is ignored and a warning is logged once; add a `handler` if you want your own schema.
|
|
223
|
-
|
|
224
|
-
Fields are re-read after every value the agent sets, so a field revealed by an earlier one (a "Company" box behind a "Business account" checkbox) is filled in the same call, and its value is checked when it appears. A key that names no field of the form is rejected before anything is written; a key whose field stays hidden is rejected without submitting. The advertised WebMCP schema is not rebuilt on every render: after you reveal fields (open a modal, switch a tab, move to a wizard step), call `refreshAgentActions({ force: true })`.
|
|
225
|
-
|
|
226
|
-
A form action reports `{ status: "submitted", url, messages }`, where `messages` is the text of any `role="alert"`, `role="status"` or `aria-live` element in the form shortly after submit, or `{ status: "invalid", errors }` when the input or the browser's form validation fails. The form is submitted through its default button (its first submit or image button, including one outside the form linked by `form="..."`), so that button's `name`/`value`, `formaction` and `formmethod` apply. If that button is disabled or hidden by markup, or the form is hidden or inside a `<fieldset disabled>`, the agent gets `{ status: "error" }` and nothing is filled or submitted.
|
|
227
|
-
|
|
228
|
-
### `useAgentAction`
|
|
229
|
-
|
|
230
|
-
For actions that are not a form, or when you want to run code instead of submitting one. Spread `hintProps` on the element and attach `ref`:
|
|
77
|
+
Mark what an agent can do on a page. Each action is listed in `agents.json` and, in browsers that support it, registered as a [WebMCP](https://webmachinelearning.github.io/webmcp/) tool.
|
|
231
78
|
|
|
232
79
|
```tsx
|
|
233
80
|
import { useAgentAction } from "@anubhav-choubey/web-for-agents";
|
|
@@ -247,363 +94,75 @@ function DeleteAccount() {
|
|
|
247
94
|
}
|
|
248
95
|
```
|
|
249
96
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
`hintProps` puts `data-agent-action`, `data-agent-description`, `aria-description` and, for destructive actions, `data-agent-destructive="true"` on the element, so the hints are also in the prerendered HTML. `agentParam(description)` adds `data-agent-param` to a form field; it is used as that field's description in the schema (otherwise its `<label>`, `aria-label` or `placeholder`).
|
|
253
|
-
|
|
254
|
-
`listAgentActions()` and `executeAgentAction(name, input)` give you the same registry directly. `refreshAgentActions()` re-checks the registry; pass `{ force: true }` to also re-describe actions that are already registered with WebMCP and re-register any whose schema changed.
|
|
255
|
-
|
|
256
|
-
### Names and duplicates
|
|
257
|
-
|
|
258
|
-
Names must be 1 to 128 characters of `A-Z a-z 0-9 _ - .`; an invalid name throws. Names are per page: if two mounted components use the same name, only the first mounted one is exposed (in WebMCP and in `agents.json`) and a warning is logged. Give each a unique name, or use one action with an id input.
|
|
259
|
-
|
|
260
|
-
### Destructive actions
|
|
261
|
-
|
|
262
|
-
`destructive: true` asks the user before the action runs, with `window.confirm` by default (pass `confirm` for your own dialog, `confirmMessage` for the text). The checks that would make the action fail (invalid input, a disabled or hidden submit button, a hidden form or element, keys that name no field) run first, so the user is never asked to approve an action that cannot run.
|
|
263
|
-
|
|
264
|
-
The default text names the action, states how many fields the agent provided, and lists every field on its own line as `path: value` (nested values as `address.zip` or `items[0].id`). Fields declared in the schema come first, in schema order, and every declared field the agent provided is shown; for a form action, every key the agent sends is listed. Each name and value is cut to `max(24, 1000 / declared fields)` characters with a "…(truncated)" marker. Keys outside the schema follow, capped at about 1,000 characters in total, with a note of how many were omitted. Unusual keys and all values are JSON-encoded, and bidi, line-separator, invisible and control characters are shown as `\uXXXX` escapes, so the input cannot reorder, hide or add lines.
|
|
265
|
-
|
|
266
|
-
If the user declines, the agent gets `{ status: "cancelled", reason: "user-declined" }` and nothing runs. It also sets the WebMCP `consequentialHint`; `readOnly: true` sets `readOnlyHint`.
|
|
267
|
-
|
|
268
|
-
### WebMCP
|
|
269
|
-
|
|
270
|
-
WebMCP is feature-detected: when `document.modelContext` (or the older `navigator.modelContext`) exists, each exposed action is registered with `registerTool`; otherwise nothing is registered and nothing breaks. Nothing branches on the user agent. Tools are unregistered when their component unmounts, so the tool list follows what is on screen. The advertised `inputSchema` is captured when the tool is registered: a tool is registered again when its name, title, description, flags, `inputSchema` or handler presence changes, but not when a form's fields change. Execution never relies on that snapshot; it always validates against the current schema (for a form, its current fields). A failed registration is logged as a warning, does not affect the page, and is retried the next time the action list is updated. If `modelContext` appears after the page loads (for example, injected by an extension), actions are registered on the next mount, unmount or `refreshAgentActions()` call.
|
|
271
|
-
|
|
272
|
-
### agents.json
|
|
273
|
-
|
|
274
|
-
After prerendering, the plugin writes `agents.json` with every listed page and the actions found on it, and links it from `llms.txt` in an `## Agents` section. Pages are sorted by path and actions by name; pages without actions are listed with `"actions": []`. Titles and descriptions are the same as in `llms.txt`:
|
|
275
|
-
|
|
276
|
-
```json
|
|
277
|
-
{
|
|
278
|
-
"version": 1,
|
|
279
|
-
"generator": "@anubhav-choubey/web-for-agents",
|
|
280
|
-
"site": { "url": "https://example.com", "name": "My Site" },
|
|
281
|
-
"llms": "https://example.com/llms.txt",
|
|
282
|
-
"webmcp": { "api": "document.modelContext", "spec": "https://webmachinelearning.github.io/webmcp/" },
|
|
283
|
-
"pages": [
|
|
284
|
-
{
|
|
285
|
-
"path": "/contact",
|
|
286
|
-
"url": "https://example.com/contact",
|
|
287
|
-
"title": "Contact",
|
|
288
|
-
"description": "Get in touch",
|
|
289
|
-
"markdown": "https://example.com/contact.md",
|
|
290
|
-
"actions": [
|
|
291
|
-
{
|
|
292
|
-
"name": "contact",
|
|
293
|
-
"description": "Send us a message",
|
|
294
|
-
"inputSchema": {
|
|
295
|
-
"type": "object",
|
|
296
|
-
"properties": {
|
|
297
|
-
"email": { "type": "string", "format": "email", "description": "Your email address" },
|
|
298
|
-
"topic": { "type": "string", "enum": ["sales", "support"], "description": "Options: sales (Sales), support (Support)" }
|
|
299
|
-
},
|
|
300
|
-
"additionalProperties": false,
|
|
301
|
-
"required": ["email"]
|
|
302
|
-
},
|
|
303
|
-
"destructive": false,
|
|
304
|
-
"readOnly": false,
|
|
305
|
-
"kind": "form"
|
|
306
|
-
}
|
|
307
|
-
]
|
|
308
|
-
}
|
|
309
|
-
]
|
|
310
|
-
}
|
|
311
|
-
```
|
|
312
|
-
|
|
313
|
-
`kind` is `"handler"`, `"form"` or `"click"`. `llms` is omitted when `llms: false`, and `markdown` when `markdown: false`.
|
|
314
|
-
|
|
315
|
-
Use `agentMap: { fileName: "ai.json" }` to change the file name (a plain `.json` name, no directories), or `agentMap: false` to turn the file and the `llms.txt` section off; WebMCP registration at runtime is unaffected. The CLI audit looks for the default `agents.json` unless you pass `--agent-map`. A `public/agents.json` of your own is never overwritten. The agent map needs prerender.
|
|
316
|
-
|
|
317
|
-
## Hooks and components
|
|
318
|
-
|
|
319
|
-
`<Seo {...props} />` is a component wrapper around `useSeo(props)`. Call the hook directly when you prefer hooks or need to compute props in a component:
|
|
97
|
+
`AgentForm` turns a form into an action; its schema comes from the fields, and the agent submits through your own `onSubmit`:
|
|
320
98
|
|
|
321
99
|
```tsx
|
|
322
|
-
import {
|
|
100
|
+
import { AgentForm, agentParam } from "@anubhav-choubey/web-for-agents";
|
|
323
101
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
102
|
+
<AgentForm agent={{ name: "contact", description: "Send us a message" }} onSubmit={send}>
|
|
103
|
+
<input type="email" name="email" required {...agentParam("Your email address")} />
|
|
104
|
+
<button type="submit">Send</button>
|
|
105
|
+
</AgentForm>
|
|
328
106
|
```
|
|
329
107
|
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
### Props
|
|
333
|
-
|
|
334
|
-
`SeoProvider` `defaults`:
|
|
335
|
-
|
|
336
|
-
| Field | Description |
|
|
337
|
-
| --- | --- |
|
|
338
|
-
| `siteName` | `og:site_name`, and the title when a page sets none. |
|
|
339
|
-
| `siteUrl` | Absolute site URL. Paths in `canonical` and `image` resolve against it; enables the default canonical. |
|
|
340
|
-
| `titleTemplate` | Title format, `%s` is replaced by the page title, e.g. `"%s \| My Site"`. |
|
|
341
|
-
| `description` | Default description. |
|
|
342
|
-
| `image` | Default Open Graph and Twitter image. |
|
|
343
|
-
| `twitterSite` | `twitter:site` handle. |
|
|
344
|
-
| `locale` | `og:locale`. |
|
|
345
|
-
|
|
346
|
-
`Seo` / `useSeo`:
|
|
347
|
-
|
|
348
|
-
| Prop | Description |
|
|
349
|
-
| --- | --- |
|
|
350
|
-
| `title` | Page title, formatted with `titleTemplate`. Also used for `og:title` and `twitter:title`. |
|
|
351
|
-
| `description` | Meta, Open Graph and Twitter description. |
|
|
352
|
-
| `canonical` | Absolute URL or path, or `false` for none. See [Canonical URL](#canonical-url). |
|
|
353
|
-
| `image` | Open Graph and Twitter image; a large Twitter card is used when set. |
|
|
354
|
-
| `type` | `og:type`: `"website"` (default), `"article"` or `"product"`. |
|
|
355
|
-
| `noindex` | Emits `robots: noindex, nofollow`; prerendered but left out of the sitemap and `llms.txt`. |
|
|
356
|
-
| `meta` | Extra `{ name, content }` or `{ property, content }` tags. |
|
|
357
|
-
|
|
358
|
-
### Canonical URL
|
|
359
|
-
|
|
360
|
-
`canonical` takes an absolute URL or a path. A path, with or without a leading slash, resolves under `defaults.siteUrl`, including its path, the same way sitemap URLs do: with `siteUrl: "https://example.com/sub"`, `canonical="/about"` and `canonical="about"` both become `https://example.com/sub/about`.
|
|
361
|
-
|
|
362
|
-
When no `<Seo>` passes one and `SeoProvider` has `defaults.siteUrl`, the head defaults the canonical link and `og:url` to the page's public URL. The current pathname comes from `document.location`, has the query and hash removed and the `siteUrl` path stripped, and loses any trailing slash except at the root. So `/sub/about/` gives `https://example.com/sub/about` and `/sub/` gives `https://example.com/sub/`. Prerendered pages carry this canonical in their HTML.
|
|
363
|
-
|
|
364
|
-
The pathname is read in effects, never during render, and the head is rebuilt on `popstate`, on the Navigation API's `navigatesuccess` where it exists, and otherwise on `history.pushState` and `replaceState`. These listeners are installed when the first `<Seo>` mounts and removed when the last one unmounts, so a layout `<Seo>` stays correct across client-side navigation.
|
|
365
|
-
|
|
366
|
-
The default is the lowest-priority layer, so an explicit `canonical` on any `<Seo>` in the tree wins over it, including when a deeper `<Seo>` omits `canonical`. Among explicit values the deepest `<Seo>` wins. `canonical={false}` emits neither the canonical link nor `og:url`, and a deeper `false` removes an ancestor's explicit canonical. Without `siteUrl`, no canonical is emitted.
|
|
367
|
-
|
|
368
|
-
## Schema builders
|
|
369
|
-
|
|
370
|
-
`schema` returns typed JSON-LD nodes for `<JsonLd data={...} />`:
|
|
371
|
-
|
|
372
|
-
- `schema.article`
|
|
373
|
-
- `schema.product`
|
|
374
|
-
- `schema.faqPage`
|
|
375
|
-
- `schema.breadcrumbList`
|
|
376
|
-
- `schema.organization`
|
|
377
|
-
- `schema.webSite`
|
|
378
|
-
|
|
379
|
-
## Plugin options
|
|
380
|
-
|
|
381
|
-
| Option | Type | Description |
|
|
382
|
-
| --- | --- | --- |
|
|
383
|
-
| `siteUrl` | `string` | Required. Absolute http(s) URL of the deployed site. |
|
|
384
|
-
| `routes` | `string[]` | Extra routes to prerender, for pages no link reaches. `/` is always included. |
|
|
385
|
-
| `exclude` | `Array<string \| RegExp>` | Routes to leave out. A string is an exact path, or a prefix when it ends in `/*`. A leading slash is optional. |
|
|
386
|
-
| `robots.ai.training` | `"allow" \| "block"` | Required. Policy for AI training crawlers. |
|
|
387
|
-
| `robots.ai.search` | `"allow" \| "block"` | Required. Policy for AI search and user-agent fetchers. |
|
|
388
|
-
| `robots.ai.overrides` | `Record<string, "allow" \| "block">` | Per-bot override, matched case-insensitively. |
|
|
389
|
-
| `robots.disallow` | `string[]` | Extra `Disallow` paths for all user agents. |
|
|
390
|
-
| `sitemap` | `boolean` | Generate `sitemap.xml` and the `Sitemap:` line. Default `true`. |
|
|
391
|
-
| `prerender` | `object \| false` | Prerender routes after the build. `false` disables prerender, markdown, llms and the agent map. |
|
|
392
|
-
| `prerender.crawl` | `boolean` | Follow same-origin links from `/` and `routes`. Default `true`. |
|
|
393
|
-
| `prerender.renderer` | `"happy-dom" \| "chrome" \| "msedge"` | Default renderer. Default `"happy-dom"`. |
|
|
394
|
-
| `prerender.fallback` | `"chrome" \| "msedge" \| false` | Retry routes that fail in this browser. Default `false`. |
|
|
395
|
-
| `prerender.routeRenderers` | `Array<{ match: string \| RegExp; renderer }>` | Per-route renderer; `match` uses `exclude` semantics, first match wins. Default `[]`. |
|
|
396
|
-
| `prerender.executablePath` | `string` | Browser binary to launch instead of the installed Chrome/Edge channel. |
|
|
397
|
-
| `prerender.timeoutMs` | `number` | Per-route readiness timeout. Default `15000`. |
|
|
398
|
-
| `prerender.concurrency` | `number` | Routes rendered at once. Default `8` when only happy-dom renders, `4` when `renderer` or any `routeRenderers` entry is `"chrome"` or `"msedge"` (`fallback` does not count). |
|
|
399
|
-
| `prerender.maxRoutes` | `number` | Stop discovering after this many routes (a warning is logged). Default `500`. |
|
|
400
|
-
| `prerender.allowOrigins` | `string[]` | http(s) origins (`"https://cdn.example.com"`) pages may load from while prerendering. Every other request outside your build is blocked and fails like a network error. Default `[]`. |
|
|
401
|
-
| `prerender.idleMs` | `number` | How long, in ms, a page must be quiet (no DOM changes, no requests in flight) to count as rendered. Default `50` for happy-dom once every timer and request has settled, else `200`; always `200` for Chrome. |
|
|
402
|
-
| `prerender.isolation` | `{ codeGeneration?: boolean }` | The happy-dom render process. `codeGeneration: true` lets code build functions from strings with Node's own `Function`, which also lets page code escape happy-dom; prefer rendering such routes with Chrome. The page's own `eval` and `new Function` work either way. Default `{ codeGeneration: false }`. |
|
|
403
|
-
| `markdown` | `{ contentSelector?: string } \| false` | Write `.md` files. `false` disables them and `llms-full.txt`. Default `{ contentSelector: "main" }`. |
|
|
404
|
-
| `llms` | `{ title?: string; description?: string } \| false` | Write `llms.txt` and `llms-full.txt`. Default on. |
|
|
405
|
-
| `agentMap` | `boolean \| { fileName?: string }` | Write `agents.json` and link it from `llms.txt`. Needs prerender. Default `{ fileName: "agents.json" }`. |
|
|
406
|
-
| `overlay` | `boolean \| { shortcut?: string; contentSelector?: string }` | Dev overlay, injected by `vite` (serve) only, under any `base` (e.g. `"/sub/"`), and skipped when Vitest runs. `false` disables it. Default on, shortcut `"alt+shift+a"`, `contentSelector` from `markdown.contentSelector` (else `"main"`). |
|
|
407
|
-
|
|
408
|
-
## AI crawlers
|
|
409
|
-
|
|
410
|
-
Training bots (`robots.ai.training`):
|
|
411
|
-
|
|
412
|
-
- `GPTBot`
|
|
413
|
-
- `ClaudeBot`
|
|
414
|
-
- `CCBot`
|
|
415
|
-
- `Google-Extended`
|
|
416
|
-
- `Applebot-Extended`
|
|
417
|
-
- `Meta-ExternalAgent`
|
|
418
|
-
- `Bytespider`
|
|
419
|
-
- `cohere-training-data-crawler`
|
|
420
|
-
|
|
421
|
-
Search bots (`robots.ai.search`):
|
|
422
|
-
|
|
423
|
-
- `OAI-SearchBot`
|
|
424
|
-
- `ChatGPT-User`
|
|
425
|
-
- `Claude-SearchBot`
|
|
426
|
-
- `Claude-User`
|
|
427
|
-
- `PerplexityBot`
|
|
428
|
-
- `Perplexity-User`
|
|
429
|
-
|
|
430
|
-
The list is exported as `AI_BOTS` from `@anubhav-choubey/web-for-agents/vite`.
|
|
108
|
+
Agents can never reach hidden, password, file, disabled or `readonly` fields, or fields hidden by markup. Full API, confirm dialog and field rules: [Agent actions](https://github.com/LazyyVenom/Web-For-Agents/blob/master/docs/agent-actions.md).
|
|
431
109
|
|
|
432
110
|
## CLI
|
|
433
111
|
|
|
434
|
-
Audit any URL for AI-agent readiness
|
|
112
|
+
Audit any URL for AI-agent readiness, with nothing installed in the target site:
|
|
435
113
|
|
|
436
114
|
```sh
|
|
437
|
-
npx @anubhav-choubey/web-for-agents audit example.com
|
|
438
|
-
npx @anubhav-choubey/web-for-agents audit https://example.com/docs --json --min-score 80
|
|
115
|
+
npx @anubhav-choubey/web-for-agents audit https://example.com --min-score 80
|
|
439
116
|
```
|
|
440
117
|
|
|
441
|
-
Flags:
|
|
442
|
-
|
|
443
|
-
- `--json` prints the report as one JSON document on stdout. Page data is kept as the site sent it (see [Security](#cli-audit)).
|
|
444
|
-
- `--min-score <0-100>` exits with code 1 when the score is lower.
|
|
445
|
-
- `--timeout <ms>` sets the per-request timeout (default 10000).
|
|
446
|
-
- `--agent-map <path>` sets where the agent map lives. A file name (`map.json`) is looked for in the same places as `agents.json`. A path starting with `/` or a full URL is fetched as given.
|
|
447
|
-
- `--no-private-redirects` refuses redirects from a public host to a private, loopback or link-local address. By default they are followed with a warning.
|
|
448
|
-
- `-h`, `--help` prints usage.
|
|
449
|
-
|
|
450
|
-
A URL without a scheme gets `http://` for `localhost`, `127.0.0.1` and `[::1]`, and `https://` for every other host.
|
|
451
|
-
|
|
452
|
-
`robots.txt` is read from the site root. The sitemap URL comes from its `Sitemap:` line, or `/sitemap.xml` if there is none. `llms.txt` and `agents.json` are looked for at the root and in each parent directory of the page and of its markdown alternate link. For example, auditing `https://example.com/sub/` also tries `/sub/llms.txt` and `/sub/agents.json`, which covers sites deployed under a subpath. If more than one copy exists, the deepest one is used. A missing file's message lists every URL tried.
|
|
453
|
-
|
|
454
|
-
Checks and weights (total 100):
|
|
455
|
-
|
|
456
|
-
| Check | Weight |
|
|
457
|
-
| --- | --- |
|
|
458
|
-
| Content without JavaScript | 25 |
|
|
459
|
-
| JSON-LD | 12 |
|
|
460
|
-
| robots.txt | 10 |
|
|
461
|
-
| Title | 8 |
|
|
462
|
-
| Meta description | 8 |
|
|
463
|
-
| Sitemap | 8 |
|
|
464
|
-
| Open Graph | 7 |
|
|
465
|
-
| llms.txt | 7 |
|
|
466
|
-
| Markdown copy | 6 |
|
|
467
|
-
| Canonical URL | 5 |
|
|
468
|
-
| Agent map | 4 |
|
|
469
|
-
|
|
470
|
-
A passing check earns its full weight, a warning half, a failure none. Each non-passing check prints a `Fix:` line.
|
|
471
|
-
|
|
472
|
-
Exit codes: `0` ok, `1` score below `--min-score`, `2` usage error or fatal error (unreachable page, non-2xx status, not HTML, timeout).
|
|
473
|
-
|
|
474
|
-
The programmatic API is available from `@anubhav-choubey/web-for-agents/cli` (`audit`, `runCli`, `formatText`, and the `AuditOptions`, `AuditReport`, `CheckResult`, `CheckId` and `CheckStatus` types).
|
|
475
|
-
|
|
476
|
-
### Auditing a local preview
|
|
477
|
-
|
|
478
|
-
You can audit a build locally with `vite preview`:
|
|
479
|
-
|
|
480
|
-
```sh
|
|
481
|
-
npx vite build && npx vite preview --port 4173
|
|
482
|
-
npx @anubhav-choubey/web-for-agents audit localhost:4173
|
|
483
|
-
```
|
|
484
|
-
|
|
485
|
-
The build writes absolute URLs from `siteUrl` into the canonical link, the sitemap, the markdown alternate link and the `Sitemap:` line in `robots.txt`. If `siteUrl` is your production origin, those URLs point away from the preview. The audit does not fetch them, so a local run never calls production. Instead it warns and names the other origin. For example:
|
|
486
|
-
|
|
487
|
-
```
|
|
488
|
-
WARN Canonical URL 2.5/5 Canonical https://example.com/ points to https://example.com (different origin than the audited page)
|
|
489
|
-
Fix: Audit the deployed site, or build with siteUrl set to http://localhost:4173 to audit this preview.
|
|
490
|
-
```
|
|
491
|
-
|
|
492
|
-
To get a clean local score, build with `siteUrl` set to the preview origin, for example `siteUrl: process.env.SITE_URL ?? "https://example.com"` with `SITE_URL=http://localhost:4173 npx vite build`. Otherwise, audit the deployed site.
|
|
493
|
-
|
|
494
|
-
## Security
|
|
495
|
-
|
|
496
|
-
### Prerendering
|
|
497
|
-
|
|
498
|
-
Prerendering runs your app's JavaScript at build time, including anything it loads. happy-dom is **not a sandbox**: code in a happy-dom page can, in principle, escape to the Node.js process that runs it. Several layers sit between a prerendered page and your build machine:
|
|
499
|
-
|
|
500
|
-
- **No outside network during the build.** Pages can only load your build's own files from the local prerender server on 127.0.0.1. Requests to any other origin (scripts, stylesheets, `fetch`/XHR, frames, WebSockets) are blocked and fail like a network error. A blocked request is not fatal by itself; if the page needs it to render, the build fails and names the blocked URL. Allow its origin with `prerender: { allowOrigins: ["https://api.example.com"] }`, and only allow origins you trust: their code runs during your build.
|
|
501
|
-
- **Isolated renderer.** happy-dom renders in a separate Node.js process with a scrubbed environment: only `PATH`, `HOME`, the temp directory variables and `NODE_ENV` (plus the system variables Windows needs for sockets). CI tokens, API keys and other environment variables are not visible to page code.
|
|
502
|
-
- **No code generation from strings.** The render process cannot build functions from strings with Node's own `Function`, the usual way out of happy-dom. The page's own `eval` and `new Function` keep working. If a route fails because something needed Node-level code generation, the error says so; render that route with Chrome (`prerender.routeRenderers` or `prerender.fallback`), or set `prerender: { isolation: { codeGeneration: true } }`. That option lets page code escape happy-dom again, so use it only for code you trust.
|
|
503
|
-
- **Contained network, even after an escape.** Inside the render process, TCP, TLS, HTTP, DNS, `fetch` and WebSockets can only reach the prerender server and your `allowOrigins`; UDP and listening for connections are blocked. These wrappers are locked so code cannot remove them.
|
|
504
|
-
- **Permission model.** On Node.js 22.13+ (or 23.5+), the render process also runs under Node's permission model: it can read only the renderer's own code and `node_modules`, and cannot write files, start processes or workers, or reach Node's internal bindings. Because Node cannot yet limit network access by host, this is what keeps the network wrappers above from being bypassed.
|
|
505
|
-
- **Contained output.** The local server never serves files outside your build output, even through symlinks, and answers only requests addressed to 127.0.0.1. Routes whose file names would be too long are skipped with a warning, and if writing the output fails, the files already written are rolled back.
|
|
506
|
-
|
|
507
|
-
**Older Node.js.** Without the permission model (Node.js before 22.13, or 23.x before 23.5), a build that renders with happy-dom logs a warning once. The layers above still apply, but code that escapes happy-dom could open raw sockets, start a child process (for example `curl`) or a worker, which loads without the network wrappers, and read or write any file the build user can access.
|
|
508
|
-
|
|
509
|
-
**For untrusted scripts** (user-generated pages, third-party widgets you must allow), build on Node.js 22.13 or later, or use the Chrome renderer (`prerender: { renderer: "chrome" }`), which runs pages in Chrome's own sandbox with the same network blocking. In Chrome, `<link rel="preconnect">` and `dns-prefetch` can still resolve DNS and open connections without sending a request, and blocking WebSockets needs playwright-core 1.48 or later.
|
|
510
|
-
|
|
511
|
-
**Classic scripts.** Under the permission model, happy-dom cannot make synchronous requests itself. To keep classic `<script src>` tags in your `index.html` working (no `async`, `defer` or `type="module"`), they are fetched from your build ahead of time and served from memory: up to 4 MiB per script and 10 MiB in total. A script over a limit, or one that could not be fetched within `prerender.timeoutMs`, is skipped with a warning naming it. Only same-origin scripts are preloaded. Under the permission model, any other synchronous load, such as a classic script the page inserts later, one from an allowed origin, or a synchronous XHR, is refused: the build fails if the page needs it, and otherwise warns once per request, listing the routes. Add `type="module"` or `defer` to the script, make the request asynchronous, or render the route with Chrome. Vite's own scripts are modules and are unaffected.
|
|
512
|
-
|
|
513
|
-
### Agent actions
|
|
514
|
-
|
|
515
|
-
- Input is validated strictly against the action's schema before anything runs, including nested objects and arrays. Keys not in the schema are rejected unless `additionalProperties: true` is set. See [`useAgentAction`](#useagentaction).
|
|
516
|
-
- Some form fields are never exposed: hidden, password and file inputs; disabled and `readonly` fields; and fields hidden by markup (`hidden`, `inert`, `aria-hidden="true"`, a closed `<dialog>` or `<details>`). A checkbox or radio hidden only by its own `hidden` attribute stays exposed through a visible, enabled label. See [`AgentForm`](#agentform).
|
|
517
|
-
- Destructive actions ask the user first, and the default dialog shows every declared field, so the agent cannot hide a field by padding others. Actions that would fail are refused before the dialog. See [Destructive actions](#destructive-actions).
|
|
518
|
-
- The WebMCP schema is captured when a tool is registered. After revealing fields, call `refreshAgentActions({ force: true })` so agents see the current form.
|
|
519
|
-
|
|
520
|
-
### CLI audit
|
|
521
|
-
|
|
522
|
-
- The text report strips terminal control and bidi characters from everything the audited site supplies (titles, URLs, error text), collapses whitespace and shortens very long values, so a page cannot rewrite your terminal.
|
|
523
|
-
- `--json` keeps those values exactly as the site sent them, with control and bidi characters written as `\u` escapes. The JSON is safe to print, but tools that render its fields should sanitize them.
|
|
524
|
-
- If a redirect goes from a public host to a private, loopback or link-local address (for example `169.254.169.254`), the audit follows it and adds a `Warning:` line (`warnings` in JSON). Pass `--no-private-redirects` (or `audit(url, { blockPrivateRedirects: true })`) to refuse such redirects. The check resolves each host before the request, and the request resolves it again, so a DNS-rebinding host can still reach a private address: treat it as a guard against accidents, not a sandbox.
|
|
525
|
-
- robots.txt is read up to 500 KiB (the minimum RFC 9309 requires), and patterns longer than 512 characters are ignored. When either limit is hit, the robots check says so in its message.
|
|
526
|
-
|
|
527
|
-
## Performance and memory
|
|
528
|
-
|
|
529
|
-
Prerendering is fast. As approximate measurements on a laptop: the 12-page demo in `examples/demo` builds and prerenders in about 1.5 seconds, and a 500-route test site prerenders in under 10 seconds with the default settings.
|
|
530
|
-
|
|
531
|
-
Every page being rendered is held in memory at once. These figures are approximate. A typical page takes tens of MB, but a heavy page (a large DOM, big data payloads) can peak at around 500 MB. Peak memory is roughly `prerender.concurrency` times the heaviest page, plus about 150 MB for the build and 100 MB for the render process; the Chrome renderer adds Chrome's own processes on top. On small CI runners or containers (1 to 2 GB), lower `prerender.concurrency`, for example to `2`. If a render runs out of memory, the build fails with a message suggesting exactly that, rather than crashing.
|
|
118
|
+
It scores 0 to 100 across content without JavaScript, JSON-LD, robots.txt, title, meta description, sitemap, Open Graph, llms.txt, the markdown copy, canonical URL and the agent map, and prints a `Fix:` line for each miss. Flags, weights and JSON output: [CLI](https://github.com/LazyyVenom/Web-For-Agents/blob/master/docs/cli.md).
|
|
532
119
|
|
|
533
120
|
## Dev overlay
|
|
534
121
|
|
|
535
|
-
While `vite` (the dev server) runs,
|
|
536
|
-
|
|
537
|
-
- title (and whether it came from `<title>` or the first `<h1>`), description, canonical, robots, Open Graph and Twitter tags
|
|
538
|
-
- each JSON-LD block, with its `@type`s and any parse error
|
|
539
|
-
- the markdown generated from the content region (the same code that writes the `.md` files), with a warning when there is no `<main>`
|
|
540
|
-
- agent actions: registered handlers, `data-agent-action` markup and WebMCP declarative tools
|
|
541
|
-
|
|
542
|
-
The panel lives in a shadow root, so it does not pick up or leak styles, and it refreshes as the page changes.
|
|
122
|
+
While `vite` (the dev server) runs, a "WFA" button on every page (or `Alt+Shift+A`) opens a panel showing what an agent sees: head tags, JSON-LD, the generated markdown and agent actions. It is never included in `vite build`. See [Dev overlay](https://github.com/LazyyVenom/Web-For-Agents/blob/master/docs/overlay.md).
|
|
543
123
|
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
The markdown in the panel matches the generated `.md` file. Links resolve against `siteUrl` plus the route, and a page with no `<title>` or `<h1>` is titled with its route. When neither `[data-wfa-content]` nor your `contentSelector` matches, the panel names the selector it looked for.
|
|
124
|
+
## Security
|
|
547
125
|
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
});
|
|
554
|
-
```
|
|
126
|
+
- Prerendering runs your app's code at build time. happy-dom is **not a sandbox**; it renders in a separate, locked-down Node.js process with a scrubbed environment.
|
|
127
|
+
- During the build, pages can only load your build's own files unless you allow other origins with `prerender.allowOrigins`.
|
|
128
|
+
- Build on Node.js 22.13+ for the permission model, or use the Chrome renderer for untrusted scripts.
|
|
129
|
+
- Agent input is validated strictly against the action's schema, and destructive actions ask the user first.
|
|
130
|
+
- The CLI strips terminal control and bidi characters from site data in its text report.
|
|
555
131
|
|
|
556
|
-
|
|
132
|
+
Details: [Security](https://github.com/LazyyVenom/Web-For-Agents/blob/master/docs/security.md).
|
|
557
133
|
|
|
558
|
-
|
|
134
|
+
## Key options
|
|
559
135
|
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
-
|
|
574
|
-
-
|
|
575
|
-
-
|
|
576
|
-
-
|
|
577
|
-
-
|
|
578
|
-
-
|
|
136
|
+
| Option | Description |
|
|
137
|
+
| --- | --- |
|
|
138
|
+
| `siteUrl` | Required. Absolute http(s) URL of the deployed site. |
|
|
139
|
+
| `robots.ai.training` / `robots.ai.search` | Required. `"allow"` or `"block"` for AI training and AI search crawlers. |
|
|
140
|
+
| `routes` | Extra routes to prerender, for pages no link reaches. |
|
|
141
|
+
| `exclude` | Routes to leave out (exact path, `/*` prefix or `RegExp`). |
|
|
142
|
+
| `prerender` | Renderer, timeouts, concurrency, `allowOrigins`; `false` turns off prerender, markdown, llms and the agent map. |
|
|
143
|
+
| `markdown`, `llms`, `agentMap`, `sitemap`, `overlay` | Turn each output off with `false`, or configure it. |
|
|
144
|
+
|
|
145
|
+
All options and the AI crawler list: [Plugin options](https://github.com/LazyyVenom/Web-For-Agents/blob/master/docs/options.md).
|
|
146
|
+
|
|
147
|
+
## Documentation
|
|
148
|
+
|
|
149
|
+
- [Plugin options](https://github.com/LazyyVenom/Web-For-Agents/blob/master/docs/options.md)
|
|
150
|
+
- [Head tags and JSON-LD](https://github.com/LazyyVenom/Web-For-Agents/blob/master/docs/seo.md)
|
|
151
|
+
- [Prerendering](https://github.com/LazyyVenom/Web-For-Agents/blob/master/docs/prerendering.md)
|
|
152
|
+
- [Agent actions](https://github.com/LazyyVenom/Web-For-Agents/blob/master/docs/agent-actions.md)
|
|
153
|
+
- [CLI](https://github.com/LazyyVenom/Web-For-Agents/blob/master/docs/cli.md)
|
|
154
|
+
- [Dev overlay](https://github.com/LazyyVenom/Web-For-Agents/blob/master/docs/overlay.md)
|
|
155
|
+
- [Security](https://github.com/LazyyVenom/Web-For-Agents/blob/master/docs/security.md)
|
|
156
|
+
- [Advanced exports](https://github.com/LazyyVenom/Web-For-Agents/blob/master/docs/advanced.md)
|
|
157
|
+
- [Changelog](https://github.com/LazyyVenom/Web-For-Agents/blob/master/CHANGELOG.md)
|
|
579
158
|
|
|
580
159
|
## Development
|
|
581
160
|
|
|
582
161
|
```sh
|
|
583
|
-
npm ci
|
|
584
|
-
npm run typecheck # tsc --noEmit
|
|
585
|
-
npm test # unit, Vite and e2e tests (vitest)
|
|
586
|
-
npm run build # tsup into dist/
|
|
587
|
-
npm run test:pack # packs the package, installs the tarball into a fresh Vite app, builds it and audits it
|
|
588
|
-
npm run demo:dev # demo site with the dev overlay
|
|
589
|
-
npm run demo:build # prerender the demo into dist-demo/
|
|
162
|
+
npm ci && npm test && npm run build
|
|
590
163
|
```
|
|
591
164
|
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
Tests that need a real browser use your installed Chrome. They are skipped when Chrome cannot be launched; set `WFA_CHROME=skip` to always skip them, or `WFA_CHROME=require` to fail instead of skipping (CI does this).
|
|
595
|
-
|
|
596
|
-
To run the Vite tests against another Vite major, swap it in without touching the lockfile, then run them:
|
|
597
|
-
|
|
598
|
-
```sh
|
|
599
|
-
node scripts/install-vite.mjs 5
|
|
600
|
-
npx vitest run test/vite test/e2e test/cli/audit.e2e.test.ts
|
|
601
|
-
npm ci # back to the locked versions
|
|
602
|
-
```
|
|
603
|
-
|
|
604
|
-
CI runs one job on every push and pull request (Node 22: typecheck, tests, build, pack and the demo build). The full matrix (Node 20.19, 22 and 24, Vite 5 to 7, and the packed package against Vite 5 to 8) runs only when started manually with "Run workflow" on the Actions tab, for example before a release.
|
|
605
|
-
|
|
606
|
-
The demo in `examples/demo` uses every feature; its README lists what each page shows.
|
|
165
|
+
Scripts, CI and testing against other Vite majors: [Development](https://github.com/LazyyVenom/Web-For-Agents/blob/master/docs/development.md).
|
|
607
166
|
|
|
608
167
|
## License
|
|
609
168
|
|