@noelclaw/mcp 1.0.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/LICENSE +21 -0
- package/README.md +246 -0
- package/dist/convex.js +107 -0
- package/dist/index.js +31 -0
- package/dist/server.js +101 -0
- package/dist/tools/automation.js +116 -0
- package/dist/tools/defi.js +241 -0
- package/dist/tools/framework.js +259 -0
- package/dist/tools/humanizer.js +120 -0
- package/dist/tools/insight.js +59 -0
- package/dist/tools/market.js +229 -0
- package/dist/tools/miroshark.js +155 -0
- package/dist/tools/news.js +99 -0
- package/dist/tools/research.js +29 -0
- package/dist/tools/swarm.js +162 -0
- package/dist/tools/twitter.js +67 -0
- package/dist/tools/vault.js +293 -0
- package/dist/tools/wallet.js +99 -0
- package/dist/types.js +2 -0
- package/dist/wallet.js +137 -0
- package/package.json +56 -0
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DEFI_TOOLS = void 0;
|
|
4
|
+
exports.handleDefiTool = handleDefiTool;
|
|
5
|
+
const zod_1 = require("zod");
|
|
6
|
+
const convex_js_1 = require("../convex.js");
|
|
7
|
+
const wallet_js_1 = require("../wallet.js");
|
|
8
|
+
exports.DEFI_TOOLS = [
|
|
9
|
+
{
|
|
10
|
+
name: "get_portfolio",
|
|
11
|
+
description: "Get your Base wallet address and full token portfolio including all token balances with USD values. Auto-creates a secure encrypted wallet on first use.",
|
|
12
|
+
inputSchema: { type: "object", properties: {}, required: [] },
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
name: "swap_tokens",
|
|
16
|
+
description: "Swap tokens on Base mainnet via 0x Permit2. Supported: ETH, USDC, USDT, DAI, WETH. Amount is human-readable. Signed and broadcast locally from your wallet.",
|
|
17
|
+
inputSchema: {
|
|
18
|
+
type: "object",
|
|
19
|
+
properties: {
|
|
20
|
+
fromToken: { type: "string", description: "Token to sell: ETH, USDC, USDT, DAI, WETH" },
|
|
21
|
+
toToken: { type: "string", description: "Token to buy: ETH, USDC, USDT, DAI, WETH" },
|
|
22
|
+
amount: { type: "string", description: "Amount to swap. Human-readable (e.g. '0.001') or percentage of balance (e.g. '50%', '100%')" },
|
|
23
|
+
},
|
|
24
|
+
required: ["fromToken", "toToken", "amount"],
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
name: "send_token",
|
|
29
|
+
description: "Send ETH or ERC-20 tokens (USDC, USDT, DAI, WETH) to any address on Base mainnet. Signed and broadcast locally from your wallet.",
|
|
30
|
+
inputSchema: {
|
|
31
|
+
type: "object",
|
|
32
|
+
properties: {
|
|
33
|
+
token: { type: "string", description: "Token to send: ETH, USDC, USDT, DAI, WETH" },
|
|
34
|
+
toAddress: { type: "string", description: "Destination address (0x...)" },
|
|
35
|
+
amount: { type: "string", description: "Human-readable amount" },
|
|
36
|
+
},
|
|
37
|
+
required: ["token", "toAddress", "amount"],
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
name: "deploy_token",
|
|
42
|
+
description: "Launch a new memecoin on Base via Flaunch. Deploys with fair launch period, sets your revenue share (default 80% of swap fees), and returns a Memestream NFT that earns ETH from every swap forever. Image must be a publicly accessible URL.",
|
|
43
|
+
inputSchema: {
|
|
44
|
+
type: "object",
|
|
45
|
+
properties: {
|
|
46
|
+
name: { type: "string", description: "Token name e.g. 'Pepe Noel'" },
|
|
47
|
+
symbol: { type: "string", description: "Ticker 3-6 chars e.g. 'PNOEL'" },
|
|
48
|
+
imageUrl: { type: "string", description: "Public image URL for the token" },
|
|
49
|
+
description: { type: "string", description: "Token description (optional)" },
|
|
50
|
+
initialMarketCapUSD: { type: "number", description: "Starting mcap in USD (default: 10000, min: 1000)" },
|
|
51
|
+
creatorFeePercent: { type: "number", description: "Your % of swap fees (default: 80, max: 100)" },
|
|
52
|
+
preminePercent: { type: "number", description: "% of supply to premine at launch (default: 0)" },
|
|
53
|
+
fairLaunchDurationMinutes: { type: "number", description: "Fair launch window in minutes (default: 30)" },
|
|
54
|
+
},
|
|
55
|
+
required: ["name", "symbol", "imageUrl"],
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
name: "claim_fees",
|
|
60
|
+
description: "Claim accumulated ETH from your Flaunch token swap fees. Calls claim() on the Flaunch PositionManager — pulls all pending ETH from your deployed tokens to your wallet.",
|
|
61
|
+
inputSchema: {
|
|
62
|
+
type: "object",
|
|
63
|
+
properties: {},
|
|
64
|
+
required: [],
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
name: "mint_nft",
|
|
69
|
+
description: "Auto-mint any NFT on Base. Pass the mint page URL or contract address. " +
|
|
70
|
+
"Noel detects the contract, checks your eligibility and balance, " +
|
|
71
|
+
"then mints directly from your wallet. Works with OpenSea, Zora, Highlight, and most Base NFT projects.",
|
|
72
|
+
inputSchema: {
|
|
73
|
+
type: "object",
|
|
74
|
+
properties: {
|
|
75
|
+
mintUrl: {
|
|
76
|
+
type: "string",
|
|
77
|
+
description: "NFT mint URL (OpenSea, Zora, Highlight) or raw contract address (0x...)",
|
|
78
|
+
},
|
|
79
|
+
quantity: {
|
|
80
|
+
type: "number",
|
|
81
|
+
description: "How many to mint (default: 1)",
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
required: ["mintUrl"],
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
];
|
|
88
|
+
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) });
|
|
89
|
+
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) });
|
|
90
|
+
const MintNftSchema = zod_1.z.object({
|
|
91
|
+
mintUrl: zod_1.z.string().min(1),
|
|
92
|
+
quantity: zod_1.z.number().min(1).max(100).optional(),
|
|
93
|
+
});
|
|
94
|
+
const DeployTokenSchema = zod_1.z.object({
|
|
95
|
+
name: zod_1.z.string().min(1),
|
|
96
|
+
symbol: zod_1.z.string().min(3).max(6),
|
|
97
|
+
imageUrl: zod_1.z.string().url(),
|
|
98
|
+
description: zod_1.z.string().optional(),
|
|
99
|
+
initialMarketCapUSD: zod_1.z.number().min(1000).optional(),
|
|
100
|
+
creatorFeePercent: zod_1.z.number().min(0).max(100).optional(),
|
|
101
|
+
preminePercent: zod_1.z.number().min(0).max(50).optional(),
|
|
102
|
+
fairLaunchDurationMinutes: zod_1.z.number().min(1).optional(),
|
|
103
|
+
});
|
|
104
|
+
async function handleDefiTool(name, args) {
|
|
105
|
+
switch (name) {
|
|
106
|
+
case "get_portfolio": {
|
|
107
|
+
const data = await (0, convex_js_1.callConvex)("/mcp/defi/portfolio", "GET", undefined, "get_portfolio");
|
|
108
|
+
if (data.error)
|
|
109
|
+
return { content: [{ type: "text", text: `Portfolio error: ${data.error}` }], isError: true };
|
|
110
|
+
const lines = [
|
|
111
|
+
`**Portfolio — Base Mainnet**`, `Address: \`${data.address}\``, ``, `**Balances**`,
|
|
112
|
+
];
|
|
113
|
+
for (const b of (data.balances ?? [])) {
|
|
114
|
+
lines.push(`• ${b.token}: ${b.balance}${b.valueUsd ? ` (~$${Number(b.valueUsd).toFixed(2)})` : ""}`);
|
|
115
|
+
}
|
|
116
|
+
lines.push(``, `**Total Value:** ~$${Number(data.totalValueUsd ?? 0).toFixed(2)}`);
|
|
117
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
118
|
+
}
|
|
119
|
+
case "swap_tokens": {
|
|
120
|
+
const parsed = SwapSchema.safeParse(args);
|
|
121
|
+
if (!parsed.success)
|
|
122
|
+
return { content: [{ type: "text", text: `Invalid input: ${String(parsed.error.issues[0].path[0])} ${parsed.error.issues[0].message}` }], isError: true };
|
|
123
|
+
let { fromToken, toToken, amount } = parsed.data;
|
|
124
|
+
// Resolve percentage amounts e.g. "50%", "100%", "25%"
|
|
125
|
+
const pctMatch = amount.trim().match(/^(\d+(?:\.\d+)?)\s*%$/);
|
|
126
|
+
if (pctMatch) {
|
|
127
|
+
const pct = parseFloat(pctMatch[1]) / 100;
|
|
128
|
+
const portfolio = await (0, convex_js_1.callConvex)("/mcp/defi/portfolio", "GET", undefined, "get_portfolio");
|
|
129
|
+
const balance = (portfolio.balances ?? []).find((b) => b.token?.toUpperCase() === fromToken.toUpperCase());
|
|
130
|
+
if (!balance)
|
|
131
|
+
return { content: [{ type: "text", text: `No ${fromToken.toUpperCase()} balance found in portfolio.` }], isError: true };
|
|
132
|
+
const rawBalance = parseFloat(balance.balance ?? "0");
|
|
133
|
+
if (rawBalance <= 0)
|
|
134
|
+
return { content: [{ type: "text", text: `${fromToken.toUpperCase()} balance is 0.` }], isError: true };
|
|
135
|
+
const resolved = (rawBalance * pct).toFixed(6).replace(/\.?0+$/, "");
|
|
136
|
+
amount = resolved;
|
|
137
|
+
}
|
|
138
|
+
const wallet = await (0, wallet_js_1.getOrCreateWallet)();
|
|
139
|
+
const result = await (0, convex_js_1.callConvex)("/mcp/defi/swap", "POST", { fromToken, toToken, amount }, "swap_tokens");
|
|
140
|
+
if (!result.success)
|
|
141
|
+
return { content: [{ type: "text", text: `Swap failed: ${result.error}` }], isError: true };
|
|
142
|
+
const txHash = await (0, wallet_js_1.signAndBroadcast)(wallet, result.quote);
|
|
143
|
+
const buyAmountHuman = (parseInt(result.quote.buyAmount) / 1e6).toFixed(4);
|
|
144
|
+
return {
|
|
145
|
+
content: [{
|
|
146
|
+
type: "text",
|
|
147
|
+
text: [`✅ Swap executed!`, `${amount} ${fromToken.toUpperCase()} → ${buyAmountHuman} ${result.quote.buyToken}`, `Tx Hash: \`${txHash}\``, `https://basescan.org/tx/${txHash}`].join("\n"),
|
|
148
|
+
}],
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
case "send_token": {
|
|
152
|
+
const parsed = SendSchema.safeParse(args);
|
|
153
|
+
if (!parsed.success)
|
|
154
|
+
return { content: [{ type: "text", text: `Invalid input: ${String(parsed.error.issues[0].path[0])} ${parsed.error.issues[0].message}` }], isError: true };
|
|
155
|
+
const { token, toAddress, amount } = parsed.data;
|
|
156
|
+
const wallet = await (0, wallet_js_1.getOrCreateWallet)();
|
|
157
|
+
const result = await (0, convex_js_1.callConvex)("/mcp/defi/send", "POST", parsed.data, "send_token");
|
|
158
|
+
if (!result.success)
|
|
159
|
+
return { content: [{ type: "text", text: `Send failed: ${result.error}` }], isError: true };
|
|
160
|
+
const txHash = await (0, wallet_js_1.signAndBroadcast)(wallet, result.txData);
|
|
161
|
+
// Collect 0.5% platform fee as a separate tx if the server returned fee data
|
|
162
|
+
if (result.feeTxData) {
|
|
163
|
+
try {
|
|
164
|
+
await (0, wallet_js_1.signAndBroadcast)(wallet, result.feeTxData);
|
|
165
|
+
}
|
|
166
|
+
catch { /* non-fatal */ }
|
|
167
|
+
}
|
|
168
|
+
return {
|
|
169
|
+
content: [{
|
|
170
|
+
type: "text",
|
|
171
|
+
text: [`✅ Sent!`, `${amount} ${token.toUpperCase()} → \`${toAddress}\``, `Tx Hash: \`${txHash}\``, `https://basescan.org/tx/${txHash}`].join("\n"),
|
|
172
|
+
}],
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
case "mint_nft": {
|
|
176
|
+
const parsed = MintNftSchema.safeParse(args);
|
|
177
|
+
if (!parsed.success)
|
|
178
|
+
return { content: [{ type: "text", text: `Invalid input: ${parsed.error.issues[0].message}` }], isError: true };
|
|
179
|
+
const { mintUrl, quantity } = parsed.data;
|
|
180
|
+
const wallet = await (0, wallet_js_1.getOrCreateWallet)();
|
|
181
|
+
const result = await (0, convex_js_1.callConvex)("/mcp/nft/mint", "POST", { mintUrl, quantity: quantity ?? 1 }, "mint_nft");
|
|
182
|
+
if (result.error)
|
|
183
|
+
return { content: [{ type: "text", text: `Mint failed: ${result.error}` }], isError: true };
|
|
184
|
+
if (result.action !== "sign_and_broadcast")
|
|
185
|
+
return { content: [{ type: "text", text: "Unexpected response from server" }], isError: true };
|
|
186
|
+
const txHash = await (0, wallet_js_1.signAndBroadcast)(wallet, result);
|
|
187
|
+
const meta = result.metadata ?? {};
|
|
188
|
+
return {
|
|
189
|
+
content: [{
|
|
190
|
+
type: "text",
|
|
191
|
+
text: [
|
|
192
|
+
`✅ Minted successfully!`,
|
|
193
|
+
`Contract: ${meta.contractAddress ?? result.to}`,
|
|
194
|
+
`Quantity: ${meta.quantity ?? quantity ?? 1}`,
|
|
195
|
+
`Cost: ${meta.totalCost ?? "0"} ETH`,
|
|
196
|
+
`Tx: \`${txHash}\``,
|
|
197
|
+
`https://basescan.org/tx/${txHash}`,
|
|
198
|
+
].join("\n"),
|
|
199
|
+
}],
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
case "deploy_token": {
|
|
203
|
+
const parsed = DeployTokenSchema.safeParse(args);
|
|
204
|
+
if (!parsed.success)
|
|
205
|
+
return { content: [{ type: "text", text: `Invalid input: ${parsed.error.issues[0].message}` }], isError: true };
|
|
206
|
+
const wallet = await (0, wallet_js_1.getOrCreateWallet)();
|
|
207
|
+
const result = await (0, convex_js_1.callConvex)("/mcp/token/deploy", "POST", parsed.data, "deploy_token");
|
|
208
|
+
if (result.error)
|
|
209
|
+
return { content: [{ type: "text", text: `Deploy failed: ${result.error}` }], isError: true };
|
|
210
|
+
const txHash = await (0, wallet_js_1.signAndBroadcast)(wallet, result);
|
|
211
|
+
return {
|
|
212
|
+
content: [{
|
|
213
|
+
type: "text",
|
|
214
|
+
text: [
|
|
215
|
+
`✅ Token deployed!`,
|
|
216
|
+
`Name: ${parsed.data.name} ($${parsed.data.symbol})`,
|
|
217
|
+
`Tx Hash: \`${txHash}\``,
|
|
218
|
+
`https://basescan.org/tx/${txHash}`,
|
|
219
|
+
``,
|
|
220
|
+
`Your Memestream NFT earns ETH from every swap.`,
|
|
221
|
+
].join("\n"),
|
|
222
|
+
}],
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
case "claim_fees": {
|
|
226
|
+
const wallet = await (0, wallet_js_1.getOrCreateWallet)();
|
|
227
|
+
const result = await (0, convex_js_1.callConvex)("/mcp/token/claim", "POST", {}, "claim_fees");
|
|
228
|
+
if (result.error)
|
|
229
|
+
return { content: [{ type: "text", text: `Claim failed: ${result.error}` }], isError: true };
|
|
230
|
+
const txHash = await (0, wallet_js_1.signAndBroadcast)(wallet, result);
|
|
231
|
+
return {
|
|
232
|
+
content: [{
|
|
233
|
+
type: "text",
|
|
234
|
+
text: [`✅ ETH claimed successfully!`, `Tx Hash: \`${txHash}\``, `https://basescan.org/tx/${txHash}`].join("\n"),
|
|
235
|
+
}],
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
default:
|
|
239
|
+
return null;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.FRAMEWORK_TOOLS = void 0;
|
|
4
|
+
exports.handleFrameworkTool = handleFrameworkTool;
|
|
5
|
+
const convex_js_1 = require("../convex.js");
|
|
6
|
+
exports.FRAMEWORK_TOOLS = [
|
|
7
|
+
{
|
|
8
|
+
name: "create_task_packet",
|
|
9
|
+
description: "Define a scoped task for Noel Framework. Converts plain-English intent into a " +
|
|
10
|
+
"structured Task Packet (territory, permissions, doNotDo constraints). " +
|
|
11
|
+
"Sentinel validates before any agent action. " +
|
|
12
|
+
"Example: 'buy ETH when it drops 5%, max $20, don't touch my USDC'.",
|
|
13
|
+
inputSchema: {
|
|
14
|
+
type: "object",
|
|
15
|
+
properties: {
|
|
16
|
+
task: {
|
|
17
|
+
type: "string",
|
|
18
|
+
description: "What you want the agents to do, in plain English.",
|
|
19
|
+
},
|
|
20
|
+
name: {
|
|
21
|
+
type: "string",
|
|
22
|
+
description: "Optional short name for this task (max 5 words).",
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
required: ["task"],
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
name: "list_task_packets",
|
|
30
|
+
description: "List all your Task Packets — draft, active, completed, and blocked.",
|
|
31
|
+
inputSchema: { type: "object", properties: {}, required: [] },
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
name: "list_playbooks",
|
|
35
|
+
description: "List available Noel Framework playbooks — predefined multi-step agent workflows. " +
|
|
36
|
+
"Includes 4 system playbooks (Daily Market Scan, DCA Setup, Portfolio Rebalance Check, " +
|
|
37
|
+
"Swarm Intel Sweep) plus any you've created. Each step is Sentinel-gated.",
|
|
38
|
+
inputSchema: { type: "object", properties: {}, required: [] },
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
name: "run_playbook",
|
|
42
|
+
description: "Execute a Noel Framework playbook. Each step runs through Sentinel before the " +
|
|
43
|
+
"appropriate swarm agent executes it (Scout → market-monitor, Tinker → workflow-executor, " +
|
|
44
|
+
"Skeptic → risk-verifier, Memory → memory-manager). " +
|
|
45
|
+
"Playbook halts immediately if Sentinel blocks a step.",
|
|
46
|
+
inputSchema: {
|
|
47
|
+
type: "object",
|
|
48
|
+
properties: {
|
|
49
|
+
playbook_name: {
|
|
50
|
+
type: "string",
|
|
51
|
+
description: "Exact name of the playbook. Use list_playbooks to see available ones.",
|
|
52
|
+
},
|
|
53
|
+
task_description: {
|
|
54
|
+
type: "string",
|
|
55
|
+
description: "Optional context passed as overrideParams to the playbook run.",
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
required: ["playbook_name"],
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
name: "get_noel_ledger",
|
|
63
|
+
description: "Get the Noel Framework audit trail — every Sentinel gate decision " +
|
|
64
|
+
"(approved / blocked / warned), which checks ran, duration, and reason. " +
|
|
65
|
+
"Full transparency on what agents are and aren't allowed to do.",
|
|
66
|
+
inputSchema: { type: "object", properties: {}, required: [] },
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
name: "get_sentinel_rules",
|
|
70
|
+
description: "Get Sentinel rules for each swarm agent and each playbook role — " +
|
|
71
|
+
"territory, permissions, blocked actions, and value caps. " +
|
|
72
|
+
"Shows exactly what each agent is and isn't allowed to do.",
|
|
73
|
+
inputSchema: { type: "object", properties: {}, required: [] },
|
|
74
|
+
},
|
|
75
|
+
];
|
|
76
|
+
async function handleFrameworkTool(name, args) {
|
|
77
|
+
const a = (args ?? {});
|
|
78
|
+
switch (name) {
|
|
79
|
+
// ── create_task_packet ──────────────────────────────────────────────────
|
|
80
|
+
case "create_task_packet": {
|
|
81
|
+
if (!a.task) {
|
|
82
|
+
return { content: [{ type: "text", text: "task is required" }], isError: true };
|
|
83
|
+
}
|
|
84
|
+
const result = await (0, convex_js_1.callConvex)("/framework/task", "POST", {
|
|
85
|
+
naturalLanguage: a.task,
|
|
86
|
+
name: a.name,
|
|
87
|
+
}, "create_task_packet");
|
|
88
|
+
if (result.error) {
|
|
89
|
+
return { content: [{ type: "text", text: `Failed: ${result.error}` }], isError: true };
|
|
90
|
+
}
|
|
91
|
+
const p = result.packet ?? {};
|
|
92
|
+
const lines = [
|
|
93
|
+
`✅ **Task Packet created**`,
|
|
94
|
+
``,
|
|
95
|
+
`**Name:** ${p.name ?? "—"}`,
|
|
96
|
+
`**Task:** ${p.task ?? a.task}`,
|
|
97
|
+
`**Territory:** ${(p.territory ?? []).join(", ") || "—"}`,
|
|
98
|
+
`**Permissions:** ${(p.permissions ?? []).join(", ") || "—"}`,
|
|
99
|
+
`**Blocked:** ${(p.doNotDo ?? []).join(", ") || "none"}`,
|
|
100
|
+
`**Max value:** ${p.maxValueUsd != null ? `$${p.maxValueUsd}` : "no limit"}`,
|
|
101
|
+
``,
|
|
102
|
+
`ID: \`${result.id}\``,
|
|
103
|
+
`Ready to run a playbook with this task scope.`,
|
|
104
|
+
];
|
|
105
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
106
|
+
}
|
|
107
|
+
// ── list_task_packets ───────────────────────────────────────────────────
|
|
108
|
+
case "list_task_packets": {
|
|
109
|
+
const result = await (0, convex_js_1.callConvex)("/framework/tasks", "GET", undefined, "list_task_packets");
|
|
110
|
+
const tasks = result.tasks ?? [];
|
|
111
|
+
if (tasks.length === 0) {
|
|
112
|
+
return {
|
|
113
|
+
content: [{
|
|
114
|
+
type: "text",
|
|
115
|
+
text: "No task packets yet. Use create_task_packet to define your first task.",
|
|
116
|
+
}],
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
const list = tasks
|
|
120
|
+
.map((t) => `• **${t.name}** [${t.status}]\n ${t.task}`)
|
|
121
|
+
.join("\n\n");
|
|
122
|
+
return { content: [{ type: "text", text: `**Your Task Packets**\n\n${list}` }] };
|
|
123
|
+
}
|
|
124
|
+
// ── list_playbooks ──────────────────────────────────────────────────────
|
|
125
|
+
case "list_playbooks": {
|
|
126
|
+
const result = await (0, convex_js_1.callConvex)("/framework/playbooks", "GET", undefined, "list_playbooks");
|
|
127
|
+
const pbs = result.playbooks ?? [];
|
|
128
|
+
if (pbs.length === 0) {
|
|
129
|
+
return { content: [{ type: "text", text: "No playbooks found." }] };
|
|
130
|
+
}
|
|
131
|
+
const list = pbs
|
|
132
|
+
.map((p) => {
|
|
133
|
+
const steps = (() => {
|
|
134
|
+
try {
|
|
135
|
+
return JSON.parse(p.steps).length;
|
|
136
|
+
}
|
|
137
|
+
catch {
|
|
138
|
+
return "?";
|
|
139
|
+
}
|
|
140
|
+
})();
|
|
141
|
+
return `• **${p.name}**${p.isPublic ? " 🌐" : " 👤"} — ${steps} steps\n ${p.description}\n Used ${p.usageCount} times`;
|
|
142
|
+
})
|
|
143
|
+
.join("\n\n");
|
|
144
|
+
return { content: [{ type: "text", text: `**Available Playbooks**\n\n${list}` }] };
|
|
145
|
+
}
|
|
146
|
+
// ── run_playbook ────────────────────────────────────────────────────────
|
|
147
|
+
case "run_playbook": {
|
|
148
|
+
if (!a.playbook_name) {
|
|
149
|
+
return { content: [{ type: "text", text: "playbook_name is required" }], isError: true };
|
|
150
|
+
}
|
|
151
|
+
// Resolve playbook ID by name
|
|
152
|
+
const pbList = await (0, convex_js_1.callConvex)("/framework/playbooks", "GET", undefined, "run_playbook");
|
|
153
|
+
const playbook = (pbList.playbooks ?? []).find((p) => p.name.toLowerCase() === String(a.playbook_name).toLowerCase());
|
|
154
|
+
if (!playbook) {
|
|
155
|
+
return {
|
|
156
|
+
content: [{
|
|
157
|
+
type: "text",
|
|
158
|
+
text: `Playbook "${a.playbook_name}" not found. Use list_playbooks to see available ones.`,
|
|
159
|
+
}],
|
|
160
|
+
isError: true,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
const result = await (0, convex_js_1.callConvex)("/framework/playbook/run", "POST", {
|
|
164
|
+
playbookId: playbook._id,
|
|
165
|
+
overrideParams: a.task_description,
|
|
166
|
+
}, "run_playbook");
|
|
167
|
+
if (result.error) {
|
|
168
|
+
return { content: [{ type: "text", text: `Run failed: ${result.error}` }], isError: true };
|
|
169
|
+
}
|
|
170
|
+
if (result.blocked) {
|
|
171
|
+
return {
|
|
172
|
+
content: [{
|
|
173
|
+
type: "text",
|
|
174
|
+
text: [
|
|
175
|
+
`🛡️ **Sentinel blocked playbook at step ${result.step}**`,
|
|
176
|
+
``,
|
|
177
|
+
`**Tool:** ${result.tool}`,
|
|
178
|
+
`**Reason:** ${result.reason}`,
|
|
179
|
+
``,
|
|
180
|
+
`This is a mechanical safety gate. The action violates the agent's permission boundary.`,
|
|
181
|
+
`Completed steps before block: ${result.results?.length ?? 0}`,
|
|
182
|
+
].join("\n"),
|
|
183
|
+
}],
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
const steps = result.results ?? [];
|
|
187
|
+
const succeeded = steps.filter(r => r.success).length;
|
|
188
|
+
const stepLines = steps.map((r) => `${r.success ? "✅" : "❌"} Step ${r.step} [${r.role}]: ${r.tool}${r.error ? ` — ${r.error}` : ""}`);
|
|
189
|
+
return {
|
|
190
|
+
content: [{
|
|
191
|
+
type: "text",
|
|
192
|
+
text: [
|
|
193
|
+
`✅ **Playbook "${a.playbook_name}" completed**`,
|
|
194
|
+
``,
|
|
195
|
+
`${succeeded}/${steps.length} steps successful`,
|
|
196
|
+
`Run ID: \`${result.runId}\``,
|
|
197
|
+
``,
|
|
198
|
+
...stepLines,
|
|
199
|
+
].join("\n"),
|
|
200
|
+
}],
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
// ── get_noel_ledger ─────────────────────────────────────────────────────
|
|
204
|
+
case "get_noel_ledger": {
|
|
205
|
+
const result = await (0, convex_js_1.callConvex)("/swarm/ledger", "GET", undefined, "get_noel_ledger");
|
|
206
|
+
const entries = result.entries ?? [];
|
|
207
|
+
if (entries.length === 0) {
|
|
208
|
+
return {
|
|
209
|
+
content: [{
|
|
210
|
+
type: "text",
|
|
211
|
+
text: "No ledger entries yet. Run a playbook to see Sentinel decisions.",
|
|
212
|
+
}],
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
const lines = entries.map((e) => {
|
|
216
|
+
const icon = e.decision === "approved" ? "✅" : e.decision === "blocked" ? "🚫" : "⚠️";
|
|
217
|
+
return `${icon} **${e.agentId}** → \`${e.action}\`\n ${e.reason} (${e.durationMs}ms)`;
|
|
218
|
+
});
|
|
219
|
+
return {
|
|
220
|
+
content: [{
|
|
221
|
+
type: "text",
|
|
222
|
+
text: `**Noel Ledger** (last ${entries.length} decisions)\n\n${lines.join("\n\n")}`,
|
|
223
|
+
}],
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
// ── get_sentinel_rules ──────────────────────────────────────────────────
|
|
227
|
+
case "get_sentinel_rules": {
|
|
228
|
+
const rules = {
|
|
229
|
+
"market-monitor": { territory: ["market_data", "price_check", "market"], permissions: ["read:market", "write:memory"], doNotDo: ["swap", "send", "transfer", "buy", "sell", "drain"], maxValueUsd: 0 },
|
|
230
|
+
"sentiment-tracker": { territory: ["sentiment", "news_analysis", "news"], permissions: ["read:market", "read:social", "write:memory"], doNotDo: ["swap", "send", "transfer", "buy", "sell"], maxValueUsd: 0 },
|
|
231
|
+
"workflow-executor": { territory: ["swap", "send", "automation"], permissions: ["read:market", "write:tx", "write:memory"], doNotDo: ["delete_wallet", "change_keys", "drain_wallet"], maxValueUsd: 500 },
|
|
232
|
+
"memory-manager": { territory: ["memory_compress", "memory_prune", "memory"], permissions: ["read:memory", "write:memory", "delete:memory"], doNotDo: ["swap", "send", "transfer"], maxValueUsd: 0 },
|
|
233
|
+
"risk-verifier": { territory: ["risk_check", "verify", "risk"], permissions: ["read:market", "read:memory"], doNotDo: ["swap", "send", "transfer", "modify_rules"], maxValueUsd: 0 },
|
|
234
|
+
"playbook:scout": { territory: ["get", "read", "list", "market", "signal", "portfolio", "insight", "research", "data", "recap", "whale", "score", "execution", "memory", "noel", "swarm", "smart"], permissions: ["read:market", "read:signals", "read:portfolio", "read:memory", "write:memory"], doNotDo: ["send_token", "deploy_token", "mint_nft", "claim_fees", "swap_tokens", "delete_automation", "stop_swarm"], maxValueUsd: 0, note: "Playbook read-only scout role" },
|
|
235
|
+
"playbook:tinker": { territory: ["create", "write", "start", "run", "swap", "send", "deploy", "mint", "claim", "automation", "memory", "swarm"], permissions: ["write:tx", "write:memory", "execute:automation", "swap:token", "deploy:token"], doNotDo: ["delete_wallet", "drain_wallet"], maxValueUsd: 100, note: "Playbook execution role" },
|
|
236
|
+
"playbook:skeptic": { territory: ["get", "read", "ask", "analyze", "verify", "research", "check", "market", "signal", "execution", "score", "portfolio", "memory", "noel", "insight"], permissions: ["read:market", "read:signals", "read:portfolio", "read:memory"], doNotDo: ["send_token", "deploy_token", "mint_nft", "claim_fees", "swap_tokens", "delete"], maxValueUsd: 0, note: "Playbook analysis/verification role" },
|
|
237
|
+
"playbook:memory": { territory: ["memory", "write", "read", "compress", "prune", "summarize"], permissions: ["read:memory", "write:memory", "delete:memory"], doNotDo: ["send_token", "swap_tokens", "deploy_token", "mint_nft", "claim_fees"], maxValueUsd: 0, note: "Playbook memory management role" },
|
|
238
|
+
};
|
|
239
|
+
const sections = [
|
|
240
|
+
"**Sentinel Rules**",
|
|
241
|
+
"",
|
|
242
|
+
"**Swarm Agents (DEFAULT_RULES)**",
|
|
243
|
+
...["market-monitor", "sentiment-tracker", "workflow-executor", "memory-manager", "risk-verifier"].map(agent => {
|
|
244
|
+
const r = rules[agent];
|
|
245
|
+
return `\n**${agent}**\n Territory: ${r.territory.join(", ")}\n Permissions: ${r.permissions.join(", ")}\n Blocked: ${r.doNotDo.join(", ")}\n Max value: $${r.maxValueUsd}`;
|
|
246
|
+
}),
|
|
247
|
+
"",
|
|
248
|
+
"**Playbook Roles (DB rules — seeded at deploy)**",
|
|
249
|
+
...["playbook:scout", "playbook:tinker", "playbook:skeptic", "playbook:memory"].map(agent => {
|
|
250
|
+
const r = rules[agent];
|
|
251
|
+
return `\n**${agent}** _(${r.note})_\n Territory: ${r.territory.slice(0, 6).join(", ")}…\n Blocked: ${r.doNotDo.join(", ")}\n Max value: $${r.maxValueUsd}`;
|
|
252
|
+
}),
|
|
253
|
+
];
|
|
254
|
+
return { content: [{ type: "text", text: sections.join("\n") }] };
|
|
255
|
+
}
|
|
256
|
+
default:
|
|
257
|
+
return null;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.HUMANIZER_TOOLS = void 0;
|
|
4
|
+
exports.handleHumanizerTool = handleHumanizerTool;
|
|
5
|
+
exports.HUMANIZER_TOOLS = [
|
|
6
|
+
{
|
|
7
|
+
name: "humanize_text",
|
|
8
|
+
description: "Remove AI writing patterns from text — makes it sound natural, direct, and human. " +
|
|
9
|
+
"Fixes 29 common AI tells: significance inflation, em dash overuse, filler phrases, " +
|
|
10
|
+
"sycophantic openers, passive voice, elegant variation, chatbot artifacts, and more. " +
|
|
11
|
+
"Optionally provide a writing sample to match your personal voice.",
|
|
12
|
+
inputSchema: {
|
|
13
|
+
type: "object",
|
|
14
|
+
properties: {
|
|
15
|
+
text: {
|
|
16
|
+
type: "string",
|
|
17
|
+
description: "The text to humanize",
|
|
18
|
+
},
|
|
19
|
+
voice_sample: {
|
|
20
|
+
type: "string",
|
|
21
|
+
description: "Optional: a sample of your own writing so the output matches your voice",
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
required: ["text"],
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
];
|
|
28
|
+
const HUMANIZER_SYSTEM = `You are a text editor that removes signs of AI-generated writing.
|
|
29
|
+
|
|
30
|
+
Your job: rewrite the input so it sounds natural, direct, and human — without changing the meaning.
|
|
31
|
+
|
|
32
|
+
Fix these patterns when present:
|
|
33
|
+
|
|
34
|
+
CONTENT
|
|
35
|
+
1. Significance inflation — remove phrases like "in today's rapidly evolving landscape", "in an era of", "now more than ever"
|
|
36
|
+
2. Notability emphasis — cut "notably", "it is worth noting", "it is important to note"
|
|
37
|
+
3. Superficial -ing openers — rewrite "By leveraging X, you can Y" → just say "X lets you Y"
|
|
38
|
+
4. Promotional language — cut "revolutionary", "game-changing", "cutting-edge", "innovative solution"
|
|
39
|
+
5. Vague attribution — replace "experts say", "studies show" with specific sources or cut entirely
|
|
40
|
+
6. Formulaic challenges sections — remove "Of course, challenges remain" boilerplate
|
|
41
|
+
|
|
42
|
+
LANGUAGE
|
|
43
|
+
7. AI vocabulary — replace: landscape → field/market/space, pivotal → key/critical, testament → proof/sign, delve → explore/look at, utilize → use, leverage → use
|
|
44
|
+
8. Copula avoidance — "serves as", "stands as", "acts as" → just use "is"
|
|
45
|
+
9. Negative parallelisms — "not only X but also Y" → just say the thing directly
|
|
46
|
+
10. Rule of three — "fast, reliable, and scalable" padding — cut to what matters
|
|
47
|
+
11. Elegant variation — don't use synonyms to avoid repeating a word; repeat it or restructure
|
|
48
|
+
12. False ranges — "anywhere from X to Y" → just say the number you know
|
|
49
|
+
13. Passive voice — rewrite to active where it feels evasive
|
|
50
|
+
14. Em dash overuse — max one per paragraph; replace others with commas or rewrite
|
|
51
|
+
15. Bullet point padding — remove bullets that just restate the intro sentence
|
|
52
|
+
|
|
53
|
+
COMMUNICATION
|
|
54
|
+
16. Chatbot artifacts — cut "Certainly!", "Of course!", "Great question!", "I hope this helps"
|
|
55
|
+
17. Knowledge disclaimers — cut "As of my last update", "Based on my training data"
|
|
56
|
+
18. Sycophancy — cut "That's a fascinating perspective", "You raise an excellent point"
|
|
57
|
+
|
|
58
|
+
FILLER
|
|
59
|
+
19. Filler phrases — cut "It is worth mentioning that", "It goes without saying", "Needless to say"
|
|
60
|
+
20. Excessive hedging — cut "it could be argued", "one might say", "in some ways"
|
|
61
|
+
21. Generic conclusions — rewrite "In conclusion, X is important" → just end on substance
|
|
62
|
+
22. Hyphenated padding — "user-friendly", "game-changing", "thought-provoking" → be specific
|
|
63
|
+
23. Signposting — cut "In this article, I will explain" — just explain it
|
|
64
|
+
24. Fragmented headers — avoid turning every sentence into a bold header
|
|
65
|
+
|
|
66
|
+
PROCESS:
|
|
67
|
+
1. Read the full text
|
|
68
|
+
2. Identify which patterns are present
|
|
69
|
+
3. Rewrite — fix all patterns found
|
|
70
|
+
4. Self-audit: scan the rewrite for any remaining AI tells
|
|
71
|
+
5. Final revision if needed
|
|
72
|
+
6. Output ONLY the final humanized text — no commentary, no explanation, no "Here is your text:"
|
|
73
|
+
|
|
74
|
+
If a voice sample is provided, match its tone, rhythm, and vocabulary. Otherwise use direct, opinionated, natural prose.`;
|
|
75
|
+
async function handleHumanizerTool(name, args) {
|
|
76
|
+
if (name !== "humanize_text")
|
|
77
|
+
return null;
|
|
78
|
+
const a = (args ?? {});
|
|
79
|
+
if (!a.text?.trim()) {
|
|
80
|
+
return { content: [{ type: "text", text: "text is required" }], isError: true };
|
|
81
|
+
}
|
|
82
|
+
const apiKey = process.env.MINIMAX_API_KEY;
|
|
83
|
+
if (!apiKey) {
|
|
84
|
+
return { content: [{ type: "text", text: "MINIMAX_API_KEY not set — humanizer requires MiniMax API access" }], isError: true };
|
|
85
|
+
}
|
|
86
|
+
const userMsg = a.voice_sample
|
|
87
|
+
? `VOICE SAMPLE (match this style):\n${a.voice_sample}\n\n---\n\nTEXT TO HUMANIZE:\n${a.text}`
|
|
88
|
+
: a.text;
|
|
89
|
+
try {
|
|
90
|
+
const res = await fetch("https://api.minimaxi.chat/v1/chat/completions", {
|
|
91
|
+
method: "POST",
|
|
92
|
+
headers: {
|
|
93
|
+
"Content-Type": "application/json",
|
|
94
|
+
"Authorization": `Bearer ${apiKey}`,
|
|
95
|
+
},
|
|
96
|
+
body: JSON.stringify({
|
|
97
|
+
model: "MiniMax-Text-01",
|
|
98
|
+
messages: [
|
|
99
|
+
{ role: "system", content: HUMANIZER_SYSTEM },
|
|
100
|
+
{ role: "user", content: userMsg },
|
|
101
|
+
],
|
|
102
|
+
temperature: 0.7,
|
|
103
|
+
max_tokens: 4096,
|
|
104
|
+
}),
|
|
105
|
+
signal: AbortSignal.timeout(30000),
|
|
106
|
+
});
|
|
107
|
+
if (!res.ok) {
|
|
108
|
+
const err = await res.text();
|
|
109
|
+
return { content: [{ type: "text", text: `API error: ${res.status} ${err.slice(0, 200)}` }], isError: true };
|
|
110
|
+
}
|
|
111
|
+
const data = await res.json();
|
|
112
|
+
const output = data?.choices?.[0]?.message?.content?.trim();
|
|
113
|
+
if (!output)
|
|
114
|
+
return { content: [{ type: "text", text: "Empty response from model" }], isError: true };
|
|
115
|
+
return { content: [{ type: "text", text: output }] };
|
|
116
|
+
}
|
|
117
|
+
catch (err) {
|
|
118
|
+
return { content: [{ type: "text", text: `Humanizer error: ${err.message}` }], isError: true };
|
|
119
|
+
}
|
|
120
|
+
}
|