@darksol/terminal 0.5.3 → 0.5.5

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@darksol/terminal",
3
- "version": "0.5.3",
3
+ "version": "0.5.5",
4
4
  "description": "DARKSOL Terminal — unified CLI for all DARKSOL services. Market intel, trading, oracle, casino, and more.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/llm/intent.js CHANGED
@@ -45,25 +45,49 @@ ACTIONS (use the most specific one):
45
45
  - "info" — general question about a token or protocol
46
46
  - "analyze" — deep analysis of a token
47
47
  - "gas" — gas price check
48
+ - "cards" — order a prepaid Visa/Mastercard with crypto (e.g. "order a $50 card", "get me a prepaid card")
48
49
  - "unknown" — can't determine what the user wants
49
50
 
51
+ CARDS ORDERING:
52
+ When the user wants to order a prepaid card, you MUST collect:
53
+ 1. amount (USD denomination: 10, 25, 50, 100, 250, 500, 1000)
54
+ 2. email (delivery address for the card activation link)
55
+ 3. provider (default: "swype" for global, "mpc" or "reward" for US-only)
56
+ 4. ticker (payment crypto, default: "usdc")
57
+
58
+ If the user says "order me a $50 card" but doesn't provide an email, set "needsInfo": ["email"] and ask naturally.
59
+ If they have AgentMail configured, suggest using their agent email as an option.
60
+ Providers: swype (Mastercard, Global), mpc (Mastercard, US), reward (Visa, US).
61
+ Accepted crypto: usdc (default), usdt, btc, eth, sol, xmr.
62
+
50
63
  When parsing, respond with ONLY valid JSON:
51
64
  {
52
- "action": "swap|send|snipe|dca|price|balance|info|analyze|gas|unknown",
65
+ "action": "swap|send|snipe|dca|price|balance|info|analyze|gas|cards|unknown",
53
66
  "tokenIn": "symbol or address (for swaps)",
54
67
  "tokenOut": "symbol or address (for swaps)",
55
68
  "token": "symbol (for send/price/analyze)",
56
69
  "amount": "number as string",
57
70
  "to": "recipient address (for send)",
71
+ "email": "delivery email (for cards)",
72
+ "provider": "card provider (for cards, default: swype)",
73
+ "ticker": "payment crypto (for cards, default: usdc)",
58
74
  "chain": "chain name if specified, null if not",
59
75
  "interval": "for DCA: 1h, 4h, 1d, etc.",
60
76
  "orders": "for DCA: number of orders",
61
77
  "confidence": 0.0-1.0,
62
78
  "reasoning": "brief explanation of interpretation",
63
79
  "warnings": ["array of risk warnings"],
80
+ "needsInfo": ["array of missing fields the AI should ask about"],
81
+ "followUp": "natural language question to ask the user if info is missing",
64
82
  "command": "the exact darksol CLI command to run"
65
83
  }
66
84
 
85
+ CONVERSATIONAL RULES:
86
+ - If the user's request is missing required info (email for cards, address for send, amount for swap), DON'T set action to "unknown". Set the correct action, list what's missing in "needsInfo", and write a natural "followUp" question.
87
+ - Be conversational — "What email should I send the card to?" not "Error: email required"
88
+ - If they mention AgentMail or "my email", suggest using their configured agent email
89
+ - For cards without a specified provider, default to "swype" (global Mastercard)
90
+
67
91
  COMMAND MAPPING:
68
92
  - swap → darksol trade swap -i <tokenIn> -o <tokenOut> -a <amount>
69
93
  - send → darksol send --to <address> --amount <amount> --token <token>
@@ -72,6 +96,7 @@ COMMAND MAPPING:
72
96
  - price → darksol price <token>
73
97
  - balance → darksol wallet balance
74
98
  - gas → darksol gas <chain>
