@noelclaw/mcp 2.3.0 → 2.4.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/index.js +10 -9
- package/dist/server.js +38 -28
- package/dist/tools/automation.js +38 -0
- package/dist/tools/coder.js +62 -0
- package/dist/tools/defi.js +127 -0
- package/dist/tools/humanizer.js +143 -2
- package/dist/tools/insight.js +197 -19
- package/dist/tools/market.js +182 -7
- package/dist/tools/memory.js +198 -170
- package/dist/tools/miroshark.js +15 -4
- package/dist/tools/os.js +223 -0
- package/dist/tools/scanner.js +183 -52
- package/dist/tools/swarm.js +327 -14
- package/dist/tools/vault.js +94 -153
- package/package.json +5 -2
- package/dist/tools/news.js +0 -6
- package/dist/tools/research.js +0 -8
- package/dist/tools/twitter.js +0 -67
package/dist/index.js
CHANGED
|
@@ -33,25 +33,26 @@ async function main() {
|
|
|
33
33
|
process.stderr.write(BANNER);
|
|
34
34
|
// ── Tool category counts ──────────────────────────────────────────────────
|
|
35
35
|
const categories = [
|
|
36
|
-
{ label: "Market", count:
|
|
37
|
-
{ label: "
|
|
36
|
+
{ label: "Market", count: 5, tools: "get_market_data · get_token_data · compare_tokens · market_overview · token_history" },
|
|
37
|
+
{ label: "Insight", count: 3, tools: "ask_noel · market_thesis · trade_plan" },
|
|
38
|
+
{ label: "DeFi", count: 6, tools: "portfolio · swap · send · scan_wallet · estimate · get_defi_yields" },
|
|
38
39
|
{ label: "Automation", count: 5, tools: "create · list · pause · delete · runs" },
|
|
39
|
-
{ label: "Scanner", count:
|
|
40
|
+
{ label: "Scanner", count: 4, tools: "scan_dips · scan_momentum · score_token · check_token" },
|
|
40
41
|
{ label: "Agents", count: 2, tools: "list_agents · hire_agent" },
|
|
41
|
-
{ label: "Swarm", count:
|
|
42
|
+
{ label: "Swarm", count: 11, tools: "start · stop · status · memory · scores · research · trigger · brief · broadcast · pulse" },
|
|
42
43
|
{ label: "Framework", count: 6, tools: "create_task · list_tasks · list_playbooks · run_playbook · ledger · sentinel" },
|
|
43
|
-
{ label: "Vault", count:
|
|
44
|
-
{ label: "Memory", count:
|
|
44
|
+
{ label: "Vault", count: 18, tools: "save · read · list · search · history · diff · export · remember · context · credential · publish · explore · connect · pin · delete · link · tag" },
|
|
45
|
+
{ label: "Memory", count: 8, tools: "add · search · context · profile · connect · list · delete · update" },
|
|
45
46
|
{ label: "MiroShark", count: 3, tools: "simulate · status · stop" },
|
|
46
47
|
{ label: "Wallet", count: 2, tools: "get_wallet_address · set_telegram" },
|
|
47
|
-
{ label: "Social", count:
|
|
48
|
-
{ label: "Coder", count:
|
|
48
|
+
{ label: "Social", count: 3, tools: "humanize_text · write_thread · write_post" },
|
|
49
|
+
{ label: "Coder", count: 7, tools: "scaffold_project · generate_component · generate_contract · audit_contract · explain_code · review_code · generate_mcp_skill" },
|
|
49
50
|
{ label: "Base", count: 4, tools: "query_vaults · list_markets · prepare_deposit · chain_stats" },
|
|
50
51
|
];
|
|
51
52
|
const total = server_js_1.ALL_TOOLS.length;
|
|
52
53
|
divider();
|
|
53
54
|
process.stderr.write(`\n`);
|
|
54
|
-
line("version", `v2.
|
|
55
|
+
line("version", `v2.4.0 ${C.dim}MCP protocol 2.1.0${C.reset}`);
|
|
55
56
|
line("network", `Base mainnet ${C.dim}via 0x Protocol · ethers v6${C.reset}`);
|
|
56
57
|
line("ai", `Bankr LLM ${C.dim}grok-3 · llm.bankr.bot${C.reset}`);
|
|
57
58
|
line("tools", `${C.white}${C.bold}${total} tools loaded${C.reset} ${C.dim}across ${categories.length} categories${C.reset}`);
|
package/dist/server.js
CHANGED
|
@@ -21,6 +21,7 @@ const scanner_js_1 = require("./tools/scanner.js");
|
|
|
21
21
|
const coder_js_1 = require("./tools/coder.js");
|
|
22
22
|
const base_js_1 = require("./tools/base.js");
|
|
23
23
|
const memory_js_1 = require("./tools/memory.js");
|
|
24
|
+
const os_js_1 = require("./tools/os.js");
|
|
24
25
|
const PRIVATE_KEY_RESPONSE = {
|
|
25
26
|
content: [{
|
|
26
27
|
type: "text",
|
|
@@ -35,45 +36,54 @@ function containsSensitiveRequest(args) {
|
|
|
35
36
|
text.includes("privatekey"));
|
|
36
37
|
}
|
|
37
38
|
exports.ALL_TOOLS = [
|
|
38
|
-
...market_js_1.MARKET_TOOLS, //
|
|
39
|
-
...insight_js_1.INSIGHT_TOOLS, //
|
|
40
|
-
...defi_js_1.DEFI_TOOLS, //
|
|
41
|
-
...automation_js_1.AUTOMATION_TOOLS, //
|
|
42
|
-
...swarm_js_1.SWARM_TOOLS, //
|
|
39
|
+
...market_js_1.MARKET_TOOLS, // 5 — get_market_data, get_token_data, compare_tokens, market_overview, token_history
|
|
40
|
+
...insight_js_1.INSIGHT_TOOLS, // 3 — ask_noel, market_thesis, trade_plan
|
|
41
|
+
...defi_js_1.DEFI_TOOLS, // 7 — get_portfolio, estimate_swap, swap_tokens, send_token, scan_wallet, analyze_wallet, get_defi_yields
|
|
42
|
+
...automation_js_1.AUTOMATION_TOOLS, // 6 — create, list, pause, delete, get_runs, run
|
|
43
|
+
...swarm_js_1.SWARM_TOOLS, // 13 — start, stop, status, read/write memory, scores, research, brief, trigger_agent, broadcast, pulse, reflect, watch
|
|
43
44
|
...framework_js_1.FRAMEWORK_TOOLS, // 6 — task packets, playbooks, sentinel, ledger
|
|
44
|
-
...vault_js_1.VAULT_TOOLS, //
|
|
45
|
+
...vault_js_1.VAULT_TOOLS, // 15 — save, read, list, search, history, diff, export, store_credential, get_credential, publish, explore, pin, delete, link, tag
|
|
45
46
|
...wallet_js_1.WALLET_TOOLS, // 2 — get_wallet_address, set_telegram
|
|
46
47
|
...miroshark_js_1.MIROSHARK_TOOLS, // 3 — simulate, status, stop
|
|
47
|
-
...humanizer_js_1.HUMANIZER_TOOLS, //
|
|
48
|
+
...humanizer_js_1.HUMANIZER_TOOLS, // 3 — humanize_text, write_thread, write_post
|
|
48
49
|
...agents_js_1.AGENT_TOOLS, // 2 — list_agents, hire_agent
|
|
49
|
-
...scanner_js_1.SCANNER_TOOLS, //
|
|
50
|
-
...coder_js_1.CODER_TOOLS, //
|
|
50
|
+
...scanner_js_1.SCANNER_TOOLS, // 4 — score_token, check_token, scan_dips, scan_momentum
|
|
51
|
+
...coder_js_1.CODER_TOOLS, // 7 — scaffold_project, generate_component, generate_contract, audit_contract, explain_code, review_code, generate_mcp_skill
|
|
51
52
|
...base_js_1.BASE_TOOLS, // 4 — query_vaults, list_markets, prepare_deposit, chain_stats
|
|
52
|
-
...memory_js_1.MEMORY_TOOLS, //
|
|
53
|
-
//
|
|
53
|
+
...memory_js_1.MEMORY_TOOLS, // 7 — memory_add, memory_search, memory_context, memory_profile, memory_list, memory_delete, memory_insight
|
|
54
|
+
...os_js_1.OS_TOOLS, // 3 — noel_status, noel_boot, noel_shutdown
|
|
55
|
+
// total: 90
|
|
54
56
|
];
|
|
55
|
-
|
|
57
|
+
const HANDLER_MAP = new Map([
|
|
58
|
+
...market_js_1.MARKET_TOOLS.map(t => [t.name, market_js_1.handleMarketTool]),
|
|
59
|
+
...defi_js_1.DEFI_TOOLS.map(t => [t.name, defi_js_1.handleDefiTool]),
|
|
60
|
+
...automation_js_1.AUTOMATION_TOOLS.map(t => [t.name, automation_js_1.handleAutomationTool]),
|
|
61
|
+
...swarm_js_1.SWARM_TOOLS.map(t => [t.name, swarm_js_1.handleSwarmTool]),
|
|
62
|
+
...framework_js_1.FRAMEWORK_TOOLS.map(t => [t.name, framework_js_1.handleFrameworkTool]),
|
|
63
|
+
...vault_js_1.VAULT_TOOLS.map(t => [t.name, vault_js_1.handleVaultTool]),
|
|
64
|
+
...wallet_js_1.WALLET_TOOLS.map(t => [t.name, wallet_js_1.handleWalletTool]),
|
|
65
|
+
...insight_js_1.INSIGHT_TOOLS.map(t => [t.name, insight_js_1.handleInsightTool]),
|
|
66
|
+
...miroshark_js_1.MIROSHARK_TOOLS.map(t => [t.name, miroshark_js_1.handleMirosharkTool]),
|
|
67
|
+
...humanizer_js_1.HUMANIZER_TOOLS.map(t => [t.name, humanizer_js_1.handleHumanizerTool]),
|
|
68
|
+
...agents_js_1.AGENT_TOOLS.map(t => [t.name, agents_js_1.handleAgentTool]),
|
|
69
|
+
...scanner_js_1.SCANNER_TOOLS.map(t => [t.name, scanner_js_1.handleScannerTool]),
|
|
70
|
+
...coder_js_1.CODER_TOOLS.map(t => [t.name, coder_js_1.handleCoderTool]),
|
|
71
|
+
...base_js_1.BASE_TOOLS.map(t => [t.name, base_js_1.handleBaseTool]),
|
|
72
|
+
...memory_js_1.MEMORY_TOOLS.map(t => [t.name, memory_js_1.handleMemoryTool]),
|
|
73
|
+
...os_js_1.OS_TOOLS.map(t => [t.name, os_js_1.handleOsTool]),
|
|
74
|
+
]);
|
|
75
|
+
exports.server = new index_js_1.Server({ name: "noelclaw", version: "2.4.0" }, { capabilities: { tools: {} } });
|
|
56
76
|
exports.server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => ({ tools: exports.ALL_TOOLS }));
|
|
57
77
|
exports.server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
|
|
58
78
|
const { name, arguments: args } = request.params;
|
|
59
79
|
if (containsSensitiveRequest(args))
|
|
60
80
|
return PRIVATE_KEY_RESPONSE;
|
|
81
|
+
const handler = HANDLER_MAP.get(name);
|
|
82
|
+
if (!handler) {
|
|
83
|
+
return { content: [{ type: "text", text: `Unknown tool: ${name}` }], isError: true };
|
|
84
|
+
}
|
|
61
85
|
try {
|
|
62
|
-
const result = await (
|
|
63
|
-
await (0, defi_js_1.handleDefiTool)(name, args) ??
|
|
64
|
-
await (0, automation_js_1.handleAutomationTool)(name, args) ??
|
|
65
|
-
await (0, swarm_js_1.handleSwarmTool)(name, args) ??
|
|
66
|
-
await (0, framework_js_1.handleFrameworkTool)(name, args) ??
|
|
67
|
-
await (0, vault_js_1.handleVaultTool)(name, args) ??
|
|
68
|
-
await (0, wallet_js_1.handleWalletTool)(name, args) ??
|
|
69
|
-
await (0, insight_js_1.handleInsightTool)(name, args) ??
|
|
70
|
-
await (0, miroshark_js_1.handleMirosharkTool)(name, args) ??
|
|
71
|
-
await (0, humanizer_js_1.handleHumanizerTool)(name, args) ??
|
|
72
|
-
await (0, agents_js_1.handleAgentTool)(name, args) ??
|
|
73
|
-
await (0, scanner_js_1.handleScannerTool)(name, args) ??
|
|
74
|
-
await (0, coder_js_1.handleCoderTool)(name, args) ??
|
|
75
|
-
await (0, base_js_1.handleBaseTool)(name, args) ??
|
|
76
|
-
await (0, memory_js_1.handleMemoryTool)(name, args);
|
|
86
|
+
const result = await handler(name, args);
|
|
77
87
|
if (result)
|
|
78
88
|
return result;
|
|
79
89
|
return { content: [{ type: "text", text: `Unknown tool: ${name}` }], isError: true };
|
|
@@ -93,7 +103,7 @@ exports.server.setRequestHandler(types_js_1.CallToolRequestSchema, async (reques
|
|
|
93
103
|
` (replace \`<txHash>\` with the actual transaction hash)`,
|
|
94
104
|
`4. Retry the tool call`, ``,
|
|
95
105
|
"**Or bypass with a session token:**",
|
|
96
|
-
"Set `NOELCLAW_SESSION_TOKEN` with your Noelclaw session token from noelclaw.
|
|
106
|
+
"Set `NOELCLAW_SESSION_TOKEN` with your Noelclaw session token from noelclaw.com",
|
|
97
107
|
] : []),
|
|
98
108
|
];
|
|
99
109
|
return { content: [{ type: "text", text: lines.join("\n") }], isError: true };
|
package/dist/tools/automation.js
CHANGED
|
@@ -49,6 +49,20 @@ exports.AUTOMATION_TOOLS = [
|
|
|
49
49
|
required: ["automationId"],
|
|
50
50
|
},
|
|
51
51
|
},
|
|
52
|
+
{
|
|
53
|
+
name: "run_automation",
|
|
54
|
+
description: "Trigger an automation immediately — regardless of its schedule or trigger condition. " +
|
|
55
|
+
"Use to test an automation after creating it, or to run a one-off DCA/swap/alert right now. " +
|
|
56
|
+
"The automation must be active (not paused or deleted). " +
|
|
57
|
+
"Get the ID from list_automations.",
|
|
58
|
+
inputSchema: {
|
|
59
|
+
type: "object",
|
|
60
|
+
properties: {
|
|
61
|
+
automationId: { type: "string", description: "Automation ID to run now (from list_automations)" },
|
|
62
|
+
},
|
|
63
|
+
required: ["automationId"],
|
|
64
|
+
},
|
|
65
|
+
},
|
|
52
66
|
];
|
|
53
67
|
const CreateAutomationSchema = zod_1.z.object({ rawInput: zod_1.z.string().min(1) });
|
|
54
68
|
const AutomationIdSchema = zod_1.z.object({ automationId: zod_1.z.string().min(1) });
|
|
@@ -148,6 +162,30 @@ async function handleAutomationTool(name, args) {
|
|
|
148
162
|
}
|
|
149
163
|
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
150
164
|
}
|
|
165
|
+
case "run_automation": {
|
|
166
|
+
const parsed = AutomationIdSchema.safeParse(args);
|
|
167
|
+
if (!parsed.success)
|
|
168
|
+
return { content: [{ type: "text", text: `Invalid input: automationId ${parsed.error.issues[0].message}` }], isError: true };
|
|
169
|
+
const data = await (0, convex_js_1.callConvex)("/automations/run", "POST", { automationId: parsed.data.automationId }, "run_automation");
|
|
170
|
+
if (data.error)
|
|
171
|
+
return { content: [{ type: "text", text: `Error: ${data.error}` }], isError: true };
|
|
172
|
+
const statusIcon = { success: "✅", failed: "❌", skipped: "⏭️" };
|
|
173
|
+
const icon = statusIcon[data.status] ?? "⚡";
|
|
174
|
+
const spent = data.amountUsd != null ? ` · $${Number(data.amountUsd).toFixed(2)} spent` : "";
|
|
175
|
+
const txLine = data.txHash ? `\nTx: https://basescan.org/tx/${data.txHash}` : "";
|
|
176
|
+
return {
|
|
177
|
+
content: [{
|
|
178
|
+
type: "text",
|
|
179
|
+
text: [
|
|
180
|
+
`${icon} **Automation triggered: ${data.status ?? "executed"}**${spent}`,
|
|
181
|
+
data.message ?? "",
|
|
182
|
+
txLine,
|
|
183
|
+
``,
|
|
184
|
+
`Use \`get_automation_runs automationId: "${parsed.data.automationId}"\` to see full history.`,
|
|
185
|
+
].filter(Boolean).join("\n"),
|
|
186
|
+
}],
|
|
187
|
+
};
|
|
188
|
+
}
|
|
151
189
|
default:
|
|
152
190
|
return null;
|
|
153
191
|
}
|
package/dist/tools/coder.js
CHANGED
|
@@ -138,6 +138,33 @@ exports.CODER_TOOLS = [
|
|
|
138
138
|
required: ["code"],
|
|
139
139
|
},
|
|
140
140
|
},
|
|
141
|
+
{
|
|
142
|
+
name: "generate_mcp_skill",
|
|
143
|
+
description: "Generate a complete Claude Code skill (.md file) from a description. " +
|
|
144
|
+
"Skills are slash-command workflows that run inside Claude Code — they can call tools, " +
|
|
145
|
+
"loop, delegate to subagents, and have persistent behavior. " +
|
|
146
|
+
"Returns a ready-to-use .md file you can drop into your .claude/skills/ directory. " +
|
|
147
|
+
"Use this to automate repetitive Claude Code workflows without writing TypeScript.",
|
|
148
|
+
inputSchema: {
|
|
149
|
+
type: "object",
|
|
150
|
+
properties: {
|
|
151
|
+
description: {
|
|
152
|
+
type: "string",
|
|
153
|
+
description: "What the skill should do — be specific about inputs, outputs, and any tools it should use",
|
|
154
|
+
},
|
|
155
|
+
name: {
|
|
156
|
+
type: "string",
|
|
157
|
+
description: "Skill name in kebab-case, e.g. 'daily-standup', 'code-review', 'deploy-check'",
|
|
158
|
+
},
|
|
159
|
+
tools: {
|
|
160
|
+
type: "array",
|
|
161
|
+
items: { type: "string" },
|
|
162
|
+
description: "Optional: list of Claude Code tools or MCP tools the skill should use, e.g. ['Bash', 'Read', 'memory_search']",
|
|
163
|
+
},
|
|
164
|
+
},
|
|
165
|
+
required: ["description", "name"],
|
|
166
|
+
},
|
|
167
|
+
},
|
|
141
168
|
{
|
|
142
169
|
name: "review_code",
|
|
143
170
|
description: "Review and improve a piece of code. Returns the improved version with a summary of changes. " +
|
|
@@ -191,6 +218,11 @@ const ReviewSchema = zod_1.z.object({
|
|
|
191
218
|
language: zod_1.z.string().optional(),
|
|
192
219
|
goals: zod_1.z.string().optional(),
|
|
193
220
|
});
|
|
221
|
+
const McpSkillSchema = zod_1.z.object({
|
|
222
|
+
description: zod_1.z.string().min(10),
|
|
223
|
+
name: zod_1.z.string().min(1).regex(/^[a-z0-9-]+$/, "must be kebab-case"),
|
|
224
|
+
tools: zod_1.z.array(zod_1.z.string()).optional(),
|
|
225
|
+
});
|
|
194
226
|
async function handleCoderTool(name, args) {
|
|
195
227
|
switch (name) {
|
|
196
228
|
case "scaffold_project": {
|
|
@@ -341,6 +373,36 @@ async function handleCoderTool(name, args) {
|
|
|
341
373
|
return err(`review_code failed: ${e.message}`);
|
|
342
374
|
}
|
|
343
375
|
}
|
|
376
|
+
case "generate_mcp_skill": {
|
|
377
|
+
const p = McpSkillSchema.safeParse(args);
|
|
378
|
+
if (!p.success)
|
|
379
|
+
return err(`Invalid input: ${p.error.message}`);
|
|
380
|
+
const { description, name: skillName, tools = [] } = p.data;
|
|
381
|
+
const prompt = `Generate a complete Claude Code skill (.md file) for the following workflow:\n\n` +
|
|
382
|
+
`Skill name: /${skillName}\n` +
|
|
383
|
+
`Description: ${description}\n` +
|
|
384
|
+
(tools.length ? `Tools to use: ${tools.join(", ")}\n` : "") +
|
|
385
|
+
`\n` +
|
|
386
|
+
`Claude Code skill format rules:\n` +
|
|
387
|
+
`- The file is a markdown document that serves as a system prompt for a Claude Code slash command\n` +
|
|
388
|
+
`- It should start with a brief description of what the skill does\n` +
|
|
389
|
+
`- Include an "## Input" section explaining what arguments the skill accepts (if any)\n` +
|
|
390
|
+
`- Include a "## Steps" section with numbered, concrete steps\n` +
|
|
391
|
+
`- Include a "## Output" section describing what the skill produces\n` +
|
|
392
|
+
`- Steps can reference tool calls like "Use the Bash tool to run X" or "Use memory_search to find Y"\n` +
|
|
393
|
+
`- Steps can reference conditional logic and loops\n` +
|
|
394
|
+
`- Keep it under 200 lines — skills should be focused, not monolithic\n` +
|
|
395
|
+
`- Write in imperative second person ("Run...", "Check...", "If X, then...")\n` +
|
|
396
|
+
`- Do NOT include markdown fences around the output — output the raw .md content directly\n\n` +
|
|
397
|
+
`Output only the .md file content, ready to save as .claude/skills/${skillName}.md`;
|
|
398
|
+
try {
|
|
399
|
+
const response = await (0, llm_js_1.callLLM)(CODER_SYSTEM, prompt, 2000);
|
|
400
|
+
return ok(`# /${skillName} skill — save to .claude/skills/${skillName}.md\n\n---\n\n${response}`);
|
|
401
|
+
}
|
|
402
|
+
catch (e) {
|
|
403
|
+
return err(`generate_mcp_skill failed: ${e.message}`);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
344
406
|
default:
|
|
345
407
|
return null;
|
|
346
408
|
}
|
package/dist/tools/defi.js
CHANGED
|
@@ -55,9 +55,45 @@ exports.DEFI_TOOLS = [
|
|
|
55
55
|
description: "AI-powered portfolio scan — concentration risk, volatility exposure, Base ecosystem opportunities, and a concrete 3-step action plan based on your actual holdings. Requires wallet auth.",
|
|
56
56
|
inputSchema: { type: "object", properties: {}, required: [] },
|
|
57
57
|
},
|
|
58
|
+
{
|
|
59
|
+
name: "analyze_wallet",
|
|
60
|
+
description: "AI-powered analysis of any public wallet on Base — not just your own. " +
|
|
61
|
+
"Enter any 0x address: see token holdings, portfolio value, concentration risk, " +
|
|
62
|
+
"DeFi positions, and a behavioral profile (whale, degen, LP provider, etc.). " +
|
|
63
|
+
"Use to track smart money, research whales, or audit any wallet before copying trades.",
|
|
64
|
+
inputSchema: {
|
|
65
|
+
type: "object",
|
|
66
|
+
properties: {
|
|
67
|
+
address: { type: "string", description: "Wallet address to analyze (0x...)" },
|
|
68
|
+
label: { type: "string", description: "Optional label for this wallet (e.g. 'whale from Twitter')" },
|
|
69
|
+
},
|
|
70
|
+
required: ["address"],
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
name: "get_defi_yields",
|
|
75
|
+
description: "Fetch top DeFi yield opportunities on Base — Morpho, Moonwell, Aerodrome, Uniswap, and more. " +
|
|
76
|
+
"Returns live APY, TVL, and pool info from DeFiLlama (no API key required). " +
|
|
77
|
+
"Filter by token or minimum APY. Use before depositing to find the best rates.",
|
|
78
|
+
inputSchema: {
|
|
79
|
+
type: "object",
|
|
80
|
+
properties: {
|
|
81
|
+
token: { type: "string", description: "Optional: filter by token symbol, e.g. 'USDC', 'ETH', 'WETH'" },
|
|
82
|
+
minApy: { type: "number", description: "Optional: minimum APY % to show (default 1)" },
|
|
83
|
+
limit: { type: "number", description: "Max results to return (default 20)" },
|
|
84
|
+
},
|
|
85
|
+
required: [],
|
|
86
|
+
},
|
|
87
|
+
},
|
|
58
88
|
];
|
|
59
89
|
const SwapSchema = zod_1.z.object({ fromToken: zod_1.z.string().min(1), toToken: zod_1.z.string().min(1), amount: zod_1.z.string().min(1) });
|
|
60
90
|
const SendSchema = zod_1.z.object({ token: zod_1.z.string().min(1), toAddress: zod_1.z.string().regex(/^0x[0-9a-fA-F]{40}$/, "must be a valid 0x address"), amount: zod_1.z.string().min(1) });
|
|
91
|
+
const AnalyzeWalletSchema = zod_1.z.object({ address: zod_1.z.string().regex(/^0x[0-9a-fA-F]{40}$/, "must be a valid 0x address"), label: zod_1.z.string().optional() });
|
|
92
|
+
const DefiYieldsSchema = zod_1.z.object({
|
|
93
|
+
token: zod_1.z.string().optional(),
|
|
94
|
+
minApy: zod_1.z.number().optional(),
|
|
95
|
+
limit: zod_1.z.number().int().min(1).max(100).optional(),
|
|
96
|
+
}).default({});
|
|
61
97
|
const BUY_DECIMALS = { USDC: 6, USDT: 6, DAI: 18, ETH: 18, WETH: 18 };
|
|
62
98
|
function formatTokenAmount(raw, token) {
|
|
63
99
|
const dec = BUY_DECIMALS[token.toUpperCase()] ?? 18;
|
|
@@ -183,6 +219,97 @@ async function handleDefiTool(name, args) {
|
|
|
183
219
|
: "";
|
|
184
220
|
return { content: [{ type: "text", text: header + body + footer }] };
|
|
185
221
|
}
|
|
222
|
+
case "analyze_wallet": {
|
|
223
|
+
const parsed = AnalyzeWalletSchema.safeParse(args);
|
|
224
|
+
if (!parsed.success)
|
|
225
|
+
return { content: [{ type: "text", text: `Invalid input: ${parsed.error.issues[0].message}` }], isError: true };
|
|
226
|
+
const { address, label } = parsed.data;
|
|
227
|
+
const data = await (0, convex_js_1.callConvex)("/wallet/analyze", "POST", { address, label }, "analyze_wallet");
|
|
228
|
+
if (data.error)
|
|
229
|
+
return { content: [{ type: "text", text: `Wallet analysis failed: ${data.error}` }], isError: true };
|
|
230
|
+
const total = (data.totalUsd ?? 0).toFixed(2);
|
|
231
|
+
const walletLabel = label ? ` — ${label}` : "";
|
|
232
|
+
const topHoldings = (data.holdings ?? [])
|
|
233
|
+
.slice(0, 8)
|
|
234
|
+
.map(h => `• **${h.token}**: $${(h.valueUsd ?? 0).toFixed(2)}${h.pct != null ? ` (${h.pct}%)` : ""}`)
|
|
235
|
+
.join("\n");
|
|
236
|
+
const profileLine = data.profile ? `**Profile:** ${data.profile}\n` : "";
|
|
237
|
+
const header = [
|
|
238
|
+
`**Wallet Analysis**${walletLabel}`,
|
|
239
|
+
`\`${address}\``,
|
|
240
|
+
`**Portfolio value:** $${total}`,
|
|
241
|
+
``,
|
|
242
|
+
profileLine,
|
|
243
|
+
`**Holdings:**`,
|
|
244
|
+
topHoldings || "No token holdings found.",
|
|
245
|
+
``,
|
|
246
|
+
].join("\n");
|
|
247
|
+
const body = data.analysis ?? (data.analysisError ? `*AI analysis unavailable: ${data.analysisError}*` : "*AI analysis not available*");
|
|
248
|
+
return { content: [{ type: "text", text: header + body }] };
|
|
249
|
+
}
|
|
250
|
+
case "get_defi_yields": {
|
|
251
|
+
const parsed = DefiYieldsSchema.safeParse(args ?? {});
|
|
252
|
+
if (!parsed.success)
|
|
253
|
+
return { content: [{ type: "text", text: `Invalid input: ${parsed.error.issues[0].message}` }], isError: true };
|
|
254
|
+
const { token, minApy = 1, limit = 20 } = parsed.data;
|
|
255
|
+
let pools;
|
|
256
|
+
try {
|
|
257
|
+
const res = await fetch("https://yields.llama.fi/pools", { signal: AbortSignal.timeout(15000) });
|
|
258
|
+
if (!res.ok)
|
|
259
|
+
throw new Error(`HTTP ${res.status}`);
|
|
260
|
+
const data = await res.json();
|
|
261
|
+
pools = data.data ?? [];
|
|
262
|
+
}
|
|
263
|
+
catch (e) {
|
|
264
|
+
return { content: [{ type: "text", text: `DeFiLlama fetch failed: ${e.message}` }], isError: true };
|
|
265
|
+
}
|
|
266
|
+
// Filter to Base chain
|
|
267
|
+
let filtered = pools.filter((p) => p.chain === "Base");
|
|
268
|
+
// Filter by token if specified
|
|
269
|
+
if (token) {
|
|
270
|
+
const upper = token.toUpperCase();
|
|
271
|
+
filtered = filtered.filter((p) => (p.symbol ?? "").toUpperCase().includes(upper) ||
|
|
272
|
+
(p.underlyingTokens ?? []).some((t) => t.toUpperCase().includes(upper)));
|
|
273
|
+
}
|
|
274
|
+
// Filter by minimum APY and remove outliers (>10000% are usually broken)
|
|
275
|
+
filtered = filtered
|
|
276
|
+
.filter((p) => (p.apy ?? 0) >= minApy && (p.apy ?? 0) <= 10000)
|
|
277
|
+
.sort((a, b) => (b.apy ?? 0) - (a.apy ?? 0))
|
|
278
|
+
.slice(0, limit);
|
|
279
|
+
if (!filtered.length) {
|
|
280
|
+
return {
|
|
281
|
+
content: [{
|
|
282
|
+
type: "text",
|
|
283
|
+
text: [
|
|
284
|
+
`## DeFi Yields on Base`,
|
|
285
|
+
`No pools found${token ? ` for ${token.toUpperCase()}` : ""} with APY ≥ ${minApy}%.`,
|
|
286
|
+
``,
|
|
287
|
+
`Try lowering \`minApy\` or removing the token filter.`,
|
|
288
|
+
].join("\n"),
|
|
289
|
+
}],
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
const fmt = (n) => n >= 1000000000 ? `$${(n / 1000000000).toFixed(1)}B`
|
|
293
|
+
: n >= 1000000 ? `$${(n / 1000000).toFixed(1)}M`
|
|
294
|
+
: n >= 1000 ? `$${(n / 1000).toFixed(0)}K`
|
|
295
|
+
: `$${n.toFixed(0)}`;
|
|
296
|
+
const lines = [
|
|
297
|
+
`## DeFi Yields on Base${token ? ` — ${token.toUpperCase()}` : ""}`,
|
|
298
|
+
`Top ${filtered.length} pools · APY ≥ ${minApy}% · Source: DeFiLlama`,
|
|
299
|
+
``,
|
|
300
|
+
`| # | Pool | Protocol | APY | TVL |`,
|
|
301
|
+
`|---|------|----------|-----|-----|`,
|
|
302
|
+
];
|
|
303
|
+
filtered.forEach((p, i) => {
|
|
304
|
+
const apy = (p.apy ?? 0).toFixed(1);
|
|
305
|
+
const tvl = fmt(p.tvlUsd ?? 0);
|
|
306
|
+
const name = (p.symbol ?? p.pool ?? "—").replace(/-/g, " ");
|
|
307
|
+
const proj = p.project ?? "—";
|
|
308
|
+
lines.push(`| ${i + 1} | ${name} | ${proj} | **${apy}%** | ${tvl} |`);
|
|
309
|
+
});
|
|
310
|
+
lines.push(``, `Use \`swap_tokens\` to position, then deposit via the protocol's UI. Always check smart contract risk before depositing.`);
|
|
311
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
312
|
+
}
|
|
186
313
|
default:
|
|
187
314
|
return null;
|
|
188
315
|
}
|
package/dist/tools/humanizer.js
CHANGED
|
@@ -5,6 +5,66 @@ exports.handleHumanizerTool = handleHumanizerTool;
|
|
|
5
5
|
const zod_1 = require("zod");
|
|
6
6
|
const llm_js_1 = require("../llm.js");
|
|
7
7
|
exports.HUMANIZER_TOOLS = [
|
|
8
|
+
{
|
|
9
|
+
name: "write_thread",
|
|
10
|
+
description: "Write a viral Twitter/X thread on any crypto or tech topic. " +
|
|
11
|
+
"Returns a numbered thread (1/, 2/, ...) with a hook tweet, " +
|
|
12
|
+
"3-7 content tweets, and a strong closer with CTA. " +
|
|
13
|
+
"Written in a direct, punchy voice — no fluff, no AI tells. " +
|
|
14
|
+
"Optionally provide your own voice sample to match your style.",
|
|
15
|
+
inputSchema: {
|
|
16
|
+
type: "object",
|
|
17
|
+
properties: {
|
|
18
|
+
topic: {
|
|
19
|
+
type: "string",
|
|
20
|
+
description: "The topic or angle for the thread, e.g. 'why Base is winning', 'how I use MCP agents for DeFi research'",
|
|
21
|
+
},
|
|
22
|
+
tone: {
|
|
23
|
+
type: "string",
|
|
24
|
+
enum: ["alpha", "educational", "opinion", "story"],
|
|
25
|
+
description: "Thread tone: 'alpha' (edge/insight), 'educational' (explainer), 'opinion' (hot take), 'story' (personal narrative). Default: opinion.",
|
|
26
|
+
},
|
|
27
|
+
tweets: {
|
|
28
|
+
type: "number",
|
|
29
|
+
description: "Number of tweets in the thread, 4–12 (default: 7)",
|
|
30
|
+
},
|
|
31
|
+
voice_sample: {
|
|
32
|
+
type: "string",
|
|
33
|
+
description: "Optional: paste 1-3 of your existing tweets to match your voice",
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
required: ["topic"],
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
name: "write_post",
|
|
41
|
+
description: "Write a single viral-style crypto/tech post for Twitter/X. " +
|
|
42
|
+
"Returns one punchy post under 280 characters (or up to 500 with long-form enabled). " +
|
|
43
|
+
"Hooks in the first line, delivers the insight, ends with impact. No AI fluff.",
|
|
44
|
+
inputSchema: {
|
|
45
|
+
type: "object",
|
|
46
|
+
properties: {
|
|
47
|
+
topic: {
|
|
48
|
+
type: "string",
|
|
49
|
+
description: "What to post about — a thought, observation, alpha, or hot take",
|
|
50
|
+
},
|
|
51
|
+
style: {
|
|
52
|
+
type: "string",
|
|
53
|
+
enum: ["hook", "hot-take", "alpha", "question", "observation"],
|
|
54
|
+
description: "Post style. Default: hook.",
|
|
55
|
+
},
|
|
56
|
+
long: {
|
|
57
|
+
type: "boolean",
|
|
58
|
+
description: "Allow up to 500 chars (long-form post). Default: false (280 chars max)",
|
|
59
|
+
},
|
|
60
|
+
voice_sample: {
|
|
61
|
+
type: "string",
|
|
62
|
+
description: "Optional: a sample of your writing to match your voice",
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
required: ["topic"],
|
|
66
|
+
},
|
|
67
|
+
},
|
|
8
68
|
{
|
|
9
69
|
name: "humanize_text",
|
|
10
70
|
description: "Remove AI writing patterns from text — makes it sound natural, direct, and human. " +
|
|
@@ -28,8 +88,20 @@ exports.HUMANIZER_TOOLS = [
|
|
|
28
88
|
},
|
|
29
89
|
];
|
|
30
90
|
const HumanizerSchema = zod_1.z.object({
|
|
31
|
-
text: zod_1.z.string().min(1),
|
|
32
|
-
voice_sample: zod_1.z.string().optional(),
|
|
91
|
+
text: zod_1.z.string().min(1).max(20000),
|
|
92
|
+
voice_sample: zod_1.z.string().max(5000).optional(),
|
|
93
|
+
});
|
|
94
|
+
const WriteThreadSchema = zod_1.z.object({
|
|
95
|
+
topic: zod_1.z.string().min(3).max(500),
|
|
96
|
+
tone: zod_1.z.enum(["alpha", "educational", "opinion", "story"]).optional(),
|
|
97
|
+
tweets: zod_1.z.number().int().min(4).max(12).optional(),
|
|
98
|
+
voice_sample: zod_1.z.string().max(5000).optional(),
|
|
99
|
+
});
|
|
100
|
+
const WritePostSchema = zod_1.z.object({
|
|
101
|
+
topic: zod_1.z.string().min(3).max(500),
|
|
102
|
+
style: zod_1.z.enum(["hook", "hot-take", "alpha", "question", "observation"]).optional(),
|
|
103
|
+
long: zod_1.z.boolean().optional(),
|
|
104
|
+
voice_sample: zod_1.z.string().max(5000).optional(),
|
|
33
105
|
});
|
|
34
106
|
const HUMANIZER_SYSTEM = `You are a text editor that removes signs of AI-generated writing.
|
|
35
107
|
|
|
@@ -78,7 +150,76 @@ PROCESS:
|
|
|
78
150
|
6. Output ONLY the final humanized text — no commentary, no explanation, no "Here is your text:"
|
|
79
151
|
|
|
80
152
|
If a voice sample is provided, match its tone, rhythm, and vocabulary. Otherwise use direct, opinionated, natural prose.`;
|
|
153
|
+
const THREAD_SYSTEM = `You are a crypto Twitter ghostwriter who writes threads that go viral. Your style: direct, no fluff, confident without being cringe. You understand DeFi, on-chain data, narratives, and market structure. You write like a smart practitioner, not a content creator.
|
|
154
|
+
|
|
155
|
+
Rules:
|
|
156
|
+
- First tweet is the hook — bold claim or surprising insight. Must make people stop scrolling.
|
|
157
|
+
- Middle tweets: each one standalone insight. No "in this thread I'll explain" filler.
|
|
158
|
+
- Last tweet: the payoff. Strong closer, optional CTA (follow, RT, reply) — one CTA max.
|
|
159
|
+
- Number format: 1/ 2/ 3/ etc. Each tweet on its own line, separated by blank line.
|
|
160
|
+
- Under 280 chars per tweet unless it genuinely needs more (max 500).
|
|
161
|
+
- No em dashes, no "delve", no "landscape", no "it's worth noting".
|
|
162
|
+
- No hashtags unless they're actually used. No emojis unless they add meaning.
|
|
163
|
+
- Write in the user's voice if a sample is provided.`;
|
|
164
|
+
const POST_SYSTEM = `You are a crypto Twitter ghostwriter. Write one punchy, high-impact post. Direct. No fluff. Hook in the first line. No em dashes, no AI vocabulary. Write like a smart practitioner with an edge.`;
|
|
81
165
|
async function handleHumanizerTool(name, args) {
|
|
166
|
+
if (name === "write_thread") {
|
|
167
|
+
const parsed = WriteThreadSchema.safeParse(args);
|
|
168
|
+
if (!parsed.success)
|
|
169
|
+
return { content: [{ type: "text", text: `Invalid input: ${parsed.error.issues[0].message}` }], isError: true };
|
|
170
|
+
const { topic, tone = "opinion", tweets = 7, voice_sample } = parsed.data;
|
|
171
|
+
const toneGuides = {
|
|
172
|
+
alpha: "Share non-obvious insights or edge. Act like you have information most people don't.",
|
|
173
|
+
educational: "Explain a concept clearly. Assume smart but non-expert reader.",
|
|
174
|
+
opinion: "Take a clear position. Defend it with reasoning. Don't hedge.",
|
|
175
|
+
story: "Tell a real story with a beginning, conflict, and lesson. Make it personal and specific.",
|
|
176
|
+
};
|
|
177
|
+
const prompt = [
|
|
178
|
+
`Write a ${tweets}-tweet Twitter/X thread on: ${topic}`,
|
|
179
|
+
``,
|
|
180
|
+
`Tone: ${tone} — ${toneGuides[tone]}`,
|
|
181
|
+
voice_sample ? `Voice sample (match this style):\n${voice_sample}` : "",
|
|
182
|
+
``,
|
|
183
|
+
`Format: number each tweet as 1/ 2/ 3/ etc., separated by blank lines.`,
|
|
184
|
+
`First tweet = hook. Last tweet = strong closer.`,
|
|
185
|
+
`Output only the tweets — no intro, no explanation.`,
|
|
186
|
+
].filter(Boolean).join("\n");
|
|
187
|
+
try {
|
|
188
|
+
const output = await (0, llm_js_1.callLLM)(THREAD_SYSTEM, prompt, 2000);
|
|
189
|
+
return { content: [{ type: "text", text: output.trim() }] };
|
|
190
|
+
}
|
|
191
|
+
catch (err) {
|
|
192
|
+
return { content: [{ type: "text", text: `write_thread error: ${err.message}` }], isError: true };
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
if (name === "write_post") {
|
|
196
|
+
const parsed = WritePostSchema.safeParse(args);
|
|
197
|
+
if (!parsed.success)
|
|
198
|
+
return { content: [{ type: "text", text: `Invalid input: ${parsed.error.issues[0].message}` }], isError: true };
|
|
199
|
+
const { topic, style = "hook", long = false, voice_sample } = parsed.data;
|
|
200
|
+
const styleGuides = {
|
|
201
|
+
hook: "Strong first line that stops the scroll. Deliver the insight after.",
|
|
202
|
+
"hot-take": "Controversial opinion stated plainly. Don't soften it.",
|
|
203
|
+
alpha: "Non-obvious market insight written like you're sharing it with one smart friend.",
|
|
204
|
+
question: "Ask a sharp, thought-provoking question. Don't answer it.",
|
|
205
|
+
observation: "One specific thing you noticed that most people missed.",
|
|
206
|
+
};
|
|
207
|
+
const charLimit = long ? 500 : 280;
|
|
208
|
+
const prompt = [
|
|
209
|
+
`Write one ${style} post about: ${topic}`,
|
|
210
|
+
`Style: ${styleGuides[style]}`,
|
|
211
|
+
`Max length: ${charLimit} characters.`,
|
|
212
|
+
voice_sample ? `Voice sample:\n${voice_sample}` : "",
|
|
213
|
+
`Output only the post text — nothing else.`,
|
|
214
|
+
].filter(Boolean).join("\n");
|
|
215
|
+
try {
|
|
216
|
+
const output = await (0, llm_js_1.callLLM)(POST_SYSTEM, prompt, 300);
|
|
217
|
+
return { content: [{ type: "text", text: output.trim() }] };
|
|
218
|
+
}
|
|
219
|
+
catch (err) {
|
|
220
|
+
return { content: [{ type: "text", text: `write_post error: ${err.message}` }], isError: true };
|
|
221
|
+
}
|
|
222
|
+
}
|
|
82
223
|
if (name !== "humanize_text")
|
|
83
224
|
return null;
|
|
84
225
|
const parsed = HumanizerSchema.safeParse(args);
|