@darksol/terminal 0.13.0 → 0.14.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.
@@ -22,6 +22,7 @@ import { spinner, kvDisplay, success, error, warn, info, table } from '../ui/com
22
22
  import { showSection } from '../ui/banner.js';
23
23
  import { resolveToken, getTokenInfo } from './swap.js';
24
24
  import { getDexesForChain, DEX_ADAPTERS } from './arb-dexes.js';
25
+ import { aiFilterOpportunity, aiScoreOpportunities } from './arb-ai.js';
25
26
 
26
27
  // ═══════════════════════════════════════════════════════════════
27
28
  // CONSTANTS & PATHS
@@ -346,21 +347,63 @@ export async function arbScan(opts = {}) {
346
347
  console.log('');
347
348
 
348
349
  const profitable = allOpps.filter(o => o.netProfitUsd >= minProfit);
349
- displayOpportunities(profitable);
350
+
351
+ // Apply AI pattern filter (fast, no API call)
352
+ const aiFiltered = profitable.map(o => {
353
+ const aiResult = aiFilterOpportunity(o);
354
+ return { ...o, aiScore: aiResult.score, aiPass: aiResult.pass, aiReason: aiResult.reason };
355
+ });
356
+ const aiPassed = aiFiltered.filter(o => o.aiPass);
357
+ const aiSkipped = aiFiltered.length - aiPassed.length;
358
+
359
+ displayOpportunities(aiPassed);
360
+
361
+ if (aiSkipped > 0) {
362
+ info(`AI filter skipped ${aiSkipped} low-confidence opportunity(s) — run 'darksol arb learn' to improve accuracy`);
363
+ console.log('');
364
+ }
365
+
366
+ // AI deep scoring for top opportunities (uses LLM)
367
+ if (aiPassed.length > 0 && !opts.skipAi) {
368
+ const scoreSpin = spinner('AI scoring opportunities...').start();
369
+ try {
370
+ const scoring = await aiScoreOpportunities(aiPassed);
371
+ if (scoring?.scored?.length > 0) {
372
+ scoreSpin.succeed('AI risk scoring complete');
373
+ console.log('');
374
+ console.log(theme.gold(' 🧠 AI Risk Assessment:'));
375
+ for (const s of scoring.scored) {
376
+ const riskColor = s.riskScore <= 3 ? theme.success : s.riskScore <= 6 ? theme.warning : theme.error;
377
+ const recColor = s.recommendation === 'execute' ? theme.success : s.recommendation === 'watch' ? theme.warning : theme.error;
378
+ console.log(` ${theme.bright(s.pair)} — risk: ${riskColor(String(s.riskScore) + '/10')} | MEV: ${theme.dim(s.mevLikelihood)} | ${recColor(s.recommendation.toUpperCase())}`);
379
+ console.log(` ${theme.dim(s.reason)}`);
380
+ }
381
+ if (scoring.summary) {
382
+ console.log('');
383
+ console.log(` ${theme.dim('Summary: ' + scoring.summary)}`);
384
+ }
385
+ console.log('');
386
+ } else {
387
+ scoreSpin.succeed('AI scoring returned no results');
388
+ }
389
+ } catch {
390
+ scoreSpin.warn('AI scoring unavailable — showing raw results');
391
+ }
392
+ }
350
393
 
351
394
  // Log everything (including unprofitable) to history
352
395
  for (const o of allOpps) {
353
396
  recordArb({ type: 'scan', ...o });
354
397
  }
355
398
 
356
- if (profitable.length > 0) {
399
+ if (aiPassed.length > 0) {
357
400
  console.log('');
358
401
  console.log(theme.warning(' ⚠ MEV Warning: ') + theme.dim('simple two-tx arb is likely to be front-run.'));
359
402
  console.log(theme.dim(' Use WSS endpoints + Flashbots bundles for reliable execution.'));
360
403
  console.log('');
361
404
  }
362
405
 
363
- return profitable;
406
+ return aiPassed;
364
407
  } catch (err) {
365
408
  spin.fail('Scan failed');
366
409
  error(err.message);
@@ -427,15 +470,20 @@ export async function arbMonitor(opts = {}) {
427
470
  }
428
471
 
429
472
  const profitable = allOpps.filter(o => o.netProfitUsd >= minProfit);
430
- oppsFound += profitable.length;
431
473
 
432
- if (profitable.length > 0) {
433
- blockSpin.succeed(`[Block ${blockNumber}] ${profitable.length} opportunity(s) found`);
434
- displayOpportunities(profitable.slice(0, 5));
474
+ // AI pattern filter (fast, no API call)
475
+ const aiPassed = profitable.filter(o => aiFilterOpportunity(o).pass);
476
+ oppsFound += aiPassed.length;
477
+
478
+ if (aiPassed.length > 0) {
479
+ const skipped = profitable.length - aiPassed.length;
480
+ const skipNote = skipped > 0 ? ` (${skipped} AI-filtered)` : '';
481
+ blockSpin.succeed(`[Block ${blockNumber}] ${aiPassed.length} opportunity(s) found${skipNote}`);
482
+ displayOpportunities(aiPassed.slice(0, 5));
435
483
 
436
484
  // Auto-execute if requested and cooldown satisfied
437
485
  if (execute && !dryRun && Date.now() - lastExecute > (arbCfg.cooldownMs || 5000)) {
438
- const best = profitable.sort((a, b) => b.netProfitUsd - a.netProfitUsd)[0];
486
+ const best = aiPassed.sort((a, b) => b.netProfitUsd - a.netProfitUsd)[0];
439
487
  if (best.netProfitUsd >= minProfit) {
440
488
  await arbExecute({ opportunity: best, dryRun: false, skipConfirm: true });
441
489
  lastExecute = Date.now();
@@ -443,7 +491,7 @@ export async function arbMonitor(opts = {}) {
443
491
  }
444
492
  }
445
493
  } else {
446
- blockSpin.text = `[Block ${blockNumber}] No profitable arb found (${allOpps.length} pairs scanned)`;
494
+ blockSpin.text = `[Block ${blockNumber}] No profitable arb found (${allOpps.length} scanned, ${profitable.length} pre-filter)`;
447
495
  blockSpin.succeed();
448
496
  }
449
497
 
@@ -1390,14 +1390,58 @@ async function cmdArb(args, ws) {
1390
1390
  return {};
1391
1391
  }
1392
1392
 
1393
+ if (sub === 'ai') {
1394
+ try {
1395
+ const { aiStrategyBriefing } = await import('../trading/arb-ai.js');
1396
+ await aiStrategyBriefing({ chain: args[1] || getConfig('chain') || 'base' });
1397
+ } catch (e) {
1398
+ ws.sendLine(` ${ANSI.red}AI briefing failed: ${e.message}${ANSI.reset}`);
1399
+ }
1400
+ return {};
1401
+ }
1402
+
1403
+ if (sub === 'discover') {
1404
+ try {
1405
+ const { aiDiscoverPairs } = await import('../trading/arb-ai.js');
1406
+ await aiDiscoverPairs({ chain: args[1] || getConfig('chain') || 'base' });
1407
+ } catch (e) {
1408
+ ws.sendLine(` ${ANSI.red}Discovery failed: ${e.message}${ANSI.reset}`);
1409
+ }
1410
+ return {};
1411
+ }
1412
+
1413
+ if (sub === 'tune') {
1414
+ try {
1415
+ const { aiTuneThresholds } = await import('../trading/arb-ai.js');
1416
+ await aiTuneThresholds({ chain: args[1] || getConfig('chain') || 'base' });
1417
+ } catch (e) {
1418
+ ws.sendLine(` ${ANSI.red}Tuning failed: ${e.message}${ANSI.reset}`);
1419
+ }
1420
+ return {};
1421
+ }
1422
+
1423
+ if (sub === 'learn') {
1424
+ try {
1425
+ const { aiLearn } = await import('../trading/arb-ai.js');
1426
+ await aiLearn({ chain: args[1] || getConfig('chain') || 'base' });
1427
+ } catch (e) {
1428
+ ws.sendLine(` ${ANSI.red}Learning failed: ${e.message}${ANSI.reset}`);
1429
+ }
1430
+ return {};
1431
+ }
1432
+
1393
1433
  // Default: show arb menu
1394
1434
  ws.sendLine(`${ANSI.gold} ◆ ARBITRAGE${ANSI.reset}`);
1395
1435
  ws.sendLine(`${ANSI.dim} ${'─'.repeat(50)}${ANSI.reset}`);
1396
- ws.sendLine(` ${ANSI.white}Cross-DEX arbitrage scanner${ANSI.reset}`);
1436
+ ws.sendLine(` ${ANSI.white}AI-powered cross-DEX arbitrage${ANSI.reset}`);
1397
1437
  ws.sendLine('');
1398
1438
 
1399
1439
  ws.sendMenu('arb_action', '◆ Arb Actions', [
1400
- { value: 'arb scan', label: '🔍 Scan', desc: 'One-shot DEX price comparison' },
1440
+ { value: 'arb scan', label: '🔍 Scan', desc: 'AI-scored DEX price comparison' },
1441
+ { value: 'arb ai', label: '🧠 AI Briefing', desc: 'Strategy assessment + recommendations' },
1442
+ { value: 'arb discover', label: '📈 Discover', desc: 'AI pair discovery — find new opportunities' },
1443
+ { value: 'arb tune', label: '🔧 Tune', desc: 'AI threshold optimization' },
1444
+ { value: 'arb learn', label: '📚 Learn', desc: 'Run learning cycle on history' },
1401
1445
  { value: 'arb stats', label: '📊 Stats', desc: 'View arb history & PnL' },
1402
1446
  { value: 'arb info', label: '📖 Guide', desc: 'How arb works, setup tips, risks' },
1403
1447
  { value: 'back', label: '← Back', desc: '' },