@darksol/terminal 0.4.9 → 0.4.11

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,6 +15,8 @@ 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
+ - Changelog: `CHANGELOG.md`
19
+
18
20
  ## Install
19
21
 
20
22
  ```bash
@@ -34,6 +36,10 @@ darksol wallet create main
34
36
  darksol wallet balance
35
37
  darksol portfolio
36
38
 
39
+ # Send / receive
40
+ darksol receive
41
+ darksol send --to 0xabc... --amount 10 --token USDC
42
+
37
43
  # Token prices & live monitoring
38
44
  darksol price ETH AERO VIRTUAL
39
45
  darksol watch AERO --above 2.0
@@ -58,34 +64,60 @@ darksol serve
58
64
  darksol agent start main
59
65
  ```
60
66
 
67
+ ## `darksol serve` (Web Terminal UX)
68
+
69
+ `darksol serve` now supports an interactive keyboard-driven UI:
70
+
71
+ - Arrow-key menus (`↑/↓` + `Enter`) for wallet/config flows
72
+ - Interactive wallet picker + wallet action menu (receive/send/portfolio/history/switch chain)
73
+ - Agent signer control center (`agent`) with guided wallet selection + start/stop/status
74
+ - Click-through help menu (`help`) with arrow-key command selection
75
+ - AI connection check at startup (shows ready/not configured)
76
+ - Interactive key setup from web terminal:
77
+ - `keys` → select provider → paste key/host directly
78
+ - masked input for API keys, plain input for Ollama URL
79
+ - Local chat memory logs at `~/.darksol/chat-logs/YYYY-MM-DD.jsonl`
80
+ - Natural language fuzzy routing to AI for non-command prompts
81
+
82
+ Useful web-shell commands:
83
+
84
+ ```bash
85
+ help # clickable command menu (arrow keys + Enter)
86
+ keys # provider status + interactive add/update
87
+ wallet # interactive wallet picker and actions
88
+ agent # signer start/stop/status controls
89
+ config # interactive config menu
90
+ logs 20 # show recent AI chat log lines
91
+ ai <prompt> # chat with trading assistant
92
+ ```
93
+
61
94
  ## Modules
62
95
 
63
96
  | Module | Description | Pricing |
64
97
  |--------|-------------|---------|
65
- | `wallet` | Create, import, manage encrypted wallets | Free |
98
+ | `wallet` | Create/import/manage encrypted EVM wallets | Free |
99
+ | `send` | Send ETH or ERC-20 tokens | Gas only |
100
+ | `receive` | Show receive address + chain safety hints | Free |
66
101
  | `trade` | Swap (Uniswap V3), snipe (V2), token trading | Gas only |
67
102
  | `dca` | Dollar-cost averaging engine | Gas only |
68
- | `ai` | LLM-powered trading assistant & analysis | Provider dependent |
103
+ | `ai` | LLM-powered trading assistant & intent execution | Provider dependent |
69
104
  | `agent` | Secure agent signer (PK-isolated proxy) | Free |
70
- | `keys` | API key vault (LLMs, data, RPCs) | Free |
105
+ | `keys` | Encrypted API key vault (LLMs/data/RPCs) | Free |
71
106
  | `script` | Execution scripts & automated strategies | Free |
72
107
  | `skills` | Agent skill directory & installer | Free |
73
- | `market` | Market intel, top movers, token analysis | x402 micropayments |
74
- | `oracle` | On-chain random number oracle | $0.05–$0.25 |
75
- | `casino` | The Clawsino — on-chain betting | $1 flat bets |
76
108
  | `portfolio` | Multi-chain balance view (5 EVM chains) | Free |
109
+ | `history` | Transaction history via block explorers | Free |
77
110
  | `gas` | Gas prices & cost estimates | Free |
78
111
  | `price` | Quick token price check (DexScreener) | Free |
79
112
  | `watch` | Live price monitoring with alerts | Free |
