agent-manifest 3.3.0 → 4.0.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 +28 -8
- package/dist/discovery/service.js +11 -0
- package/dist/generator/json.js +3 -1
- package/dist/parser/auth-detector.js +88 -6
- package/dist/parser/contract-parser.js +6 -6
- package/dist/parser/express-parser.js +3 -3
- package/dist/parser/intent-classifier.js +36 -11
- package/dist/parser/openapi-parser.js +6 -6
- package/dist/parser/prisma-parser.js +39 -143
- package/dist/parser/remix-parser.js +3 -3
- package/dist/parser/socketio-parser.js +3 -3
- package/dist/parser/sse-parser.js +3 -3
- package/dist/parser/trpc-parser.js +3 -3
- package/dist/parser/ts-parser.js +20 -20
- package/dist/parser/websocket-parser.js +6 -6
- package/dist/parser/zod-extractor.js +9 -2
- package/package.json +1 -1
- package/schema/agent.schema.json +36 -6
package/dist/cli.js
CHANGED
|
@@ -43,7 +43,7 @@ const program = new commander_1.Command();
|
|
|
43
43
|
program
|
|
44
44
|
.name('agentjson')
|
|
45
45
|
.description('Universal agent manifest compiler — generates agent.json for any web app')
|
|
46
|
-
.version('
|
|
46
|
+
.version('4.0.0')
|
|
47
47
|
.option('-p, --path <path>', 'path to the project root', '.')
|
|
48
48
|
.option('-o, --output <output>', 'output path for agent.json', './public/agent.json')
|
|
49
49
|
.option('--author <author>', 'author name or organization')
|
|
@@ -51,6 +51,7 @@ program
|
|
|
51
51
|
.option('--auth-type <type>', 'override detected auth type: none | bearer | api-key | oauth2 | basic | farcaster-frame | cookie')
|
|
52
52
|
.option('--auth-header <header>', 'auth header name (default: Authorization)')
|
|
53
53
|
.option('--auth-docs <url>', 'URL where agents can obtain credentials')
|
|
54
|
+
.option('--base-url <url>', 'canonical base URL for the app (e.g. https://myapp.com)')
|
|
54
55
|
.action(async (options) => {
|
|
55
56
|
const projectPath = path.resolve(options.path);
|
|
56
57
|
const outputPath = path.resolve(options.output);
|
|
@@ -69,6 +70,7 @@ program
|
|
|
69
70
|
const openApiParser = new index_1.OpenAPIParser();
|
|
70
71
|
const prismaParser = new index_1.PrismaParser();
|
|
71
72
|
const actions = [];
|
|
73
|
+
const dataModel = {};
|
|
72
74
|
// Determine which files are OpenAPI / Prisma by extension/name
|
|
73
75
|
const openApiExts = new Set(['.json', '.yaml', '.yml']);
|
|
74
76
|
const prismaExt = '.prisma';
|
|
@@ -90,7 +92,8 @@ program
|
|
|
90
92
|
// ── Prisma schema ─────────────────────────────────────────────────────
|
|
91
93
|
if (ext === prismaExt || base === 'schema.prisma') {
|
|
92
94
|
try {
|
|
93
|
-
|
|
95
|
+
const prismaResult = prismaParser.parseFile(file);
|
|
96
|
+
Object.assign(dataModel, prismaResult.dataModel);
|
|
94
97
|
}
|
|
95
98
|
catch { /* ignore */ }
|
|
96
99
|
continue;
|
|
@@ -131,7 +134,7 @@ program
|
|
|
131
134
|
const uniqueActions = new Map();
|
|
132
135
|
for (const action of actions) {
|
|
133
136
|
const existing = uniqueActions.get(action.name);
|
|
134
|
-
if (!existing || Object.keys(action.
|
|
137
|
+
if (!existing || Object.keys(action.parameters?.properties ?? {}).length > Object.keys(existing.parameters?.properties ?? {}).length) {
|
|
135
138
|
uniqueActions.set(action.name, action);
|
|
136
139
|
}
|
|
137
140
|
}
|
|
@@ -148,11 +151,18 @@ program
|
|
|
148
151
|
...(options.authDocs && { docsUrl: options.authDocs }),
|
|
149
152
|
};
|
|
150
153
|
const generator = new index_1.ManifestGenerator();
|
|
151
|
-
const manifest = generator.generate(Array.from(uniqueActions.values()), appMetadata, tsParser.getCapabilities(), auth
|
|
154
|
+
const manifest = generator.generate(Array.from(uniqueActions.values()), appMetadata, tsParser.getCapabilities(), auth, '1.0.0', {
|
|
155
|
+
baseUrl: options.baseUrl ?? appMetadata.url,
|
|
156
|
+
dataModel,
|
|
157
|
+
});
|
|
152
158
|
fs.mkdirSync(path.dirname(outputPath), { recursive: true });
|
|
153
159
|
fs.writeFileSync(outputPath, JSON.stringify(manifest, null, 2));
|
|
160
|
+
const uiCount = Array.from(uniqueActions.values()).filter(a => a.type === 'ui').length;
|
|
161
|
+
const modelCount = Object.keys(dataModel).length;
|
|
154
162
|
console.log(`✅ agent.json generated at: ${outputPath}`);
|
|
155
|
-
console.log(` ${uniqueActions.size} actions · ${manifest.capabilities.length} capabilities · auth: ${auth.type}`);
|
|
163
|
+
console.log(` ${uniqueActions.size} actions${uiCount ? ` · ${uiCount} ui-interactions` : ''} · ${manifest.capabilities.length} capabilities · auth: ${auth.type}`);
|
|
164
|
+
if (modelCount > 0)
|
|
165
|
+
console.log(` dataModel: ${modelCount} models`);
|
|
156
166
|
});
|
|
157
167
|
// ─── validate ────────────────────────────────────────────────────────────────
|
|
158
168
|
program
|
|
@@ -186,8 +196,12 @@ program
|
|
|
186
196
|
});
|
|
187
197
|
// ─── Structural validator ────────────────────────────────────────────────────
|
|
188
198
|
const SAFETY_LEVELS = new Set(['read', 'write', 'financial', 'destructive', 'confidential']);
|
|
189
|
-
const ACTION_TYPES = new Set(['api', 'contract', 'function', 'socket']);
|
|
190
|
-
const AUTH_TYPES = new Set([
|
|
199
|
+
const ACTION_TYPES = new Set(['api', 'contract', 'function', 'socket', 'ui']);
|
|
200
|
+
const AUTH_TYPES = new Set([
|
|
201
|
+
'none', 'bearer', 'api-key', 'oauth2', 'basic', 'cookie',
|
|
202
|
+
'siwe', 'farcaster-siwf', 'farcaster-frame',
|
|
203
|
+
'clerk', 'privy', 'dynamic', 'magic', 'passkey', 'saml', 'supabase',
|
|
204
|
+
]);
|
|
191
205
|
const INTENT_RE = /^[a-z][a-z0-9]*\.[a-z][a-z0-9]*$/;
|
|
192
206
|
function validateManifest(m) {
|
|
193
207
|
const errors = [];
|
|
@@ -211,13 +225,19 @@ function validateManifest(m) {
|
|
|
211
225
|
else if (!INTENT_RE.test(action.intent))
|
|
212
226
|
errors.push(`${prefix}: \`intent\` must match domain.verb format`);
|
|
213
227
|
if (!ACTION_TYPES.has(action.type))
|
|
214
|
-
errors.push(`${prefix}: \`type\` must be one of api|contract|function`);
|
|
228
|
+
errors.push(`${prefix}: \`type\` must be one of api|contract|function|socket|ui`);
|
|
215
229
|
if (!SAFETY_LEVELS.has(action.safety))
|
|
216
230
|
errors.push(`${prefix}: \`safety\` must be one of read|write|financial|destructive|confidential`);
|
|
217
231
|
if (typeof action.agentSafe !== 'boolean')
|
|
218
232
|
errors.push(`${prefix}: \`agentSafe\` must be a boolean`);
|
|
219
233
|
if (!action.requiredAuth)
|
|
220
234
|
errors.push(`${prefix}: \`requiredAuth\` is missing`);
|
|
235
|
+
else if (!['public', 'required', 'farcaster-signed'].includes(action.requiredAuth.required))
|
|
236
|
+
errors.push(`${prefix}: \`requiredAuth.required\` must be "public", "required", or "farcaster-signed"`);
|
|
237
|
+
if (!action.parameters || typeof action.parameters.properties !== 'object')
|
|
238
|
+
errors.push(`${prefix}: \`parameters.properties\` must be an object`);
|
|
239
|
+
if (!action.returns || typeof action.returns.type !== 'string')
|
|
240
|
+
errors.push(`${prefix}: \`returns.type\` must be a string`);
|
|
221
241
|
});
|
|
222
242
|
}
|
|
223
243
|
return errors;
|
|
@@ -72,6 +72,15 @@ const ALWAYS_EXCLUDE = [
|
|
|
72
72
|
'!**/secrets/**',
|
|
73
73
|
'!**/credentials/**',
|
|
74
74
|
];
|
|
75
|
+
/**
|
|
76
|
+
* Checks if a path is strictly within the project directory.
|
|
77
|
+
* Prevents path traversal via symbolic links or malformed paths.
|
|
78
|
+
*/
|
|
79
|
+
function isPathWithinProject(filePath, projectPath) {
|
|
80
|
+
const resolved = path.resolve(filePath);
|
|
81
|
+
const root = path.resolve(projectPath);
|
|
82
|
+
return resolved.startsWith(root);
|
|
83
|
+
}
|
|
75
84
|
class DiscoveryService {
|
|
76
85
|
projectPath;
|
|
77
86
|
cache = {};
|
|
@@ -150,6 +159,8 @@ class DiscoveryService {
|
|
|
150
159
|
...ALWAYS_EXCLUDE,
|
|
151
160
|
], { cwd: this.projectPath, absolute: true });
|
|
152
161
|
for (const file of allTsFiles) {
|
|
162
|
+
if (!isPathWithinProject(file, this.projectPath))
|
|
163
|
+
continue;
|
|
153
164
|
if (relevantFiles.includes(file))
|
|
154
165
|
continue;
|
|
155
166
|
const hash = fileHash(file);
|
package/dist/generator/json.js
CHANGED
|
@@ -2,13 +2,14 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.ManifestGenerator = void 0;
|
|
4
4
|
class ManifestGenerator {
|
|
5
|
-
generate(actions, metadata = {}, capabilities = [], auth = { type: 'none' }, version = '1.0.0') {
|
|
5
|
+
generate(actions, metadata = {}, capabilities = [], auth = { type: 'none' }, version = '1.0.0', options = {}) {
|
|
6
6
|
return {
|
|
7
7
|
name: metadata.name ?? 'Web App',
|
|
8
8
|
description: metadata.description ?? 'Auto-generated agent manifest',
|
|
9
9
|
version,
|
|
10
10
|
...(metadata.author && { author: metadata.author }),
|
|
11
11
|
...(metadata.url && { url: metadata.url }),
|
|
12
|
+
...(options.baseUrl && { baseUrl: options.baseUrl }),
|
|
12
13
|
auth,
|
|
13
14
|
metadata: {
|
|
14
15
|
...(metadata.iconUrl && { iconUrl: metadata.iconUrl }),
|
|
@@ -19,6 +20,7 @@ class ManifestGenerator {
|
|
|
19
20
|
},
|
|
20
21
|
capabilities,
|
|
21
22
|
actions,
|
|
23
|
+
...(options.dataModel && Object.keys(options.dataModel).length > 0 && { dataModel: options.dataModel }),
|
|
22
24
|
};
|
|
23
25
|
}
|
|
24
26
|
}
|
|
@@ -33,7 +33,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.AuthDetector = void 0;
|
|
36
|
+
exports.AuthFlowInferrer = exports.AuthDetector = void 0;
|
|
37
37
|
const fs = __importStar(require("fs"));
|
|
38
38
|
const path = __importStar(require("path"));
|
|
39
39
|
const AUTH_SIGNALS = [
|
|
@@ -42,6 +42,15 @@ const AUTH_SIGNALS = [
|
|
|
42
42
|
{ pattern: '@farcaster/frame-node', type: 'farcaster-frame', priority: 100 },
|
|
43
43
|
{ pattern: 'validateFrameMessage', type: 'farcaster-frame', priority: 100 },
|
|
44
44
|
{ pattern: 'frameActionPayload', type: 'farcaster-frame', priority: 100 },
|
|
45
|
+
// Farcaster SIWF — must be before SIWE (higher priority)
|
|
46
|
+
{ pattern: '@farcaster/auth-kit', type: 'farcaster-siwf', priority: 98 },
|
|
47
|
+
{ pattern: 'verifySignInMessage', type: 'farcaster-siwf', priority: 98 },
|
|
48
|
+
{ pattern: 'useSignIn', type: 'farcaster-siwf', priority: 95 },
|
|
49
|
+
// SIWE
|
|
50
|
+
{ pattern: 'SiweMessage', type: 'siwe', priority: 95 },
|
|
51
|
+
{ pattern: "from 'siwe'", type: 'siwe', priority: 95 },
|
|
52
|
+
{ pattern: 'verifySiweMessage', type: 'siwe', priority: 95 },
|
|
53
|
+
{ pattern: 'generateNonce', type: 'siwe', priority: 90 },
|
|
45
54
|
// OAuth2 / NextAuth / Auth.js
|
|
46
55
|
{ pattern: 'NextAuth', type: 'oauth2', priority: 90 },
|
|
47
56
|
{ pattern: 'next-auth', type: 'oauth2', priority: 90 },
|
|
@@ -51,8 +60,14 @@ const AUTH_SIGNALS = [
|
|
|
51
60
|
{ pattern: 'oauth2', type: 'oauth2', priority: 80 },
|
|
52
61
|
{ pattern: 'access_token', type: 'oauth2', priority: 75 },
|
|
53
62
|
{ pattern: 'refresh_token', type: 'oauth2', priority: 75 },
|
|
54
|
-
|
|
55
|
-
{ pattern: '
|
|
63
|
+
// SAML
|
|
64
|
+
{ pattern: 'samlify', type: 'saml', priority: 92 },
|
|
65
|
+
{ pattern: 'passport-saml', type: 'saml', priority: 92 },
|
|
66
|
+
{ pattern: '@node-saml', type: 'saml', priority: 92 },
|
|
67
|
+
{ pattern: 'SAMLResponse', type: 'saml', priority: 88 },
|
|
68
|
+
// Clerk
|
|
69
|
+
{ pattern: 'clerkMiddleware', type: 'clerk', priority: 88 },
|
|
70
|
+
{ pattern: 'currentUser', type: 'clerk', priority: 70 },
|
|
56
71
|
// JWT / Bearer
|
|
57
72
|
{ pattern: 'jwt.verify', type: 'bearer', header: 'Authorization', scheme: 'Bearer', priority: 70 },
|
|
58
73
|
{ pattern: 'jsonwebtoken', type: 'bearer', header: 'Authorization', scheme: 'Bearer', priority: 70 },
|
|
@@ -61,6 +76,25 @@ const AUTH_SIGNALS = [
|
|
|
61
76
|
{ pattern: 'authorization.split', type: 'bearer', header: 'Authorization', scheme: 'Bearer', priority: 70 },
|
|
62
77
|
{ pattern: "headers.get('authorization')", type: 'bearer', header: 'Authorization', scheme: 'Bearer', priority: 65 },
|
|
63
78
|
{ pattern: "req.headers.authorization", type: 'bearer', header: 'Authorization', scheme: 'Bearer', priority: 65 },
|
|
79
|
+
// Privy
|
|
80
|
+
{ pattern: '@privy-io/react-auth', type: 'privy', priority: 85 },
|
|
81
|
+
{ pattern: 'usePrivy', type: 'privy', priority: 85 },
|
|
82
|
+
{ pattern: 'PrivyProvider', type: 'privy', priority: 85 },
|
|
83
|
+
// Dynamic
|
|
84
|
+
{ pattern: '@dynamic-labs/sdk-react-core', type: 'dynamic', priority: 85 },
|
|
85
|
+
{ pattern: 'DynamicContextProvider', type: 'dynamic', priority: 85 },
|
|
86
|
+
{ pattern: 'useDynamicContext', type: 'dynamic', priority: 80 },
|
|
87
|
+
// Magic
|
|
88
|
+
{ pattern: 'magic-sdk', type: 'magic', priority: 80 },
|
|
89
|
+
{ pattern: '@magic-sdk', type: 'magic', priority: 80 },
|
|
90
|
+
{ pattern: 'new Magic(', type: 'magic', priority: 80 },
|
|
91
|
+
// Passkey / WebAuthn
|
|
92
|
+
{ pattern: '@simplewebauthn/browser', type: 'passkey', priority: 78 },
|
|
93
|
+
{ pattern: 'startAuthentication', type: 'passkey', priority: 78 },
|
|
94
|
+
{ pattern: 'startRegistration', type: 'passkey', priority: 78 },
|
|
95
|
+
{ pattern: 'navigator.credentials.create', type: 'passkey', priority: 75 },
|
|
96
|
+
// Supabase
|
|
97
|
+
{ pattern: 'supabase.auth', type: 'supabase', priority: 75 },
|
|
64
98
|
// API key
|
|
65
99
|
{ pattern: "'x-api-key'", type: 'api-key', header: 'X-API-Key', priority: 60 },
|
|
66
100
|
{ pattern: '"x-api-key"', type: 'api-key', header: 'X-API-Key', priority: 60 },
|
|
@@ -80,6 +114,7 @@ const AUTH_SIGNALS = [
|
|
|
80
114
|
];
|
|
81
115
|
class AuthDetector {
|
|
82
116
|
best = null;
|
|
117
|
+
_flowInferrer = new AuthFlowInferrer();
|
|
83
118
|
scanContent(content) {
|
|
84
119
|
for (const signal of AUTH_SIGNALS) {
|
|
85
120
|
if (content.includes(signal.pattern)) {
|
|
@@ -88,6 +123,7 @@ class AuthDetector {
|
|
|
88
123
|
}
|
|
89
124
|
}
|
|
90
125
|
}
|
|
126
|
+
this._flowInferrer.addContent(content);
|
|
91
127
|
}
|
|
92
128
|
/** Read package.json to detect auth libraries in dependencies */
|
|
93
129
|
readPackageJson(projectPath) {
|
|
@@ -99,8 +135,24 @@ class AuthDetector {
|
|
|
99
135
|
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
100
136
|
if (deps['next-auth'] || deps['@auth/core'])
|
|
101
137
|
this.applySignal({ pattern: 'next-auth', type: 'oauth2', priority: 90 });
|
|
138
|
+
if (deps['@farcaster/auth-kit'])
|
|
139
|
+
this.applySignal({ pattern: 'farcaster-siwf', type: 'farcaster-siwf', priority: 98 });
|
|
140
|
+
if (deps['siwe'])
|
|
141
|
+
this.applySignal({ pattern: 'siwe', type: 'siwe', priority: 95 });
|
|
142
|
+
if (deps['samlify'] || deps['passport-saml'])
|
|
143
|
+
this.applySignal({ pattern: 'saml', type: 'saml', priority: 92 });
|
|
102
144
|
if (deps['@clerk/nextjs'] || deps['@clerk/clerk-sdk-node'])
|
|
103
|
-
this.applySignal({ pattern: 'clerk', type: '
|
|
145
|
+
this.applySignal({ pattern: 'clerk', type: 'clerk', priority: 88 });
|
|
146
|
+
if (deps['@privy-io/react-auth'])
|
|
147
|
+
this.applySignal({ pattern: 'privy', type: 'privy', priority: 85 });
|
|
148
|
+
if (deps['@dynamic-labs/sdk-react-core'])
|
|
149
|
+
this.applySignal({ pattern: 'dynamic', type: 'dynamic', priority: 85 });
|
|
150
|
+
if (deps['magic-sdk'] || deps['@magic-sdk/admin'])
|
|
151
|
+
this.applySignal({ pattern: 'magic', type: 'magic', priority: 80 });
|
|
152
|
+
if (deps['@simplewebauthn/browser'])
|
|
153
|
+
this.applySignal({ pattern: 'passkey', type: 'passkey', priority: 78 });
|
|
154
|
+
if (deps['@supabase/supabase-js'])
|
|
155
|
+
this.applySignal({ pattern: 'supabase', type: 'supabase', priority: 75 });
|
|
104
156
|
if (deps['jsonwebtoken'] || deps['jose'])
|
|
105
157
|
this.applySignal({ pattern: 'jwt', type: 'bearer', header: 'Authorization', scheme: 'Bearer', priority: 70 });
|
|
106
158
|
if (deps['iron-session'])
|
|
@@ -118,14 +170,44 @@ class AuthDetector {
|
|
|
118
170
|
getAuth() {
|
|
119
171
|
if (!this.best)
|
|
120
172
|
return { type: 'none' };
|
|
121
|
-
const
|
|
173
|
+
const type = this.best.type;
|
|
174
|
+
const config = { type };
|
|
122
175
|
if (this.best.header)
|
|
123
176
|
config.header = this.best.header;
|
|
124
177
|
if (this.best.scheme)
|
|
125
178
|
config.scheme = this.best.scheme;
|
|
126
179
|
if (this.best.queryParam)
|
|
127
180
|
config.queryParam = this.best.queryParam;
|
|
128
|
-
|
|
181
|
+
const flowFields = this._flowInferrer.infer(type);
|
|
182
|
+
return { ...config, ...flowFields };
|
|
129
183
|
}
|
|
130
184
|
}
|
|
131
185
|
exports.AuthDetector = AuthDetector;
|
|
186
|
+
class AuthFlowInferrer {
|
|
187
|
+
scannedContents = [];
|
|
188
|
+
addContent(content) {
|
|
189
|
+
this.scannedContents.push(content);
|
|
190
|
+
}
|
|
191
|
+
infer(authType) {
|
|
192
|
+
const result = {};
|
|
193
|
+
for (const content of this.scannedContents) {
|
|
194
|
+
if (!result.nonceUrl && (authType === 'siwe' || authType === 'farcaster-siwf')) {
|
|
195
|
+
const m = content.match(/['"`](\/api\/auth\/nonce[^'"`\s]*)['"`]/);
|
|
196
|
+
if (m)
|
|
197
|
+
result.nonceUrl = m[1];
|
|
198
|
+
}
|
|
199
|
+
if (!result.callbackUrl && authType === 'oauth2') {
|
|
200
|
+
const m = content.match(/['"`](\/api\/auth\/callback[^'"`\s]*)['"`]/);
|
|
201
|
+
if (m)
|
|
202
|
+
result.callbackUrl = m[1];
|
|
203
|
+
}
|
|
204
|
+
if (!result.loginUrl) {
|
|
205
|
+
const m = content.match(/['"`](\/sign-in|\/login|\/auth\/login)['"`]/);
|
|
206
|
+
if (m)
|
|
207
|
+
result.loginUrl = m[1];
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
return result;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
exports.AuthFlowInferrer = AuthFlowInferrer;
|
|
@@ -93,10 +93,10 @@ class ContractParser {
|
|
|
93
93
|
abiFunction: item.name,
|
|
94
94
|
isReadOnly,
|
|
95
95
|
safety,
|
|
96
|
-
agentSafe: (0, intent_classifier_1.deriveAgentSafe)(safety),
|
|
96
|
+
agentSafe: (0, intent_classifier_1.deriveAgentSafe)(safety, item.name),
|
|
97
97
|
requiredAuth: (0, intent_classifier_1.inferActionAuth)({ safety, isReadOnly, type: 'contract' }),
|
|
98
|
-
|
|
99
|
-
|
|
98
|
+
parameters: { properties: this.mapAbiInputs(item.inputs ?? []) },
|
|
99
|
+
returns: {
|
|
100
100
|
type: this.mapAbiOutputs(item.outputs ?? []),
|
|
101
101
|
description: '',
|
|
102
102
|
},
|
|
@@ -172,10 +172,10 @@ class ContractParser {
|
|
|
172
172
|
...(chainId !== undefined ? { chainId } : {}),
|
|
173
173
|
...(contractAddress !== undefined ? { contractAddress } : {}),
|
|
174
174
|
safety,
|
|
175
|
-
agentSafe: (0, intent_classifier_1.deriveAgentSafe)(safety),
|
|
175
|
+
agentSafe: (0, intent_classifier_1.deriveAgentSafe)(safety, functionName),
|
|
176
176
|
requiredAuth: (0, intent_classifier_1.inferActionAuth)({ safety, isReadOnly: false, type: 'contract' }),
|
|
177
|
-
|
|
178
|
-
|
|
177
|
+
parameters: { properties: parameters },
|
|
178
|
+
returns: { type: 'any' },
|
|
179
179
|
});
|
|
180
180
|
});
|
|
181
181
|
return actions;
|
|
@@ -113,10 +113,10 @@ class ExpressParser {
|
|
|
113
113
|
location: routePath,
|
|
114
114
|
method: httpMethod,
|
|
115
115
|
safety,
|
|
116
|
-
agentSafe: (0, intent_classifier_1.deriveAgentSafe)(safety),
|
|
116
|
+
agentSafe: (0, intent_classifier_1.deriveAgentSafe)(safety, actionName),
|
|
117
117
|
requiredAuth: (0, intent_classifier_1.inferActionAuth)({ safety, httpMethod, type: 'api' }),
|
|
118
|
-
|
|
119
|
-
|
|
118
|
+
parameters: { properties: this.extractRouteParams(routePath) },
|
|
119
|
+
returns: { type: 'any' },
|
|
120
120
|
});
|
|
121
121
|
});
|
|
122
122
|
return actions;
|
|
@@ -25,16 +25,16 @@ const INTENT_RULES = [
|
|
|
25
25
|
{ pattern: /\b(listNFT|sellNFT|listFor(?:Sale)?)/i, intent: 'nft.list' },
|
|
26
26
|
{ pattern: /\b(buyNFT|purchaseNFT)/i, intent: 'nft.buy' },
|
|
27
27
|
{ pattern: /\b(burnNFT|burn)/i, intent: 'nft.burn' },
|
|
28
|
+
// Governance (must come BEFORE Social so castVote → governance.vote, not social.cast)
|
|
29
|
+
{ pattern: /\b(vote|castVote|submitVote)/i, intent: 'governance.vote' },
|
|
30
|
+
{ pattern: /\b(propose|createProposal|submitProposal)/i, intent: 'governance.propose' },
|
|
31
|
+
{ pattern: /\b(delegate|undelegate)/i, intent: 'governance.delegate' },
|
|
28
32
|
// Social (Farcaster-native)
|
|
29
|
-
{ pattern: /\b(cast|compose(?:Cast)?|post(?:Cast)?)/i, intent: 'social.cast' },
|
|
33
|
+
{ pattern: /\b(cast(?!Vote)|compose(?:Cast)?|post(?:Cast)?)/i, intent: 'social.cast' },
|
|
30
34
|
{ pattern: /\b(follow|unfollow|subscribe)/i, intent: 'social.follow' },
|
|
31
35
|
{ pattern: /\b(like|react|upvote|downvote)/i, intent: 'social.react' },
|
|
32
36
|
{ pattern: /\b(comment|reply)/i, intent: 'social.reply' },
|
|
33
37
|
{ pattern: /\b(share|recast|repost)/i, intent: 'social.share' },
|
|
34
|
-
// Governance
|
|
35
|
-
{ pattern: /\b(vote|castVote|submitVote)/i, intent: 'governance.vote' },
|
|
36
|
-
{ pattern: /\b(propose|createProposal|submitProposal)/i, intent: 'governance.propose' },
|
|
37
|
-
{ pattern: /\b(delegate|undelegate)/i, intent: 'governance.delegate' },
|
|
38
38
|
// Auth
|
|
39
39
|
{ pattern: /\b(login|logout|signIn|signOut|connect|disconnect)/i, intent: 'auth.session' },
|
|
40
40
|
{ pattern: /\b(register|signup|createAccount)/i, intent: 'auth.register' },
|
|
@@ -73,7 +73,9 @@ const DESTRUCTIVE_VERBS = /\b(delete|remove|destroy|burn|archive|purge|wipe|clea
|
|
|
73
73
|
* updateSsn, exportPrivateKey, getMedicalRecord.
|
|
74
74
|
*/
|
|
75
75
|
// No \b — these nouns appear anywhere in camelCase (resetPassword, submitKyc, getSsn)
|
|
76
|
-
const CONFIDENTIAL_NOUNS = /(password|credential|privateKey|secretKey|biometric|ssn|taxId|pii|kyc|
|
|
76
|
+
const CONFIDENTIAL_NOUNS = /(password|credential|privateKey|secretKey|biometric|ssn|taxId|tax_id|pii|kyc|medical|health|passport|license|creditCard|cvv|encrypted|identity|dob|birthDate|address|phone|mobile|email)/i;
|
|
77
|
+
/** Verbs that imply high-privilege administrative or sensitive state changes. */
|
|
78
|
+
const SENSITIVE_WRITE_VERBS = /\b(admin|role|permission|password|owner|ban|block|lock|unlock|grant|revoke|delegate|sudo|root|secret)/i;
|
|
77
79
|
/**
|
|
78
80
|
* Classify the safety level of an action.
|
|
79
81
|
*
|
|
@@ -97,19 +99,40 @@ function classifySafety(opts) {
|
|
|
97
99
|
return 'confidential';
|
|
98
100
|
return 'write';
|
|
99
101
|
}
|
|
102
|
+
// Financial and confidential always win regardless of type
|
|
100
103
|
if (FINANCIAL_VERBS.test(name))
|
|
101
104
|
return 'financial';
|
|
102
105
|
if (CONFIDENTIAL_NOUNS.test(name))
|
|
103
106
|
return 'confidential';
|
|
107
|
+
if (type === 'function' || type === 'ui') {
|
|
108
|
+
if (DESTRUCTIVE_VERBS.test(name))
|
|
109
|
+
return 'destructive';
|
|
110
|
+
// Auth functions are confidential (handle credentials)
|
|
111
|
+
if (/\b(login|logout|signIn|signOut|register|signup|createAccount)/i.test(name))
|
|
112
|
+
return 'confidential';
|
|
113
|
+
// Social/game → write
|
|
114
|
+
return 'write';
|
|
115
|
+
}
|
|
104
116
|
if (httpMethod === 'GET')
|
|
105
117
|
return 'read';
|
|
118
|
+
if (isReadOnly)
|
|
119
|
+
return 'read';
|
|
106
120
|
if (DESTRUCTIVE_VERBS.test(name))
|
|
107
121
|
return 'destructive';
|
|
108
122
|
return 'write';
|
|
109
123
|
}
|
|
110
|
-
/**
|
|
111
|
-
|
|
112
|
-
|
|
124
|
+
/**
|
|
125
|
+
* Derive agentSafe from safety level.
|
|
126
|
+
* Only 'read' and non-sensitive 'write' actions are safe for autonomous execution.
|
|
127
|
+
*/
|
|
128
|
+
function deriveAgentSafe(safety, name) {
|
|
129
|
+
if (safety === 'read')
|
|
130
|
+
return true;
|
|
131
|
+
if (safety === 'write') {
|
|
132
|
+
// Narrowing: check if the write action name contains high-privilege keywords
|
|
133
|
+
return !SENSITIVE_WRITE_VERBS.test(name);
|
|
134
|
+
}
|
|
135
|
+
return false;
|
|
113
136
|
}
|
|
114
137
|
/**
|
|
115
138
|
* Infer per-action auth requirement.
|
|
@@ -149,8 +172,10 @@ function inferActionAuth(opts) {
|
|
|
149
172
|
if (safety === 'destructive') {
|
|
150
173
|
return { required: 'required' };
|
|
151
174
|
}
|
|
152
|
-
//
|
|
153
|
-
|
|
175
|
+
// No-auth app → read and write actions are public (non-contract only).
|
|
176
|
+
// Financial/confidential/destructive retain their required auth even on no-auth apps
|
|
177
|
+
// (those actions handle money or PII and should always require confirmation).
|
|
178
|
+
if (type !== 'contract' && (appAuthType === 'none' || !appAuthType) && (safety === 'read' || safety === 'write')) {
|
|
154
179
|
return { required: 'public' };
|
|
155
180
|
}
|
|
156
181
|
return { required: 'required' };
|
|
@@ -63,9 +63,9 @@ class OpenAPIParser {
|
|
|
63
63
|
const httpMethod = method.toUpperCase();
|
|
64
64
|
const safety = (0, intent_classifier_1.classifySafety)({ name: operationId, httpMethod, type: 'api' });
|
|
65
65
|
// Extract parameters (path + query + header)
|
|
66
|
-
const
|
|
66
|
+
const props = {};
|
|
67
67
|
for (const param of operation.parameters ?? []) {
|
|
68
|
-
|
|
68
|
+
props[param.name] = {
|
|
69
69
|
type: param.schema?.type ?? 'string',
|
|
70
70
|
description: param.description,
|
|
71
71
|
required: param.required ?? false,
|
|
@@ -76,7 +76,7 @@ class OpenAPIParser {
|
|
|
76
76
|
if (bodySchema?.properties) {
|
|
77
77
|
const required = bodySchema.required ?? [];
|
|
78
78
|
for (const [field, schema] of Object.entries(bodySchema.properties)) {
|
|
79
|
-
|
|
79
|
+
props[field] = {
|
|
80
80
|
type: schema.type ?? 'any',
|
|
81
81
|
description: schema.description,
|
|
82
82
|
required: required.includes(field),
|
|
@@ -94,12 +94,12 @@ class OpenAPIParser {
|
|
|
94
94
|
location: routePath,
|
|
95
95
|
method: httpMethod,
|
|
96
96
|
safety,
|
|
97
|
-
agentSafe: (0, intent_classifier_1.deriveAgentSafe)(safety),
|
|
97
|
+
agentSafe: (0, intent_classifier_1.deriveAgentSafe)(safety, operationId),
|
|
98
98
|
requiredAuth: hasSecurity
|
|
99
99
|
? { required: 'required' }
|
|
100
100
|
: (0, intent_classifier_1.inferActionAuth)({ safety, httpMethod, type: 'api' }),
|
|
101
|
-
|
|
102
|
-
|
|
101
|
+
parameters: { properties: props },
|
|
102
|
+
returns: this.extractOutputSchema(operation),
|
|
103
103
|
});
|
|
104
104
|
}
|
|
105
105
|
}
|
|
@@ -35,159 +35,55 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.PRISMA_PATTERNS = exports.PrismaParser = void 0;
|
|
37
37
|
const fs = __importStar(require("fs"));
|
|
38
|
-
const intent_classifier_1 = require("./intent-classifier");
|
|
39
38
|
/**
|
|
40
|
-
* Reads a Prisma schema file and
|
|
39
|
+
* Reads a Prisma schema file and extracts the data model as a structured
|
|
40
|
+
* `dataModel` map. Each Prisma model becomes an entry with its writeable
|
|
41
|
+
* fields (auto-managed fields like @id, @default(now()), @updatedAt are excluded).
|
|
41
42
|
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
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.
|
|
43
|
+
* Returns `{ actions: [], dataModel }` — no CRUD actions are generated.
|
|
44
|
+
* Prisma models are data schema, not API endpoints.
|
|
50
45
|
*/
|
|
51
46
|
class PrismaParser {
|
|
52
47
|
parseFile(filePath) {
|
|
53
48
|
const content = fs.readFileSync(filePath, 'utf8');
|
|
54
|
-
const
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
const
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
49
|
+
const dataModel = {};
|
|
50
|
+
const modelRegex = /^model\s+(\w+)\s*\{([^}]+)\}/gm;
|
|
51
|
+
let match;
|
|
52
|
+
while ((match = modelRegex.exec(content)) !== null) {
|
|
53
|
+
const modelName = match[1];
|
|
54
|
+
const body = match[2];
|
|
55
|
+
const fields = {};
|
|
56
|
+
for (const line of body.split('\n')) {
|
|
57
|
+
const trimmed = line.trim();
|
|
58
|
+
if (!trimmed || trimmed.startsWith('//') || trimmed.startsWith('@@'))
|
|
59
|
+
continue;
|
|
60
|
+
const fieldMatch = /^(\w+)\s+(\w+)(\[\])?([\?])?(.*)?$/.exec(trimmed);
|
|
61
|
+
if (!fieldMatch)
|
|
62
|
+
continue;
|
|
63
|
+
const [, fieldName, fieldType, isList, isOptional, attrs] = fieldMatch;
|
|
64
|
+
// Skip auto-managed fields (any @default(...) variant, not just @default(now()))
|
|
65
|
+
if (attrs?.includes('@id') || attrs?.includes('@default(') || attrs?.includes('@updatedAt'))
|
|
66
|
+
continue;
|
|
67
|
+
fields[fieldName] = {
|
|
68
|
+
type: this.mapFieldType(fieldType, !!isList),
|
|
69
|
+
required: !isOptional,
|
|
71
70
|
};
|
|
72
71
|
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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
|
-
});
|
|
72
|
+
dataModel[modelName] = {
|
|
73
|
+
description: `Prisma model: ${modelName}`,
|
|
74
|
+
fields,
|
|
75
|
+
};
|
|
176
76
|
}
|
|
177
|
-
return
|
|
77
|
+
return { actions: [], dataModel };
|
|
178
78
|
}
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
case 'Json': return 'object';
|
|
188
|
-
case 'Bytes': return 'string';
|
|
189
|
-
default: return 'object'; // relations / enums
|
|
190
|
-
}
|
|
79
|
+
mapFieldType(prismaType, isList) {
|
|
80
|
+
if (isList)
|
|
81
|
+
return 'array';
|
|
82
|
+
const map = {
|
|
83
|
+
String: 'string', Int: 'number', Float: 'number', Decimal: 'number',
|
|
84
|
+
Boolean: 'boolean', DateTime: 'string', Json: 'object', Bytes: 'string',
|
|
85
|
+
};
|
|
86
|
+
return map[prismaType] ?? 'object';
|
|
191
87
|
}
|
|
192
88
|
}
|
|
193
89
|
exports.PrismaParser = PrismaParser;
|
|
@@ -78,10 +78,10 @@ class RemixParser {
|
|
|
78
78
|
location: routePath,
|
|
79
79
|
method: httpMethod,
|
|
80
80
|
safety,
|
|
81
|
-
agentSafe: (0, intent_classifier_1.deriveAgentSafe)(safety),
|
|
81
|
+
agentSafe: (0, intent_classifier_1.deriveAgentSafe)(safety, actionName),
|
|
82
82
|
requiredAuth: (0, intent_classifier_1.inferActionAuth)({ safety, httpMethod, type: 'api' }),
|
|
83
|
-
|
|
84
|
-
|
|
83
|
+
parameters: { properties: this.extractRouteParams(routePath) },
|
|
84
|
+
returns: { type: 'object' },
|
|
85
85
|
});
|
|
86
86
|
}
|
|
87
87
|
return actions;
|
|
@@ -108,10 +108,10 @@ class SocketIOParser {
|
|
|
108
108
|
location: relativePath,
|
|
109
109
|
socketEvent: eventName,
|
|
110
110
|
safety,
|
|
111
|
-
agentSafe: (0, intent_classifier_1.deriveAgentSafe)(safety),
|
|
111
|
+
agentSafe: (0, intent_classifier_1.deriveAgentSafe)(safety, camelName),
|
|
112
112
|
requiredAuth: (0, intent_classifier_1.inferActionAuth)({ safety, type: 'socket' }),
|
|
113
|
-
inputs,
|
|
114
|
-
|
|
113
|
+
parameters: { properties: inputs },
|
|
114
|
+
returns: { type: 'object' },
|
|
115
115
|
});
|
|
116
116
|
});
|
|
117
117
|
return actions;
|
|
@@ -92,10 +92,10 @@ class SSEParser {
|
|
|
92
92
|
location: relativePath,
|
|
93
93
|
method: 'GET',
|
|
94
94
|
safety,
|
|
95
|
-
agentSafe: (0, intent_classifier_1.deriveAgentSafe)(safety),
|
|
95
|
+
agentSafe: (0, intent_classifier_1.deriveAgentSafe)(safety, name),
|
|
96
96
|
requiredAuth: (0, intent_classifier_1.inferActionAuth)({ safety, httpMethod: 'GET', type: 'api' }),
|
|
97
|
-
|
|
98
|
-
|
|
97
|
+
parameters: { properties: {} },
|
|
98
|
+
returns: { type: 'stream', description: 'Server-Sent Events stream' },
|
|
99
99
|
});
|
|
100
100
|
}
|
|
101
101
|
return actions;
|
|
@@ -101,15 +101,15 @@ class TRPCParser {
|
|
|
101
101
|
location: relativePath,
|
|
102
102
|
method: methodName,
|
|
103
103
|
safety,
|
|
104
|
-
agentSafe: (0, intent_classifier_1.deriveAgentSafe)(safety),
|
|
104
|
+
agentSafe: (0, intent_classifier_1.deriveAgentSafe)(safety, procedureName),
|
|
105
105
|
requiredAuth: (0, intent_classifier_1.inferActionAuth)({
|
|
106
106
|
safety,
|
|
107
107
|
httpMethod: httpLike,
|
|
108
108
|
appAuthType: isProtected ? 'bearer' : undefined,
|
|
109
109
|
type: 'function',
|
|
110
110
|
}),
|
|
111
|
-
inputs,
|
|
112
|
-
|
|
111
|
+
parameters: { properties: inputs },
|
|
112
|
+
returns: { type: 'object' },
|
|
113
113
|
});
|
|
114
114
|
});
|
|
115
115
|
return actions;
|
package/dist/parser/ts-parser.js
CHANGED
|
@@ -151,10 +151,10 @@ class TSParser {
|
|
|
151
151
|
location,
|
|
152
152
|
method: methodName,
|
|
153
153
|
safety,
|
|
154
|
-
agentSafe: (0, intent_classifier_1.deriveAgentSafe)(safety),
|
|
154
|
+
agentSafe: (0, intent_classifier_1.deriveAgentSafe)(safety, actionName),
|
|
155
155
|
requiredAuth: this.actionAuth(safety, methodName),
|
|
156
|
-
|
|
157
|
-
|
|
156
|
+
parameters: { properties: zodInputs },
|
|
157
|
+
returns: { type: 'any' },
|
|
158
158
|
});
|
|
159
159
|
}
|
|
160
160
|
// export const POST = async (request: Request) => { ... }
|
|
@@ -177,10 +177,10 @@ class TSParser {
|
|
|
177
177
|
location,
|
|
178
178
|
method: methodName,
|
|
179
179
|
safety,
|
|
180
|
-
agentSafe: (0, intent_classifier_1.deriveAgentSafe)(safety),
|
|
180
|
+
agentSafe: (0, intent_classifier_1.deriveAgentSafe)(safety, actionName),
|
|
181
181
|
requiredAuth: this.actionAuth(safety, methodName),
|
|
182
|
-
|
|
183
|
-
|
|
182
|
+
parameters: { properties: zodInputs },
|
|
183
|
+
returns: { type: 'any' },
|
|
184
184
|
});
|
|
185
185
|
}
|
|
186
186
|
}
|
|
@@ -201,10 +201,10 @@ class TSParser {
|
|
|
201
201
|
location,
|
|
202
202
|
method,
|
|
203
203
|
safety,
|
|
204
|
-
agentSafe: (0, intent_classifier_1.deriveAgentSafe)(safety),
|
|
204
|
+
agentSafe: (0, intent_classifier_1.deriveAgentSafe)(safety, actionName),
|
|
205
205
|
requiredAuth: this.actionAuth(safety, method),
|
|
206
|
-
|
|
207
|
-
|
|
206
|
+
parameters: { properties: zodShape ?? {} },
|
|
207
|
+
returns: { type: 'any' },
|
|
208
208
|
});
|
|
209
209
|
}
|
|
210
210
|
// 3d. Generic non-Next.js API routes: api/**
|
|
@@ -224,10 +224,10 @@ class TSParser {
|
|
|
224
224
|
location,
|
|
225
225
|
method,
|
|
226
226
|
safety,
|
|
227
|
-
agentSafe: (0, intent_classifier_1.deriveAgentSafe)(safety),
|
|
227
|
+
agentSafe: (0, intent_classifier_1.deriveAgentSafe)(safety, actionName),
|
|
228
228
|
requiredAuth: this.actionAuth(safety, method),
|
|
229
|
-
|
|
230
|
-
|
|
229
|
+
parameters: { properties: zodShape ?? {} },
|
|
230
|
+
returns: { type: 'any' },
|
|
231
231
|
});
|
|
232
232
|
}
|
|
233
233
|
// 3e. Server Actions: files with 'use server' directive
|
|
@@ -261,10 +261,10 @@ class TSParser {
|
|
|
261
261
|
type: 'function',
|
|
262
262
|
location: `./${relativePath}`,
|
|
263
263
|
safety,
|
|
264
|
-
agentSafe: (0, intent_classifier_1.deriveAgentSafe)(safety),
|
|
264
|
+
agentSafe: (0, intent_classifier_1.deriveAgentSafe)(safety, name),
|
|
265
265
|
requiredAuth: this.actionAuth(safety, undefined, undefined, 'function'),
|
|
266
|
-
|
|
267
|
-
|
|
266
|
+
parameters: { properties: this.extractFunctionParams(init) },
|
|
267
|
+
returns: { type: 'any' },
|
|
268
268
|
});
|
|
269
269
|
}
|
|
270
270
|
}
|
|
@@ -357,14 +357,14 @@ class TSParser {
|
|
|
357
357
|
?.getComment()
|
|
358
358
|
?.toString()
|
|
359
359
|
.match(/safety=(read|write|financial|destructive)/)?.[1];
|
|
360
|
-
const
|
|
360
|
+
const properties = {};
|
|
361
361
|
if ('getParameters' in declaration) {
|
|
362
362
|
for (const param of declaration.getParameters()) {
|
|
363
363
|
const paramName = param.getName();
|
|
364
364
|
const paramDoc = jsDoc
|
|
365
365
|
?.getTags()
|
|
366
366
|
.find(t => t.getTagName() === 'param' && t.getName() === paramName);
|
|
367
|
-
|
|
367
|
+
properties[paramName] = {
|
|
368
368
|
type: this.mapType(param.getType()),
|
|
369
369
|
description: paramDoc?.getComment()?.toString().trim() || '',
|
|
370
370
|
required: !param.isOptional(),
|
|
@@ -380,10 +380,10 @@ class TSParser {
|
|
|
380
380
|
type: 'function',
|
|
381
381
|
location: `./${relativePath}`,
|
|
382
382
|
safety,
|
|
383
|
-
agentSafe: (0, intent_classifier_1.deriveAgentSafe)(safety),
|
|
383
|
+
agentSafe: (0, intent_classifier_1.deriveAgentSafe)(safety, name),
|
|
384
384
|
requiredAuth: this.actionAuth(safety, undefined, undefined, 'function'),
|
|
385
|
-
|
|
386
|
-
|
|
385
|
+
parameters: { properties },
|
|
386
|
+
returns: {
|
|
387
387
|
type: returnType ? this.mapType(returnType) : 'any',
|
|
388
388
|
description: jsDoc?.getTags().find(t => t.getTagName() === 'returns')?.getComment()?.toString().trim() || '',
|
|
389
389
|
},
|
|
@@ -104,10 +104,10 @@ class WebSocketParser {
|
|
|
104
104
|
location: relativePath,
|
|
105
105
|
socketEvent: msgType,
|
|
106
106
|
safety,
|
|
107
|
-
agentSafe: (0, intent_classifier_1.deriveAgentSafe)(safety),
|
|
107
|
+
agentSafe: (0, intent_classifier_1.deriveAgentSafe)(safety, msgType),
|
|
108
108
|
requiredAuth: (0, intent_classifier_1.inferActionAuth)({ safety, type: 'socket' }),
|
|
109
|
-
|
|
110
|
-
|
|
109
|
+
parameters: { properties: { type: { type: 'string', required: true }, payload: { type: 'object', required: false } } },
|
|
110
|
+
returns: { type: 'object' },
|
|
111
111
|
});
|
|
112
112
|
}
|
|
113
113
|
}
|
|
@@ -124,10 +124,10 @@ class WebSocketParser {
|
|
|
124
124
|
location: relativePath,
|
|
125
125
|
socketEvent: 'message',
|
|
126
126
|
safety: 'write',
|
|
127
|
-
agentSafe:
|
|
127
|
+
agentSafe: (0, intent_classifier_1.deriveAgentSafe)('write', name),
|
|
128
128
|
requiredAuth: (0, intent_classifier_1.inferActionAuth)({ safety: 'write', type: 'socket' }),
|
|
129
|
-
|
|
130
|
-
|
|
129
|
+
parameters: { properties: { data: { type: 'string', required: true } } },
|
|
130
|
+
returns: { type: 'object' },
|
|
131
131
|
});
|
|
132
132
|
}
|
|
133
133
|
}
|
|
@@ -319,8 +319,15 @@ class ZodExtractor {
|
|
|
319
319
|
const envMatch = text.match(/process\.env\.([A-Z0-9_]+)/);
|
|
320
320
|
if (envMatch)
|
|
321
321
|
return { $env: envMatch[1] };
|
|
322
|
-
if (ts_morph_1.Node.isStringLiteral(node))
|
|
323
|
-
|
|
322
|
+
if (ts_morph_1.Node.isStringLiteral(node)) {
|
|
323
|
+
const val = node.getLiteralValue();
|
|
324
|
+
// SECURITY: Redact high-entropy strings or common secret prefixes
|
|
325
|
+
if (/^(sk_|pk_|api_key|token|secret|key-|passwd|auth_|ghp_)/i.test(val) ||
|
|
326
|
+
(val.length > 20 && /[a-z]/i.test(val) && /[0-9]/.test(val) && /[A-Z]/.test(val))) {
|
|
327
|
+
return '[REDACTED_SECRET]';
|
|
328
|
+
}
|
|
329
|
+
return val;
|
|
330
|
+
}
|
|
324
331
|
if (ts_morph_1.Node.isNumericLiteral(node))
|
|
325
332
|
return Number(node.getLiteralValue());
|
|
326
333
|
if (text === 'true')
|
package/package.json
CHANGED
package/schema/agent.schema.json
CHANGED
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"properties": {
|
|
38
38
|
"type": {
|
|
39
39
|
"type": "string",
|
|
40
|
-
"enum": ["none", "bearer", "api-key", "oauth2", "basic", "farcaster-frame", "
|
|
40
|
+
"enum": ["none", "bearer", "api-key", "oauth2", "basic", "cookie", "siwe", "farcaster-siwf", "farcaster-frame", "clerk", "privy", "dynamic", "magic", "passkey", "saml", "supabase"],
|
|
41
41
|
"description": "Authentication scheme"
|
|
42
42
|
},
|
|
43
43
|
"header": { "type": "string", "description": "Header name, e.g. Authorization or X-API-Key" },
|
|
@@ -65,6 +65,14 @@
|
|
|
65
65
|
"actions": {
|
|
66
66
|
"type": "array",
|
|
67
67
|
"items": { "$ref": "#/definitions/AgentAction" }
|
|
68
|
+
},
|
|
69
|
+
"baseUrl": {
|
|
70
|
+
"type": "string",
|
|
71
|
+
"format": "uri"
|
|
72
|
+
},
|
|
73
|
+
"dataModel": {
|
|
74
|
+
"type": "object",
|
|
75
|
+
"additionalProperties": { "$ref": "#/definitions/DataModelEntry" }
|
|
68
76
|
}
|
|
69
77
|
},
|
|
70
78
|
"definitions": {
|
|
@@ -88,7 +96,7 @@
|
|
|
88
96
|
},
|
|
89
97
|
"AgentAction": {
|
|
90
98
|
"type": "object",
|
|
91
|
-
"required": ["name", "description", "intent", "type", "location", "safety", "agentSafe", "
|
|
99
|
+
"required": ["name", "description", "intent", "type", "location", "safety", "agentSafe", "parameters", "returns"],
|
|
92
100
|
"additionalProperties": false,
|
|
93
101
|
"properties": {
|
|
94
102
|
"name": { "type": "string", "description": "Unique action identifier" },
|
|
@@ -100,7 +108,7 @@
|
|
|
100
108
|
},
|
|
101
109
|
"type": {
|
|
102
110
|
"type": "string",
|
|
103
|
-
"enum": ["api", "contract", "function", "socket"]
|
|
111
|
+
"enum": ["api", "contract", "function", "socket", "ui"]
|
|
104
112
|
},
|
|
105
113
|
"location": { "type": "string" },
|
|
106
114
|
"method": {
|
|
@@ -149,11 +157,14 @@
|
|
|
149
157
|
}
|
|
150
158
|
}
|
|
151
159
|
},
|
|
152
|
-
"
|
|
160
|
+
"parameters": {
|
|
153
161
|
"type": "object",
|
|
154
|
-
"
|
|
162
|
+
"properties": {
|
|
163
|
+
"properties": { "type": "object" }
|
|
164
|
+
},
|
|
165
|
+
"required": ["properties"]
|
|
155
166
|
},
|
|
156
|
-
"
|
|
167
|
+
"returns": {
|
|
157
168
|
"type": "object",
|
|
158
169
|
"required": ["type"],
|
|
159
170
|
"additionalProperties": false,
|
|
@@ -163,6 +174,25 @@
|
|
|
163
174
|
}
|
|
164
175
|
}
|
|
165
176
|
}
|
|
177
|
+
},
|
|
178
|
+
"DataModelEntry": {
|
|
179
|
+
"type": "object",
|
|
180
|
+
"properties": {
|
|
181
|
+
"description": { "type": "string" },
|
|
182
|
+
"fields": {
|
|
183
|
+
"type": "object",
|
|
184
|
+
"additionalProperties": {
|
|
185
|
+
"type": "object",
|
|
186
|
+
"properties": {
|
|
187
|
+
"type": { "type": "string" },
|
|
188
|
+
"required": { "type": "boolean" },
|
|
189
|
+
"description": { "type": "string" }
|
|
190
|
+
},
|
|
191
|
+
"required": ["type"]
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
},
|
|
195
|
+
"required": ["fields"]
|
|
166
196
|
}
|
|
167
197
|
}
|
|
168
198
|
}
|