nuxt-ai-ready 0.7.0 → 0.7.1

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
@@ -43,14 +43,10 @@ interface ModulePublicRuntimeConfig {
43
43
  enabled: boolean;
44
44
  ttl: number;
45
45
  batchSize: number;
46
- secret?: string;
47
46
  pruneTtl: number;
48
47
  };
49
- indexNow?: {
50
- enabled: boolean;
51
- key?: string;
52
- host?: string;
53
- };
48
+ runtimeSyncSecret?: string;
49
+ indexNowKey?: string;
54
50
  }
55
51
  declare const _default: _nuxt_schema.NuxtModule<ModuleOptions, ModuleOptions, false>;
56
52
 
package/dist/module.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "nuxt": ">=4.0.0"
5
5
  },
6
6
  "configKey": "aiReady",
7
- "version": "0.7.0",
7
+ "version": "0.7.1",
8
8
  "builder": {
9
9
  "@nuxt/module-builder": "1.0.2",
10
10
  "unbuild": "3.6.1"
package/dist/module.mjs CHANGED
@@ -541,16 +541,27 @@ const module$1 = defineNuxtModule({
541
541
  nuxt.hooks.hook("nitro:config", (nitroConfig) => {
542
542
  nitroConfig.experimental = nitroConfig.experimental || {};
543
543
  nitroConfig.experimental.asyncContext = true;
544
- const runtimeSyncEnabled2 = config.runtimeSync?.enabled ?? false;
545
- const cron = config.runtimeSync?.cron;
546
- if (runtimeSyncEnabled2 && cron) {
547
- nitroConfig.tasks = nitroConfig.tasks || {};
548
- nitroConfig.tasks["ai-ready:index"] = {
549
- handler: resolve("./runtime/server/tasks/ai-ready-index")
550
- };
551
- nitroConfig.scheduledTasks = nitroConfig.scheduledTasks || {};
552
- nitroConfig.scheduledTasks[cron] = nitroConfig.scheduledTasks[cron] || [];
553
- nitroConfig.scheduledTasks[cron].push("ai-ready:index");
544
+ if (config.cron) {
545
+ const cronSchedule = "* * * * *";
546
+ const isVercel = nitroConfig.preset === "vercel" || nitroConfig.preset === "vercel-edge";
547
+ if (isVercel) {
548
+ nitroConfig.vercel = nitroConfig.vercel || {};
549
+ nitroConfig.vercel.config = nitroConfig.vercel.config || {};
550
+ nitroConfig.vercel.config.crons = nitroConfig.vercel.config.crons || [];
551
+ nitroConfig.vercel.config.crons.push({
552
+ schedule: cronSchedule,
553
+ path: "/__ai-ready/cron"
554
+ });
555
+ } else {
556
+ nitroConfig.experimental.tasks = true;
557
+ nitroConfig.tasks = nitroConfig.tasks || {};
558
+ nitroConfig.tasks["ai-ready:cron"] = {
559
+ handler: resolve("./runtime/server/tasks/ai-ready-cron")
560
+ };
561
+ nitroConfig.scheduledTasks = nitroConfig.scheduledTasks || {};
562
+ nitroConfig.scheduledTasks[cronSchedule] = nitroConfig.scheduledTasks[cronSchedule] || [];
563
+ nitroConfig.scheduledTasks[cronSchedule].push("ai-ready:cron");
564
+ }
554
565
  }
555
566
  nitroConfig.virtual = nitroConfig.virtual || {};
556
567
  nitroConfig.virtual["#ai-ready-virtual/read-page-data.mjs"] = `
@@ -592,9 +603,9 @@ export async function readPageDataFromFilesystem() {
592
603
  export const errorRoutes = []`;
593
604
  });
594
605
  const database = refineDatabaseConfig(config.database || {}, nuxt.options.rootDir);
595
- const runtimeSyncEnabled = config.runtimeSync?.enabled ?? false;
596
- const indexNowKey = config.indexNow?.key || process.env.NUXT_AI_READY_INDEXNOW_KEY;
597
- const indexNowEnabled = !!(config.indexNow?.enabled !== false && indexNowKey);
606
+ const runtimeSyncConfig = typeof config.runtimeSync === "object" ? config.runtimeSync : {};
607
+ const runtimeSyncEnabled = !!config.runtimeSync || !!config.cron;
608
+ const indexNowKey = config.indexNowKey || process.env.NUXT_AI_READY_INDEX_NOW_KEY;
598
609
  nuxt.options.runtimeConfig["nuxt-ai-ready"] = {
599
610
  version: version || "0.0.0",
600
611
  debug: config.debug || false,
@@ -609,16 +620,12 @@ export const errorRoutes = []`;
609
620
  database,
610
621
  runtimeSync: {
611
622
  enabled: runtimeSyncEnabled,
612
- ttl: config.runtimeSync?.ttl ?? 3600,
613
- batchSize: config.runtimeSync?.batchSize ?? 20,
614
- secret: config.runtimeSync?.secret,
615
- pruneTtl: config.runtimeSync?.pruneTtl ?? 0
623
+ ttl: runtimeSyncConfig.ttl ?? 3600,
624
+ batchSize: runtimeSyncConfig.batchSize ?? 20,
625
+ pruneTtl: runtimeSyncConfig.pruneTtl ?? 0
616
626
  },
617
- indexNow: indexNowEnabled ? {
618
- enabled: true,
619
- key: indexNowKey,
620
- host: config.indexNow?.host || "api.indexnow.org"
621
- } : void 0
627
+ runtimeSyncSecret: config.runtimeSyncSecret,
628
+ indexNowKey
622
629
  };
623
630
  nuxt.options.nitro.plugins = nuxt.options.nitro.plugins || [];
624
631
  nuxt.options.nitro.plugins.push(resolve("./runtime/server/plugins/db-restore"));
@@ -648,13 +655,16 @@ export const errorRoutes = []`;
648
655
  addServerHandler({ route: "/__ai-ready/poll", method: "post", handler: resolve("./runtime/server/routes/__ai-ready/poll.post") });
649
656
  addServerHandler({ route: "/__ai-ready/prune", method: "post", handler: resolve("./runtime/server/routes/__ai-ready/prune.post") });
650
657
  }
651
- if (indexNowEnabled && indexNowKey) {
658
+ if (indexNowKey) {
652
659
  addServerHandler({ route: `/${indexNowKey}.txt`, handler: resolve("./runtime/server/routes/indexnow-key.get") });
653
660
  addServerHandler({ route: "/__ai-ready/indexnow", method: "post", handler: resolve("./runtime/server/routes/__ai-ready/indexnow.post") });
654
661
  if (!runtimeSyncEnabled) {
655
662
  addServerHandler({ route: "/__ai-ready/status", handler: resolve("./runtime/server/routes/__ai-ready/status.get") });
656
663
  }
657
664
  }
665
+ if (config.cron) {
666
+ addServerHandler({ route: "/__ai-ready/cron", handler: resolve("./runtime/server/routes/__ai-ready/cron.get") });
667
+ }
658
668
  const isStatic = nuxt.options.nitro.static || nuxt.options._generate || false;
659
669
  const hasPrerenderedRoutes = nuxt.options.nitro.prerender?.routes?.length;
660
670
  const isSPA = nuxt.options.ssr === false;
@@ -0,0 +1,25 @@
1
+ export declare const tools: readonly [import("@nuxtjs/mcp-toolkit").McpToolDefinition<Readonly<{
2
+ [k: string]: import("zod/v4/core").$ZodType<unknown, unknown, import("zod/v4/core").$ZodTypeInternals<unknown, unknown>>;
3
+ }>, Readonly<{
4
+ [k: string]: import("zod/v4/core").$ZodType<unknown, unknown, import("zod/v4/core").$ZodTypeInternals<unknown, unknown>>;
5
+ }>>, import("@nuxtjs/mcp-toolkit").McpToolDefinition<Readonly<{
6
+ [k: string]: import("zod/v4/core").$ZodType<unknown, unknown, import("zod/v4/core").$ZodTypeInternals<unknown, unknown>>;
7
+ }>, Readonly<{
8
+ [k: string]: import("zod/v4/core").$ZodType<unknown, unknown, import("zod/v4/core").$ZodTypeInternals<unknown, unknown>>;
9
+ }>>];
10
+ export declare const resources: readonly [{
11
+ uri: string;
12
+ name: string;
13
+ description: string;
14
+ metadata: {
15
+ mimeType: string;
16
+ };
17
+ cache: "1h";
18
+ handler(uri: URL): Promise<{
19
+ contents: {
20
+ uri: string;
21
+ mimeType: string;
22
+ text: string;
23
+ }[];
24
+ }>;
25
+ }];
@@ -0,0 +1,5 @@
1
+ import pages from "./server/mcp/resources/pages.js";
2
+ import listPages from "./server/mcp/tools/list-pages.js";
3
+ import searchPages from "./server/mcp/tools/search-pages.js";
4
+ export const tools = [listPages, searchPages];
5
+ export const resources = [pages];
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Cron endpoint for platforms that use HTTP-based cron (Vercel, etc.)
3
+ */
4
+ declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<import("../../utils/runCron.js").CronResult>>;
5
+ export default _default;
@@ -0,0 +1,3 @@
1
+ import { eventHandler } from "h3";
2
+ import { runCron } from "../../utils/runCron.js";
3
+ export default eventHandler((event) => runCron(event));
@@ -3,13 +3,13 @@ import { useRuntimeConfig } from "nitropack/runtime";
3
3
  import { syncToIndexNow } from "../../utils/indexnow.js";
4
4
  export default eventHandler(async (event) => {
5
5
  const config = useRuntimeConfig(event)["nuxt-ai-ready"];
6
- if (!config.indexNow?.enabled) {
7
- throw createError({ statusCode: 400, message: "IndexNow not enabled" });
6
+ if (!config.indexNowKey) {
7
+ throw createError({ statusCode: 400, message: "IndexNow not configured" });
8
8
  }
9
9
  const query = getQuery(event);
10
- if (config.runtimeSync?.secret) {
10
+ if (config.runtimeSyncSecret) {
11
11
  const secret = query.secret;
12
- if (secret !== config.runtimeSync.secret) {
12
+ if (secret !== config.runtimeSyncSecret) {
13
13
  throw createError({ statusCode: 401, message: "Unauthorized" });
14
14
  }
15
15
  }
@@ -4,9 +4,9 @@ import { batchIndexPages } from "../../utils/batchIndex.js";
4
4
  export default eventHandler(async (event) => {
5
5
  const config = useRuntimeConfig()["nuxt-ai-ready"];
6
6
  const query = getQuery(event);
7
- if (config.runtimeSync.secret) {
7
+ if (config.runtimeSyncSecret) {
8
8
  const secret = query.secret;
9
- if (secret !== config.runtimeSync.secret) {
9
+ if (secret !== config.runtimeSyncSecret) {
10
10
  throw createError({ statusCode: 401, message: "Unauthorized" });
11
11
  }
12
12
  }
@@ -5,9 +5,9 @@ export default eventHandler(async (event) => {
5
5
  const config = useRuntimeConfig()["nuxt-ai-ready"];
6
6
  const query = getQuery(event);
7
7
  const dry = query.dry === "true" || query.dry === "1";
8
- if (!dry && config.runtimeSync.secret) {
8
+ if (!dry && config.runtimeSyncSecret) {
9
9
  const secret = query.secret;
10
- if (secret !== config.runtimeSync.secret) {
10
+ if (secret !== config.runtimeSyncSecret) {
11
11
  throw createError({ statusCode: 401, message: "Unauthorized" });
12
12
  }
13
13
  }
@@ -12,7 +12,7 @@ export default eventHandler(async (event) => {
12
12
  indexed: total - pending,
13
13
  pending
14
14
  };
15
- if (config.indexNow?.enabled) {
15
+ if (config.indexNowKey) {
16
16
  const [indexNowPending, indexNowStats] = await Promise.all([
17
17
  countPagesNeedingIndexNowSync(event),
18
18
  getIndexNowStats(event)
@@ -0,0 +1,2 @@
1
+ declare const _default: import("nitropack/types").Task<import("../utils/runCron.js").CronResult>;
2
+ export default _default;
@@ -0,0 +1,17 @@
1
+ import { defineTask } from "nitropack/runtime";
2
+ import { runCron } from "../utils/runCron.js";
3
+ export default defineTask({
4
+ meta: {
5
+ name: "ai-ready:cron",
6
+ description: "Scheduled task for AI Ready - runs indexing and IndexNow sync"
7
+ },
8
+ async run({ payload }) {
9
+ const mockEvent = {
10
+ $fetch: globalThis.$fetch
11
+ };
12
+ const result = await runCron(mockEvent, {
13
+ batchSize: payload?.limit
14
+ });
15
+ return { result };
16
+ }
17
+ });
@@ -5,11 +5,6 @@ export interface IndexNowResult {
5
5
  remaining: number;
6
6
  error?: string;
7
7
  }
8
- export interface IndexNowConfig {
9
- enabled?: boolean;
10
- key?: string;
11
- host?: string;
12
- }
13
8
  /**
14
9
  * Submit URLs to IndexNow API
15
10
  */
@@ -38,7 +38,7 @@ export async function submitToIndexNow(routes, config, siteUrl) {
38
38
  export async function syncToIndexNow(event, limit = 100) {
39
39
  const config = useRuntimeConfig(event)["nuxt-ai-ready"];
40
40
  const siteConfig = useSiteConfig();
41
- if (!config.indexNow?.enabled || !config.indexNow?.key) {
41
+ if (!config.indexNowKey) {
42
42
  return { success: false, submitted: 0, remaining: 0, error: "IndexNow not configured" };
43
43
  }
44
44
  if (!siteConfig.url) {
@@ -49,7 +49,7 @@ export async function syncToIndexNow(event, limit = 100) {
49
49
  return { success: true, submitted: 0, remaining: 0 };
50
50
  }
51
51
  const routes = pages.map((p) => p.route);
52
- const result = await submitToIndexNow(routes, config.indexNow, siteConfig.url);
52
+ const result = await submitToIndexNow(routes, { key: config.indexNowKey }, siteConfig.url);
53
53
  if (result.success) {
54
54
  await markIndexNowSynced(event, routes);
55
55
  await updateIndexNowStats(event, routes.length);
@@ -0,0 +1,20 @@
1
+ import type { H3Event } from 'h3';
2
+ export interface CronResult {
3
+ index?: {
4
+ indexed: number;
5
+ remaining: number;
6
+ errors?: string[];
7
+ complete: boolean;
8
+ };
9
+ indexNow?: {
10
+ submitted: number;
11
+ remaining: number;
12
+ error?: string;
13
+ };
14
+ }
15
+ /**
16
+ * Run cron job logic - shared between scheduled task and HTTP endpoint
17
+ */
18
+ export declare function runCron(event: H3Event, options?: {
19
+ batchSize?: number;
20
+ }): Promise<CronResult>;
@@ -0,0 +1,32 @@
1
+ import { useRuntimeConfig } from "nitropack/runtime";
2
+ import { batchIndexPages } from "./batchIndex.js";
3
+ import { syncToIndexNow } from "./indexnow.js";
4
+ export async function runCron(event, options) {
5
+ const config = useRuntimeConfig()["nuxt-ai-ready"];
6
+ const results = {};
7
+ if (config.runtimeSync.enabled) {
8
+ const limit = options?.batchSize ?? config.runtimeSync.batchSize;
9
+ const indexResult = await batchIndexPages(event, {
10
+ limit,
11
+ all: false
12
+ });
13
+ results.index = {
14
+ indexed: indexResult.indexed,
15
+ remaining: indexResult.remaining,
16
+ errors: indexResult.errors.length > 0 ? indexResult.errors : void 0,
17
+ complete: indexResult.complete
18
+ };
19
+ }
20
+ if (config.indexNowKey) {
21
+ const indexNowResult = await syncToIndexNow(event, 100).catch((err) => {
22
+ console.warn("[ai-ready:cron] IndexNow sync failed:", err.message);
23
+ return { success: false, submitted: 0, remaining: 0, error: err.message };
24
+ });
25
+ results.indexNow = {
26
+ submitted: indexNowResult.submitted,
27
+ remaining: indexNowResult.remaining,
28
+ error: indexNowResult.error
29
+ };
30
+ }
31
+ return results;
32
+ }
@@ -103,18 +103,31 @@ export interface ModuleOptions {
103
103
  */
104
104
  authToken?: string;
105
105
  };
106
+ /**
107
+ * Enable scheduled cron task (runs every minute)
108
+ * When true, automatically enables runtimeSync for background indexing
109
+ * Also runs IndexNow sync if indexNowKey is configured
110
+ */
111
+ cron?: boolean;
112
+ /**
113
+ * IndexNow API key for instant search engine notifications
114
+ * When set, enables IndexNow submissions to Bing, Yandex, Naver, Seznam
115
+ * Get one from https://www.bing.com/indexnow
116
+ * Can also be set via NUXT_AI_READY_INDEX_NOW_KEY env var
117
+ */
118
+ indexNowKey?: string;
119
+ /**
120
+ * Secret token for authenticating runtime sync endpoints
121
+ * When set, requires ?secret=<token> query param for poll/prune/indexnow endpoints
122
+ */
123
+ runtimeSyncSecret?: string;
106
124
  /**
107
125
  * Runtime sync configuration (opt-in for dynamic content sites)
108
126
  * When enabled, pages are re-indexed at runtime from sitemap
109
- * @default disabled - prerendered data is used
127
+ * Set to `true` for defaults or object to customize
128
+ * @default false - prerendered data is used
110
129
  */
111
- runtimeSync?: {
112
- /**
113
- * Enable runtime sync
114
- * When false (default), runtime uses prerendered data only
115
- * @default false
116
- */
117
- enabled?: boolean;
130
+ runtimeSync?: boolean | {
118
131
  /**
119
132
  * TTL for refresh in seconds (sitemap + page re-indexing)
120
133
  * Controls how often to refresh sitemap routes and re-index stale pages
@@ -126,16 +139,6 @@ export interface ModuleOptions {
126
139
  * @default 20
127
140
  */
128
141
  batchSize?: number;
129
- /**
130
- * Cron expression for scheduled indexing (e.g. every 5 minutes)
131
- * When set, enables automatic background indexing via Nitro task
132
- */
133
- cron?: string;
134
- /**
135
- * Secret token for authenticating poll endpoint
136
- * When set, requires ?secret=<token> query param
137
- */
138
- secret?: string;
139
142
  /**
140
143
  * TTL for pruning stale routes in seconds
141
144
  * Routes not seen in sitemap for longer than this are deleted
@@ -144,27 +147,6 @@ export interface ModuleOptions {
144
147
  */
145
148
  pruneTtl?: number;
146
149
  };
147
- /**
148
- * IndexNow configuration for instant search engine notifications
149
- * Submits changed URLs to Bing, Yandex, Naver, Seznam via IndexNow protocol
150
- */
151
- indexNow?: {
152
- /**
153
- * Enable IndexNow submissions
154
- * @default false
155
- */
156
- enabled?: boolean;
157
- /**
158
- * Your IndexNow API key
159
- * Get one from https://www.bing.com/indexnow
160
- */
161
- key?: string;
162
- /**
163
- * IndexNow endpoint host
164
- * @default 'api.indexnow.org'
165
- */
166
- host?: string;
167
- };
168
150
  }
169
151
  /**
170
152
  * Page-level entry for discovery and metadata queries
package/mcp.d.ts ADDED
@@ -0,0 +1,4 @@
1
+ import type { McpResourceDefinition, McpToolDefinition } from '@nuxtjs/mcp-toolkit'
2
+
3
+ export declare const tools: readonly [McpToolDefinition, McpToolDefinition]
4
+ export declare const resources: readonly [McpResourceDefinition]
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "nuxt-ai-ready",
3
3
  "type": "module",
4
- "version": "0.7.0",
4
+ "version": "0.7.1",
5
5
  "description": "Best practice AI & LLM discoverability for Nuxt sites.",
6
6
  "author": {
7
7
  "name": "Harlan Wilton",
@@ -25,11 +25,16 @@
25
25
  ".": {
26
26
  "types": "./dist/types.d.mts",
27
27
  "import": "./dist/module.mjs"
28
+ },
29
+ "./mcp": {
30
+ "types": "./mcp.d.ts",
31
+ "import": "./dist/runtime/mcp.js"
28
32
  }
29
33
  },
30
34
  "main": "./dist/module.mjs",
31
35
  "files": [
32
- "dist"
36
+ "dist",
37
+ "mcp.d.ts"
33
38
  ],
34
39
  "peerDependencies": {
35
40
  "@nuxtjs/sitemap": "^7.0.0",
@@ -1,11 +0,0 @@
1
- declare const _default: import("nitropack/types").Task<{
2
- indexed: number;
3
- remaining: number;
4
- errors: string[];
5
- complete: boolean;
6
- indexNow: {
7
- submitted: number;
8
- error: any;
9
- } | undefined;
10
- }>;
11
- export default _default;
@@ -1,39 +0,0 @@
1
- import { defineTask, useRuntimeConfig } from "nitropack/runtime";
2
- import { batchIndexPages } from "../utils/batchIndex.js";
3
- import { syncToIndexNow } from "../utils/indexnow.js";
4
- export default defineTask({
5
- meta: {
6
- name: "ai-ready:index",
7
- description: "Index pending pages for AI Ready"
8
- },
9
- async run({ payload }) {
10
- const config = useRuntimeConfig()["nuxt-ai-ready"];
11
- const limit = payload?.limit ?? config.runtimeSync.batchSize;
12
- const mockEvent = {
13
- $fetch: globalThis.$fetch
14
- };
15
- const result = await batchIndexPages(mockEvent, {
16
- limit,
17
- all: false
18
- });
19
- let indexNowResult;
20
- if (config.indexNow?.enabled) {
21
- indexNowResult = await syncToIndexNow(mockEvent, 100).catch((err) => {
22
- console.warn("[ai-ready:index] IndexNow sync failed:", err.message);
23
- return { success: false, submitted: 0, error: err.message };
24
- });
25
- }
26
- return {
27
- result: {
28
- indexed: result.indexed,
29
- remaining: result.remaining,
30
- errors: result.errors,
31
- complete: result.complete,
32
- indexNow: indexNowResult ? {
33
- submitted: indexNowResult.submitted,
34
- error: indexNowResult.error
35
- } : void 0
36
- }
37
- };
38
- }
39
- });