nuxt-ai-ready 3.2.0 → 3.3.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/dist/chunks/agent-skills.mjs +2 -1
- package/dist/chunks/prerender.mjs +189 -27
- package/dist/module.d.mts +1 -0
- package/dist/module.json +1 -1
- package/dist/module.mjs +2 -1
- package/dist/runtime/llms-txt-files.d.ts +30 -0
- package/dist/runtime/llms-txt-files.js +141 -0
- package/dist/runtime/llms-txt-utils.d.ts +3 -2
- package/dist/runtime/llms-txt-utils.js +85 -224
- package/dist/runtime/server/db/queries.d.ts +3 -0
- package/dist/runtime/server/db/queries.js +28 -2
- package/dist/runtime/server/middleware/markdown.js +1 -1
- package/dist/runtime/server/routes/llms-full.txt.get.js +2 -1
- package/dist/runtime/server/routes/llms.txt.get.js +19 -11
- package/dist/runtime/server/routes/sitemap.md.get.js +2 -1
- package/dist/runtime/server/utils/markdown-request.js +2 -5
- package/dist/runtime/server/utils/sitemap.d.ts +1 -0
- package/dist/runtime/server/utils/sitemap.js +22 -10
- package/dist/runtime/types.d.ts +13 -1
- package/dist/shared/{nuxt-ai-ready.DH9mSayy.mjs → nuxt-ai-ready.BwnSMqRn.mjs} +41 -12
- package/package.json +1 -1
- package/skills/nuxt-ai-ready/SKILL.md +34 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createHash, randomBytes } from 'node:crypto';
|
|
2
|
-
import { readFile, mkdir, writeFile
|
|
2
|
+
import { readFile, access, mkdir, writeFile } from 'node:fs/promises';
|
|
3
3
|
import { createRequire } from 'node:module';
|
|
4
4
|
import { dirname, basename, join, resolve, relative } from 'node:path';
|
|
5
5
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
@@ -8,6 +8,7 @@ import defu$1, { defu } from 'defu';
|
|
|
8
8
|
import { useSiteConfig, withSiteUrl } from 'nuxt-site-config/kit';
|
|
9
9
|
import { resolveNuxtContentVersion, renderNitroTypeAugmentations, setupRuntimeAliases, setupNitroRuntimeCompatibility } from 'nuxtseo-shared/kit';
|
|
10
10
|
import { r as readPackageMetadata, a as resolvePackageMetadataPath } from './nuxt-ai-ready.B3hAuOLQ.mjs';
|
|
11
|
+
import { parseLlmsTxtConfig } from '../../dist/runtime/llms-txt-files.js';
|
|
11
12
|
import { toDeployedRoute } from '../../dist/runtime/route-path.js';
|
|
12
13
|
import { SITEMAP_MD_ROUTE } from '../../dist/runtime/server/utils/sitemap-md.js';
|
|
13
14
|
import { publicCacheControl, mergeVaryHeader } from '../../dist/runtime/cache-control.js';
|
|
@@ -210,7 +211,11 @@ export {}
|
|
|
210
211
|
});
|
|
211
212
|
addTemplate({
|
|
212
213
|
filename: "types/ai-ready-virtual.d.ts",
|
|
213
|
-
getContents: () => `declare module '#ai-ready-virtual/
|
|
214
|
+
getContents: () => `declare module '#ai-ready-virtual/sitemap-config.mjs' {
|
|
215
|
+
const config: { sitemaps?: Record<string, { sitemapName?: string, route?: string, _route?: string }> }
|
|
216
|
+
export default config
|
|
217
|
+
}
|
|
218
|
+
declare module '#ai-ready-virtual/read-page-data.mjs' {
|
|
214
219
|
export function readMarkdownLinkAvailabilityFromFilesystem(): Promise<{
|
|
215
220
|
runtimeMarkdownAvailable: boolean
|
|
216
221
|
paths: string[]
|
|
@@ -1840,7 +1845,7 @@ ${details}`);
|
|
|
1840
1845
|
const llmsFullRoute = withSiteUrl("llms-full.txt", { withBase: true });
|
|
1841
1846
|
defaultLlmsTxtSections.push({
|
|
1842
1847
|
title: "LLM Resources",
|
|
1843
|
-
links: [
|
|
1848
|
+
links: config.llmsFullTxt === false ? [] : [
|
|
1844
1849
|
{
|
|
1845
1850
|
title: "Full Content",
|
|
1846
1851
|
href: llmsFullRoute,
|
|
@@ -1853,18 +1858,31 @@ ${details}`);
|
|
|
1853
1858
|
defaultLlmsTxtSections.push({
|
|
1854
1859
|
title: "Agent Skills",
|
|
1855
1860
|
description: `Skills an agent can install from this site. The index at ${indexUrl} carries a sha256 digest for each one.`,
|
|
1856
|
-
links: agentSkillsResult.links.map((link) => ({
|
|
1861
|
+
links: [{ title: "Agent Skills index", href: indexUrl, description: "Every published skill and its sha256 digest." }, ...agentSkillsResult.links.map((link) => ({
|
|
1857
1862
|
title: link.name,
|
|
1858
1863
|
href: link.source === "local" ? withSiteUrl(link.href.slice(1), { withBase: true }) : resolveExternalSkillUrl(link.href, indexUrl),
|
|
1859
1864
|
description: link.description
|
|
1860
|
-
}))
|
|
1865
|
+
}))]
|
|
1861
1866
|
});
|
|
1862
1867
|
}
|
|
1868
|
+
const directiveConfig = config.llmsTxt === false ? void 0 : config.llmsTxt?.directive;
|
|
1863
1869
|
const llmsTxtDirective = {
|
|
1864
|
-
markdown:
|
|
1865
|
-
html:
|
|
1870
|
+
markdown: directiveConfig?.markdown !== false,
|
|
1871
|
+
html: directiveConfig?.html === true
|
|
1866
1872
|
};
|
|
1873
|
+
const parsedLlmsTxt = parseLlmsTxtConfig(config.llmsTxt || {});
|
|
1874
|
+
if (parsedLlmsTxt._tag === "Invalid")
|
|
1875
|
+
throw new Error(`[nuxt-ai-ready] ${parsedLlmsTxt.message}`);
|
|
1876
|
+
const publicLlmsTxt = await Promise.all(getLayerDirectories(nuxt).map(
|
|
1877
|
+
(layer) => access(join(layer.public, "llms.txt")).then(() => true).catch((error) => {
|
|
1878
|
+
if (error.code === "ENOENT")
|
|
1879
|
+
return false;
|
|
1880
|
+
throw error;
|
|
1881
|
+
})
|
|
1882
|
+
));
|
|
1883
|
+
const generateLlmsTxt = config.llmsTxt !== false && !publicLlmsTxt.some(Boolean);
|
|
1867
1884
|
const mergedLlmsTxt = config.llmsTxt ? {
|
|
1885
|
+
...parsedLlmsTxt.config,
|
|
1868
1886
|
markdownLinks: config.llmsTxt.markdownLinks ?? true,
|
|
1869
1887
|
directive: llmsTxtDirective,
|
|
1870
1888
|
sections: [
|
|
@@ -1880,6 +1898,9 @@ ${details}`);
|
|
|
1880
1898
|
await nuxt.callHook("ai-ready:llms-txt", llmsTxtPayload);
|
|
1881
1899
|
mergedLlmsTxt.sections = llmsTxtPayload.sections;
|
|
1882
1900
|
mergedLlmsTxt.notes = llmsTxtPayload.notes.length > 0 ? llmsTxtPayload.notes : void 0;
|
|
1901
|
+
const parsedHookLlmsTxt = parseLlmsTxtConfig(mergedLlmsTxt);
|
|
1902
|
+
if (parsedHookLlmsTxt._tag === "Invalid")
|
|
1903
|
+
throw new Error(`[nuxt-ai-ready] ${parsedHookLlmsTxt.message}`);
|
|
1883
1904
|
const prerenderCacheDir = join(nuxt.options.rootDir, "node_modules/.cache/nuxt-seo/ai-ready/routes");
|
|
1884
1905
|
const buildDbPath = join(nuxt.options.buildDir, ".data/ai-ready/build.db");
|
|
1885
1906
|
const runtimeSyncConfig = typeof config.runtimeSync === "object" ? config.runtimeSync : {};
|
|
@@ -1896,6 +1917,8 @@ ${details}`);
|
|
|
1896
1917
|
if (apiCatalogRegistered)
|
|
1897
1918
|
return;
|
|
1898
1919
|
apiCatalogRegistered = true;
|
|
1920
|
+
const resources = mergedLlmsTxt.sections?.find((section) => section.title === "LLM Resources");
|
|
1921
|
+
resources?.links?.push({ title: "API catalog", href: catalog.href, description: "The public API and service descriptions." });
|
|
1899
1922
|
addServerHandler({ route: API_CATALOG_PATH, handler: resolve$1("./runtime/server/routes/api-catalog"), lazy: true });
|
|
1900
1923
|
const apiCatalogLink = `<${catalog.href}>; rel="api-catalog"`;
|
|
1901
1924
|
extendRouteRules("/**", {
|
|
@@ -2273,6 +2296,7 @@ export function trackDrizzleWork(event, work) { return work }
|
|
|
2273
2296
|
cron: !!config.cron
|
|
2274
2297
|
})}`;
|
|
2275
2298
|
nitroConfig.virtual["#ai-ready-virtual/content-lookup.mjs"] = contentLookupModule(contentSource);
|
|
2299
|
+
nitroConfig.virtual["#ai-ready-virtual/sitemap-config.mjs"] = `export { default } from '#sitemap-virtual/static-config.mjs'`;
|
|
2276
2300
|
});
|
|
2277
2301
|
nuxt.options.runtimeConfig["nuxt-ai-ready"] = {
|
|
2278
2302
|
version: version || "0.0.0",
|
|
@@ -2288,6 +2312,7 @@ export function trackDrizzleWork(event, work) { return work }
|
|
|
2288
2312
|
swr: true
|
|
2289
2313
|
}),
|
|
2290
2314
|
llmsTxt: mergedLlmsTxt,
|
|
2315
|
+
llmsFullTxt: config.llmsFullTxt !== false,
|
|
2291
2316
|
llmsTxtCacheSeconds: config.llmsTxtCacheSeconds ?? 600,
|
|
2292
2317
|
prerenderCacheDir,
|
|
2293
2318
|
database,
|
|
@@ -2335,10 +2360,14 @@ export function trackDrizzleWork(event, work) { return work }
|
|
|
2335
2360
|
mode: "server",
|
|
2336
2361
|
src: resolve$1("./runtime/app/plugins/md-alternate.server")
|
|
2337
2362
|
});
|
|
2338
|
-
|
|
2339
|
-
|
|
2363
|
+
if (generateLlmsTxt) {
|
|
2364
|
+
for (const route of ["/llms.txt", "/llms/**"])
|
|
2365
|
+
addServerHandler({ route, method: void 0, handler: resolve$1("./runtime/server/routes/llms.txt.get"), lazy: true });
|
|
2366
|
+
}
|
|
2367
|
+
if (config.llmsFullTxt !== false)
|
|
2368
|
+
addServerHandler({ route: "/llms-full.txt", method: void 0, handler: resolve$1("./runtime/server/routes/llms-full.txt.get"), lazy: true });
|
|
2340
2369
|
if (config.sitemapMd !== false) {
|
|
2341
|
-
addServerHandler({ route: SITEMAP_MD_ROUTE, handler: resolve$1("./runtime/server/routes/sitemap.md.get"), lazy: true });
|
|
2370
|
+
addServerHandler({ route: SITEMAP_MD_ROUTE, method: void 0, handler: resolve$1("./runtime/server/routes/sitemap.md.get"), lazy: true });
|
|
2342
2371
|
}
|
|
2343
2372
|
if (agentSkillsResult._tag === "Enabled") {
|
|
2344
2373
|
addServerHandler({ route: AGENT_SKILLS_INDEX_ROUTE, handler: resolve$1("./runtime/server/routes/agent-skills-index"), lazy: true });
|
|
@@ -2414,7 +2443,7 @@ export function trackDrizzleWork(event, work) { return work }
|
|
|
2414
2443
|
}
|
|
2415
2444
|
if (prerenderEnabled) {
|
|
2416
2445
|
const { setupPrerenderHandler } = await import('../chunks/prerender.mjs');
|
|
2417
|
-
setupPrerenderHandler(config, buildDbPath, {
|
|
2446
|
+
setupPrerenderHandler({ ...config, llmsTxt: generateLlmsTxt ? config.llmsTxt : false }, buildDbPath, {
|
|
2418
2447
|
name: siteConfig.name,
|
|
2419
2448
|
url: siteConfig.url ? withSiteUrl("/", { withBase: true }) : void 0,
|
|
2420
2449
|
description: siteConfig.description
|
|
@@ -2437,7 +2466,7 @@ export function trackDrizzleWork(event, work) { return work }
|
|
|
2437
2466
|
icon: "carbon:ai-label"
|
|
2438
2467
|
}, resolve$1, nuxt);
|
|
2439
2468
|
}
|
|
2440
|
-
for (const route of ["/llms.txt", "/llms-full.txt"]) {
|
|
2469
|
+
for (const route of [generateLlmsTxt && "/llms.txt", generateLlmsTxt && "/llms/**", config.llmsFullTxt !== false && "/llms-full.txt"].filter((route2) => Boolean(route2))) {
|
|
2441
2470
|
extendRouteRules(route, { headers: { "Content-Type": "text/plain; charset=utf-8" } });
|
|
2442
2471
|
}
|
|
2443
2472
|
if (config.sitemapMd !== false) {
|
package/package.json
CHANGED
|
@@ -31,7 +31,7 @@ A page visit in dev or production does not index anything.
|
|
|
31
31
|
|
|
32
32
|
With no `aiReady` config, the module adds:
|
|
33
33
|
|
|
34
|
-
- `/llms.txt`: site name, description, resource links, and
|
|
34
|
+
- `/llms.txt`: site name, description, resource links, and current sitemap pages. Stored pages supply metadata. Large lists split under `/llms/`.
|
|
35
35
|
- `/llms-full.txt`: the full Markdown of each prerendered page. Only prerendered pages go in the static file.
|
|
36
36
|
- A `.md` twin per page. `/about` becomes `/about.md`, and `/` becomes `/index.md`. Paths under `/api` and `/_` get none.
|
|
37
37
|
- Accept negotiation: a request that prefers `text/markdown` gets a `307` to the `.md` URL. Responses send `Vary: Accept`. AI bot user agents get Markdown only with `botNegotiation: true`.
|
|
@@ -58,6 +58,32 @@ export default defineNuxtConfig({
|
|
|
58
58
|
})
|
|
59
59
|
```
|
|
60
60
|
|
|
61
|
+
Publish separate discovery files from named sitemaps. Use module configuration before writing sitemap and database middleware:
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
export default defineNuxtConfig({
|
|
65
|
+
aiReady: {
|
|
66
|
+
llmsFullTxt: false,
|
|
67
|
+
llmsTxt: {
|
|
68
|
+
markdownLinks: true,
|
|
69
|
+
sitemaps: ['pages', 'skills', 'demos'],
|
|
70
|
+
sections: [
|
|
71
|
+
{ title: 'Skills', sitemap: 'skills', path: '/llms/skills.txt' },
|
|
72
|
+
{ title: 'Demos', sitemap: 'demos', path: '/llms/demos.txt' },
|
|
73
|
+
],
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
})
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
The root links each section file. Chunked sitemaps keep their configured name.
|
|
80
|
+
Every file stays within `maxLength`, default 80,000 characters. Static builds emit every linked child.
|
|
81
|
+
GET and HEAD work on every generated discovery file. Empty sections produce no link or file.
|
|
82
|
+
|
|
83
|
+
For manual discovery, place `public/llms.txt`. The module preserves it and keeps Markdown conversion enabled.
|
|
84
|
+
If a server handler owns discovery, set `llmsTxt: false`.
|
|
85
|
+
Use `llmsFullTxt: false` instead of redirecting the full export to the index.
|
|
86
|
+
|
|
61
87
|
Remove an element from the Markdown on every path, build and runtime. Put it in `mdreamOptions`; the defaults `minimal` and `clean` stay on:
|
|
62
88
|
|
|
63
89
|
```ts
|
|
@@ -92,6 +118,8 @@ Runtime indexing, database drivers, Cloudflare D1, and the server query helpers:
|
|
|
92
118
|
|
|
93
119
|
## Traps
|
|
94
120
|
|
|
121
|
+
- **Discovery is not authorization.** Selected sitemaps exclude stale rows from `llms.txt`; they do not delete stored content or protect other APIs.
|
|
122
|
+
- **Custom route policies must allow Markdown twins.** If middleware rejects `/skills/planning.md`, allow it whenever `/skills/planning` is allowed.
|
|
95
123
|
- **The same edit in both hooks applies twice.** During prerendering the Nitro `ai-ready:page:markdown` hook runs, then the Nuxt hook of the same name. Put an edit in one of them, not both.
|
|
96
124
|
- **Content source pages skip conversion hooks.** A Nuxt Content page ignores `mdreamOptions` and both Nitro hooks.
|
|
97
125
|
- **Dev has no page data.** Query helpers, MCP tools, and search return empty results in `nuxt dev`. Test with `nuxt build` and `node .output/server/index.mjs`.
|
|
@@ -126,6 +154,11 @@ Runtime indexing, database drivers, Cloudflare D1, and the server query helpers:
|
|
|
126
154
|
|
|
127
155
|
## Config
|
|
128
156
|
|
|
157
|
+
- `llmsTxt` (`object`): generated discovery. Set `false` for application-owned server routes. A public `llms.txt` disables generation automatically.
|
|
158
|
+
- `llmsTxt.pages` (`'sitemap'`): sitemap admission. Use `'all'` for stored pages outside the sitemap, or `false` for configured sections only.
|
|
159
|
+
- `llmsTxt.sitemaps`: selected sitemap names. An empty array stops sitemap discovery.
|
|
160
|
+
- `llmsTxt.maxLength` (`80000`): maximum characters per file. Minimum `1024`. Larger lists split without dropping links.
|
|
161
|
+
- `llmsFullTxt` (`true`): generate the full export and advertise its link. Set `false` to omit both.
|
|
129
162
|
- `llmsTxt.directive` (`{ markdown: true, html: false }`): a `> Index of every page on this site: [llms.txt](...)` line after the frontmatter of each page's `.md`; `html: true` adds a visually hidden `/llms.txt` link at the top of each HTML body.
|
|
130
163
|
- `llmsTxt.markdownLinks` (`true`): link page entries in `llms.txt` to their `.md` twins. `false` links canonical URLs. With `site.url` set, page links in `llms.txt`, `sitemap.md`, and `.md` bodies are absolute.
|
|
131
164
|
- `describedby` (`true`): the `describedby` link tag and header.
|