agent-manifest 3.2.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.
@@ -0,0 +1,419 @@
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.TSParser = void 0;
37
+ const ts_morph_1 = require("ts-morph");
38
+ const path = __importStar(require("path"));
39
+ const fs = __importStar(require("fs"));
40
+ const contract_parser_1 = require("./contract-parser");
41
+ const zod_extractor_1 = require("./zod-extractor");
42
+ const capability_detector_1 = require("./capability-detector");
43
+ const auth_detector_1 = require("./auth-detector");
44
+ const intent_classifier_1 = require("./intent-classifier");
45
+ const HTTP_METHODS = new Set(['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS']);
46
+ class TSParser {
47
+ projectPath;
48
+ project;
49
+ contractParser;
50
+ zodExtractor;
51
+ capabilityDetector;
52
+ authDetector;
53
+ _appMetadata = {};
54
+ constructor(projectPath) {
55
+ this.projectPath = projectPath;
56
+ this.project = new ts_morph_1.Project({
57
+ compilerOptions: { allowJs: true, checkJs: false },
58
+ });
59
+ this.contractParser = new contract_parser_1.ContractParser(projectPath);
60
+ this.zodExtractor = new zod_extractor_1.ZodExtractor();
61
+ this.capabilityDetector = new capability_detector_1.CapabilityDetector();
62
+ this.authDetector = new auth_detector_1.AuthDetector();
63
+ // Seed auth detection from package.json dependencies
64
+ this.authDetector.readPackageJson(projectPath);
65
+ // Seed app metadata from package.json (overridden by farcaster.json if present)
66
+ this.readPackageJsonMetadata();
67
+ }
68
+ /** Expose the shared Project so callers can pass it to other parsers, avoiding duplicate AST work. */
69
+ getProject() { return this.project; }
70
+ /** Populate _appMetadata from package.json as a baseline fallback */
71
+ readPackageJsonMetadata() {
72
+ const pkgPath = path.join(this.projectPath, 'package.json');
73
+ if (!fs.existsSync(pkgPath))
74
+ return;
75
+ try {
76
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
77
+ this._appMetadata = {
78
+ name: pkg.name,
79
+ description: pkg.description,
80
+ author: typeof pkg.author === 'string' ? pkg.author : pkg.author?.name,
81
+ url: pkg.homepage,
82
+ };
83
+ }
84
+ catch { /* ignore */ }
85
+ }
86
+ async parseFile(filePath) {
87
+ const relativePath = path.relative(this.projectPath, filePath).replace(/\\/g, '/');
88
+ // 1. farcaster.json — extract full app metadata and declared capabilities
89
+ if (relativePath.endsWith('farcaster.json')) {
90
+ try {
91
+ const content = JSON.parse(fs.readFileSync(filePath, 'utf8'));
92
+ const frame = content.frame ?? {};
93
+ this._appMetadata = {
94
+ name: frame.name,
95
+ description: frame.buttonTitle || (frame.name ? `Farcaster App: ${frame.name}` : undefined),
96
+ iconUrl: frame.iconUrl,
97
+ homeUrl: frame.homeUrl,
98
+ imageUrl: frame.imageUrl,
99
+ splashImageUrl: frame.splashImageUrl,
100
+ splashBackgroundColor: frame.splashBackgroundColor,
101
+ };
102
+ this.capabilityDetector.readManifest(frame);
103
+ }
104
+ catch { /* ignore */ }
105
+ return [];
106
+ }
107
+ // 2. ABI JSON — delegate entirely to contract parser
108
+ if (filePath.endsWith('.json')) {
109
+ return this.contractParser.parseAbiFile(filePath);
110
+ }
111
+ // 3. TypeScript / TSX
112
+ const sourceFile = this.project.addSourceFileAtPath(filePath);
113
+ const actions = [];
114
+ // 3a. @agent-action JSDoc annotations (highest priority, any file)
115
+ const exported = sourceFile.getExportedDeclarations();
116
+ for (const [name, declarations] of exported) {
117
+ for (const declaration of declarations) {
118
+ if (!('getJsDocs' in declaration))
119
+ continue;
120
+ const jsDocs = declaration.getJsDocs();
121
+ for (const jsDoc of jsDocs) {
122
+ if (jsDoc.getTags().some(tag => tag.getTagName() === 'agent-action')) {
123
+ actions.push(this.parseAnnotatedFunction(name, declaration, jsDoc, filePath, relativePath));
124
+ }
125
+ }
126
+ }
127
+ }
128
+ // 3b. App Router route handlers: app/api/**/route.(ts|tsx|js|jsx)
129
+ const isAppRouterRoute = /app\/api\/.+\/route\.(ts|tsx|js|jsx)$/.test(relativePath);
130
+ if (isAppRouterRoute) {
131
+ const zodSchemas = this.zodExtractor.extractSchemas(sourceFile);
132
+ const zodInputs = this.zodExtractor.findUsedSchema(sourceFile, zodSchemas) ?? {};
133
+ const routeName = this.routeNameFromPath(relativePath);
134
+ const location = this.routeLocationFromPath(relativePath);
135
+ // export async function POST(request: Request) { ... }
136
+ for (const func of sourceFile.getFunctions()) {
137
+ if (!func.isExported())
138
+ continue;
139
+ const methodName = func.getName();
140
+ if (!methodName || !HTTP_METHODS.has(methodName))
141
+ continue;
142
+ const actionName = `${routeName}_${methodName}`;
143
+ if (actions.some(a => a.name === actionName))
144
+ continue;
145
+ const safety = (0, intent_classifier_1.classifySafety)({ name: actionName, httpMethod: methodName, type: 'api' });
146
+ actions.push({
147
+ name: actionName,
148
+ description: `${methodName} ${location}`,
149
+ intent: (0, intent_classifier_1.inferIntent)(actionName),
150
+ type: 'api',
151
+ location,
152
+ method: methodName,
153
+ safety,
154
+ agentSafe: (0, intent_classifier_1.deriveAgentSafe)(safety),
155
+ requiredAuth: this.actionAuth(safety, methodName),
156
+ inputs: zodInputs,
157
+ outputs: { type: 'any' },
158
+ });
159
+ }
160
+ // export const POST = async (request: Request) => { ... }
161
+ for (const varDecl of sourceFile.getVariableDeclarations()) {
162
+ const methodName = varDecl.getName();
163
+ if (!HTTP_METHODS.has(methodName))
164
+ continue;
165
+ const stmt = varDecl.getVariableStatement();
166
+ if (!stmt?.isExported())
167
+ continue;
168
+ const actionName = `${routeName}_${methodName}`;
169
+ if (actions.some(a => a.name === actionName))
170
+ continue;
171
+ const safety = (0, intent_classifier_1.classifySafety)({ name: actionName, httpMethod: methodName, type: 'api' });
172
+ actions.push({
173
+ name: actionName,
174
+ description: `${methodName} ${location}`,
175
+ intent: (0, intent_classifier_1.inferIntent)(actionName),
176
+ type: 'api',
177
+ location,
178
+ method: methodName,
179
+ safety,
180
+ agentSafe: (0, intent_classifier_1.deriveAgentSafe)(safety),
181
+ requiredAuth: this.actionAuth(safety, methodName),
182
+ inputs: zodInputs,
183
+ outputs: { type: 'any' },
184
+ });
185
+ }
186
+ }
187
+ // 3c. Pages Router API routes: pages/api/**
188
+ const isPagesRoute = relativePath.startsWith('pages/api/');
189
+ if (isPagesRoute && actions.length === 0) {
190
+ const zodSchemas = this.zodExtractor.extractSchemas(sourceFile);
191
+ const zodShape = this.zodExtractor.findUsedSchema(sourceFile, zodSchemas);
192
+ const method = this.detectHttpMethod(sourceFile);
193
+ const actionName = path.basename(filePath, path.extname(filePath));
194
+ const location = '/' + relativePath.replace(/\.[^/.]+$/, '').replace(/^pages\/api\//, 'api/');
195
+ const safety = (0, intent_classifier_1.classifySafety)({ name: actionName, httpMethod: method, type: 'api' });
196
+ actions.push({
197
+ name: actionName,
198
+ description: `API endpoint at /${relativePath}`,
199
+ intent: (0, intent_classifier_1.inferIntent)(actionName),
200
+ type: 'api',
201
+ location,
202
+ method,
203
+ safety,
204
+ agentSafe: (0, intent_classifier_1.deriveAgentSafe)(safety),
205
+ requiredAuth: this.actionAuth(safety, method),
206
+ inputs: zodShape ?? {},
207
+ outputs: { type: 'any' },
208
+ });
209
+ }
210
+ // 3d. Generic non-Next.js API routes: api/**
211
+ const isGenericApiRoute = relativePath.startsWith('api/') && !isAppRouterRoute && !isPagesRoute;
212
+ if (isGenericApiRoute && actions.length === 0) {
213
+ const zodSchemas = this.zodExtractor.extractSchemas(sourceFile);
214
+ const zodShape = this.zodExtractor.findUsedSchema(sourceFile, zodSchemas);
215
+ const method = this.detectHttpMethod(sourceFile);
216
+ const actionName = path.basename(filePath, path.extname(filePath));
217
+ const location = '/' + relativePath.replace(/\.[^/.]+$/, '');
218
+ const safety = (0, intent_classifier_1.classifySafety)({ name: actionName, httpMethod: method, type: 'api' });
219
+ actions.push({
220
+ name: actionName,
221
+ description: `API endpoint at /${relativePath}`,
222
+ intent: (0, intent_classifier_1.inferIntent)(actionName),
223
+ type: 'api',
224
+ location,
225
+ method,
226
+ safety,
227
+ agentSafe: (0, intent_classifier_1.deriveAgentSafe)(safety),
228
+ requiredAuth: this.actionAuth(safety, method),
229
+ inputs: zodShape ?? {},
230
+ outputs: { type: 'any' },
231
+ });
232
+ }
233
+ // 3e. Server Actions: files with 'use server' directive
234
+ if (this.hasUseServerDirective(sourceFile)) {
235
+ // Named function declarations
236
+ for (const func of sourceFile.getFunctions()) {
237
+ if (!func.isExported())
238
+ continue;
239
+ const name = func.getName();
240
+ if (!name || actions.some(a => a.name === name))
241
+ continue;
242
+ const jsDocs = func.getJsDocs();
243
+ actions.push(this.parseAnnotatedFunction(name, func, jsDocs[0] ?? null, filePath, relativePath));
244
+ }
245
+ // Arrow functions / function expressions in exported variables
246
+ for (const varDecl of sourceFile.getVariableDeclarations()) {
247
+ const stmt = varDecl.getVariableStatement();
248
+ if (!stmt?.isExported())
249
+ continue;
250
+ const name = varDecl.getName();
251
+ if (actions.some(a => a.name === name))
252
+ continue;
253
+ const init = varDecl.getInitializer();
254
+ if (!init || (!ts_morph_1.Node.isArrowFunction(init) && !ts_morph_1.Node.isFunctionExpression(init)))
255
+ continue;
256
+ const safety = (0, intent_classifier_1.classifySafety)({ name, type: 'function' });
257
+ actions.push({
258
+ name,
259
+ description: `Server Action: ${name}`,
260
+ intent: (0, intent_classifier_1.inferIntent)(name),
261
+ type: 'function',
262
+ location: `./${relativePath}`,
263
+ safety,
264
+ agentSafe: (0, intent_classifier_1.deriveAgentSafe)(safety),
265
+ requiredAuth: this.actionAuth(safety, undefined, undefined, 'function'),
266
+ inputs: this.extractFunctionParams(init),
267
+ outputs: { type: 'any' },
268
+ });
269
+ }
270
+ }
271
+ // 3f. Wagmi contract hooks (detectHooks is additive — never overrides annotated actions)
272
+ const hookActions = await this.contractParser.detectHooks(sourceFile);
273
+ for (const hook of hookActions) {
274
+ if (!actions.some(a => a.name === hook.name)) {
275
+ actions.push(hook);
276
+ }
277
+ }
278
+ // 3g. Scan file content for capability + auth signals
279
+ try {
280
+ const rawContent = fs.readFileSync(filePath, 'utf8');
281
+ this.capabilityDetector.scanContent(rawContent);
282
+ this.authDetector.scanContent(rawContent);
283
+ }
284
+ catch { /* ignore */ }
285
+ return actions;
286
+ }
287
+ getAppMetadata() {
288
+ return this._appMetadata;
289
+ }
290
+ getCapabilities() {
291
+ return this.capabilityDetector.getCapabilities();
292
+ }
293
+ getAuth() {
294
+ return this.authDetector.getAuth();
295
+ }
296
+ // ─── Helpers ─────────────────────────────────────────────────────────────
297
+ hasUseServerDirective(sourceFile) {
298
+ const text = sourceFile.getFullText().trimStart();
299
+ return text.startsWith("'use server'") || text.startsWith('"use server"');
300
+ }
301
+ /** Convenience wrapper — infers per-action auth using the current app auth type. */
302
+ actionAuth(safety, httpMethod, isReadOnly, type = 'api') {
303
+ return (0, intent_classifier_1.inferActionAuth)({
304
+ safety,
305
+ httpMethod,
306
+ isReadOnly,
307
+ appAuthType: this.authDetector.getAuth().type,
308
+ type,
309
+ });
310
+ }
311
+ /** Detect the HTTP method a Pages-Router handler accepts from req.method checks. */
312
+ detectHttpMethod(sourceFile) {
313
+ const methods = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'];
314
+ // Prefer methods seen in inequality guards (req.method !== "POST" means POST is accepted)
315
+ const text = sourceFile.getFullText();
316
+ for (const m of methods) {
317
+ if (text.includes(`!== "${m}"`) || text.includes(`!== '${m}'`))
318
+ return m;
319
+ }
320
+ // Fall back to any method string literal present
321
+ for (const m of methods) {
322
+ if (text.includes(`"${m}"`) || text.includes(`'${m}'`))
323
+ return m;
324
+ }
325
+ return 'POST';
326
+ }
327
+ /** Derive a clean action name from an App Router route path. */
328
+ routeNameFromPath(relativePath) {
329
+ return relativePath
330
+ .replace(/^.*app\/api\//, '')
331
+ .replace(/\/route\.[^/]+$/, '')
332
+ .replace(/\[/g, '')
333
+ .replace(/\]/g, '')
334
+ .replace(/\//g, '_') || 'root';
335
+ }
336
+ /** Derive the URL path from an App Router route file path. */
337
+ routeLocationFromPath(relativePath) {
338
+ return '/' + relativePath
339
+ .replace(/^.*app\/api\//, 'api/')
340
+ .replace(/\/route\.[^/]+$/, '');
341
+ }
342
+ parseAnnotatedFunction(name, declaration, jsDoc, filePath, relativePath) {
343
+ const description = jsDoc?.getDescription().trim() ||
344
+ jsDoc?.getTags().find(t => t.getTagName() === 'description')?.getComment()?.toString().trim() ||
345
+ `Function ${name}`;
346
+ // Allow @agent-action intent=finance.transfer override
347
+ const intentOverride = jsDoc
348
+ ?.getTags()
349
+ .find(t => t.getTagName() === 'agent-action')
350
+ ?.getComment()
351
+ ?.toString()
352
+ .match(/intent=(\S+)/)?.[1];
353
+ // Allow @agent-action safety=financial override
354
+ const safetyOverride = jsDoc
355
+ ?.getTags()
356
+ .find(t => t.getTagName() === 'agent-action')
357
+ ?.getComment()
358
+ ?.toString()
359
+ .match(/safety=(read|write|financial|destructive)/)?.[1];
360
+ const inputs = {};
361
+ if ('getParameters' in declaration) {
362
+ for (const param of declaration.getParameters()) {
363
+ const paramName = param.getName();
364
+ const paramDoc = jsDoc
365
+ ?.getTags()
366
+ .find(t => t.getTagName() === 'param' && t.getName() === paramName);
367
+ inputs[paramName] = {
368
+ type: this.mapType(param.getType()),
369
+ description: paramDoc?.getComment()?.toString().trim() || '',
370
+ required: !param.isOptional(),
371
+ };
372
+ }
373
+ }
374
+ const returnType = 'getReturnType' in declaration ? declaration.getReturnType() : null;
375
+ const safety = safetyOverride ?? (0, intent_classifier_1.classifySafety)({ name, type: 'function' });
376
+ return {
377
+ name,
378
+ description,
379
+ intent: (0, intent_classifier_1.inferIntent)(name, intentOverride),
380
+ type: 'function',
381
+ location: `./${relativePath}`,
382
+ safety,
383
+ agentSafe: (0, intent_classifier_1.deriveAgentSafe)(safety),
384
+ requiredAuth: this.actionAuth(safety, undefined, undefined, 'function'),
385
+ inputs,
386
+ outputs: {
387
+ type: returnType ? this.mapType(returnType) : 'any',
388
+ description: jsDoc?.getTags().find(t => t.getTagName() === 'returns')?.getComment()?.toString().trim() || '',
389
+ },
390
+ };
391
+ }
392
+ /** Extract parameter info from an arrow function or function expression node. */
393
+ extractFunctionParams(func) {
394
+ const params = {};
395
+ if (!('getParameters' in func))
396
+ return params;
397
+ for (const param of func.getParameters()) {
398
+ params[param.getName()] = {
399
+ type: this.mapType(param.getType()),
400
+ required: !param.isOptional(),
401
+ };
402
+ }
403
+ return params;
404
+ }
405
+ mapType(type) {
406
+ if (type.isString())
407
+ return 'string';
408
+ if (type.isNumber())
409
+ return 'number';
410
+ if (type.isBoolean())
411
+ return 'boolean';
412
+ if (type.isArray())
413
+ return 'array';
414
+ if (type.isObject())
415
+ return 'object';
416
+ return type.getText();
417
+ }
418
+ }
419
+ exports.TSParser = TSParser;