@darksol/terminal 0.4.10 → 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 +4 -0
- package/package.json +1 -1
- package/src/wallet/agent-signer.js +10 -6
- package/src/web/commands.js +168 -10
- package/src/web/server.js +26 -1
- package/src/web/terminal.js +1 -1
package/README.md
CHANGED
|
@@ -70,6 +70,8 @@ darksol agent start main
|
|
|
70
70
|
|
|
71
71
|
- Arrow-key menus (`↑/↓` + `Enter`) for wallet/config flows
|
|
72
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
|
|
73
75
|
- AI connection check at startup (shows ready/not configured)
|
|
74
76
|
- Interactive key setup from web terminal:
|
|
75
77
|
- `keys` → select provider → paste key/host directly
|
|
@@ -80,8 +82,10 @@ darksol agent start main
|
|
|
80
82
|
Useful web-shell commands:
|
|
81
83
|
|
|
82
84
|
```bash
|
|
85
|
+
help # clickable command menu (arrow keys + Enter)
|
|
83
86
|
keys # provider status + interactive add/update
|
|
84
87
|
wallet # interactive wallet picker and actions
|
|
88
|
+
agent # signer start/stop/status controls
|
|
85
89
|
config # interactive config menu
|
|
86
90
|
logs 20 # show recent AI chat log lines
|
|
87
91
|
ai <prompt> # chat with trading assistant
|
package/package.json
CHANGED
|
@@ -402,12 +402,16 @@ export async function startAgentSigner(walletName, opts = {}) {
|
|
|
402
402
|
return;
|
|
403
403
|
}
|
|
404
404
|
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
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
|
|
package/src/web/commands.js
CHANGED
|
@@ -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.
|
|
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
|
|
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'],
|
package/src/web/terminal.js
CHANGED
|
@@ -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
|
|