@darksol/terminal 0.4.4 → 0.4.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.4.4",
3
+ "version": "0.4.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/cli.js CHANGED
@@ -516,18 +516,53 @@ export function cli(argv) {
516
516
  .description('One-shot AI query')
517
517
  .option('-p, --provider <name>', 'LLM provider')
518
518
  .option('-m, --model <model>', 'Model name')
519
+ .option('-x, --execute', 'Auto-execute if confidence > 60%')
519
520
  .action(async (promptParts, opts) => {
520
521
  const prompt = promptParts.join(' ');
521
522
  const result = await parseIntent(prompt, opts);
522
523
  if (result.action !== 'error' && result.action !== 'unknown') {
523
524
  showSection('PARSED INTENT');
524
- kvDisplay(Object.entries(result)
525
- .filter(([k]) => !['raw', 'model'].includes(k))
526
- .map(([k, v]) => [k, Array.isArray(v) ? v.join(', ') : String(v)])
527
- );
525
+ const displayEntries = Object.entries(result)
526
+ .filter(([k]) => !['raw', 'model', 'reasoning'].includes(k))
527
+ .map(([k, v]) => [k, Array.isArray(v) ? v.join(', ') : String(v)]);
528
+ kvDisplay(displayEntries);
529
+
530
+ if (result.reasoning) {
531
+ console.log('');
532
+ info(result.reasoning);
533
+ }
534
+
535
+ if (result.warnings?.length > 0) {
536
+ result.warnings.forEach(w => warn(w));
537
+ }
538
+
528
539
  if (result.command) {
529
540
  console.log('');
530
- info(`Suggested command: ${theme.gold(result.command)}`);
541
+ info(`Command: ${theme.gold(result.command)}`);
542
+ }
543
+
544
+ // Offer to execute actionable intents
545
+ const actionable = ['swap', 'send', 'transfer', 'snipe', 'dca', 'price', 'balance', 'gas', 'analyze'];
546
+ if (actionable.includes(result.action)) {
547
+ if (opts.execute && result.confidence >= 0.6) {
548
+ console.log('');
549
+ await executeIntent(result, {});
550
+ } else if (!opts.execute) {
551
+ console.log('');
552
+ const inquirer = (await import('inquirer')).default;
553
+ const { run } = await inquirer.prompt([{
554
+ type: 'confirm',
555
+ name: 'run',
556
+ message: theme.gold('Execute this?'),
557
+ default: result.confidence >= 0.7,
558
+ }]);
559
+ if (run) await executeIntent(result, {});
560
+ }
561
+ }
562
+ } else {
563
+ if (result.raw) {
564
+ console.log('');
565
+ console.log(theme.dim(' ') + result.raw);
531
566
  }
532
567
  }
533
568
  });
package/src/llm/intent.js CHANGED
@@ -9,42 +9,70 @@ import { showSection } from '../ui/banner.js';
9
9
  // INTENT SYSTEM PROMPT
10
10
  // ──────────────────────────────────────────────────
11
11
 
12
- const INTENT_SYSTEM_PROMPT = `You are DARKSOL Terminal's trading AI assistant. You help users execute trades, analyze markets, manage DCA strategies, and navigate the DARKSOL ecosystem.
12
+ const INTENT_SYSTEM_PROMPT = `You are DARKSOL Terminal's trading AI assistant. You help users execute trades, send/receive tokens, analyze markets, manage DCA strategies, and navigate the DARKSOL ecosystem.
13
13
 
14
14
  CAPABILITIES:
15
- - Parse natural language trade instructions into structured commands
16
- - Analyze token prices, liquidity, and market conditions
15
+ - Parse natural language into structured trade/transfer commands
16
+ - Analyze token prices, liquidity, and market conditions
17
17
  - Suggest DCA strategies based on user goals
18
18
  - Explain transaction results and gas costs
19
19
  - Warn about risks (low liquidity, high slippage, unverified contracts)
20
20
 
21
21
  SUPPORTED CHAINS: Base (default), Ethereum, Polygon, Arbitrum, Optimism
22
-
23
- RESPONSE RULES:
24
- - Be concise and direct
25
- - Always include risk warnings for trades
26
- - When parsing trade intent, output structured JSON
27
- - Never reveal private keys or sensitive wallet info
28
- - If uncertain about a token, say so
29
- - Use plain numbers, avoid scientific notation
22
+ KNOWN TOKENS: ETH, USDC, USDT, DAI, WETH, AERO, VIRTUAL, ARB, OP, WMATIC
30
23
 
31
24
  USER CONTEXT:
32
25
  - Active chain: {{chain}}
33
26
  - Active wallet: {{wallet}}
34
27
  - Slippage setting: {{slippage}}%
35
28
 
36
- When parsing trade instructions, respond with JSON:
29
+ RESPONSE RULES:
30
+ - Be concise and direct
31
+ - Always include risk warnings for trades
32
+ - When parsing trade/transfer intent, output structured JSON
33
+ - Never reveal private keys or sensitive wallet info
34
+ - If uncertain about a token, say so — don't guess contract addresses
35
+ - Use plain numbers, avoid scientific notation
36
+ - For ambiguous amounts, ask for clarification (confidence < 0.5)
37
+
38
+ ACTIONS (use the most specific one):
39
+ - "swap" — exchange one token for another (e.g. "swap ETH to USDC", "buy VIRTUAL with 0.1 ETH")
40
+ - "send" — transfer tokens to an address (e.g. "send 10 USDC to 0x...", "transfer 0.5 ETH to vitalik.eth")
41
+ - "snipe" — fast-buy a new/low-liquidity token with ETH
42
+ - "dca" — set up recurring buys (e.g. "DCA $100 into ETH over 30 days")
43
+ - "price" — check current price (e.g. "price of AERO", "how much is VIRTUAL")
44
+ - "balance" — check wallet balance
45
+ - "info" — general question about a token or protocol
46
+ - "analyze" — deep analysis of a token
47
+ - "gas" — gas price check
48
+ - "unknown" — can't determine what the user wants
49
+
50
+ When parsing, respond with ONLY valid JSON:
37
51
  {
38
- "action": "swap|snipe|dca|transfer|info|analyze|unknown",
39
- "tokenIn": "symbol or address",
40
- "tokenOut": "symbol or address",
41
- "amount": "number",
42
- "chain": "chain name",
43
- "confidence": 0-1,
44
- "reasoning": "brief explanation",
52
+ "action": "swap|send|snipe|dca|price|balance|info|analyze|gas|unknown",
53
+ "tokenIn": "symbol or address (for swaps)",
54
+ "tokenOut": "symbol or address (for swaps)",
55
+ "token": "symbol (for send/price/analyze)",
56
+ "amount": "number as string",
57
+ "to": "recipient address (for send)",
58
+ "chain": "chain name if specified, null if not",
59
+ "interval": "for DCA: 1h, 4h, 1d, etc.",
60
+ "orders": "for DCA: number of orders",
61
+ "confidence": 0.0-1.0,
62
+ "reasoning": "brief explanation of interpretation",
45
63
  "warnings": ["array of risk warnings"],
46
- "command": "the CLI command to execute"
47
- }`;
64
+ "command": "the exact darksol CLI command to run"
65
+ }
66
+
67
+ COMMAND MAPPING:
68
+ - swap → darksol trade swap -i <tokenIn> -o <tokenOut> -a <amount>
69
+ - send → darksol send --to <address> --amount <amount> --token <token>
70
+ - snipe → darksol trade snipe <address> <ethAmount>
71
+ - dca → darksol dca create -t <token> -a <amount> -i <interval> -n <orders>
72
+ - price → darksol price <token>
73
+ - balance → darksol wallet balance
74
+ - gas → darksol gas <chain>
75
+ - analyze → darksol ai analyze <token>`;
48
76
 
49
77
  // ──────────────────────────────────────────────────
50
78
  // INTENT PARSER
@@ -126,7 +154,8 @@ export async function parseIntent(input, opts = {}) {
126
154
  export async function startChat(opts = {}) {
127
155
  showSection('DARKSOL AI — TRADING ASSISTANT');
128
156
  console.log(theme.dim(' Natural language trading. Type "exit" to quit.'));
129
- console.log(theme.dim(' Examples: "buy 0.1 ETH worth of VIRTUAL", "what\'s the price of AERO?"'));
157
+ console.log(theme.dim(' Try: "swap 0.1 ETH to USDC", "send 5 USDC to 0x...", "price of AERO"'));
158
+ console.log(theme.dim(' Actions auto-detected — you\'ll be asked to confirm before execution.'));
130
159
  console.log('');
131
160
 
132
161
  const spin = spinner('Initializing AI...').start();
@@ -187,7 +216,31 @@ export async function startChat(opts = {}) {
187
216
  enriched += `\n\n[Live data: ${priceData.join(', ')}]`;
188
217
  }
189
218
 
190
- const result = await llm.chat(enriched);
219
+ // Try to detect actionable intent
220
+ const actionKeywords = /\b(swap|send|transfer|buy|sell|snipe|dca|price|balance|gas)\b/i;
221
+ const isActionable = actionKeywords.test(input);
222
+
223
+ let result;
224
+ let parsedIntent = null;
225
+
226
+ if (isActionable) {
227
+ // Use JSON mode to get structured intent
228
+ const intentResult = await llm.json(
229
+ `Parse this as a trading/transfer instruction:\n\n"${enriched}"`,
230
+ { ephemeral: true }
231
+ );
232
+
233
+ if (intentResult.parsed && intentResult.parsed.action && intentResult.parsed.action !== 'unknown') {
234
+ parsedIntent = intentResult.parsed;
235
+ // Also get a human-readable response
236
+ result = await llm.chat(enriched);
237
+ } else {
238
+ result = await llm.chat(enriched);
239
+ }
240
+ } else {
241
+ result = await llm.chat(enriched);
242
+ }
243
+
191
244
  spin2.succeed('');
192
245
 
193
246
  // Display response
@@ -199,6 +252,41 @@ export async function startChat(opts = {}) {
199
252
  }
200
253
  console.log('');
201
254
 
255
+ // If actionable intent was detected, offer to execute
256
+ if (parsedIntent) {
257
+ const execActions = ['swap', 'send', 'transfer', 'snipe', 'dca', 'price', 'balance', 'gas'];
258
+ if (execActions.includes(parsedIntent.action)) {
259
+ const displayPairs = [];
260
+ if (parsedIntent.action) displayPairs.push(['Action', parsedIntent.action]);
261
+ if (parsedIntent.tokenIn) displayPairs.push(['From', parsedIntent.tokenIn]);
262
+ if (parsedIntent.tokenOut) displayPairs.push(['To token', parsedIntent.tokenOut]);
263
+ if (parsedIntent.token) displayPairs.push(['Token', parsedIntent.token]);
264
+ if (parsedIntent.amount) displayPairs.push(['Amount', parsedIntent.amount]);
265
+ if (parsedIntent.to) displayPairs.push(['Recipient', parsedIntent.to]);
266
+ if (parsedIntent.confidence) displayPairs.push(['Confidence', `${(parsedIntent.confidence * 100).toFixed(0)}%`]);
267
+
268
+ if (displayPairs.length > 1) {
269
+ showSection('DETECTED INTENT');
270
+ kvDisplay(displayPairs);
271
+ if (parsedIntent.warnings?.length > 0) {
272
+ parsedIntent.warnings.forEach(w => warn(w));
273
+ }
274
+ console.log('');
275
+
276
+ const { execute } = await inquirer.prompt([{
277
+ type: 'confirm',
278
+ name: 'execute',
279
+ message: theme.gold(`Execute ${parsedIntent.action}?`),
280
+ default: false,
281
+ }]);
282
+
283
+ if (execute) {
284
+ await executeIntent(parsedIntent, {});
285
+ }
286
+ }
287
+ }
288
+ }
289
+
202
290
  } catch (err) {
203
291
  spin2.fail('Error');
204
292
  error(err.message);
@@ -402,11 +490,37 @@ export async function executeIntent(intent, opts = {}) {
402
490
  });
403
491
  }
404
492
 
493
+ case 'send':
405
494
  case 'transfer': {
406
- // Transfer intent use execution script engine
407
- info(`Transfer intent detected. Use: darksol script create (template: transfer)`);
408
- info(`To: ${intent.to || 'specify address'}, Amount: ${intent.amount || 'specify amount'}`);
409
- return { success: true, action: 'transfer', note: 'Use script system for transfers' };
495
+ const { sendFunds } = await import('../wallet/manager.js');
496
+ return await sendFunds({
497
+ to: intent.to,
498
+ amount: intent.amount,
499
+ token: intent.token || intent.tokenIn || 'ETH',
500
+ });
501
+ }
502
+
503
+ case 'price': {
504
+ const token = intent.token || intent.tokenOut || intent.tokenIn;
505
+ if (token) {
506
+ const { checkPrices } = await import('../services/watch.js');
507
+ await checkPrices([token]);
508
+ return { success: true, action: 'price' };
509
+ }
510
+ info('No token specified');
511
+ return { success: false, reason: 'no token specified' };
512
+ }
513
+
514
+ case 'balance': {
515
+ const { getBalance } = await import('../wallet/manager.js');
516
+ await getBalance();
517
+ return { success: true, action: 'balance' };
518
+ }
519
+
520
+ case 'gas': {
521
+ const { showGas } = await import('../services/gas.js');
522
+ await showGas(intent.chain || getConfig('chain') || 'base');
523
+ return { success: true, action: 'gas' };
410
524
  }
411
525
 
412
526
  case 'info':
@@ -72,6 +72,10 @@ export async function handleCommand(cmd, ws) {
72
72
  return await cmdCasino(args, ws);
73
73
  case 'facilitator':
74
74
  return await cmdFacilitator(args, ws);
75
+ case 'send':
76
+ return await cmdSend(args, ws);
77
+ case 'receive':
78
+ return await cmdReceive(ws);
75
79
  default:
76
80
  return {
77
81
  output: `\r\n ${ANSI.red}✗ Unknown command: ${cmd}${ANSI.reset}\r\n ${ANSI.dim}Type ${ANSI.gold}help${ANSI.dim} for available commands.${ANSI.reset}\r\n\r\n`,
@@ -504,6 +508,84 @@ async function cmdConfig(ws) {
504
508
  return {};
505
509
  }
506
510
 
511
+ // ══════════════════════════════════════════════════
512
+ // SEND / RECEIVE (web shell — info only, actual sends require CLI)
513
+ // ══════════════════════════════════════════════════
514
+ async function cmdSend(args, ws) {
515
+ const chain = getConfig('chain') || 'base';
516
+ const wallet = getConfig('activeWallet');
517
+
518
+ ws.sendLine(`${ANSI.gold} ◆ SEND TOKENS${ANSI.reset}`);
519
+ ws.sendLine(`${ANSI.dim} ${'─'.repeat(50)}${ANSI.reset}`);
520
+ ws.sendLine('');
521
+
522
+ if (!wallet) {
523
+ ws.sendLine(` ${ANSI.red}No wallet configured.${ANSI.reset}`);
524
+ ws.sendLine(` Create one: ${ANSI.gold}darksol wallet create <name>${ANSI.reset}`);
525
+ ws.sendLine('');
526
+ return {};
527
+ }
528
+
529
+ ws.sendLine(` ${ANSI.white}Send ETH or any ERC-20 token from your wallet.${ANSI.reset}`);
530
+ ws.sendLine('');
531
+ ws.sendLine(` ${ANSI.darkGold}Usage:${ANSI.reset}`);
532
+ ws.sendLine(` ${ANSI.gold}darksol send --to 0x... --amount 0.1 --token ETH${ANSI.reset}`);
533
+ ws.sendLine(` ${ANSI.gold}darksol send --to 0x... --amount 50 --token USDC${ANSI.reset}`);
534
+ ws.sendLine(` ${ANSI.gold}darksol send${ANSI.reset} ${ANSI.dim}(interactive mode — prompts for everything)${ANSI.reset}`);
535
+ ws.sendLine('');
536
+ ws.sendLine(` ${ANSI.darkGold}Features:${ANSI.reset}`);
537
+ ws.sendLine(` ${ANSI.dim}•${ANSI.reset} ETH and any ERC-20 token`);
538
+ ws.sendLine(` ${ANSI.dim}•${ANSI.reset} Balance check before sending`);
539
+ ws.sendLine(` ${ANSI.dim}•${ANSI.reset} Gas estimation in preview`);
540
+ ws.sendLine(` ${ANSI.dim}•${ANSI.reset} Confirmation prompt before execution`);
541
+ ws.sendLine(` ${ANSI.dim}•${ANSI.reset} On-chain receipt after confirmation`);
542
+ ws.sendLine('');
543
+ ws.sendLine(` ${ANSI.darkGold}Active:${ANSI.reset} ${ANSI.white}${wallet}${ANSI.reset} on ${ANSI.white}${chain}${ANSI.reset}`);
544
+ ws.sendLine('');
545
+ ws.sendLine(` ${ANSI.dim}⚠ Sending requires the CLI. Install: npm i -g @darksol/terminal${ANSI.reset}`);
546
+ ws.sendLine('');
547
+ return {};
548
+ }
549
+
550
+ async function cmdReceive(ws) {
551
+ const chain = getConfig('chain') || 'base';
552
+ const wallet = getConfig('activeWallet');
553
+
554
+ ws.sendLine(`${ANSI.gold} ◆ RECEIVE${ANSI.reset}`);
555
+ ws.sendLine(`${ANSI.dim} ${'─'.repeat(50)}${ANSI.reset}`);
556
+ ws.sendLine('');
557
+
558
+ if (!wallet) {
559
+ ws.sendLine(` ${ANSI.red}No wallet configured.${ANSI.reset}`);
560
+ ws.sendLine(` Create one: ${ANSI.gold}darksol wallet create <name>${ANSI.reset}`);
561
+ ws.sendLine('');
562
+ return {};
563
+ }
564
+
565
+ try {
566
+ const { loadWallet } = await import('../wallet/keystore.js');
567
+ const walletData = loadWallet(wallet);
568
+ const addr = walletData.address;
569
+
570
+ ws.sendLine(` ${ANSI.white}Your address:${ANSI.reset}`);
571
+ ws.sendLine('');
572
+ ws.sendLine(` ${ANSI.dim}┌${'─'.repeat(addr.length + 4)}┐${ANSI.reset}`);
573
+ ws.sendLine(` ${ANSI.dim}│ ${ANSI.gold}${addr}${ANSI.dim} │${ANSI.reset}`);
574
+ ws.sendLine(` ${ANSI.dim}└${'─'.repeat(addr.length + 4)}┘${ANSI.reset}`);
575
+ ws.sendLine('');
576
+ ws.sendLine(` ${ANSI.dim}Works on ALL EVM chains:${ANSI.reset}`);
577
+ ws.sendLine(` ${ANSI.dim}Base • Ethereum • Arbitrum • Optimism • Polygon${ANSI.reset}`);
578
+ ws.sendLine('');
579
+ ws.sendLine(` ${ANSI.darkGold}Active chain:${ANSI.reset} ${ANSI.white}${chain}${ANSI.reset}`);
580
+ ws.sendLine(` ${ANSI.red}Make sure the sender is on the same chain!${ANSI.reset}`);
581
+ ws.sendLine('');
582
+ } catch {
583
+ ws.sendLine(` ${ANSI.dim}Run: darksol wallet receive${ANSI.reset}`);
584
+ ws.sendLine('');
585
+ }
586
+ return {};
587
+ }
588
+
507
589
  // ══════════════════════════════════════════════════
508
590
  // HELPERS
509
591
  // ══════════════════════════════════════════════════