@ai-me-chat/nextjs 0.0.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 AI-Me Contributors
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/dist/index.cjs ADDED
@@ -0,0 +1,262 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ createAIMeHandler: () => createAIMeHandler,
34
+ filterRoutes: () => filterRoutes,
35
+ scanRoutes: () => scanRoutes
36
+ });
37
+ module.exports = __toCommonJS(index_exports);
38
+
39
+ // src/scanner.ts
40
+ var fs = __toESM(require("fs"), 1);
41
+ var path = __toESM(require("path"), 1);
42
+ var HTTP_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE"];
43
+ var ROUTE_FILE_NAMES = ["route.ts", "route.js", "route.tsx", "route.jsx"];
44
+ function scanRoutes(appDir) {
45
+ const apiDir = path.join(appDir, "api");
46
+ if (!fs.existsSync(apiDir)) {
47
+ return [];
48
+ }
49
+ const routes = [];
50
+ walkDirectory(apiDir, appDir, routes);
51
+ return routes.sort((a, b) => a.path.localeCompare(b.path));
52
+ }
53
+ function walkDirectory(dir, appDir, routes) {
54
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
55
+ for (const entry of entries) {
56
+ const fullPath = path.join(dir, entry.name);
57
+ if (entry.isDirectory()) {
58
+ walkDirectory(fullPath, appDir, routes);
59
+ } else if (ROUTE_FILE_NAMES.includes(entry.name)) {
60
+ const route = parseRouteFile(fullPath, appDir);
61
+ if (route && route.methods.length > 0) {
62
+ routes.push(route);
63
+ }
64
+ }
65
+ }
66
+ }
67
+ function parseRouteFile(filePath, appDir) {
68
+ const content = fs.readFileSync(filePath, "utf-8");
69
+ const methods = extractHttpMethods(content);
70
+ if (methods.length === 0) {
71
+ return null;
72
+ }
73
+ const relativePath = path.relative(appDir, path.dirname(filePath));
74
+ const apiPath = "/" + relativePath.split(path.sep).join("/");
75
+ const pathParams = extractPathParams(apiPath);
76
+ return {
77
+ path: cleanPath(apiPath),
78
+ methods,
79
+ pathParams,
80
+ filePath: path.relative(path.resolve(appDir, ".."), filePath)
81
+ };
82
+ }
83
+ function extractHttpMethods(content) {
84
+ const methods = [];
85
+ for (const method of HTTP_METHODS) {
86
+ const patterns = [
87
+ // export async function GET(
88
+ new RegExp(`export\\s+(async\\s+)?function\\s+${method}\\s*\\(`),
89
+ // export const GET =
90
+ new RegExp(`export\\s+const\\s+${method}\\s*=`),
91
+ // export { handler as GET }
92
+ new RegExp(`export\\s*\\{[^}]*\\bas\\s+${method}\\b[^}]*\\}`)
93
+ ];
94
+ if (patterns.some((p) => p.test(content))) {
95
+ methods.push(method);
96
+ }
97
+ }
98
+ return methods;
99
+ }
100
+ function extractPathParams(routePath) {
101
+ const params = [];
102
+ const matches = routePath.matchAll(/\[([^\]]+)\]/g);
103
+ for (const match of matches) {
104
+ params.push(match[1]);
105
+ }
106
+ return params;
107
+ }
108
+ function cleanPath(routePath) {
109
+ return routePath.replace(/\/\([^)]+\)/g, "").replace(/\[\.\.\.(\w+)\]/g, ":$1*").replace(/\[(\w+)\]/g, ":$1");
110
+ }
111
+
112
+ // src/filter.ts
113
+ var import_picomatch = __toESM(require("picomatch"), 1);
114
+ function filterRoutes(routes, config) {
115
+ let filtered = routes;
116
+ if (config.include && config.include.length > 0) {
117
+ const isIncluded = (0, import_picomatch.default)(config.include);
118
+ filtered = filtered.filter((r) => isIncluded(r.path));
119
+ }
120
+ if (config.exclude && config.exclude.length > 0) {
121
+ const isExcluded = (0, import_picomatch.default)(config.exclude);
122
+ filtered = filtered.filter((r) => !isExcluded(r.path));
123
+ }
124
+ return filtered;
125
+ }
126
+
127
+ // src/handler.ts
128
+ var import_ai = require("ai");
129
+ var import_core = require("@ai-me-chat/core");
130
+ function createAIMeHandler(config) {
131
+ let toolDefinitions = null;
132
+ let toolsPromise = null;
133
+ async function getToolDefinitions(appDir) {
134
+ if (toolDefinitions) return toolDefinitions;
135
+ if (toolsPromise) return toolsPromise;
136
+ toolsPromise = initTools(appDir);
137
+ toolDefinitions = await toolsPromise;
138
+ toolsPromise = null;
139
+ return toolDefinitions;
140
+ }
141
+ async function initTools(appDir) {
142
+ if (config.discovery.mode === "openapi") {
143
+ let spec;
144
+ if (config.discovery.spec) {
145
+ spec = config.discovery.spec;
146
+ } else if (config.discovery.specUrl) {
147
+ spec = await (0, import_core.fetchOpenAPISpec)(config.discovery.specUrl);
148
+ } else {
149
+ throw new Error(
150
+ 'OpenAPI discovery mode requires either "spec" (inline object) or "specUrl" (remote URL) in discovery config'
151
+ );
152
+ }
153
+ const tools = (0, import_core.generateToolsFromOpenAPI)(spec, config.confirmation);
154
+ if (config.discovery.include || config.discovery.exclude) {
155
+ const routes2 = tools.map((t) => ({
156
+ path: t.path ?? "",
157
+ methods: [t.httpMethod ?? "GET"],
158
+ pathParams: [],
159
+ filePath: ""
160
+ }));
161
+ const filtered = filterRoutes(routes2, config.discovery);
162
+ const filteredPaths = new Set(
163
+ filtered.flatMap((r) => r.methods.map((m) => `${m}:${r.path}`))
164
+ );
165
+ return tools.filter((t) => filteredPaths.has(`${t.httpMethod}:${t.path}`));
166
+ }
167
+ return tools;
168
+ }
169
+ let routes = scanRoutes(appDir);
170
+ routes = filterRoutes(routes, config.discovery);
171
+ return (0, import_core.generateToolDefinitions)(routes, config.confirmation);
172
+ }
173
+ async function handler(req) {
174
+ const session = await config.getSession(req);
175
+ if (!session) {
176
+ return new Response("Unauthorized", { status: 401 });
177
+ }
178
+ const url = new URL(req.url);
179
+ if (req.method === "GET" && url.pathname.endsWith("/tools")) {
180
+ const appDir = process.cwd() + "/app";
181
+ let tools;
182
+ try {
183
+ tools = await getToolDefinitions(appDir);
184
+ } catch (error) {
185
+ return new Response(
186
+ JSON.stringify({ error: error instanceof Error ? error.message : "Tool discovery failed" }),
187
+ { status: 500, headers: { "Content-Type": "application/json" } }
188
+ );
189
+ }
190
+ return Response.json(
191
+ tools.map((t) => ({
192
+ name: t.name,
193
+ description: t.description,
194
+ httpMethod: t.httpMethod,
195
+ path: t.path,
196
+ requiresConfirmation: t.requiresConfirmation
197
+ }))
198
+ );
199
+ }
200
+ if (req.method === "GET" && url.pathname.endsWith("/health")) {
201
+ return Response.json({ status: "ok", version: "0.0.1" });
202
+ }
203
+ if (req.method === "POST") {
204
+ return handleChat(req, config, session, getToolDefinitions);
205
+ }
206
+ return new Response("Not Found", { status: 404 });
207
+ }
208
+ return handler;
209
+ }
210
+ async function handleChat(req, config, session, getToolDefinitions) {
211
+ const { messages } = await req.json();
212
+ const appDir = process.cwd() + "/app";
213
+ const toolDefs = await getToolDefinitions(appDir);
214
+ const baseUrl = new URL(req.url).origin;
215
+ const executionContext = {
216
+ baseUrl,
217
+ headers: {
218
+ cookie: req.headers.get("cookie") ?? "",
219
+ authorization: req.headers.get("authorization") ?? ""
220
+ }
221
+ };
222
+ const aiTools = {};
223
+ for (const toolDef of toolDefs) {
224
+ const def = toolDef;
225
+ aiTools[def.name] = (0, import_ai.tool)({
226
+ description: def.description,
227
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
228
+ inputSchema: def.parameters,
229
+ // Disable strict schema validation for OpenAI-compatible providers
230
+ // (e.g., Groq) that reject additionalProperties in tool schemas
231
+ strict: false,
232
+ execute: async (params) => {
233
+ const result2 = await (0, import_core.executeTool)(
234
+ def,
235
+ params,
236
+ executionContext
237
+ );
238
+ return result2.response;
239
+ }
240
+ });
241
+ }
242
+ const modelMessages = await (0, import_ai.convertToModelMessages)(messages);
243
+ const maxHistory = config.maxHistoryMessages ?? 20;
244
+ const trimmedMessages = modelMessages.length > maxHistory ? modelMessages.slice(-maxHistory) : modelMessages;
245
+ const result = (0, import_ai.streamText)({
246
+ model: config.model,
247
+ system: config.systemPrompt ?? `You are an AI assistant for this application. You can help users query data and perform actions. User: ${session.user.id}${session.user.role ? ` (role: ${session.user.role})` : ""}`,
248
+ messages: trimmedMessages,
249
+ tools: aiTools,
250
+ stopWhen: (0, import_ai.stepCountIs)(5),
251
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
252
+ ...config.providerOptions ? { providerOptions: config.providerOptions } : {}
253
+ });
254
+ return result.toUIMessageStreamResponse();
255
+ }
256
+ // Annotate the CommonJS export names for ESM import in node:
257
+ 0 && (module.exports = {
258
+ createAIMeHandler,
259
+ filterRoutes,
260
+ scanRoutes
261
+ });
262
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/scanner.ts","../src/filter.ts","../src/handler.ts"],"sourcesContent":["export { scanRoutes } from \"./scanner.js\";\nexport { filterRoutes } from \"./filter.js\";\nexport { createAIMeHandler } from \"./handler.js\";\n","import * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport type { DiscoveredRoute } from \"@ai-me-chat/core\";\n\nconst HTTP_METHODS = [\"GET\", \"POST\", \"PUT\", \"PATCH\", \"DELETE\"] as const;\n\nconst ROUTE_FILE_NAMES = [\"route.ts\", \"route.js\", \"route.tsx\", \"route.jsx\"];\n\n/**\n * Scan a Next.js App Router directory for API routes.\n * Finds all route.ts/route.js files under `appDir/api/` and extracts\n * HTTP methods and path parameters.\n */\nexport function scanRoutes(appDir: string): DiscoveredRoute[] {\n const apiDir = path.join(appDir, \"api\");\n if (!fs.existsSync(apiDir)) {\n return [];\n }\n const routes: DiscoveredRoute[] = [];\n walkDirectory(apiDir, appDir, routes);\n return routes.sort((a, b) => a.path.localeCompare(b.path));\n}\n\nfunction walkDirectory(\n dir: string,\n appDir: string,\n routes: DiscoveredRoute[],\n): void {\n const entries = fs.readdirSync(dir, { withFileTypes: true });\n\n for (const entry of entries) {\n const fullPath = path.join(dir, entry.name);\n if (entry.isDirectory()) {\n walkDirectory(fullPath, appDir, routes);\n } else if (ROUTE_FILE_NAMES.includes(entry.name)) {\n const route = parseRouteFile(fullPath, appDir);\n if (route && route.methods.length > 0) {\n routes.push(route);\n }\n }\n }\n}\n\nfunction parseRouteFile(filePath: string, appDir: string): DiscoveredRoute | null {\n const content = fs.readFileSync(filePath, \"utf-8\");\n const methods = extractHttpMethods(content);\n\n if (methods.length === 0) {\n return null;\n }\n\n const relativePath = path.relative(appDir, path.dirname(filePath));\n const apiPath = \"/\" + relativePath.split(path.sep).join(\"/\");\n const pathParams = extractPathParams(apiPath);\n\n return {\n path: cleanPath(apiPath),\n methods,\n pathParams,\n filePath: path.relative(path.resolve(appDir, \"..\"), filePath),\n };\n}\n\n/**\n * Extract exported HTTP method handlers from route file content.\n * Matches patterns like:\n * export async function GET(...)\n * export function POST(...)\n * export const PUT = ...\n * export { handler as DELETE }\n */\nfunction extractHttpMethods(content: string): string[] {\n const methods: string[] = [];\n\n for (const method of HTTP_METHODS) {\n const patterns = [\n // export async function GET(\n new RegExp(`export\\\\s+(async\\\\s+)?function\\\\s+${method}\\\\s*\\\\(`),\n // export const GET =\n new RegExp(`export\\\\s+const\\\\s+${method}\\\\s*=`),\n // export { handler as GET }\n new RegExp(`export\\\\s*\\\\{[^}]*\\\\bas\\\\s+${method}\\\\b[^}]*\\\\}`),\n ];\n\n if (patterns.some((p) => p.test(content))) {\n methods.push(method);\n }\n }\n\n return methods;\n}\n\n/**\n * Extract path parameters from a Next.js dynamic route path.\n * e.g., \"/api/projects/[id]/tasks/[taskId]\" → [\"id\", \"taskId\"]\n */\nfunction extractPathParams(routePath: string): string[] {\n const params: string[] = [];\n const matches = routePath.matchAll(/\\[([^\\]]+)\\]/g);\n for (const match of matches) {\n params.push(match[1]);\n }\n return params;\n}\n\n/**\n * Clean up path by removing route groups like (group) and catch-all segments.\n * Converts Next.js bracket params to colon params for readability.\n * e.g., \"/api/(admin)/users/[id]\" → \"/api/users/:id\"\n */\nfunction cleanPath(routePath: string): string {\n return routePath\n .replace(/\\/\\([^)]+\\)/g, \"\") // Remove route groups\n .replace(/\\[\\.\\.\\.(\\w+)\\]/g, \":$1*\") // Catch-all [...slug] → :slug*\n .replace(/\\[(\\w+)\\]/g, \":$1\"); // Dynamic [id] → :id\n}\n","import picomatch from \"picomatch\";\nimport type { DiscoveredRoute, DiscoveryConfig } from \"@ai-me-chat/core\";\n\n/**\n * Filter discovered routes based on include/exclude glob patterns.\n * - If include is specified, only routes matching at least one include pattern are kept.\n * - If exclude is specified, routes matching any exclude pattern are removed.\n * - Exclude takes precedence over include.\n */\nexport function filterRoutes(\n routes: DiscoveredRoute[],\n config: Pick<DiscoveryConfig, \"include\" | \"exclude\">,\n): DiscoveredRoute[] {\n let filtered = routes;\n\n if (config.include && config.include.length > 0) {\n const isIncluded = picomatch(config.include);\n filtered = filtered.filter((r) => isIncluded(r.path));\n }\n\n if (config.exclude && config.exclude.length > 0) {\n const isExcluded = picomatch(config.exclude);\n filtered = filtered.filter((r) => !isExcluded(r.path));\n }\n\n return filtered;\n}\n","import { streamText, convertToModelMessages, tool, stepCountIs } from \"ai\";\nimport type { UIMessage } from \"ai\";\nimport {\n type AIMeConfig,\n type AIMeToolDefinition,\n generateToolDefinitions,\n generateToolsFromOpenAPI,\n fetchOpenAPISpec,\n executeTool,\n} from \"@ai-me-chat/core\";\nimport type { OpenAPISpec, ExecutionContext } from \"@ai-me-chat/core\";\nimport { scanRoutes } from \"./scanner.js\";\nimport { filterRoutes } from \"./filter.js\";\n\n/**\n * Create an AI-Me API route handler for Next.js App Router.\n *\n * Usage:\n * const handler = createAIMeHandler({ model, discovery, getSession });\n * export { handler as GET, handler as POST };\n */\nexport function createAIMeHandler(config: AIMeConfig) {\n // Discover tools at initialization time\n let toolDefinitions: AIMeToolDefinition[] | null = null;\n let toolsPromise: Promise<AIMeToolDefinition[]> | null = null;\n\n async function getToolDefinitions(appDir: string): Promise<AIMeToolDefinition[]> {\n if (toolDefinitions) return toolDefinitions;\n if (toolsPromise) return toolsPromise;\n\n toolsPromise = initTools(appDir);\n toolDefinitions = await toolsPromise;\n toolsPromise = null;\n return toolDefinitions;\n }\n\n async function initTools(appDir: string): Promise<AIMeToolDefinition[]> {\n if (config.discovery.mode === \"openapi\") {\n let spec: OpenAPISpec;\n if (config.discovery.spec) {\n spec = config.discovery.spec as unknown as OpenAPISpec;\n } else if (config.discovery.specUrl) {\n spec = await fetchOpenAPISpec(config.discovery.specUrl);\n } else {\n throw new Error(\n 'OpenAPI discovery mode requires either \"spec\" (inline object) or \"specUrl\" (remote URL) in discovery config',\n );\n }\n const tools = generateToolsFromOpenAPI(spec, config.confirmation);\n if (config.discovery.include || config.discovery.exclude) {\n const routes = tools.map((t) => ({\n path: t.path ?? \"\",\n methods: [t.httpMethod ?? \"GET\"],\n pathParams: [],\n filePath: \"\",\n }));\n const filtered = filterRoutes(routes, config.discovery);\n const filteredPaths = new Set(\n filtered.flatMap((r) => r.methods.map((m) => `${m}:${r.path}`)),\n );\n return tools.filter((t) => filteredPaths.has(`${t.httpMethod}:${t.path}`));\n }\n return tools;\n }\n\n let routes = scanRoutes(appDir);\n routes = filterRoutes(routes, config.discovery);\n return generateToolDefinitions(routes, config.confirmation);\n }\n\n async function handler(req: Request): Promise<Response> {\n // Auth check\n const session = await config.getSession(req);\n if (!session) {\n return new Response(\"Unauthorized\", { status: 401 });\n }\n\n const url = new URL(req.url);\n\n // Route: GET /api/ai-me/tools — list available tools (debug)\n if (req.method === \"GET\" && url.pathname.endsWith(\"/tools\")) {\n const appDir = process.cwd() + \"/app\";\n let tools: AIMeToolDefinition[];\n try {\n tools = await getToolDefinitions(appDir);\n } catch (error) {\n return new Response(\n JSON.stringify({ error: error instanceof Error ? error.message : \"Tool discovery failed\" }),\n { status: 500, headers: { \"Content-Type\": \"application/json\" } },\n );\n }\n return Response.json(\n tools.map((t) => ({\n name: t.name,\n description: t.description,\n httpMethod: t.httpMethod,\n path: t.path,\n requiresConfirmation: t.requiresConfirmation,\n })),\n );\n }\n\n // Route: GET /api/ai-me/health\n if (req.method === \"GET\" && url.pathname.endsWith(\"/health\")) {\n return Response.json({ status: \"ok\", version: \"0.0.1\" });\n }\n\n // Route: POST /api/ai-me (chat)\n if (req.method === \"POST\") {\n return handleChat(req, config, session, getToolDefinitions);\n }\n\n return new Response(\"Not Found\", { status: 404 });\n }\n\n return handler;\n}\n\nasync function handleChat(\n req: Request,\n config: AIMeConfig,\n session: NonNullable<Awaited<ReturnType<AIMeConfig[\"getSession\"]>>>,\n getToolDefinitions: (appDir: string) => Promise<AIMeToolDefinition[]>,\n): Promise<Response> {\n const { messages }: { messages: UIMessage[] } = await req.json();\n const appDir = process.cwd() + \"/app\";\n const toolDefs = await getToolDefinitions(appDir);\n\n // Build execution context with forwarded auth headers\n const baseUrl = new URL(req.url).origin;\n const executionContext: ExecutionContext = {\n baseUrl,\n headers: {\n cookie: req.headers.get(\"cookie\") ?? \"\",\n authorization: req.headers.get(\"authorization\") ?? \"\",\n },\n };\n\n // Convert tool definitions to AI SDK v6 tool() format\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const aiTools: Record<string, any> = {};\n for (const toolDef of toolDefs) {\n const def = toolDef; // capture for closure\n aiTools[def.name] = tool({\n description: def.description,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n inputSchema: def.parameters as any,\n // Disable strict schema validation for OpenAI-compatible providers\n // (e.g., Groq) that reject additionalProperties in tool schemas\n strict: false,\n execute: async (params: Record<string, unknown>) => {\n const result = await executeTool(\n def,\n params,\n executionContext,\n );\n return result.response;\n },\n });\n }\n\n const modelMessages = await convertToModelMessages(messages);\n\n // Limit history if configured\n const maxHistory = config.maxHistoryMessages ?? 20;\n const trimmedMessages =\n modelMessages.length > maxHistory\n ? modelMessages.slice(-maxHistory)\n : modelMessages;\n\n const result = streamText({\n model: config.model,\n system:\n config.systemPrompt ??\n `You are an AI assistant for this application. You can help users query data and perform actions. User: ${session.user.id}${session.user.role ? ` (role: ${session.user.role})` : \"\"}`,\n messages: trimmedMessages,\n tools: aiTools,\n stopWhen: stepCountIs(5),\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n ...(config.providerOptions ? { providerOptions: config.providerOptions as any } : {}),\n });\n\n return result.toUIMessageStreamResponse();\n}\n"],"mappings":"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","names":["picomatch","routes","result"]}
@@ -0,0 +1,27 @@
1
+ import { DiscoveredRoute, DiscoveryConfig, AIMeConfig } from '@ai-me-chat/core';
2
+
3
+ /**
4
+ * Scan a Next.js App Router directory for API routes.
5
+ * Finds all route.ts/route.js files under `appDir/api/` and extracts
6
+ * HTTP methods and path parameters.
7
+ */
8
+ declare function scanRoutes(appDir: string): DiscoveredRoute[];
9
+
10
+ /**
11
+ * Filter discovered routes based on include/exclude glob patterns.
12
+ * - If include is specified, only routes matching at least one include pattern are kept.
13
+ * - If exclude is specified, routes matching any exclude pattern are removed.
14
+ * - Exclude takes precedence over include.
15
+ */
16
+ declare function filterRoutes(routes: DiscoveredRoute[], config: Pick<DiscoveryConfig, "include" | "exclude">): DiscoveredRoute[];
17
+
18
+ /**
19
+ * Create an AI-Me API route handler for Next.js App Router.
20
+ *
21
+ * Usage:
22
+ * const handler = createAIMeHandler({ model, discovery, getSession });
23
+ * export { handler as GET, handler as POST };
24
+ */
25
+ declare function createAIMeHandler(config: AIMeConfig): (req: Request) => Promise<Response>;
26
+
27
+ export { createAIMeHandler, filterRoutes, scanRoutes };
@@ -0,0 +1,27 @@
1
+ import { DiscoveredRoute, DiscoveryConfig, AIMeConfig } from '@ai-me-chat/core';
2
+
3
+ /**
4
+ * Scan a Next.js App Router directory for API routes.
5
+ * Finds all route.ts/route.js files under `appDir/api/` and extracts
6
+ * HTTP methods and path parameters.
7
+ */
8
+ declare function scanRoutes(appDir: string): DiscoveredRoute[];
9
+
10
+ /**
11
+ * Filter discovered routes based on include/exclude glob patterns.
12
+ * - If include is specified, only routes matching at least one include pattern are kept.
13
+ * - If exclude is specified, routes matching any exclude pattern are removed.
14
+ * - Exclude takes precedence over include.
15
+ */
16
+ declare function filterRoutes(routes: DiscoveredRoute[], config: Pick<DiscoveryConfig, "include" | "exclude">): DiscoveredRoute[];
17
+
18
+ /**
19
+ * Create an AI-Me API route handler for Next.js App Router.
20
+ *
21
+ * Usage:
22
+ * const handler = createAIMeHandler({ model, discovery, getSession });
23
+ * export { handler as GET, handler as POST };
24
+ */
25
+ declare function createAIMeHandler(config: AIMeConfig): (req: Request) => Promise<Response>;
26
+
27
+ export { createAIMeHandler, filterRoutes, scanRoutes };
package/dist/index.js ADDED
@@ -0,0 +1,228 @@
1
+ // src/scanner.ts
2
+ import * as fs from "fs";
3
+ import * as path from "path";
4
+ var HTTP_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE"];
5
+ var ROUTE_FILE_NAMES = ["route.ts", "route.js", "route.tsx", "route.jsx"];
6
+ function scanRoutes(appDir) {
7
+ const apiDir = path.join(appDir, "api");
8
+ if (!fs.existsSync(apiDir)) {
9
+ return [];
10
+ }
11
+ const routes = [];
12
+ walkDirectory(apiDir, appDir, routes);
13
+ return routes.sort((a, b) => a.path.localeCompare(b.path));
14
+ }
15
+ function walkDirectory(dir, appDir, routes) {
16
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
17
+ for (const entry of entries) {
18
+ const fullPath = path.join(dir, entry.name);
19
+ if (entry.isDirectory()) {
20
+ walkDirectory(fullPath, appDir, routes);
21
+ } else if (ROUTE_FILE_NAMES.includes(entry.name)) {
22
+ const route = parseRouteFile(fullPath, appDir);
23
+ if (route && route.methods.length > 0) {
24
+ routes.push(route);
25
+ }
26
+ }
27
+ }
28
+ }
29
+ function parseRouteFile(filePath, appDir) {
30
+ const content = fs.readFileSync(filePath, "utf-8");
31
+ const methods = extractHttpMethods(content);
32
+ if (methods.length === 0) {
33
+ return null;
34
+ }
35
+ const relativePath = path.relative(appDir, path.dirname(filePath));
36
+ const apiPath = "/" + relativePath.split(path.sep).join("/");
37
+ const pathParams = extractPathParams(apiPath);
38
+ return {
39
+ path: cleanPath(apiPath),
40
+ methods,
41
+ pathParams,
42
+ filePath: path.relative(path.resolve(appDir, ".."), filePath)
43
+ };
44
+ }
45
+ function extractHttpMethods(content) {
46
+ const methods = [];
47
+ for (const method of HTTP_METHODS) {
48
+ const patterns = [
49
+ // export async function GET(
50
+ new RegExp(`export\\s+(async\\s+)?function\\s+${method}\\s*\\(`),
51
+ // export const GET =
52
+ new RegExp(`export\\s+const\\s+${method}\\s*=`),
53
+ // export { handler as GET }
54
+ new RegExp(`export\\s*\\{[^}]*\\bas\\s+${method}\\b[^}]*\\}`)
55
+ ];
56
+ if (patterns.some((p) => p.test(content))) {
57
+ methods.push(method);
58
+ }
59
+ }
60
+ return methods;
61
+ }
62
+ function extractPathParams(routePath) {
63
+ const params = [];
64
+ const matches = routePath.matchAll(/\[([^\]]+)\]/g);
65
+ for (const match of matches) {
66
+ params.push(match[1]);
67
+ }
68
+ return params;
69
+ }
70
+ function cleanPath(routePath) {
71
+ return routePath.replace(/\/\([^)]+\)/g, "").replace(/\[\.\.\.(\w+)\]/g, ":$1*").replace(/\[(\w+)\]/g, ":$1");
72
+ }
73
+
74
+ // src/filter.ts
75
+ import picomatch from "picomatch";
76
+ function filterRoutes(routes, config) {
77
+ let filtered = routes;
78
+ if (config.include && config.include.length > 0) {
79
+ const isIncluded = picomatch(config.include);
80
+ filtered = filtered.filter((r) => isIncluded(r.path));
81
+ }
82
+ if (config.exclude && config.exclude.length > 0) {
83
+ const isExcluded = picomatch(config.exclude);
84
+ filtered = filtered.filter((r) => !isExcluded(r.path));
85
+ }
86
+ return filtered;
87
+ }
88
+
89
+ // src/handler.ts
90
+ import { streamText, convertToModelMessages, tool, stepCountIs } from "ai";
91
+ import {
92
+ generateToolDefinitions,
93
+ generateToolsFromOpenAPI,
94
+ fetchOpenAPISpec,
95
+ executeTool
96
+ } from "@ai-me-chat/core";
97
+ function createAIMeHandler(config) {
98
+ let toolDefinitions = null;
99
+ let toolsPromise = null;
100
+ async function getToolDefinitions(appDir) {
101
+ if (toolDefinitions) return toolDefinitions;
102
+ if (toolsPromise) return toolsPromise;
103
+ toolsPromise = initTools(appDir);
104
+ toolDefinitions = await toolsPromise;
105
+ toolsPromise = null;
106
+ return toolDefinitions;
107
+ }
108
+ async function initTools(appDir) {
109
+ if (config.discovery.mode === "openapi") {
110
+ let spec;
111
+ if (config.discovery.spec) {
112
+ spec = config.discovery.spec;
113
+ } else if (config.discovery.specUrl) {
114
+ spec = await fetchOpenAPISpec(config.discovery.specUrl);
115
+ } else {
116
+ throw new Error(
117
+ 'OpenAPI discovery mode requires either "spec" (inline object) or "specUrl" (remote URL) in discovery config'
118
+ );
119
+ }
120
+ const tools = generateToolsFromOpenAPI(spec, config.confirmation);
121
+ if (config.discovery.include || config.discovery.exclude) {
122
+ const routes2 = tools.map((t) => ({
123
+ path: t.path ?? "",
124
+ methods: [t.httpMethod ?? "GET"],
125
+ pathParams: [],
126
+ filePath: ""
127
+ }));
128
+ const filtered = filterRoutes(routes2, config.discovery);
129
+ const filteredPaths = new Set(
130
+ filtered.flatMap((r) => r.methods.map((m) => `${m}:${r.path}`))
131
+ );
132
+ return tools.filter((t) => filteredPaths.has(`${t.httpMethod}:${t.path}`));
133
+ }
134
+ return tools;
135
+ }
136
+ let routes = scanRoutes(appDir);
137
+ routes = filterRoutes(routes, config.discovery);
138
+ return generateToolDefinitions(routes, config.confirmation);
139
+ }
140
+ async function handler(req) {
141
+ const session = await config.getSession(req);
142
+ if (!session) {
143
+ return new Response("Unauthorized", { status: 401 });
144
+ }
145
+ const url = new URL(req.url);
146
+ if (req.method === "GET" && url.pathname.endsWith("/tools")) {
147
+ const appDir = process.cwd() + "/app";
148
+ let tools;
149
+ try {
150
+ tools = await getToolDefinitions(appDir);
151
+ } catch (error) {
152
+ return new Response(
153
+ JSON.stringify({ error: error instanceof Error ? error.message : "Tool discovery failed" }),
154
+ { status: 500, headers: { "Content-Type": "application/json" } }
155
+ );
156
+ }
157
+ return Response.json(
158
+ tools.map((t) => ({
159
+ name: t.name,
160
+ description: t.description,
161
+ httpMethod: t.httpMethod,
162
+ path: t.path,
163
+ requiresConfirmation: t.requiresConfirmation
164
+ }))
165
+ );
166
+ }
167
+ if (req.method === "GET" && url.pathname.endsWith("/health")) {
168
+ return Response.json({ status: "ok", version: "0.0.1" });
169
+ }
170
+ if (req.method === "POST") {
171
+ return handleChat(req, config, session, getToolDefinitions);
172
+ }
173
+ return new Response("Not Found", { status: 404 });
174
+ }
175
+ return handler;
176
+ }
177
+ async function handleChat(req, config, session, getToolDefinitions) {
178
+ const { messages } = await req.json();
179
+ const appDir = process.cwd() + "/app";
180
+ const toolDefs = await getToolDefinitions(appDir);
181
+ const baseUrl = new URL(req.url).origin;
182
+ const executionContext = {
183
+ baseUrl,
184
+ headers: {
185
+ cookie: req.headers.get("cookie") ?? "",
186
+ authorization: req.headers.get("authorization") ?? ""
187
+ }
188
+ };
189
+ const aiTools = {};
190
+ for (const toolDef of toolDefs) {
191
+ const def = toolDef;
192
+ aiTools[def.name] = tool({
193
+ description: def.description,
194
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
195
+ inputSchema: def.parameters,
196
+ // Disable strict schema validation for OpenAI-compatible providers
197
+ // (e.g., Groq) that reject additionalProperties in tool schemas
198
+ strict: false,
199
+ execute: async (params) => {
200
+ const result2 = await executeTool(
201
+ def,
202
+ params,
203
+ executionContext
204
+ );
205
+ return result2.response;
206
+ }
207
+ });
208
+ }
209
+ const modelMessages = await convertToModelMessages(messages);
210
+ const maxHistory = config.maxHistoryMessages ?? 20;
211
+ const trimmedMessages = modelMessages.length > maxHistory ? modelMessages.slice(-maxHistory) : modelMessages;
212
+ const result = streamText({
213
+ model: config.model,
214
+ system: config.systemPrompt ?? `You are an AI assistant for this application. You can help users query data and perform actions. User: ${session.user.id}${session.user.role ? ` (role: ${session.user.role})` : ""}`,
215
+ messages: trimmedMessages,
216
+ tools: aiTools,
217
+ stopWhen: stepCountIs(5),
218
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
219
+ ...config.providerOptions ? { providerOptions: config.providerOptions } : {}
220
+ });
221
+ return result.toUIMessageStreamResponse();
222
+ }
223
+ export {
224
+ createAIMeHandler,
225
+ filterRoutes,
226
+ scanRoutes
227
+ };
228
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/scanner.ts","../src/filter.ts","../src/handler.ts"],"sourcesContent":["import * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport type { DiscoveredRoute } from \"@ai-me-chat/core\";\n\nconst HTTP_METHODS = [\"GET\", \"POST\", \"PUT\", \"PATCH\", \"DELETE\"] as const;\n\nconst ROUTE_FILE_NAMES = [\"route.ts\", \"route.js\", \"route.tsx\", \"route.jsx\"];\n\n/**\n * Scan a Next.js App Router directory for API routes.\n * Finds all route.ts/route.js files under `appDir/api/` and extracts\n * HTTP methods and path parameters.\n */\nexport function scanRoutes(appDir: string): DiscoveredRoute[] {\n const apiDir = path.join(appDir, \"api\");\n if (!fs.existsSync(apiDir)) {\n return [];\n }\n const routes: DiscoveredRoute[] = [];\n walkDirectory(apiDir, appDir, routes);\n return routes.sort((a, b) => a.path.localeCompare(b.path));\n}\n\nfunction walkDirectory(\n dir: string,\n appDir: string,\n routes: DiscoveredRoute[],\n): void {\n const entries = fs.readdirSync(dir, { withFileTypes: true });\n\n for (const entry of entries) {\n const fullPath = path.join(dir, entry.name);\n if (entry.isDirectory()) {\n walkDirectory(fullPath, appDir, routes);\n } else if (ROUTE_FILE_NAMES.includes(entry.name)) {\n const route = parseRouteFile(fullPath, appDir);\n if (route && route.methods.length > 0) {\n routes.push(route);\n }\n }\n }\n}\n\nfunction parseRouteFile(filePath: string, appDir: string): DiscoveredRoute | null {\n const content = fs.readFileSync(filePath, \"utf-8\");\n const methods = extractHttpMethods(content);\n\n if (methods.length === 0) {\n return null;\n }\n\n const relativePath = path.relative(appDir, path.dirname(filePath));\n const apiPath = \"/\" + relativePath.split(path.sep).join(\"/\");\n const pathParams = extractPathParams(apiPath);\n\n return {\n path: cleanPath(apiPath),\n methods,\n pathParams,\n filePath: path.relative(path.resolve(appDir, \"..\"), filePath),\n };\n}\n\n/**\n * Extract exported HTTP method handlers from route file content.\n * Matches patterns like:\n * export async function GET(...)\n * export function POST(...)\n * export const PUT = ...\n * export { handler as DELETE }\n */\nfunction extractHttpMethods(content: string): string[] {\n const methods: string[] = [];\n\n for (const method of HTTP_METHODS) {\n const patterns = [\n // export async function GET(\n new RegExp(`export\\\\s+(async\\\\s+)?function\\\\s+${method}\\\\s*\\\\(`),\n // export const GET =\n new RegExp(`export\\\\s+const\\\\s+${method}\\\\s*=`),\n // export { handler as GET }\n new RegExp(`export\\\\s*\\\\{[^}]*\\\\bas\\\\s+${method}\\\\b[^}]*\\\\}`),\n ];\n\n if (patterns.some((p) => p.test(content))) {\n methods.push(method);\n }\n }\n\n return methods;\n}\n\n/**\n * Extract path parameters from a Next.js dynamic route path.\n * e.g., \"/api/projects/[id]/tasks/[taskId]\" → [\"id\", \"taskId\"]\n */\nfunction extractPathParams(routePath: string): string[] {\n const params: string[] = [];\n const matches = routePath.matchAll(/\\[([^\\]]+)\\]/g);\n for (const match of matches) {\n params.push(match[1]);\n }\n return params;\n}\n\n/**\n * Clean up path by removing route groups like (group) and catch-all segments.\n * Converts Next.js bracket params to colon params for readability.\n * e.g., \"/api/(admin)/users/[id]\" → \"/api/users/:id\"\n */\nfunction cleanPath(routePath: string): string {\n return routePath\n .replace(/\\/\\([^)]+\\)/g, \"\") // Remove route groups\n .replace(/\\[\\.\\.\\.(\\w+)\\]/g, \":$1*\") // Catch-all [...slug] → :slug*\n .replace(/\\[(\\w+)\\]/g, \":$1\"); // Dynamic [id] → :id\n}\n","import picomatch from \"picomatch\";\nimport type { DiscoveredRoute, DiscoveryConfig } from \"@ai-me-chat/core\";\n\n/**\n * Filter discovered routes based on include/exclude glob patterns.\n * - If include is specified, only routes matching at least one include pattern are kept.\n * - If exclude is specified, routes matching any exclude pattern are removed.\n * - Exclude takes precedence over include.\n */\nexport function filterRoutes(\n routes: DiscoveredRoute[],\n config: Pick<DiscoveryConfig, \"include\" | \"exclude\">,\n): DiscoveredRoute[] {\n let filtered = routes;\n\n if (config.include && config.include.length > 0) {\n const isIncluded = picomatch(config.include);\n filtered = filtered.filter((r) => isIncluded(r.path));\n }\n\n if (config.exclude && config.exclude.length > 0) {\n const isExcluded = picomatch(config.exclude);\n filtered = filtered.filter((r) => !isExcluded(r.path));\n }\n\n return filtered;\n}\n","import { streamText, convertToModelMessages, tool, stepCountIs } from \"ai\";\nimport type { UIMessage } from \"ai\";\nimport {\n type AIMeConfig,\n type AIMeToolDefinition,\n generateToolDefinitions,\n generateToolsFromOpenAPI,\n fetchOpenAPISpec,\n executeTool,\n} from \"@ai-me-chat/core\";\nimport type { OpenAPISpec, ExecutionContext } from \"@ai-me-chat/core\";\nimport { scanRoutes } from \"./scanner.js\";\nimport { filterRoutes } from \"./filter.js\";\n\n/**\n * Create an AI-Me API route handler for Next.js App Router.\n *\n * Usage:\n * const handler = createAIMeHandler({ model, discovery, getSession });\n * export { handler as GET, handler as POST };\n */\nexport function createAIMeHandler(config: AIMeConfig) {\n // Discover tools at initialization time\n let toolDefinitions: AIMeToolDefinition[] | null = null;\n let toolsPromise: Promise<AIMeToolDefinition[]> | null = null;\n\n async function getToolDefinitions(appDir: string): Promise<AIMeToolDefinition[]> {\n if (toolDefinitions) return toolDefinitions;\n if (toolsPromise) return toolsPromise;\n\n toolsPromise = initTools(appDir);\n toolDefinitions = await toolsPromise;\n toolsPromise = null;\n return toolDefinitions;\n }\n\n async function initTools(appDir: string): Promise<AIMeToolDefinition[]> {\n if (config.discovery.mode === \"openapi\") {\n let spec: OpenAPISpec;\n if (config.discovery.spec) {\n spec = config.discovery.spec as unknown as OpenAPISpec;\n } else if (config.discovery.specUrl) {\n spec = await fetchOpenAPISpec(config.discovery.specUrl);\n } else {\n throw new Error(\n 'OpenAPI discovery mode requires either \"spec\" (inline object) or \"specUrl\" (remote URL) in discovery config',\n );\n }\n const tools = generateToolsFromOpenAPI(spec, config.confirmation);\n if (config.discovery.include || config.discovery.exclude) {\n const routes = tools.map((t) => ({\n path: t.path ?? \"\",\n methods: [t.httpMethod ?? \"GET\"],\n pathParams: [],\n filePath: \"\",\n }));\n const filtered = filterRoutes(routes, config.discovery);\n const filteredPaths = new Set(\n filtered.flatMap((r) => r.methods.map((m) => `${m}:${r.path}`)),\n );\n return tools.filter((t) => filteredPaths.has(`${t.httpMethod}:${t.path}`));\n }\n return tools;\n }\n\n let routes = scanRoutes(appDir);\n routes = filterRoutes(routes, config.discovery);\n return generateToolDefinitions(routes, config.confirmation);\n }\n\n async function handler(req: Request): Promise<Response> {\n // Auth check\n const session = await config.getSession(req);\n if (!session) {\n return new Response(\"Unauthorized\", { status: 401 });\n }\n\n const url = new URL(req.url);\n\n // Route: GET /api/ai-me/tools — list available tools (debug)\n if (req.method === \"GET\" && url.pathname.endsWith(\"/tools\")) {\n const appDir = process.cwd() + \"/app\";\n let tools: AIMeToolDefinition[];\n try {\n tools = await getToolDefinitions(appDir);\n } catch (error) {\n return new Response(\n JSON.stringify({ error: error instanceof Error ? error.message : \"Tool discovery failed\" }),\n { status: 500, headers: { \"Content-Type\": \"application/json\" } },\n );\n }\n return Response.json(\n tools.map((t) => ({\n name: t.name,\n description: t.description,\n httpMethod: t.httpMethod,\n path: t.path,\n requiresConfirmation: t.requiresConfirmation,\n })),\n );\n }\n\n // Route: GET /api/ai-me/health\n if (req.method === \"GET\" && url.pathname.endsWith(\"/health\")) {\n return Response.json({ status: \"ok\", version: \"0.0.1\" });\n }\n\n // Route: POST /api/ai-me (chat)\n if (req.method === \"POST\") {\n return handleChat(req, config, session, getToolDefinitions);\n }\n\n return new Response(\"Not Found\", { status: 404 });\n }\n\n return handler;\n}\n\nasync function handleChat(\n req: Request,\n config: AIMeConfig,\n session: NonNullable<Awaited<ReturnType<AIMeConfig[\"getSession\"]>>>,\n getToolDefinitions: (appDir: string) => Promise<AIMeToolDefinition[]>,\n): Promise<Response> {\n const { messages }: { messages: UIMessage[] } = await req.json();\n const appDir = process.cwd() + \"/app\";\n const toolDefs = await getToolDefinitions(appDir);\n\n // Build execution context with forwarded auth headers\n const baseUrl = new URL(req.url).origin;\n const executionContext: ExecutionContext = {\n baseUrl,\n headers: {\n cookie: req.headers.get(\"cookie\") ?? \"\",\n authorization: req.headers.get(\"authorization\") ?? \"\",\n },\n };\n\n // Convert tool definitions to AI SDK v6 tool() format\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const aiTools: Record<string, any> = {};\n for (const toolDef of toolDefs) {\n const def = toolDef; // capture for closure\n aiTools[def.name] = tool({\n description: def.description,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n inputSchema: def.parameters as any,\n // Disable strict schema validation for OpenAI-compatible providers\n // (e.g., Groq) that reject additionalProperties in tool schemas\n strict: false,\n execute: async (params: Record<string, unknown>) => {\n const result = await executeTool(\n def,\n params,\n executionContext,\n );\n return result.response;\n },\n });\n }\n\n const modelMessages = await convertToModelMessages(messages);\n\n // Limit history if configured\n const maxHistory = config.maxHistoryMessages ?? 20;\n const trimmedMessages =\n modelMessages.length > maxHistory\n ? modelMessages.slice(-maxHistory)\n : modelMessages;\n\n const result = streamText({\n model: config.model,\n system:\n config.systemPrompt ??\n `You are an AI assistant for this application. You can help users query data and perform actions. User: ${session.user.id}${session.user.role ? ` (role: ${session.user.role})` : \"\"}`,\n messages: trimmedMessages,\n tools: aiTools,\n stopWhen: stepCountIs(5),\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n ...(config.providerOptions ? { providerOptions: config.providerOptions as any } : {}),\n });\n\n return result.toUIMessageStreamResponse();\n}\n"],"mappings":"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","names":["routes","result"]}
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "@ai-me-chat/nextjs",
3
+ "version": "0.0.1",
4
+ "description": "AI-Me Next.js integration — handler, proxy, route scanner",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/aselims/ai-me-chat",
9
+ "directory": "packages/nextjs"
10
+ },
11
+ "type": "module",
12
+ "main": "./dist/index.cjs",
13
+ "module": "./dist/index.js",
14
+ "types": "./dist/index.d.ts",
15
+ "exports": {
16
+ ".": {
17
+ "types": "./dist/index.d.ts",
18
+ "import": "./dist/index.js",
19
+ "require": "./dist/index.cjs"
20
+ }
21
+ },
22
+ "files": [
23
+ "dist"
24
+ ],
25
+ "sideEffects": false,
26
+ "peerDependencies": {
27
+ "ai": "^6.0.0",
28
+ "next": "^16.0.0",
29
+ "react": "^19.0.0"
30
+ },
31
+ "dependencies": {
32
+ "picomatch": "^4.0.3",
33
+ "@ai-me-chat/core": "0.0.1"
34
+ },
35
+ "devDependencies": {
36
+ "@types/node": "^25.5.0",
37
+ "@types/picomatch": "^4.0.2",
38
+ "@types/react": "^19.0.0",
39
+ "ai": "^6.0.116",
40
+ "next": "^16.1.6",
41
+ "react": "^19.2.4",
42
+ "react-dom": "^19.2.4",
43
+ "tsup": "^8.5.1",
44
+ "typescript": "^5.9.3",
45
+ "vitest": "^4.1.0",
46
+ "zod": "^4.3.6"
47
+ },
48
+ "scripts": {
49
+ "build": "tsup",
50
+ "dev": "tsup --watch",
51
+ "test": "vitest run",
52
+ "typecheck": "tsc --noEmit",
53
+ "lint": "eslint src/",
54
+ "clean": "rm -rf dist"
55
+ }
56
+ }