@noelclaw/mcp 1.5.7 → 2.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,269 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.BASE_TOOLS = void 0;
4
+ exports.handleBaseTool = handleBaseTool;
5
+ const MORPHO_API = "https://blue-api.morpho.org/graphql";
6
+ const MOONWELL_API = "https://api.moonwell.fi/v1/markets";
7
+ exports.BASE_TOOLS = [
8
+ {
9
+ name: "base_query_vaults",
10
+ description: "List Morpho yield vaults on Base sorted by APY. Shows vault name, asset, current APY, and total deposits. Use this to find the best yield opportunities on Base.",
11
+ inputSchema: {
12
+ type: "object",
13
+ properties: {
14
+ asset: {
15
+ type: "string",
16
+ description: "Filter by asset symbol (e.g. USDC, WETH, cbBTC). Leave empty for all.",
17
+ },
18
+ limit: {
19
+ type: "number",
20
+ description: "Max vaults to return (default 10)",
21
+ },
22
+ },
23
+ required: [],
24
+ },
25
+ },
26
+ {
27
+ name: "base_list_markets",
28
+ description: "List Moonwell lending/borrowing markets on Base. Shows supply APY, borrow APY, total liquidity, and utilization rate for each asset.",
29
+ inputSchema: {
30
+ type: "object",
31
+ properties: {
32
+ asset: {
33
+ type: "string",
34
+ description: "Filter by asset symbol (e.g. USDC, ETH, cbBTC). Leave empty for all.",
35
+ },
36
+ },
37
+ required: [],
38
+ },
39
+ },
40
+ {
41
+ name: "base_prepare_deposit",
42
+ description: "Get deposit instructions for a Morpho vault — shows the vault address, expected APY, and step-by-step instructions. Does NOT execute the transaction.",
43
+ inputSchema: {
44
+ type: "object",
45
+ properties: {
46
+ vaultName: {
47
+ type: "string",
48
+ description: "Name or partial name of the vault (e.g. 'Gauntlet USDC', 'steakUSDC')",
49
+ },
50
+ amount: {
51
+ type: "string",
52
+ description: "Amount to deposit (e.g. '100', '1000')",
53
+ },
54
+ asset: {
55
+ type: "string",
56
+ description: "Asset to deposit (e.g. USDC, WETH)",
57
+ },
58
+ },
59
+ required: ["asset", "amount"],
60
+ },
61
+ },
62
+ {
63
+ name: "base_chain_stats",
64
+ description: "Get real-time Base chain stats: ETH price, gas price in gwei, and latest block info.",
65
+ inputSchema: {
66
+ type: "object",
67
+ properties: {},
68
+ required: [],
69
+ },
70
+ },
71
+ ];
72
+ // ── Helpers ────────────────────────────────────────────────────────────────────
73
+ function fmt(n, decimals = 2) {
74
+ if (n >= 1000000000)
75
+ return `$${(n / 1000000000).toFixed(1)}B`;
76
+ if (n >= 1000000)
77
+ return `$${(n / 1000000).toFixed(1)}M`;
78
+ if (n >= 1000)
79
+ return `$${(n / 1000).toFixed(1)}K`;
80
+ return `$${n.toFixed(decimals)}`;
81
+ }
82
+ function pct(n) {
83
+ return `${(n * 100).toFixed(2)}%`;
84
+ }
85
+ // ── Morpho vaults ─────────────────────────────────────────────────────────────
86
+ async function fetchMorphoVaults(asset, limit = 10) {
87
+ const gql = `{
88
+ vaults(
89
+ where: { chainId_in: [8453] }
90
+ orderBy: NetApy
91
+ orderDirection: Desc
92
+ first: 50
93
+ ) {
94
+ items {
95
+ name
96
+ address
97
+ asset { symbol name }
98
+ state { apy netApy totalAssetsUsd }
99
+ }
100
+ }
101
+ }`;
102
+ const res = await fetch(MORPHO_API, {
103
+ method: "POST",
104
+ headers: { "Content-Type": "application/json" },
105
+ body: JSON.stringify({ query: gql }),
106
+ signal: AbortSignal.timeout(15000),
107
+ });
108
+ if (!res.ok)
109
+ throw new Error(`Morpho API error: ${res.status}`);
110
+ const data = await res.json();
111
+ let vaults = data?.data?.vaults?.items ?? [];
112
+ // Filter out test/spam vaults: min $10k TVL, max 500% APY
113
+ vaults = vaults.filter((v) => {
114
+ const tvl = v.state?.totalAssetsUsd ?? 0;
115
+ const apy = v.state?.netApy ?? v.state?.apy ?? 0;
116
+ return tvl >= 10000 && apy <= 5;
117
+ });
118
+ if (asset) {
119
+ vaults = vaults.filter((v) => v.asset?.symbol?.toLowerCase().includes(asset.toLowerCase()));
120
+ }
121
+ vaults = vaults.slice(0, limit);
122
+ if (!vaults.length)
123
+ return "No vaults found for that asset.";
124
+ const lines = vaults.map((v, i) => {
125
+ const apy = pct(v.state?.netApy ?? v.state?.apy ?? 0);
126
+ const tvl = fmt(v.state?.totalAssetsUsd ?? 0);
127
+ const addr = `${v.address?.slice(0, 6)}...${v.address?.slice(-4)}`;
128
+ return `${i + 1}. ${v.name}\n Asset: ${v.asset?.symbol} APY: ${apy} TVL: ${tvl}\n Address: ${addr}`;
129
+ });
130
+ return `Morpho Vaults on Base (sorted by APY):\n\n${lines.join("\n\n")}`;
131
+ }
132
+ // ── Moonwell markets ───────────────────────────────────────────────────────────
133
+ async function fetchMoonwellMarkets(asset) {
134
+ const res = await fetch(`${MOONWELL_API}?network=base`, {
135
+ signal: AbortSignal.timeout(15000),
136
+ });
137
+ if (!res.ok)
138
+ throw new Error(`Moonwell API error: ${res.status}`);
139
+ const data = await res.json();
140
+ let markets = Array.isArray(data) ? data : (data?.data ?? data?.markets ?? []);
141
+ // Filter deprecated markets
142
+ markets = markets.filter((m) => !m.deprecated);
143
+ if (asset) {
144
+ markets = markets.filter((m) => (m.asset ?? m.underlyingSymbol ?? m.symbol ?? "").toLowerCase().includes(asset.toLowerCase()));
145
+ }
146
+ if (!markets.length)
147
+ return "No markets found.";
148
+ const lines = markets.slice(0, 15).map((m, i) => {
149
+ const symbol = m.asset ?? m.underlyingSymbol ?? m.symbol ?? "?";
150
+ const supplyApy = pct((m.baseSupplyApy ?? m.supplyApy ?? m.supplyRate ?? 0) / 100);
151
+ const borrowApy = pct((m.baseBorrowApy ?? m.borrowApy ?? m.borrowRate ?? 0) / 100);
152
+ const liquidity = fmt(m.totalSupplyUsd ?? m.liquidityUsd ?? m.totalSupply ?? 0);
153
+ const util = m.utilization != null ? `${(m.utilization * 100).toFixed(1)}%` : "—";
154
+ return `${i + 1}. ${symbol}\n Supply APY: ${supplyApy} Borrow APY: ${borrowApy} Liquidity: ${liquidity} Util: ${util}`;
155
+ });
156
+ return `Moonwell Markets on Base:\n\n${lines.join("\n\n")}`;
157
+ }
158
+ // ── Base chain stats ───────────────────────────────────────────────────────────
159
+ async function fetchBaseStats() {
160
+ const rpc = "https://mainnet.base.org";
161
+ const [blockRes, priceRes, gasRes] = await Promise.all([
162
+ fetch(rpc, {
163
+ method: "POST",
164
+ headers: { "Content-Type": "application/json" },
165
+ body: JSON.stringify({ jsonrpc: "2.0", id: 1, method: "eth_blockNumber", params: [] }),
166
+ signal: AbortSignal.timeout(8000),
167
+ }).then(r => r.json()).catch(() => null),
168
+ fetch("https://api.coingecko.com/api/v3/simple/price?ids=ethereum&vs_currencies=usd", {
169
+ signal: AbortSignal.timeout(8000),
170
+ }).then(r => r.json()).catch(() => null),
171
+ fetch(rpc, {
172
+ method: "POST",
173
+ headers: { "Content-Type": "application/json" },
174
+ body: JSON.stringify({ jsonrpc: "2.0", id: 2, method: "eth_gasPrice", params: [] }),
175
+ signal: AbortSignal.timeout(8000),
176
+ }).then(r => r.json()).catch(() => null),
177
+ ]);
178
+ const block = blockRes?.result ? parseInt(blockRes.result, 16) : "—";
179
+ const ethPrice = priceRes?.ethereum?.usd ?? "—";
180
+ const gasPriceGwei = gasRes?.result
181
+ ? (parseInt(gasRes.result, 16) / 1e9).toFixed(4)
182
+ : "—";
183
+ return `Base Chain Stats:\n\n• ETH Price: $${ethPrice}\n• Gas Price: ${gasPriceGwei} gwei\n• Latest Block: ${block.toLocaleString()}\n• Network: Base Mainnet (Chain ID 8453)`;
184
+ }
185
+ // ── Prepare deposit info ───────────────────────────────────────────────────────
186
+ async function prepareDeposit(vaultName, asset, amount) {
187
+ const gql = `{
188
+ vaults(
189
+ where: { chainId_in: [8453] }
190
+ orderBy: NetApy
191
+ orderDirection: Desc
192
+ first: 100
193
+ ) {
194
+ items {
195
+ name
196
+ address
197
+ asset { symbol name }
198
+ state { netApy apy totalAssetsUsd }
199
+ }
200
+ }
201
+ }`;
202
+ const res = await fetch(MORPHO_API, {
203
+ method: "POST",
204
+ headers: { "Content-Type": "application/json" },
205
+ body: JSON.stringify({ query: gql }),
206
+ signal: AbortSignal.timeout(15000),
207
+ });
208
+ if (!res.ok)
209
+ throw new Error(`Morpho API error: ${res.status}`);
210
+ const data = await res.json();
211
+ let vaults = data?.data?.vaults?.items ?? [];
212
+ // Filter by asset first
213
+ vaults = vaults.filter((v) => v.asset?.symbol?.toLowerCase() === asset.toLowerCase());
214
+ // Filter by vault name if provided
215
+ if (vaultName) {
216
+ const match = vaults.find((v) => v.name?.toLowerCase().includes(vaultName.toLowerCase()));
217
+ if (match)
218
+ vaults = [match];
219
+ }
220
+ // Take best APY vault
221
+ const vault = vaults[0];
222
+ if (!vault) {
223
+ return `No Morpho vault found for ${asset} on Base. Try base_query_vaults to see available vaults.`;
224
+ }
225
+ const apy = pct(vault.state?.netApy ?? vault.state?.apy ?? 0);
226
+ const tvl = fmt(vault.state?.totalAssetsUsd ?? 0);
227
+ return [
228
+ `Morpho Vault Deposit Instructions`,
229
+ ``,
230
+ `Vault: ${vault.name}`,
231
+ `Asset: ${vault.asset?.symbol}`,
232
+ `APY: ${apy} | TVL: ${tvl}`,
233
+ `Contract: ${vault.address}`,
234
+ ``,
235
+ `Steps to deposit ${amount} ${asset}:`,
236
+ `1. Go to app.morpho.org or use the vault address above`,
237
+ `2. Connect your wallet (ensure you have ${amount} ${asset})`,
238
+ `3. Approve the vault contract to spend your ${asset}`,
239
+ `4. Call deposit(${amount}, yourAddress) on the vault contract`,
240
+ `5. You'll receive vault shares representing your deposit`,
241
+ ``,
242
+ `Expected yield: ~${apy} on ${amount} ${asset}`,
243
+ `Note: APY is variable and changes based on market conditions.`,
244
+ ].join("\n");
245
+ }
246
+ // ── Handler ────────────────────────────────────────────────────────────────────
247
+ async function handleBaseTool(name, args) {
248
+ const a = (args ?? {});
249
+ switch (name) {
250
+ case "base_query_vaults": {
251
+ const text = await fetchMorphoVaults(a.asset, a.limit ?? 10);
252
+ return { content: [{ type: "text", text }] };
253
+ }
254
+ case "base_list_markets": {
255
+ const text = await fetchMoonwellMarkets(a.asset);
256
+ return { content: [{ type: "text", text }] };
257
+ }
258
+ case "base_prepare_deposit": {
259
+ const text = await prepareDeposit(a.vaultName, a.asset, a.amount);
260
+ return { content: [{ type: "text", text }] };
261
+ }
262
+ case "base_chain_stats": {
263
+ const text = await fetchBaseStats();
264
+ return { content: [{ type: "text", text }] };
265
+ }
266
+ default:
267
+ return null;
268
+ }
269
+ }
@@ -0,0 +1,347 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CODER_TOOLS = void 0;
4
+ exports.handleCoderTool = handleCoderTool;
5
+ const zod_1 = require("zod");
6
+ const llm_js_1 = require("../llm.js");
7
+ const CODER_SYSTEM = `You are Noel Coder — an expert software engineer embedded in the Noelclaw AI OS on Base chain.
8
+ You specialize in:
9
+ - Solidity smart contracts (ERC-20, ERC-721, DeFi hooks, Uniswap v3/v4 integrations)
10
+ - TypeScript / React / Next.js frontend development
11
+ - Convex backend (functions, schema, mutations, queries)
12
+ - MCP (Model Context Protocol) server tools in TypeScript
13
+ - Base chain ecosystem: 0x Protocol, Uniswap v3, ethers v6, wagmi v2
14
+
15
+ Rules:
16
+ - Output clean, production-ready code. No placeholders.
17
+ - Add brief inline comments only where logic is non-obvious.
18
+ - For smart contracts: always include NatSpec, SPDX license, pragma.
19
+ - For React: use TypeScript, functional components, Tailwind CSS.
20
+ - For MCP tools: follow the Noelclaw pattern (Zod schema, handler returns ToolResult | null).
21
+ - Never include secrets or private keys in output.`;
22
+ function ok(text) {
23
+ return { content: [{ type: "text", text }] };
24
+ }
25
+ function err(text) {
26
+ return { content: [{ type: "text", text }], isError: true };
27
+ }
28
+ // ── Tool definitions ──────────────────────────────────────────────────────────
29
+ exports.CODER_TOOLS = [
30
+ {
31
+ name: "scaffold_project",
32
+ description: "Generate a complete project scaffold for a described idea — file tree + key file contents. " +
33
+ "Supports: Solidity contract projects, MCP skill packages, React/Next.js dApps, Convex backends, CLI tools.",
34
+ inputSchema: {
35
+ type: "object",
36
+ properties: {
37
+ description: {
38
+ type: "string",
39
+ description: "What to build — be specific: tech stack, purpose, target chain, key features",
40
+ },
41
+ stack: {
42
+ type: "string",
43
+ enum: ["solidity", "mcp-skill", "react-dapp", "convex-backend", "node-cli", "auto"],
44
+ description: "Tech stack. Use 'auto' to let Noel decide based on description.",
45
+ },
46
+ extras: {
47
+ type: "string",
48
+ description: "Optional: extra requirements, constraints, or preferred libraries",
49
+ },
50
+ },
51
+ required: ["description"],
52
+ },
53
+ },
54
+ {
55
+ name: "generate_component",
56
+ description: "Generate a production-ready React/TypeScript component. Returns the full .tsx file content. " +
57
+ "Includes props typing, Tailwind styling, and any hooks needed.",
58
+ inputSchema: {
59
+ type: "object",
60
+ properties: {
61
+ description: {
62
+ type: "string",
63
+ description: "What the component does, what it shows, and how it should behave",
64
+ },
65
+ name: {
66
+ type: "string",
67
+ description: "Component name in PascalCase, e.g. 'TokenPriceCard'",
68
+ },
69
+ context: {
70
+ type: "string",
71
+ description: "Optional: existing imports, store usage, or API calls the component should use",
72
+ },
73
+ },
74
+ required: ["description", "name"],
75
+ },
76
+ },
77
+ {
78
+ name: "generate_contract",
79
+ description: "Generate a Solidity smart contract from a description. Returns the full .sol file. " +
80
+ "Follows OpenZeppelin patterns, includes NatSpec, and is Base/EVM compatible.",
81
+ inputSchema: {
82
+ type: "object",
83
+ properties: {
84
+ description: {
85
+ type: "string",
86
+ description: "What the contract does — token type, mechanics, access control, any special logic",
87
+ },
88
+ name: {
89
+ type: "string",
90
+ description: "Contract name in PascalCase, e.g. 'NoelRewards'",
91
+ },
92
+ features: {
93
+ type: "array",
94
+ items: { type: "string" },
95
+ description: "Optional: specific features to include, e.g. ['ERC-20', 'burnable', 'pausable', 'ownable']",
96
+ },
97
+ },
98
+ required: ["description", "name"],
99
+ },
100
+ },
101
+ {
102
+ name: "audit_contract",
103
+ description: "Audit a Solidity smart contract for security vulnerabilities, gas inefficiencies, and logic bugs. " +
104
+ "Returns a structured report with severity ratings and fix recommendations.",
105
+ inputSchema: {
106
+ type: "object",
107
+ properties: {
108
+ code: {
109
+ type: "string",
110
+ description: "The full Solidity contract source code to audit",
111
+ },
112
+ focus: {
113
+ type: "array",
114
+ items: { type: "string" },
115
+ description: "Optional: specific areas to focus on, e.g. ['reentrancy', 'access control', 'overflow']",
116
+ },
117
+ },
118
+ required: ["code"],
119
+ },
120
+ },
121
+ {
122
+ name: "explain_code",
123
+ description: "Explain what a piece of code does in plain language. Works with Solidity, TypeScript, JavaScript, Python, and more. " +
124
+ "Breaks down logic, highlights key patterns, and explains the 'why' behind decisions.",
125
+ inputSchema: {
126
+ type: "object",
127
+ properties: {
128
+ code: {
129
+ type: "string",
130
+ description: "The code to explain",
131
+ },
132
+ depth: {
133
+ type: "string",
134
+ enum: ["overview", "detailed", "line-by-line"],
135
+ description: "How detailed the explanation should be. Default: 'detailed'",
136
+ },
137
+ },
138
+ required: ["code"],
139
+ },
140
+ },
141
+ {
142
+ name: "review_code",
143
+ description: "Review and improve a piece of code. Returns the improved version with a summary of changes. " +
144
+ "Fixes bugs, improves readability, adds types, optimizes gas (for Solidity), and removes anti-patterns.",
145
+ inputSchema: {
146
+ type: "object",
147
+ properties: {
148
+ code: {
149
+ type: "string",
150
+ description: "The code to review and improve",
151
+ },
152
+ language: {
153
+ type: "string",
154
+ description: "Language/framework, e.g. 'solidity', 'typescript', 'react', 'python'. Auto-detected if omitted.",
155
+ },
156
+ goals: {
157
+ type: "string",
158
+ description: "Optional: specific improvement goals, e.g. 'reduce gas costs', 'improve readability', 'add error handling'",
159
+ },
160
+ },
161
+ required: ["code"],
162
+ },
163
+ },
164
+ ];
165
+ // ── Handlers ──────────────────────────────────────────────────────────────────
166
+ const ScaffoldSchema = zod_1.z.object({
167
+ description: zod_1.z.string().min(10),
168
+ stack: zod_1.z.enum(["solidity", "mcp-skill", "react-dapp", "convex-backend", "node-cli", "auto"]).optional(),
169
+ extras: zod_1.z.string().optional(),
170
+ });
171
+ const ComponentSchema = zod_1.z.object({
172
+ description: zod_1.z.string().min(5),
173
+ name: zod_1.z.string().min(1),
174
+ context: zod_1.z.string().optional(),
175
+ });
176
+ const ContractSchema = zod_1.z.object({
177
+ description: zod_1.z.string().min(5),
178
+ name: zod_1.z.string().min(1),
179
+ features: zod_1.z.array(zod_1.z.string()).optional(),
180
+ });
181
+ const AuditSchema = zod_1.z.object({
182
+ code: zod_1.z.string().min(10),
183
+ focus: zod_1.z.array(zod_1.z.string()).optional(),
184
+ });
185
+ const ExplainSchema = zod_1.z.object({
186
+ code: zod_1.z.string().min(1),
187
+ depth: zod_1.z.enum(["overview", "detailed", "line-by-line"]).optional(),
188
+ });
189
+ const ReviewSchema = zod_1.z.object({
190
+ code: zod_1.z.string().min(1),
191
+ language: zod_1.z.string().optional(),
192
+ goals: zod_1.z.string().optional(),
193
+ });
194
+ async function handleCoderTool(name, args) {
195
+ switch (name) {
196
+ case "scaffold_project": {
197
+ const p = ScaffoldSchema.safeParse(args);
198
+ if (!p.success)
199
+ return err(`Invalid input: ${p.error.message}`);
200
+ const { description, stack = "auto", extras } = p.data;
201
+ const prompt = `Generate a complete project scaffold for the following:\n\n` +
202
+ `Description: ${description}\n` +
203
+ `Stack: ${stack}\n` +
204
+ (extras ? `Extra requirements: ${extras}\n` : "") +
205
+ `\nOutput format:\n` +
206
+ `1. Brief overview (2-3 sentences)\n` +
207
+ `2. Full file tree with all files listed\n` +
208
+ `3. Full content of each key file (package.json, main entry, config, one core module)\n` +
209
+ `4. Setup instructions (3-5 steps)\n\n` +
210
+ `Use real, runnable code. No TODO placeholders.`;
211
+ try {
212
+ const response = await (0, llm_js_1.callLLM)(CODER_SYSTEM, prompt, 4096);
213
+ return ok(response);
214
+ }
215
+ catch (e) {
216
+ return err(`scaffold_project failed: ${e.message}`);
217
+ }
218
+ }
219
+ case "generate_component": {
220
+ const p = ComponentSchema.safeParse(args);
221
+ if (!p.success)
222
+ return err(`Invalid input: ${p.error.message}`);
223
+ const { description, name: componentName, context } = p.data;
224
+ const prompt = `Generate a production-ready React TypeScript component.\n\n` +
225
+ `Component name: ${componentName}\n` +
226
+ `Description: ${description}\n` +
227
+ (context ? `Context / existing code to integrate with:\n${context}\n` : "") +
228
+ `\nRequirements:\n` +
229
+ `- Full .tsx file, no omissions\n` +
230
+ `- TypeScript props interface\n` +
231
+ `- Tailwind CSS for styling\n` +
232
+ `- Use lucide-react for icons if needed\n` +
233
+ `- Framer Motion for animations if the component is interactive\n` +
234
+ `- Named export (not default)\n` +
235
+ `Output only the .tsx file content, no prose before or after.`;
236
+ try {
237
+ const response = await (0, llm_js_1.callLLM)(CODER_SYSTEM, prompt, 3000);
238
+ return ok(response);
239
+ }
240
+ catch (e) {
241
+ return err(`generate_component failed: ${e.message}`);
242
+ }
243
+ }
244
+ case "generate_contract": {
245
+ const p = ContractSchema.safeParse(args);
246
+ if (!p.success)
247
+ return err(`Invalid input: ${p.error.message}`);
248
+ const { description, name: contractName, features = [] } = p.data;
249
+ const prompt = `Generate a production-ready Solidity smart contract.\n\n` +
250
+ `Contract name: ${contractName}\n` +
251
+ `Description: ${description}\n` +
252
+ (features.length ? `Required features: ${features.join(", ")}\n` : "") +
253
+ `\nRequirements:\n` +
254
+ `- Solidity ^0.8.20\n` +
255
+ `- SPDX-License-Identifier: MIT\n` +
256
+ `- Use OpenZeppelin contracts where appropriate (import paths: @openzeppelin/contracts/...)\n` +
257
+ `- Full NatSpec comments on all public functions\n` +
258
+ `- Optimized for Base chain (EVM compatible)\n` +
259
+ `- Include events for all state changes\n` +
260
+ `- Include a basic test outline as a comment at the bottom\n` +
261
+ `Output only the .sol file content.`;
262
+ try {
263
+ const response = await (0, llm_js_1.callLLM)(CODER_SYSTEM, prompt, 3500);
264
+ return ok(response);
265
+ }
266
+ catch (e) {
267
+ return err(`generate_contract failed: ${e.message}`);
268
+ }
269
+ }
270
+ case "audit_contract": {
271
+ const p = AuditSchema.safeParse(args);
272
+ if (!p.success)
273
+ return err(`Invalid input: ${p.error.message}`);
274
+ const { code, focus = [] } = p.data;
275
+ const prompt = `Audit the following Solidity smart contract for security vulnerabilities, gas issues, and logic bugs.\n` +
276
+ (focus.length ? `Focus areas: ${focus.join(", ")}\n` : "") +
277
+ `\nContract:\n\`\`\`solidity\n${code}\n\`\`\`\n\n` +
278
+ `Output a structured audit report with these sections:\n` +
279
+ `## Summary\n` +
280
+ `(overall risk rating: Critical/High/Medium/Low/Informational, brief overview)\n\n` +
281
+ `## Findings\n` +
282
+ `For each issue:\n` +
283
+ `- **[SEVERITY]** Title\n` +
284
+ ` - Location: (function/line)\n` +
285
+ ` - Description: (what's wrong and why)\n` +
286
+ ` - Recommendation: (exact fix or mitigation)\n\n` +
287
+ `## Gas Optimizations\n` +
288
+ `(list 2-5 concrete gas savings with estimated impact)\n\n` +
289
+ `## Positive Patterns\n` +
290
+ `(what the contract does well)`;
291
+ try {
292
+ const response = await (0, llm_js_1.callLLM)(CODER_SYSTEM, prompt, 3000);
293
+ return ok(response);
294
+ }
295
+ catch (e) {
296
+ return err(`audit_contract failed: ${e.message}`);
297
+ }
298
+ }
299
+ case "explain_code": {
300
+ const p = ExplainSchema.safeParse(args);
301
+ if (!p.success)
302
+ return err(`Invalid input: ${p.error.message}`);
303
+ const { code, depth = "detailed" } = p.data;
304
+ const depthInstructions = {
305
+ overview: "Give a high-level explanation in 3-5 sentences. What does it do, what problem does it solve, what are the main moving parts.",
306
+ detailed: "Explain the purpose, break down each major section, explain key patterns and design decisions, and note any non-obvious logic.",
307
+ "line-by-line": "Go through the code line by line (or block by block for repetitive parts), explaining exactly what each part does and why.",
308
+ };
309
+ const prompt = `Explain the following code.\n\nDepth: ${depth}\n${depthInstructions[depth]}\n\n` +
310
+ `Code:\n\`\`\`\n${code}\n\`\`\`\n\n` +
311
+ `Write in plain language. Assume the reader understands programming but may not know this specific codebase or language.`;
312
+ try {
313
+ const response = await (0, llm_js_1.callLLM)(CODER_SYSTEM, prompt, 2000);
314
+ return ok(response);
315
+ }
316
+ catch (e) {
317
+ return err(`explain_code failed: ${e.message}`);
318
+ }
319
+ }
320
+ case "review_code": {
321
+ const p = ReviewSchema.safeParse(args);
322
+ if (!p.success)
323
+ return err(`Invalid input: ${p.error.message}`);
324
+ const { code, language, goals } = p.data;
325
+ const prompt = `Review and improve the following code.\n` +
326
+ (language ? `Language/framework: ${language}\n` : "") +
327
+ (goals ? `Improvement goals: ${goals}\n` : "") +
328
+ `\nOriginal code:\n\`\`\`\n${code}\n\`\`\`\n\n` +
329
+ `Output:\n` +
330
+ `## What I changed\n` +
331
+ `(bullet list: each change and why)\n\n` +
332
+ `## Improved code\n` +
333
+ `\`\`\`\n(full improved code — no omissions, no "rest of code unchanged")\n\`\`\`\n\n` +
334
+ `## Further recommendations\n` +
335
+ `(optional: things to consider beyond this snippet)`;
336
+ try {
337
+ const response = await (0, llm_js_1.callLLM)(CODER_SYSTEM, prompt, 3500);
338
+ return ok(response);
339
+ }
340
+ catch (e) {
341
+ return err(`review_code failed: ${e.message}`);
342
+ }
343
+ }
344
+ default:
345
+ return null;
346
+ }
347
+ }