trenchfeed-cli 0.1.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/.github/dependabot.yml +15 -0
- package/.github/workflows/ci.yml +27 -0
- package/CHANGELOG.md +28 -0
- package/CONTRIBUTING.md +60 -0
- package/LICENSE +21 -0
- package/README.md +467 -0
- package/SECURITY.md +28 -0
- package/dist/api.d.ts +110 -0
- package/dist/api.js +52 -0
- package/dist/api.js.map +1 -0
- package/dist/config.d.ts +13 -0
- package/dist/config.js +48 -0
- package/dist/config.js.map +1 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.js +932 -0
- package/dist/index.js.map +1 -0
- package/logo.png +0 -0
- package/package.json +42 -0
- package/src/api.ts +147 -0
- package/src/config.ts +53 -0
- package/src/index.ts +989 -0
- package/tsconfig.json +19 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,932 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
/**
|
|
4
|
+
* TrenchFeed CLI — Deploy and manage AI trading agents from your terminal.
|
|
5
|
+
*
|
|
6
|
+
* Commands:
|
|
7
|
+
* trenchfeed setup — Interactive agent setup wizard
|
|
8
|
+
* trenchfeed status — Show agent status + PnL
|
|
9
|
+
* trenchfeed start — Start trading
|
|
10
|
+
* trenchfeed stop — Stop trading
|
|
11
|
+
* trenchfeed pause — Pause trading
|
|
12
|
+
* trenchfeed resume — Resume trading
|
|
13
|
+
* trenchfeed emergency — Emergency stop + sell all
|
|
14
|
+
* trenchfeed config — View/update agent config
|
|
15
|
+
* trenchfeed wallet — Show wallet address + balance
|
|
16
|
+
* trenchfeed trades — Show recent trade history
|
|
17
|
+
* trenchfeed stream — Live stream agent events
|
|
18
|
+
* trenchfeed chat <msg> — Send a message to your agent
|
|
19
|
+
* trenchfeed logout — Clear stored credentials
|
|
20
|
+
*/
|
|
21
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
22
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
23
|
+
};
|
|
24
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
25
|
+
const commander_1 = require("commander");
|
|
26
|
+
const inquirer_1 = __importDefault(require("inquirer"));
|
|
27
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
28
|
+
const figlet_1 = __importDefault(require("figlet"));
|
|
29
|
+
const ws_1 = require("ws");
|
|
30
|
+
const config_1 = require("./config");
|
|
31
|
+
const api_1 = require("./api");
|
|
32
|
+
const program = new commander_1.Command();
|
|
33
|
+
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
|
34
|
+
function banner() {
|
|
35
|
+
console.log(chalk_1.default.red(figlet_1.default.textSync('TRENCHFEED', { font: 'Small' })));
|
|
36
|
+
console.log(chalk_1.default.gray(' AI Trading Agent Platform — CLI\n'));
|
|
37
|
+
}
|
|
38
|
+
function requireSetup() {
|
|
39
|
+
const config = (0, config_1.loadConfig)();
|
|
40
|
+
if (!config.apiKey || !config.agentId) {
|
|
41
|
+
console.log(chalk_1.default.yellow('No agent configured. Run `trenchfeed setup` first.'));
|
|
42
|
+
process.exit(1);
|
|
43
|
+
}
|
|
44
|
+
return { apiKey: config.apiKey, agentId: config.agentId };
|
|
45
|
+
}
|
|
46
|
+
function formatSol(n) {
|
|
47
|
+
const sign = n >= 0 ? '+' : '';
|
|
48
|
+
const color = n >= 0 ? chalk_1.default.green : chalk_1.default.red;
|
|
49
|
+
return color(`${sign}${n.toFixed(4)} SOL`);
|
|
50
|
+
}
|
|
51
|
+
function formatPct(n) {
|
|
52
|
+
const sign = n >= 0 ? '+' : '';
|
|
53
|
+
const color = n >= 0 ? chalk_1.default.green : chalk_1.default.red;
|
|
54
|
+
return color(`${sign}${n.toFixed(1)}%`);
|
|
55
|
+
}
|
|
56
|
+
function timeAgo(ts) {
|
|
57
|
+
const s = Math.floor((Date.now() - ts) / 1000);
|
|
58
|
+
if (s < 60)
|
|
59
|
+
return `${s}s ago`;
|
|
60
|
+
if (s < 3600)
|
|
61
|
+
return `${Math.floor(s / 60)}m ago`;
|
|
62
|
+
if (s < 86400)
|
|
63
|
+
return `${Math.floor(s / 3600)}h ago`;
|
|
64
|
+
return `${Math.floor(s / 86400)}d ago`;
|
|
65
|
+
}
|
|
66
|
+
function statusColor(status) {
|
|
67
|
+
switch (status) {
|
|
68
|
+
case 'running': return chalk_1.default.green(status);
|
|
69
|
+
case 'paused': return chalk_1.default.yellow(status);
|
|
70
|
+
case 'stopped': return chalk_1.default.gray(status);
|
|
71
|
+
case 'error': return chalk_1.default.red(status);
|
|
72
|
+
default: return status;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
// ─── Setup Command ────────────────────────────────────────────────────────────
|
|
76
|
+
program
|
|
77
|
+
.command('setup')
|
|
78
|
+
.description('Interactive agent setup wizard')
|
|
79
|
+
.action(async () => {
|
|
80
|
+
banner();
|
|
81
|
+
console.log(chalk_1.default.white('Agent Setup Wizard\n'));
|
|
82
|
+
const config = (0, config_1.loadConfig)();
|
|
83
|
+
// Check if already set up
|
|
84
|
+
if (config.apiKey && config.agentId) {
|
|
85
|
+
const { overwrite } = await inquirer_1.default.prompt([{
|
|
86
|
+
type: 'confirm',
|
|
87
|
+
name: 'overwrite',
|
|
88
|
+
message: `Agent ${chalk_1.default.cyan(config.agentId)} already configured. Set up a new one?`,
|
|
89
|
+
default: false,
|
|
90
|
+
}]);
|
|
91
|
+
if (!overwrite) {
|
|
92
|
+
console.log(chalk_1.default.gray('Keeping existing configuration.'));
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
// Step 1: API URL
|
|
97
|
+
const { apiUrl } = await inquirer_1.default.prompt([{
|
|
98
|
+
type: 'input',
|
|
99
|
+
name: 'apiUrl',
|
|
100
|
+
message: 'Backend API URL:',
|
|
101
|
+
default: config.apiUrl,
|
|
102
|
+
}]);
|
|
103
|
+
(0, config_1.saveConfig)({ apiUrl });
|
|
104
|
+
// Step 2: Check server health
|
|
105
|
+
process.stdout.write(chalk_1.default.gray(' Checking server... '));
|
|
106
|
+
try {
|
|
107
|
+
const health = await api_1.api.health();
|
|
108
|
+
console.log(chalk_1.default.green(`OK`) + chalk_1.default.gray(` (${health.agents} agents, ${health.running} running)`));
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
console.log(chalk_1.default.red('FAILED'));
|
|
112
|
+
console.log(chalk_1.default.red(` Could not reach ${apiUrl}`));
|
|
113
|
+
console.log(chalk_1.default.gray(' Make sure the TrenchFeed server is running.'));
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
// Step 3: Check burn gate
|
|
117
|
+
let burnRequired = false;
|
|
118
|
+
let burnMint = '';
|
|
119
|
+
let burnAmount = 0;
|
|
120
|
+
let burnSymbol = '$TRENCH';
|
|
121
|
+
try {
|
|
122
|
+
const platformConfig = await api_1.api.getCliConfig();
|
|
123
|
+
burnRequired = platformConfig.burnRequired;
|
|
124
|
+
burnMint = platformConfig.burnMint ?? '';
|
|
125
|
+
burnAmount = platformConfig.burnAmount;
|
|
126
|
+
burnSymbol = platformConfig.burnTokenSymbol;
|
|
127
|
+
}
|
|
128
|
+
catch {
|
|
129
|
+
// If config endpoint unavailable, continue without burn gate info
|
|
130
|
+
}
|
|
131
|
+
if (burnRequired) {
|
|
132
|
+
console.log();
|
|
133
|
+
console.log(chalk_1.default.yellow(` Token burn required: ${burnAmount} ${burnSymbol}`));
|
|
134
|
+
console.log(chalk_1.default.gray(` Burn mint: ${burnMint}`));
|
|
135
|
+
console.log(chalk_1.default.gray(` Burn your tokens, then paste the transaction signature below.`));
|
|
136
|
+
console.log();
|
|
137
|
+
}
|
|
138
|
+
// Step 4: Auth
|
|
139
|
+
const { authMethod } = await inquirer_1.default.prompt([{
|
|
140
|
+
type: 'list',
|
|
141
|
+
name: 'authMethod',
|
|
142
|
+
message: 'How do you want to authenticate?',
|
|
143
|
+
choices: [
|
|
144
|
+
{ name: 'Solana wallet address (paste your pubkey)', value: 'wallet' },
|
|
145
|
+
{ name: 'Existing API key', value: 'apikey' },
|
|
146
|
+
],
|
|
147
|
+
}]);
|
|
148
|
+
if (authMethod === 'apikey') {
|
|
149
|
+
const { key } = await inquirer_1.default.prompt([{
|
|
150
|
+
type: 'password',
|
|
151
|
+
name: 'key',
|
|
152
|
+
message: 'API key:',
|
|
153
|
+
mask: '*',
|
|
154
|
+
}]);
|
|
155
|
+
(0, config_1.saveConfig)({ apiKey: key });
|
|
156
|
+
try {
|
|
157
|
+
const agents = await api_1.api.getMyAgent();
|
|
158
|
+
if (agents.length > 0) {
|
|
159
|
+
(0, config_1.saveConfig)({ agentId: agents[0].id, wallet: agents[0].wallet.publicKey });
|
|
160
|
+
console.log(chalk_1.default.green(`\nConnected to agent ${chalk_1.default.cyan(agents[0].name)} (${agents[0].id})`));
|
|
161
|
+
console.log(chalk_1.default.gray(` Wallet: ${agents[0].wallet.publicKey}`));
|
|
162
|
+
console.log(chalk_1.default.gray(` Status: ${statusColor(agents[0].status)}`));
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
catch {
|
|
167
|
+
console.log(chalk_1.default.red('Invalid API key.'));
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
// Wallet-based auth
|
|
173
|
+
const { walletAddress } = await inquirer_1.default.prompt([{
|
|
174
|
+
type: 'input',
|
|
175
|
+
name: 'walletAddress',
|
|
176
|
+
message: 'Your Solana wallet address:',
|
|
177
|
+
validate: (v) => /^[1-9A-HJ-NP-Za-km-z]{32,64}$/.test(v) || 'Invalid Solana address',
|
|
178
|
+
}]);
|
|
179
|
+
// Burn TX if required
|
|
180
|
+
let burnTxSignature;
|
|
181
|
+
if (burnRequired) {
|
|
182
|
+
const { burnTx } = await inquirer_1.default.prompt([{
|
|
183
|
+
type: 'input',
|
|
184
|
+
name: 'burnTx',
|
|
185
|
+
message: `Burn TX signature (${burnAmount} ${burnSymbol}):`,
|
|
186
|
+
validate: (v) => v.length >= 64 || 'Invalid transaction signature',
|
|
187
|
+
}]);
|
|
188
|
+
burnTxSignature = burnTx;
|
|
189
|
+
}
|
|
190
|
+
// Step 5: Agent configuration
|
|
191
|
+
console.log(chalk_1.default.white('\n─── Agent Identity ───\n'));
|
|
192
|
+
const { name } = await inquirer_1.default.prompt([{
|
|
193
|
+
type: 'input',
|
|
194
|
+
name: 'name',
|
|
195
|
+
message: 'Agent name:',
|
|
196
|
+
default: 'TrenchAgent',
|
|
197
|
+
}]);
|
|
198
|
+
const { personality } = await inquirer_1.default.prompt([{
|
|
199
|
+
type: 'input',
|
|
200
|
+
name: 'personality',
|
|
201
|
+
message: 'Agent personality:',
|
|
202
|
+
default: 'Sharp, data-driven Solana memecoin trader. Concise analysis, no fluff.',
|
|
203
|
+
}]);
|
|
204
|
+
const { strategy } = await inquirer_1.default.prompt([{
|
|
205
|
+
type: 'list',
|
|
206
|
+
name: 'strategy',
|
|
207
|
+
message: 'Trading strategy:',
|
|
208
|
+
choices: [
|
|
209
|
+
{ name: 'Ghost Filtered — Uses Ghost V2 insider detection to filter tokens', value: 'ghost-filtered' },
|
|
210
|
+
{ name: 'Momentum — Buys tokens showing price momentum', value: 'momentum' },
|
|
211
|
+
{ name: 'Volume Spike — Catches volume-driven moves', value: 'volume-spike' },
|
|
212
|
+
{ name: 'New Pairs — Snipes newly created tokens', value: 'new-pairs' },
|
|
213
|
+
{ name: 'Custom — Define your own strategy prompt', value: 'custom' },
|
|
214
|
+
],
|
|
215
|
+
}]);
|
|
216
|
+
let customStrategyPrompt;
|
|
217
|
+
if (strategy === 'custom') {
|
|
218
|
+
const { prompt } = await inquirer_1.default.prompt([{
|
|
219
|
+
type: 'editor',
|
|
220
|
+
name: 'prompt',
|
|
221
|
+
message: 'Custom strategy prompt (opens editor):',
|
|
222
|
+
}]);
|
|
223
|
+
customStrategyPrompt = prompt;
|
|
224
|
+
}
|
|
225
|
+
// ─── Trading Mode ──────────────────────────────────────────────
|
|
226
|
+
console.log(chalk_1.default.white('\n─── Trading Mode ───\n'));
|
|
227
|
+
let dryRun = true;
|
|
228
|
+
const { dryRunChoice } = await inquirer_1.default.prompt([{
|
|
229
|
+
type: 'list',
|
|
230
|
+
name: 'dryRunChoice',
|
|
231
|
+
message: 'Trading mode:',
|
|
232
|
+
choices: [
|
|
233
|
+
{ name: chalk_1.default.green('Paper Trading') + chalk_1.default.gray(' — Simulated, no real SOL at risk'), value: true },
|
|
234
|
+
{ name: chalk_1.default.red('Live Trading') + chalk_1.default.gray(' — Real SOL, real trades on-chain'), value: false },
|
|
235
|
+
],
|
|
236
|
+
}]);
|
|
237
|
+
dryRun = dryRunChoice;
|
|
238
|
+
if (!dryRun) {
|
|
239
|
+
const { confirm } = await inquirer_1.default.prompt([{
|
|
240
|
+
type: 'confirm',
|
|
241
|
+
name: 'confirm',
|
|
242
|
+
message: chalk_1.default.red('WARNING: Live trading uses REAL SOL. Losses are permanent. Continue?'),
|
|
243
|
+
default: false,
|
|
244
|
+
}]);
|
|
245
|
+
if (!confirm) {
|
|
246
|
+
dryRun = true;
|
|
247
|
+
console.log(chalk_1.default.gray('Switched to paper trading.'));
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
// ─── Execution Mode ────────────────────────────────────────────
|
|
251
|
+
console.log(chalk_1.default.white('\n─── Execution Speed ───\n'));
|
|
252
|
+
const { executionMode } = await inquirer_1.default.prompt([{
|
|
253
|
+
type: 'list',
|
|
254
|
+
name: 'executionMode',
|
|
255
|
+
message: 'Execution mode:',
|
|
256
|
+
choices: [
|
|
257
|
+
{ name: chalk_1.default.cyan('Sniper') + chalk_1.default.gray(' — Fastest execution (~400ms). Skips AI eval, auto-executes, fire-and-forget'), value: 'sniper' },
|
|
258
|
+
{ name: chalk_1.default.white('Default') + chalk_1.default.gray(' — Balanced. Ghost scoring + heuristic filters, visual pauses for streaming'), value: 'default' },
|
|
259
|
+
{ name: chalk_1.default.yellow('Careful') + chalk_1.default.gray(' — Full pipeline. Claude AI eval on every trade, all safety checks'), value: 'careful' },
|
|
260
|
+
],
|
|
261
|
+
}]);
|
|
262
|
+
// ─── RPC Endpoint ────────────────────────────────────────────
|
|
263
|
+
const { rpcUrl } = await inquirer_1.default.prompt([{
|
|
264
|
+
type: 'input',
|
|
265
|
+
name: 'rpcUrl',
|
|
266
|
+
message: `Your Solana RPC URL ${chalk_1.default.gray('(Helius, QuickNode, etc. — used for tx sending)')}:`,
|
|
267
|
+
default: '',
|
|
268
|
+
validate: (val) => {
|
|
269
|
+
if (!val)
|
|
270
|
+
return true; // optional
|
|
271
|
+
try {
|
|
272
|
+
new URL(val);
|
|
273
|
+
return true;
|
|
274
|
+
}
|
|
275
|
+
catch {
|
|
276
|
+
return 'Must be a valid URL';
|
|
277
|
+
}
|
|
278
|
+
},
|
|
279
|
+
}]);
|
|
280
|
+
// ─── Risk Management ───────────────────────────────────────────
|
|
281
|
+
console.log(chalk_1.default.white('\n─── Risk Management ───\n'));
|
|
282
|
+
const riskAnswers = await inquirer_1.default.prompt([
|
|
283
|
+
{ type: 'number', name: 'maxPositionSol', message: 'Max SOL per position:', default: 0.5 },
|
|
284
|
+
{ type: 'number', name: 'maxTradeSizeSol', message: 'Max SOL per single trade:', default: 1 },
|
|
285
|
+
{ type: 'number', name: 'maxConcurrentPositions', message: 'Max concurrent positions:', default: 3 },
|
|
286
|
+
{ type: 'number', name: 'stopLossPct', message: 'Stop loss % (e.g. 20 = sell if down 20%):', default: 20 },
|
|
287
|
+
{ type: 'number', name: 'takeProfitPct', message: 'Take profit % (e.g. 50 = sell if up 50%):', default: 50 },
|
|
288
|
+
{ type: 'number', name: 'dailyLossLimitSol', message: 'Daily loss limit SOL (stop trading after):', default: 2 },
|
|
289
|
+
]);
|
|
290
|
+
// ─── Ghost Insider Detection ───────────────────────────────────
|
|
291
|
+
console.log(chalk_1.default.white('\n─── Ghost V2 Insider Detection ───\n'));
|
|
292
|
+
const ghostAnswers = await inquirer_1.default.prompt([
|
|
293
|
+
{
|
|
294
|
+
type: 'confirm',
|
|
295
|
+
name: 'ghostEnabled',
|
|
296
|
+
message: 'Enable Ghost V2 insider wallet scanning?',
|
|
297
|
+
default: true,
|
|
298
|
+
},
|
|
299
|
+
{
|
|
300
|
+
type: 'number',
|
|
301
|
+
name: 'minGhostScore',
|
|
302
|
+
message: 'Min Ghost score to buy (0-100, higher = pickier):',
|
|
303
|
+
default: 60,
|
|
304
|
+
when: (a) => a.ghostEnabled === true,
|
|
305
|
+
},
|
|
306
|
+
]);
|
|
307
|
+
// ─── Automation ────────────────────────────────────────────────
|
|
308
|
+
console.log(chalk_1.default.white('\n─── Automation ───\n'));
|
|
309
|
+
const autoAnswers = await inquirer_1.default.prompt([
|
|
310
|
+
{
|
|
311
|
+
type: 'confirm',
|
|
312
|
+
name: 'autoTrade',
|
|
313
|
+
message: 'Auto-trade (execute trades automatically)?',
|
|
314
|
+
default: true,
|
|
315
|
+
},
|
|
316
|
+
{
|
|
317
|
+
type: 'number',
|
|
318
|
+
name: 'confirmAboveSol',
|
|
319
|
+
message: 'Require confirmation above SOL amount (0 = never):',
|
|
320
|
+
default: 0,
|
|
321
|
+
},
|
|
322
|
+
]);
|
|
323
|
+
if (autoAnswers.confirmAboveSol === 0)
|
|
324
|
+
delete autoAnswers.confirmAboveSol;
|
|
325
|
+
// ─── Copy Trading ──────────────────────────────────────────────
|
|
326
|
+
const copyAnswers = await inquirer_1.default.prompt([
|
|
327
|
+
{
|
|
328
|
+
type: 'confirm',
|
|
329
|
+
name: 'allowFollowers',
|
|
330
|
+
message: 'Allow others to copy-trade your agent?',
|
|
331
|
+
default: false,
|
|
332
|
+
},
|
|
333
|
+
{
|
|
334
|
+
type: 'number',
|
|
335
|
+
name: 'followerFeePercent',
|
|
336
|
+
message: 'Follower fee % (charged on profits):',
|
|
337
|
+
default: 1,
|
|
338
|
+
when: (a) => a.allowFollowers === true,
|
|
339
|
+
},
|
|
340
|
+
]);
|
|
341
|
+
// ─── Token Filters (opt-in sections) ───────────────────────────
|
|
342
|
+
console.log(chalk_1.default.white('\n─── Token Filters ───'));
|
|
343
|
+
console.log(chalk_1.default.gray(' Configure which tokens the agent can buy.'));
|
|
344
|
+
console.log(chalk_1.default.gray(' Leave blank/0 to skip any filter.\n'));
|
|
345
|
+
const { configureSections } = await inquirer_1.default.prompt([{
|
|
346
|
+
type: 'checkbox',
|
|
347
|
+
name: 'configureSections',
|
|
348
|
+
message: 'Which filter sections do you want to configure?',
|
|
349
|
+
choices: [
|
|
350
|
+
{ name: 'Market Cap & Liquidity', value: 'market' },
|
|
351
|
+
{ name: 'Token Age & Holders', value: 'age' },
|
|
352
|
+
{ name: 'Bonding Curve / Graduation', value: 'bonding' },
|
|
353
|
+
{ name: 'Transaction Activity', value: 'activity' },
|
|
354
|
+
{ name: 'Volume & Price Momentum', value: 'momentum' },
|
|
355
|
+
{ name: 'Anti-Rug (holder concentration, fresh wallets)', value: 'antirug' },
|
|
356
|
+
{ name: 'DexScreener & Security', value: 'security' },
|
|
357
|
+
{ name: 'Blacklisted Mints', value: 'blacklist' },
|
|
358
|
+
],
|
|
359
|
+
}]);
|
|
360
|
+
const sections = new Set(configureSections);
|
|
361
|
+
const filters = {};
|
|
362
|
+
// Helper: prompt for optional number, return undefined if 0/empty
|
|
363
|
+
const optNum = (answers, key) => {
|
|
364
|
+
const v = answers[key];
|
|
365
|
+
if (v != null && v !== 0)
|
|
366
|
+
filters[key] = v;
|
|
367
|
+
};
|
|
368
|
+
if (sections.has('market')) {
|
|
369
|
+
console.log(chalk_1.default.gray('\n Market Cap & Liquidity'));
|
|
370
|
+
const a = await inquirer_1.default.prompt([
|
|
371
|
+
{ type: 'number', name: 'minMarketCap', message: 'Min market cap USD (0=skip):', default: 0 },
|
|
372
|
+
{ type: 'number', name: 'maxMarketCap', message: 'Max market cap USD (0=skip):', default: 0 },
|
|
373
|
+
{ type: 'number', name: 'minLiquidity', message: 'Min liquidity SOL (0=skip):', default: 0 },
|
|
374
|
+
{ type: 'number', name: 'maxLiquidity', message: 'Max liquidity SOL (0=skip):', default: 0 },
|
|
375
|
+
]);
|
|
376
|
+
optNum(a, 'minMarketCap');
|
|
377
|
+
optNum(a, 'maxMarketCap');
|
|
378
|
+
optNum(a, 'minLiquidity');
|
|
379
|
+
optNum(a, 'maxLiquidity');
|
|
380
|
+
}
|
|
381
|
+
if (sections.has('age')) {
|
|
382
|
+
console.log(chalk_1.default.gray('\n Token Age & Holders'));
|
|
383
|
+
const a = await inquirer_1.default.prompt([
|
|
384
|
+
{ type: 'number', name: 'tokenAgeMinSeconds', message: 'Min token age seconds (0=skip):', default: 0 },
|
|
385
|
+
{ type: 'number', name: 'tokenAgeMaxSeconds', message: 'Max token age seconds (0=skip):', default: 0 },
|
|
386
|
+
{ type: 'number', name: 'minHolders', message: 'Min holders (0=skip):', default: 0 },
|
|
387
|
+
{ type: 'number', name: 'maxHolders', message: 'Max holders (0=skip):', default: 0 },
|
|
388
|
+
]);
|
|
389
|
+
optNum(a, 'tokenAgeMinSeconds');
|
|
390
|
+
optNum(a, 'tokenAgeMaxSeconds');
|
|
391
|
+
optNum(a, 'minHolders');
|
|
392
|
+
optNum(a, 'maxHolders');
|
|
393
|
+
}
|
|
394
|
+
if (sections.has('bonding')) {
|
|
395
|
+
console.log(chalk_1.default.gray('\n Bonding Curve / Graduation'));
|
|
396
|
+
const a = await inquirer_1.default.prompt([
|
|
397
|
+
{ type: 'number', name: 'minBondingProgress', message: 'Min bonding progress (0-1, e.g. 0.8 = 80%):', default: 0 },
|
|
398
|
+
{ type: 'number', name: 'maxBondingProgress', message: 'Max bonding progress (0-1, 0=skip):', default: 0 },
|
|
399
|
+
{ type: 'confirm', name: 'onlyGraduated', message: 'Only buy graduated tokens?', default: false },
|
|
400
|
+
{ type: 'confirm', name: 'onlyBondingCurve', message: 'Only buy bonding curve tokens?', default: false },
|
|
401
|
+
]);
|
|
402
|
+
optNum(a, 'minBondingProgress');
|
|
403
|
+
optNum(a, 'maxBondingProgress');
|
|
404
|
+
if (a.onlyGraduated)
|
|
405
|
+
filters.onlyGraduated = true;
|
|
406
|
+
if (a.onlyBondingCurve)
|
|
407
|
+
filters.onlyBondingCurve = true;
|
|
408
|
+
}
|
|
409
|
+
if (sections.has('activity')) {
|
|
410
|
+
console.log(chalk_1.default.gray('\n Transaction Activity'));
|
|
411
|
+
const a = await inquirer_1.default.prompt([
|
|
412
|
+
{ type: 'number', name: 'minBuyCount', message: 'Min buy transactions (0=skip):', default: 0 },
|
|
413
|
+
{ type: 'number', name: 'maxBuyCount', message: 'Max buy transactions (0=skip):', default: 0 },
|
|
414
|
+
{ type: 'number', name: 'minSellCount', message: 'Min sell transactions (0=skip):', default: 0 },
|
|
415
|
+
{ type: 'number', name: 'maxSellCount', message: 'Max sell transactions (0=skip):', default: 0 },
|
|
416
|
+
{ type: 'number', name: 'minTxCount', message: 'Min total transactions (0=skip):', default: 0 },
|
|
417
|
+
]);
|
|
418
|
+
optNum(a, 'minBuyCount');
|
|
419
|
+
optNum(a, 'maxBuyCount');
|
|
420
|
+
optNum(a, 'minSellCount');
|
|
421
|
+
optNum(a, 'maxSellCount');
|
|
422
|
+
optNum(a, 'minTxCount');
|
|
423
|
+
}
|
|
424
|
+
if (sections.has('momentum')) {
|
|
425
|
+
console.log(chalk_1.default.gray('\n Volume & Price Momentum'));
|
|
426
|
+
const a = await inquirer_1.default.prompt([
|
|
427
|
+
{ type: 'number', name: 'minVolume24h', message: 'Min 24h volume USD (0=skip):', default: 0 },
|
|
428
|
+
{ type: 'number', name: 'maxVolume24h', message: 'Max 24h volume USD (0=skip):', default: 0 },
|
|
429
|
+
{ type: 'number', name: 'minPriceChange5m', message: 'Min 5m price change % (0=skip):', default: 0 },
|
|
430
|
+
{ type: 'number', name: 'maxPriceChange5m', message: 'Max 5m price change % (0=skip):', default: 0 },
|
|
431
|
+
{ type: 'number', name: 'minPriceChange1h', message: 'Min 1h price change % (0=skip):', default: 0 },
|
|
432
|
+
{ type: 'number', name: 'maxPriceChange1h', message: 'Max 1h price change % (0=skip):', default: 0 },
|
|
433
|
+
]);
|
|
434
|
+
optNum(a, 'minVolume24h');
|
|
435
|
+
optNum(a, 'maxVolume24h');
|
|
436
|
+
optNum(a, 'minPriceChange5m');
|
|
437
|
+
optNum(a, 'maxPriceChange5m');
|
|
438
|
+
optNum(a, 'minPriceChange1h');
|
|
439
|
+
optNum(a, 'maxPriceChange1h');
|
|
440
|
+
}
|
|
441
|
+
if (sections.has('antirug')) {
|
|
442
|
+
console.log(chalk_1.default.gray('\n Anti-Rug Filters'));
|
|
443
|
+
const a = await inquirer_1.default.prompt([
|
|
444
|
+
{ type: 'number', name: 'maxTopHolderPct', message: 'Max top holder % (0=skip, e.g. 30):', default: 0 },
|
|
445
|
+
{ type: 'number', name: 'maxDevHoldingsPct', message: 'Max dev holdings % (0=skip, e.g. 20):', default: 0 },
|
|
446
|
+
{ type: 'confirm', name: 'rejectDevBlacklisted', message: 'Reject blacklisted devs?', default: false },
|
|
447
|
+
{ type: 'number', name: 'maxFreshWalletPct', message: 'Max fresh wallet % of buys (0=skip):', default: 0 },
|
|
448
|
+
{ type: 'number', name: 'maxBundledBuysPct', message: 'Max bundled/sniped buys % (0=skip):', default: 0 },
|
|
449
|
+
]);
|
|
450
|
+
optNum(a, 'maxTopHolderPct');
|
|
451
|
+
optNum(a, 'maxDevHoldingsPct');
|
|
452
|
+
if (a.rejectDevBlacklisted)
|
|
453
|
+
filters.rejectDevBlacklisted = true;
|
|
454
|
+
optNum(a, 'maxFreshWalletPct');
|
|
455
|
+
optNum(a, 'maxBundledBuysPct');
|
|
456
|
+
}
|
|
457
|
+
if (sections.has('security')) {
|
|
458
|
+
console.log(chalk_1.default.gray('\n DexScreener & Security'));
|
|
459
|
+
const a = await inquirer_1.default.prompt([
|
|
460
|
+
{ type: 'confirm', name: 'requireDexPaid', message: 'Require paid DexScreener listing?', default: false },
|
|
461
|
+
{ type: 'number', name: 'minDexBoosts', message: 'Min DexScreener boosts (0=skip):', default: 0 },
|
|
462
|
+
{ type: 'confirm', name: 'requireMintRevoked', message: 'Require mint authority revoked?', default: false },
|
|
463
|
+
{ type: 'confirm', name: 'requireFreezeRevoked', message: 'Require freeze authority revoked?', default: false },
|
|
464
|
+
]);
|
|
465
|
+
if (a.requireDexPaid)
|
|
466
|
+
filters.requireDexPaid = true;
|
|
467
|
+
optNum(a, 'minDexBoosts');
|
|
468
|
+
if (a.requireMintRevoked)
|
|
469
|
+
filters.requireMintRevoked = true;
|
|
470
|
+
if (a.requireFreezeRevoked)
|
|
471
|
+
filters.requireFreezeRevoked = true;
|
|
472
|
+
}
|
|
473
|
+
if (sections.has('blacklist')) {
|
|
474
|
+
console.log(chalk_1.default.gray('\n Blacklisted Mints'));
|
|
475
|
+
const { mints } = await inquirer_1.default.prompt([{
|
|
476
|
+
type: 'input',
|
|
477
|
+
name: 'mints',
|
|
478
|
+
message: 'Blacklisted token addresses (comma-separated, or empty):',
|
|
479
|
+
default: '',
|
|
480
|
+
}]);
|
|
481
|
+
const mintList = mints.split(',').map((m) => m.trim()).filter(Boolean);
|
|
482
|
+
if (mintList.length > 0)
|
|
483
|
+
filters.blacklistedMints = mintList;
|
|
484
|
+
}
|
|
485
|
+
// ─── Twitter Config (opt-in) ───────────────────────────────────
|
|
486
|
+
console.log();
|
|
487
|
+
const { configureTwitter } = await inquirer_1.default.prompt([{
|
|
488
|
+
type: 'confirm',
|
|
489
|
+
name: 'configureTwitter',
|
|
490
|
+
message: 'Configure Twitter auto-posting?',
|
|
491
|
+
default: false,
|
|
492
|
+
}]);
|
|
493
|
+
let twitter;
|
|
494
|
+
if (configureTwitter) {
|
|
495
|
+
console.log(chalk_1.default.white('\n─── Twitter Config ───\n'));
|
|
496
|
+
const tw = await inquirer_1.default.prompt([
|
|
497
|
+
{ type: 'confirm', name: 'enabled', message: 'Enable Twitter posting?', default: true },
|
|
498
|
+
{ type: 'confirm', name: 'tweetOnBuy', message: 'Tweet on buy?', default: true },
|
|
499
|
+
{ type: 'confirm', name: 'tweetOnSell', message: 'Tweet on sell?', default: true },
|
|
500
|
+
{ type: 'confirm', name: 'dailySummary', message: 'Post daily summary?', default: false },
|
|
501
|
+
{ type: 'confirm', name: 'approvalRequired', message: 'Require approval before posting?', default: false },
|
|
502
|
+
{ type: 'confirm', name: 'autoPost', message: 'Auto-post tweets?', default: true },
|
|
503
|
+
{ type: 'number', name: 'maxPostsPerDay', message: 'Max posts per day:', default: 10 },
|
|
504
|
+
{ type: 'number', name: 'activeHoursStart', message: 'Active hours start (0-23):', default: 0 },
|
|
505
|
+
{ type: 'number', name: 'activeHoursEnd', message: 'Active hours end (0-24):', default: 24 },
|
|
506
|
+
{ type: 'confirm', name: 'weekendPosting', message: 'Post on weekends?', default: true },
|
|
507
|
+
{ type: 'confirm', name: 'twDryRun', message: 'Twitter dry run (log but don\'t post)?', default: true },
|
|
508
|
+
]);
|
|
509
|
+
const { twSystemPrompt } = await inquirer_1.default.prompt([{
|
|
510
|
+
type: 'input',
|
|
511
|
+
name: 'twSystemPrompt',
|
|
512
|
+
message: 'Twitter persona prompt:',
|
|
513
|
+
default: 'You are a Solana memecoin trading agent. Be concise, data-first. Max 280 chars.',
|
|
514
|
+
}]);
|
|
515
|
+
twitter = {
|
|
516
|
+
enabled: tw.enabled,
|
|
517
|
+
events: {
|
|
518
|
+
buy: tw.tweetOnBuy,
|
|
519
|
+
sell: tw.tweetOnSell,
|
|
520
|
+
dailySummary: tw.dailySummary,
|
|
521
|
+
},
|
|
522
|
+
persona: {
|
|
523
|
+
systemPrompt: twSystemPrompt,
|
|
524
|
+
},
|
|
525
|
+
approvalRequired: tw.approvalRequired,
|
|
526
|
+
autoPost: tw.autoPost,
|
|
527
|
+
maxPostsPerDay: tw.maxPostsPerDay,
|
|
528
|
+
activeHoursStart: tw.activeHoursStart,
|
|
529
|
+
activeHoursEnd: tw.activeHoursEnd,
|
|
530
|
+
weekendPosting: tw.weekendPosting,
|
|
531
|
+
dryRun: tw.twDryRun,
|
|
532
|
+
};
|
|
533
|
+
}
|
|
534
|
+
// ─── Voice Config (opt-in) ─────────────────────────────────────
|
|
535
|
+
const { configureVoice } = await inquirer_1.default.prompt([{
|
|
536
|
+
type: 'confirm',
|
|
537
|
+
name: 'configureVoice',
|
|
538
|
+
message: 'Configure voice narration?',
|
|
539
|
+
default: false,
|
|
540
|
+
}]);
|
|
541
|
+
let voice;
|
|
542
|
+
if (configureVoice) {
|
|
543
|
+
console.log(chalk_1.default.white('\n─── Voice Config ───\n'));
|
|
544
|
+
const v = await inquirer_1.default.prompt([
|
|
545
|
+
{ type: 'confirm', name: 'enabled', message: 'Enable voice narration?', default: true },
|
|
546
|
+
{
|
|
547
|
+
type: 'list', name: 'voice', message: 'Voice:', choices: [
|
|
548
|
+
{ name: 'Ryan — English male, clear, confident', value: 'Ryan' },
|
|
549
|
+
{ name: 'Aiden — English male, calm, analytical', value: 'Aiden' },
|
|
550
|
+
{ name: 'Cherry — Female, bright, energetic', value: 'Cherry' },
|
|
551
|
+
{ name: 'Serena — Female, warm, composed', value: 'Serena' },
|
|
552
|
+
{ name: 'Ethan — Male, deep, authoritative', value: 'Ethan' },
|
|
553
|
+
{ name: 'Jada — Female, smooth, professional', value: 'Jada' },
|
|
554
|
+
{ name: 'Kai — Male, youthful, energetic', value: 'Kai' },
|
|
555
|
+
],
|
|
556
|
+
},
|
|
557
|
+
{ type: 'input', name: 'instruct', message: 'Voice instruction:', default: 'Speak clearly with a confident, analytical tone' },
|
|
558
|
+
{ type: 'confirm', name: 'narrateAll', message: 'Narrate all events (not just trades)?', default: false },
|
|
559
|
+
]);
|
|
560
|
+
voice = v;
|
|
561
|
+
}
|
|
562
|
+
// ─── Build final config ────────────────────────────────────────
|
|
563
|
+
const agentConfig = {
|
|
564
|
+
name,
|
|
565
|
+
personality,
|
|
566
|
+
strategy,
|
|
567
|
+
executionMode,
|
|
568
|
+
...(rpcUrl ? { rpcUrl } : {}),
|
|
569
|
+
dryRun,
|
|
570
|
+
...riskAnswers,
|
|
571
|
+
...ghostAnswers,
|
|
572
|
+
...autoAnswers,
|
|
573
|
+
...copyAnswers,
|
|
574
|
+
...filters,
|
|
575
|
+
};
|
|
576
|
+
if (customStrategyPrompt)
|
|
577
|
+
agentConfig.customStrategyPrompt = customStrategyPrompt;
|
|
578
|
+
if (twitter)
|
|
579
|
+
agentConfig.twitter = twitter;
|
|
580
|
+
if (voice)
|
|
581
|
+
agentConfig.voice = voice;
|
|
582
|
+
// Step 6: Register
|
|
583
|
+
console.log(chalk_1.default.gray('\nDeploying agent...'));
|
|
584
|
+
try {
|
|
585
|
+
const result = await api_1.api.register(walletAddress, {
|
|
586
|
+
config: agentConfig,
|
|
587
|
+
burnTxSignature,
|
|
588
|
+
});
|
|
589
|
+
(0, config_1.saveConfig)({
|
|
590
|
+
apiKey: result.apiKey,
|
|
591
|
+
agentId: result.agentId,
|
|
592
|
+
wallet: result.wallet,
|
|
593
|
+
});
|
|
594
|
+
console.log(chalk_1.default.green('\nAgent deployed successfully!\n'));
|
|
595
|
+
console.log(chalk_1.default.white(` Agent ID: ${chalk_1.default.cyan(result.agentId)}`));
|
|
596
|
+
console.log(chalk_1.default.white(` Wallet: ${chalk_1.default.cyan(result.wallet)}`));
|
|
597
|
+
console.log(chalk_1.default.white(` Mode: ${dryRun ? chalk_1.default.green('PAPER') : chalk_1.default.red('LIVE')}`));
|
|
598
|
+
console.log(chalk_1.default.white(` Execution: ${executionMode === 'sniper' ? chalk_1.default.cyan('SNIPER') : executionMode === 'careful' ? chalk_1.default.yellow('CAREFUL') : chalk_1.default.white('DEFAULT')}`));
|
|
599
|
+
console.log(chalk_1.default.white(` Strategy: ${chalk_1.default.cyan(strategy)}`));
|
|
600
|
+
if (rpcUrl)
|
|
601
|
+
console.log(chalk_1.default.white(` RPC: ${chalk_1.default.gray(rpcUrl.replace(/api[-_]?key=[^&]+/i, 'api-key=***'))}`));
|
|
602
|
+
console.log(chalk_1.default.gray(`\n Config saved to ${(0, config_1.getConfigPath)()}`));
|
|
603
|
+
console.log(chalk_1.default.gray(' API key stored securely.\n'));
|
|
604
|
+
console.log(chalk_1.default.white('Next steps:'));
|
|
605
|
+
console.log(chalk_1.default.gray(' trenchfeed start — Start your agent'));
|
|
606
|
+
console.log(chalk_1.default.gray(' trenchfeed status — View agent status'));
|
|
607
|
+
console.log(chalk_1.default.gray(' trenchfeed stream — Watch live activity'));
|
|
608
|
+
if (!dryRun) {
|
|
609
|
+
console.log(chalk_1.default.yellow(`\n Fund your wallet to start live trading:`));
|
|
610
|
+
console.log(chalk_1.default.cyan(` ${result.wallet}`));
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
catch (err) {
|
|
614
|
+
console.log(chalk_1.default.red(`\nSetup failed: ${err instanceof Error ? err.message : 'Unknown error'}`));
|
|
615
|
+
}
|
|
616
|
+
});
|
|
617
|
+
// ─── Status Command ───────────────────────────────────────────────────────────
|
|
618
|
+
program
|
|
619
|
+
.command('status')
|
|
620
|
+
.description('Show agent status, PnL, and positions')
|
|
621
|
+
.action(async () => {
|
|
622
|
+
const { agentId } = requireSetup();
|
|
623
|
+
try {
|
|
624
|
+
const agent = await api_1.api.getAgent(agentId);
|
|
625
|
+
const config = agent.config;
|
|
626
|
+
console.log();
|
|
627
|
+
console.log(chalk_1.default.white.bold(config.name ?? agentId));
|
|
628
|
+
console.log(chalk_1.default.gray(` ID: ${agentId}`));
|
|
629
|
+
console.log(chalk_1.default.gray(` Status: ${statusColor(agent.status)}`));
|
|
630
|
+
console.log(chalk_1.default.gray(` Strategy: ${config.strategy}`));
|
|
631
|
+
console.log(chalk_1.default.gray(` Mode: ${config.dryRun !== false ? chalk_1.default.green('PAPER') : chalk_1.default.red('LIVE')}`));
|
|
632
|
+
console.log(chalk_1.default.gray(` Wallet: ${agent.wallet.publicKey}`));
|
|
633
|
+
console.log();
|
|
634
|
+
console.log(chalk_1.default.white(' PnL'));
|
|
635
|
+
console.log(` Open: ${formatSol(agent.openPnlSol)}`);
|
|
636
|
+
console.log(` Closed: ${formatSol(agent.closedPnlSol)}`);
|
|
637
|
+
console.log(` Total: ${formatSol(agent.openPnlSol + agent.closedPnlSol)}`);
|
|
638
|
+
console.log(` Trades: ${chalk_1.default.white(agent.totalTrades.toString())}`);
|
|
639
|
+
if (agent.positions && agent.positions.length > 0) {
|
|
640
|
+
console.log();
|
|
641
|
+
console.log(chalk_1.default.white(` Positions (${agent.positions.length})`));
|
|
642
|
+
for (const p of agent.positions) {
|
|
643
|
+
const sym = p.symbol ?? p.mint.slice(0, 8);
|
|
644
|
+
const pnl = p.unrealizedPnl != null ? formatSol(p.unrealizedPnl) : chalk_1.default.gray('—');
|
|
645
|
+
const pnlPct = p.unrealizedPnlPct != null ? formatPct(p.unrealizedPnlPct) : '';
|
|
646
|
+
console.log(` ${chalk_1.default.cyan(sym.padEnd(12))} ${p.amountSol.toFixed(4)} SOL ${pnl} ${pnlPct}`);
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
if (agent.startedAt) {
|
|
650
|
+
console.log();
|
|
651
|
+
console.log(chalk_1.default.gray(` Started ${timeAgo(agent.startedAt)}`));
|
|
652
|
+
}
|
|
653
|
+
console.log();
|
|
654
|
+
}
|
|
655
|
+
catch (err) {
|
|
656
|
+
console.log(chalk_1.default.red(`Failed: ${err instanceof Error ? err.message : 'Unknown error'}`));
|
|
657
|
+
}
|
|
658
|
+
});
|
|
659
|
+
// ─── Start / Stop / Pause / Resume / Emergency ───────────────────────────────
|
|
660
|
+
for (const [cmd, desc, fn] of [
|
|
661
|
+
['start', 'Start trading', (id) => api_1.api.startAgent(id)],
|
|
662
|
+
['stop', 'Stop trading', (id) => api_1.api.stopAgent(id)],
|
|
663
|
+
['pause', 'Pause trading', (id) => api_1.api.pauseAgent(id)],
|
|
664
|
+
['resume', 'Resume trading', (id) => api_1.api.resumeAgent(id)],
|
|
665
|
+
['emergency', 'Emergency stop + sell all positions', (id) => api_1.api.emergencyStop(id)],
|
|
666
|
+
]) {
|
|
667
|
+
program
|
|
668
|
+
.command(cmd)
|
|
669
|
+
.description(desc)
|
|
670
|
+
.action(async () => {
|
|
671
|
+
const { agentId } = requireSetup();
|
|
672
|
+
if (cmd === 'emergency') {
|
|
673
|
+
const { confirm } = await inquirer_1.default.prompt([{
|
|
674
|
+
type: 'confirm',
|
|
675
|
+
name: 'confirm',
|
|
676
|
+
message: chalk_1.default.red('Emergency stop will sell ALL positions immediately. Continue?'),
|
|
677
|
+
default: false,
|
|
678
|
+
}]);
|
|
679
|
+
if (!confirm)
|
|
680
|
+
return;
|
|
681
|
+
}
|
|
682
|
+
try {
|
|
683
|
+
const result = await fn(agentId);
|
|
684
|
+
console.log(chalk_1.default.green(`Agent ${cmd}: ${result.status}`));
|
|
685
|
+
}
|
|
686
|
+
catch (err) {
|
|
687
|
+
console.log(chalk_1.default.red(`Failed: ${err instanceof Error ? err.message : 'Unknown error'}`));
|
|
688
|
+
}
|
|
689
|
+
});
|
|
690
|
+
}
|
|
691
|
+
// ─── Config Command ───────────────────────────────────────────────────────────
|
|
692
|
+
program
|
|
693
|
+
.command('config')
|
|
694
|
+
.description('View or update agent config')
|
|
695
|
+
.option('-s, --set <key=value>', 'Set a config value (e.g. --set maxPositionSol=1)')
|
|
696
|
+
.action(async (opts) => {
|
|
697
|
+
const { agentId } = requireSetup();
|
|
698
|
+
if (opts.set) {
|
|
699
|
+
const eq = opts.set.indexOf('=');
|
|
700
|
+
if (eq === -1) {
|
|
701
|
+
console.log(chalk_1.default.red('Usage: --set key=value'));
|
|
702
|
+
return;
|
|
703
|
+
}
|
|
704
|
+
const key = opts.set.slice(0, eq);
|
|
705
|
+
const value = opts.set.slice(eq + 1);
|
|
706
|
+
let parsed = value;
|
|
707
|
+
if (value === 'true')
|
|
708
|
+
parsed = true;
|
|
709
|
+
else if (value === 'false')
|
|
710
|
+
parsed = false;
|
|
711
|
+
else if (!isNaN(Number(value)) && value !== '')
|
|
712
|
+
parsed = Number(value);
|
|
713
|
+
try {
|
|
714
|
+
await api_1.api.updateConfig(agentId, { [key]: parsed });
|
|
715
|
+
console.log(chalk_1.default.green(`Updated ${key} = ${JSON.stringify(parsed)}`));
|
|
716
|
+
}
|
|
717
|
+
catch (err) {
|
|
718
|
+
console.log(chalk_1.default.red(`Failed: ${err instanceof Error ? err.message : 'Unknown error'}`));
|
|
719
|
+
}
|
|
720
|
+
return;
|
|
721
|
+
}
|
|
722
|
+
try {
|
|
723
|
+
const agent = await api_1.api.getAgent(agentId);
|
|
724
|
+
console.log();
|
|
725
|
+
console.log(chalk_1.default.white.bold('Agent Config'));
|
|
726
|
+
console.log();
|
|
727
|
+
const cfg = agent.config;
|
|
728
|
+
const entries = Object.entries(cfg).filter(([, v]) => v !== undefined && v !== null);
|
|
729
|
+
const maxKeyLen = Math.max(...entries.map(([k]) => k.length));
|
|
730
|
+
for (const [k, v] of entries) {
|
|
731
|
+
const val = typeof v === 'object' ? JSON.stringify(v) : String(v);
|
|
732
|
+
console.log(` ${chalk_1.default.gray(k.padEnd(maxKeyLen + 2))}${chalk_1.default.white(val)}`);
|
|
733
|
+
}
|
|
734
|
+
console.log();
|
|
735
|
+
console.log(chalk_1.default.gray(` Update: trenchfeed config --set key=value`));
|
|
736
|
+
console.log();
|
|
737
|
+
}
|
|
738
|
+
catch (err) {
|
|
739
|
+
console.log(chalk_1.default.red(`Failed: ${err instanceof Error ? err.message : 'Unknown error'}`));
|
|
740
|
+
}
|
|
741
|
+
});
|
|
742
|
+
// ─── Wallet Command ───────────────────────────────────────────────────────────
|
|
743
|
+
program
|
|
744
|
+
.command('wallet')
|
|
745
|
+
.description('Show wallet address and balance')
|
|
746
|
+
.option('-w, --withdraw <address>', 'Withdraw SOL to address')
|
|
747
|
+
.option('-a, --amount <sol>', 'Amount of SOL to withdraw')
|
|
748
|
+
.action(async (opts) => {
|
|
749
|
+
const { agentId } = requireSetup();
|
|
750
|
+
if (opts.withdraw) {
|
|
751
|
+
const amount = Number(opts.amount);
|
|
752
|
+
if (!amount || isNaN(amount) || amount <= 0) {
|
|
753
|
+
console.log(chalk_1.default.red('Specify amount with --amount <sol>'));
|
|
754
|
+
return;
|
|
755
|
+
}
|
|
756
|
+
const { confirm } = await inquirer_1.default.prompt([{
|
|
757
|
+
type: 'confirm',
|
|
758
|
+
name: 'confirm',
|
|
759
|
+
message: `Withdraw ${amount} SOL to ${opts.withdraw}?`,
|
|
760
|
+
default: false,
|
|
761
|
+
}]);
|
|
762
|
+
if (!confirm)
|
|
763
|
+
return;
|
|
764
|
+
try {
|
|
765
|
+
const result = await api_1.api.withdraw(agentId, opts.withdraw, amount);
|
|
766
|
+
console.log(chalk_1.default.green(`Withdrawn! TX: ${result.signature}`));
|
|
767
|
+
}
|
|
768
|
+
catch (err) {
|
|
769
|
+
console.log(chalk_1.default.red(`Failed: ${err instanceof Error ? err.message : 'Unknown error'}`));
|
|
770
|
+
}
|
|
771
|
+
return;
|
|
772
|
+
}
|
|
773
|
+
try {
|
|
774
|
+
const wallet = await api_1.api.getWallet(agentId);
|
|
775
|
+
console.log();
|
|
776
|
+
console.log(chalk_1.default.white.bold('Agent Wallet'));
|
|
777
|
+
console.log(` Address: ${chalk_1.default.cyan(wallet.publicKey)}`);
|
|
778
|
+
console.log(` Balance: ${chalk_1.default.white(wallet.balance.toFixed(4))} SOL`);
|
|
779
|
+
console.log(` Lamports: ${chalk_1.default.gray(wallet.lamports.toLocaleString())}`);
|
|
780
|
+
console.log();
|
|
781
|
+
}
|
|
782
|
+
catch (err) {
|
|
783
|
+
console.log(chalk_1.default.red(`Failed: ${err instanceof Error ? err.message : 'Unknown error'}`));
|
|
784
|
+
}
|
|
785
|
+
});
|
|
786
|
+
// ─── Trades Command ───────────────────────────────────────────────────────────
|
|
787
|
+
program
|
|
788
|
+
.command('trades')
|
|
789
|
+
.description('Show recent trade history')
|
|
790
|
+
.option('-n, --limit <number>', 'Number of trades to show', '20')
|
|
791
|
+
.action(async (opts) => {
|
|
792
|
+
const { agentId } = requireSetup();
|
|
793
|
+
try {
|
|
794
|
+
const trades = await api_1.api.getTrades(agentId, Number(opts.limit));
|
|
795
|
+
if (trades.length === 0) {
|
|
796
|
+
console.log(chalk_1.default.gray('\nNo trades yet.\n'));
|
|
797
|
+
return;
|
|
798
|
+
}
|
|
799
|
+
console.log();
|
|
800
|
+
console.log(chalk_1.default.white.bold(`Recent Trades (${trades.length})`));
|
|
801
|
+
console.log();
|
|
802
|
+
for (const t of trades) {
|
|
803
|
+
const sym = (t.symbol ?? t.mint.slice(0, 8)).padEnd(10);
|
|
804
|
+
const action = t.action === 'buy'
|
|
805
|
+
? chalk_1.default.green('BUY ')
|
|
806
|
+
: chalk_1.default.red('SELL');
|
|
807
|
+
const sol = t.amountSol != null ? `${t.amountSol.toFixed(4)} SOL` : '—';
|
|
808
|
+
const pnl = t.pnlSol != null ? formatSol(t.pnlSol) : '';
|
|
809
|
+
const time = chalk_1.default.gray(timeAgo(t.createdAt));
|
|
810
|
+
console.log(` ${action} ${chalk_1.default.cyan(sym)} ${sol.padEnd(14)} ${pnl.padEnd(20)} ${time}`);
|
|
811
|
+
if (t.reason) {
|
|
812
|
+
console.log(chalk_1.default.gray(` ${t.reason}`));
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
console.log();
|
|
816
|
+
}
|
|
817
|
+
catch (err) {
|
|
818
|
+
console.log(chalk_1.default.red(`Failed: ${err instanceof Error ? err.message : 'Unknown error'}`));
|
|
819
|
+
}
|
|
820
|
+
});
|
|
821
|
+
// ─── Stream Command ───────────────────────────────────────────────────────────
|
|
822
|
+
program
|
|
823
|
+
.command('stream')
|
|
824
|
+
.description('Live stream agent events via WebSocket')
|
|
825
|
+
.action(async () => {
|
|
826
|
+
const { agentId } = requireSetup();
|
|
827
|
+
const config = (0, config_1.loadConfig)();
|
|
828
|
+
const wsUrl = config.apiUrl.replace(/^http/, 'ws');
|
|
829
|
+
console.log(chalk_1.default.gray(`Connecting to ${wsUrl}/ws/agents/${agentId}...\n`));
|
|
830
|
+
const ws = new ws_1.WebSocket(`${wsUrl}/ws/agents/${agentId}`);
|
|
831
|
+
ws.on('open', () => {
|
|
832
|
+
console.log(chalk_1.default.green('Connected. Streaming events...\n'));
|
|
833
|
+
});
|
|
834
|
+
ws.on('message', (data) => {
|
|
835
|
+
try {
|
|
836
|
+
const event = JSON.parse(data.toString());
|
|
837
|
+
const type = event.type;
|
|
838
|
+
switch (type) {
|
|
839
|
+
case 'thought':
|
|
840
|
+
console.log(chalk_1.default.gray(`[thought] ${event.text}`));
|
|
841
|
+
break;
|
|
842
|
+
case 'trade':
|
|
843
|
+
console.log(chalk_1.default.yellow(`[trade] ${event.action} ${event.symbol ?? event.mint} — ${event.amountSol} SOL`));
|
|
844
|
+
break;
|
|
845
|
+
case 'pnl_update':
|
|
846
|
+
console.log(chalk_1.default.blue(`[pnl] open: ${formatSol(event.openPnlSol)} | closed: ${formatSol(event.closedPnlSol)} | positions: ${event.positions?.length ?? 0}`));
|
|
847
|
+
break;
|
|
848
|
+
case 'status_change':
|
|
849
|
+
console.log(chalk_1.default.white(`[status] ${event.oldStatus} → ${statusColor(event.newStatus)}`));
|
|
850
|
+
break;
|
|
851
|
+
case 'error':
|
|
852
|
+
console.log(chalk_1.default.red(`[error] ${event.message}`));
|
|
853
|
+
break;
|
|
854
|
+
case 'chat_response':
|
|
855
|
+
console.log(chalk_1.default.magenta(`[chat] ${event.text}`));
|
|
856
|
+
break;
|
|
857
|
+
default:
|
|
858
|
+
console.log(chalk_1.default.gray(`[${type}] ${JSON.stringify(event).slice(0, 120)}`));
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
catch {
|
|
862
|
+
// ignore malformed
|
|
863
|
+
}
|
|
864
|
+
});
|
|
865
|
+
ws.on('close', () => {
|
|
866
|
+
console.log(chalk_1.default.gray('\nDisconnected.'));
|
|
867
|
+
process.exit(0);
|
|
868
|
+
});
|
|
869
|
+
ws.on('error', (err) => {
|
|
870
|
+
console.log(chalk_1.default.red(`WebSocket error: ${err.message}`));
|
|
871
|
+
process.exit(1);
|
|
872
|
+
});
|
|
873
|
+
process.on('SIGINT', () => {
|
|
874
|
+
console.log(chalk_1.default.gray('\nClosing stream...'));
|
|
875
|
+
ws.close();
|
|
876
|
+
});
|
|
877
|
+
});
|
|
878
|
+
// ─── Chat Command ─────────────────────────────────────────────────────────────
|
|
879
|
+
program
|
|
880
|
+
.command('chat <message>')
|
|
881
|
+
.description('Send a message to your agent')
|
|
882
|
+
.action(async (message) => {
|
|
883
|
+
const { agentId } = requireSetup();
|
|
884
|
+
const config = (0, config_1.loadConfig)();
|
|
885
|
+
const wsUrl = config.apiUrl.replace(/^http/, 'ws');
|
|
886
|
+
const ws = new ws_1.WebSocket(`${wsUrl}/ws/agents/${agentId}`);
|
|
887
|
+
ws.on('open', () => {
|
|
888
|
+
ws.send(JSON.stringify({ type: 'chat', text: message }));
|
|
889
|
+
console.log(chalk_1.default.gray(`> ${message}`));
|
|
890
|
+
});
|
|
891
|
+
ws.on('message', (data) => {
|
|
892
|
+
try {
|
|
893
|
+
const event = JSON.parse(data.toString());
|
|
894
|
+
if (event.type === 'chat_response') {
|
|
895
|
+
console.log(chalk_1.default.cyan(`\n${event.text}\n`));
|
|
896
|
+
ws.close();
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
catch {
|
|
900
|
+
// ignore
|
|
901
|
+
}
|
|
902
|
+
});
|
|
903
|
+
setTimeout(() => {
|
|
904
|
+
console.log(chalk_1.default.gray('(timeout — no response)'));
|
|
905
|
+
ws.close();
|
|
906
|
+
process.exit(0);
|
|
907
|
+
}, 30000);
|
|
908
|
+
ws.on('close', () => process.exit(0));
|
|
909
|
+
ws.on('error', (err) => {
|
|
910
|
+
console.log(chalk_1.default.red(`Error: ${err.message}`));
|
|
911
|
+
process.exit(1);
|
|
912
|
+
});
|
|
913
|
+
});
|
|
914
|
+
// ─── Logout Command ──────────────────────────────────────────────────────────
|
|
915
|
+
program
|
|
916
|
+
.command('logout')
|
|
917
|
+
.description('Clear stored credentials')
|
|
918
|
+
.action(() => {
|
|
919
|
+
(0, config_1.clearConfig)();
|
|
920
|
+
console.log(chalk_1.default.gray('Credentials cleared.'));
|
|
921
|
+
});
|
|
922
|
+
// ─── Main ─────────────────────────────────────────────────────────────────────
|
|
923
|
+
program
|
|
924
|
+
.name('trenchfeed')
|
|
925
|
+
.description('TrenchFeed — AI Trading Agent CLI')
|
|
926
|
+
.version('0.1.0');
|
|
927
|
+
program.parse(process.argv);
|
|
928
|
+
if (process.argv.length <= 2) {
|
|
929
|
+
banner();
|
|
930
|
+
program.help();
|
|
931
|
+
}
|
|
932
|
+
//# sourceMappingURL=index.js.map
|