nuxt-ai-ready 0.0.3 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/module.d.mts CHANGED
@@ -1,36 +1,8 @@
1
1
  import * as _nuxt_schema from '@nuxt/schema';
2
- import { ModuleOptions, BulkChunk } from '../dist/runtime/types.js';
2
+ import { BulkChunk, ModuleOptions } from '../dist/runtime/types.js';
3
3
  export { BulkChunk, ModuleOptions } from '../dist/runtime/types.js';
4
- import { ProcessedFile } from 'mdream/llms-txt';
5
-
6
- /**
7
- * Hook payload for mdream:llms-txt
8
- * Called after mdream has generated llms.txt, before writing to disk
9
- *
10
- * IMPORTANT: This uses a mutable pattern. Hooks should modify the content
11
- * and fullContent properties directly rather than returning values.
12
- *
13
- * @example
14
- * nuxt.hooks.hook('mdream:llms-txt', async (payload) => {
15
- * payload.content += '\n\n## Custom Section\n\nAdded by hook!'
16
- * payload.fullContent += '\n\n## Custom Section (Full)\n\nAdded by hook!'
17
- * })
18
- */
19
- interface LlmsTxtGeneratePayload {
20
- /** Current llms.txt content - modify this directly */
21
- content: string;
22
- /** Current llms-full.txt content - modify this directly */
23
- fullContent: string;
24
- /** All routes with their metadata (read-only) */
25
- pages: ProcessedFile[];
26
- }
27
4
 
