nuxt-ai-ready 3.0.4 → 3.1.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 +1 -1
- package/dist/chunks/prerender.mjs +1 -1
- package/dist/devtools/lib/ai-ready/types.ts +1 -1
- package/dist/module.json +1 -1
- package/dist/module.mjs +1 -1
- package/dist/runtime/types.d.ts +6 -1
- package/dist/shared/{nuxt-ai-ready.iq1L3kll.mjs → nuxt-ai-ready.Dmvy7CIJ.mjs} +91 -47
- package/package.json +1 -1
- package/skills/nuxt-ai-ready/SKILL.md +6 -5
|
@@ -2,7 +2,7 @@ import { createHash } from 'node:crypto';
|
|
|
2
2
|
import { realpath, readFile, readdir } from 'node:fs/promises';
|
|
3
3
|
import { join, resolve, isAbsolute, relative, sep } from 'node:path';
|
|
4
4
|
import { parseDocument } from 'yaml';
|
|
5
|
-
import { A as AGENT_SKILLS_SCHEMA } from '../shared/nuxt-ai-ready.
|
|
5
|
+
import { A as AGENT_SKILLS_SCHEMA } from '../shared/nuxt-ai-ready.Dmvy7CIJ.mjs';
|
|
6
6
|
import 'node:module';
|
|
7
7
|
import 'node:url';
|
|
8
8
|
import '@nuxt/kit';
|
|
@@ -6,7 +6,7 @@ import { resolveLocaleFromRoute } from 'nuxtseo-shared/i18n-runtime';
|
|
|
6
6
|
import { createPrerenderFetch } from 'nuxtseo-shared/prerender';
|
|
7
7
|
import { collectSitemap } from 'sitemapd/parse';
|
|
8
8
|
import { withLeadingSlash, joinURL, withBase } from 'ufo';
|
|
9
|
-
import { l as logger, M as MARKDOWN_LINK_AVAILABILITY_FILE } from '../shared/nuxt-ai-ready.
|
|
9
|
+
import { l as logger, M as MARKDOWN_LINK_AVAILABILITY_FILE } from '../shared/nuxt-ai-ready.Dmvy7CIJ.mjs';
|
|
10
10
|
import { normalizePagePath, toMarkdownPath } from '../../dist/runtime/markdown-path.js';
|
|
11
11
|
import { toDeployedRoute, toLogicalRoute } from '../../dist/runtime/route-path.js';
|
|
12
12
|
import { initSchema, computeContentHash, insertPage, queryAllPages, exportDbDump } from '../../dist/runtime/server/db/shared.js';
|
|
@@ -4,7 +4,7 @@ export interface DevtoolsConfig {
|
|
|
4
4
|
sitemapPrerendered: boolean
|
|
5
5
|
markdownCacheHeaders: { maxAge: number, swr: boolean }
|
|
6
6
|
llmsTxtCacheSeconds: number
|
|
7
|
-
contentSignal: false | { aiTrain
|
|
7
|
+
contentSignal: false | { aiTrain?: boolean, search?: boolean, aiInput?: boolean }
|
|
8
8
|
mcp: { enabled: boolean, tools: boolean, resources: boolean }
|
|
9
9
|
cron: boolean
|
|
10
10
|
}
|
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -7,7 +7,7 @@ import '@nuxt/kit';
|
|
|
7
7
|
import 'defu';
|
|
8
8
|
import 'nuxt-site-config/kit';
|
|
9
9
|
import 'nuxtseo-shared/kit';
|
|
10
|
-
export { m as default } from './shared/nuxt-ai-ready.
|
|
10
|
+
export { m as default } from './shared/nuxt-ai-ready.Dmvy7CIJ.mjs';
|
|
11
11
|
import './shared/nuxt-ai-ready.B3hAuOLQ.mjs';
|
|
12
12
|
import '../dist/runtime/route-path.js';
|
|
13
13
|
import '../dist/runtime/server/utils/sitemap-md.js';
|
package/dist/runtime/types.d.ts
CHANGED
|
@@ -200,7 +200,12 @@ export interface ModuleOptions {
|
|
|
200
200
|
*/
|
|
201
201
|
describedby?: boolean;
|
|
202
202
|
/**
|
|
203
|
-
* Content Signal
|
|
203
|
+
* Content Signal directives for robots.txt.
|
|
204
|
+
*
|
|
205
|
+
* Unset, robots.txt gets `Content-Signal: search=yes, ai-input=yes` and no
|
|
206
|
+
* `ai-train` preference, unless a robots group already sets one. An object
|
|
207
|
+
* states the whole policy: each permission left out is sent as `no`.
|
|
208
|
+
* `false` sends no Content-Signal line.
|
|
204
209
|
*/
|
|
205
210
|
contentSignal?: false | {
|
|
206
211
|
/**
|
|
@@ -748,6 +748,58 @@ function resolveConfigurationWarnings(input) {
|
|
|
748
748
|
return warnings;
|
|
749
749
|
}
|
|
750
750
|
|
|
751
|
+
const DEFAULT_CONTENT_SIGNAL = "search=yes, ai-input=yes";
|
|
752
|
+
function resolveContentSignalPolicy(option) {
|
|
753
|
+
if (option === false)
|
|
754
|
+
return { _tag: "Disabled" };
|
|
755
|
+
if (option === void 0)
|
|
756
|
+
return { _tag: "Default" };
|
|
757
|
+
const value = (allowed) => allowed ? "yes" : "no";
|
|
758
|
+
return {
|
|
759
|
+
_tag: "Configured",
|
|
760
|
+
// One comma-separated line: validators reject one line per signal.
|
|
761
|
+
contentSignal: `ai-train=${value(option.aiTrain)}, search=${value(option.search)}, ai-input=${value(option.aiInput)}`,
|
|
762
|
+
...option.contentUsage !== false && { contentUsage: `train-ai=${option.aiTrain ? "y" : "n"}` }
|
|
763
|
+
};
|
|
764
|
+
}
|
|
765
|
+
function hasContentSignal(value) {
|
|
766
|
+
if (Array.isArray(value))
|
|
767
|
+
return value.some(Boolean);
|
|
768
|
+
if (value && typeof value === "object")
|
|
769
|
+
return Object.keys(value).length > 0;
|
|
770
|
+
return Boolean(value);
|
|
771
|
+
}
|
|
772
|
+
function isWildcardGroup(group) {
|
|
773
|
+
const userAgents = Array.isArray(group.userAgent) ? group.userAgent : [group.userAgent ?? "*"];
|
|
774
|
+
return userAgents.length === 1 && userAgents[0] === "*";
|
|
775
|
+
}
|
|
776
|
+
function wildcardGroup(groups) {
|
|
777
|
+
const existing = groups.find(isWildcardGroup);
|
|
778
|
+
if (existing)
|
|
779
|
+
return existing;
|
|
780
|
+
const group = { userAgent: ["*"], disallow: [""] };
|
|
781
|
+
groups.push(group);
|
|
782
|
+
return group;
|
|
783
|
+
}
|
|
784
|
+
function applyContentSignalPolicy(groups, policy) {
|
|
785
|
+
switch (policy._tag) {
|
|
786
|
+
case "Disabled":
|
|
787
|
+
return;
|
|
788
|
+
case "Default": {
|
|
789
|
+
if (groups.some((group) => hasContentSignal(group.contentSignal)))
|
|
790
|
+
return;
|
|
791
|
+
wildcardGroup(groups).contentSignal = [DEFAULT_CONTENT_SIGNAL];
|
|
792
|
+
return;
|
|
793
|
+
}
|
|
794
|
+
case "Configured": {
|
|
795
|
+
const group = wildcardGroup(groups);
|
|
796
|
+
group.contentSignal = [policy.contentSignal];
|
|
797
|
+
if (policy.contentUsage)
|
|
798
|
+
group.contentUsage = [policy.contentUsage];
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
|
|
751
803
|
const DATABASE_TYPE_NONE = "none";
|
|
752
804
|
const DATABASE_DEPENDENT_OPTIONS = ["runtimeSync", "cron"];
|
|
753
805
|
const DATABASE_CONSUMER_LABELS = {
|
|
@@ -1071,6 +1123,7 @@ function parseMcpServerCardConfig(input) {
|
|
|
1071
1123
|
}
|
|
1072
1124
|
};
|
|
1073
1125
|
}
|
|
1126
|
+
const MCP_SERVER_CARD_WELL_KNOWN_PATHS = ["/.well-known/mcp/server-card.json", "/.well-known/mcp.json"];
|
|
1074
1127
|
function resolveMcpServerCardRoute(mcpRoute) {
|
|
1075
1128
|
return `${mcpRoute.replace(/\/+$/, "") || "/mcp"}/server-card`;
|
|
1076
1129
|
}
|
|
@@ -1744,25 +1797,11 @@ ${details}`);
|
|
|
1744
1797
|
(paths.resources ||= []).push(`${mcpRuntimeDir}/resources`);
|
|
1745
1798
|
});
|
|
1746
1799
|
const contentSource = await resolveContentSource(config.contentSource !== false);
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
nuxt.
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
const group = {
|
|
1753
|
-
userAgent: "*",
|
|
1754
|
-
// Preserve nuxt-robots' default wildcard rule so the injected group remains valid.
|
|
1755
|
-
disallow: [""],
|
|
1756
|
-
// Object form renders one comma-separated line; an array renders one line per entry, which validators reject.
|
|
1757
|
-
contentSignal: {
|
|
1758
|
-
"ai-train": config.contentSignal.aiTrain ? "yes" : "no",
|
|
1759
|
-
"search": config.contentSignal.search ? "yes" : "no",
|
|
1760
|
-
"ai-input": config.contentSignal.aiInput ? "yes" : "no"
|
|
1761
|
-
}
|
|
1762
|
-
};
|
|
1763
|
-
if (config.contentSignal.contentUsage !== false)
|
|
1764
|
-
group.contentUsage = [`train-ai=${config.contentSignal.aiTrain ? "y" : "n"}`];
|
|
1765
|
-
groups.push(group);
|
|
1800
|
+
const contentSignalPolicy = resolveContentSignalPolicy(config.contentSignal);
|
|
1801
|
+
if (contentSignalPolicy._tag !== "Disabled") {
|
|
1802
|
+
nuxt.hook("robots:config", (robotsConfig) => {
|
|
1803
|
+
applyContentSignalPolicy(robotsConfig.groups, contentSignalPolicy);
|
|
1804
|
+
});
|
|
1766
1805
|
}
|
|
1767
1806
|
registerTypeTemplates({ config, nitroCompatibility });
|
|
1768
1807
|
const defaultLlmsTxtSections = [];
|
|
@@ -1876,18 +1915,21 @@ ${details}`);
|
|
|
1876
1915
|
updateSeoProFeatures(seoProModules);
|
|
1877
1916
|
return;
|
|
1878
1917
|
}
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
firstSection
|
|
1887
|
-
if (
|
|
1888
|
-
firstSection.links
|
|
1889
|
-
|
|
1890
|
-
|
|
1918
|
+
if (mcpServerCardResult._tag === "Enabled") {
|
|
1919
|
+
const endpoint = withSiteUrl(finalMcpToolkitState.route, { withBase: true });
|
|
1920
|
+
const mcpLink = {
|
|
1921
|
+
title: "MCP server card",
|
|
1922
|
+
href: withSiteUrl(MCP_SERVER_CARD_WELL_KNOWN_PATHS[0].slice(1), { withBase: true }),
|
|
1923
|
+
description: config.mcp?.tools !== false && hasMcpSiteTools ? `Connect an MCP client to ${endpoint} when you need information from this site's indexed pages.` : `Connect an MCP client to ${endpoint} to use this site's Model Context Protocol server.`
|
|
1924
|
+
};
|
|
1925
|
+
const firstSection = mergedLlmsTxt.sections?.[0];
|
|
1926
|
+
if (firstSection) {
|
|
1927
|
+
firstSection.links ||= [];
|
|
1928
|
+
if (!firstSection.links.some((link) => link.title === mcpLink.title))
|
|
1929
|
+
firstSection.links.push(mcpLink);
|
|
1930
|
+
} else {
|
|
1931
|
+
mergedLlmsTxt.sections = [{ title: "LLM Tools", links: [mcpLink] }];
|
|
1932
|
+
}
|
|
1891
1933
|
}
|
|
1892
1934
|
const mcpServerCardRoute = resolveMcpServerCardRoute(finalMcpToolkitState.route);
|
|
1893
1935
|
if (siteConfig.url && config.apiCatalog !== false) {
|
|
@@ -1964,21 +2006,23 @@ ${details}`);
|
|
|
1964
2006
|
etag
|
|
1965
2007
|
};
|
|
1966
2008
|
const handler = resolve$1("./runtime/server/routes/mcp-server-card");
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
2009
|
+
for (const route of [mcpServerCardRoute, ...MCP_SERVER_CARD_WELL_KNOWN_PATHS]) {
|
|
2010
|
+
addServerHandler({ route, method: "get", handler, lazy: true });
|
|
2011
|
+
addServerHandler({ route, method: "head", handler, lazy: true });
|
|
2012
|
+
addServerHandler({ route, method: "options", handler, lazy: true });
|
|
2013
|
+
extendRouteRules(route, {
|
|
2014
|
+
sitemap: false,
|
|
2015
|
+
headers: {
|
|
2016
|
+
"Access-Control-Allow-Headers": "Content-Type, If-None-Match",
|
|
2017
|
+
"Access-Control-Allow-Methods": "GET, HEAD",
|
|
2018
|
+
"Access-Control-Allow-Origin": "*",
|
|
2019
|
+
"Access-Control-Expose-Headers": "ETag",
|
|
2020
|
+
"Cache-Control": `public, max-age=${mcpServerCardResult.config.cacheMaxAge}`,
|
|
2021
|
+
"Content-Type": MCP_SERVER_CARD_MEDIA_TYPE,
|
|
2022
|
+
"ETag": etag
|
|
2023
|
+
}
|
|
2024
|
+
});
|
|
2025
|
+
}
|
|
1982
2026
|
if (siteConfig.url) {
|
|
1983
2027
|
const document = resolveAiCatalog({
|
|
1984
2028
|
siteUrl: siteConfig.url,
|
|
@@ -2188,7 +2232,7 @@ export function trackDrizzleWork(event, work) { return work }
|
|
|
2188
2232
|
const schemaPath = database._tag === "Enabled" && (database.type === "neon" || database.type === "postgres") ? "#ai-ready/server/db/schema/postgres" : "#ai-ready/server/db/schema/sqlite";
|
|
2189
2233
|
nitroConfig.virtual["#ai-ready-virtual/db-schema.mjs"] = database._tag === "Enabled" ? `export * from '${schemaPath}'` : "export {}";
|
|
2190
2234
|
nitroConfig.virtual["#ai-ready-virtual/devtools-meta.mjs"] = `export const devtoolsMeta = ${JSON.stringify({
|
|
2191
|
-
contentSignal: config.contentSignal
|
|
2235
|
+
contentSignal: config.contentSignal ?? { search: true, aiInput: true },
|
|
2192
2236
|
mcp: { enabled: mcpAvailable, tools: mcpAvailable && config.mcp?.tools !== false && hasMcpSiteTools, resources: mcpAvailable && config.mcp?.resources !== false },
|
|
2193
2237
|
cron: !!config.cron
|
|
2194
2238
|
})}`;
|
package/package.json
CHANGED
|
@@ -39,8 +39,9 @@ With no `aiReady` config, the module adds:
|
|
|
39
39
|
- `/sitemap.md`, and a `## Sitemap` footer on every generated `.md` page. Set `sitemapMd: false` to remove both.
|
|
40
40
|
- Nuxt Content v3 `page` collections: the `.md` route serves the source Markdown, not converted HTML. Set `contentSource: false` to convert HTML.
|
|
41
41
|
- Agent skills: `public/skills/<name>/SKILL.md` in the project or a local layer is published at `/.well-known/agent-skills/`, `/skills/<name>/SKILL.md`, and in `llms.txt`. Custom public directories are supported. One local skill is also served at `/SKILL.md`. Set `agentSkills: false` to publish nothing.
|
|
42
|
+
- `Content-Signal: search=yes, ai-input=yes` in `robots.txt`. Set `contentSignal: false` to remove it.
|
|
42
43
|
|
|
43
|
-
Off by default:
|
|
44
|
+
Off by default: MCP, WebMCP, runtime sync, cron, and the database.
|
|
44
45
|
|
|
45
46
|
## Common tasks
|
|
46
47
|
|
|
@@ -76,14 +77,14 @@ export default defineNitroPlugin((nitroApp) => {
|
|
|
76
77
|
})
|
|
77
78
|
```
|
|
78
79
|
|
|
79
|
-
|
|
80
|
+
Content Signals. Without config, robots.txt gets `Content-Signal: search=yes, ai-input=yes` and no `ai-train`. A robots group or merged robots.txt that already sets `Content-Signal` replaces the default. `contentSignal: false` removes the line. An object states the whole policy, so an omitted permission renders as `no`:
|
|
80
81
|
|
|
81
82
|
```ts
|
|
82
|
-
export default defineNuxtConfig({ aiReady: { contentSignal: { search: true } } })
|
|
83
|
-
// robots.txt: Content-Signal: ai-train=no, search=yes, ai-input=
|
|
83
|
+
export default defineNuxtConfig({ aiReady: { contentSignal: { search: true, aiInput: true, aiTrain: false } } })
|
|
84
|
+
// robots.txt: Content-Signal: ai-train=no, search=yes, ai-input=yes
|
|
84
85
|
```
|
|
85
86
|
|
|
86
|
-
MCP: install `@nuxtjs/mcp-toolkit` and add it to `modules`. The module then registers `list_pages`, `search_pages`, and `get_page_markdown` at `/mcp`. These tools need the database, so also run `npx nypm add drizzle-orm@1.0.0-rc.4`. MCP needs a running server; `nuxt generate` output cannot host it.
|
|
87
|
+
MCP: install `@nuxtjs/mcp-toolkit` and add it to `modules`. The module then registers `list_pages`, `search_pages`, and `get_page_markdown` at `/mcp`. These tools need the database, so also run `npx nypm add drizzle-orm@1.0.0-rc.4`. MCP needs a running server; `nuxt generate` output cannot host it. The server card is served at `/mcp/server-card`, `/.well-known/mcp/server-card.json`, and `/.well-known/mcp.json`. llms.txt links the card, never the endpoint, because the endpoint answers GET with 405 or 406. `mcpServerCard: false` removes the card routes and the llms.txt entry.
|
|
87
88
|
|
|
88
89
|
WebMCP: set `webmcp: true`. This registers the same three tools in the browser and auto-imports `useWebMcpTool()` and `useWebMcpSupported()`. The tools need the database and `drizzle-orm`. Without `webmcp`, those composables are not defined.
|
|
89
90
|
|