@darksol/terminal 0.4.8 → 0.4.10

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,56 @@ 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
+ - AI connection check at startup (shows ready/not configured)
74
+ - Interactive key setup from web terminal:
75
+ - `keys` → select provider → paste key/host directly
76
+ - masked input for API keys, plain input for Ollama URL
77
+ - Local chat memory logs at `~/.darksol/chat-logs/YYYY-MM-DD.jsonl`
78
+ - Natural language fuzzy routing to AI for non-command prompts
79
+
80
+ Useful web-shell commands:
81
+
82
+ ```bash
83
+ keys # provider status + interactive add/update
84
+ wallet # interactive wallet picker and actions
85
+ config # interactive config menu
86
+ logs 20 # show recent AI chat log lines
87
+ ai <prompt> # chat with trading assistant
88
+ ```
89
+
61
90
  ## Modules
62
91
 
63
92
  | Module | Description | Pricing |
64
93
  |--------|-------------|---------|
65
- | `wallet` | Create, import, manage encrypted wallets | Free |
94
+ | `wallet` | Create/import/manage encrypted EVM wallets | Free |
95
+ | `send` | Send ETH or ERC-20 tokens | Gas only |
96
+ | `receive` | Show receive address + chain safety hints | Free |
66
97
  | `trade` | Swap (Uniswap V3), snipe (V2), token trading | Gas only |
67
98
  | `dca` | Dollar-cost averaging engine | Gas only |
68
- | `ai` | LLM-powered trading assistant & analysis | Provider dependent |
99
+ | `ai` | LLM-powered trading assistant & intent execution | Provider dependent |
69
100
  | `agent` | Secure agent signer (PK-isolated proxy) | Free |
70
- | `keys` | API key vault (LLMs, data, RPCs) | Free |
101
+ | `keys` | Encrypted API key vault (LLMs/data/RPCs) | Free |
71
102
  | `script` | Execution scripts & automated strategies | Free |
72
103
  | `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
104
  | `portfolio` | Multi-chain balance view (5 EVM chains) | Free |
105
+ | `history` | Transaction history via block explorers | Free |
77
106
  | `gas` | Gas prices & cost estimates | Free |
78
107
  | `price` | Quick token price check (DexScreener) | Free |
79
108
  | `watch` | Live price monitoring with alerts | Free |
80
- | `history` | Transaction history via block explorers | Free |
109
+ | `market` | Market intel, top movers, token analysis | x402 micropayments |
81
110
  | `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 |
111
+ | `oracle` | On-chain random number oracle | $0.05–$0.25 |
112
+ | `casino` | The Clawsino on-chain betting | $1 flat bets |
113
+ | `cards` | Crypto → prepaid Visa/MC cards | Service fees |
114
+ | `builders` | ERC-8021 builder directory + leaderboard | Free |
88
115
  | `facilitator` | x402 payment verification & settlement | Free |
116
+ | `serve` | Local interactive web terminal (xterm.js) | Free |
89
117
  | `config` | Terminal configuration | Free |
90
118
 
91
119
  ---
@@ -152,9 +180,12 @@ Natural language trading powered by multi-provider LLM support.
152
180
  # Interactive chat with live market data
153
181
  darksol ai chat
154
182
 
155
- # One-shot intent parsing
183
+ # One-shot intent parsing (+ optional execution prompt)
156
184
  darksol ai ask "buy 0.5 ETH worth of AERO on Base"
157
185
 
186
+ # Parse + execute directly
187
+ darksol ai execute "send 10 USDC to 0x..."
188
+
158
189
  # DCA strategy recommendation
159
190
  darksol ai strategy VIRTUAL --budget 500 --timeframe "30 days"
