@anubhav-choubey/web-for-agents 0.0.0-stage → 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/LICENSE +21 -0
- package/README.md +168 -2
- package/dist/bin.js +13 -0
- package/dist/chunk-AAIYCJD3.js +8 -0
- package/dist/chunk-CGIIB6AF.js +127 -0
- package/dist/chunk-F7PN5R25.js +95 -0
- package/dist/chunk-FEAKVNMG.js +12 -0
- package/dist/chunk-JJN6FNJD.js +20 -0
- package/dist/chunk-LCLDQ3A3.js +26 -0
- package/dist/chunk-LCVPXUFN.js +245 -0
- package/dist/chunk-P6JSPAA2.js +1148 -0
- package/dist/chunk-PGZH33B5.js +129 -0
- package/dist/chunk-PHTV5NAY.js +44 -0
- package/dist/chunk-PSNLS4GL.js +211 -0
- package/dist/chunk-SMFRAZAA.js +367 -0
- package/dist/cli.d.ts +59 -0
- package/dist/cli.js +16 -0
- package/dist/happyDom-WTYQ5UAC.js +13 -0
- package/dist/index.d.ts +270 -0
- package/dist/index.js +943 -0
- package/dist/overlay.d.ts +25 -0
- package/dist/overlay.js +333 -0
- package/dist/renderChild-H6TR4SGV.js +268 -0
- package/dist/vite.d.ts +194 -0
- package/dist/vite.js +2531 -0
- package/package.json +128 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Anubhav Choubey
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,169 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @anubhav-choubey/web-for-agents
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://github.com/LazyyVenom/Web-For-Agents/actions/workflows/ci.yml)
|
|
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. 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
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
npm i @anubhav-choubey/web-for-agents
|
|
11
|
+
```
|
|
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](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
|
+
|
|
15
|
+
## Quick start
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
// vite.config.ts
|
|
19
|
+
import { defineConfig } from "vite";
|
|
20
|
+
import react from "@vitejs/plugin-react";
|
|
21
|
+
import { webForAgents } from "@anubhav-choubey/web-for-agents/vite";
|
|
22
|
+
|
|
23
|
+
export default defineConfig({
|
|
24
|
+
plugins: [
|
|
25
|
+
react(),
|
|
26
|
+
webForAgents({
|
|
27
|
+
siteUrl: "https://example.com",
|
|
28
|
+
routes: ["/about", "/blog/hello"],
|
|
29
|
+
robots: { ai: { training: "block", search: "allow" } },
|
|
30
|
+
}),
|
|
31
|
+
],
|
|
32
|
+
});
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
```tsx
|
|
36
|
+
import { SeoProvider, Seo, JsonLd, schema } from "@anubhav-choubey/web-for-agents";
|
|
37
|
+
|
|
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
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
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
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
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).
|
|
57
|
+
|
|
58
|
+
## What the build produces
|
|
59
|
+
|
|
60
|
+
After `vite build`, the plugin crawls from `/` plus `routes` and writes:
|
|
61
|
+
|
|
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. |
|
|
70
|
+
|
|
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).
|
|
72
|
+
|
|
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).
|
|
74
|
+
|
|
75
|
+
## Agent actions
|
|
76
|
+
|
|
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.
|
|
78
|
+
|
|
79
|
+
```tsx
|
|
80
|
+
import { useAgentAction } from "@anubhav-choubey/web-for-agents";
|
|
81
|
+
|
|
82
|
+
function DeleteAccount() {
|
|
83
|
+
const { ref, hintProps } = useAgentAction({
|
|
84
|
+
name: "delete_account",
|
|
85
|
+
description: "Delete the account",
|
|
86
|
+
destructive: true,
|
|
87
|
+
handler: async () => deleteAccount(),
|
|
88
|
+
});
|
|
89
|
+
return (
|
|
90
|
+
<button ref={ref} {...hintProps} onClick={() => deleteAccount()}>
|
|
91
|
+
Delete account
|
|
92
|
+
</button>
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
`AgentForm` turns a form into an action; its schema comes from the fields, and the agent submits through your own `onSubmit`:
|
|
98
|
+
|
|
99
|
+
```tsx
|
|
100
|
+
import { AgentForm, agentParam } from "@anubhav-choubey/web-for-agents";
|
|
101
|
+
|
|
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>
|
|
106
|
+
```
|
|
107
|
+
|
|
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).
|
|
109
|
+
|
|
110
|
+
## CLI
|
|
111
|
+
|
|
112
|
+
Audit any URL for AI-agent readiness, with nothing installed in the target site:
|
|
113
|
+
|
|
114
|
+
```sh
|
|
115
|
+
npx @anubhav-choubey/web-for-agents audit https://example.com --min-score 80
|
|
116
|
+
```
|
|
117
|
+
|
|
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).
|
|
119
|
+
|
|
120
|
+
## Dev overlay
|
|
121
|
+
|
|
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).
|
|
123
|
+
|
|
124
|
+
## Security
|
|
125
|
+
|
|
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.
|
|
131
|
+
|
|
132
|
+
Details: [Security](https://github.com/LazyyVenom/Web-For-Agents/blob/master/docs/security.md).
|
|
133
|
+
|
|
134
|
+
## Key options
|
|
135
|
+
|
|
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)
|
|
158
|
+
|
|
159
|
+
## Development
|
|
160
|
+
|
|
161
|
+
```sh
|
|
162
|
+
npm ci && npm test && npm run build
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
Scripts, CI and testing against other Vite majors: [Development](https://github.com/LazyyVenom/Web-For-Agents/blob/master/docs/development.md).
|
|
166
|
+
|
|
167
|
+
## License
|
|
168
|
+
|
|
169
|
+
MIT
|
package/dist/bin.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
runCli
|
|
4
|
+
} from "./chunk-P6JSPAA2.js";
|
|
5
|
+
import "./chunk-CGIIB6AF.js";
|
|
6
|
+
import "./chunk-PHTV5NAY.js";
|
|
7
|
+
import "./chunk-LCLDQ3A3.js";
|
|
8
|
+
import "./chunk-FEAKVNMG.js";
|
|
9
|
+
import "./chunk-F7PN5R25.js";
|
|
10
|
+
import "./chunk-JJN6FNJD.js";
|
|
11
|
+
|
|
12
|
+
// src/cli/bin.ts
|
|
13
|
+
process.exitCode = await runCli(process.argv.slice(2));
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
// src/shared/bots.ts
|
|
2
|
+
var AI_BOTS = {
|
|
3
|
+
training: [
|
|
4
|
+
"GPTBot",
|
|
5
|
+
"ClaudeBot",
|
|
6
|
+
"CCBot",
|
|
7
|
+
"Google-Extended",
|
|
8
|
+
"Applebot-Extended",
|
|
9
|
+
"Meta-ExternalAgent",
|
|
10
|
+
"Bytespider",
|
|
11
|
+
"cohere-training-data-crawler"
|
|
12
|
+
],
|
|
13
|
+
search: [
|
|
14
|
+
"OAI-SearchBot",
|
|
15
|
+
"ChatGPT-User",
|
|
16
|
+
"Claude-SearchBot",
|
|
17
|
+
"Claude-User",
|
|
18
|
+
"PerplexityBot",
|
|
19
|
+
"Perplexity-User"
|
|
20
|
+
]
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
// src/shared/htmlScan.ts
|
|
24
|
+
var isSpace = (c) => c === 32 || c === 9 || c === 10 || c === 12 || c === 13;
|
|
25
|
+
var isAlpha = (c) => c >= 65 && c <= 90 || c >= 97 && c <= 122;
|
|
26
|
+
function findRawEnd(html, name, from) {
|
|
27
|
+
let i = html.indexOf("</", from);
|
|
28
|
+
while (i !== -1) {
|
|
29
|
+
if (html.slice(i + 2, i + 2 + name.length).toLowerCase() === name) return i;
|
|
30
|
+
i = html.indexOf("</", i + 2);
|
|
31
|
+
}
|
|
32
|
+
return -1;
|
|
33
|
+
}
|
|
34
|
+
function* scanHtml(html, rawText) {
|
|
35
|
+
const n = html.length;
|
|
36
|
+
let i = 0;
|
|
37
|
+
while (i < n) {
|
|
38
|
+
const lt = html.indexOf("<", i);
|
|
39
|
+
if (lt === -1) return;
|
|
40
|
+
const c1 = lt + 1 < n ? html.charCodeAt(lt + 1) : 0;
|
|
41
|
+
if (c1 === 33 || c1 === 63) {
|
|
42
|
+
let end;
|
|
43
|
+
if (html.startsWith("<!--", lt)) {
|
|
44
|
+
const close = html.indexOf("-->", lt + 4);
|
|
45
|
+
end = close === -1 ? n : close + 3;
|
|
46
|
+
} else {
|
|
47
|
+
const close = html.indexOf(">", lt + 2);
|
|
48
|
+
end = close === -1 ? n : close + 1;
|
|
49
|
+
}
|
|
50
|
+
yield { type: "comment", start: lt, end };
|
|
51
|
+
i = end;
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
const closing = c1 === 47;
|
|
55
|
+
const nameStart = lt + (closing ? 2 : 1);
|
|
56
|
+
if (!isAlpha(nameStart < n ? html.charCodeAt(nameStart) : 0)) {
|
|
57
|
+
i = lt + 1;
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
let p = nameStart;
|
|
61
|
+
while (p < n) {
|
|
62
|
+
const c = html.charCodeAt(p);
|
|
63
|
+
if (isSpace(c) || c === 62 || c === 47) break;
|
|
64
|
+
p++;
|
|
65
|
+
}
|
|
66
|
+
const name = html.slice(nameStart, p).toLowerCase();
|
|
67
|
+
const attrs = [];
|
|
68
|
+
let selfClosing = false;
|
|
69
|
+
while (p < n) {
|
|
70
|
+
const c = html.charCodeAt(p);
|
|
71
|
+
if (isSpace(c)) {
|
|
72
|
+
p++;
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
if (c === 62) {
|
|
76
|
+
p++;
|
|
77
|
+
break;
|
|
78
|
+
}
|
|
79
|
+
if (c === 47) {
|
|
80
|
+
selfClosing = p + 1 < n && html.charCodeAt(p + 1) === 62;
|
|
81
|
+
p++;
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
const as = p;
|
|
85
|
+
while (p < n) {
|
|
86
|
+
const d = html.charCodeAt(p);
|
|
87
|
+
if (isSpace(d) || d === 62 || d === 47 || d === 61) break;
|
|
88
|
+
p++;
|
|
89
|
+
}
|
|
90
|
+
if (p === as) {
|
|
91
|
+
p++;
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
const attr = { name: html.slice(as, p).toLowerCase(), value: "", valueStart: -1, valueEnd: -1, quote: "" };
|
|
95
|
+
while (p < n && isSpace(html.charCodeAt(p))) p++;
|
|
96
|
+
if (p < n && html.charCodeAt(p) === 61) {
|
|
97
|
+
p++;
|
|
98
|
+
while (p < n && isSpace(html.charCodeAt(p))) p++;
|
|
99
|
+
const q = p < n ? html[p] : "";
|
|
100
|
+
if (q === '"' || q === "'") {
|
|
101
|
+
const close = html.indexOf(q, p + 1);
|
|
102
|
+
const stop = close === -1 ? n : close;
|
|
103
|
+
Object.assign(attr, { value: html.slice(p + 1, stop), valueStart: p + 1, valueEnd: stop, quote: q });
|
|
104
|
+
p = close === -1 ? n : close + 1;
|
|
105
|
+
} else {
|
|
106
|
+
const vs = p;
|
|
107
|
+
while (p < n && !isSpace(html.charCodeAt(p)) && html.charCodeAt(p) !== 62) p++;
|
|
108
|
+
Object.assign(attr, { value: html.slice(vs, p), valueStart: vs, valueEnd: p });
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
attrs.push(attr);
|
|
112
|
+
}
|
|
113
|
+
yield { type: "tag", closing, name, attrs, selfClosing, start: lt, end: p };
|
|
114
|
+
i = p;
|
|
115
|
+
if (!closing && !selfClosing && rawText.has(name)) {
|
|
116
|
+
const end = findRawEnd(html, name, i);
|
|
117
|
+
const stop = end === -1 ? n : end;
|
|
118
|
+
yield { type: "raw", name, start: i, end: stop };
|
|
119
|
+
i = stop;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export {
|
|
125
|
+
AI_BOTS,
|
|
126
|
+
scanHtml
|
|
127
|
+
};
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
// src/shared/url.ts
|
|
2
|
+
function normalizeRoute(path) {
|
|
3
|
+
let p = path.split("#")[0].split("?")[0];
|
|
4
|
+
if (!p.startsWith("/")) p = "/" + p;
|
|
5
|
+
if (p.length > 1) p = p.replace(/\/+$/, "");
|
|
6
|
+
return p === "" ? "/" : p;
|
|
7
|
+
}
|
|
8
|
+
function trimTrailingSlash(s) {
|
|
9
|
+
return s.replace(/\/+$/, "");
|
|
10
|
+
}
|
|
11
|
+
function hasScheme(s) {
|
|
12
|
+
return /^[a-z][a-z0-9+.-]*:/i.test(s);
|
|
13
|
+
}
|
|
14
|
+
function tryResolveUrl(value, base) {
|
|
15
|
+
try {
|
|
16
|
+
return new URL(value, base).href;
|
|
17
|
+
} catch {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function resolveUrl(value, base) {
|
|
22
|
+
if (!base || !value) return value;
|
|
23
|
+
return tryResolveUrl(value, base) ?? value;
|
|
24
|
+
}
|
|
25
|
+
function mountPath(base) {
|
|
26
|
+
return base.startsWith("/") && !base.startsWith("//") ? trimTrailingSlash(base) : "";
|
|
27
|
+
}
|
|
28
|
+
function routeOf(pathname, base = "/") {
|
|
29
|
+
const b = mountPath(base);
|
|
30
|
+
if (b !== "" && (pathname === b || pathname.startsWith(b + "/"))) pathname = pathname.slice(b.length);
|
|
31
|
+
return normalizeRoute(pathname);
|
|
32
|
+
}
|
|
33
|
+
function publicUrl(siteUrl, route) {
|
|
34
|
+
return trimTrailingSlash(siteUrl) + normalizeRoute(route);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// src/shared/conventions.ts
|
|
38
|
+
var ROBOTS_PATH = "/robots.txt";
|
|
39
|
+
var SITEMAP_PATH = "/sitemap.xml";
|
|
40
|
+
var LLMS_TXT_PATH = "/llms.txt";
|
|
41
|
+
var LLMS_FULL_TXT_PATH = "/llms-full.txt";
|
|
42
|
+
var AGENT_MAP_PATH = "/agents.json";
|
|
43
|
+
var MARKDOWN_MIME = "text/markdown";
|
|
44
|
+
var CONTENT_ATTR = "data-wfa-content";
|
|
45
|
+
var EXCLUDE_ATTR = "data-wfa-exclude";
|
|
46
|
+
var PRERENDERED_ATTR = "data-wfa-prerendered";
|
|
47
|
+
var READY_GLOBAL = "__WFA_READY__";
|
|
48
|
+
var HIDDEN_SELECTOR = '[hidden], [aria-hidden="true"]';
|
|
49
|
+
var HEAD_OWNED_ATTR = "data-wfa";
|
|
50
|
+
var MARKDOWN_LINK_KEY = "markdown";
|
|
51
|
+
function markdownPathFor(pathname) {
|
|
52
|
+
const p = normalizeRoute(pathname);
|
|
53
|
+
return p === "/" ? "/index.md" : p + ".md";
|
|
54
|
+
}
|
|
55
|
+
var ACTION_ATTR = "data-agent-action";
|
|
56
|
+
var ACTION_DESCRIPTION_ATTR = "data-agent-description";
|
|
57
|
+
var ACTION_DESTRUCTIVE_ATTR = "data-agent-destructive";
|
|
58
|
+
var ACTION_PARAM_ATTR = "data-agent-param";
|
|
59
|
+
var WEBMCP_TOOLNAME_ATTR = "toolname";
|
|
60
|
+
var WEBMCP_TOOLDESCRIPTION_ATTR = "tooldescription";
|
|
61
|
+
var WEBMCP_TOOLPARAMDESCRIPTION_ATTR = "toolparamdescription";
|
|
62
|
+
var AGENT_GLOBAL = "__WEB_FOR_AGENTS__";
|
|
63
|
+
|
|
64
|
+
export {
|
|
65
|
+
normalizeRoute,
|
|
66
|
+
trimTrailingSlash,
|
|
67
|
+
hasScheme,
|
|
68
|
+
tryResolveUrl,
|
|
69
|
+
resolveUrl,
|
|
70
|
+
mountPath,
|
|
71
|
+
routeOf,
|
|
72
|
+
publicUrl,
|
|
73
|
+
ROBOTS_PATH,
|
|
74
|
+
SITEMAP_PATH,
|
|
75
|
+
LLMS_TXT_PATH,
|
|
76
|
+
LLMS_FULL_TXT_PATH,
|
|
77
|
+
AGENT_MAP_PATH,
|
|
78
|
+
MARKDOWN_MIME,
|
|
79
|
+
CONTENT_ATTR,
|
|
80
|
+
EXCLUDE_ATTR,
|
|
81
|
+
PRERENDERED_ATTR,
|
|
82
|
+
READY_GLOBAL,
|
|
83
|
+
HIDDEN_SELECTOR,
|
|
84
|
+
HEAD_OWNED_ATTR,
|
|
85
|
+
MARKDOWN_LINK_KEY,
|
|
86
|
+
markdownPathFor,
|
|
87
|
+
ACTION_ATTR,
|
|
88
|
+
ACTION_DESCRIPTION_ATTR,
|
|
89
|
+
ACTION_DESTRUCTIVE_ATTR,
|
|
90
|
+
ACTION_PARAM_ATTR,
|
|
91
|
+
WEBMCP_TOOLNAME_ATTR,
|
|
92
|
+
WEBMCP_TOOLDESCRIPTION_ATTR,
|
|
93
|
+
WEBMCP_TOOLPARAMDESCRIPTION_ATTR,
|
|
94
|
+
AGENT_GLOBAL
|
|
95
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// src/shared/errors.ts
|
|
2
|
+
var LOG_PREFIX = "[web-for-agents]";
|
|
3
|
+
function withPrefix(msg) {
|
|
4
|
+
return msg.startsWith(LOG_PREFIX) ? msg : `${LOG_PREFIX} ${msg}`;
|
|
5
|
+
}
|
|
6
|
+
function errorMessage(e) {
|
|
7
|
+
if (e instanceof Error) return e.message;
|
|
8
|
+
if (typeof e === "string") return e;
|
|
9
|
+
try {
|
|
10
|
+
return String(e);
|
|
11
|
+
} catch {
|
|
12
|
+
return "unknown error";
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export {
|
|
17
|
+
LOG_PREFIX,
|
|
18
|
+
withPrefix,
|
|
19
|
+
errorMessage
|
|
20
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// src/shared/jsonLd.ts
|
|
2
|
+
var MAX_NODES = 1e5;
|
|
3
|
+
function jsonLdTypes(value) {
|
|
4
|
+
const out = [];
|
|
5
|
+
const stack = [value];
|
|
6
|
+
for (let visited = 0; stack.length > 0 && visited < MAX_NODES; visited++) {
|
|
7
|
+
const v = stack.pop();
|
|
8
|
+
if (Array.isArray(v)) {
|
|
9
|
+
for (let i = v.length - 1; i >= 0; i--) stack.push(v[i]);
|
|
10
|
+
continue;
|
|
11
|
+
}
|
|
12
|
+
if (v === null || typeof v !== "object") continue;
|
|
13
|
+
const o = v;
|
|
14
|
+
const t = o["@type"];
|
|
15
|
+
if (typeof t === "string") out.push(t);
|
|
16
|
+
else if (Array.isArray(t)) {
|
|
17
|
+
for (const x of t) if (typeof x === "string") out.push(x);
|
|
18
|
+
}
|
|
19
|
+
if ("@graph" in o) stack.push(o["@graph"]);
|
|
20
|
+
}
|
|
21
|
+
return out;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export {
|
|
25
|
+
jsonLdTypes
|
|
26
|
+
};
|