99
+ - cards → darksol cards order -p <provider> -a <amount> -e <email> --ticker <crypto>
75
100
  - analyze → darksol ai analyze <token>`;
76
101
 
77
102
  // ──────────────────────────────────────────────────
@@ -217,7 +242,7 @@ export async function startChat(opts = {}) {
217
242
  }
218
243
 
219
244
  // Try to detect actionable intent
220
- const actionKeywords = /\b(swap|send|transfer|buy|sell|snipe|dca|price|balance|gas)\b/i;
245
+ const actionKeywords = /\b(swap|send|transfer|buy|sell|snipe|dca|price|balance|gas|card|cards|order|prepaid|visa|mastercard)\b/i;
221
246
  const isActionable = actionKeywords.test(input);
222
247
 
223
248
  let result;
@@ -456,8 +481,27 @@ export async function executeIntent(intent, opts = {}) {
456
481
  }
457
482
 
458
483
  try {
484
+ // Check if the AI needs more info before executing
485
+ if (intent.needsInfo?.length > 0) {
486
+ if (intent.followUp) {
487
+ console.log('');
488
+ console.log(theme.gold(' DARKSOL AI:'));
489
+ console.log(theme.dim(' ') + intent.followUp);
490
+ console.log('');
491
+ }
492
+ return { success: false, reason: 'needs_info', needsInfo: intent.needsInfo, followUp: intent.followUp };
493
+ }
494
+
459
495
  switch (intent.action) {
460
496
  case 'swap': {
497
+ if (!intent.tokenIn || !intent.tokenOut) {
498
+ info('I need to know what tokens to swap. Example: "swap 0.1 ETH to USDC"');
499
+ return { success: false, reason: 'Missing token pair — tell me what to swap from and to.' };
500
+ }
501
+ if (!intent.amount) {
502
+ info('How much do you want to swap? Example: "swap 0.1 ETH to USDC"');
503
+ return { success: false, reason: 'Missing amount — how much do you want to swap?' };
504
+ }
461
505
  const { executeSwap } = await import('../trading/swap.js');
462
506
  return await executeSwap({
463
507
  tokenIn: intent.tokenIn,
@@ -469,29 +513,40 @@ export async function executeIntent(intent, opts = {}) {
469
513
  }
470
514
 
471
515
  case 'snipe': {
472
- const { executeSnipe } = await import('../trading/snipe.js');
473
- return await executeSnipe({
474
- token: intent.tokenOut || intent.tokenIn,
475
- amount: intent.amount,
516
+ const snipeToken = intent.tokenOut || intent.tokenIn;
517
+ if (!snipeToken || !snipeToken.startsWith('0x')) {
518
+ info('Snipe needs a contract address. Example: "snipe 0x1234... with 0.1 ETH"');
519
+ return { success: false, reason: 'I need a token contract address to snipe.' };
520
+ }
521
+ const { snipeToken: doSnipe } = await import('../trading/snipe.js');
522
+ return await doSnipe(snipeToken, intent.amount || '0.01', {
476
523
  chain: intent.chain,
477
- gasMultiplier: intent.gasMultiplier,
478
- password: opts.password,
524
+ slippage: intent.slippage,
525
+ gas: intent.gasMultiplier,
479
526
  });
480
527
  }
481
528
 
482
529
  case 'dca': {
483
- const { createDCAOrder } = await import('../trading/dca.js');
484
- return await createDCAOrder({
485
- token: intent.tokenOut || intent.tokenIn,
530
+ const { createDCA } = await import('../trading/dca.js');
531
+ return await createDCA({
532
+ tokenOut: intent.tokenOut || intent.tokenIn,
486
533
  amount: intent.amount,
487
534
  interval: intent.interval || '1h',
488
- orders: intent.orders || 10,
535
+ totalOrders: intent.orders || 10,
489
536
  chain: intent.chain,
490
537
  });
491
538
  }
492
539
 
493
540
  case 'send':
494
541
  case 'transfer': {
542
+ if (!intent.to) {
543
+ info('Where should I send it? Give me a wallet address (0x...)');
544
+ return { success: false, reason: 'Missing recipient address — who are you sending to?' };
545
+ }
546
+ if (!intent.amount) {
547
+ info('How much? Example: "send 10 USDC to 0x..."');
548
+ return { success: false, reason: 'Missing amount — how much do you want to send?' };
549
+ }
495
550
  const { sendFunds } = await import('../wallet/manager.js');
496
551
  return await sendFunds({
497
552
  to: intent.to,
@@ -507,8 +562,8 @@ export async function executeIntent(intent, opts = {}) {
507
562
  await checkPrices([token]);
508
563
  return { success: true, action: 'price' };
509
564
  }
510
- info('No token specified');
511
- return { success: false, reason: 'no token specified' };
565
+ info('Which token? Example: "price ETH" or "how much is AERO"');
566
+ return { success: false, reason: 'Which token do you want the price for?' };
512
567
  }
513
568
 
514
569
  case 'balance': {
@@ -523,6 +578,23 @@ export async function executeIntent(intent, opts = {}) {
523
578
  return { success: true, action: 'gas' };
524
579
  }
525
580
 
581
+ case 'cards': {
582
+ if (!intent.amount) {
583
+ info('What denomination? We have $10, $25, $50, $100, $250, $500, $1000');
584
+ return { success: false, reason: 'What card amount do you want?' };
585
+ }
586
+ if (!intent.email) {
587
+ info('I need an email to deliver the card activation link to.');
588
+ return { success: false, reason: 'What email should I send the card to?' };
589
+ }
590
+ const { cardsOrder } = await import('../services/cards.js');
591
+ return await cardsOrder(intent.provider || 'swype', intent.amount, {
592
+ email: intent.email,
593
+ ticker: intent.ticker || 'usdc',
594
+ network: intent.network,
595
+ });
596
+ }
597
+
526
598
  case 'info':
527
599
  case 'analyze': {
528
600
  const token = intent.tokenOut || intent.tokenIn || intent.token;
@@ -530,20 +602,37 @@ export async function executeIntent(intent, opts = {}) {
530
602
  await analyzeToken(token, opts);
531
603
  return { success: true, action: 'analyze' };
532
604
  }
533
- info('No token specified for analysis');
534
- return { success: false, reason: 'no token specified' };
605
+ info('Which token do you want me to analyze?');
606
+ return { success: false, reason: 'Tell me which token to look at.' };
535
607
  }
536
608
 
537
609
  default:
538
- warn(`Action "${intent.action}" not yet wired for execution`);
610
+ warn(`I don't know how to do "${intent.action}" yet.`);
539
611
  if (intent.command) {
540
- info(`Suggested command: ${theme.gold(intent.command)}`);
612
+ info(`Try running: ${theme.gold(intent.command)}`);
541
613
  }
