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/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