agent-manifest 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/cli.js +58 -7
- package/dist/discovery/service.js +37 -12
- package/dist/index.js +22 -1
- package/dist/parser/intent-classifier.js +1 -1
- package/dist/parser/openapi-parser.js +156 -0
- package/dist/parser/prisma-parser.js +198 -0
- package/dist/parser/remix-parser.js +158 -0
- package/dist/parser/socketio-parser.js +169 -0
- package/dist/parser/sse-parser.js +141 -0
- package/dist/parser/trpc-parser.js +192 -0
- package/dist/parser/websocket-parser.js +168 -0
- package/package.json +1 -1
- package/schema/agent.schema.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -59,18 +59,69 @@ program
|
|
|
59
59
|
const files = await discovery.findRelevantFiles();
|
|
60
60
|
console.log(`🔍 Found ${files.length} relevant files.`);
|
|
61
61
|
const tsParser = new index_1.TSParser(projectPath);
|
|
62
|
-
const
|
|
62
|
+
const sharedProject = tsParser.getProject();
|
|
63
|
+
const expressParser = new index_1.ExpressParser(sharedProject);
|
|
64
|
+
const socketParser = new index_1.SocketIOParser(sharedProject);
|
|
65
|
+
const trpcParser = new index_1.TRPCParser(sharedProject);
|
|
66
|
+
const sseParser = new index_1.SSEParser(sharedProject);
|
|
67
|
+
const remixParser = new index_1.RemixParser(sharedProject);
|
|
68
|
+
const wsParser = new index_1.WebSocketParser(sharedProject);
|
|
69
|
+
const openApiParser = new index_1.OpenAPIParser();
|
|
70
|
+
const prismaParser = new index_1.PrismaParser();
|
|
63
71
|
const actions = [];
|
|
72
|
+
// Determine which files are OpenAPI / Prisma by extension/name
|
|
73
|
+
const openApiExts = new Set(['.json', '.yaml', '.yml']);
|
|
74
|
+
const prismaExt = '.prisma';
|
|
64
75
|
for (const file of files) {
|
|
65
76
|
console.log(`📄 Parsing: ${path.relative(projectPath, file)}`);
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
|
|
77
|
+
const ext = path.extname(file).toLowerCase();
|
|
78
|
+
const base = path.basename(file).toLowerCase();
|
|
79
|
+
// ── OpenAPI spec ──────────────────────────────────────────────────────
|
|
80
|
+
if (openApiExts.has(ext) && index_1.OPENAPI_PATTERNS.some(p => {
|
|
81
|
+
const pat = p.replace('**/', '').replace('*', '');
|
|
82
|
+
return base.includes(pat.split('.')[0]);
|
|
83
|
+
})) {
|
|
84
|
+
try {
|
|
85
|
+
actions.push(...openApiParser.parseFile(file, projectPath));
|
|
86
|
+
}
|
|
87
|
+
catch { /* ignore */ }
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
// ── Prisma schema ─────────────────────────────────────────────────────
|
|
91
|
+
if (ext === prismaExt || base === 'schema.prisma') {
|
|
92
|
+
try {
|
|
93
|
+
actions.push(...prismaParser.parseFile(file));
|
|
94
|
+
}
|
|
95
|
+
catch { /* ignore */ }
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
// ── TypeScript / JavaScript source files ──────────────────────────────
|
|
99
|
+
if (ext === '.ts' || ext === '.tsx' || ext === '.js' || ext === '.jsx') {
|
|
100
|
+
// Standard TS server-action / contract / annotation parsing
|
|
101
|
+
try {
|
|
102
|
+
const fileActions = await tsParser.parseFile(file);
|
|
103
|
+
actions.push(...fileActions);
|
|
104
|
+
}
|
|
105
|
+
catch { /* ignore */ }
|
|
69
106
|
try {
|
|
70
107
|
const content = fs.readFileSync(file, 'utf8');
|
|
71
108
|
if ((0, index_1.looksLikeRouteFile)(content)) {
|
|
72
|
-
|
|
73
|
-
|
|
109
|
+
actions.push(...await expressParser.parseFile(file, projectPath));
|
|
110
|
+
}
|
|
111
|
+
if ((0, index_1.looksLikeSocketIOFile)(content)) {
|
|
112
|
+
actions.push(...await socketParser.parseFile(file, projectPath));
|
|
113
|
+
}
|
|
114
|
+
if ((0, index_1.looksLikeTRPCFile)(content)) {
|
|
115
|
+
actions.push(...await trpcParser.parseFile(file, projectPath));
|
|
116
|
+
}
|
|
117
|
+
if ((0, index_1.looksLikeSSEFile)(content)) {
|
|
118
|
+
actions.push(...await sseParser.parseFile(file, projectPath));
|
|
119
|
+
}
|
|
120
|
+
if ((0, index_1.looksLikeRemixRouteFile)(content)) {
|
|
121
|
+
actions.push(...await remixParser.parseFile(file, projectPath));
|
|
122
|
+
}
|
|
123
|
+
if ((0, index_1.looksLikeWebSocketFile)(content)) {
|
|
124
|
+
actions.push(...await wsParser.parseFile(file, projectPath));
|
|
74
125
|
}
|
|
75
126
|
}
|
|
76
127
|
catch { /* ignore */ }
|
|
@@ -135,7 +186,7 @@ program
|
|
|
135
186
|
});
|
|
136
187
|
// ─── Structural validator ────────────────────────────────────────────────────
|
|
137
188
|
const SAFETY_LEVELS = new Set(['read', 'write', 'financial', 'destructive', 'confidential']);
|
|
138
|
-
const ACTION_TYPES = new Set(['api', 'contract', 'function']);
|
|
189
|
+
const ACTION_TYPES = new Set(['api', 'contract', 'function', 'socket']);
|
|
139
190
|
const AUTH_TYPES = new Set(['none', 'bearer', 'api-key', 'oauth2', 'basic', 'farcaster-frame', 'cookie']);
|
|
140
191
|
const INTENT_RE = /^[a-z][a-z0-9]*\.[a-z][a-z0-9]*$/;
|
|
141
192
|
function validateManifest(m) {
|
|
@@ -39,6 +39,13 @@ const path = __importStar(require("path"));
|
|
|
39
39
|
const crypto = __importStar(require("crypto"));
|
|
40
40
|
const tinyglobby_1 = require("tinyglobby");
|
|
41
41
|
const express_parser_1 = require("../parser/express-parser");
|
|
42
|
+
const socketio_parser_1 = require("../parser/socketio-parser");
|
|
43
|
+
const trpc_parser_1 = require("../parser/trpc-parser");
|
|
44
|
+
const sse_parser_1 = require("../parser/sse-parser");
|
|
45
|
+
const remix_parser_1 = require("../parser/remix-parser");
|
|
46
|
+
const websocket_parser_1 = require("../parser/websocket-parser");
|
|
47
|
+
const openapi_parser_1 = require("../parser/openapi-parser");
|
|
48
|
+
const prisma_parser_1 = require("../parser/prisma-parser");
|
|
42
49
|
/** SHA-1 of a file's content — used for change detection caching. */
|
|
43
50
|
function fileHash(filePath) {
|
|
44
51
|
try {
|
|
@@ -60,7 +67,6 @@ const ALWAYS_EXCLUDE = [
|
|
|
60
67
|
'!**/out/**',
|
|
61
68
|
'!**/build/**',
|
|
62
69
|
'!**/.vercel/**',
|
|
63
|
-
// Never scan env files — they may contain secrets
|
|
64
70
|
'!**/.env',
|
|
65
71
|
'!**/.env.*',
|
|
66
72
|
'!**/secrets/**',
|
|
@@ -94,14 +100,14 @@ class DiscoveryService {
|
|
|
94
100
|
}
|
|
95
101
|
async findRelevantFiles() {
|
|
96
102
|
const relevantFiles = [];
|
|
97
|
-
// 0. Farcaster manifest
|
|
103
|
+
// 0. Farcaster manifest
|
|
98
104
|
const manifests = await (0, tinyglobby_1.glob)([
|
|
99
105
|
'.well-known/farcaster.json',
|
|
100
106
|
'public/.well-known/farcaster.json',
|
|
101
107
|
'**/public/.well-known/farcaster.json',
|
|
102
108
|
], { cwd: this.projectPath, absolute: true });
|
|
103
109
|
relevantFiles.push(...manifests);
|
|
104
|
-
// 0.5. ABI JSON files
|
|
110
|
+
// 0.5. ABI JSON files
|
|
105
111
|
const abis = await (0, tinyglobby_1.glob)([
|
|
106
112
|
'**/*ABI.json',
|
|
107
113
|
'**/abi/*.json',
|
|
@@ -111,20 +117,36 @@ class DiscoveryService {
|
|
|
111
117
|
...ALWAYS_EXCLUDE,
|
|
112
118
|
], { cwd: this.projectPath, absolute: true });
|
|
113
119
|
relevantFiles.push(...abis);
|
|
114
|
-
//
|
|
120
|
+
// 0.6. OpenAPI / Swagger specs
|
|
121
|
+
const openApiFiles = await (0, tinyglobby_1.glob)([
|
|
122
|
+
...openapi_parser_1.OPENAPI_PATTERNS,
|
|
123
|
+
...ALWAYS_EXCLUDE,
|
|
124
|
+
], { cwd: this.projectPath, absolute: true });
|
|
125
|
+
relevantFiles.push(...openApiFiles);
|
|
126
|
+
// 0.7. Prisma schema files
|
|
127
|
+
const prismaFiles = await (0, tinyglobby_1.glob)([
|
|
128
|
+
...prisma_parser_1.PRISMA_PATTERNS,
|
|
129
|
+
...ALWAYS_EXCLUDE,
|
|
130
|
+
], { cwd: this.projectPath, absolute: true });
|
|
131
|
+
relevantFiles.push(...prismaFiles);
|
|
132
|
+
// 1. Next.js API routes
|
|
115
133
|
const apiRoutes = await (0, tinyglobby_1.glob)([
|
|
116
|
-
// Next.js App Router
|
|
117
134
|
'**/app/api/**/*.{ts,js,tsx,jsx}',
|
|
118
|
-
// Next.js Pages Router
|
|
119
135
|
'**/pages/api/**/*.{ts,js,tsx,jsx}',
|
|
120
|
-
// Generic api/ folder
|
|
121
136
|
'**/api/**/*.{ts,js,tsx,jsx}',
|
|
122
137
|
...ALWAYS_EXCLUDE,
|
|
123
138
|
], { cwd: this.projectPath, absolute: true });
|
|
124
139
|
relevantFiles.push(...apiRoutes);
|
|
125
|
-
// 2.
|
|
140
|
+
// 2. Remix route files
|
|
141
|
+
const remixRoutes = await (0, tinyglobby_1.glob)([
|
|
142
|
+
'**/app/routes/**/*.{ts,js,tsx,jsx}',
|
|
143
|
+
'**/routes/**/*.{ts,js,tsx,jsx}',
|
|
144
|
+
...ALWAYS_EXCLUDE,
|
|
145
|
+
], { cwd: this.projectPath, absolute: true });
|
|
146
|
+
relevantFiles.push(...remixRoutes);
|
|
147
|
+
// 3. Scan all TS/TSX/JS files for signal keywords
|
|
126
148
|
const allTsFiles = await (0, tinyglobby_1.glob)([
|
|
127
|
-
'**/*.{ts,tsx}',
|
|
149
|
+
'**/*.{ts,tsx,js,jsx}',
|
|
128
150
|
...ALWAYS_EXCLUDE,
|
|
129
151
|
], { cwd: this.projectPath, absolute: true });
|
|
130
152
|
for (const file of allTsFiles) {
|
|
@@ -132,13 +154,11 @@ class DiscoveryService {
|
|
|
132
154
|
continue;
|
|
133
155
|
const hash = fileHash(file);
|
|
134
156
|
const cached = this.cache[file];
|
|
135
|
-
// Cache hit: file unchanged, reuse previous relevance decision
|
|
136
157
|
if (cached && cached.hash === hash) {
|
|
137
158
|
if (cached.relevant)
|
|
138
159
|
relevantFiles.push(file);
|
|
139
160
|
continue;
|
|
140
161
|
}
|
|
141
|
-
// Cache miss: read and classify
|
|
142
162
|
const content = fs.readFileSync(file, 'utf8');
|
|
143
163
|
const relevant = content.includes('@agent-action') ||
|
|
144
164
|
content.includes('useWriteContract') ||
|
|
@@ -146,7 +166,12 @@ class DiscoveryService {
|
|
|
146
166
|
content.includes('writeContract') ||
|
|
147
167
|
content.includes("'use server'") ||
|
|
148
168
|
content.includes('"use server"') ||
|
|
149
|
-
(0, express_parser_1.looksLikeRouteFile)(content)
|
|
169
|
+
(0, express_parser_1.looksLikeRouteFile)(content) ||
|
|
170
|
+
(0, socketio_parser_1.looksLikeSocketIOFile)(content) ||
|
|
171
|
+
(0, trpc_parser_1.looksLikeTRPCFile)(content) ||
|
|
172
|
+
(0, sse_parser_1.looksLikeSSEFile)(content) ||
|
|
173
|
+
(0, remix_parser_1.looksLikeRemixRouteFile)(content) ||
|
|
174
|
+
(0, websocket_parser_1.looksLikeWebSocketFile)(content);
|
|
150
175
|
this.cache[file] = { hash, relevant };
|
|
151
176
|
this.cacheModified = true;
|
|
152
177
|
if (relevant)
|
package/dist/index.js
CHANGED
|
@@ -14,7 +14,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
14
14
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
-
exports.CapabilityDetector = exports.AuthDetector = exports.ManifestGenerator = exports.ZodExtractor = exports.ContractParser = exports.looksLikeRouteFile = exports.ExpressParser = exports.TSParser = exports.DiscoveryService = void 0;
|
|
17
|
+
exports.CapabilityDetector = exports.AuthDetector = exports.ManifestGenerator = exports.ZodExtractor = exports.ContractParser = exports.PRISMA_PATTERNS = exports.PrismaParser = exports.OPENAPI_PATTERNS = exports.OpenAPIParser = exports.looksLikeWebSocketFile = exports.WebSocketParser = exports.looksLikeRemixRouteFile = exports.RemixParser = exports.looksLikeSSEFile = exports.SSEParser = exports.looksLikeTRPCFile = exports.TRPCParser = exports.looksLikeSocketIOFile = exports.SocketIOParser = exports.looksLikeRouteFile = exports.ExpressParser = exports.TSParser = exports.DiscoveryService = void 0;
|
|
18
18
|
__exportStar(require("./types"), exports);
|
|
19
19
|
var service_1 = require("./discovery/service");
|
|
20
20
|
Object.defineProperty(exports, "DiscoveryService", { enumerable: true, get: function () { return service_1.DiscoveryService; } });
|
|
@@ -23,6 +23,27 @@ Object.defineProperty(exports, "TSParser", { enumerable: true, get: function ()
|
|
|
23
23
|
var express_parser_1 = require("./parser/express-parser");
|
|
24
24
|
Object.defineProperty(exports, "ExpressParser", { enumerable: true, get: function () { return express_parser_1.ExpressParser; } });
|
|
25
25
|
Object.defineProperty(exports, "looksLikeRouteFile", { enumerable: true, get: function () { return express_parser_1.looksLikeRouteFile; } });
|
|
26
|
+
var socketio_parser_1 = require("./parser/socketio-parser");
|
|
27
|
+
Object.defineProperty(exports, "SocketIOParser", { enumerable: true, get: function () { return socketio_parser_1.SocketIOParser; } });
|
|
28
|
+
Object.defineProperty(exports, "looksLikeSocketIOFile", { enumerable: true, get: function () { return socketio_parser_1.looksLikeSocketIOFile; } });
|
|
29
|
+
var trpc_parser_1 = require("./parser/trpc-parser");
|
|
30
|
+
Object.defineProperty(exports, "TRPCParser", { enumerable: true, get: function () { return trpc_parser_1.TRPCParser; } });
|
|
31
|
+
Object.defineProperty(exports, "looksLikeTRPCFile", { enumerable: true, get: function () { return trpc_parser_1.looksLikeTRPCFile; } });
|
|
32
|
+
var sse_parser_1 = require("./parser/sse-parser");
|
|
33
|
+
Object.defineProperty(exports, "SSEParser", { enumerable: true, get: function () { return sse_parser_1.SSEParser; } });
|
|
34
|
+
Object.defineProperty(exports, "looksLikeSSEFile", { enumerable: true, get: function () { return sse_parser_1.looksLikeSSEFile; } });
|
|
35
|
+
var remix_parser_1 = require("./parser/remix-parser");
|
|
36
|
+
Object.defineProperty(exports, "RemixParser", { enumerable: true, get: function () { return remix_parser_1.RemixParser; } });
|
|
37
|
+
Object.defineProperty(exports, "looksLikeRemixRouteFile", { enumerable: true, get: function () { return remix_parser_1.looksLikeRemixRouteFile; } });
|
|
38
|
+
var websocket_parser_1 = require("./parser/websocket-parser");
|
|
39
|
+
Object.defineProperty(exports, "WebSocketParser", { enumerable: true, get: function () { return websocket_parser_1.WebSocketParser; } });
|
|
40
|
+
Object.defineProperty(exports, "looksLikeWebSocketFile", { enumerable: true, get: function () { return websocket_parser_1.looksLikeWebSocketFile; } });
|
|
41
|
+
var openapi_parser_1 = require("./parser/openapi-parser");
|
|
42
|
+
Object.defineProperty(exports, "OpenAPIParser", { enumerable: true, get: function () { return openapi_parser_1.OpenAPIParser; } });
|
|
43
|
+
Object.defineProperty(exports, "OPENAPI_PATTERNS", { enumerable: true, get: function () { return openapi_parser_1.OPENAPI_PATTERNS; } });
|
|
44
|
+
var prisma_parser_1 = require("./parser/prisma-parser");
|
|
45
|
+
Object.defineProperty(exports, "PrismaParser", { enumerable: true, get: function () { return prisma_parser_1.PrismaParser; } });
|
|
46
|
+
Object.defineProperty(exports, "PRISMA_PATTERNS", { enumerable: true, get: function () { return prisma_parser_1.PRISMA_PATTERNS; } });
|
|
26
47
|
var contract_parser_1 = require("./parser/contract-parser");
|
|
27
48
|
Object.defineProperty(exports, "ContractParser", { enumerable: true, get: function () { return contract_parser_1.ContractParser; } });
|
|
28
49
|
var zod_extractor_1 = require("./parser/zod-extractor");
|
|
@@ -9,7 +9,7 @@ const INTENT_RULES = [
|
|
|
9
9
|
// getUsers don't have a word boundary after the verb segment. Leading \b is kept
|
|
10
10
|
// to avoid matching mid-word (e.g. "undo" matching "do").
|
|
11
11
|
// Game
|
|
12
|
-
{ pattern: /\b(play|flip|roll|spin|bet|guess|draw|move|attack|defend|claim(?:Prize|Reward|Win))/i, intent: 'game.play' },
|
|
12
|
+
{ pattern: /\b(play|flip|roll|spin|bet|guess|draw|move|make(?:Move)?|drop(?:Piece)?|attack|defend|claim(?:Prize|Reward|Win))/i, intent: 'game.play' },
|
|
13
13
|
{ pattern: /\b(score|leaderboard|rank|highscore)/i, intent: 'game.score' },
|
|
14
14
|
{ pattern: /\b(join(?:Game|Room|Lobby)|create(?:Game|Room)|start(?:Game|Round))/i, intent: 'game.join' },
|
|
15
15
|
// Finance / DeFi
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.OPENAPI_PATTERNS = exports.OpenAPIParser = void 0;
|
|
37
|
+
const fs = __importStar(require("fs"));
|
|
38
|
+
const intent_classifier_1 = require("./intent-classifier");
|
|
39
|
+
/**
|
|
40
|
+
* Ingests existing OpenAPI 3.x / Swagger 2.x spec files and converts
|
|
41
|
+
* each operation into an agent action.
|
|
42
|
+
*
|
|
43
|
+
* Supports JSON natively. YAML is supported if `js-yaml` is installed
|
|
44
|
+
* in the target project (optional peer dep — we require() it at runtime
|
|
45
|
+
* so the compiler doesn't hard-depend on it).
|
|
46
|
+
*/
|
|
47
|
+
class OpenAPIParser {
|
|
48
|
+
parseFile(filePath, projectPath) {
|
|
49
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
50
|
+
const spec = this.parseSpec(filePath, content);
|
|
51
|
+
if (!spec)
|
|
52
|
+
return [];
|
|
53
|
+
// Support both OpenAPI 3.x (paths) and Swagger 2.x (basePath + paths)
|
|
54
|
+
const paths = spec.paths ?? {};
|
|
55
|
+
const actions = [];
|
|
56
|
+
for (const [routePath, pathItem] of Object.entries(paths)) {
|
|
57
|
+
for (const method of ['get', 'post', 'put', 'patch', 'delete', 'head']) {
|
|
58
|
+
const operation = pathItem[method];
|
|
59
|
+
if (!operation)
|
|
60
|
+
continue;
|
|
61
|
+
const operationId = operation.operationId ?? this.pathToActionName(routePath, method);
|
|
62
|
+
const description = operation.summary ?? operation.description ?? `${method.toUpperCase()} ${routePath}`;
|
|
63
|
+
const httpMethod = method.toUpperCase();
|
|
64
|
+
const safety = (0, intent_classifier_1.classifySafety)({ name: operationId, httpMethod, type: 'api' });
|
|
65
|
+
// Extract parameters (path + query + header)
|
|
66
|
+
const inputs = {};
|
|
67
|
+
for (const param of operation.parameters ?? []) {
|
|
68
|
+
inputs[param.name] = {
|
|
69
|
+
type: param.schema?.type ?? 'string',
|
|
70
|
+
description: param.description,
|
|
71
|
+
required: param.required ?? false,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
// Extract requestBody schema fields (OpenAPI 3.x)
|
|
75
|
+
const bodySchema = operation.requestBody?.content?.['application/json']?.schema;
|
|
76
|
+
if (bodySchema?.properties) {
|
|
77
|
+
const required = bodySchema.required ?? [];
|
|
78
|
+
for (const [field, schema] of Object.entries(bodySchema.properties)) {
|
|
79
|
+
inputs[field] = {
|
|
80
|
+
type: schema.type ?? 'any',
|
|
81
|
+
description: schema.description,
|
|
82
|
+
required: required.includes(field),
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
// Derive auth requirement from OpenAPI security field
|
|
87
|
+
const hasSecurity = (operation.security && operation.security.length > 0) ||
|
|
88
|
+
(spec.security && spec.security.length > 0);
|
|
89
|
+
actions.push({
|
|
90
|
+
name: operationId,
|
|
91
|
+
description,
|
|
92
|
+
intent: (0, intent_classifier_1.inferIntent)(operationId),
|
|
93
|
+
type: 'api',
|
|
94
|
+
location: routePath,
|
|
95
|
+
method: httpMethod,
|
|
96
|
+
safety,
|
|
97
|
+
agentSafe: (0, intent_classifier_1.deriveAgentSafe)(safety),
|
|
98
|
+
requiredAuth: hasSecurity
|
|
99
|
+
? { required: 'required' }
|
|
100
|
+
: (0, intent_classifier_1.inferActionAuth)({ safety, httpMethod, type: 'api' }),
|
|
101
|
+
inputs,
|
|
102
|
+
outputs: this.extractOutputSchema(operation),
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return actions;
|
|
107
|
+
}
|
|
108
|
+
parseSpec(filePath, content) {
|
|
109
|
+
// JSON
|
|
110
|
+
if (filePath.endsWith('.json')) {
|
|
111
|
+
try {
|
|
112
|
+
return JSON.parse(content);
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
// YAML — optional peer dep
|
|
119
|
+
try {
|
|
120
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
121
|
+
const yaml = require('js-yaml');
|
|
122
|
+
return yaml.load(content);
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
// js-yaml not installed or parse error
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
pathToActionName(routePath, method) {
|
|
130
|
+
const slug = routePath
|
|
131
|
+
.replace(/^\//, '')
|
|
132
|
+
.replace(/\{([^}]+)\}/g, '_$1')
|
|
133
|
+
.replace(/\//g, '_')
|
|
134
|
+
.replace(/[^a-zA-Z0-9_]/g, '');
|
|
135
|
+
return slug ? `${slug}_${method.toUpperCase()}` : `root_${method.toUpperCase()}`;
|
|
136
|
+
}
|
|
137
|
+
extractOutputSchema(operation) {
|
|
138
|
+
const response200 = operation.responses?.['200'] ?? operation.responses?.['201'];
|
|
139
|
+
if (!response200)
|
|
140
|
+
return { type: 'any' };
|
|
141
|
+
const schema = response200.content?.['application/json']?.schema;
|
|
142
|
+
return { type: schema?.type ?? 'object', description: response200.description };
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
exports.OpenAPIParser = OpenAPIParser;
|
|
146
|
+
/** File patterns that contain OpenAPI specs */
|
|
147
|
+
exports.OPENAPI_PATTERNS = [
|
|
148
|
+
'**/openapi.json',
|
|
149
|
+
'**/openapi.yaml',
|
|
150
|
+
'**/openapi.yml',
|
|
151
|
+
'**/swagger.json',
|
|
152
|
+
'**/swagger.yaml',
|
|
153
|
+
'**/swagger.yml',
|
|
154
|
+
'**/api-spec.json',
|
|
155
|
+
'**/api.json',
|
|
156
|
+
];
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.PRISMA_PATTERNS = exports.PrismaParser = void 0;
|
|
37
|
+
const fs = __importStar(require("fs"));
|
|
38
|
+
const intent_classifier_1 = require("./intent-classifier");
|
|
39
|
+
/**
|
|
40
|
+
* Reads a Prisma schema file and infers CRUD agent actions for every model.
|
|
41
|
+
*
|
|
42
|
+
* For a model named `User` it emits:
|
|
43
|
+
* listUsers → data.read (GET-like)
|
|
44
|
+
* getUser → data.read (GET-like, by id)
|
|
45
|
+
* createUser → data.create (POST-like)
|
|
46
|
+
* updateUser → data.update (PATCH-like)
|
|
47
|
+
* deleteUser → data.delete (DELETE-like)
|
|
48
|
+
*
|
|
49
|
+
* Fields declared on the model are mapped to action inputs where relevant.
|
|
50
|
+
*/
|
|
51
|
+
class PrismaParser {
|
|
52
|
+
parseFile(filePath) {
|
|
53
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
54
|
+
const models = this.extractModels(content);
|
|
55
|
+
const actions = [];
|
|
56
|
+
for (const model of models) {
|
|
57
|
+
const name = model.name;
|
|
58
|
+
const lower = name.charAt(0).toLowerCase() + name.slice(1);
|
|
59
|
+
const location = filePath;
|
|
60
|
+
// Writeable fields (non-id, non-auto) used as create/update inputs
|
|
61
|
+
const writeableFields = model.fields.filter(f => !f.isId && !f.isAuto && !f.isList);
|
|
62
|
+
const idField = model.fields.find(f => f.isId) ?? { name: 'id', type: 'String' };
|
|
63
|
+
const idInput = {
|
|
64
|
+
[idField.name]: { type: this.prismaTypeToJson(idField.type), required: true },
|
|
65
|
+
};
|
|
66
|
+
const writeInputs = {};
|
|
67
|
+
for (const f of writeableFields) {
|
|
68
|
+
writeInputs[f.name] = {
|
|
69
|
+
type: this.prismaTypeToJson(f.type),
|
|
70
|
+
required: !f.isOptional,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
// list
|
|
74
|
+
actions.push({
|
|
75
|
+
name: `list${name}s`,
|
|
76
|
+
description: `List all ${name} records`,
|
|
77
|
+
intent: 'data.read',
|
|
78
|
+
type: 'function',
|
|
79
|
+
location,
|
|
80
|
+
safety: 'read',
|
|
81
|
+
agentSafe: true,
|
|
82
|
+
requiredAuth: (0, intent_classifier_1.inferActionAuth)({ safety: 'read', httpMethod: 'GET', type: 'function' }),
|
|
83
|
+
inputs: {},
|
|
84
|
+
outputs: { type: 'array', description: `Array of ${name}` },
|
|
85
|
+
});
|
|
86
|
+
// get
|
|
87
|
+
actions.push({
|
|
88
|
+
name: `get${name}`,
|
|
89
|
+
description: `Get a single ${name} by ${idField.name}`,
|
|
90
|
+
intent: 'data.read',
|
|
91
|
+
type: 'function',
|
|
92
|
+
location,
|
|
93
|
+
safety: 'read',
|
|
94
|
+
agentSafe: true,
|
|
95
|
+
requiredAuth: (0, intent_classifier_1.inferActionAuth)({ safety: 'read', httpMethod: 'GET', type: 'function' }),
|
|
96
|
+
inputs: idInput,
|
|
97
|
+
outputs: { type: 'object', description: name },
|
|
98
|
+
});
|
|
99
|
+
// create
|
|
100
|
+
actions.push({
|
|
101
|
+
name: `create${name}`,
|
|
102
|
+
description: `Create a new ${name} record`,
|
|
103
|
+
intent: 'data.create',
|
|
104
|
+
type: 'function',
|
|
105
|
+
location,
|
|
106
|
+
safety: 'write',
|
|
107
|
+
agentSafe: (0, intent_classifier_1.deriveAgentSafe)('write'),
|
|
108
|
+
requiredAuth: (0, intent_classifier_1.inferActionAuth)({ safety: 'write', type: 'function' }),
|
|
109
|
+
inputs: writeInputs,
|
|
110
|
+
outputs: { type: 'object', description: `Created ${name}` },
|
|
111
|
+
});
|
|
112
|
+
// update
|
|
113
|
+
actions.push({
|
|
114
|
+
name: `update${name}`,
|
|
115
|
+
description: `Update an existing ${name} record`,
|
|
116
|
+
intent: 'data.update',
|
|
117
|
+
type: 'function',
|
|
118
|
+
location,
|
|
119
|
+
safety: 'write',
|
|
120
|
+
agentSafe: (0, intent_classifier_1.deriveAgentSafe)('write'),
|
|
121
|
+
requiredAuth: (0, intent_classifier_1.inferActionAuth)({ safety: 'write', type: 'function' }),
|
|
122
|
+
inputs: { ...idInput, ...writeInputs },
|
|
123
|
+
outputs: { type: 'object', description: `Updated ${name}` },
|
|
124
|
+
});
|
|
125
|
+
// delete
|
|
126
|
+
actions.push({
|
|
127
|
+
name: `delete${name}`,
|
|
128
|
+
description: `Delete a ${name} record`,
|
|
129
|
+
intent: 'data.delete',
|
|
130
|
+
type: 'function',
|
|
131
|
+
location,
|
|
132
|
+
safety: 'destructive',
|
|
133
|
+
agentSafe: false,
|
|
134
|
+
requiredAuth: (0, intent_classifier_1.inferActionAuth)({ safety: 'destructive', type: 'function' }),
|
|
135
|
+
inputs: idInput,
|
|
136
|
+
outputs: { type: 'object', description: `Deleted ${name}` },
|
|
137
|
+
});
|
|
138
|
+
void lower; // suppress unused-variable warning
|
|
139
|
+
}
|
|
140
|
+
return actions;
|
|
141
|
+
}
|
|
142
|
+
// ─── Schema parsing helpers ───────────────────────────────────────────────
|
|
143
|
+
extractModels(content) {
|
|
144
|
+
const models = [];
|
|
145
|
+
// Match: model ModelName { ... }
|
|
146
|
+
const modelRe = /^model\s+(\w+)\s*\{([^}]+)\}/gm;
|
|
147
|
+
let modelMatch;
|
|
148
|
+
while ((modelMatch = modelRe.exec(content)) !== null) {
|
|
149
|
+
const modelName = modelMatch[1];
|
|
150
|
+
const body = modelMatch[2];
|
|
151
|
+
const fields = this.parseFields(body);
|
|
152
|
+
models.push({ name: modelName, fields });
|
|
153
|
+
}
|
|
154
|
+
return models;
|
|
155
|
+
}
|
|
156
|
+
parseFields(body) {
|
|
157
|
+
const fields = [];
|
|
158
|
+
const lines = body.split('\n');
|
|
159
|
+
for (const raw of lines) {
|
|
160
|
+
const line = raw.trim();
|
|
161
|
+
if (!line || line.startsWith('//') || line.startsWith('@@'))
|
|
162
|
+
continue;
|
|
163
|
+
// field Type @attr @attr
|
|
164
|
+
const match = line.match(/^(\w+)\s+(\w+)(\[\])?(\?)?(.*)$/);
|
|
165
|
+
if (!match)
|
|
166
|
+
continue;
|
|
167
|
+
const [, name, type, isList, isOptional, attrs] = match;
|
|
168
|
+
fields.push({
|
|
169
|
+
name,
|
|
170
|
+
type,
|
|
171
|
+
isList: !!isList,
|
|
172
|
+
isOptional: !!isOptional,
|
|
173
|
+
isId: attrs.includes('@id'),
|
|
174
|
+
isAuto: attrs.includes('@default(autoincrement())') || attrs.includes('@default(uuid())') || attrs.includes('@default(cuid())') || attrs.includes('@updatedAt'),
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
return fields;
|
|
178
|
+
}
|
|
179
|
+
prismaTypeToJson(prismaType) {
|
|
180
|
+
switch (prismaType) {
|
|
181
|
+
case 'String': return 'string';
|
|
182
|
+
case 'Int':
|
|
183
|
+
case 'Float':
|
|
184
|
+
case 'Decimal': return 'number';
|
|
185
|
+
case 'Boolean': return 'boolean';
|
|
186
|
+
case 'DateTime': return 'string';
|
|
187
|
+
case 'Json': return 'object';
|
|
188
|
+
case 'Bytes': return 'string';
|
|
189
|
+
default: return 'object'; // relations / enums
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
exports.PrismaParser = PrismaParser;
|
|
194
|
+
/** Prisma schema file patterns */
|
|
195
|
+
exports.PRISMA_PATTERNS = [
|
|
196
|
+
'**/schema.prisma',
|
|
197
|
+
'**/prisma/schema.prisma',
|
|
198
|
+
];
|