80
- | `history` | Transaction history via block explorers | Free |
113
+ | `market` | Market intel, top movers, token analysis | x402 micropayments |
81
114
  | `mail` | AgentMail — email for AI agents | Free tier |
82
- | `serve` | Web terminal in browser (xterm.js) | Free |
83
- | `facilitator` | x402 payment facilitator | Free |
84
- | `cards` | Prepaid Visa/MC cards | Service fees |
85
- | `builders` | ERC-8021 builder code directory | Free |
86
- | `cards` | Crypto → prepaid Visa/MC (no KYC) | 3% markup |
87
- | `builders` | ERC-8021 builder leaderboard | Free |
115
+ | `oracle` | On-chain random number oracle | $0.05–$0.25 |
116
+ | `casino` | The Clawsino on-chain betting | $1 flat bets |
117
+ | `cards` | Crypto → prepaid Visa/MC cards | Service fees |
118
+ | `builders` | ERC-8021 builder directory + leaderboard | Free |
88
119
  | `facilitator` | x402 payment verification & settlement | Free |
120
+ | `serve` | Local interactive web terminal (xterm.js) | Free |
89
121
  | `config` | Terminal configuration | Free |
90
122
 
91
123
  ---
@@ -152,9 +184,12 @@ Natural language trading powered by multi-provider LLM support.
152
184
  # Interactive chat with live market data
153
185
  darksol ai chat
154
186
 
155
- # One-shot intent parsing
187
+ # One-shot intent parsing (+ optional execution prompt)
156
188
  darksol ai ask "buy 0.5 ETH worth of AERO on Base"
157
189
 
190
+ # Parse + execute directly
191
+ darksol ai execute "send 10 USDC to 0x..."
192
+
158
193
  # DCA strategy recommendation
159
194
  darksol ai strategy VIRTUAL --budget 500 --timeframe "30 days"
160
195
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@darksol/terminal",
3
- "version": "0.4.9",
3
+ "version": "0.4.11",
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": {
@@ -402,12 +402,16 @@ export async function startAgentSigner(walletName, opts = {}) {
402
402
  return;
403
403
  }
404
404
 
405
- const { password } = await inquirer.prompt([{
406
- type: 'password',
407
- name: 'password',
408
- message: theme.gold('Wallet password:'),
409
- mask: '',
410
- }]);
405
+ let password = process.env.DARKSOL_WALLET_PASSWORD;
406
+ if (!password) {
407
+ const promptRes = await inquirer.prompt([{
408
+ type: 'password',
409
+ name: 'password',
410
+ message: theme.gold('Wallet password:'),
411
+ mask: '●',
412
+ }]);
413
+ password = promptRes.password;
414
+ }
411
415
 
412
416
  const spin = spinner('Starting agent signer...').start();
413
417
 
@@ -3,13 +3,27 @@ import { getConfig, setConfig } from '../config/store.js';
3
3
  import { hasKey, hasAnyLLM, getKeyAuto, addKeyDirect, SERVICES } from '../config/keys.js';
4
4
  import { ethers } from 'ethers';
5
5
  import { existsSync, mkdirSync, appendFileSync, readFileSync } from 'fs';
6
- import { join } from 'path';
6
+ import { join, dirname } from 'path';
7
7
  import { homedir } from 'os';
8
+ import { spawn } from 'child_process';
9
+ import { fileURLToPath } from 'url';
8
10
 
9
11
  // ══════════════════════════════════════════════════
10
12
  // CHAT LOG PERSISTENCE
11
13
  // ══════════════════════════════════════════════════
12
14
  const CHAT_LOG_DIR = join(homedir(), '.darksol', 'chat-logs');
15
+ const __filename = fileURLToPath(import.meta.url);
16
+ const __dirname = dirname(__filename);
17
+ const PROJECT_ROOT = join(__dirname, '..', '..');
18
+
19
+ // Agent signer runtime state (for web serve session)
20
+ const signerState = {
21
+ proc: null,
22
+ wallet: null,
23
+ port: 18790,
24
+ startedAt: null,
25
+ lastOutput: [],
26
+ };
13
27
 
