@darksol/terminal 0.8.1 → 0.9.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 +59 -3
- package/package.json +1 -1
- package/src/agent/index.js +134 -0
- package/src/agent/loop.js +190 -0
- package/src/agent/tools.js +311 -0
- package/src/cli.js +194 -3
- package/src/config/keys.js +9 -1
- package/src/config/store.js +29 -0
- package/src/llm/engine.js +65 -91
- package/src/memory/index.js +275 -0
- package/src/setup/wizard.js +29 -11
- package/src/soul/index.js +139 -0
- package/src/web/commands.js +97 -3
- package/src/web/server.js +15 -0
package/src/setup/wizard.js
CHANGED
|
@@ -4,6 +4,7 @@ import { showSection, showDivider } from '../ui/banner.js';
|
|
|
4
4
|
import { success, error, warn, info, kvDisplay } from '../ui/components.js';
|
|
5
5
|
import { getConfig, setConfig } from '../config/store.js';
|
|
6
6
|
import { addKeyDirect, hasKey, hasAnyLLM, SERVICES } from '../config/keys.js';
|
|
7
|
+
import { hasSoul, runSoulSetup } from '../soul/index.js';
|
|
7
8
|
import { createServer } from 'http';
|
|
8
9
|
import open from 'open';
|
|
9
10
|
import crypto from 'crypto';
|
|
@@ -18,7 +19,7 @@ import crypto from 'crypto';
|
|
|
18
19
|
export function isFirstRun() {
|
|
19
20
|
const llmReady = hasAnyLLM();
|
|
20
21
|
const setupDone = getConfig('setupComplete');
|
|
21
|
-
return !llmReady && !setupDone;
|
|
22
|
+
return (!llmReady && !setupDone) || !hasSoul();
|
|
22
23
|
}
|
|
23
24
|
|
|
24
25
|
/**
|
|
@@ -42,17 +43,21 @@ export async function runSetupWizard(opts = {}) {
|
|
|
42
43
|
|
|
43
44
|
showDivider();
|
|
44
45
|
|
|
45
|
-
// Step 1:
|
|
46
|
+
// Step 1: Soul / identity
|
|
47
|
+
await runSoulSetup({ showBanner: false, reset: force });
|
|
48
|
+
|
|
49
|
+
// Step 2: Choose LLM provider
|
|
46
50
|
const { provider } = await inquirer.prompt([{
|
|
47
51
|
type: 'list',
|
|
48
52
|
name: 'provider',
|
|
49
53
|
message: theme.gold('Choose your AI provider:'),
|
|
50
54
|
choices: [
|
|
51
|
-
{ name: '
|
|
52
|
-
{ name: '
|
|
53
|
-
{ name: '
|
|
54
|
-
{ name: '
|
|
55
|
-
{ name: '
|
|
55
|
+
{ name: 'OpenAI (GPT-4o, GPT-5) - API key or OAuth', value: 'openai' },
|
|
56
|
+
{ name: 'Anthropic (Claude Opus, Sonnet) - API key or OAuth', value: 'anthropic' },
|
|
57
|
+
{ name: 'OpenRouter (any model, one key) - API key', value: 'openrouter' },
|
|
58
|
+
{ name: 'MiniMax (MiniMax-M2.5) - API key', value: 'minimax' },
|
|
59
|
+
{ name: 'Ollama (local models, free, private) - no key needed', value: 'ollama' },
|
|
60
|
+
{ name: 'Skip for now', value: 'skip' },
|
|
56
61
|
],
|
|
57
62
|
}]);
|
|
58
63
|
|
|
@@ -69,7 +74,7 @@ export async function runSetupWizard(opts = {}) {
|
|
|
69
74
|
await setupCloudProvider(provider);
|
|
70
75
|
}
|
|
71
76
|
|
|
72
|
-
// Step
|
|
77
|
+
// Step 3: Chain selection
|
|
73
78
|
console.log('');
|
|
74
79
|
const { chain } = await inquirer.prompt([{
|
|
75
80
|
type: 'list',
|
|
@@ -87,7 +92,7 @@ export async function runSetupWizard(opts = {}) {
|
|
|
87
92
|
setConfig('chain', chain);
|
|
88
93
|
success(`Chain set to ${chain}`);
|
|
89
94
|
|
|
90
|
-
// Step
|
|
95
|
+
// Step 4: Wallet
|
|
91
96
|
console.log('');
|
|
92
97
|
const { wantWallet } = await inquirer.prompt([{
|
|
93
98
|
type: 'confirm',
|
|
@@ -108,7 +113,7 @@ export async function runSetupWizard(opts = {}) {
|
|
|
108
113
|
}
|
|
109
114
|
|
|
110
115
|
/**
|
|
111
|
-
* Setup a cloud provider (OpenAI, Anthropic, OpenRouter)
|
|
116
|
+
* Setup a cloud provider (OpenAI, Anthropic, OpenRouter, MiniMax)
|
|
112
117
|
*/
|
|
113
118
|
async function setupCloudProvider(provider) {
|
|
114
119
|
const supportsOAuth = ['openai', 'anthropic'].includes(provider);
|
|
@@ -116,6 +121,7 @@ async function setupCloudProvider(provider) {
|
|
|
116
121
|
openai: 'OpenAI',
|
|
117
122
|
anthropic: 'Anthropic',
|
|
118
123
|
openrouter: 'OpenRouter',
|
|
124
|
+
minimax: 'MiniMax',
|
|
119
125
|
}[provider];
|
|
120
126
|
|
|
121
127
|
if (supportsOAuth) {
|
|
@@ -152,6 +158,7 @@ async function setupAPIKey(provider) {
|
|
|
152
158
|
openai: 'OpenAI',
|
|
153
159
|
anthropic: 'Anthropic',
|
|
154
160
|
openrouter: 'OpenRouter',
|
|
161
|
+
minimax: 'MiniMax',
|
|
155
162
|
}[provider];
|
|
156
163
|
|
|
157
164
|
const { key } = await inquirer.prompt([{
|
|
@@ -169,6 +176,7 @@ async function setupAPIKey(provider) {
|
|
|
169
176
|
success(`${providerName} key saved (encrypted)`);
|
|
170
177
|
|
|
171
178
|
// Set as default provider
|
|
179
|
+
setConfig('llm.provider', provider);
|
|
172
180
|
setConfig('llmProvider', provider);
|
|
173
181
|
info(`Default AI provider set to ${provider}`);
|
|
174
182
|
}
|
|
@@ -189,6 +197,7 @@ async function setupOllama() {
|
|
|
189
197
|
default: 'http://localhost:11434',
|
|
190
198
|
}]);
|
|
191
199
|
|
|
200
|
+
setConfig('llm.ollamaHost', host);
|
|
192
201
|
setConfig('ollamaHost', host);
|
|
193
202
|
|
|
194
203
|
const { model } = await inquirer.prompt([{
|
|
@@ -198,7 +207,9 @@ async function setupOllama() {
|
|
|
198
207
|
default: 'llama3',
|
|
199
208
|
}]);
|
|
200
209
|
|
|
210
|
+
setConfig('llm.model', model);
|
|
201
211
|
setConfig('ollamaModel', model);
|
|
212
|
+
setConfig('llm.provider', 'ollama');
|
|
202
213
|
setConfig('llmProvider', 'ollama');
|
|
203
214
|
|
|
204
215
|
success(`Ollama configured: ${host} / ${model}`);
|
|
@@ -227,8 +238,14 @@ function showKeyInstructions(provider) {
|
|
|
227
238
|
console.log(theme.dim(' 3. Copy the key (starts with sk-ant-)'));
|
|
228
239
|
console.log(theme.dim(' 4. Paste it below'));
|
|
229
240
|
console.log('');
|
|
230
|
-
console.log(theme.dim('
|
|
241
|
+
console.log(theme.dim(' If you have a Claude Pro/Team subscription,'));
|
|
231
242
|
console.log(theme.dim(' you can use OAuth instead.'));
|
|
243
|
+
} else if (provider === 'minimax') {
|
|
244
|
+
showSection('GET A MINIMAX API KEY');
|
|
245
|
+
console.log(theme.dim(' 1. Go to https://platform.minimax.io/docs/guides/models-intro'));
|
|
246
|
+
console.log(theme.dim(' 2. Open your MiniMax developer console / API key page'));
|
|
247
|
+
console.log(theme.dim(' 3. Create or copy an API key'));
|
|
248
|
+
console.log(theme.dim(' 4. Paste it below'));
|
|
232
249
|
}
|
|
233
250
|
|
|
234
251
|
console.log('');
|
|
@@ -428,6 +445,7 @@ async function executeOAuthFlow(provider, clientId, clientSecret) {
|
|
|
428
445
|
if (tokenData.refresh_token) {
|
|
429
446
|
addKeyDirect(`${provider}_refresh`, tokenData.refresh_token);
|
|
430
447
|
}
|
|
448
|
+
setConfig('llm.provider', provider);
|
|
431
449
|
setConfig('llmProvider', provider);
|
|
432
450
|
|
|
433
451
|
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import inquirer from 'inquirer';
|
|
2
|
+
import { getConfig, setConfig, deleteConfig } from '../config/store.js';
|
|
3
|
+
import { theme } from '../ui/theme.js';
|
|
4
|
+
import { showMiniBanner, showSection } from '../ui/banner.js';
|
|
5
|
+
import { kvDisplay, info, success, warn } from '../ui/components.js';
|
|
6
|
+
|
|
7
|
+
const TONE_CHOICES = ['professional', 'casual', 'hacker', 'friendly', 'sarcastic', 'custom'];
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Return the current persisted soul configuration.
|
|
11
|
+
* @returns {{userName: string, agentName: string, tone: string, createdAt: string}}
|
|
12
|
+
*/
|
|
13
|
+
export function getSoul() {
|
|
14
|
+
const soul = getConfig('soul') || {};
|
|
15
|
+
return {
|
|
16
|
+
userName: soul.userName || '',
|
|
17
|
+
agentName: soul.agentName || 'Darksol',
|
|
18
|
+
tone: soul.tone || '',
|
|
19
|
+
createdAt: soul.createdAt || '',
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Whether a usable soul profile has been created.
|
|
25
|
+
* @returns {boolean}
|
|
26
|
+
*/
|
|
27
|
+
export function hasSoul() {
|
|
28
|
+
const soul = getSoul();
|
|
29
|
+
return Boolean(soul.userName && soul.agentName && soul.tone);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Generate a soul-derived system prompt for LLM calls.
|
|
34
|
+
* @returns {string}
|
|
35
|
+
*/
|
|
36
|
+
export function formatSystemPrompt() {
|
|
37
|
+
if (!hasSoul()) return '';
|
|
38
|
+
|
|
39
|
+
const soul = getSoul();
|
|
40
|
+
return [
|
|
41
|
+
`You are ${soul.agentName}, the user's persistent DARKSOL Terminal agent.`,
|
|
42
|
+
`Address the user as ${soul.userName}.`,
|
|
43
|
+
`Maintain a ${soul.tone} tone unless the user explicitly asks for a different style.`,
|
|
44
|
+
'Stay concise, terminal-native, and practical.',
|
|
45
|
+
'Preserve the deep black DARKSOL aesthetic: sharp, calm, and low-noise.',
|
|
46
|
+
].join('\n');
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Pretty-print the current soul configuration.
|
|
51
|
+
* @returns {void}
|
|
52
|
+
*/
|
|
53
|
+
export function displaySoul() {
|
|
54
|
+
const soul = getSoul();
|
|
55
|
+
|
|
56
|
+
showMiniBanner();
|
|
57
|
+
showSection('SOUL CONFIG');
|
|
58
|
+
kvDisplay([
|
|
59
|
+
['User', soul.userName || theme.dim('(not set)')],
|
|
60
|
+
['Agent', soul.agentName],
|
|
61
|
+
['Tone', soul.tone || theme.dim('(not set)')],
|
|
62
|
+
['Created', soul.createdAt || theme.dim('(not set)')],
|
|
63
|
+
]);
|
|
64
|
+
console.log('');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Interactive soul setup flow.
|
|
69
|
+
* @param {{showBanner?: boolean, reset?: boolean}} opts
|
|
70
|
+
* @returns {Promise<{userName: string, agentName: string, tone: string, createdAt: string}>}
|
|
71
|
+
*/
|
|
72
|
+
export async function runSoulSetup(opts = {}) {
|
|
73
|
+
const currentSoul = getSoul();
|
|
74
|
+
|
|
75
|
+
if (opts.showBanner !== false) {
|
|
76
|
+
showMiniBanner();
|
|
77
|
+
showSection(hasSoul() && !opts.reset ? 'UPDATE SOUL' : 'SOUL SETUP');
|
|
78
|
+
console.log(theme.dim(' Shape how DARKSOL knows you and how your agent should speak.'));
|
|
79
|
+
console.log('');
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const answers = await inquirer.prompt([
|
|
83
|
+
{
|
|
84
|
+
type: 'input',
|
|
85
|
+
name: 'userName',
|
|
86
|
+
message: 'What should I call you?',
|
|
87
|
+
default: currentSoul.userName || undefined,
|
|
88
|
+
validate: (value) => value.trim().length > 0 || 'Name is required',
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
type: 'input',
|
|
92
|
+
name: 'agentName',
|
|
93
|
+
message: 'Name your agent:',
|
|
94
|
+
default: currentSoul.agentName || 'Darksol',
|
|
95
|
+
validate: (value) => value.trim().length > 0 || 'Agent name is required',
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
type: 'list',
|
|
99
|
+
name: 'tonePreset',
|
|
100
|
+
message: 'Agent tone:',
|
|
101
|
+
choices: TONE_CHOICES.map((tone) => ({
|
|
102
|
+
name: tone === 'custom' ? 'custom' : tone,
|
|
103
|
+
value: tone,
|
|
104
|
+
})),
|
|
105
|
+
default: TONE_CHOICES.includes(currentSoul.tone) ? currentSoul.tone : 'professional',
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
type: 'input',
|
|
109
|
+
name: 'customTone',
|
|
110
|
+
message: 'Describe the tone:',
|
|
111
|
+
when: (answers) => answers.tonePreset === 'custom',
|
|
112
|
+
default: TONE_CHOICES.includes(currentSoul.tone) ? undefined : currentSoul.tone || undefined,
|
|
113
|
+
validate: (value) => value.trim().length > 0 || 'Tone is required',
|
|
114
|
+
},
|
|
115
|
+
]);
|
|
116
|
+
|
|
117
|
+
const soul = {
|
|
118
|
+
userName: answers.userName.trim(),
|
|
119
|
+
agentName: answers.agentName.trim() || 'Darksol',
|
|
120
|
+
tone: (answers.tonePreset === 'custom' ? answers.customTone : answers.tonePreset).trim(),
|
|
121
|
+
createdAt: currentSoul.createdAt && !opts.reset ? currentSoul.createdAt : new Date().toISOString(),
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
setConfig('soul', soul);
|
|
125
|
+
success(`Soul bound: ${soul.agentName} → ${soul.userName}`);
|
|
126
|
+
info(`Tone locked to ${soul.tone}`);
|
|
127
|
+
console.log('');
|
|
128
|
+
|
|
129
|
+
return soul;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Reset persisted soul configuration.
|
|
134
|
+
* @returns {void}
|
|
135
|
+
*/
|
|
136
|
+
export function resetSoul() {
|
|
137
|
+
deleteConfig('soul');
|
|
138
|
+
warn('Soul profile cleared.');
|
|
139
|
+
}
|
package/src/web/commands.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import fetch from 'node-fetch';
|
|
2
2
|
import { getConfig, setConfig } from '../config/store.js';
|
|
3
3
|
import { hasKey, hasAnyLLM, getKeyAuto, addKeyDirect, SERVICES } from '../config/keys.js';
|
|
4
|
+
import { getRecentMemories } from '../memory/index.js';
|
|
5
|
+
import { getSoul, hasSoul } from '../soul/index.js';
|
|
4
6
|
import { ethers } from 'ethers';
|
|
5
7
|
import { existsSync, mkdirSync, appendFileSync, readFileSync } from 'fs';
|
|
6
8
|
import { join, dirname } from 'path';
|
|
@@ -450,6 +452,7 @@ export async function handlePromptResponse(id, value, meta, ws) {
|
|
|
450
452
|
if (service === 'openai') ws.sendLine(` ${ANSI.dim}Key should start with sk-${ANSI.reset}`);
|
|
451
453
|
if (service === 'anthropic') ws.sendLine(` ${ANSI.dim}Key should start with sk-ant-${ANSI.reset}`);
|
|
452
454
|
if (service === 'openrouter') ws.sendLine(` ${ANSI.dim}Key should start with sk-or-${ANSI.reset}`);
|
|
455
|
+
if (service === 'minimax') ws.sendLine(` ${ANSI.dim}Get a key: ${svc.docsUrl}${ANSI.reset}`);
|
|
453
456
|
if (service === 'ollama') ws.sendLine(` ${ANSI.dim}Should be a URL like http://localhost:11434${ANSI.reset}`);
|
|
454
457
|
ws.sendLine('');
|
|
455
458
|
return {};
|
|
@@ -780,8 +783,9 @@ export function getAIStatus() {
|
|
|
780
783
|
const dim = '\x1b[38;2;102;102;102m';
|
|
781
784
|
const reset = '\x1b[0m';
|
|
782
785
|
|
|
783
|
-
const providers = ['openai', 'anthropic', 'openrouter', 'ollama', 'bankr'];
|
|
786
|
+
const providers = ['openai', 'anthropic', 'openrouter', 'minimax', 'ollama', 'bankr'];
|
|
784
787
|
const connected = providers.filter(p => hasKey(p));
|
|
788
|
+
const soul = hasSoul() ? getSoul() : null;
|
|
785
789
|
|
|
786
790
|
if (connected.length > 0) {
|
|
787
791
|
const names = connected.map(p => SERVICES[p]?.name || p).join(', ');
|
|
@@ -795,6 +799,7 @@ export function getAIStatus() {
|
|
|
795
799
|
` ${green}keys add openai sk-...${reset} ${dim}OpenAI (GPT-4o)${reset}`,
|
|
796
800
|
` ${green}keys add anthropic sk-ant-...${reset} ${dim}Anthropic (Claude)${reset}`,
|
|
797
801
|
` ${green}keys add openrouter sk-or-...${reset} ${dim}OpenRouter (any model)${reset}`,
|
|
802
|
+
` ${green}keys add minimax <key>${reset} ${dim}MiniMax (MiniMax-M2.5)${reset}`,
|
|
798
803
|
` ${green}keys add bankr bk_...${reset} ${dim}Bankr LLM Gateway (crypto credits)${reset}`,
|
|
799
804
|
` ${green}keys add ollama http://...${reset} ${dim}Ollama (free, local)${reset}`,
|
|
800
805
|
'',
|
|
@@ -847,6 +852,8 @@ export async function handleCommand(cmd, ws) {
|
|
|
847
852
|
case 'agent':
|
|
848
853
|
case 'signer':
|
|
849
854
|
return await cmdAgent(args, ws);
|
|
855
|
+
case 'task':
|
|
856
|
+
return await cmdAgent(['task', ...args], ws);
|
|
850
857
|
case 'ai':
|
|
851
858
|
case 'ask':
|
|
852
859
|
case 'chat':
|
|
@@ -1336,7 +1343,84 @@ async function showWalletDetail(name, ws) {
|
|
|
1336
1343
|
async function cmdAgent(args, ws) {
|
|
1337
1344
|
const sub = (args[0] || 'menu').toLowerCase();
|
|
1338
1345
|
|
|
1346
|
+
if (sub === 'task') {
|
|
1347
|
+
const goal = args.slice(1).join(' ').trim();
|
|
1348
|
+
if (!goal) {
|
|
1349
|
+
return {
|
|
1350
|
+
output: `\r\n ${ANSI.dim}Usage: agent task <goal> [--max-steps N] [--allow-actions]${ANSI.reset}\r\n ${ANSI.dim}Shortcut: task <goal>${ANSI.reset}\r\n\r\n`,
|
|
1351
|
+
};
|
|
1352
|
+
}
|
|
1353
|
+
|
|
1354
|
+
const allowActions = args.includes('--allow-actions');
|
|
1355
|
+
const maxIndex = args.findIndex((arg) => arg === '--max-steps');
|
|
1356
|
+
const maxSteps = maxIndex >= 0 ? parseInt(args[maxIndex + 1], 10) || 10 : 10;
|
|
1357
|
+
const filteredGoal = args
|
|
1358
|
+
.slice(1)
|
|
1359
|
+
.filter((arg, index, arr) => arg !== '--allow-actions' && !(arg === '--max-steps' || arr[index - 1] === '--max-steps'))
|
|
1360
|
+
.join(' ')
|
|
1361
|
+
.trim();
|
|
1362
|
+
|
|
1363
|
+
const { runAgentTask } = await import('../agent/index.js');
|
|
1364
|
+
ws.sendLine(`${ANSI.gold} ◆ AGENT TASK${ANSI.reset}`);
|
|
1365
|
+
ws.sendLine(`${ANSI.dim} ${'─'.repeat(50)}${ANSI.reset}`);
|
|
1366
|
+
ws.sendLine(` ${ANSI.white}Goal:${ANSI.reset} ${filteredGoal}`);
|
|
1367
|
+
ws.sendLine(` ${ANSI.darkGold}Mode:${ANSI.reset} ${allowActions ? 'actions enabled' : 'safe mode'}`);
|
|
1368
|
+
ws.sendLine('');
|
|
1369
|
+
|
|
1370
|
+
const result = await runAgentTask(filteredGoal, {
|
|
1371
|
+
maxSteps,
|
|
1372
|
+
allowActions,
|
|
1373
|
+
onProgress: (event) => {
|
|
1374
|
+
if (event.type === 'thought') {
|
|
1375
|
+
ws.sendLine(` ${ANSI.darkGold}[step ${event.step}]${ANSI.reset} ${ANSI.white}${event.action}${ANSI.reset}`);
|
|
1376
|
+
if (event.thought) {
|
|
1377
|
+
ws.sendLine(` ${ANSI.dim}${event.thought}${ANSI.reset}`);
|
|
1378
|
+
}
|
|
1379
|
+
}
|
|
1380
|
+
if (event.type === 'observation') {
|
|
1381
|
+
const summary = event.observation?.summary || event.observation?.error;
|
|
1382
|
+
if (summary) ws.sendLine(` ${ANSI.dim}${summary}${ANSI.reset}`);
|
|
1383
|
+
ws.sendLine('');
|
|
1384
|
+
}
|
|
1385
|
+
},
|
|
1386
|
+
});
|
|
1387
|
+
|
|
1388
|
+
ws.sendLine(` ${ANSI.green}Final:${ANSI.reset} ${result.final}`);
|
|
1389
|
+
ws.sendLine(` ${ANSI.dim}Status ${result.status} • ${result.stepsTaken}/${result.maxSteps} steps • ${result.stopReason}${ANSI.reset}`);
|
|
1390
|
+
ws.sendLine('');
|
|
1391
|
+
return {};
|
|
1392
|
+
}
|
|
1393
|
+
|
|
1394
|
+
if (sub === 'plan') {
|
|
1395
|
+
const goal = args.slice(1).join(' ').trim();
|
|
1396
|
+
if (!goal) {
|
|
1397
|
+
return { output: ` ${ANSI.dim}Usage: agent plan <goal>${ANSI.reset}\r\n` };
|
|
1398
|
+
}
|
|
1399
|
+
const { planAgentGoal } = await import('../agent/index.js');
|
|
1400
|
+
const plan = await planAgentGoal(goal);
|
|
1401
|
+
ws.sendLine(`${ANSI.gold} ◆ AGENT PLAN${ANSI.reset}`);
|
|
1402
|
+
ws.sendLine(`${ANSI.dim} ${'─'.repeat(50)}${ANSI.reset}`);
|
|
1403
|
+
ws.sendLine(` ${ANSI.white}${plan.summary}${ANSI.reset}`);
|
|
1404
|
+
ws.sendLine('');
|
|
1405
|
+
plan.steps.forEach((step, index) => ws.sendLine(` ${ANSI.darkGold}${index + 1}.${ANSI.reset} ${step}`));
|
|
1406
|
+
ws.sendLine('');
|
|
1407
|
+
return {};
|
|
1408
|
+
}
|
|
1409
|
+
|
|
1339
1410
|
if (sub === 'status') {
|
|
1411
|
+
const { getAgentStatus } = await import('../agent/index.js');
|
|
1412
|
+
const status = getAgentStatus();
|
|
1413
|
+
if (status?.goal || status?.summary) {
|
|
1414
|
+
ws.sendLine(`${ANSI.gold} ◆ AGENT STATUS${ANSI.reset}`);
|
|
1415
|
+
ws.sendLine(`${ANSI.dim} ${'─'.repeat(50)}${ANSI.reset}`);
|
|
1416
|
+
ws.sendLine(` ${ANSI.darkGold}Status${ANSI.reset} ${ANSI.white}${status.status || '-'}${ANSI.reset}`);
|
|
1417
|
+
ws.sendLine(` ${ANSI.darkGold}Goal${ANSI.reset} ${ANSI.white}${status.goal || '-'}${ANSI.reset}`);
|
|
1418
|
+
ws.sendLine(` ${ANSI.darkGold}Summary${ANSI.reset} ${ANSI.white}${status.summary || '-'}${ANSI.reset}`);
|
|
1419
|
+
ws.sendLine(` ${ANSI.darkGold}Steps${ANSI.reset} ${ANSI.white}${status.stepsTaken || 0}${status.maxSteps ? `/${status.maxSteps}` : ''}${ANSI.reset}`);
|
|
1420
|
+
ws.sendLine(` ${ANSI.darkGold}Actions${ANSI.reset} ${ANSI.white}${status.allowActions ? 'enabled' : 'safe mode'}${ANSI.reset}`);
|
|
1421
|
+
ws.sendLine('');
|
|
1422
|
+
return {};
|
|
1423
|
+
}
|
|
1340
1424
|
return await showSignerStatus(ws);
|
|
1341
1425
|
}
|
|
1342
1426
|
|
|
@@ -1802,6 +1886,8 @@ async function cmdAI(args, ws) {
|
|
|
1802
1886
|
const chain = getConfig('chain') || 'base';
|
|
1803
1887
|
const wallet = getConfig('activeWallet') || '(not set)';
|
|
1804
1888
|
const slippage = getConfig('slippage') || 0.5;
|
|
1889
|
+
const soul = hasSoul() ? getSoul() : null;
|
|
1890
|
+
const recentMemories = await getRecentMemories(3);
|
|
1805
1891
|
|
|
1806
1892
|
engine.setSystemPrompt(`You are DARKSOL Terminal's AI trading assistant running in a web terminal.
|
|
1807
1893
|
|
|
@@ -1818,6 +1904,10 @@ USER CONTEXT:
|
|
|
1818
1904
|
- Active wallet: ${wallet}
|
|
1819
1905
|
- Slippage: ${slippage}%
|
|
1820
1906
|
- Supported chains: Base (default), Ethereum, Polygon, Arbitrum, Optimism
|
|
1907
|
+
- Soul user: ${soul?.userName || '(unknown)'}
|
|
1908
|
+
- Soul agent: ${soul?.agentName || 'Darksol'}
|
|
1909
|
+
- Soul tone: ${soul?.tone || 'practical'}
|
|
1910
|
+
- Recent persistent memories loaded: ${recentMemories.length}
|
|
1821
1911
|
|
|
1822
1912
|
RULES:
|
|
1823
1913
|
- Be concise — this is a terminal, not a blog
|
|
@@ -1839,6 +1929,9 @@ COMMAND REFERENCE:
|
|
|
1839
1929
|
|
|
1840
1930
|
chatEngines.set(ws, engine);
|
|
1841
1931
|
ws.sendLine(` ${ANSI.green}● AI connected${ANSI.reset} ${ANSI.dim}(${engine.provider}/${engine.model})${ANSI.reset}`);
|
|
1932
|
+
if (soul) {
|
|
1933
|
+
ws.sendLine(` ${ANSI.dim}${soul.agentName} is live for ${soul.userName} with ${soul.tone} tone.${ANSI.reset}`);
|
|
1934
|
+
}
|
|
1842
1935
|
ws.sendLine('');
|
|
1843
1936
|
} catch (err) {
|
|
1844
1937
|
ws.sendLine(` ${ANSI.red}✗ AI initialization failed: ${err.message}${ANSI.reset}`);
|
|
@@ -1926,7 +2019,7 @@ async function cmdKeys(args, ws) {
|
|
|
1926
2019
|
|
|
1927
2020
|
if (!svc) {
|
|
1928
2021
|
ws.sendLine(` ${ANSI.red}✗ Unknown service: ${service}${ANSI.reset}`);
|
|
1929
|
-
ws.sendLine(` ${ANSI.dim}Available: openai, anthropic, openrouter, ollama${ANSI.reset}`);
|
|
2022
|
+
ws.sendLine(` ${ANSI.dim}Available: openai, anthropic, openrouter, minimax, ollama, bankr${ANSI.reset}`);
|
|
1930
2023
|
ws.sendLine('');
|
|
1931
2024
|
return {};
|
|
1932
2025
|
}
|
|
@@ -1974,7 +2067,7 @@ async function cmdKeys(args, ws) {
|
|
|
1974
2067
|
ws.sendLine(`${ANSI.dim} ${'─'.repeat(50)}${ANSI.reset}`);
|
|
1975
2068
|
ws.sendLine('');
|
|
1976
2069
|
|
|
1977
|
-
const llmProviders = ['openai', 'anthropic', 'openrouter', 'ollama', 'bankr'];
|
|
2070
|
+
const llmProviders = ['openai', 'anthropic', 'openrouter', 'minimax', 'ollama', 'bankr'];
|
|
1978
2071
|
ws.sendLine(` ${ANSI.gold}LLM Providers:${ANSI.reset}`);
|
|
1979
2072
|
for (const p of llmProviders) {
|
|
1980
2073
|
const svc = SERVICES[p];
|
|
@@ -1988,6 +2081,7 @@ async function cmdKeys(args, ws) {
|
|
|
1988
2081
|
ws.sendLine(` ${ANSI.green}keys add openai sk-...${ANSI.reset} ${ANSI.dim}Add OpenAI key${ANSI.reset}`);
|
|
1989
2082
|
ws.sendLine(` ${ANSI.green}keys add anthropic sk-ant-...${ANSI.reset} ${ANSI.dim}Add Anthropic key${ANSI.reset}`);
|
|
1990
2083
|
ws.sendLine(` ${ANSI.green}keys add openrouter sk-or-...${ANSI.reset} ${ANSI.dim}Add OpenRouter key${ANSI.reset}`);
|
|
2084
|
+
ws.sendLine(` ${ANSI.green}keys add minimax <key>${ANSI.reset} ${ANSI.dim}Add MiniMax key${ANSI.reset}`);
|
|
1991
2085
|
ws.sendLine(` ${ANSI.green}keys add ollama http://...${ANSI.reset} ${ANSI.dim}Add Ollama host${ANSI.reset}`);
|
|
1992
2086
|
ws.sendLine('');
|
|
1993
2087
|
|
package/src/web/server.js
CHANGED
|
@@ -5,6 +5,8 @@ import { fileURLToPath } from 'url';
|
|
|
5
5
|
import { dirname, join } from 'path';
|
|
6
6
|
import open from 'open';
|
|
7
7
|
import { theme } from '../ui/theme.js';
|
|
8
|
+
import { getRecentMemories } from '../memory/index.js';
|
|
9
|
+
import { getSoul, hasSoul } from '../soul/index.js';
|
|
8
10
|
import { createRequire } from 'module';
|
|
9
11
|
const require = createRequire(import.meta.url);
|
|
10
12
|
const { version: PKG_VERSION } = require('../../package.json');
|
|
@@ -89,6 +91,19 @@ export async function startWebShell(opts = {}) {
|
|
|
89
91
|
data: getBanner(),
|
|
90
92
|
}));
|
|
91
93
|
|
|
94
|
+
if (hasSoul()) {
|
|
95
|
+
const soul = getSoul();
|
|
96
|
+
getRecentMemories(3).then((memories) => {
|
|
97
|
+
const memoryHint = memories.length > 0
|
|
98
|
+
? `\r\n \x1b[38;2;102;102;102m${soul.agentName} loaded ${memories.length} recent memories.\x1b[0m`
|
|
99
|
+
: '';
|
|
100
|
+
ws.send(JSON.stringify({
|
|
101
|
+
type: 'output',
|
|
102
|
+
data: ` \x1b[38;2;255;215;0mWelcome back, ${soul.userName}.\x1b[0m\r\n \x1b[38;2;102;102;102m${soul.agentName} is online with a ${soul.tone} tone.\x1b[0m${memoryHint}\r\n\r\n`,
|
|
103
|
+
}));
|
|
104
|
+
}).catch(() => {});
|
|
105
|
+
}
|
|
106
|
+
|
|
92
107
|
// AI connection check right after banner
|
|
93
108
|
const aiStatus = getAIStatus();
|
|
94
109
|
ws.send(JSON.stringify({
|