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,163 @@
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.ExpressParser = void 0;
37
+ exports.looksLikeRouteFile = looksLikeRouteFile;
38
+ const ts_morph_1 = require("ts-morph");
39
+ const intent_classifier_1 = require("./intent-classifier");
40
+ const path = __importStar(require("path"));
41
+ /**
42
+ * Parses Express, Hono, and Fastify route definitions.
43
+ *
44
+ * Detects patterns like:
45
+ * // Express / Express Router
46
+ * app.get('/api/users', handler)
47
+ * router.post('/api/payments', handler)
48
+ *
49
+ * // Hono
50
+ * app.get('/api/users', (c) => { ... })
51
+ * const app = new Hono()
52
+ *
53
+ * // Fastify
54
+ * fastify.get('/api/users', handler)
55
+ * app.register(fastifyPlugin)
56
+ */
57
+ const HTTP_METHODS = ['get', 'post', 'put', 'delete', 'patch', 'head', 'options', 'all'];
58
+ const HTTP_METHODS_UPPER = {
59
+ get: 'GET', post: 'POST', put: 'PUT', delete: 'DELETE',
60
+ patch: 'PATCH', head: 'HEAD', options: 'OPTIONS', all: 'POST',
61
+ };
62
+ class ExpressParser {
63
+ project;
64
+ /** Pass the shared Project from TSParser to avoid duplicate AST construction. */
65
+ constructor(sharedProject) {
66
+ this.project = sharedProject ?? new ts_morph_1.Project({
67
+ compilerOptions: { allowJs: true, checkJs: false },
68
+ });
69
+ }
70
+ async parseFile(filePath, projectPath) {
71
+ const sourceFile = this.project.addSourceFileAtPath(filePath);
72
+ const relativePath = path.relative(projectPath, filePath).replace(/\\/g, '/');
73
+ const actions = [];
74
+ // Walk all call expressions looking for app.METHOD / router.METHOD / fastify.METHOD
75
+ sourceFile.forEachDescendant(node => {
76
+ if (!ts_morph_1.Node.isCallExpression(node))
77
+ return;
78
+ const expr = node.getExpression();
79
+ if (!ts_morph_1.Node.isPropertyAccessExpression(expr))
80
+ return;
81
+ const methodName = expr.getName().toLowerCase();
82
+ if (!HTTP_METHODS.includes(methodName))
83
+ return;
84
+ const args = node.getArguments();
85
+ if (args.length < 2)
86
+ return;
87
+ // First arg must be a string literal route path
88
+ const routeArg = args[0];
89
+ if (!ts_morph_1.Node.isStringLiteral(routeArg))
90
+ return;
91
+ const routePath = routeArg.getLiteralValue();
92
+ if (!routePath.startsWith('/'))
93
+ return;
94
+ const httpMethod = HTTP_METHODS_UPPER[methodName];
95
+ const actionName = this.routeToActionName(routePath, httpMethod);
96
+ if (actions.some(a => a.name === actionName))
97
+ return;
98
+ // Extract JSDoc from the handler if it's an inline function
99
+ let description = `${httpMethod} ${routePath}`;
100
+ const handler = args[args.length - 1];
101
+ if (ts_morph_1.Node.isArrowFunction(handler) || ts_morph_1.Node.isFunctionExpression(handler)) {
102
+ const jsDocs = handler.getJsDocs?.() ?? [];
103
+ if (jsDocs.length > 0) {
104
+ description = jsDocs[0].getDescription().trim() || description;
105
+ }
106
+ }
107
+ const safety = (0, intent_classifier_1.classifySafety)({ name: actionName, httpMethod, type: 'api' });
108
+ actions.push({
109
+ name: actionName,
110
+ description,
111
+ intent: (0, intent_classifier_1.inferIntent)(actionName),
112
+ type: 'api',
113
+ location: routePath,
114
+ method: httpMethod,
115
+ safety,
116
+ agentSafe: (0, intent_classifier_1.deriveAgentSafe)(safety),
117
+ requiredAuth: (0, intent_classifier_1.inferActionAuth)({ safety, httpMethod, type: 'api' }),
118
+ inputs: this.extractRouteParams(routePath),
119
+ outputs: { type: 'any' },
120
+ });
121
+ });
122
+ return actions;
123
+ }
124
+ /** Convert a route path like /api/users/:id to a snake_case action name */
125
+ routeToActionName(routePath, method) {
126
+ const slug = routePath
127
+ .replace(/^\//, '')
128
+ .replace(/\/:([^/]+)/g, '_$1') // :param → _param
129
+ .replace(/\//g, '_')
130
+ .replace(/[^a-zA-Z0-9_]/g, '');
131
+ return slug ? `${slug}_${method}` : `root_${method}`;
132
+ }
133
+ /** Extract path params like :id, :userId as required string inputs */
134
+ extractRouteParams(routePath) {
135
+ const params = {};
136
+ const matches = routePath.matchAll(/:([a-zA-Z][a-zA-Z0-9_]*)/g);
137
+ for (const match of matches) {
138
+ params[match[1]] = {
139
+ type: 'string',
140
+ description: `Path parameter: ${match[1]}`,
141
+ required: true,
142
+ };
143
+ }
144
+ return params;
145
+ }
146
+ }
147
+ exports.ExpressParser = ExpressParser;
148
+ /**
149
+ * Quick check: does this file look like it registers Express/Hono/Fastify routes?
150
+ * Used by DiscoveryService to filter files before expensive AST parsing.
151
+ */
152
+ function looksLikeRouteFile(content) {
153
+ return (
154
+ // Express / Hono app method calls
155
+ /\.(get|post|put|delete|patch)\s*\(\s*['"`]\//.test(content) ||
156
+ // Fastify route registration
157
+ /fastify\.(get|post|put|delete|patch)\s*\(/.test(content) ||
158
+ // Hono new Hono()
159
+ /new\s+Hono\s*\(/.test(content) ||
160
+ // Express Router
161
+ /Router\s*\(\s*\)/.test(content) ||
162
+ /express\.Router/.test(content));
163
+ }
@@ -0,0 +1,157 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.inferIntent = inferIntent;
4
+ exports.classifySafety = classifySafety;
5
+ exports.deriveAgentSafe = deriveAgentSafe;
6
+ exports.inferActionAuth = inferActionAuth;
7
+ const INTENT_RULES = [
8
+ // Trailing \b is intentionally omitted — camelCase names like rollDice, sendTokens,
9
+ // getUsers don't have a word boundary after the verb segment. Leading \b is kept
10
+ // to avoid matching mid-word (e.g. "undo" matching "do").
11
+ // Game
12
+ { pattern: /\b(play|flip|roll|spin|bet|guess|draw|move|attack|defend|claim(?:Prize|Reward|Win))/i, intent: 'game.play' },
13
+ { pattern: /\b(score|leaderboard|rank|highscore)/i, intent: 'game.score' },
14
+ { pattern: /\b(join(?:Game|Room|Lobby)|create(?:Game|Room)|start(?:Game|Round))/i, intent: 'game.join' },
15
+ // Finance / DeFi
16
+ { pattern: /\b(transfer|send(?:Token|ETH|USDC)?|pay(?:ment)?)/i, intent: 'finance.transfer' },
17
+ { pattern: /\b(swap|exchange|trade)/i, intent: 'finance.swap' },
18
+ { pattern: /\b(stake|unstake|deposit|withdraw|bond|unbond)/i, intent: 'finance.stake' },
19
+ { pattern: /\b(mint(?!NFT)|buy|purchase)/i, intent: 'finance.purchase' },
20
+ { pattern: /\b(approve|allowance|permit)/i, intent: 'finance.approve' },
21
+ { pattern: /\b(balance|getBalance|totalSupply)/i, intent: 'finance.balance' },
22
+ { pattern: /\b(borrow|repay|liquidate|collateral)/i, intent: 'finance.lending' },
23
+ // NFT
24
+ { pattern: /\b(mint(?:NFT)?|safeMint|createNFT)/i, intent: 'nft.mint' },
25
+ { pattern: /\b(listNFT|sellNFT|listFor(?:Sale)?)/i, intent: 'nft.list' },
26
+ { pattern: /\b(buyNFT|purchaseNFT)/i, intent: 'nft.buy' },
27
+ { pattern: /\b(burnNFT|burn)/i, intent: 'nft.burn' },
28
+ // Social (Farcaster-native)
29
+ { pattern: /\b(cast|compose(?:Cast)?|post(?:Cast)?)/i, intent: 'social.cast' },
30
+ { pattern: /\b(follow|unfollow|subscribe)/i, intent: 'social.follow' },
31
+ { pattern: /\b(like|react|upvote|downvote)/i, intent: 'social.react' },
32
+ { pattern: /\b(comment|reply)/i, intent: 'social.reply' },
33
+ { 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
+ // Auth
39
+ { pattern: /\b(login|logout|signIn|signOut|connect|disconnect)/i, intent: 'auth.session' },
40
+ { pattern: /\b(register|signup|createAccount)/i, intent: 'auth.register' },
41
+ { pattern: /\b(verify(?:Signature|Address|Identity)?)/i, intent: 'auth.verify' },
42
+ // Data / CRUD
43
+ { pattern: /\b(get|fetch|load|read|list|query|search|find)/i, intent: 'data.read' },
44
+ { pattern: /\b(create|add|insert|save|store|upload)/i, intent: 'data.create' },
45
+ { pattern: /\b(update|edit|patch|set|change)/i, intent: 'data.update' },
46
+ { pattern: /\b(delete|remove|destroy|archive)/i, intent: 'data.delete' },
47
+ // Media
48
+ { pattern: /\b(upload(?:Image|File|Media)?|setAvatar|setImage)/i, intent: 'media.upload' },
49
+ ];
50
+ /**
51
+ * Infer a semantic intent from the action name.
52
+ * Returns the first matching intent, or "util.action" as fallback.
53
+ */
54
+ function inferIntent(name, overrideIntent) {
55
+ if (overrideIntent)
56
+ return overrideIntent;
57
+ for (const { pattern, intent } of INTENT_RULES) {
58
+ if (pattern.test(name))
59
+ return intent;
60
+ }
61
+ return 'util.action';
62
+ }
63
+ // ─── Safety classification ────────────────────────────────────────────────
64
+ // No trailing \b — camelCase verbs like sendPayment, deleteAccount need prefix matching only
65
+ const FINANCIAL_VERBS = /\b(transfer|send|pay|swap|exchange|trade|stake|unstake|deposit|withdraw|buy|purchase|mint|approve|borrow|repay|liquidate|bond|unbond)/i;
66
+ const DESTRUCTIVE_VERBS = /\b(delete|remove|destroy|burn|archive|purge|wipe|clear)/i;
67
+ /**
68
+ * Matches names that handle PII, credentials, or sensitive identity data.
69
+ * An agent touching these fields should always require human confirmation and
70
+ * encrypted transport — even if the HTTP verb is a GET.
71
+ *
72
+ * Examples: resetPassword, storeCredentials, uploadPassport, submitKyc,
73
+ * updateSsn, exportPrivateKey, getMedicalRecord.
74
+ */
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|medicalRecord|healthRecord|passport|driverLicense|creditCard|cvv|encryptedData|identityVerif)/i;
77
+ /**
78
+ * Classify the safety level of an action.
79
+ *
80
+ * Rules (in priority order):
81
+ * 1. ABI write (non view/pure) + financial verb → financial
82
+ * 2. Financial verb anywhere → financial
83
+ * 3. Confidential noun (PII/credential) → confidential
84
+ * 4. ABI view/pure → read
85
+ * 5. GET HTTP method → read
86
+ * 6. Destructive verb → destructive
87
+ * 7. Everything else → write
88
+ */
89
+ function classifySafety(opts) {
90
+ const { name, httpMethod, isReadOnly, type } = opts;
91
+ if (type === 'contract') {
92
+ if (isReadOnly)
93
+ return 'read';
94
+ if (FINANCIAL_VERBS.test(name))
95
+ return 'financial';
96
+ if (CONFIDENTIAL_NOUNS.test(name))
97
+ return 'confidential';
98
+ return 'write';
99
+ }
100
+ if (FINANCIAL_VERBS.test(name))
101
+ return 'financial';
102
+ if (CONFIDENTIAL_NOUNS.test(name))
103
+ return 'confidential';
104
+ if (httpMethod === 'GET')
105
+ return 'read';
106
+ if (DESTRUCTIVE_VERBS.test(name))
107
+ return 'destructive';
108
+ return 'write';
109
+ }
110
+ /** Derive agentSafe from safety level. Non-read/write levels require human confirmation. */
111
+ function deriveAgentSafe(safety) {
112
+ return safety === 'read' || safety === 'write';
113
+ }
114
+ /**
115
+ * Infer per-action auth requirement.
116
+ *
117
+ * Rules:
118
+ * 1. Contract: view/pure → public; write → required (or farcaster-signed if app uses frames)
119
+ * 2. API GET + safety=read → public (heuristic: read endpoints are often open)
120
+ * 3. Farcaster frame app + write/financial/confidential → farcaster-signed
121
+ * 4. Financial → required + payments:write scope
122
+ * 5. Confidential → required + pii:read or pii:write scope
123
+ * 6. Destructive → required
124
+ * 7. Everything else → required (inherits app-level auth)
125
+ */
126
+ function inferActionAuth(opts) {
127
+ const { safety, httpMethod, isReadOnly, appAuthType, type } = opts;
128
+ // Contract view/pure: always public (read-only, on-chain data)
129
+ if (type === 'contract' && isReadOnly) {
130
+ return { required: 'public' };
131
+ }
132
+ // Farcaster frame apps: sensitive actions need frame signature
133
+ if (appAuthType === 'farcaster-frame' &&
134
+ (safety === 'write' || safety === 'financial' || safety === 'destructive' || safety === 'confidential')) {
135
+ return { required: 'farcaster-signed' };
136
+ }
137
+ // Financial → required + payments:write scope
138
+ if (safety === 'financial') {
139
+ return { required: 'required', scope: 'payments:write' };
140
+ }
141
+ // Confidential → required + pii scope (write for mutations, read for fetches)
142
+ if (safety === 'confidential') {
143
+ return {
144
+ required: 'required',
145
+ scope: httpMethod === 'GET' ? 'pii:read' : 'pii:write',
146
+ };
147
+ }
148
+ // Destructive → required, no special scope
149
+ if (safety === 'destructive') {
150
+ return { required: 'required' };
151
+ }
152
+ // Read-only GET on a public (no-auth) app → public
153
+ if (httpMethod === 'GET' && safety === 'read' && (appAuthType === 'none' || !appAuthType)) {
154
+ return { required: 'public' };
155
+ }
156
+ return { required: 'required' };
157
+ }