28
5
  interface ModuleHooks {
29
- /**
30
- * Hook to modify llms.txt content before final output
31
- * Other modules can append their own API endpoints here
32
- */
33
- 'ai-ready:llms-txt': (payload: LlmsTxtGeneratePayload) => void | Promise<void>;
34
6
  /**
35
7
  * Hook to add routes to the AI ready
36
8
  * Other modules can register their own API routes
@@ -58,5 +30,10 @@ interface ModulePublicRuntimeConfig {
58
30
  }
59
31
  declare const _default: _nuxt_schema.NuxtModule<ModuleOptions, ModuleOptions, false>;
60
32
 
33
+ declare module '@nuxt/schema' {
34
+ interface NuxtHooks extends ModuleHooks {
35
+ }
36
+ }
37
+
61
38
  export { _default as default };
62
39
  export type { ModuleHooks, ModulePublicRuntimeConfig };
package/dist/module.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "nuxt": ">=4.0.0"
5
5
  },
6
6
  "configKey": "aiReady",
7
- "version": "0.0.3",
7
+ "version": "0.1.2",
8
8
  "builder": {
9
9
  "@nuxt/module-builder": "1.0.2",
10
10
  "unbuild": "3.6.1"
package/dist/module.mjs CHANGED
@@ -6,26 +6,39 @@ import { TagIdMap } from 'mdream';
6
6
  import { extractionPlugin } from 'mdream/plugins';
7
7
  import { htmlToMarkdownSplitChunksStream } from 'mdream/splitter';
8
8
  import { useSiteConfig, installNuxtSiteConfig, withSiteUrl } from 'nuxt-site-config/kit';
9
+ import { isPathFile } from 'nuxt-site-config/urls';
9
10
  import { relative, resolve, dirname } from 'pathe';
10
11
  import { readPackageJSON } from 'pkg-types';
11
12
  import { estimateTokenCount } from 'tokenx';
12
- import { writeFile } from 'node:fs/promises';
13
+ import { stat } from 'node:fs/promises';
13
14
  import { join } from 'node:path';
14
- import { generateLlmsTxtArtifacts } from 'mdream/llms-txt';
15
- import { normalizeLlmsTxtConfig } from '../dist/runtime/llms-txt.js';
15
+ import { createLlmsTxtStream } from 'mdream/llms-txt';
16
16
 
17
17
  const logger = useLogger("nuxt-ai-ready");
18
18
 
19
19
  function setupPrerenderHandler() {
20
20
  const nuxt = useNuxt();
21
- const pages = [];
22
21
  nuxt.hooks.hook("nitro:init", async (nitro) => {
22
+ let writer = null;
23
+ let pageCount = 0;
24
+ const startTime = Date.now();
23
25
  nitro.hooks.hook("prerender:generate", async (route) => {
24
26
  if (!route.fileName?.endsWith(".md")) {
25
27
  return;
26
28
  }
29
+ if (!writer) {
30
+ const siteConfig = useSiteConfig();
31
+ const stream = createLlmsTxtStream({
32
+ siteName: siteConfig.name || siteConfig.url,
33
+ description: siteConfig.description,
34
+ origin: siteConfig.url,
35
+ generateFull: true,
36
+ outputDir: nitro.options.output.publicDir
37
+ });
38
+ writer = stream.getWriter();
39
+ }
27
40
  const { markdown, title, description } = JSON.parse(route.contents || "{}");
28
- const page = {
41
+ await writer.write({
29
42
  filePath: route.fileName,
30
43
  url: route.route,
31
44
  title,
@@ -34,68 +47,37 @@ function setupPrerenderHandler() {
34
47
  description,
35
48
  title
36
49
  }
37
- };
38
- pages.push(page);
50
+ });
51
+ pageCount++;
39
52
  route.contents = markdown;
40
53
  });
41
54
  nitro.hooks.hook("prerender:done", async () => {
42
- if (pages.length === 0) {
55
+ if (!writer) {
43
56
  return;
44
57
  }
45
- const startTime = Date.now();
46
- const siteConfig = useSiteConfig();
47
- const artifacts = await generateLlmsTxtArtifacts({
48
- origin: siteConfig.url,
49
- files: pages,
50
- generateFull: true,
51
- siteName: siteConfig.name || siteConfig.url,
52
- description: siteConfig.description
53
- });
54
- logger.success(`Generated markdown for ${pages.length} pages`);
55
- const hookPayload = {
56
- content: artifacts.llmsTxt || "",
57
- fullContent: artifacts.llmsFullTxt || "",
58
- pages
59
- };
60
- const llmsTxtConfig = nuxt.options.runtimeConfig["nuxt-ai-ready"].llmsTxt;
61
- const normalizedContent = normalizeLlmsTxtConfig(llmsTxtConfig);
62
- if (normalizedContent) {
63
- hookPayload.content = `${hookPayload.content}
64
-
65
- ${normalizedContent}
66
- `;
67
- }
68
- await nuxt.hooks.callHook("ai-ready:llms-txt", hookPayload);
69
- const finalLlmsTxt = hookPayload.content;
70
- const finalLlmsFullTxt = hookPayload.fullContent;
71
- const generatedFiles = [];
72
- if (finalLlmsTxt) {
73
- const llmsTxtPath = join(nitro.options.output.publicDir, "llms.txt");
74
- await writeFile(llmsTxtPath, finalLlmsTxt, "utf-8");
75
- const sizeKb = (Buffer.byteLength(finalLlmsTxt, "utf-8") / 1024).toFixed(2);
76
- generatedFiles.push({ path: "llms.txt", size: `${sizeKb}kb` });
77
- nitro._prerenderedRoutes.push({
58
+ await writer.close();
59
+ const llmsTxtPath = join(nitro.options.output.publicDir, "llms.txt");
60
+ const llmsFullTxtPath = join(nitro.options.output.publicDir, "llms-full.txt");
61
+ const [llmsStats, llmsFullStats] = await Promise.all([
62
+ stat(llmsTxtPath),
63
+ stat(llmsFullTxtPath)
64
+ ]);
65
+ nitro._prerenderedRoutes.push(
66
+ {
78
67
  route: "/llms.txt",
79
68
  fileName: llmsTxtPath,
80
69
  generateTimeMS: 0
81
- });
82
- }
83
- if (finalLlmsFullTxt) {
84
- const llmsFullTxtPath = join(nitro.options.output.publicDir, "llms-full.txt");
85
- await writeFile(llmsFullTxtPath, finalLlmsFullTxt, "utf-8");
86
- const sizeKb = (Buffer.byteLength(finalLlmsFullTxt, "utf-8") / 1024).toFixed(2);
87
- generatedFiles.push({ path: "llms-full.txt", size: `${sizeKb}kb` });
88
- nitro._prerenderedRoutes.push({
70
+ },
71
+ {
89
72
  route: "/llms-full.txt",
90
73
  fileName: llmsFullTxtPath,
91
74
  generateTimeMS: 0
92
- });
93
- }
94
- if (generatedFiles.length > 0) {
95
- const elapsed = Date.now() - startTime;
96
- const fileList = generatedFiles.map((f) => `${f.path} (${f.size})`).join(" and ");
97
- logger.info(`Generated ${fileList} in ${elapsed}ms`);
98
- }
75
+ }
76
+ );
77
+ const elapsed = Date.now() - startTime;
78
+ const llmsKb = (llmsStats.size / 1024).toFixed(2);
79
+ const llmsFullKb = (llmsFullStats.size / 1024).toFixed(2);
80
+ logger.info(`Generated llms.txt (${llmsKb}kb) and llms-full.txt (${llmsFullKb}kb) from ${pageCount} pages in ${elapsed}ms`);
99
81
  });
100
82
  });
101
83
  }
@@ -114,16 +96,7 @@ const module = defineNuxtModule({
114
96
  },
115
97
  moduleDependencies: {
116
98
  "@nuxtjs/robots": {
117
- version: ">=5.6.0",
118
- defaults: {
119
- groups: [
120
- {
121
- userAgent: "*",
122
- contentUsage: ["train-ai=y"],
123
- contentSignal: ["ai-train=yes", "search=yes", "ai-input=yes"]
124
- }
125
- ]
126
- }
99
+ version: ">=5.6.0"
127
100
  },
128
101
  "nuxt-site-config": {
129
102
  version: ">=3"
@@ -168,6 +141,13 @@ const module = defineNuxtModule({
168
141
  resolve$1("./runtime/server/utils"),
169
142
  resolve$1("./runtime/server/mcp")
170
143
  );
144
+ if (typeof config.contentSignal === "object") {
145
+ nuxt.options.robots.groups.push({
146
+ userAgent: "*",
147
+ contentUsage: [`train-ai=${config.contentSignal.aiTrain ? "y" : "n"}`],
148
+ contentSignal: [`ai-train=${config.contentSignal.aiTrain ? "yes" : "no"}`, `search=${config.contentSignal.search ? "yes" : "no"}`, `ai-input=${config.contentSignal.aiInput ? "yes" : "no"}`]
149
+ });
150
+ }
171
151
  addTypeTemplate({
172
152
  filename: "module/nuxt-ai-ready.d.ts",
173
153
  getContents: (data) => {
@@ -195,7 +175,7 @@ export {}
195
175
  if (config.bulkRoute !== false) {
196
176
  const resolvedBulkRoute = withSiteUrl(config.bulkRoute);
197
177
  defaultLlmsTxtSections.push({
198
- title: "AI Tools",
178
+ title: "LLM Tools",
199
179
  links: [
200
180
  {
201
181
  title: "Bulk Data",
@@ -228,7 +208,7 @@ Returns JSONL (newline-delimited JSON) with all indexed content.`
228
208
  defaultLlmsTxtSections[0].links.push(mcpLink);
229
209
  } else {
230
210
  defaultLlmsTxtSections.push({
231
- title: "AI Tools - API Endpoints",
211
+ title: "LLM Tools",
232
212
  links: [mcpLink]
233
213
  });
234
214
  }
@@ -295,6 +275,9 @@ Returns JSONL (newline-delimited JSON) with all indexed content.`
295
275
  if (typeof route._sitemap !== "undefined" && !route._sitemap) {
296
276
  return;
297
277
  }
278
+ if (isPathFile(route.route)) {
279
+ return;
280
+ }
298
281
  let title = "";
299
282
  let description = "";
300
283
  const headings = [];
@@ -44,6 +44,24 @@ export interface ModuleOptions {
44
44
  * Structured llms.txt configuration
45
45
  */
46
46
  llmsTxt?: LlmsTxtConfig;
47
+ /**
48
+ * Content Signal Directives
49
+ */
50
+ contentSignal?: false | {
51
+ /**
52
+ * Allow Training or fine-tuning AI models.
53
+ */
54
+ aiTrain?: boolean;
55
+ /**
56
+ * Allow building a search index and providing search results (e.g., returning hyperlinks and short excerpts from your website's contents).
57
+ * Search does not include providing AI-generated search summaries.
58
+ */
59
+ search?: boolean;
60
+ /**
61
+ * Inputting content into one or more AI models (e.g., retrieval augmented generation, grounding, or other real-time taking of content for generative AI search answers).
62
+ */
63
+ aiInput?: boolean;
64
+ };
47
65
  }