542
- return { success: false, reason: `unhandled action: ${intent.action}` };
614
+ return { success: false, reason: `Action "${intent.action}" isn't wired up yet.` };
543
615
  }
544
616
  } catch (err) {
545
- error(`Execution failed: ${err.message}`);
546
- return { success: false, error: err.message };
617
+ // Human-readable error messages
618
+ const msg = err.message || String(err);
619
+ if (msg.includes('CALL_EXCEPTION')) {
620
+ error('The on-chain call failed. This usually means the RPC is having issues or the contract call reverted.');
621
+ info('Try again in a moment, or switch to a different RPC with: darksol config set rpcs.base <url>');
622
+ } else if (msg.includes('insufficient funds') || msg.includes('Insufficient')) {
623
+ error('Not enough funds in your wallet for this transaction (including gas fees).');
624
+ } else if (msg.includes('nonce')) {
625
+ error('Transaction nonce conflict. You may have a pending transaction — wait for it to confirm or try again.');
626
+ } else if (msg.includes('timeout') || msg.includes('ETIMEDOUT')) {
627
+ error('Network timeout — the RPC server didn\'t respond in time. Try again or switch RPCs.');
628
+ } else if (msg.includes('could not detect network')) {
629
+ error('Can\'t connect to the blockchain. Check your internet connection and RPC settings.');
630
+ } else if (msg.includes('password') || msg.includes('decrypt')) {
631
+ error('Wrong wallet password. The private key couldn\'t be decrypted.');
632
+ } else {
633
+ error(`Failed: ${msg}`);
634
+ }
635
+ return { success: false, error: msg };
547
636
  }