14
28
  function ensureChatLogDir() {
15
29
  if (!existsSync(CHAT_LOG_DIR)) mkdirSync(CHAT_LOG_DIR, { recursive: true });
@@ -153,13 +167,45 @@ export async function handleMenuSelect(id, value, item, ws) {
153
167
  }
154
168
  ws.sendLine('');
155
169
  // Send a prompt request to the client
156
- ws.send(JSON.stringify({
157
- type: 'prompt',
158
- id: 'keys_input',
159
- label: `${svc.name} key:`,
170
+ ws.sendPrompt('keys_input', `${svc.name} key:`, {
160
171
  service: value,
161
172
  mask: value !== 'ollama', // mask API keys, not URLs
162
- }));
173
+ });
174
+ return {};
175
+
176
+ case 'agent_action':
177
+ if (value === 'start') {
178
+ const { listWallets } = await import('../wallet/keystore.js');
179
+ const wallets = listWallets();
180
+ if (!wallets.length) {
181
+ ws.sendLine(` ${ANSI.red}No wallets found. Create one in CLI first: darksol wallet create <name>${ANSI.reset}`);
182
+ ws.sendLine('');
183
+ return {};
184
+ }
185
+ ws.sendMenu('agent_wallet_select', '◆ Select Wallet for Signer', wallets.map(w => ({
186
+ value: w.name,
187
+ label: w.name,
188
+ desc: `${w.address.slice(0, 6)}...${w.address.slice(-4)}`,
189
+ })));
190
+ return {};
191
+ }
192
+ if (value === 'status') return await showSignerStatus(ws);
193
+ if (value === 'stop') return await cmdAgent(['stop'], ws);
194
+ if (value === 'docs') {
195
+ ws.sendLine('');
196
+ ws.sendLine(`${ANSI.gold} ◆ OPENCLAW INTEGRATION${ANSI.reset}`);
197
+ ws.sendLine(`${ANSI.dim} ${'─'.repeat(50)}${ANSI.reset}`);
198
+ ws.sendLine(` ${ANSI.dim}Endpoint: http://127.0.0.1:${signerState.port}${ANSI.reset}`);
199
+ ws.sendLine(` ${ANSI.dim}Health: GET /health${ANSI.reset}`);
200
+ ws.sendLine(` ${ANSI.dim}Send TX: POST /send${ANSI.reset}`);
201
+ ws.sendLine(` ${ANSI.dim}Policy: GET /policy${ANSI.reset}`);
202
+ ws.sendLine('');
203
+ return {};
204
+ }
205
+ return {};
206
+
207
+ case 'agent_wallet_select':
208
+ ws.sendPrompt('agent_signer_password', `Password for wallet \"${value}\":`, { service: 'agent', wallet: value, mask: true });
163
209
  return {};
164
210
 
165
211
  case 'config_action':
@@ -180,6 +226,10 @@ export async function handleMenuSelect(id, value, item, ws) {
180
226
  return {};
181
227
 
182
228
  case 'main_menu':
229
+ if (value === 'back') {
230
+ ws.sendLine('');
231
+ return {};
232
+ }
183
233
  return await handleCommand(value, ws);
184
234
  }
185
235
 
@@ -216,10 +266,6 @@ export async function handlePromptResponse(id, value, meta, ws) {
216
266
  ws.sendLine(` ${ANSI.green}✓ ${svc.name} key stored securely${ANSI.reset}`);
217
267
  ws.sendLine(` ${ANSI.dim}Encrypted at ~/.darksol/keys/vault.json${ANSI.reset}`);
218
268
  ws.sendLine('');
219
-
220
- // Clear cached AI engine
221
- // (chatEngines is WeakMap keyed by ws, but we can't access the real ws here —
222
- // the engine will reinit on next ai command since keys changed)
223
269
  ws.sendLine(` ${ANSI.green}● AI ready!${ANSI.reset} ${ANSI.dim}Type ${ANSI.gold}ai <question>${ANSI.dim} to start chatting.${ANSI.reset}`);
224
270
  ws.sendLine('');
225
271
  } catch (err) {
@@ -229,6 +275,28 @@ export async function handlePromptResponse(id, value, meta, ws) {
229
275
  return {};
230
276
  }
231
277
 
278
+ if (id === 'agent_signer_password') {
279
+ const wallet = meta.wallet;
280
+ if (!wallet || !value) {
281
+ ws.sendLine(` ${ANSI.red}✗ Cancelled${ANSI.reset}`);
282
+ ws.sendLine('');
283
+ return {};
284
+ }
285
+
286
+ ws.sendLine(` ${ANSI.dim}Starting signer for ${wallet}...${ANSI.reset}`);
287
+ ws.sendLine('');
288
+ startSignerProcess({ wallet, password: value, port: 18790, maxValue: '1.0', dailyLimit: '5.0' }, ws);
289
+
290
+ // Give it a second then show status
291
+ setTimeout(() => {
292
+ showSignerStatus(ws);
293
+ ws.sendLine(` ${ANSI.dim}Use ${ANSI.gold}agent${ANSI.dim} for controls (status/stop/docs).${ANSI.reset}`);
294
+ ws.sendLine('');
295
+ }, 1200);
296
+
297
+ return {};
298
+ }
299
+
232
300
  return {};
233
301
  }
234
302
 
@@ -299,6 +367,9 @@ export async function handleCommand(cmd, ws) {
299
367
  return await cmdSend(args, ws);
300
368
  case 'receive':
301
369
  return await cmdReceive(ws);
370
+ case 'agent':
371
+ case 'signer':
372
+ return await cmdAgent(args, ws);
302
373
  case 'ai':
303
374
  case 'ask':
304
375
  case 'chat':
@@ -680,6 +751,93 @@ async function showWalletDetail(name, ws) {
680
751
  return {};
681
752
  }
682
753
 
754
+ // ══════════════════════════════════════════════════
755
+ // AGENT SIGNER (web controls)
756
+ // ══════════════════════════════════════════════════
757
+ async function cmdAgent(args, ws) {
758
+ const sub = (args[0] || 'menu').toLowerCase();
759
+
760
+ if (sub === 'status') {
761
+ return await showSignerStatus(ws);
762
+ }
763
+
764
+ if (sub === 'stop') {
765
+ if (!signerState.proc) {
766
+ ws.sendLine(` ${ANSI.dim}Signer is not running${ANSI.reset}`);
767
+ ws.sendLine('');
768
+ return {};
769
+ }
770
+ signerState.proc.kill('SIGTERM');
771
+ signerState.proc = null;
772
+ ws.sendLine(` ${ANSI.green}✓ Signer stopped${ANSI.reset}`);
773
+ ws.sendLine('');
774
+ return {};
775
+ }
776
+
777
+ // default menu
778
+ await showSignerStatus(ws);
779
+ ws.sendMenu('agent_action', '◆ Agent Signer Controls', [
780
+ { value: 'start', label: signerState.proc ? '🔁 Restart signer' : '▶ Start signer', desc: signerState.proc ? `Running on :${signerState.port}` : 'Guided setup' },
781
+ { value: 'status', label: '📊 Status', desc: 'Health, wallet, endpoint' },
782
+ { value: 'stop', label: '⏹ Stop signer', desc: signerState.proc ? 'Stop current signer session' : 'Not running' },
783
+ { value: 'docs', label: '📘 Integration', desc: 'OpenClaw endpoint + usage tips' },
784
+ { value: 'back', label: '← Back', desc: '' },
785
+ ]);
786
+ return {};
787
+ }
788
+
789
+ async function showSignerStatus(ws) {
790
+ ws.sendLine(`${ANSI.gold} ◆ AGENT SIGNER${ANSI.reset}`);
791
+ ws.sendLine(`${ANSI.dim} ${'─'.repeat(50)}${ANSI.reset}`);
792
+ ws.sendLine(` ${ANSI.darkGold}Status${ANSI.reset} ${signerState.proc ? `${ANSI.green}● Running${ANSI.reset}` : `${ANSI.dim}○ Stopped${ANSI.reset}`}`);
793
+ ws.sendLine(` ${ANSI.darkGold}Wallet${ANSI.reset} ${ANSI.white}${signerState.wallet || '(none)'}${ANSI.reset}`);
794
+ ws.sendLine(` ${ANSI.darkGold}Endpoint${ANSI.reset} ${ANSI.white}http://127.0.0.1:${signerState.port}${ANSI.reset}`);
795
+ ws.sendLine(` ${ANSI.darkGold}Started${ANSI.reset} ${signerState.startedAt ? ANSI.dim + new Date(signerState.startedAt).toLocaleTimeString() + ANSI.reset : ANSI.dim + '(n/a)' + ANSI.reset}`);
796
+ ws.sendLine('');
797
+ }
798
+
799
+ function startSignerProcess({ wallet, password, port = 18790, maxValue = '1.0', dailyLimit = '5.0' }, ws) {
800
+ if (signerState.proc) {
801
+ try { signerState.proc.kill('SIGTERM'); } catch {}
802
+ }
803
+
804
+ const args = [
805
+ 'bin/darksol.js',
806
+ 'agent', 'start', wallet,
807
+ '--port', String(port),
808
+ '--max-value', String(maxValue),
809
+ '--daily-limit', String(dailyLimit),
810
+ ];
811
+
812
+ // Pass password non-interactively via env to avoid terminal prompt complexity
813
+ const child = spawn(process.execPath, args, {
814
+ cwd: PROJECT_ROOT,
815
+ env: { ...process.env, DARKSOL_WALLET_PASSWORD: password },
816
+ stdio: ['ignore', 'pipe', 'pipe'],
817
+ });
818
+
819
+ signerState.proc = child;
820
+ signerState.wallet = wallet;
821
+ signerState.port = Number(port);
822
+ signerState.startedAt = Date.now();
823
+ signerState.lastOutput = [];
824
+
825
+ const onOut = (buf) => {
826
+ const text = buf.toString();
827
+ signerState.lastOutput.push(text);
828
+ if (signerState.lastOutput.length > 30) signerState.lastOutput.shift();
829
+ // Show a compact boot stream
830
+ const lines = text.split('\n').map(s => s.trim()).filter(Boolean).slice(0, 2);
831
+ for (const l of lines) ws.sendLine(` ${ANSI.dim}${l}${ANSI.reset}`);
832
+ };
833
+ child.stdout.on('data', onOut);
834
+ child.stderr.on('data', onOut);
835
+
836
+ child.on('exit', () => {
837
+ signerState.proc = null;
838
+ });
839
+ }
840
+
683
841
  // ══════════════════════════════════════════════════
684
842
  // MAIL
685
843
  // ══════════════════════════════════════════════════
package/src/web/server.js CHANGED
@@ -108,6 +108,7 @@ export async function startWebShell(opts = {}) {
108
108
  send: (text) => ws.send(JSON.stringify({ type: 'output', data: text })),
109
109
  sendLine: (text) => ws.send(JSON.stringify({ type: 'output', data: text + '\r\n' })),
110
110
  sendMenu: (id, title, items) => ws.send(JSON.stringify({ type: 'menu', id, title, items })),
111
+ sendPrompt: (id, label, meta = {}) => ws.send(JSON.stringify({ type: 'prompt', id, label, ...meta })),
111
112
  });
112
113
  if (result?.output) {
113
114
  ws.send(JSON.stringify({ type: 'output', data: result.output }));
@@ -129,6 +130,7 @@ export async function startWebShell(opts = {}) {
129
130
  send: (text) => ws.send(JSON.stringify({ type: 'output', data: text })),
130
131
  sendLine: (text) => ws.send(JSON.stringify({ type: 'output', data: text + '\r\n' })),
131
132
  sendMenu: (id, title, items) => ws.send(JSON.stringify({ type: 'menu', id, title, items })),
133
+ sendPrompt: (id, label, meta = {}) => ws.send(JSON.stringify({ type: 'prompt', id, label, ...meta })),
132
134
  });
133
135
  if (result?.output) {
134
136
  ws.send(JSON.stringify({ type: 'output', data: result.output }));
@@ -154,6 +156,25 @@ export async function startWebShell(opts = {}) {
154
156
 
155
157
  if (cmd === 'help') {
156
158
  ws.send(JSON.stringify({ type: 'output', data: getHelp() }));
159
+ ws.send(JSON.stringify({
160
+ type: 'menu',
161
+ id: 'main_menu',
162
+ title: '◆ Help Menu — Select Command',
163
+ items: [
164
+ { value: 'ai', label: '🧠 AI Chat', desc: 'Natural language assistant' },
165
+ { value: 'wallet', label: '👛 Wallet', desc: 'Picker + balance + actions' },
166
+ { value: 'agent', label: '🔐 Agent Signer', desc: 'Start/stop/status controls' },
167
+ { value: 'keys', label: '🔑 Keys', desc: 'Add/update LLM providers' },
168
+ { value: 'config', label: '⚙ Config', desc: 'Chain + settings' },
169
+ { value: 'portfolio', label: '📊 Portfolio', desc: 'Multi-chain balances' },
170
+ { value: 'market', label: '📈 Market', desc: 'Price + liquidity intel' },
171
+ { value: 'mail', label: '📧 Mail', desc: 'AgentMail status/inbox' },
172
+ { value: 'oracle', label: '🎲 Oracle', desc: 'Randomness service' },
173
+ { value: 'casino', label: '🎰 Casino', desc: 'Service status' },
174
+ { value: 'facilitator', label: '💸 Facilitator', desc: 'x402 health' },
175
+ { value: 'back', label: '← Back', desc: '' },
176
+ ],
177
+ }));
157
178
  return;
158
179
  }
159
180
 
@@ -176,6 +197,7 @@ export async function startWebShell(opts = {}) {
176
197
  send: (text) => ws.send(JSON.stringify({ type: 'output', data: text })),
177
198
  sendLine: (text) => ws.send(JSON.stringify({ type: 'output', data: text + '\r\n' })),
178
199
  sendMenu: (id, title, items) => ws.send(JSON.stringify({ type: 'menu', id, title, items })),
200
+ sendPrompt: (id, label, meta = {}) => ws.send(JSON.stringify({ type: 'prompt', id, label, ...meta })),
179
201
  });
180
202
 
181
203
  if (result?.output) {
@@ -277,9 +299,12 @@ function getHelp() {
277
299
  ['portfolio', 'Multi-chain balances'],
278
300
  ['send', 'Send ETH or tokens'],
279
301
  ['receive', 'Show address to receive'],
280
- ['wallet list', 'List wallets'],
302
+ ['wallet', 'Interactive wallet menu'],
281
303
  ['wallet balance', 'Wallet balance'],
282
304
  ['history', 'Transaction history'],
305
+ ['agent', 'Agent signer controls'],
306
+ ['keys', 'Interactive LLM/API key setup'],
307
+ ['logs [n]', 'Recent AI chat memory logs'],
283
308
  ['', ''],
284
309
  ['', `${gold}SERVICES${reset}`],
285
310
  ['market <token>', 'Market intel & data'],
@@ -16,7 +16,7 @@ const A = {
16
16
 
17
17
  const COMMANDS = [
18
18
  'ai', 'price', 'watch', 'gas', 'portfolio', 'history', 'market',
19
- 'wallet', 'send', 'receive', 'mail', 'keys', 'oracle', 'casino',
19
+ 'wallet', 'send', 'receive', 'agent', 'mail', 'keys', 'oracle', 'casino',
20
20
  'facilitator', 'config', 'logs', 'help', 'clear', 'banner', 'exit',
21
21
  ];
22
22