160
191
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@darksol/terminal",
3
- "version": "0.4.8",
3
+ "version": "0.4.10",
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": {
@@ -135,6 +135,33 @@ export async function handleMenuSelect(id, value, item, ws) {
135
135
  ws.sendLine('');
136
136
  return {};
137
137
 
138
+ case 'keys_provider':
139
+ if (value === 'back') {
140
+ ws.sendLine('');
141
+ return {};
142
+ }
143
+ // Ask for the key via a prompt
144
+ const svc = SERVICES[value];
145
+ if (!svc) return {};
146
+ ws.sendLine('');
147
+ ws.sendLine(` ${ANSI.gold}◆ ${svc.name}${ANSI.reset}`);
148
+ ws.sendLine(` ${ANSI.dim}Docs: ${svc.docsUrl}${ANSI.reset}`);
149
+ if (value === 'ollama') {
150
+ ws.sendLine(` ${ANSI.dim}Enter your Ollama host URL (e.g. http://localhost:11434)${ANSI.reset}`);
151
+ } else {
152
+ ws.sendLine(` ${ANSI.dim}Paste your API key below:${ANSI.reset}`);
153
+ }
154
+ ws.sendLine('');
155
+ // Send a prompt request to the client
156
+ ws.send(JSON.stringify({
157
+ type: 'prompt',
158
+ id: 'keys_input',
159
+ label: `${svc.name} key:`,
160
+ service: value,
161
+ mask: value !== 'ollama', // mask API keys, not URLs
162
+ }));
163
+ return {};
164
+
138
165
  case 'config_action':
139
166
  if (value === 'chain') {
140
167
  const chains = ['base', 'ethereum', 'arbitrum', 'optimism', 'polygon'];
@@ -159,6 +186,52 @@ export async function handleMenuSelect(id, value, item, ws) {
159
186
  return {};
160
187
  }
161
188
 
189
+ /**
190
+ * Handle text prompt responses from the client
191
+ */
192
+ export async function handlePromptResponse(id, value, meta, ws) {
193
+ if (id === 'keys_input') {
194
+ const service = meta.service;
195
+ const svc = SERVICES[service];
196
+ if (!svc || !value) {
197
+ ws.sendLine(` ${ANSI.red}✗ Cancelled${ANSI.reset}`);
198
+ ws.sendLine('');
199
+ return {};
200
+ }
201
+
202
+ // Validate
203
+ if (svc.validate && !svc.validate(value)) {
204
+ ws.sendLine(` ${ANSI.red}✗ Invalid format for ${svc.name}${ANSI.reset}`);
205
+ if (service === 'openai') ws.sendLine(` ${ANSI.dim}Key should start with sk-${ANSI.reset}`);
206
+ if (service === 'anthropic') ws.sendLine(` ${ANSI.dim}Key should start with sk-ant-${ANSI.reset}`);
207
+ if (service === 'openrouter') ws.sendLine(` ${ANSI.dim}Key should start with sk-or-${ANSI.reset}`);
208
+ if (service === 'ollama') ws.sendLine(` ${ANSI.dim}Should be a URL like http://localhost:11434${ANSI.reset}`);
209
+ ws.sendLine('');
210
+ return {};
211
+ }
212
+
213
+ // Store it
214
+ try {
215
+ addKeyDirect(service, value);
216
+ ws.sendLine(` ${ANSI.green}✓ ${svc.name} key stored securely${ANSI.reset}`);
217
+ ws.sendLine(` ${ANSI.dim}Encrypted at ~/.darksol/keys/vault.json${ANSI.reset}`);
218
+ 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
+ ws.sendLine(` ${ANSI.green}● AI ready!${ANSI.reset} ${ANSI.dim}Type ${ANSI.gold}ai <question>${ANSI.dim} to start chatting.${ANSI.reset}`);
224
+ ws.sendLine('');
225
+ } catch (err) {
226
+ ws.sendLine(` ${ANSI.red}✗ Failed: ${err.message}${ANSI.reset}`);
227
+ ws.sendLine('');
228
+ }
229
+ return {};
230
+ }
231
+
232
+ return {};
233
+ }
234
+
162
235
  /**
163
236
  * AI status check — shown on connection
164
237
  */
@@ -180,14 +253,12 @@ export function getAIStatus() {
180
253
  return [
181
254
  ` ${red}○ AI not configured${reset} ${dim}— no LLM provider connected${reset}`,
182
255
  '',
183
- ` ${gold}Quick setup pick one:${reset}`,
256
+ ` ${dim}Type ${gold}keys${dim} to set up an LLM provider, or paste directly:${reset}`,
184
257
  ` ${green}keys add openai sk-...${reset} ${dim}OpenAI (GPT-4o)${reset}`,
185
258
  ` ${green}keys add anthropic sk-ant-...${reset} ${dim}Anthropic (Claude)${reset}`,
186
259
  ` ${green}keys add openrouter sk-or-...${reset} ${dim}OpenRouter (any model)${reset}`,
187
260
  ` ${green}keys add ollama http://...${reset} ${dim}Ollama (free, local)${reset}`,
188
261
  '',
189
- ` ${dim}Or run the full setup wizard: ${gold}darksol setup${reset}`,
190
- '',
191
262
  ].join('\r\n');
192
263
  }
193
264
 
@@ -1014,6 +1085,19 @@ async function cmdKeys(args, ws) {
1014
1085
  ws.sendLine(` ${ANSI.green}keys add ollama http://...${ANSI.reset} ${ANSI.dim}Add Ollama host${ANSI.reset}`);
1015
1086
  ws.sendLine('');
1016
1087
 
1088
+ // Interactive menu to add keys
1089
+ const llmItems = llmProviders.map(p => {
1090
+ const svc = SERVICES[p];
1091
+ const has = hasKey(p);
1092
+ return {
1093
+ value: p,
1094
+ label: `${has ? '✓' : '+'} ${svc.name}`,
1095
+ desc: has ? 'Connected — replace key' : `Add key (${svc.docsUrl})`,
1096
+ };
1097
+ });
1098
+ llmItems.push({ value: 'back', label: '← Back', desc: '' });
1099
+ ws.sendMenu('keys_provider', '◆ Add / Update API Key', llmItems);
1100
+
1017
1101
  const dataProviders = ['coingecko', 'dexscreener', 'alchemy', 'agentmail'];
1018
1102
  ws.sendLine(` ${ANSI.gold}Other Services:${ANSI.reset}`);
1019
1103
  for (const p of dataProviders) {
package/src/web/server.js CHANGED
@@ -100,6 +100,27 @@ export async function startWebShell(opts = {}) {
100
100
  try {
101
101
  const msg = JSON.parse(raw.toString());
102
102
 
103
+ if (msg.type === 'prompt_response') {
104
+ // User typed a response to a prompt (e.g. API key input)
105
+ try {
106
+ const { handlePromptResponse } = await import('./commands.js');
107
+ const result = await handlePromptResponse(msg.id, msg.value, msg.meta || {}, {
108
+ send: (text) => ws.send(JSON.stringify({ type: 'output', data: text })),
109
+ sendLine: (text) => ws.send(JSON.stringify({ type: 'output', data: text + '\r\n' })),
110
+ sendMenu: (id, title, items) => ws.send(JSON.stringify({ type: 'menu', id, title, items })),
111
+ });
112
+ if (result?.output) {
113
+ ws.send(JSON.stringify({ type: 'output', data: result.output }));
114
+ }
115
+ } catch (err) {
116
+ ws.send(JSON.stringify({
117
+ type: 'output',
118
+ data: `\r\n \x1b[31m✗ Error: ${err.message}\x1b[0m\r\n`,
119
+ }));
120
+ }
121
+ return;
122
+ }
123
+
103
124
  if (msg.type === 'menu_select') {
104
125
  // User selected something from an interactive menu
105
126
  try {
@@ -34,6 +34,13 @@ let menuIndex = 0;
34
34
  let menuId = '';
35
35
  let menuTitle = '';
36
36
 
37
+ // ── PROMPT STATE (text input) ─────────────────
38
+ let promptActive = false;
39
+ let promptId = '';
40
+ let promptMeta = {};
41
+ let promptInput = '';
42
+ let promptMask = false;
43
+
37
44
  async function init() {
38
45
  term = new Terminal({
39
46
  theme: {
@@ -85,6 +92,48 @@ async function init() {
85
92
  const code = domEvent.keyCode;
86
93
  const ctrl = domEvent.ctrlKey;
87
94
 
95
+ // ── PROMPT MODE (text input) ──
96
+ if (promptActive) {
97
+ if (code === 13) { // Enter — submit
98
+ promptActive = false;
99
+ term.write('\r\n');
100
+ if (promptInput) {
101
+ ws.send(JSON.stringify({
102
+ type: 'prompt_response',
103
+ id: promptId,
104
+ value: promptInput,
105
+ meta: promptMeta,
106
+ }));
107
+ } else {
108
+ term.write(` ${A.dim}Cancelled${A.r}\r\n\r\n`);
109
+ writePrompt();
110
+ }
111
+ promptInput = '';
112
+ return;
113
+ }
114
+ if (code === 27 || (ctrl && code === 67)) { // Esc/Ctrl+C — cancel
115
+ promptActive = false;
116
+ promptInput = '';
117
+ term.write('\r\n');
118
+ term.write(` ${A.dim}Cancelled${A.r}\r\n\r\n`);
119
+ writePrompt();
120
+ return;
121
+ }
122
+ if (code === 8) { // Backspace
123
+ if (promptInput.length > 0) {
124
+ promptInput = promptInput.slice(0, -1);
125
+ term.write('\b \b');
126
+ }
127
+ return;
128
+ }
129
+ // Printable chars
130
+ if (key.length === 1 && !ctrl) {
131
+ promptInput += key;
132
+ term.write(promptMask ? '●' : key);
133
+ }
134
+ return;
135
+ }
136
+
88
137
  // ── MENU MODE ──
89
138
  if (menuActive) {
90
139
  if (code === 38) { // Up
@@ -188,8 +237,13 @@ async function init() {
188
237
  if (menuActive) return;
189
238
  if (data.length > 1 && !data.startsWith('\x1b')) {
190
239
  const clean = data.replace(/[\r\n]/g, '');
191
- currentLine += clean;
192
- term.write(clean);
240
+ if (promptActive) {
241
+ promptInput += clean;
242
+ term.write(promptMask ? '●'.repeat(clean.length) : clean);
243
+ } else {
244
+ currentLine += clean;
245
+ term.write(clean);
246
+ }
193
247
  }
194
248
  });
195
249
 
@@ -277,8 +331,15 @@ function connectWS() {
277
331
  term.clear();
278
332
  writePrompt();
279
333
  } else if (msg.type === 'menu') {
280
- // Server is requesting user to pick from a menu
281
334
  showMenu(msg.id, msg.title, msg.items);
335
+ } else if (msg.type === 'prompt') {
336
+ // Server wants text input (e.g. API key)
337
+ promptActive = true;
338
+ promptId = msg.id;
339
+ promptMeta = { service: msg.service, ...msg };
340
+ promptInput = '';
341
+ promptMask = msg.mask || false;
342
+ term.write(` ${A.gold}${msg.label || 'Input:'}${A.r} `);
282
343
  }
283
344
  } catch {
284
345
  term.write(event.data);