548
637
  }
549
638
 
@@ -6,6 +6,42 @@ import { showSection } from '../ui/banner.js';
6
6
 
7
7
  const BASE = () => getServiceURL('cards') || 'https://acp.darksol.net';
8
8
 
9
+ // Verified working ticker/network combos (tested against Trocador API)
10
+ const VALID_CRYPTO = {
11
+ 'usdc': { network: 'base', display: 'USDC on Base' },
12
+ 'usdc_base': { ticker: 'usdc', network: 'base', display: 'USDC on Base' },
13
+ 'usdc_erc20': { ticker: 'usdc', network: 'ERC20', display: 'USDC on Ethereum' },
14
+ 'usdt': { network: 'trc20', display: 'USDT on Tron' },
15
+ 'usdt_trc20': { ticker: 'usdt', network: 'trc20', display: 'USDT on Tron' },
16
+ 'btc': { network: 'Mainnet', display: 'Bitcoin' },
17
+ 'eth': { network: 'ERC20', display: 'ETH on Ethereum' },
18
+ 'sol': { network: 'Mainnet', display: 'Solana' },
19
+ 'xmr': { network: 'Mainnet', display: 'Monero' },
20
+ };
21
+
22
+ // Map user-friendly network names to Trocador-compatible ones
23
+ const NETWORK_MAP = {
24
+ 'base': 'base',
25
+ 'ethereum': 'ERC20', 'eth': 'ERC20', 'erc20': 'ERC20',
26
+ 'tron': 'trc20', 'trc20': 'trc20',
27
+ 'mainnet': 'Mainnet', 'btc': 'Mainnet', 'sol': 'Mainnet', 'xmr': 'Mainnet',
28
+ };
29
+
30
+ function resolveTickerNetwork(ticker, network) {
31
+ const t = (ticker || '').toLowerCase();
32
+ const n = (network || '').toLowerCase();
33
+
34
+ // If just ticker provided, use known defaults
35
+ if (t && !n) {
36
+ const known = VALID_CRYPTO[t];
37
+ if (known) return { ticker: known.ticker || t, network: known.network };
38
+ }
39
+
40
+ // Map network to Trocador format
41
+ const mappedNetwork = NETWORK_MAP[n] || network;
42
+ return { ticker: t, network: mappedNetwork };
43
+ }
44
+
9
45
  export async function cardsCatalog() {
10
46
  const spin = spinner('Loading card catalog...').start();
11
47
  try {
@@ -58,9 +94,13 @@ export async function cardsOrder(provider, amount, opts = {}) {
58
94
  amount: Number(amount),
59
95
  email: opts.email,
60
96
  };
61
- // Optional: custom crypto + network
62
- if (opts.ticker) body.tickerFrom = opts.ticker;
63
- if (opts.network) body.networkFrom = opts.network;
97
+ // Resolve ticker/network to Trocador-compatible values
98
+ if (opts.ticker) {
99
+ const resolved = resolveTickerNetwork(opts.ticker, opts.network);
100
+ body.tickerFrom = resolved.ticker;
101
+ body.networkFrom = resolved.network;
102
+ info(`Payment: ${resolved.ticker.toUpperCase()} on ${resolved.network}`);
103
+ }
64
104
 
65
105
  const data = await fetchJSON(`${BASE()}/api/cards/order`, {
66
106
  method: 'POST',
@@ -454,7 +454,7 @@ export async function handleCommand(cmd, ws) {
454
454
  return await cmdChatLogs(args, ws);
455
455
  default: {
456
456
  // Fuzzy: if it looks like natural language, route to AI
457
- const nlKeywords = /\b(swap|buy|sell|send|transfer|price|what|how|should|analyze|check|balance|gas|dca)\b/i;
457
+ const nlKeywords = /\b(swap|buy|sell|send|transfer|price|what|how|should|analyze|check|balance|gas|dca|order|card|prepaid|visa|mastercard)\b/i;
458
458
  if (nlKeywords.test(cmd)) {
459
459
  return await cmdAI(cmd.split(/\s+/), ws);
460
460
  }