@noelclaw/mcp 3.2.0 → 3.3.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/README.md +61 -86
- package/dist/agent-loop.js +61 -4
- package/dist/cli.js +1 -1
- package/dist/convex.js +6 -6
- package/dist/index.js +19 -12
- package/dist/llm.js +1 -1
- package/dist/server.js +8 -2
- package/dist/tools/defi.js +0 -6
- package/dist/tools/insight.js +28 -2
- package/dist/tools/monitor.js +226 -0
- package/dist/tools/os.js +2 -2
- package/dist/tools/research.js +154 -0
- package/dist/tools/swarm.js +7 -7
- package/dist/tools/vault.js +12 -8
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/@noelclaw/mcp)
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
81 MCP tools for your AI — persistent memory, autonomous research monitors, live web search, DeFi execution on Base, multi-agent swarms, and on-chain actions.
|
|
6
6
|
|
|
7
7
|
No API key required to start. Ask your AI in plain English.
|
|
8
8
|
|
|
@@ -21,37 +21,7 @@ npx -y @noelclaw/mcp
|
|
|
21
21
|
|
|
22
22
|
### Claude Code
|
|
23
23
|
```bash
|
|
24
|
-
claude mcp add noelclaw -- npx -y @noelclaw/mcp
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
### Aeon
|
|
28
|
-
|
|
29
|
-
Open **Settings → MCP Servers** and add:
|
|
30
|
-
|
|
31
|
-
```json
|
|
32
|
-
{
|
|
33
|
-
"mcpServers": {
|
|
34
|
-
"noelclaw": {
|
|
35
|
-
"command": "npx",
|
|
36
|
-
"args": ["-y", "@noelclaw/mcp"]
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
### Hermes
|
|
43
|
-
|
|
44
|
-
Open **Settings → MCP Servers** and add:
|
|
45
|
-
|
|
46
|
-
```json
|
|
47
|
-
{
|
|
48
|
-
"mcpServers": {
|
|
49
|
-
"noelclaw": {
|
|
50
|
-
"command": "npx",
|
|
51
|
-
"args": ["-y", "@noelclaw/mcp"]
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
}
|
|
24
|
+
claude mcp add noelclaw -s user -- npx -y @noelclaw/mcp
|
|
55
25
|
```
|
|
56
26
|
|
|
57
27
|
### Claude Desktop
|
|
@@ -69,7 +39,7 @@ Add to `%APPDATA%\Claude\claude_desktop_config.json` (Windows) or `~/Library/App
|
|
|
69
39
|
}
|
|
70
40
|
```
|
|
71
41
|
|
|
72
|
-
###
|
|
42
|
+
### Aeon / Hermes / Cursor / Windsurf
|
|
73
43
|
|
|
74
44
|
```json
|
|
75
45
|
{
|
|
@@ -91,27 +61,24 @@ Restart your client. Tools load automatically.
|
|
|
91
61
|
Ask your AI anything — it routes to the right tool automatically.
|
|
92
62
|
|
|
93
63
|
```
|
|
94
|
-
|
|
64
|
+
search for AI agent news today
|
|
65
|
+
→ Returns top results from across the web, summarized
|
|
66
|
+
|
|
67
|
+
set up a daily monitor for AI agents news
|
|
68
|
+
→ ✓ monitor created · runs daily at 8am · findings saved to vault + Telegram
|
|
69
|
+
|
|
70
|
+
what's the ETH gas price on Base right now?
|
|
95
71
|
→ Gas: 0.006 gwei · Block: 46,773,463 · ETH: $1,991
|
|
96
72
|
|
|
97
73
|
show me the best yield vaults on Base right now
|
|
98
74
|
→ Clearstar USDC Reactor: 7.57% APY ($2.6M TVL)
|
|
99
75
|
→ Moonwell Flagship USDC: 4.63% APY ($9.1M TVL)
|
|
100
76
|
|
|
101
|
-
|
|
102
|
-
→
|
|
103
|
-
|
|
104
|
-
set up weekly $100 ETH DCA every Monday
|
|
105
|
-
→ ✓ automation created · runs weekly 09:00 UTC
|
|
77
|
+
give me a bull vs bear thesis on ETH
|
|
78
|
+
→ Full analysis · auto-saved to vault
|
|
106
79
|
|
|
107
80
|
swap 0.5 ETH to USDC on Base
|
|
108
81
|
→ ✓ swapped → 1,842 USDC · tx confirmed in 2s
|
|
109
|
-
|
|
110
|
-
swarm research topic: "ETH vs SOL which wins 2025"
|
|
111
|
-
→ Triggers multi-agent research, saves findings to your vault
|
|
112
|
-
|
|
113
|
-
generate a Solidity ERC-20 with burn and pause features
|
|
114
|
-
→ Returns production-ready OpenZeppelin contract
|
|
115
82
|
```
|
|
116
83
|
|
|
117
84
|
---
|
|
@@ -126,7 +93,7 @@ Three pillars: **Remember** · **Act** · **Know**
|
|
|
126
93
|
|
|
127
94
|
Your AI loads your context before you type a single word.
|
|
128
95
|
|
|
129
|
-
#### Vault — Structured Notes (
|
|
96
|
+
#### Vault — Structured Notes (12 tools)
|
|
130
97
|
|
|
131
98
|
> Save research, decisions, and notes. Every entry is versioned and auto-tagged.
|
|
132
99
|
|
|
@@ -141,14 +108,11 @@ Your AI loads your context before you type a single word.
|
|
|
141
108
|
| **Vault Export** `vault_export` | Export vault as JSON or markdown |
|
|
142
109
|
| **Store Credential** `vault_store_credential` | Securely store an API key or secret |
|
|
143
110
|
| **Get Credential** `vault_get_credential` | Retrieve a stored credential |
|
|
144
|
-
| **Vault Publish** `vault_publish` | Publish an entry as a public note |
|
|
145
|
-
| **Vault Explore** `vault_explore` | Browse by tag or category |
|
|
146
111
|
| **Vault Pin** `vault_pin` | Pin an important entry to the top |
|
|
147
112
|
| **Vault Delete** `vault_delete` | Delete an entry |
|
|
148
|
-
| **Vault Link** `vault_link` | Link two entries together |
|
|
149
113
|
| **Vault Tag** `vault_tag` | Add or update tags on an entry |
|
|
150
114
|
|
|
151
|
-
#### Memory — Semantic Search (
|
|
115
|
+
#### Memory — Semantic Search (9 tools)
|
|
152
116
|
|
|
153
117
|
> Find anything by meaning, not keywords. "What did I say about ETH yield?" just works.
|
|
154
118
|
|
|
@@ -161,6 +125,8 @@ Your AI loads your context before you type a single word.
|
|
|
161
125
|
| **Memory List** `memory_list` | List recent memory entries |
|
|
162
126
|
| **Memory Delete** `memory_delete` | Remove a memory entry |
|
|
163
127
|
| **Memory Insight** `memory_insight` | AI-generated insights from your memory patterns |
|
|
128
|
+
| **Memory Extract** `memory_extract` | Auto-extract discrete facts from any text and save each individually |
|
|
129
|
+
| **Memory Consolidate** `memory_consolidate` | Merge overlapping memories on a topic into one clean summary |
|
|
164
130
|
|
|
165
131
|
#### OS — Session Lifecycle (3 tools)
|
|
166
132
|
|
|
@@ -176,6 +142,16 @@ Your AI loads your context before you type a single word.
|
|
|
176
142
|
|
|
177
143
|
Tell it what to do. It runs — on schedule, on-chain, or right now.
|
|
178
144
|
|
|
145
|
+
#### Autonomous Monitor (3 tools)
|
|
146
|
+
|
|
147
|
+
> Runs research on a schedule with no prompting needed.
|
|
148
|
+
|
|
149
|
+
| Name | Description |
|
|
150
|
+
|------|-------------|
|
|
151
|
+
| **Create Monitor** `create_monitor` | Schedule a recurring research agent for any topic |
|
|
152
|
+
| **List Monitors** `list_monitors` | View all active monitors with schedule and next run |
|
|
153
|
+
| **Cancel Monitor** `cancel_monitor` | Stop a monitor by ID |
|
|
154
|
+
|
|
179
155
|
#### Automations (6 tools)
|
|
180
156
|
|
|
181
157
|
| Name | Description |
|
|
@@ -187,7 +163,7 @@ Tell it what to do. It runs — on schedule, on-chain, or right now.
|
|
|
187
163
|
| **Automation Runs** `get_automation_runs` | Execution history |
|
|
188
164
|
| **Run Now** `run_automation` | Trigger an automation manually |
|
|
189
165
|
|
|
190
|
-
#### DeFi Execution (
|
|
166
|
+
#### DeFi Execution (6 tools)
|
|
191
167
|
|
|
192
168
|
> Transactions are signed client-side — your private key never leaves your machine.
|
|
193
169
|
|
|
@@ -197,7 +173,6 @@ Tell it what to do. It runs — on schedule, on-chain, or right now.
|
|
|
197
173
|
| **Estimate Swap** `estimate_swap` | Get a swap quote via 0x before executing |
|
|
198
174
|
| **Swap Tokens** `swap_tokens` | Execute a token swap on Base |
|
|
199
175
|
| **Send Token** `send_token` | Send ETH or ERC-20 to any address |
|
|
200
|
-
| **Scan Wallet** `scan_wallet` | Analyze a wallet — holdings, activity, risk signals |
|
|
201
176
|
| **Analyze Wallet** `analyze_wallet` | Deep wallet analysis with on-chain patterns |
|
|
202
177
|
| **DeFi Yields** `get_defi_yields` | Find the best yield opportunities on Base |
|
|
203
178
|
|
|
@@ -215,20 +190,17 @@ Tell it what to do. It runs — on schedule, on-chain, or right now.
|
|
|
215
190
|
| Name | Description |
|
|
216
191
|
|------|-------------|
|
|
217
192
|
| **Wallet Address** `get_wallet_address` | Get or generate your MCP wallet address |
|
|
218
|
-
| **Set Telegram** `set_telegram` | Connect Telegram for automation notifications |
|
|
193
|
+
| **Set Telegram** `set_telegram` | Connect Telegram for monitor and automation notifications |
|
|
219
194
|
|
|
220
|
-
#### Playbooks (
|
|
195
|
+
#### Playbooks (3 tools)
|
|
221
196
|
|
|
222
197
|
> Reusable sequences with safety rules applied before any action runs.
|
|
223
198
|
|
|
224
199
|
| Name | Description |
|
|
225
200
|
|------|-------------|
|
|
226
|
-
| **Create Task** `create_task_packet` | Convert intent into a structured task with constraints |
|
|
227
|
-
| **List Tasks** `list_task_packets` | List all task packets |
|
|
228
201
|
| **List Playbooks** `list_playbooks` | Browse available playbooks |
|
|
229
202
|
| **Run Playbook** `run_playbook` | Execute a playbook by ID |
|
|
230
|
-
| **Noel Ledger** `get_noel_ledger` |
|
|
231
|
-
| **Sentinel Rules** `get_sentinel_rules` | View safety rules applied before actions |
|
|
203
|
+
| **Noel Ledger** `get_noel_ledger` | Audit trail of all agent actions |
|
|
232
204
|
|
|
233
205
|
---
|
|
234
206
|
|
|
@@ -236,6 +208,13 @@ Tell it what to do. It runs — on schedule, on-chain, or right now.
|
|
|
236
208
|
|
|
237
209
|
Always informed before you act.
|
|
238
210
|
|
|
211
|
+
#### Web Research (2 tools)
|
|
212
|
+
|
|
213
|
+
| Name | Description |
|
|
214
|
+
|------|-------------|
|
|
215
|
+
| **Web Search** `web_search` | Search the web in real time for any topic |
|
|
216
|
+
| **Web Scrape** `web_scrape` | Read the full content of any URL |
|
|
217
|
+
|
|
239
218
|
#### Market & Prices (5 tools)
|
|
240
219
|
|
|
241
220
|
| Name | Description |
|
|
@@ -260,10 +239,10 @@ Always informed before you act.
|
|
|
260
239
|
| Name | Description |
|
|
261
240
|
|------|-------------|
|
|
262
241
|
| **Ask Noel** `ask_noel` | AI crypto analyst — opinions, trade ideas, market outlook |
|
|
263
|
-
| **Market Thesis** `market_thesis` | Investment thesis for any token or sector |
|
|
264
|
-
| **Trade Plan** `trade_plan` | Structured trade plan with entry, exit, and risk levels |
|
|
242
|
+
| **Market Thesis** `market_thesis` | Investment thesis for any token or sector — auto-saved to vault |
|
|
243
|
+
| **Trade Plan** `trade_plan` | Structured trade plan with entry, exit, and risk levels — auto-saved to vault |
|
|
265
244
|
|
|
266
|
-
#### Agent Network (
|
|
245
|
+
#### Agent Network (8 tools)
|
|
267
246
|
|
|
268
247
|
> Spin up multiple AI agents that research and monitor in parallel.
|
|
269
248
|
|
|
@@ -272,16 +251,9 @@ Always informed before you act.
|
|
|
272
251
|
| **Start Swarm** `start_swarm` | Start the agent network |
|
|
273
252
|
| **Stop Swarm** `stop_swarm` | Stop the active swarm |
|
|
274
253
|
| **Swarm Status** `get_swarm_status` | Status and shared memory snapshot |
|
|
275
|
-
| **Trigger Agent** `trigger_agent` | Run a specific agent now |
|
|
276
|
-
| **Write Memory** `write_swarm_memory` | Write to shared agent memory |
|
|
277
|
-
| **Read Memory** `get_swarm_memory` | Read from shared agent memory |
|
|
278
|
-
| **Execution Scores** `get_execution_scores` | Performance scores across all agents |
|
|
279
254
|
| **Swarm Research** `swarm_research` | Multi-agent research on any topic — saves to vault |
|
|
255
|
+
| **Trigger Agent** `trigger_agent` | Run a specific agent now |
|
|
280
256
|
| **Swarm Brief** `swarm_brief` | Summary of everything the swarm has found |
|
|
281
|
-
| **Broadcast** `swarm_broadcast` | Send a message to all active agents |
|
|
282
|
-
| **Swarm Pulse** `swarm_pulse` | Heartbeat — active agents and last activity |
|
|
283
|
-
| **Swarm Reflect** `swarm_reflect` | Agents self-evaluate what went well |
|
|
284
|
-
| **Swarm Watch** `swarm_watch` | Watch a token or topic for changes |
|
|
285
257
|
| **List Agents** `list_agents` | Browse available specialist agents |
|
|
286
258
|
| **Hire Agent** `hire_agent` | Hire an agent for a specific task |
|
|
287
259
|
|
|
@@ -289,17 +261,15 @@ Always informed before you act.
|
|
|
289
261
|
|
|
290
262
|
### 🛠 BUILD — Developer & Content Tools
|
|
291
263
|
|
|
292
|
-
#### Coder (
|
|
264
|
+
#### Coder (5 tools)
|
|
293
265
|
|
|
294
266
|
| Name | Description |
|
|
295
267
|
|------|-------------|
|
|
296
|
-
| **Scaffold Project** `scaffold_project` | Scaffold a DeFi or Web3 project |
|
|
297
|
-
| **Generate Component** `generate_component` | React component with wagmi/viem |
|
|
298
268
|
| **Generate Contract** `generate_contract` | Solidity smart contract |
|
|
299
269
|
| **Audit Contract** `audit_contract` | Contract vulnerability audit |
|
|
300
270
|
| **Explain Code** `explain_code` | Explain any code in plain English |
|
|
301
|
-
| **Generate MCP Skill** `generate_mcp_skill` | Generate a new MCP tool from plain English |
|
|
302
271
|
| **Review Code** `review_code` | Code review with actionable feedback |
|
|
272
|
+
| **Generate MCP Skill** `generate_mcp_skill` | Generate a new MCP tool from plain English |
|
|
303
273
|
|
|
304
274
|
#### Content & Humanizer (3 tools)
|
|
305
275
|
|
|
@@ -321,9 +291,7 @@ Always informed before you act.
|
|
|
321
291
|
|
|
322
292
|
## Configuration
|
|
323
293
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
For additional capabilities, add to the `env` block in your MCP config:
|
|
294
|
+
All tools work out of the box. Add optional keys for extra features:
|
|
327
295
|
|
|
328
296
|
```json
|
|
329
297
|
{
|
|
@@ -332,8 +300,8 @@ For additional capabilities, add to the `env` block in your MCP config:
|
|
|
332
300
|
"command": "npx",
|
|
333
301
|
"args": ["-y", "@noelclaw/mcp"],
|
|
334
302
|
"env": {
|
|
335
|
-
"
|
|
336
|
-
"
|
|
303
|
+
"FIRECRAWL_API_KEY": "fc-...",
|
|
304
|
+
"TRIGGER_SECRET_KEY": "tr_prod_..."
|
|
337
305
|
}
|
|
338
306
|
}
|
|
339
307
|
}
|
|
@@ -342,10 +310,14 @@ For additional capabilities, add to the `env` block in your MCP config:
|
|
|
342
310
|
|
|
343
311
|
| Variable | Purpose |
|
|
344
312
|
|----------|---------|
|
|
345
|
-
| `
|
|
346
|
-
| `ANTHROPIC_API_KEY` |
|
|
347
|
-
| `
|
|
348
|
-
| `
|
|
313
|
+
| `NOELCLAW_SESSION_TOKEN` | Session token from noelclaw.com — recommended for full access |
|
|
314
|
+
| `ANTHROPIC_API_KEY` | Use your own Claude quota |
|
|
315
|
+
| `BANKR_API_KEY` | Use Bankr/Grok instead of Anthropic |
|
|
316
|
+
| `FIRECRAWL_API_KEY` | Required for `web_search` and `web_scrape` |
|
|
317
|
+
| `TRIGGER_SECRET_KEY` | Required for `create_monitor` |
|
|
318
|
+
| `TELEGRAM_BOT_TOKEN` | Your Telegram bot token — for monitor notifications |
|
|
319
|
+
| `TELEGRAM_CHAT_ID` | Your Telegram chat ID |
|
|
320
|
+
| `ALCHEMY_API_KEY` | Faster swap quotes and Base balance lookups |
|
|
349
321
|
|
|
350
322
|
---
|
|
351
323
|
|
|
@@ -354,9 +326,12 @@ For additional capabilities, add to the `env` block in your MCP config:
|
|
|
354
326
|
| Problem | Fix |
|
|
355
327
|
|---------|-----|
|
|
356
328
|
| Tools not appearing | Restart your MCP client after adding the config |
|
|
357
|
-
|
|
|
358
|
-
|
|
|
359
|
-
|
|
|
329
|
+
| Old version loading | Run `npx clear-npx-cache` then restart |
|
|
330
|
+
| `web_search` fails | Add `FIRECRAWL_API_KEY` to env |
|
|
331
|
+
| `create_monitor` fails | Add `TRIGGER_SECRET_KEY` to env |
|
|
332
|
+
| No Telegram notifications | Add `TELEGRAM_BOT_TOKEN` and `TELEGRAM_CHAT_ID` to env |
|
|
333
|
+
| Swap fails | Check balance with `get_portfolio`, confirm Base mainnet connectivity |
|
|
334
|
+
| Rate limit (429) | Auto-retries up to 3× with backoff — no action needed |
|
|
360
335
|
|
|
361
336
|
---
|
|
362
337
|
|
|
@@ -365,4 +340,4 @@ For additional capabilities, add to the `env` block in your MCP config:
|
|
|
365
340
|
- npm: [npmjs.com/package/@noelclaw/mcp](https://www.npmjs.com/package/@noelclaw/mcp)
|
|
366
341
|
- GitHub: [github.com/noelclaw/mcp](https://github.com/noelclaw/mcp)
|
|
367
342
|
- App: [app.noelclaw.com](https://app.noelclaw.com)
|
|
368
|
-
- Docs: [docs.noelclaw.
|
|
343
|
+
- Docs: [docs.noelclaw.com](https://docs.noelclaw.com)
|
package/dist/agent-loop.js
CHANGED
|
@@ -3,7 +3,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.runAgent = runAgent;
|
|
4
4
|
const server_js_1 = require("./server.js");
|
|
5
5
|
const llm_js_1 = require("./llm.js");
|
|
6
|
-
const
|
|
6
|
+
const convex_js_1 = require("./convex.js");
|
|
7
|
+
const SYSTEM_PROMPT = "You are Noelclaw, a persistent AI with 76 tools covering memory, automations, DeFi execution, research, and code. " +
|
|
7
8
|
"Be concise and direct. Use tools when needed. Summarize tool results in plain English.";
|
|
8
9
|
async function runAgent(userMessage, history, onToolCall) {
|
|
9
10
|
const anthropicKey = process.env.ANTHROPIC_API_KEY;
|
|
@@ -12,9 +13,16 @@ async function runAgent(userMessage, history, onToolCall) {
|
|
|
12
13
|
return runAnthropicLoop(anthropicKey, userMessage, history, onToolCall);
|
|
13
14
|
if (bankrKey)
|
|
14
15
|
return runBankrLoop(bankrKey, userMessage, history, onToolCall);
|
|
15
|
-
//
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
// No direct key — proxy through Noelclaw backend. Wallet auto-creates at ~/.noelclaw/wallet.json
|
|
17
|
+
// on first use and signs requests transparently. No account or config needed.
|
|
18
|
+
try {
|
|
19
|
+
return await runConvexProxiedLoop(userMessage, history, onToolCall);
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
// Network down or backend unavailable — plain chat fallback
|
|
23
|
+
const text = await (0, llm_js_1.callLLM)(SYSTEM_PROMPT, userMessage, 1024, history);
|
|
24
|
+
return { text, toolCalls: [] };
|
|
25
|
+
}
|
|
18
26
|
}
|
|
19
27
|
// ── Anthropic agent loop ─────────────────────────────────────────────────────
|
|
20
28
|
function toAnthropicTool(tool) {
|
|
@@ -80,6 +88,55 @@ async function runAnthropicLoop(apiKey, userMessage, history, onToolCall) {
|
|
|
80
88
|
}
|
|
81
89
|
return { text: "Reached max tool iterations.", toolCalls };
|
|
82
90
|
}
|
|
91
|
+
// ── Convex-proxied Anthropic loop (session token only — platform covers LLM) ──
|
|
92
|
+
async function runConvexProxiedLoop(userMessage, history, onToolCall) {
|
|
93
|
+
const model = process.env.ANTHROPIC_MODEL ?? "claude-haiku-4-5-20251001";
|
|
94
|
+
const tools = server_js_1.ALL_TOOLS.map(toAnthropicTool);
|
|
95
|
+
const toolCalls = [];
|
|
96
|
+
const messages = [
|
|
97
|
+
...history.map(h => ({ role: h.role, content: h.content })),
|
|
98
|
+
{ role: "user", content: userMessage },
|
|
99
|
+
];
|
|
100
|
+
for (let turn = 0; turn < 10; turn++) {
|
|
101
|
+
// callConvex handles wallet/session auth automatically; 90s timeout matches the proxy endpoint
|
|
102
|
+
const data = await (0, convex_js_1.callConvex)("/llm/complete", "POST", {
|
|
103
|
+
model,
|
|
104
|
+
max_tokens: 2048,
|
|
105
|
+
system: SYSTEM_PROMPT,
|
|
106
|
+
tools,
|
|
107
|
+
messages,
|
|
108
|
+
}, "llm_complete", 90000);
|
|
109
|
+
messages.push({ role: "assistant", content: data.content });
|
|
110
|
+
if (data.stop_reason !== "tool_use") {
|
|
111
|
+
const text = data.content
|
|
112
|
+
.filter(b => b.type === "text")
|
|
113
|
+
.map(b => b.text)
|
|
114
|
+
.join("");
|
|
115
|
+
return { text, toolCalls };
|
|
116
|
+
}
|
|
117
|
+
const toolResults = [];
|
|
118
|
+
for (const block of data.content) {
|
|
119
|
+
if (block.type !== "tool_use")
|
|
120
|
+
continue;
|
|
121
|
+
onToolCall(block.name);
|
|
122
|
+
toolCalls.push({ name: block.name });
|
|
123
|
+
let resultText;
|
|
124
|
+
try {
|
|
125
|
+
const handler = server_js_1.HANDLER_MAP.get(block.name);
|
|
126
|
+
if (!handler)
|
|
127
|
+
throw new Error(`Unknown tool: ${block.name}`);
|
|
128
|
+
const result = await handler(block.name, block.input ?? {});
|
|
129
|
+
resultText = result?.content?.[0]?.text ?? "Done.";
|
|
130
|
+
}
|
|
131
|
+
catch (err) {
|
|
132
|
+
resultText = `Error: ${err.message}`;
|
|
133
|
+
}
|
|
134
|
+
toolResults.push({ type: "tool_result", tool_use_id: block.id, content: resultText });
|
|
135
|
+
}
|
|
136
|
+
messages.push({ role: "user", content: toolResults });
|
|
137
|
+
}
|
|
138
|
+
return { text: "Reached max tool iterations.", toolCalls };
|
|
139
|
+
}
|
|
83
140
|
// ── Bankr (OpenAI-compatible) agent loop ─────────────────────────────────────
|
|
84
141
|
function toBankrTool(tool) {
|
|
85
142
|
return {
|
package/dist/cli.js
CHANGED
|
@@ -90,7 +90,7 @@ async function main() {
|
|
|
90
90
|
? `${C.green}Anthropic${C.reset} ${C.dim}(full tool use)${C.reset}`
|
|
91
91
|
: process.env.BANKR_API_KEY
|
|
92
92
|
? `${C.green}Bankr${C.reset} ${C.dim}(full tool use)${C.reset}`
|
|
93
|
-
: `${C.
|
|
93
|
+
: `${C.green}Noelclaw${C.reset} ${C.dim}(full tool use · auto-wallet)${C.reset}`;
|
|
94
94
|
console.log(` ${C.dim}Mode:${C.reset} ${mode}\n`);
|
|
95
95
|
const history = [];
|
|
96
96
|
const rl = readline.createInterface({
|
package/dist/convex.js
CHANGED
|
@@ -19,15 +19,15 @@ exports.PaymentRequiredError = PaymentRequiredError;
|
|
|
19
19
|
function buildPaymentHeader(txHash, requestId) {
|
|
20
20
|
return Buffer.from(`${txHash}:${requestId}`).toString("base64");
|
|
21
21
|
}
|
|
22
|
-
async function attemptConvex(url, method, headers, body) {
|
|
22
|
+
async function attemptConvex(url, method, headers, body, timeoutMs = 30000) {
|
|
23
23
|
return fetch(url, {
|
|
24
24
|
method,
|
|
25
25
|
headers,
|
|
26
26
|
body: body ? JSON.stringify(body) : undefined,
|
|
27
|
-
signal: AbortSignal.timeout(
|
|
27
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
28
28
|
});
|
|
29
29
|
}
|
|
30
|
-
async function callConvex(path, method, body, toolName = "unknown") {
|
|
30
|
+
async function callConvex(path, method, body, toolName = "unknown", timeoutMs = 30000) {
|
|
31
31
|
const url = `${CONVEX_SITE}${path}`;
|
|
32
32
|
const headers = { "Content-Type": "application/json" };
|
|
33
33
|
const apiKey = process.env.NOELCLAW_API_KEY;
|
|
@@ -72,7 +72,7 @@ async function callConvex(path, method, body, toolName = "unknown") {
|
|
|
72
72
|
}
|
|
73
73
|
let res;
|
|
74
74
|
try {
|
|
75
|
-
res = await attemptConvex(url, method, headers, body);
|
|
75
|
+
res = await attemptConvex(url, method, headers, body, timeoutMs);
|
|
76
76
|
}
|
|
77
77
|
catch (err) {
|
|
78
78
|
lastError = err;
|
|
@@ -85,8 +85,8 @@ async function callConvex(path, method, body, toolName = "unknown") {
|
|
|
85
85
|
if (res.status === 401) {
|
|
86
86
|
const b = await res.json().catch(() => ({}));
|
|
87
87
|
throw new Error(`🔑 ${b.message || "Authentication required"}\n\n` +
|
|
88
|
-
`→
|
|
89
|
-
`Hint: ${b.hint ||
|
|
88
|
+
`→ Sign in at: ${b.url || "https://noelclaw.com"}\n\n` +
|
|
89
|
+
`Hint: ${b.hint || 'Add NOELCLAW_SESSION_TOKEN=noel_... to the env block in your MCP config'}\n\n` +
|
|
90
90
|
`${b.alternative ? `Alternative: ${b.alternative}` : ""}`);
|
|
91
91
|
}
|
|
92
92
|
if (RETRY_STATUSES.has(res.status) && attempt < RETRY_DELAYS.length) {
|
package/dist/index.js
CHANGED
|
@@ -35,26 +35,33 @@ async function main() {
|
|
|
35
35
|
const categories = [
|
|
36
36
|
{ label: "Market", count: 5, tools: "get_market_data · get_token_data · compare_tokens · market_overview · token_history" },
|
|
37
37
|
{ label: "Insight", count: 3, tools: "ask_noel · market_thesis · trade_plan" },
|
|
38
|
-
{ label: "DeFi", count: 6, tools: "
|
|
39
|
-
{ label: "Automation", count:
|
|
38
|
+
{ label: "DeFi", count: 6, tools: "get_portfolio · estimate_swap · swap_tokens · send_token · analyze_wallet · get_defi_yields" },
|
|
39
|
+
{ label: "Automation", count: 6, tools: "create · list · pause · delete · runs · run_now" },
|
|
40
|
+
{ label: "Swarm", count: 6, tools: "start_swarm · stop_swarm · get_swarm_status · swarm_research · trigger_agent · swarm_brief" },
|
|
41
|
+
{ label: "Framework", count: 3, tools: "list_playbooks · run_playbook · get_noel_ledger" },
|
|
42
|
+
{ label: "Vault", count: 12, tools: "save · read · list · search · history · diff · export · credential · pin · delete · link · tag" },
|
|
43
|
+
{ label: "Wallet", count: 2, tools: "get_wallet_address · set_telegram" },
|
|
44
|
+
{ label: "MiroShark", count: 3, tools: "simulate · status · stop" },
|
|
40
45
|
{ label: "Scanner", count: 4, tools: "scan_dips · scan_momentum · score_token · check_token" },
|
|
41
46
|
{ label: "Agents", count: 2, tools: "list_agents · hire_agent" },
|
|
42
|
-
{ label: "Swarm", count: 11, tools: "start · stop · status · memory · scores · research · trigger · brief · broadcast · pulse" },
|
|
43
|
-
{ label: "Framework", count: 6, tools: "create_task · list_tasks · list_playbooks · run_playbook · ledger · sentinel" },
|
|
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" },
|
|
46
|
-
{ label: "MiroShark", count: 3, tools: "simulate · status · stop" },
|
|
47
|
-
{ label: "Wallet", count: 2, tools: "get_wallet_address · set_telegram" },
|
|
48
47
|
{ label: "Social", count: 3, tools: "humanize_text · write_thread · write_post" },
|
|
49
|
-
{ label: "Coder", count:
|
|
48
|
+
{ label: "Coder", count: 5, tools: "generate_contract · audit_contract · explain_code · review_code · generate_mcp_skill" },
|
|
50
49
|
{ label: "Base", count: 4, tools: "query_vaults · list_markets · prepare_deposit · chain_stats" },
|
|
50
|
+
{ label: "Memory", count: 9, tools: "add · search · context · profile · list · delete · insight · extract · consolidate" },
|
|
51
|
+
{ label: "OS", count: 3, tools: "noel_boot · noel_status · noel_shutdown" },
|
|
52
|
+
{ label: "Research", count: 2, tools: "web_scrape · web_search" },
|
|
53
|
+
{ label: "Monitor", count: 3, tools: "create_monitor · list_monitors · cancel_monitor" },
|
|
51
54
|
];
|
|
52
55
|
const total = server_js_1.ALL_TOOLS.length;
|
|
53
56
|
divider();
|
|
54
57
|
process.stderr.write(`\n`);
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
+
const aiMode = process.env.ANTHROPIC_API_KEY
|
|
59
|
+
? `Anthropic ${C.dim}claude-haiku-4.5${C.reset}`
|
|
60
|
+
: process.env.BANKR_API_KEY
|
|
61
|
+
? `Bankr ${C.dim}claude-haiku-4.5${C.reset}`
|
|
62
|
+
: `Noelclaw ${C.dim}proxy · auto-auth${C.reset}`;
|
|
63
|
+
line("version", `v3.3.0 ${C.dim}MCP protocol 2.1.0${C.reset}`);
|
|
64
|
+
line("ai", aiMode);
|
|
58
65
|
line("tools", `${C.white}${C.bold}${total} tools loaded${C.reset} ${C.dim}across ${categories.length} categories${C.reset}`);
|
|
59
66
|
process.stderr.write(`\n`);
|
|
60
67
|
divider();
|
package/dist/llm.js
CHANGED
|
@@ -75,7 +75,7 @@ async function callAnthropic(apiKey, systemPrompt, userPrompt, maxTokens, histor
|
|
|
75
75
|
return data.content?.find(b => b.type === "text")?.text ?? "";
|
|
76
76
|
}
|
|
77
77
|
async function callBankr(apiKey, systemPrompt, userPrompt, maxTokens, history, timeoutMs) {
|
|
78
|
-
const model = process.env.BANKR_MODEL ?? "
|
|
78
|
+
const model = process.env.BANKR_MODEL ?? "claude-haiku-4.5";
|
|
79
79
|
const res = await fetch(BANKR_URL, {
|
|
80
80
|
method: "POST",
|
|
81
81
|
headers: { "Content-Type": "application/json", "X-API-Key": apiKey },
|
package/dist/server.js
CHANGED
|
@@ -22,6 +22,8 @@ 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
24
|
const os_js_1 = require("./tools/os.js");
|
|
25
|
+
const research_js_1 = require("./tools/research.js");
|
|
26
|
+
const monitor_js_1 = require("./tools/monitor.js");
|
|
25
27
|
const PRIVATE_KEY_RESPONSE = {
|
|
26
28
|
content: [{
|
|
27
29
|
type: "text",
|
|
@@ -52,7 +54,9 @@ exports.ALL_TOOLS = [
|
|
|
52
54
|
...base_js_1.BASE_TOOLS, // 4 — query_vaults, list_markets, prepare_deposit, chain_stats
|
|
53
55
|
...memory_js_1.MEMORY_TOOLS, // 9 — memory_add, memory_search, memory_context, memory_profile, memory_list, memory_delete, memory_insight, memory_extract, memory_consolidate
|
|
54
56
|
...os_js_1.OS_TOOLS, // 3 — noel_status, noel_boot, noel_shutdown
|
|
55
|
-
//
|
|
57
|
+
...research_js_1.RESEARCH_TOOLS, // 2 — web_scrape, web_search
|
|
58
|
+
...monitor_js_1.MONITOR_TOOLS, // 3 — create_monitor, list_monitors, cancel_monitor
|
|
59
|
+
// total: 81
|
|
56
60
|
];
|
|
57
61
|
exports.HANDLER_MAP = new Map([
|
|
58
62
|
...market_js_1.MARKET_TOOLS.map(t => [t.name, market_js_1.handleMarketTool]),
|
|
@@ -71,8 +75,10 @@ exports.HANDLER_MAP = new Map([
|
|
|
71
75
|
...base_js_1.BASE_TOOLS.map(t => [t.name, base_js_1.handleBaseTool]),
|
|
72
76
|
...memory_js_1.MEMORY_TOOLS.map(t => [t.name, memory_js_1.handleMemoryTool]),
|
|
73
77
|
...os_js_1.OS_TOOLS.map(t => [t.name, os_js_1.handleOsTool]),
|
|
78
|
+
...research_js_1.RESEARCH_TOOLS.map(t => [t.name, research_js_1.handleResearchTool]),
|
|
79
|
+
...monitor_js_1.MONITOR_TOOLS.map(t => [t.name, monitor_js_1.handleMonitorTool]),
|
|
74
80
|
]);
|
|
75
|
-
exports.server = new index_js_1.Server({ name: "noelclaw", version: "3.
|
|
81
|
+
exports.server = new index_js_1.Server({ name: "noelclaw", version: "3.3.0" }, { capabilities: { tools: {} } });
|
|
76
82
|
exports.server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => ({ tools: exports.ALL_TOOLS }));
|
|
77
83
|
exports.server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
|
|
78
84
|
const { name, arguments: args } = request.params;
|
package/dist/tools/defi.js
CHANGED
|
@@ -168,12 +168,6 @@ async function handleDefiTool(name, args) {
|
|
|
168
168
|
if (!result.success)
|
|
169
169
|
return { content: [{ type: "text", text: `Send failed: ${result.error}` }], isError: true };
|
|
170
170
|
const txHash = await (0, wallet_js_1.signAndBroadcast)(wallet, result.txData);
|
|
171
|
-
if (result.feeTxData) {
|
|
172
|
-
try {
|
|
173
|
-
await (0, wallet_js_1.signAndBroadcast)(wallet, result.feeTxData);
|
|
174
|
-
}
|
|
175
|
-
catch { /* non-fatal */ }
|
|
176
|
-
}
|
|
177
171
|
return {
|
|
178
172
|
content: [{
|
|
179
173
|
type: "text",
|
package/dist/tools/insight.js
CHANGED
|
@@ -167,7 +167,20 @@ async function handleInsightTool(name, args) {
|
|
|
167
167
|
try {
|
|
168
168
|
const systemPrompt = await buildSystemPrompt(`${token} ${context ?? ""}`);
|
|
169
169
|
const answer = await (0, llm_js_1.callLLM)(systemPrompt, prompt, 1200);
|
|
170
|
-
|
|
170
|
+
const date = new Date().toISOString().slice(0, 10);
|
|
171
|
+
(0, convex_js_1.callConvex)("/vault/save", "POST", {
|
|
172
|
+
type: "research",
|
|
173
|
+
title: `${token.toUpperCase()} Thesis — ${date}`,
|
|
174
|
+
content: answer,
|
|
175
|
+
key: `thesis/${token.toLowerCase()}-${date}`,
|
|
176
|
+
agentId: "noel",
|
|
177
|
+
tags: ["thesis", token.toLowerCase()],
|
|
178
|
+
commitMsg: "market_thesis auto-save",
|
|
179
|
+
}, "vault_save").catch(() => { });
|
|
180
|
+
const suggest = process.env.TRIGGER_SECRET_KEY
|
|
181
|
+
? `\n\n---\n💡 Want to stay on top of this? Use \`create_monitor\` to get automatic research briefings on ${token.toUpperCase()} delivered on a schedule — no prompting needed.`
|
|
182
|
+
: "";
|
|
183
|
+
return { content: [{ type: "text", text: answer + suggest }] };
|
|
171
184
|
}
|
|
172
185
|
catch (err) {
|
|
173
186
|
return { content: [{ type: "text", text: `market_thesis error: ${err.message}` }], isError: true };
|
|
@@ -228,7 +241,20 @@ async function handleInsightTool(name, args) {
|
|
|
228
241
|
try {
|
|
229
242
|
const systemPrompt = await buildSystemPrompt(`${token} trade ${riskTolerance} ${timeframe ?? ""}`);
|
|
230
243
|
const answer = await (0, llm_js_1.callLLM)(systemPrompt, prompt, 1200);
|
|
231
|
-
|
|
244
|
+
const date = new Date().toISOString().slice(0, 10);
|
|
245
|
+
(0, convex_js_1.callConvex)("/vault/save", "POST", {
|
|
246
|
+
type: "execution",
|
|
247
|
+
title: `Trade Plan: ${token.toUpperCase()} ${side.toUpperCase()} — ${date}`,
|
|
248
|
+
content: answer,
|
|
249
|
+
key: `trade-plan/${token.toLowerCase()}-${side}-${date}`,
|
|
250
|
+
agentId: "noel",
|
|
251
|
+
tags: ["trade-plan", token.toLowerCase(), side],
|
|
252
|
+
commitMsg: "trade_plan auto-save",
|
|
253
|
+
}, "vault_save").catch(() => { });
|
|
254
|
+
const suggest = process.env.TRIGGER_SECRET_KEY
|
|
255
|
+
? `\n\n---\n💡 Want to stay on top of this? Use \`create_monitor\` to get automatic research briefings on ${token.toUpperCase()} delivered on a schedule — no prompting needed.`
|
|
256
|
+
: "";
|
|
257
|
+
return { content: [{ type: "text", text: answer + suggest }] };
|
|
232
258
|
}
|
|
233
259
|
catch (err) {
|
|
234
260
|
return { content: [{ type: "text", text: `trade_plan error: ${err.message}` }], isError: true };
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MONITOR_TOOLS = void 0;
|
|
4
|
+
exports.handleMonitorTool = handleMonitorTool;
|
|
5
|
+
const zod_1 = require("zod");
|
|
6
|
+
const convex_js_1 = require("../convex.js");
|
|
7
|
+
const TRIGGER_BASE = "https://api.trigger.dev/api/v1";
|
|
8
|
+
const MONITOR_TASK_ID = "noelclaw-monitor";
|
|
9
|
+
exports.MONITOR_TOOLS = [
|
|
10
|
+
{
|
|
11
|
+
name: "create_monitor",
|
|
12
|
+
description: "Set up a recurring autonomous monitor — runs on a schedule, researches a topic, saves findings to vault, " +
|
|
13
|
+
"and sends a Telegram notification. No chat needed: the agent runs completely on its own. " +
|
|
14
|
+
"Requires TRIGGER_SECRET_KEY env var (trigger.dev). " +
|
|
15
|
+
"Examples: monitor news on a topic daily, track competitors weekly, get morning briefings.",
|
|
16
|
+
inputSchema: {
|
|
17
|
+
type: "object",
|
|
18
|
+
properties: {
|
|
19
|
+
topic: { type: "string", description: "What to monitor — topic, keyword, or research question" },
|
|
20
|
+
schedule: {
|
|
21
|
+
type: "string",
|
|
22
|
+
description: "Cron expression or preset. Presets: 'daily-8am', 'daily-6pm', 'weekly-monday', 'hourly'. Or raw cron: '0 8 * * *'",
|
|
23
|
+
},
|
|
24
|
+
label: { type: "string", description: "Optional: short label to identify this monitor (e.g. 'morning brief', 'competitor watch')" },
|
|
25
|
+
},
|
|
26
|
+
required: ["topic", "schedule"],
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
name: "list_monitors",
|
|
31
|
+
description: "List all active autonomous monitors — shows topic, schedule, next run, and monitor ID.",
|
|
32
|
+
inputSchema: { type: "object", properties: {}, required: [] },
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
name: "cancel_monitor",
|
|
36
|
+
description: "Cancel and delete an autonomous monitor by its ID. Use list_monitors to get the ID.",
|
|
37
|
+
inputSchema: {
|
|
38
|
+
type: "object",
|
|
39
|
+
properties: {
|
|
40
|
+
id: { type: "string", description: "Monitor ID (from list_monitors)" },
|
|
41
|
+
},
|
|
42
|
+
required: ["id"],
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
];
|
|
46
|
+
const CRON_PRESETS = {
|
|
47
|
+
"daily-8am": "0 8 * * *",
|
|
48
|
+
"daily-6pm": "0 18 * * *",
|
|
49
|
+
"weekly-monday": "0 8 * * 1",
|
|
50
|
+
"hourly": "0 * * * *",
|
|
51
|
+
};
|
|
52
|
+
const CreateSchema = zod_1.z.object({
|
|
53
|
+
topic: zod_1.z.string().min(1).max(200),
|
|
54
|
+
schedule: zod_1.z.string().min(1),
|
|
55
|
+
label: zod_1.z.string().max(80).optional(),
|
|
56
|
+
});
|
|
57
|
+
const CancelSchema = zod_1.z.object({ id: zod_1.z.string().min(1) });
|
|
58
|
+
function getKey() {
|
|
59
|
+
return process.env.TRIGGER_SECRET_KEY ?? null;
|
|
60
|
+
}
|
|
61
|
+
function noKeyMsg() {
|
|
62
|
+
return {
|
|
63
|
+
content: [{
|
|
64
|
+
type: "text",
|
|
65
|
+
text: [
|
|
66
|
+
`⚠️ **TRIGGER_SECRET_KEY not set.**`,
|
|
67
|
+
``,
|
|
68
|
+
`To enable autonomous monitors:`,
|
|
69
|
+
`1. Sign up at trigger.dev (free tier available)`,
|
|
70
|
+
`2. Create a project, go to API Keys → copy your Secret Key`,
|
|
71
|
+
`3. Add to your MCP config env block: \`"TRIGGER_SECRET_KEY": "tr_prod_..."\``,
|
|
72
|
+
`4. In the noelclaw worker directory, run: \`npx trigger.dev@latest deploy\``,
|
|
73
|
+
``,
|
|
74
|
+
`Then monitors will run autonomously — no chat needed.`,
|
|
75
|
+
].join("\n"),
|
|
76
|
+
}],
|
|
77
|
+
isError: true,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
function resolveCron(input) {
|
|
81
|
+
return CRON_PRESETS[input] ?? input;
|
|
82
|
+
}
|
|
83
|
+
async function handleMonitorTool(name, args) {
|
|
84
|
+
if (name === "create_monitor") {
|
|
85
|
+
const parsed = CreateSchema.safeParse(args);
|
|
86
|
+
if (!parsed.success)
|
|
87
|
+
return { content: [{ type: "text", text: `Invalid input: ${parsed.error.issues[0].message}` }], isError: true };
|
|
88
|
+
const key = getKey();
|
|
89
|
+
if (!key)
|
|
90
|
+
return noKeyMsg();
|
|
91
|
+
const { topic, schedule, label } = parsed.data;
|
|
92
|
+
const cron = resolveCron(schedule);
|
|
93
|
+
// Unique stable ID — worker reads config from vault using this as key
|
|
94
|
+
const externalId = `monitor-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 7)}`;
|
|
95
|
+
try {
|
|
96
|
+
const res = await fetch(`${TRIGGER_BASE}/schedules`, {
|
|
97
|
+
method: "POST",
|
|
98
|
+
headers: { "Content-Type": "application/json", "Authorization": `Bearer ${key}` },
|
|
99
|
+
body: JSON.stringify({ task: MONITOR_TASK_ID, cron, externalId, deduplicationKey: externalId }),
|
|
100
|
+
signal: AbortSignal.timeout(10000),
|
|
101
|
+
});
|
|
102
|
+
if (!res.ok) {
|
|
103
|
+
const err = await res.text();
|
|
104
|
+
return { content: [{ type: "text", text: `Failed to create monitor: ${err}` }], isError: true };
|
|
105
|
+
}
|
|
106
|
+
const data = await res.json();
|
|
107
|
+
const scheduleId = data.id ?? externalId;
|
|
108
|
+
// Save config to vault so the worker can retrieve topic + metadata
|
|
109
|
+
let configSaved = true;
|
|
110
|
+
try {
|
|
111
|
+
await (0, convex_js_1.callConvex)("/vault/save", "POST", {
|
|
112
|
+
type: "workflow",
|
|
113
|
+
title: `Monitor: ${label ?? topic}`,
|
|
114
|
+
content: JSON.stringify({ topic, label: label ?? topic, cron, scheduleId, externalId }),
|
|
115
|
+
key: `monitor-config/${externalId}`,
|
|
116
|
+
agentId: "os",
|
|
117
|
+
tags: ["monitor-config"],
|
|
118
|
+
commitMsg: "create_monitor config",
|
|
119
|
+
}, "vault_save");
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
configSaved = false;
|
|
123
|
+
}
|
|
124
|
+
return {
|
|
125
|
+
content: [{
|
|
126
|
+
type: "text",
|
|
127
|
+
text: [
|
|
128
|
+
`✅ **Monitor created**`,
|
|
129
|
+
``,
|
|
130
|
+
`📌 Topic: ${topic}`,
|
|
131
|
+
`🕐 Schedule: ${cron}${CRON_PRESETS[schedule] ? ` (${schedule})` : ""}`,
|
|
132
|
+
`🆔 ID: ${scheduleId}`,
|
|
133
|
+
data.nextRun ? `⏭️ Next run: ${new Date(data.nextRun).toUTCString()}` : "",
|
|
134
|
+
``,
|
|
135
|
+
configSaved
|
|
136
|
+
? `The agent will research "${topic}" on schedule, save findings to vault, and send a Telegram notification if configured.`
|
|
137
|
+
: `⚠️ Monitor schedule created but config save failed — the agent may use a default topic on first run. Try \`cancel_monitor\` and recreate.`,
|
|
138
|
+
`Use \`list_monitors\` to see all active monitors.`,
|
|
139
|
+
].filter(Boolean).join("\n"),
|
|
140
|
+
}],
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
catch (err) {
|
|
144
|
+
return { content: [{ type: "text", text: `Monitor error: ${err.message}` }], isError: true };
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
if (name === "list_monitors") {
|
|
148
|
+
const key = getKey();
|
|
149
|
+
if (!key)
|
|
150
|
+
return noKeyMsg();
|
|
151
|
+
try {
|
|
152
|
+
const res = await fetch(`${TRIGGER_BASE}/schedules`, {
|
|
153
|
+
headers: { "Authorization": `Bearer ${key}` },
|
|
154
|
+
signal: AbortSignal.timeout(10000),
|
|
155
|
+
});
|
|
156
|
+
if (!res.ok) {
|
|
157
|
+
const err = await res.text();
|
|
158
|
+
return { content: [{ type: "text", text: `Failed to list monitors: ${err}` }], isError: true };
|
|
159
|
+
}
|
|
160
|
+
const data = await res.json();
|
|
161
|
+
const schedules = (data.data ?? []).filter((s) => (s.task ?? s.taskIdentifier) === MONITOR_TASK_ID);
|
|
162
|
+
if (!schedules.length) {
|
|
163
|
+
return {
|
|
164
|
+
content: [{
|
|
165
|
+
type: "text",
|
|
166
|
+
text: `No active monitors.\n\nUse \`create_monitor\` to set up an autonomous agent that runs on a schedule.`,
|
|
167
|
+
}],
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
// Load topic labels from vault for each schedule
|
|
171
|
+
const configMap = new Map();
|
|
172
|
+
await Promise.allSettled(schedules
|
|
173
|
+
.filter(s => s.externalId)
|
|
174
|
+
.map(async (s) => {
|
|
175
|
+
try {
|
|
176
|
+
const cfg = await (0, convex_js_1.callConvex)(`/vault/read?key=monitor-config/${s.externalId}`, "GET", undefined, "vault_read");
|
|
177
|
+
if (cfg?.content) {
|
|
178
|
+
const parsed = JSON.parse(cfg.content);
|
|
179
|
+
configMap.set(s.externalId, { topic: parsed.topic, label: parsed.label ?? parsed.topic });
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
catch { /* skip — show raw externalId */ }
|
|
183
|
+
}));
|
|
184
|
+
const lines = [`📋 **Active Monitors** — ${schedules.length} running\n`];
|
|
185
|
+
for (const s of schedules) {
|
|
186
|
+
const cfg = s.externalId ? configMap.get(s.externalId) : undefined;
|
|
187
|
+
const label = cfg?.label ?? s.externalId ?? s.id;
|
|
188
|
+
const topic = cfg?.topic ? ` — ${cfg.topic}` : "";
|
|
189
|
+
const next = s.nextRun ? new Date(s.nextRun).toUTCString() : "unknown";
|
|
190
|
+
lines.push(`**${label}**${topic}`);
|
|
191
|
+
lines.push(` ID: \`${s.id}\` · Cron: \`${s.cron}\` · Next: ${next}`);
|
|
192
|
+
lines.push("");
|
|
193
|
+
}
|
|
194
|
+
lines.push(`Use \`cancel_monitor id: "<id>"\` to stop a monitor.`);
|
|
195
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
196
|
+
}
|
|
197
|
+
catch (err) {
|
|
198
|
+
return { content: [{ type: "text", text: `List error: ${err.message}` }], isError: true };
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
if (name === "cancel_monitor") {
|
|
202
|
+
const parsed = CancelSchema.safeParse(args);
|
|
203
|
+
if (!parsed.success)
|
|
204
|
+
return { content: [{ type: "text", text: `Invalid input: ${parsed.error.issues[0].message}` }], isError: true };
|
|
205
|
+
const key = getKey();
|
|
206
|
+
if (!key)
|
|
207
|
+
return noKeyMsg();
|
|
208
|
+
const { id } = parsed.data;
|
|
209
|
+
try {
|
|
210
|
+
const res = await fetch(`${TRIGGER_BASE}/schedules/${id}`, {
|
|
211
|
+
method: "DELETE",
|
|
212
|
+
headers: { "Authorization": `Bearer ${key}` },
|
|
213
|
+
signal: AbortSignal.timeout(10000),
|
|
214
|
+
});
|
|
215
|
+
if (!res.ok) {
|
|
216
|
+
const err = await res.text();
|
|
217
|
+
return { content: [{ type: "text", text: `Failed to cancel monitor: ${err}` }], isError: true };
|
|
218
|
+
}
|
|
219
|
+
return { content: [{ type: "text", text: `✅ Monitor \`${id}\` cancelled.` }] };
|
|
220
|
+
}
|
|
221
|
+
catch (err) {
|
|
222
|
+
return { content: [{ type: "text", text: `Cancel error: ${err.message}` }], isError: true };
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
return null;
|
|
226
|
+
}
|
package/dist/tools/os.js
CHANGED
|
@@ -99,7 +99,7 @@ async function handleOsTool(name, args) {
|
|
|
99
99
|
lines.push("");
|
|
100
100
|
}
|
|
101
101
|
lines.push(swarmActive
|
|
102
|
-
? `💡 Run \`
|
|
102
|
+
? `💡 Run \`get_swarm_status\` for a live snapshot from all agents.`
|
|
103
103
|
: `💡 Run \`noel_boot\` to wake up the full system.`);
|
|
104
104
|
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
105
105
|
}
|
|
@@ -169,7 +169,7 @@ async function handleOsTool(name, args) {
|
|
|
169
169
|
lines.push("");
|
|
170
170
|
}
|
|
171
171
|
lines.push(`**Quick actions:**`);
|
|
172
|
-
lines.push(` • \`
|
|
172
|
+
lines.push(` • \`get_swarm_status\` — live readings from all agents`);
|
|
173
173
|
if (focus) {
|
|
174
174
|
lines.push(` • \`swarm_research topic: "${focus}"\` — deep research session`);
|
|
175
175
|
lines.push(` • \`memory_insight topic: "${focus}"\` — full intelligence report`);
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RESEARCH_TOOLS = void 0;
|
|
4
|
+
exports.handleResearchTool = handleResearchTool;
|
|
5
|
+
const zod_1 = require("zod");
|
|
6
|
+
const FC_BASE = "https://api.firecrawl.dev/v1";
|
|
7
|
+
exports.RESEARCH_TOOLS = [
|
|
8
|
+
{
|
|
9
|
+
name: "web_scrape",
|
|
10
|
+
description: "Fetch and extract clean readable content from any URL — returns markdown. " +
|
|
11
|
+
"Use when an agent needs to read an article, docs page, GitHub repo, or any web page. " +
|
|
12
|
+
"Set FIRECRAWL_API_KEY for best quality (firecrawl.dev). Falls back to basic fetch if not set.",
|
|
13
|
+
inputSchema: {
|
|
14
|
+
type: "object",
|
|
15
|
+
properties: {
|
|
16
|
+
url: { type: "string", description: "URL to fetch" },
|
|
17
|
+
focus: { type: "string", description: "Optional: specific topic or section to extract from the page" },
|
|
18
|
+
},
|
|
19
|
+
required: ["url"],
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
name: "web_search",
|
|
24
|
+
description: "Search the web and get clean results: titles, URLs, and content snippets. " +
|
|
25
|
+
"Use for research on any topic, finding recent news, or gathering live information. " +
|
|
26
|
+
"Requires FIRECRAWL_API_KEY (firecrawl.dev).",
|
|
27
|
+
inputSchema: {
|
|
28
|
+
type: "object",
|
|
29
|
+
properties: {
|
|
30
|
+
query: { type: "string", description: "Search query" },
|
|
31
|
+
limit: { type: "number", description: "Max results (default 5, max 10)" },
|
|
32
|
+
},
|
|
33
|
+
required: ["query"],
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
];
|
|
37
|
+
const ScrapeSchema = zod_1.z.object({
|
|
38
|
+
url: zod_1.z.string().url(),
|
|
39
|
+
focus: zod_1.z.string().optional(),
|
|
40
|
+
});
|
|
41
|
+
const SearchSchema = zod_1.z.object({
|
|
42
|
+
query: zod_1.z.string().min(1),
|
|
43
|
+
limit: zod_1.z.number().int().min(1).max(10).optional(),
|
|
44
|
+
});
|
|
45
|
+
async function firecrawlScrape(url) {
|
|
46
|
+
const key = process.env.FIRECRAWL_API_KEY;
|
|
47
|
+
if (!key)
|
|
48
|
+
return null;
|
|
49
|
+
try {
|
|
50
|
+
const res = await fetch(`${FC_BASE}/scrape`, {
|
|
51
|
+
method: "POST",
|
|
52
|
+
headers: { "Content-Type": "application/json", "Authorization": `Bearer ${key}` },
|
|
53
|
+
body: JSON.stringify({ url, formats: ["markdown"], onlyMainContent: true }),
|
|
54
|
+
signal: AbortSignal.timeout(20000),
|
|
55
|
+
});
|
|
56
|
+
if (!res.ok)
|
|
57
|
+
return null;
|
|
58
|
+
const data = await res.json();
|
|
59
|
+
return data.data?.markdown ?? null;
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
async function basicFetch(url) {
|
|
66
|
+
try {
|
|
67
|
+
const res = await fetch(url, {
|
|
68
|
+
headers: { "User-Agent": "Mozilla/5.0 (compatible; NoelclawBot/1.0)" },
|
|
69
|
+
signal: AbortSignal.timeout(10000),
|
|
70
|
+
});
|
|
71
|
+
if (!res.ok)
|
|
72
|
+
return null;
|
|
73
|
+
const html = await res.text();
|
|
74
|
+
return html
|
|
75
|
+
.replace(/<script[\s\S]*?<\/script>/gi, "")
|
|
76
|
+
.replace(/<style[\s\S]*?<\/style>/gi, "")
|
|
77
|
+
.replace(/<[^>]+>/g, " ")
|
|
78
|
+
.replace(/\s+/g, " ")
|
|
79
|
+
.trim()
|
|
80
|
+
.slice(0, 4000);
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
async function handleResearchTool(name, args) {
|
|
87
|
+
if (name === "web_scrape") {
|
|
88
|
+
const parsed = ScrapeSchema.safeParse(args);
|
|
89
|
+
if (!parsed.success)
|
|
90
|
+
return { content: [{ type: "text", text: `Invalid input: ${parsed.error.issues[0].message}` }], isError: true };
|
|
91
|
+
const { url, focus } = parsed.data;
|
|
92
|
+
let content = await firecrawlScrape(url);
|
|
93
|
+
const source = content ? "Firecrawl" : "basic fetch";
|
|
94
|
+
if (!content)
|
|
95
|
+
content = await basicFetch(url);
|
|
96
|
+
if (!content) {
|
|
97
|
+
return { content: [{ type: "text", text: `Could not fetch ${url} — page may require JavaScript or block crawlers.` }], isError: true };
|
|
98
|
+
}
|
|
99
|
+
const focusNote = focus ? `\n\n_Focus: ${focus}_\n\n` : "\n\n";
|
|
100
|
+
const body = content.length > 6000 ? content.slice(0, 6000) + "\n\n…(truncated)" : content;
|
|
101
|
+
return { content: [{ type: "text", text: `**${url}**${focusNote}${body}\n\n_Source: ${source}_` }] };
|
|
102
|
+
}
|
|
103
|
+
if (name === "web_search") {
|
|
104
|
+
const parsed = SearchSchema.safeParse(args);
|
|
105
|
+
if (!parsed.success)
|
|
106
|
+
return { content: [{ type: "text", text: `Invalid input: ${parsed.error.issues[0].message}` }], isError: true };
|
|
107
|
+
const { query, limit = 5 } = parsed.data;
|
|
108
|
+
const key = process.env.FIRECRAWL_API_KEY;
|
|
109
|
+
if (!key) {
|
|
110
|
+
return {
|
|
111
|
+
content: [{
|
|
112
|
+
type: "text",
|
|
113
|
+
text: [
|
|
114
|
+
`web_search requires FIRECRAWL_API_KEY.`,
|
|
115
|
+
``,
|
|
116
|
+
`Get a free key at firecrawl.dev, then add to your MCP config:`,
|
|
117
|
+
`\`"env": { "FIRECRAWL_API_KEY": "fc-..." }\``,
|
|
118
|
+
].join("\n"),
|
|
119
|
+
}],
|
|
120
|
+
isError: true,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
try {
|
|
124
|
+
const res = await fetch(`${FC_BASE}/search`, {
|
|
125
|
+
method: "POST",
|
|
126
|
+
headers: { "Content-Type": "application/json", "Authorization": `Bearer ${key}` },
|
|
127
|
+
body: JSON.stringify({ query, limit }),
|
|
128
|
+
signal: AbortSignal.timeout(15000),
|
|
129
|
+
});
|
|
130
|
+
if (!res.ok) {
|
|
131
|
+
const err = await res.text();
|
|
132
|
+
return { content: [{ type: "text", text: `Search failed: ${err}` }], isError: true };
|
|
133
|
+
}
|
|
134
|
+
const data = await res.json();
|
|
135
|
+
const results = data.data ?? [];
|
|
136
|
+
if (!results.length) {
|
|
137
|
+
return { content: [{ type: "text", text: `No results found for: "${query}"` }] };
|
|
138
|
+
}
|
|
139
|
+
const lines = [`🔍 **Web Search: "${query}"** — ${results.length} results\n`];
|
|
140
|
+
for (const r of results) {
|
|
141
|
+
lines.push(`**${r.title ?? r.url}**`);
|
|
142
|
+
lines.push(r.url);
|
|
143
|
+
if (r.description)
|
|
144
|
+
lines.push(r.description.slice(0, 200));
|
|
145
|
+
lines.push("");
|
|
146
|
+
}
|
|
147
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
148
|
+
}
|
|
149
|
+
catch (err) {
|
|
150
|
+
return { content: [{ type: "text", text: `Search error: ${err.message}` }], isError: true };
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return null;
|
|
154
|
+
}
|
package/dist/tools/swarm.js
CHANGED
|
@@ -102,10 +102,9 @@ const TriggerAgentSchema = zod_1.z.object({
|
|
|
102
102
|
agentId: zod_1.z.enum(["market-monitor", "sentiment-tracker", "memory-manager", "risk-verifier", "workflow-executor", "onchain-analyst", "news-aggregator"]),
|
|
103
103
|
params: zod_1.z.record(zod_1.z.string(), zod_1.z.any()).optional(),
|
|
104
104
|
});
|
|
105
|
-
const NO_KEY_MSG =
|
|
106
|
-
|
|
107
|
-
`
|
|
108
|
-
` "env": { "NOELCLAW_API_KEY": "noel_sk_..." }`;
|
|
105
|
+
const NO_KEY_MSG = `⚠️ Swarm auth failed.\n\n` +
|
|
106
|
+
`Your local wallet auto-generates on first use at ~/.noelclaw/wallet.json — no API key needed.\n\n` +
|
|
107
|
+
`Try restarting your MCP client. If the error persists, run \`get_wallet_address\` to confirm the wallet is initialised.`;
|
|
109
108
|
function swarmAuthError(err) {
|
|
110
109
|
const msg = err instanceof Error ? err.message : String(err);
|
|
111
110
|
const isAuth = msg.includes("🔑") || msg.includes("Authentication") || msg.includes("401");
|
|
@@ -240,7 +239,7 @@ async function handleSwarmTool(name, args) {
|
|
|
240
239
|
``,
|
|
241
240
|
data.message ?? "Research triggered. Findings will appear in vault automatically.",
|
|
242
241
|
``,
|
|
243
|
-
`**Next:** \`
|
|
242
|
+
`**Next:** \`memory_consolidate topic: "${topic}"\` to merge findings into one summary`,
|
|
244
243
|
`Or: \`memory_insight topic: "${topic}"\` to see full intelligence report`,
|
|
245
244
|
].join("\n"),
|
|
246
245
|
}],
|
|
@@ -279,7 +278,7 @@ async function handleSwarmTool(name, args) {
|
|
|
279
278
|
resultText,
|
|
280
279
|
``,
|
|
281
280
|
`Findings saved to vault automatically.`,
|
|
282
|
-
`Use \`
|
|
281
|
+
`Use \`memory_consolidate topic: "${contextQuery.split(" ")[1] ?? agentId}"\` to merge findings, or \`memory_insight\` for full report.`,
|
|
283
282
|
].filter(Boolean).join("\n"),
|
|
284
283
|
}],
|
|
285
284
|
};
|
|
@@ -299,7 +298,8 @@ async function handleSwarmTool(name, args) {
|
|
|
299
298
|
}
|
|
300
299
|
if (data.error)
|
|
301
300
|
return { content: [{ type: "text", text: `Error: ${data.error}` }], isError: true };
|
|
302
|
-
const
|
|
301
|
+
const SWARM_AGENTS = new Set(["market-monitor", "sentiment-tracker", "onchain-analyst", "news-aggregator", "risk-verifier", "memory-manager", "workflow-executor"]);
|
|
302
|
+
const entries = (data.entries ?? []).filter((e) => e.agentId && SWARM_AGENTS.has(e.agentId));
|
|
303
303
|
if (!entries.length) {
|
|
304
304
|
return { content: [{ type: "text", text: `No swarm research in vault yet.\nStart the swarm with \`start_swarm\` or run \`swarm_research topic: "BTC"\` to build your knowledge base.` }] };
|
|
305
305
|
}
|
package/dist/tools/vault.js
CHANGED
|
@@ -18,7 +18,7 @@ exports.VAULT_TOOLS = [
|
|
|
18
18
|
type: "object",
|
|
19
19
|
properties: {
|
|
20
20
|
type: { type: "string", enum: [...VAULT_TYPES], description: "Entry type" },
|
|
21
|
-
title: { type: "string", description: "Human-readable title" },
|
|
21
|
+
title: { type: "string", description: "Human-readable title (auto-generated from content if omitted)" },
|
|
22
22
|
content: { type: "string", description: "Main content — markdown, JSON, code, or plain text" },
|
|
23
23
|
key: { type: "string", description: "Optional slug key e.g. 'research/btc-dominance-analysis'. Auto-generated if omitted." },
|
|
24
24
|
contentType: { type: "string", enum: ["markdown", "json", "text", "code"], description: "Content format hint" },
|
|
@@ -27,7 +27,7 @@ exports.VAULT_TOOLS = [
|
|
|
27
27
|
commitMsg: { type: "string", description: "Commit message for this version, e.g. 'initial research', 'refined with on-chain data'" },
|
|
28
28
|
metadata: { type: "string", description: "Optional JSON string for extra structured fields" },
|
|
29
29
|
},
|
|
30
|
-
required: ["type", "
|
|
30
|
+
required: ["type", "content"],
|
|
31
31
|
},
|
|
32
32
|
},
|
|
33
33
|
{
|
|
@@ -179,7 +179,7 @@ exports.VAULT_TOOLS = [
|
|
|
179
179
|
// ─── Zod schemas ─────────────────────────────────────────────────────────────
|
|
180
180
|
const SaveSchema = zod_1.z.object({
|
|
181
181
|
type: zod_1.z.enum(VAULT_TYPES),
|
|
182
|
-
title: zod_1.z.string().
|
|
182
|
+
title: zod_1.z.string().optional(),
|
|
183
183
|
content: zod_1.z.string().min(1),
|
|
184
184
|
key: zod_1.z.string().optional(),
|
|
185
185
|
contentType: zod_1.z.enum(["markdown", "json", "text", "code"]).optional(),
|
|
@@ -228,15 +228,19 @@ async function handleVaultTool(name, args) {
|
|
|
228
228
|
const parsed = SaveSchema.safeParse(args);
|
|
229
229
|
if (!parsed.success)
|
|
230
230
|
return { content: [{ type: "text", text: `Invalid input: ${parsed.error.issues[0].message}` }], isError: true };
|
|
231
|
-
|
|
231
|
+
// Auto-generate title from content if not provided
|
|
232
|
+
const firstLine = parsed.data.content.split("\n")[0].replace(/^#+\s*/, "").slice(0, 80);
|
|
233
|
+
const autoTitle = parsed.data.title ?? (firstLine || `${parsed.data.type} — ${new Date().toISOString().slice(0, 10)}`);
|
|
234
|
+
const savePayload = { ...parsed.data, title: autoTitle };
|
|
235
|
+
const data = await (0, convex_js_1.callConvex)("/vault/save", "POST", savePayload, "vault_save");
|
|
232
236
|
if (data.error)
|
|
233
237
|
return { content: [{ type: "text", text: `Error: ${data.error}` }], isError: true };
|
|
234
238
|
const { key, version, changed } = data;
|
|
235
239
|
// Mirror to semantic memory (fire-and-forget)
|
|
236
|
-
if (
|
|
237
|
-
(0, memory_js_1.syncToSupermemory)(
|
|
238
|
-
vaultKey: key, title:
|
|
239
|
-
tags:
|
|
240
|
+
if (savePayload.type !== "credential") {
|
|
241
|
+
(0, memory_js_1.syncToSupermemory)(savePayload.content, {
|
|
242
|
+
vaultKey: key, title: autoTitle, type: savePayload.type,
|
|
243
|
+
tags: savePayload.tags, version, source: "vault_save",
|
|
240
244
|
});
|
|
241
245
|
}
|
|
242
246
|
const lines = [
|
package/package.json
CHANGED