48
66
  /**
49
67
  * Individual chunk entry in bulk.jsonl (one per chunk)
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "nuxt-ai-ready",
3
3
  "type": "module",
4
- "version": "0.0.3",
4
+ "version": "0.1.2",
5
5
  "description": "Best practice AI & LLM discoverability for Nuxt sites.",
6
6
  "author": {
7
7
  "name": "Harlan Wilton",
@@ -35,7 +35,7 @@
35
35
  "@nuxt/kit": "4.2.1",
36
36
  "consola": "^3.4.2",
37
37
  "defu": "^6.1.4",
38
- "mdream": "^0.14.0",
38
+ "mdream": "^0.15.0",
39
39
  "minimatch": "^10.1.1",
40
40
  "nuxt-site-config": "^3.2.11",
41
41
  "pathe": "^2.0.3",
@@ -55,10 +55,10 @@
55
55
  "@nuxtjs/color-mode": "^4.0.0",
56
56
  "@nuxtjs/eslint-config-typescript": "^12.1.0",
57
57
  "@nuxtjs/i18n": "^10.2.1",
58
- "@nuxtjs/mcp-toolkit": "^0.4.1",
58
+ "@nuxtjs/mcp-toolkit": "^0.5.1",
59
59
  "@nuxtjs/robots": "^5.6.0",
60
60
  "@nuxtjs/sitemap": "^7.4.7",
61
- "@vitest/coverage-v8": "^4.0.14",
61
+ "@vitest/coverage-v8": "^4.0.15",
62
62
  "@vueuse/nuxt": "^14.1.0",
63
63
  "better-sqlite3": "^12.5.0",
64
64
  "bumpp": "^10.3.2",
@@ -71,7 +71,7 @@
71
71
  "playwright-core": "^1.57.0",
72
72
  "postgres": "^3.4.7",
73
73
  "typescript": "^5.9.3",
74
- "vitest": "^4.0.14",
74
+ "vitest": "^4.0.15",
75
75
  "vue": "^3.5.25",
76
76
  "vue-router": "^4.6.3",
77
77
  "vue-tsc": "^3.1.5",