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,131 @@
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.AuthDetector = void 0;
37
+ const fs = __importStar(require("fs"));
38
+ const path = __importStar(require("path"));
39
+ const AUTH_SIGNALS = [
40
+ // Farcaster frame auth
41
+ { pattern: 'verifyFrameSignature', type: 'farcaster-frame', priority: 100 },
42
+ { pattern: '@farcaster/frame-node', type: 'farcaster-frame', priority: 100 },
43
+ { pattern: 'validateFrameMessage', type: 'farcaster-frame', priority: 100 },
44
+ { pattern: 'frameActionPayload', type: 'farcaster-frame', priority: 100 },
45
+ // OAuth2 / NextAuth / Auth.js
46
+ { pattern: 'NextAuth', type: 'oauth2', priority: 90 },
47
+ { pattern: 'next-auth', type: 'oauth2', priority: 90 },
48
+ { pattern: 'getServerSession', type: 'oauth2', priority: 85 },
49
+ { pattern: 'auth()', type: 'oauth2', priority: 85 },
50
+ { pattern: 'OAuthProvider', type: 'oauth2', priority: 80 },
51
+ { pattern: 'oauth2', type: 'oauth2', priority: 80 },
52
+ { pattern: 'access_token', type: 'oauth2', priority: 75 },
53
+ { pattern: 'refresh_token', type: 'oauth2', priority: 75 },
54
+ { pattern: 'clerkMiddleware', type: 'bearer', scheme: 'Bearer', priority: 88 },
55
+ { pattern: 'currentUser', type: 'bearer', scheme: 'Bearer', priority: 70 },
56
+ // JWT / Bearer
57
+ { pattern: 'jwt.verify', type: 'bearer', header: 'Authorization', scheme: 'Bearer', priority: 70 },
58
+ { pattern: 'jsonwebtoken', type: 'bearer', header: 'Authorization', scheme: 'Bearer', priority: 70 },
59
+ { pattern: 'jose', type: 'bearer', header: 'Authorization', scheme: 'Bearer', priority: 65 },
60
+ { pattern: "split('Bearer ')", type: 'bearer', header: 'Authorization', scheme: 'Bearer', priority: 70 },
61
+ { pattern: 'authorization.split', type: 'bearer', header: 'Authorization', scheme: 'Bearer', priority: 70 },
62
+ { pattern: "headers.get('authorization')", type: 'bearer', header: 'Authorization', scheme: 'Bearer', priority: 65 },
63
+ { pattern: "req.headers.authorization", type: 'bearer', header: 'Authorization', scheme: 'Bearer', priority: 65 },
64
+ // API key
65
+ { pattern: "'x-api-key'", type: 'api-key', header: 'X-API-Key', priority: 60 },
66
+ { pattern: '"x-api-key"', type: 'api-key', header: 'X-API-Key', priority: 60 },
67
+ { pattern: 'X-API-Key', type: 'api-key', header: 'X-API-Key', priority: 60 },
68
+ { pattern: 'api_key', type: 'api-key', queryParam: 'api_key', priority: 55 },
69
+ { pattern: 'apiKey', type: 'api-key', queryParam: 'apiKey', priority: 50 },
70
+ // Basic auth
71
+ { pattern: "'Basic '", type: 'basic', header: 'Authorization', scheme: 'Basic', priority: 40 },
72
+ { pattern: '"Basic "', type: 'basic', header: 'Authorization', scheme: 'Basic', priority: 40 },
73
+ { pattern: 'btoa(', type: 'basic', header: 'Authorization', scheme: 'Basic', priority: 35 },
74
+ // Session / cookie
75
+ { pattern: 'getSession(', type: 'cookie', priority: 30 },
76
+ { pattern: 'req.session', type: 'cookie', priority: 30 },
77
+ { pattern: 'iron-session', type: 'cookie', priority: 30 },
78
+ { pattern: 'cookies()', type: 'cookie', priority: 25 },
79
+ { pattern: 'req.cookies', type: 'cookie', priority: 25 },
80
+ ];
81
+ class AuthDetector {
82
+ best = null;
83
+ scanContent(content) {
84
+ for (const signal of AUTH_SIGNALS) {
85
+ if (content.includes(signal.pattern)) {
86
+ if (!this.best || signal.priority > this.best.priority) {
87
+ this.best = signal;
88
+ }
89
+ }
90
+ }
91
+ }
92
+ /** Read package.json to detect auth libraries in dependencies */
93
+ readPackageJson(projectPath) {
94
+ const pkgPath = path.join(projectPath, 'package.json');
95
+ if (!fs.existsSync(pkgPath))
96
+ return;
97
+ try {
98
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
99
+ const deps = { ...pkg.dependencies, ...pkg.devDependencies };
100
+ if (deps['next-auth'] || deps['@auth/core'])
101
+ this.applySignal({ pattern: 'next-auth', type: 'oauth2', priority: 90 });
102
+ if (deps['@clerk/nextjs'] || deps['@clerk/clerk-sdk-node'])
103
+ this.applySignal({ pattern: 'clerk', type: 'bearer', header: 'Authorization', scheme: 'Bearer', priority: 88 });
104
+ if (deps['jsonwebtoken'] || deps['jose'])
105
+ this.applySignal({ pattern: 'jwt', type: 'bearer', header: 'Authorization', scheme: 'Bearer', priority: 70 });
106
+ if (deps['iron-session'])
107
+ this.applySignal({ pattern: 'iron-session', type: 'cookie', priority: 30 });
108
+ if (deps['@farcaster/frame-node'])
109
+ this.applySignal({ pattern: 'farcaster-frame', type: 'farcaster-frame', priority: 100 });
110
+ }
111
+ catch { /* ignore */ }
112
+ }
113
+ applySignal(signal) {
114
+ if (!this.best || signal.priority > this.best.priority) {
115
+ this.best = signal;
116
+ }
117
+ }
118
+ getAuth() {
119
+ if (!this.best)
120
+ return { type: 'none' };
121
+ const config = { type: this.best.type };
122
+ if (this.best.header)
123
+ config.header = this.best.header;
124
+ if (this.best.scheme)
125
+ config.scheme = this.best.scheme;
126
+ if (this.best.queryParam)
127
+ config.queryParam = this.best.queryParam;
128
+ return config;
129
+ }
130
+ }
131
+ exports.AuthDetector = AuthDetector;
@@ -0,0 +1,115 @@
1
+ "use strict";
2
+ /**
3
+ * Detects app capabilities by scanning file content for known SDK/library patterns.
4
+ *
5
+ * Capabilities are generic (not Farcaster-specific) so agent.json works for any app.
6
+ * Farcaster-specific signals are still included but map to generic capability names
7
+ * where possible, plus a 'farcaster' capability for Farcaster-specific features.
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.CapabilityDetector = void 0;
11
+ const CAPABILITY_SIGNALS = [
12
+ // ── Payments ────────────────────────────────────────────────────────────
13
+ { pattern: 'stripe', capability: 'payments' },
14
+ { pattern: 'Stripe(', capability: 'payments' },
15
+ { pattern: 'loadStripe', capability: 'payments' },
16
+ { pattern: 'paymentIntent', capability: 'payments' },
17
+ { pattern: 'checkout.sessions', capability: 'payments' },
18
+ // ── Wallet / onchain ────────────────────────────────────────────────────
19
+ { pattern: 'sdk.wallet', capability: 'wallet' },
20
+ { pattern: 'ethProvider', capability: 'wallet' },
21
+ { pattern: 'useWalletClient', capability: 'wallet' },
22
+ { pattern: 'useConnect', capability: 'wallet' },
23
+ { pattern: 'useAccount', capability: 'wallet' },
24
+ { pattern: 'useWriteContract', capability: 'wallet' },
25
+ { pattern: 'writeContract', capability: 'wallet' },
26
+ { pattern: 'ethers.provider', capability: 'wallet' },
27
+ { pattern: 'web3.eth', capability: 'wallet' },
28
+ // ── Notifications ───────────────────────────────────────────────────────
29
+ { pattern: 'sendNotification', capability: 'notifications' },
30
+ { pattern: 'useNotifications', capability: 'notifications' },
31
+ { pattern: 'addFrameNotification', capability: 'notifications' },
32
+ { pattern: 'sdk.actions.addFrameNotification', capability: 'notifications' },
33
+ { pattern: 'webpush', capability: 'notifications' },
34
+ { pattern: 'PushSubscription', capability: 'notifications' },
35
+ { pattern: 'Notification.requestPermission', capability: 'notifications' },
36
+ { pattern: 'sendEmail', capability: 'notifications' },
37
+ { pattern: 'resend.emails', capability: 'notifications' },
38
+ { pattern: 'nodemailer', capability: 'notifications' },
39
+ { pattern: 'twilio', capability: 'notifications' },
40
+ // ── File / media uploads ────────────────────────────────────────────────
41
+ { pattern: 'put(', capability: 'storage' },
42
+ { pattern: 'upload(', capability: 'storage' },
43
+ { pattern: 'multer', capability: 'storage' },
44
+ { pattern: 'formData()', capability: 'storage' },
45
+ { pattern: 'vercel/blob', capability: 'storage' },
46
+ { pattern: 'sdk.actions.openCamera', capability: 'storage' },
47
+ { pattern: 's3.upload', capability: 'storage' },
48
+ { pattern: 'cloudinary', capability: 'storage' },
49
+ // ── Social (generic) ────────────────────────────────────────────────────
50
+ { pattern: 'sdk.actions.composeCast', capability: 'social' },
51
+ { pattern: 'openCompose', capability: 'social' },
52
+ { pattern: 'composeCast', capability: 'social' },
53
+ // ── Location / geo ──────────────────────────────────────────────────────
54
+ { pattern: 'sdk.context.location', capability: 'location' },
55
+ { pattern: 'sdk.location', capability: 'location' },
56
+ { pattern: 'geolocation', capability: 'location' },
57
+ { pattern: 'navigator.geolocation', capability: 'location' },
58
+ // ── AI / LLM ────────────────────────────────────────────────────────────
59
+ { pattern: 'openai', capability: 'ai' },
60
+ { pattern: 'anthropic', capability: 'ai' },
61
+ { pattern: 'streamText(', capability: 'ai' },
62
+ { pattern: 'generateText(', capability: 'ai' },
63
+ { pattern: 'useChat(', capability: 'ai' },
64
+ // ── Farcaster-specific (preserved for Farcaster mini-apps) ───────────────
65
+ { pattern: 'sdk.actions.addFrame', capability: 'farcaster' },
66
+ { pattern: 'sdk.actions.ready', capability: 'farcaster' },
67
+ { pattern: 'sdk.haptics', capability: 'farcaster' },
68
+ { pattern: 'sdk.actions.openUrl', capability: 'navigation' },
69
+ { pattern: 'requestContact', capability: 'contacts' },
70
+ // ── Database / data ─────────────────────────────────────────────────────
71
+ { pattern: 'prisma.', capability: 'database' },
72
+ { pattern: 'supabase.', capability: 'database' },
73
+ { pattern: 'drizzle(', capability: 'database' },
74
+ // ── Real-time ────────────────────────────────────────────────────────────
75
+ { pattern: 'socket.io', capability: 'realtime' },
76
+ { pattern: 'new WebSocket', capability: 'realtime' },
77
+ { pattern: 'pusher', capability: 'realtime' },
78
+ { pattern: 'ably', capability: 'realtime' },
79
+ ];
80
+ class CapabilityDetector {
81
+ detected = new Set();
82
+ /**
83
+ * Scan a file's raw content for capability signal strings.
84
+ * This is intentionally fast (string search, no AST) since it runs over every file.
85
+ */
86
+ scanContent(content) {
87
+ for (const { pattern, capability } of CAPABILITY_SIGNALS) {
88
+ if (content.includes(pattern)) {
89
+ this.detected.add(capability);
90
+ }
91
+ }
92
+ }
93
+ /**
94
+ * Read capabilities declared in farcaster.json.
95
+ * Accepts the parsed frame object.
96
+ */
97
+ readManifest(frame) {
98
+ // Explicit capability declarations
99
+ const declared = frame.capabilities ?? frame.requiredCapabilities;
100
+ if (Array.isArray(declared)) {
101
+ for (const cap of declared) {
102
+ if (typeof cap === 'string')
103
+ this.detected.add(cap);
104
+ }
105
+ }
106
+ // Infer wallet capability if a default chain or web3 config is present
107
+ if (frame.chainId || frame.defaultChain) {
108
+ this.detected.add('wallet');
109
+ }
110
+ }
111
+ getCapabilities() {
112
+ return Array.from(this.detected).sort();
113
+ }
114
+ }
115
+ exports.CapabilityDetector = CapabilityDetector;
@@ -0,0 +1,347 @@
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.ContractParser = void 0;
37
+ const ts_morph_1 = require("ts-morph");
38
+ const fs = __importStar(require("fs"));
39
+ const path = __importStar(require("path"));
40
+ const intent_classifier_1 = require("./intent-classifier");
41
+ /** viem/wagmi chain variable names → EIP-155 chain IDs */
42
+ const KNOWN_CHAINS = {
43
+ mainnet: 1,
44
+ goerli: 5,
45
+ sepolia: 11155111,
46
+ optimism: 10,
47
+ optimismGoerli: 420,
48
+ optimismSepolia: 11155420,
49
+ base: 8453,
50
+ baseSepolia: 84532,
51
+ arbitrum: 42161,
52
+ arbitrumSepolia: 421614,
53
+ polygon: 137,
54
+ polygonMumbai: 80001,
55
+ polygonAmoy: 80002,
56
+ zora: 7777777,
57
+ zoraSepolia: 999999999,
58
+ avalanche: 43114,
59
+ bsc: 56,
60
+ gnosis: 100,
61
+ celo: 42220,
62
+ linea: 59144,
63
+ scroll: 534352,
64
+ mode: 34443,
65
+ blast: 81457,
66
+ mantle: 5000,
67
+ fraxtal: 252,
68
+ };
69
+ class ContractParser {
70
+ projectPath;
71
+ constructor(projectPath) {
72
+ this.projectPath = projectPath;
73
+ }
74
+ async parseAbiFile(filePath) {
75
+ try {
76
+ const content = JSON.parse(fs.readFileSync(filePath, 'utf8'));
77
+ if (!Array.isArray(content))
78
+ return [];
79
+ const actions = [];
80
+ for (const item of content) {
81
+ if (item.type !== 'function')
82
+ continue;
83
+ const isReadOnly = item.stateMutability === 'view' || item.stateMutability === 'pure';
84
+ const safety = (0, intent_classifier_1.classifySafety)({ name: item.name, isReadOnly, type: 'contract' });
85
+ actions.push({
86
+ name: item.name,
87
+ description: isReadOnly
88
+ ? `Read contract: ${item.name}`
89
+ : `Write contract: ${item.name}`,
90
+ intent: (0, intent_classifier_1.inferIntent)(item.name),
91
+ type: 'contract',
92
+ location: `./${path.relative(this.projectPath, filePath)}`,
93
+ abiFunction: item.name,
94
+ isReadOnly,
95
+ safety,
96
+ agentSafe: (0, intent_classifier_1.deriveAgentSafe)(safety),
97
+ requiredAuth: (0, intent_classifier_1.inferActionAuth)({ safety, isReadOnly, type: 'contract' }),
98
+ inputs: this.mapAbiInputs(item.inputs ?? []),
99
+ outputs: {
100
+ type: this.mapAbiOutputs(item.outputs ?? []),
101
+ description: '',
102
+ },
103
+ });
104
+ }
105
+ return actions;
106
+ }
107
+ catch {
108
+ return [];
109
+ }
110
+ }
111
+ /**
112
+ * Detect wagmi contract interactions (useWriteContract, writeContract, etc.)
113
+ * and cross-reference imported ABI files to extract real parameter types.
114
+ */
115
+ async detectHooks(sourceFile) {
116
+ const actions = [];
117
+ const abiMap = this.buildAbiImportMap(sourceFile);
118
+ sourceFile.forEachDescendant(node => {
119
+ if (!ts_morph_1.Node.isObjectLiteralExpression(node))
120
+ return;
121
+ const props = node.getProperties();
122
+ // Collect relevant property assignments by key name
123
+ let abiVarName = null;
124
+ let functionName = null;
125
+ let chainId;
126
+ let contractAddress;
127
+ for (const prop of props) {
128
+ if (!ts_morph_1.Node.isPropertyAssignment(prop))
129
+ continue;
130
+ const key = prop.getName();
131
+ const init = prop.getInitializer();
132
+ if (!init)
133
+ continue;
134
+ if (key === 'abi') {
135
+ abiVarName = init.getText().trim();
136
+ }
137
+ if (key === 'functionName' && ts_morph_1.Node.isStringLiteral(init)) {
138
+ functionName = init.getLiteralValue();
139
+ }
140
+ // Explicit numeric chainId: { chainId: 8453 }
141
+ if (key === 'chainId' && ts_morph_1.Node.isNumericLiteral(init)) {
142
+ chainId = Number(init.getLiteralValue());
143
+ }
144
+ // Chain object reference: { chain: base } → resolve known chain names
145
+ if (key === 'chain') {
146
+ chainId = KNOWN_CHAINS[init.getText().trim()] ?? chainId;
147
+ }
148
+ // Contract address: literal or env var reference
149
+ if (key === 'address') {
150
+ contractAddress = resolveAddressNode(init);
151
+ }
152
+ }
153
+ if (!abiVarName || !functionName)
154
+ return;
155
+ // Cross-reference against imported ABI for real parameter types
156
+ let parameters = {};
157
+ const abi = abiMap.get(abiVarName);
158
+ if (abi) {
159
+ const abiFunc = abi.find((item) => item.type === 'function' && item.name === functionName);
160
+ if (abiFunc) {
161
+ parameters = this.mapAbiInputs(abiFunc.inputs ?? []);
162
+ }
163
+ }
164
+ const safety = (0, intent_classifier_1.classifySafety)({ name: functionName, isReadOnly: false, type: 'contract' });
165
+ actions.push({
166
+ name: functionName,
167
+ description: `Contract interaction: ${functionName}`,
168
+ intent: (0, intent_classifier_1.inferIntent)(functionName),
169
+ type: 'contract',
170
+ location: sourceFile.getFilePath(),
171
+ abiFunction: functionName,
172
+ ...(chainId !== undefined ? { chainId } : {}),
173
+ ...(contractAddress !== undefined ? { contractAddress } : {}),
174
+ safety,
175
+ agentSafe: (0, intent_classifier_1.deriveAgentSafe)(safety),
176
+ requiredAuth: (0, intent_classifier_1.inferActionAuth)({ safety, isReadOnly: false, type: 'contract' }),
177
+ inputs: parameters,
178
+ outputs: { type: 'any' },
179
+ });
180
+ });
181
+ return actions;
182
+ }
183
+ // ─── Private helpers ─────────────────────────────────────────────────────
184
+ /**
185
+ * Resolve relative imports that point to JSON ABI files.
186
+ * Returns a map of imported identifier -> ABI array.
187
+ * Also resolves TypeScript path aliases (e.g. @/abis/GameABI).
188
+ */
189
+ buildAbiImportMap(sourceFile) {
190
+ const map = new Map();
191
+ const sourceDir = path.dirname(sourceFile.getFilePath());
192
+ const aliasMap = this.loadTsAliases();
193
+ for (const importDecl of sourceFile.getImportDeclarations()) {
194
+ const spec = importDecl.getModuleSpecifierValue();
195
+ // Resolve the specifier to a filesystem path
196
+ let resolvedSpec = null;
197
+ if (spec.startsWith('.')) {
198
+ resolvedSpec = path.resolve(sourceDir, spec);
199
+ }
200
+ else {
201
+ // Try TS path aliases
202
+ const aliasResolved = this.resolveAlias(spec, aliasMap);
203
+ if (aliasResolved)
204
+ resolvedSpec = aliasResolved;
205
+ }
206
+ if (!resolvedSpec)
207
+ continue;
208
+ const candidates = [
209
+ resolvedSpec,
210
+ `${resolvedSpec}.json`,
211
+ ];
212
+ for (const candidate of candidates) {
213
+ if (!fs.existsSync(candidate))
214
+ continue;
215
+ try {
216
+ const parsed = JSON.parse(fs.readFileSync(candidate, 'utf8'));
217
+ if (!Array.isArray(parsed))
218
+ break;
219
+ for (const namedImport of importDecl.getNamedImports()) {
220
+ map.set(namedImport.getName(), parsed);
221
+ }
222
+ const defaultImport = importDecl.getDefaultImport();
223
+ if (defaultImport) {
224
+ map.set(defaultImport.getText(), parsed);
225
+ }
226
+ break;
227
+ }
228
+ catch { /* ignore */ }
229
+ }
230
+ }
231
+ return map;
232
+ }
233
+ /**
234
+ * Read tsconfig.json compilerOptions.paths and baseUrl.
235
+ * Returns a map of alias prefix → array of filesystem root paths.
236
+ * e.g. { "@/*": ["/project/src/*"] }
237
+ */
238
+ loadTsAliases() {
239
+ const aliases = new Map();
240
+ const tsconfigPath = path.join(this.projectPath, 'tsconfig.json');
241
+ if (!fs.existsSync(tsconfigPath))
242
+ return aliases;
243
+ try {
244
+ // Strip JSON comments before parsing (tsconfig allows them)
245
+ const raw = fs.readFileSync(tsconfigPath, 'utf8')
246
+ .replace(/\/\/[^\n]*/g, '')
247
+ .replace(/\/\*[\s\S]*?\*\//g, '');
248
+ const tsconfig = JSON.parse(raw);
249
+ const opts = tsconfig.compilerOptions ?? {};
250
+ const baseUrl = opts.baseUrl
251
+ ? path.resolve(this.projectPath, opts.baseUrl)
252
+ : this.projectPath;
253
+ for (const [alias, targets] of Object.entries(opts.paths ?? {})) {
254
+ const resolved = targets.map(t => path.resolve(baseUrl, t));
255
+ aliases.set(alias, resolved);
256
+ }
257
+ }
258
+ catch { /* ignore malformed tsconfig */ }
259
+ return aliases;
260
+ }
261
+ /**
262
+ * Resolve a module specifier against tsconfig path aliases.
263
+ * e.g. "@/abis/GameABI" → "/project/src/abis/GameABI"
264
+ */
265
+ resolveAlias(spec, aliases) {
266
+ for (const [pattern, targets] of aliases) {
267
+ if (pattern.endsWith('/*')) {
268
+ const prefix = pattern.slice(0, -2); // "@/"
269
+ if (!spec.startsWith(prefix))
270
+ continue;
271
+ const rest = spec.slice(prefix.length); // "abis/GameABI"
272
+ for (const target of targets) {
273
+ const resolvedTarget = target.endsWith('/*')
274
+ ? path.join(target.slice(0, -2), rest)
275
+ : path.join(target, rest);
276
+ if (fs.existsSync(resolvedTarget) || fs.existsSync(`${resolvedTarget}.json`)) {
277
+ return resolvedTarget;
278
+ }
279
+ }
280
+ }
281
+ else if (spec === pattern) {
282
+ // Exact match
283
+ return targets[0] ?? null;
284
+ }
285
+ }
286
+ return null;
287
+ }
288
+ mapAbiInputs(inputs) {
289
+ const props = {};
290
+ for (const input of inputs) {
291
+ props[input.name || 'arg'] = {
292
+ type: this.mapSolidityType(input.type),
293
+ description: `Solidity type: ${input.type}`,
294
+ required: true,
295
+ };
296
+ }
297
+ return props;
298
+ }
299
+ mapAbiOutputs(outputs) {
300
+ if (!outputs.length)
301
+ return 'void';
302
+ if (outputs.length === 1)
303
+ return this.mapSolidityType(outputs[0].type);
304
+ return 'object';
305
+ }
306
+ mapSolidityType(type) {
307
+ if (type.startsWith('uint') || type.startsWith('int'))
308
+ return 'number';
309
+ if (type === 'bool')
310
+ return 'boolean';
311
+ if (type === 'address')
312
+ return 'string';
313
+ if (type.startsWith('bytes'))
314
+ return 'string';
315
+ if (type.endsWith('[]'))
316
+ return 'array';
317
+ return 'string';
318
+ }
319
+ }
320
+ exports.ContractParser = ContractParser;
321
+ // ─── Module-level helpers ─────────────────────────────────────────────────────
322
+ /**
323
+ * Resolve a wagmi `address` node to either:
324
+ * - a literal `0x...` string (safe — on-chain public data)
325
+ * - `{ $env: "VAR_NAME" }` when referencing process.env.* (never leak actual value)
326
+ * - undefined if not resolvable
327
+ */
328
+ function resolveAddressNode(node) {
329
+ // Literal string: address: '0xABC...'
330
+ if (ts_morph_1.Node.isStringLiteral(node)) {
331
+ const val = node.getLiteralValue();
332
+ if (/^0x[0-9a-fA-F]{40}$/i.test(val))
333
+ return val;
334
+ return undefined;
335
+ }
336
+ // Type assertion: address: '0xABC...' as `0x${string}`
337
+ if (ts_morph_1.Node.isAsExpression(node)) {
338
+ return resolveAddressNode(node.getExpression());
339
+ }
340
+ // process.env.NEXT_PUBLIC_CONTRACT_ADDRESS → { $env: "NEXT_PUBLIC_CONTRACT_ADDRESS" }
341
+ // Also handles: process.env.NEXT_PUBLIC_CONTRACT_ADDRESS as `0x${string}`
342
+ const text = node.getText().trim();
343
+ const envMatch = text.match(/process\.env\.([A-Z0-9_]+)/);
344
+ if (envMatch)
345
+ return { $env: envMatch[1] };
346
+ return undefined;
347
+ }