@darksol/terminal 0.6.3 → 0.7.1

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/README.md CHANGED
@@ -15,7 +15,7 @@ A unified CLI for market intel, trading, AI-powered analysis, on-chain oracle, c
15
15
  [![License: MIT](https://img.shields.io/badge/License-MIT-gold.svg)](https://opensource.org/licenses/MIT)
16
16
  [![Node](https://img.shields.io/badge/node-%3E%3D18.0.0-green.svg)](https://nodejs.org/)
17
17
 
18
- - Current release: **0.5.0**
18
+ - Current release: **0.7.1**
19
19
  - Changelog: `CHANGELOG.md`
20
20
 
21
21
  ## Install
@@ -67,10 +67,13 @@ darksol agent start main
67
67
 
68
68
  ## `darksol serve` (Web Terminal UX)
69
69
 
70
- `darksol serve` now supports an interactive keyboard-driven UI:
70
+ `darksol serve` is a full interactive web terminal with keyboard-driven menus:
71
71
 
72
- - Arrow-key menus (`↑/↓` + `Enter`) for wallet/config flows
73
- - Interactive wallet picker + wallet action menu (receive/send/portfolio/history/switch chain)
72
+ - Arrow-key menus (`↑/↓` + `Enter`) for wallet/config/trade flows
73
+ - **Interactive send** token recipient amount → password → on-chain transfer
74
+ - **Interactive swap** — pair picker (presets + custom) → amount → password → Uniswap V3 execution
75
+ - **Interactive snipe** — contract input → amount → password → fast buy
76
+ - Wallet picker + wallet action menu (receive/send/portfolio/history/switch chain)
74
77
  - Agent signer control center (`agent`) with guided wallet selection + start/stop/status
75
78
  - Click-through help menu (`help`) with arrow-key command selection
76
79
  - AI connection check at startup (shows ready/not configured)
@@ -84,8 +87,10 @@ Useful web-shell commands:
84
87
 
85
88
  ```bash
86
89
  help # clickable command menu (arrow keys + Enter)
87
- keys # provider status + interactive add/update
90
+ trade # interactive swap / snipe menu
91
+ send # interactive token transfer
88
92
  wallet # interactive wallet picker and actions
93
+ keys # provider status + interactive add/update
89
94
  agent # signer start/stop/status controls
90
95
  config # interactive config menu
91
96
  logs 20 # show recent AI chat log lines
@@ -201,7 +206,7 @@ darksol ai analyze AERO
201
206
  darksol ai chat --provider ollama --model llama3
202
207
  ```
203
208
 
204
- **Supported providers:** OpenAI, Anthropic, OpenRouter, Ollama (local = free)
209
+ **Supported providers:** OpenAI, Anthropic, OpenRouter, Bankr LLM Gateway, Ollama (local = free)
205
210
 
206
211
  The AI gets live market context (prices from DexScreener), knows your config (chain, slippage, wallet), and returns structured intents with confidence scores and risk warnings.
207
212
 
@@ -227,7 +232,7 @@ darksol keys remove openai
227
232
  **Supported services:**
228
233
  | Category | Services |
229
234
  |----------|----------|
230
- | LLM | OpenAI, Anthropic, OpenRouter, Ollama |
235
+ | LLM | OpenAI, Anthropic, OpenRouter, Bankr LLM Gateway, Ollama |
231
236
  | Data | CoinGecko Pro, DexScreener, DefiLlama |
232
237
  | RPC | Alchemy, Infura, QuickNode |
233
238
  | Trading | 1inch, ParaSwap |
@@ -239,14 +244,23 @@ Keys can also come from environment variables (e.g., `OPENAI_API_KEY`).
239
244
  ## 💰 Trading
240
245
 
241
246
  ```bash
242
- # Swap via Uniswap V3
247
+ # Interactive swap (prompts for pair + amount if flags omitted)
248
+ darksol trade swap
249
+
250
+ # Swap with full flags (Uniswap V3 with slippage protection)
243
251
  darksol trade swap -i ETH -o USDC -a 0.1
244
252
 
253
+ # Non-interactive swap (for automation / cron)
254
+ darksol trade swap -i ETH -o USDC -a 0.1 -p "password" -y
255
+
256
+ # Show common pairs for current chain
257
+ darksol trade pairs
258
+
245
259
  # Snipe a token (Uniswap V2, fast buy)
246
260
  darksol trade snipe 0xTOKEN -a 0.05
247
261
 
248
- # Snipe with gas boost
249
- darksol trade snipe 0xTOKEN -a 0.05 -g 2.0
262
+ # Snipe with gas boost + non-interactive
263
+ darksol trade snipe 0xTOKEN -a 0.05 -g 2.0 -p "password" -y
250
264
 
251
265
  # Watch for new pairs
252
266
  darksol trade watch
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@darksol/terminal",
3
- "version": "0.6.3",
3
+ "version": "0.7.1",
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/skill/SKILL.md CHANGED
@@ -7,7 +7,7 @@ description: "DARKSOL Terminal — unified CLI + x402 platform for trading, wall
7
7
 
8
8
  **All DARKSOL services. One terminal. Zero trust required. 🌑**
9
9
 
10
- `@darksol/terminal` v0.6.x | npm: `npm install -g @darksol/terminal`
10
+ `@darksol/terminal` v0.7.1 | npm: `npm install -g @darksol/terminal`
11
11
 
12
12
  ---
13
13
 
@@ -65,10 +65,12 @@ darksol wallet export [name] # Export (password required for PK)
65
65
 
66
66
  ### 📊 Trading (5 chains)
67
67
  ```bash
68
+ darksol trade swap # Interactive swap (prompts for pair + amount)
68
69
  darksol trade swap -i ETH -o USDC -a 0.1 # Uniswap V3 swap with slippage protection
69
- darksol trade swap -i USDC -o ETH -a 100 -c polygon # Swap on Polygon
70
+ darksol trade swap -i ETH -o USDC -a 0.1 -p "pw" -y # Non-interactive (automation/cron)
71
+ darksol trade pairs # Show common pairs for active chain
70
72
  darksol trade snipe <token> -a 0.05 # Fast buy with gas boost
71
- darksol trade snipe <token> -a 0.05 -g 2.0 # Snipe with 2x gas priority
73
+ darksol trade snipe <token> -a 0.05 -g 2.0 -p "pw" -y # Non-interactive snipe
72
74
  darksol trade watch # Monitor new pairs (experimental)
73
75
  darksol send # Interactive ETH/ERC-20 transfer
74
76
  darksol receive # Show your address for receiving
@@ -88,12 +90,16 @@ darksol dca cancel <id> # Cancel
88
90
  ### 🤖 AI Trading Assistant
89
91
  ```bash
90
92
  darksol ai chat # Interactive AI chat (supports swap/send/price/casino/cards)
93
+ darksol ai chat --provider bankr # Use Bankr LLM Gateway (crypto credits)
94
+ darksol ai chat --provider bankr --model claude-opus-4.6 # Specific Bankr model
91
95
  darksol ai ask "buy 0.5 ETH of AERO" # Parse natural language → trade intent
92
96
  darksol ai ask "flip a coin" -x # Auto-execute if confidence ≥ 60%
93
97
  darksol ai strategy VIRTUAL -b 500 # DCA strategy recommendation
94
98
  darksol ai analyze AERO # Token analysis
95
99
  ```
96
100
 
101
+ **AI Providers:** OpenAI, Anthropic, OpenRouter, Bankr LLM Gateway (`bk_...`), Ollama (local/free)
102
+ **Bankr Models:** claude-opus-4.6, claude-sonnet-4.6, gemini-3-pro, gpt-5.2, kimi-k2.5, qwen3-coder
97
103
  **AI Intent Actions:** swap, send, snipe, dca, price, balance, info, analyze, gas, cards, casino, unknown
98
104
 
99
105
  The AI understands natural language and maps it to executable commands:
@@ -241,7 +247,7 @@ darksol config rpc base https://... # Custom RPC
241
247
  ```bash
242
248
  darksol keys list # All services + status
243
249
  darksol keys add openai # Add key (encrypted AES-256-GCM)
244
- darksol keys add anthropic # Supported: openai, anthropic, openrouter, ollama,
250
+ darksol keys add anthropic # Supported: openai, anthropic, openrouter, bankr, ollama,
245
251
  darksol keys add email # coingecko, dexscreener, alchemy, infura, email
246
252
  darksol keys remove <service> # Remove key
247
253
  ```
@@ -293,8 +299,10 @@ const result = await fetchWithX402(
293
299
 
294
300
  ### Non-interactive mode (for cron / automation)
295
301
  ```bash
296
- # All trading commands accept flags for non-interactive use
297
- darksol trade swap -i ETH -o USDC -a 0.1 -y
302
+ # All trading commands accept --password (-p) and --yes (-y) for non-interactive use
303
+ darksol trade swap -i ETH -o USDC -a 0.1 -p "password" -y
304
+ darksol trade snipe 0xTOKEN -a 0.05 -p "password" -y
305
+ darksol send --to 0x... --amount 0.1 --token ETH -p "password" -y
298
306
  darksol script run my-dca -p "password" -y
299
307
  darksol casino bet coinflip -c heads -w 0x1234...
300
308
 
package/src/cli.js CHANGED
@@ -113,19 +113,41 @@ export function cli(argv) {
113
113
 
114
114
  trade
115
115
  .command('swap')
116
- .description('Swap tokens via DEX')
117
- .requiredOption('-i, --in <token>', 'Token to sell (symbol or address)')
118
- .requiredOption('-o, --out <token>', 'Token to buy (symbol or address)')
119
- .requiredOption('-a, --amount <amount>', 'Amount to swap')
116
+ .description('Swap tokens via DEX (interactive if flags omitted)')
117
+ .option('-i, --in <token>', 'Token to sell (symbol or address)')
118
+ .option('-o, --out <token>', 'Token to buy (symbol or address)')
119
+ .option('-a, --amount <amount>', 'Amount to swap')
120
120
  .option('-s, --slippage <percent>', 'Max slippage %', '0.5')
121
121
  .option('-w, --wallet <name>', 'Wallet to use')
122
- .action((opts) => executeSwap({
123
- tokenIn: opts.in,
124
- tokenOut: opts.out,
125
- amount: opts.amount,
126
- slippage: parseFloat(opts.slippage),
127
- wallet: opts.wallet,
128
- }));
122
+ .option('-p, --password <pw>', 'Wallet password (non-interactive)')
123
+ .option('-y, --yes', 'Skip confirmation')
124
+ .action(async (opts) => {
125
+ let tokenIn = opts.in;
126
+ let tokenOut = opts.out;
127
+ let amount = opts.amount;
128
+
129
+ if (!tokenIn || !tokenOut || !amount) {
130
+ const inquirer = (await import('inquirer')).default;
131
+ const answers = await inquirer.prompt([
132
+ { type: 'input', name: 'tokenIn', message: 'Token to sell (e.g. ETH):', default: tokenIn || 'ETH' },
133
+ { type: 'input', name: 'tokenOut', message: 'Token to buy (e.g. USDC):', default: tokenOut || 'USDC' },
134
+ { type: 'input', name: 'amount', message: 'Amount to swap:', default: amount || '0.1' },
135
+ ]);
136
+ tokenIn = answers.tokenIn;
137
+ tokenOut = answers.tokenOut;
138
+ amount = answers.amount;
139
+ }
140
+
141
+ return executeSwap({
142
+ tokenIn,
143
+ tokenOut,
144
+ amount,
145
+ slippage: parseFloat(opts.slippage),
146
+ wallet: opts.wallet,
147
+ password: opts.password,
148
+ confirm: opts.yes ? true : undefined,
149
+ });
150
+ });
129
151
 
130
152
  trade
131
153
  .command('snipe <token>')
@@ -134,10 +156,14 @@ export function cli(argv) {
134
156
  .option('-s, --slippage <percent>', 'Max slippage %', '1')
135
157
  .option('-g, --gas <multiplier>', 'Gas priority multiplier', '1.5')
136
158
  .option('-w, --wallet <name>', 'Wallet to use')
159
+ .option('-p, --password <pw>', 'Wallet password (non-interactive)')
160
+ .option('-y, --yes', 'Skip confirmation')
137
161
  .action((token, opts) => snipeToken(token, opts.amount, {
138
162
  slippage: parseFloat(opts.slippage),
139
163
  gas: parseFloat(opts.gas),
140
164
  wallet: opts.wallet,
165
+ password: opts.password,
166
+ confirm: opts.yes ? true : undefined,
141
167
  }));
142
168
 
143
169
  trade
@@ -147,6 +173,26 @@ export function cli(argv) {
147
173
  .option('-a, --amount <eth>', 'Auto-snipe amount')
148
174
  .action((opts) => watchSnipe(opts));
149
175
 
176
+ trade
177
+ .command('pairs')
178
+ .description('Show common swap pairs for current chain')
179
+ .action(() => {
180
+ const chain = getConfig('chain') || 'base';
181
+ const byChain = {
182
+ base: ['ETH/USDC', 'ETH/AERO', 'ETH/VIRTUAL', 'USDC/AERO'],
183
+ ethereum: ['ETH/USDC', 'ETH/USDT', 'ETH/DAI'],
184
+ arbitrum: ['ETH/USDC', 'ETH/USDT', 'ETH/ARB'],
185
+ optimism: ['ETH/USDC', 'ETH/OP'],
186
+ polygon: ['POL/USDC', 'POL/WETH', 'USDC/USDT'],
187
+ };
188
+ showSection(`COMMON PAIRS — ${chain.toUpperCase()}`);
189
+ const pairs = byChain[chain] || byChain.base;
190
+ pairs.forEach((p) => console.log(` ${theme.gold(p)}`));
191
+ console.log('');
192
+ info('Swap command: darksol trade swap -i <tokenIn> -o <tokenOut> -a <amount>');
193
+ console.log('');
194
+ });
195
+
150
196
  // ═══════════════════════════════════════
151
197
  // DCA COMMANDS
152
198
  // ═══════════════════════════════════════
@@ -92,6 +92,14 @@ export const SERVICES = {
92
92
  docsUrl: 'https://ollama.ai',
93
93
  validate: (key) => key.startsWith('http'),
94
94
  },
95
+ bankr: {
96
+ name: 'Bankr LLM Gateway',
97
+ category: 'llm',
98
+ description: 'Multi-model gateway — Claude, Gemini, GPT via crypto credits',
99
+ envVar: 'BANKR_LLM_KEY',
100
+ docsUrl: 'https://docs.bankr.bot/llm-gateway/overview',
101
+ validate: (key) => key.startsWith('bk_'),
102
+ },
95
103
 
96
104
  // Data Providers
97
105
  coingecko: {
@@ -404,7 +412,7 @@ export function hasKey(service) {
404
412
  */
405
413
  export function hasAnyLLM() {
406
414
  // Cloud providers — need real validated API keys
407
- if (['openai', 'anthropic', 'openrouter'].some(s => hasKey(s))) return true;
415
+ if (['openai', 'anthropic', 'openrouter', 'bankr'].some(s => hasKey(s))) return true;
408
416
  // Ollama — check if explicitly configured via hasKey (validates URL format)
409
417
  if (hasKey('ollama')) return true;
410
418
  return false;
package/src/llm/engine.js CHANGED
@@ -51,6 +51,13 @@ const PROVIDERS = {
51
51
  parseResponse: (data) => data.choices?.[0]?.message?.content || data.message?.content,
52
52
  parseUsage: () => ({ input: 0, output: 0 }),
53
53
  },
54
+ bankr: {
55
+ url: 'https://llm.bankr.bot/v1/chat/completions',
56
+ defaultModel: 'claude-sonnet-4.6',
57
+ authHeader: (key) => ({ 'X-API-Key': key }),
58
+ parseResponse: (data) => data.choices?.[0]?.message?.content,
59
+ parseUsage: (data) => data.usage,
60
+ },
54
61
  };
55
62
 
56
63
  // ──────────────────────────────────────────────────
@@ -85,6 +92,12 @@ export class LLMEngine {
85
92
  }
86
93
  }
87
94
 
95
+ if (!this.apiKey && this.provider !== 'ollama') {
96
+ // Try auto-stored keys as last resort
97
+ const { getKeyAuto } = await import('../config/keys.js');
98
+ this.apiKey = getKeyAuto(this.provider);
99
+ }
100
+
88
101
  if (!this.apiKey && this.provider !== 'ollama') {
89
102
  throw new Error(`No API key for ${this.provider}. Run: darksol keys add ${this.provider}`);
90
103
  }
@@ -25,42 +25,149 @@ const SKILL_CATALOG = [
25
25
  {
26
26
  name: 'darksol-facilitator',
27
27
  description: 'Free on-chain x402 payment facilitator — verify and settle micropayments',
28
- version: '1.0.0',
28
+ version: '1.0.1',
29
29
  source: 'url',
30
30
  url: 'https://facilitator.darksol.net/skill/SKILL.md',
31
+ urlCandidates: [
32
+ 'https://facilitator.darksol.net/skill/SKILL.md',
33
+ 'https://facilitator.darksol.net/skill',
34
+ ],
35
+ fallbackSkill: 'facilitator',
31
36
  category: 'payments',
32
37
  installed: () => existsSync(join(OPENCLAW_SKILLS_DIR, 'darksol-facilitator', 'SKILL.md')),
33
38
  },
34
39
  {
35
40
  name: 'darksol-prepaid-cards',
36
41
  description: 'Crypto → prepaid Visa/MC cards, no KYC, agent-native REST API',
37
- version: '1.0.0',
42
+ version: '1.0.1',
38
43
  source: 'url',
39
44
  url: 'https://acp.darksol.net/dist/darksol-prepaid-cards.skill',
40
45
  skillMdUrl: 'https://acp.darksol.net/cards/skill/SKILL.md',
46
+ urlCandidates: [
47
+ 'https://acp.darksol.net/cards/skill/SKILL.md',
48
+ 'https://acp.darksol.net/dist/darksol-prepaid-cards.skill',
49
+ ],
50
+ fallbackSkill: 'cards',
41
51
  category: 'payments',
42
52
  installed: () => existsSync(join(OPENCLAW_SKILLS_DIR, 'darksol-prepaid-cards', 'SKILL.md')),
43
53
  },
44
54
  {
45
55
  name: 'random-oracle',
46
56
  description: 'On-chain random oracle — verifiable randomness via x402',
47
- version: '1.0.0',
57
+ version: '1.0.1',
48
58
  source: 'url',
49
59
  url: 'https://acp.darksol.net/oracle/skill/SKILL.md',
60
+ urlCandidates: [
61
+ 'https://acp.darksol.net/oracle/skill/SKILL.md',
62
+ 'https://acp.darksol.net/oracle/skill',
63
+ ],
64
+ fallbackSkill: 'oracle',
50
65
  category: 'oracle',
51
66
  installed: () => existsSync(join(OPENCLAW_SKILLS_DIR, 'random-oracle', 'SKILL.md')),
52
67
  },
53
68
  {
54
69
  name: 'the-clawsino',
55
70
  description: 'On-chain agent casino — coin flip, dice, hi-lo, slots via x402',
56
- version: '1.0.0',
71
+ version: '1.0.1',
57
72
  source: 'url',
58
73
  url: 'https://casino.darksol.net/skill/SKILL.md',
74
+ urlCandidates: [
75
+ 'https://casino.darksol.net/skill/SKILL.md',
76
+ 'https://casino.darksol.net/skill',
77
+ ],
78
+ fallbackSkill: 'casino',
59
79
  category: 'gaming',
60
80
  installed: () => existsSync(join(OPENCLAW_SKILLS_DIR, 'the-clawsino', 'SKILL.md')),
61
81
  },
62
82
  ];
63
83
 
84
+ const FALLBACK_SKILLS = {
85
+ facilitator: `---
86
+ name: darksol-facilitator
87
+ description: Free on-chain x402 payment facilitator by DARKSOL. Verifies and settles EIP-3009 micropayments on Base and Polygon.
88
+ ---
89
+
90
+ # DARKSOL Facilitator
91
+
92
+ ## Base URL
93
+ - https://facilitator.darksol.net
94
+
95
+ ## Endpoints
96
+ - GET / (service health/info)
97
+ - POST /verify (verify payment payload)
98
+ - POST /settle (settle payment on-chain)
99
+
100
+ Use this for free x402 settlement flows.`,
101
+ oracle: `---
102
+ name: random-oracle
103
+ description: On-chain random oracle — verifiable randomness via x402.
104
+ ---
105
+
106
+ # Random Oracle
107
+
108
+ ## Base URL
109
+ - https://acp.darksol.net/api/oracle
110
+
111
+ ## Endpoints
112
+ - GET /health
113
+ - GET /coin
114
+ - GET /dice?sides=6
115
+ - GET /number?min=1&max=100
116
+ - POST /shuffle
117
+
118
+ Game endpoints are x402-gated; health is public.`,
119
+ cards: `---
120
+ name: darksol-prepaid-cards
121
+ description: Crypto to prepaid cards via DARKSOL Cards API.
122
+ ---
123
+
124
+ # DARKSOL Prepaid Cards
125
+
126
+ ## Base URL
127
+ - https://acp.darksol.net/api/cards
128
+
129
+ ## Endpoints
130
+ - GET /catalog
131
+ - POST /order
132
+ - GET /status?tradeId=<id>
133
+
134
+ Supports provider/amount/email + optional ticker/network route selection.`,
135
+ casino: `---
136
+ name: the-clawsino
137
+ description: On-chain casino endpoints for coin flip, dice, hi-lo, slots.
138
+ ---
139
+
140
+ # The Clawsino
141
+
142
+ ## Base URL
143
+ - https://casino.darksol.net
144
+
145
+ ## Endpoints
146
+ - GET /api/stats
147
+ - GET /api/tables
148
+ - POST /api/bet
149
+ - GET /api/receipt/:id
150
+ - GET /api/verify/:id
151
+
152
+ All bets are $1 USDC with payment proof in bet request.`,
153
+ };
154
+
155
+ async function fetchFirstAvailable(urls = []) {
156
+ for (const u of urls.filter(Boolean)) {
157
+ try {
158
+ const resp = await fetch(u);
159
+ if (!resp.ok) continue;
160
+ const content = await resp.text();
161
+ if (content && content.trim().length > 0) {
162
+ return { url: u, content };
163
+ }
164
+ } catch {
165
+ // try next URL
166
+ }
167
+ }
168
+ return null;
169
+ }
170
+
64
171
  // ──────────────────────────────────────────────────
65
172
  // LIST SKILLS
66
173
  // ──────────────────────────────────────────────────
@@ -129,18 +236,17 @@ export async function installSkill(name) {
129
236
  throw new Error('Bundled SKILL.md not found in package');
130
237
  }
131
238
  } else if (skill.source === 'url') {
132
- // Fetch from remote
133
- const url = skill.skillMdUrl || skill.url;
134
- const resp = await fetch(url);
135
- if (!resp.ok) throw new Error(`Failed to fetch: ${resp.status}`);
136
- const content = await resp.text();
137
-
138
- // Handle .skill files (may be a zip or just SKILL.md content)
139
- if (url.endsWith('.skill')) {
140
- // .skill files are typically just the SKILL.md content
141
- writeFileSync(join(targetDir, 'SKILL.md'), content);
239
+ // Fetch from first live endpoint, with fallback stub if remote is unavailable
240
+ const candidates = skill.urlCandidates || [skill.skillMdUrl, skill.url];
241
+ const fetched = await fetchFirstAvailable(candidates);
242
+
243
+ if (fetched?.content) {
244
+ writeFileSync(join(targetDir, 'SKILL.md'), fetched.content);
245
+ } else if (skill.fallbackSkill && FALLBACK_SKILLS[skill.fallbackSkill]) {
246
+ writeFileSync(join(targetDir, 'SKILL.md'), FALLBACK_SKILLS[skill.fallbackSkill]);
247
+ warn(`Remote skill endpoint unavailable installed fallback spec for ${name}`);
142
248
  } else {
143
- writeFileSync(join(targetDir, 'SKILL.md'), content);
249
+ throw new Error(`Failed to fetch skill from: ${candidates.filter(Boolean).join(', ')}`);
144
250
  }
145
251
  }
146
252
 
@@ -34,6 +34,8 @@ export async function snipeToken(tokenAddress, amount, opts = {}) {
34
34
  const chain = getConfig('chain') || 'base';
35
35
  const maxSlippage = opts.slippage || getConfig('slippage') || 1.0;
36
36
  const gasMultiplier = opts.gas || getConfig('gasMultiplier') || 1.5;
37
+ const providedPassword = opts.password;
38
+ const providedConfirm = opts.confirm;
37
39
 
38
40
  if (!tokenAddress || !tokenAddress.startsWith('0x')) {
39
41
  error('Provide a valid token contract address');
@@ -45,13 +47,17 @@ export async function snipeToken(tokenAddress, amount, opts = {}) {
45
47
  return;
46
48
  }
47
49
 
48
- // Get password
49
- const { password } = await inquirer.prompt([{
50
- type: 'password',
51
- name: 'password',
52
- message: theme.gold('Wallet password:'),
53
- mask: '',
54
- }]);
50
+ // Get password (prompt unless provided)
51
+ let password = providedPassword;
52
+ if (!password) {
53
+ const prompted = await inquirer.prompt([{
54
+ type: 'password',
55
+ name: 'password',
56
+ message: theme.gold('Wallet password:'),
57
+ mask: '●',
58
+ }]);
59
+ password = prompted.password;
60
+ }
55
61
 
56
62
  const spin = spinner('Preparing snipe...').start();
57
63
 
@@ -107,12 +113,16 @@ export async function snipeToken(tokenAddress, amount, opts = {}) {
107
113
  ]);
108
114
  console.log('');
109
115
 
110
- const { confirm } = await inquirer.prompt([{
111
- type: 'confirm',
112
- name: 'confirm',
113
- message: theme.accent('Execute snipe? This is HIGH RISK.'),
114
- default: false,
115
- }]);
116
+ let confirm = providedConfirm;
117
+ if (typeof confirm !== 'boolean') {
118
+ const prompted = await inquirer.prompt([{
119
+ type: 'confirm',
120
+ name: 'confirm',
121
+ message: theme.accent('Execute snipe? This is HIGH RISK.'),
122
+ default: false,
123
+ }]);
124
+ confirm = prompted.confirm;
125
+ }
116
126
 
117
127
  if (!confirm) {
118
128
  warn('Snipe cancelled');
@@ -137,6 +137,8 @@ export async function executeSwap(opts = {}) {
137
137
  amount,
138
138
  wallet: walletName,
139
139
  slippage,
140
+ password: providedPassword,
141
+ confirm: providedConfirm,
140
142
  } = opts;
141
143
 
142
144
  const chain = getConfig('chain') || 'base';
@@ -155,13 +157,17 @@ export async function executeSwap(opts = {}) {
155
157
  return;
156
158
  }
157
159
 
158
- // Get password for wallet
159
- const { password } = await inquirer.prompt([{
160
- type: 'password',
161
- name: 'password',
162
- message: theme.gold('Wallet password:'),
163
- mask: '',
164
- }]);
160
+ // Get password for wallet (prompt unless provided)
161
+ let password = providedPassword;
162
+ if (!password) {
163
+ const prompted = await inquirer.prompt([{
164
+ type: 'password',
165
+ name: 'password',
166
+ message: theme.gold('Wallet password:'),
167
+ mask: '●',
168
+ }]);
169
+ password = prompted.password;
170
+ }
165
171
 
166
172
  const spin = spinner('Preparing swap...').start();
167
173
 
@@ -214,12 +220,16 @@ export async function executeSwap(opts = {}) {
214
220
  ]);
215
221
  console.log('');
216
222
 
217
- const { confirm } = await inquirer.prompt([{
218
- type: 'confirm',
219
- name: 'confirm',
220
- message: theme.gold('Execute swap?'),
221
- default: false,
222
- }]);
223
+ let confirm = providedConfirm;
224
+ if (typeof confirm !== 'boolean') {
225
+ const prompted = await inquirer.prompt([{
226
+ type: 'confirm',
227
+ name: 'confirm',
228
+ message: theme.gold('Execute swap?'),
229
+ default: false,
230
+ }]);
231
+ confirm = prompted.confirm;
232
+ }
223
233
 
224
234
  if (!confirm) {
225
235
  warn('Swap cancelled');
@@ -269,6 +269,8 @@ const ERC20_SEND_ABI = [
269
269
 
270
270
  export async function sendFunds(opts = {}) {
271
271
  const name = opts.wallet || getConfig('activeWallet');
272
+ const providedPassword = opts.password;
273
+ const providedConfirm = opts.confirm;
272
274
  if (!name) {
273
275
  error('No active wallet. Set one: darksol wallet use <name>');
274
276
  return;
@@ -331,13 +333,17 @@ export async function sendFunds(opts = {}) {
331
333
  }]));
332
334
  }
333
335
 
334
- // Password
335
- const { password } = await inquirer.prompt([{
336
- type: 'password',
337
- name: 'password',
338
- message: theme.gold('Wallet password:'),
339
- mask: '',
340
- }]);
336
+ // Password (prompt unless provided)
337
+ let password = providedPassword;
338
+ if (!password) {
339
+ const prompted = await inquirer.prompt([{
340
+ type: 'password',
341
+ name: 'password',
342
+ message: theme.gold('Wallet password:'),
343
+ mask: '●',
344
+ }]);
345
+ password = prompted.password;
346
+ }
341
347
 
342
348
  const spin = spinner('Preparing transaction...').start();
343
349
 
@@ -420,12 +426,16 @@ export async function sendFunds(opts = {}) {
420
426
  ]);
421
427
  console.log('');
422
428
 
423
- const { confirm } = await inquirer.prompt([{
424
- type: 'confirm',
425
- name: 'confirm',
426
- message: theme.accent('Send this transaction?'),
427
- default: false,
428
- }]);
429
+ let confirm = providedConfirm;
430
+ if (typeof confirm !== 'boolean') {
431
+ const prompted = await inquirer.prompt([{
432
+ type: 'confirm',
433
+ name: 'confirm',
434
+ message: theme.accent('Send this transaction?'),
435
+ default: false,
436
+ }]);
437
+ confirm = prompted.confirm;
438
+ }
429
439
 
430
440
  if (!confirm) {
431
441
  warn('Transaction cancelled');
@@ -115,12 +115,12 @@ export async function handleMenuSelect(id, value, item, ws) {
115
115
  return {};
116
116
  }
117
117
  case 'send':
118
- ws.sendLine('');
119
- ws.sendLine(` ${ANSI.gold}◆ SEND${ANSI.reset}`);
120
- ws.sendLine(` ${ANSI.dim}Sending requires wallet password use the CLI:${ANSI.reset}`);
121
- ws.sendLine(` ${ANSI.gold}darksol send --to 0x... --amount 0.1 --token ETH${ANSI.reset}`);
122
- ws.sendLine(` ${ANSI.gold}darksol send${ANSI.reset} ${ANSI.dim}(interactive mode)${ANSI.reset}`);
123
- ws.sendLine('');
118
+ ws.sendMenu('send_token', '◆ Send Token', [
119
+ { value: 'ETH', label: 'ETH', desc: 'Native token transfer' },
120
+ { value: 'USDC', label: 'USDC', desc: 'Stablecoin transfer' },
121
+ { value: 'custom', label: 'Custom token (0x...)', desc: 'ERC-20 contract address' },
122
+ { value: 'back', label: '← Back', desc: '' },
123
+ ]);
124
124
  return {};
125
125
  case 'portfolio':
126
126
  return await handleCommand('portfolio', ws);
@@ -221,6 +221,86 @@ export async function handleMenuSelect(id, value, item, ws) {
221
221
  case 'cards_status_check':
222
222
  return await showCardStatus(value, ws);
223
223
 
224
+ case 'trade_action':
225
+ if (value === 'swap') {
226
+ ws.sendMenu('trade_swap_pair', '◆ Swap Pair', [
227
+ { value: 'ETH->USDC', label: 'ETH → USDC', desc: 'Most common' },
228
+ { value: 'USDC->ETH', label: 'USDC → ETH', desc: 'Reverse' },
229
+ { value: 'ETH->AERO', label: 'ETH → AERO', desc: 'Base ecosystem' },
230
+ { value: 'ETH->VIRTUAL', label: 'ETH → VIRTUAL', desc: 'Base ecosystem' },
231
+ { value: 'custom', label: 'Custom pair', desc: 'Any symbol or 0x token' },
232
+ { value: 'back', label: '← Back', desc: '' },
233
+ ]);
234
+ return {};
235
+ }
236
+ if (value === 'snipe') {
237
+ ws.sendPrompt('trade_snipe_token', 'Token contract (0x...):', {});
238
+ return {};
239
+ }
240
+ if (value === 'watch') {
241
+ return await handleCommand('trade watch', ws);
242
+ }
243
+ return {};
244
+
245
+ case 'trade_swap_pair': {
246
+ if (value === 'back') return {};
247
+ if (value === 'custom') {
248
+ ws.sendPrompt('trade_swap_custom_pair', 'Pair (format: TOKEN_IN TOKEN_OUT):', {});
249
+ return {};
250
+ }
251
+ const [tokenIn, tokenOut] = value.split('->');
252
+ ws.sendMenu('trade_swap_amount', `◆ Amount (${tokenIn} → ${tokenOut})`, [
253
+ { value: '0.01', label: `0.01 ${tokenIn}`, desc: 'small' , meta: { tokenIn, tokenOut }},
254
+ { value: '0.05', label: `0.05 ${tokenIn}`, desc: 'small' , meta: { tokenIn, tokenOut }},
255
+ { value: '0.1', label: `0.1 ${tokenIn}`, desc: 'standard' , meta: { tokenIn, tokenOut }},
256
+ { value: '0.25', label: `0.25 ${tokenIn}`, desc: 'medium' , meta: { tokenIn, tokenOut }},
257
+ { value: '1', label: `1 ${tokenIn}`, desc: 'large' , meta: { tokenIn, tokenOut }},
258
+ { value: 'custom', label: 'Custom amount', desc: '', meta: { tokenIn, tokenOut }},
259
+ ]);
260
+ return {};
261
+ }
262
+
263
+ case 'trade_swap_amount':
264
+ if (value === 'custom') {
265
+ ws.sendPrompt('trade_swap_custom_amount', `Amount (${item?.meta?.tokenIn || 'token'}):`, item?.meta || {});
266
+ return {};
267
+ }
268
+ ws.sendPrompt('trade_swap_password', `Wallet password (${item?.meta?.tokenIn || ''} → ${item?.meta?.tokenOut || ''}, ${value}):`, {
269
+ ...(item?.meta || {}),
270
+ amount: value,
271
+ mask: true,
272
+ });
273
+ return {};
274
+
275
+ case 'trade_snipe_amount':
276
+ ws.sendPrompt('trade_snipe_password', `Wallet password (snipe ${item?.meta?.token || ''} with ${value} ETH):`, {
277
+ ...(item?.meta || {}),
278
+ amount: value,
279
+ mask: true,
280
+ });
281
+ return {};
282
+
283
+ case 'send_token':
284
+ if (value === 'back') return {};
285
+ if (value === 'custom') {
286
+ ws.sendPrompt('send_custom_token', 'Token contract address (0x...):', {});
287
+ return {};
288
+ }
289
+ ws.sendPrompt('send_to', `Recipient address (for ${value}):`, { token: value });
290
+ return {};
291
+
292
+ case 'send_amount':
293
+ if (value === 'custom') {
294
+ ws.sendPrompt('send_custom_amount', `Amount (${item?.meta?.token || 'token'}):`, item?.meta || {});
295
+ return {};
296
+ }
297
+ ws.sendPrompt('send_password', `Wallet password (send ${value} ${item?.meta?.token || 'token'}):`, {
298
+ ...(item?.meta || {}),
299
+ amount: value,
300
+ mask: true,
301
+ });
302
+ return {};
303
+
224
304
  case 'agent_action':
225
305
  if (value === 'start') {
226
306
  const { listWallets } = await import('../wallet/keystore.js');
@@ -348,6 +428,185 @@ export async function handlePromptResponse(id, value, meta, ws) {
348
428
  return {};
349
429
  }
350
430
 
431
+ if (id === 'send_custom_token') {
432
+ if (!value || !value.startsWith('0x') || value.length !== 42) {
433
+ ws.sendLine(` ${ANSI.red}✗ Invalid token address${ANSI.reset}`);
434
+ ws.sendLine('');
435
+ return {};
436
+ }
437
+ ws.sendPrompt('send_to', 'Recipient address:', { token: value.trim() });
438
+ return {};
439
+ }
440
+
441
+ if (id === 'send_to') {
442
+ if (!value || !value.startsWith('0x') || value.length !== 42) {
443
+ ws.sendLine(` ${ANSI.red}✗ Invalid recipient address${ANSI.reset}`);
444
+ ws.sendLine('');
445
+ return {};
446
+ }
447
+
448
+ const token = meta?.token || 'ETH';
449
+ const defaultAmounts = token === 'ETH'
450
+ ? ['0.005', '0.01', '0.05', '0.1']
451
+ : ['1', '5', '10', '25'];
452
+
453
+ ws.sendMenu('send_amount', `◆ Amount (${token})`, [
454
+ ...defaultAmounts.map(a => ({ value: a, label: `${a} ${token === 'ETH' ? 'ETH' : ''}`.trim(), desc: 'quick', meta: { token, to: value.trim() } })),
455
+ { value: 'custom', label: 'Custom amount', desc: '', meta: { token, to: value.trim() } },
456
+ ]);
457
+ return {};
458
+ }
459
+
460
+ if (id === 'send_custom_amount') {
461
+ const n = Number(value);
462
+ if (!Number.isFinite(n) || n <= 0) {
463
+ ws.sendLine(` ${ANSI.red}✗ Invalid amount${ANSI.reset}`);
464
+ ws.sendLine('');
465
+ return {};
466
+ }
467
+ ws.sendPrompt('send_password', `Wallet password (send ${value} ${meta?.token || 'token'}):`, {
468
+ ...meta,
469
+ amount: String(value),
470
+ mask: true,
471
+ });
472
+ return {};
473
+ }
474
+
475
+ if (id === 'send_password') {
476
+ if (!meta?.to || !meta?.token || !meta?.amount || !value) {
477
+ ws.sendLine(` ${ANSI.red}✗ Missing send details${ANSI.reset}`);
478
+ ws.sendLine('');
479
+ return {};
480
+ }
481
+
482
+ ws.sendLine(` ${ANSI.dim}Sending ${meta.amount} ${meta.token} to ${meta.to.slice(0, 8)}...${ANSI.reset}`);
483
+ ws.sendLine('');
484
+
485
+ try {
486
+ const { sendFunds } = await import('../wallet/manager.js');
487
+ await sendFunds({
488
+ wallet: getConfig('activeWallet'),
489
+ to: meta.to,
490
+ amount: meta.amount,
491
+ token: meta.token,
492
+ password: value,
493
+ confirm: true,
494
+ });
495
+ ws.sendLine(` ${ANSI.green}✓ Send flow completed (check terminal output for receipt)${ANSI.reset}`);
496
+ } catch (err) {
497
+ ws.sendLine(` ${ANSI.red}✗ Send failed: ${err.message}${ANSI.reset}`);
498
+ }
499
+ ws.sendLine('');
500
+ return {};
501
+ }
502
+
503
+ if (id === 'trade_swap_custom_pair') {
504
+ if (!value) { ws.sendLine(` ${ANSI.red}✗ Cancelled${ANSI.reset}`); ws.sendLine(''); return {}; }
505
+ const parts = value.trim().split(/\s+/).filter(Boolean);
506
+ if (parts.length < 2) {
507
+ ws.sendLine(` ${ANSI.red}✗ Format: TOKEN_IN TOKEN_OUT${ANSI.reset}`);
508
+ ws.sendLine('');
509
+ return {};
510
+ }
511
+ const [tokenIn, tokenOut] = parts;
512
+ ws.sendMenu('trade_swap_amount', `◆ Amount (${tokenIn} → ${tokenOut})`, [
513
+ { value: '0.01', label: `0.01 ${tokenIn}`, desc: 'small', meta: { tokenIn, tokenOut } },
514
+ { value: '0.05', label: `0.05 ${tokenIn}`, desc: 'small', meta: { tokenIn, tokenOut } },
515
+ { value: '0.1', label: `0.1 ${tokenIn}`, desc: 'standard', meta: { tokenIn, tokenOut } },
516
+ { value: '0.25', label: `0.25 ${tokenIn}`, desc: 'medium', meta: { tokenIn, tokenOut } },
517
+ { value: '1', label: `1 ${tokenIn}`, desc: 'large', meta: { tokenIn, tokenOut } },
518
+ { value: 'custom', label: 'Custom amount', desc: '', meta: { tokenIn, tokenOut } },
519
+ ]);
520
+ return {};
521
+ }
522
+
523
+ if (id === 'trade_swap_custom_amount') {
524
+ if (!value) { ws.sendLine(` ${ANSI.red}✗ Cancelled${ANSI.reset}`); ws.sendLine(''); return {}; }
525
+ const n = Number(value);
526
+ if (!Number.isFinite(n) || n <= 0) {
527
+ ws.sendLine(` ${ANSI.red}✗ Invalid amount${ANSI.reset}`);
528
+ ws.sendLine('');
529
+ return {};
530
+ }
531
+ ws.sendPrompt('trade_swap_password', `Wallet password (${meta?.tokenIn || ''} → ${meta?.tokenOut || ''}, ${value}):`, {
532
+ ...meta,
533
+ amount: String(value),
534
+ mask: true,
535
+ });
536
+ return {};
537
+ }
538
+
539
+ if (id === 'trade_swap_password') {
540
+ if (!meta?.tokenIn || !meta?.tokenOut || !meta?.amount || !value) {
541
+ ws.sendLine(` ${ANSI.red}✗ Missing swap details${ANSI.reset}`);
542
+ ws.sendLine('');
543
+ return {};
544
+ }
545
+
546
+ ws.sendLine(` ${ANSI.dim}Executing swap ${meta.amount} ${meta.tokenIn} → ${meta.tokenOut}...${ANSI.reset}`);
547
+ ws.sendLine('');
548
+
549
+ try {
550
+ const { executeSwap } = await import('../trading/swap.js');
551
+ await executeSwap({
552
+ tokenIn: meta.tokenIn,
553
+ tokenOut: meta.tokenOut,
554
+ amount: meta.amount,
555
+ slippage: parseFloat(getConfig('slippage') || 0.5),
556
+ wallet: getConfig('activeWallet'),
557
+ password: value,
558
+ confirm: true,
559
+ });
560
+ ws.sendLine(` ${ANSI.green}✓ Swap flow completed (check terminal output for receipt)${ANSI.reset}`);
561
+ } catch (err) {
562
+ ws.sendLine(` ${ANSI.red}✗ Swap failed: ${err.message}${ANSI.reset}`);
563
+ }
564
+ ws.sendLine('');
565
+ return {};
566
+ }
567
+
568
+ if (id === 'trade_snipe_token') {
569
+ if (!value || !value.startsWith('0x') || value.length !== 42) {
570
+ ws.sendLine(` ${ANSI.red}✗ Invalid token contract${ANSI.reset}`);
571
+ ws.sendLine('');
572
+ return {};
573
+ }
574
+ ws.sendMenu('trade_snipe_amount', '◆ Snipe Amount (ETH)', [
575
+ { value: '0.01', label: '0.01 ETH', desc: 'small', meta: { token: value.trim() } },
576
+ { value: '0.05', label: '0.05 ETH', desc: 'standard', meta: { token: value.trim() } },
577
+ { value: '0.1', label: '0.1 ETH', desc: 'medium', meta: { token: value.trim() } },
578
+ { value: '0.25', label: '0.25 ETH', desc: 'large', meta: { token: value.trim() } },
579
+ ]);
580
+ return {};
581
+ }
582
+
583
+ if (id === 'trade_snipe_password') {
584
+ if (!meta?.token || !meta?.amount || !value) {
585
+ ws.sendLine(` ${ANSI.red}✗ Missing snipe details${ANSI.reset}`);
586
+ ws.sendLine('');
587
+ return {};
588
+ }
589
+
590
+ ws.sendLine(` ${ANSI.dim}Executing snipe ${meta.amount} ETH -> ${meta.token.slice(0, 8)}...${ANSI.reset}`);
591
+ ws.sendLine('');
592
+
593
+ try {
594
+ const { snipeToken } = await import('../trading/snipe.js');
595
+ await snipeToken(meta.token, meta.amount, {
596
+ slippage: parseFloat(getConfig('slippage') || 1),
597
+ gas: parseFloat(getConfig('gasMultiplier') || 1.5),
598
+ wallet: getConfig('activeWallet'),
599
+ password: value,
600
+ confirm: true,
601
+ });
602
+ ws.sendLine(` ${ANSI.green}✓ Snipe flow completed (check terminal output for receipt)${ANSI.reset}`);
603
+ } catch (err) {
604
+ ws.sendLine(` ${ANSI.red}✗ Snipe failed: ${err.message}${ANSI.reset}`);
605
+ }
606
+ ws.sendLine('');
607
+ return {};
608
+ }
609
+
351
610
  if (id === 'agent_signer_password') {
352
611
  const wallet = meta.wallet;
353
612
  if (!wallet || !value) {
@@ -383,7 +642,7 @@ export function getAIStatus() {
383
642
  const dim = '\x1b[38;2;102;102;102m';
384
643
  const reset = '\x1b[0m';
385
644
 
386
- const providers = ['openai', 'anthropic', 'openrouter', 'ollama'];
645
+ const providers = ['openai', 'anthropic', 'openrouter', 'ollama', 'bankr'];
387
646
  const connected = providers.filter(p => hasKey(p));
388
647
 
389
648
  if (connected.length > 0) {
@@ -398,6 +657,7 @@ export function getAIStatus() {
398
657
  ` ${green}keys add openai sk-...${reset} ${dim}OpenAI (GPT-4o)${reset}`,
399
658
  ` ${green}keys add anthropic sk-ant-...${reset} ${dim}Anthropic (Claude)${reset}`,
400
659
  ` ${green}keys add openrouter sk-or-...${reset} ${dim}OpenRouter (any model)${reset}`,
660
+ ` ${green}keys add bankr bk_...${reset} ${dim}Bankr LLM Gateway (crypto credits)${reset}`,
401
661
  ` ${green}keys add ollama http://...${reset} ${dim}Ollama (free, local)${reset}`,
402
662
  '',
403
663
  ].join('\r\n');
@@ -424,6 +684,8 @@ export async function handleCommand(cmd, ws) {
424
684
  return await cmdHistory(args, ws);
425
685
  case 'market':
426
686
  return await cmdMarket(args, ws);
687
+ case 'trade':
688
+ return await cmdTrade(args, ws);
427
689
  case 'wallet':
428
690
  return await cmdWallet(args, ws);
429
691
  case 'mail':
@@ -705,6 +967,34 @@ async function cmdMarket(args, ws) {
705
967
  return {};
706
968
  }
707
969
 
970
+ // ══════════════════════════════════════════════════
971
+ // TRADE (interactive web flow)
972
+ // ══════════════════════════════════════════════════
973
+ async function cmdTrade(args, ws) {
974
+ const sub = (args[0] || '').toLowerCase();
975
+
976
+ if (sub === 'watch') {
977
+ ws.sendLine(`${ANSI.dim}Pair watch is CLI-first right now:${ANSI.reset}`);
978
+ ws.sendLine(` ${ANSI.gold}darksol trade watch${ANSI.reset}`);
979
+ ws.sendLine('');
980
+ return {};
981
+ }
982
+
983
+ ws.sendLine(`${ANSI.gold} ◆ TRADE${ANSI.reset}`);
984
+ ws.sendLine(`${ANSI.dim} ${'─'.repeat(50)}${ANSI.reset}`);
985
+ ws.sendLine(` ${ANSI.white}Choose an execution flow:${ANSI.reset}`);
986
+ ws.sendLine('');
987
+
988
+ ws.sendMenu('trade_action', '◆ Trade Actions', [
989
+ { value: 'swap', label: '🔄 Swap', desc: 'Interactive token swap (password prompt)' },
990
+ { value: 'snipe', label: '⚡ Snipe', desc: 'Fast buy by token contract' },
991
+ { value: 'watch', label: '👀 Watch Pairs', desc: 'Monitor new pairs (CLI guidance)' },
992
+ { value: 'back', label: '← Back', desc: '' },
993
+ ]);
994
+
995
+ return {};
996
+ }
997
+
708
998
  // ══════════════════════════════════════════════════
709
999
  // WALLET
710
1000
  // ══════════════════════════════════════════════════
@@ -1470,7 +1760,7 @@ async function cmdKeys(args, ws) {
1470
1760
  ws.sendLine(`${ANSI.dim} ${'─'.repeat(50)}${ANSI.reset}`);
1471
1761
  ws.sendLine('');
1472
1762
 
1473
- const llmProviders = ['openai', 'anthropic', 'openrouter', 'ollama'];
1763
+ const llmProviders = ['openai', 'anthropic', 'openrouter', 'ollama', 'bankr'];
1474
1764
  ws.sendLine(` ${ANSI.gold}LLM Providers:${ANSI.reset}`);
1475
1765
  for (const p of llmProviders) {
1476
1766
  const svc = SERVICES[p];
@@ -1564,24 +1854,17 @@ async function cmdSend(args, ws) {
1564
1854
  return {};
1565
1855
  }
1566
1856
 
1567
- ws.sendLine(` ${ANSI.white}Send ETH or any ERC-20 token from your wallet.${ANSI.reset}`);
1568
- ws.sendLine('');
1569
- ws.sendLine(` ${ANSI.darkGold}Usage:${ANSI.reset}`);
1570
- ws.sendLine(` ${ANSI.gold}darksol send --to 0x... --amount 0.1 --token ETH${ANSI.reset}`);
1571
- ws.sendLine(` ${ANSI.gold}darksol send --to 0x... --amount 50 --token USDC${ANSI.reset}`);
1572
- ws.sendLine(` ${ANSI.gold}darksol send${ANSI.reset} ${ANSI.dim}(interactive mode — prompts for everything)${ANSI.reset}`);
1573
- ws.sendLine('');
1574
- ws.sendLine(` ${ANSI.darkGold}Features:${ANSI.reset}`);
1575
- ws.sendLine(` ${ANSI.dim}•${ANSI.reset} ETH and any ERC-20 token`);
1576
- ws.sendLine(` ${ANSI.dim}•${ANSI.reset} Balance check before sending`);
1577
- ws.sendLine(` ${ANSI.dim}•${ANSI.reset} Gas estimation in preview`);
1578
- ws.sendLine(` ${ANSI.dim}•${ANSI.reset} Confirmation prompt before execution`);
1579
- ws.sendLine(` ${ANSI.dim}•${ANSI.reset} On-chain receipt after confirmation`);
1580
- ws.sendLine('');
1581
- ws.sendLine(` ${ANSI.darkGold}Active:${ANSI.reset} ${ANSI.white}${wallet}${ANSI.reset} on ${ANSI.white}${chain}${ANSI.reset}`);
1582
- ws.sendLine('');
1583
- ws.sendLine(` ${ANSI.dim}⚠ Sending requires the CLI. Install: npm i -g @darksol/terminal${ANSI.reset}`);
1857
+ ws.sendLine(` ${ANSI.white}Wallet:${ANSI.reset} ${ANSI.gold}${wallet}${ANSI.reset} ${ANSI.dim}on ${chain}${ANSI.reset}`);
1858
+ ws.sendLine(` ${ANSI.dim}Interactive send flow will ask token → recipient → amount → password.${ANSI.reset}`);
1584
1859
  ws.sendLine('');
1860
+
1861
+ ws.sendMenu('send_token', '◆ Send Token', [
1862
+ { value: 'ETH', label: 'ETH', desc: 'Native token transfer' },
1863
+ { value: 'USDC', label: 'USDC', desc: 'Stablecoin transfer' },
1864
+ { value: 'custom', label: 'Custom token (0x...)', desc: 'ERC-20 contract address' },
1865
+ { value: 'back', label: '← Back', desc: '' },
1866
+ ]);
1867
+
1585
1868
  return {};
1586
1869
  }
1587
1870
 
package/src/web/server.js CHANGED
@@ -163,10 +163,12 @@ export async function startWebShell(opts = {}) {
163
163
  items: [
164
164
  { value: 'ai', label: '🧠 AI Chat', desc: 'Natural language assistant' },
165
165
  { value: 'wallet', label: '👛 Wallet', desc: 'Picker + balance + actions' },
166
+ { value: 'send', label: '📤 Send', desc: 'Interactive transfer flow' },
166
167
  { value: 'agent', label: '🔐 Agent Signer', desc: 'Start/stop/status controls' },
167
168
  { value: 'keys', label: '🔑 Keys', desc: 'Add/update LLM providers' },
168
169
  { value: 'config', label: '⚙ Config', desc: 'Chain + settings' },
169
170
  { value: 'portfolio', label: '📊 Portfolio', desc: 'Multi-chain balances' },
171
+ { value: 'trade', label: '🔄 Trade', desc: 'Swap / snipe click-through flows' },
170
172
  { value: 'market', label: '📈 Market', desc: 'Price + liquidity intel' },
171
173
  { value: 'mail', label: '📧 Mail', desc: 'AgentMail status/inbox' },
172
174
  { value: 'cards', label: '💳 Cards', desc: 'Order prepaid Visa/MC' },
@@ -298,6 +300,7 @@ function getHelp() {
298
300
  ['watch <token>', 'Live price monitor'],
299
301
  ['gas [chain]', 'Gas prices & estimates'],
300
302
  ['portfolio', 'Multi-chain balances'],
303
+ ['trade', 'Interactive swap/snipe menu'],
301
304
  ['send', 'Send ETH or tokens'],
302
305
  ['receive', 'Show address to receive'],
303
306
  ['wallet', 'Interactive wallet menu'],