@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.
@@ -0,0 +1,311 @@
1
+ import fetch from 'node-fetch';
2
+ import { ethers } from 'ethers';
3
+ import { getConfig, getRPC } from '../config/store.js';
4
+ import { loadWallet } from '../wallet/keystore.js';
5
+
6
+ const DEXSCREENER_API = 'https://api.dexscreener.com/latest/dex/search?q=';
7
+ const USDC_ADDRESSES = {
8
+ base: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',
9
+ ethereum: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
10
+ arbitrum: '0xaf88d065e77c8cC2239327C5EDb3A432268e5831',
11
+ optimism: '0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85',
12
+ polygon: '0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359',
13
+ };
14
+ const PORTFOLIO_CHAINS = {
15
+ base: { name: 'Base' },
16
+ ethereum: { name: 'Ethereum' },
17
+ arbitrum: { name: 'Arbitrum' },
18
+ optimism: { name: 'Optimism' },
19
+ polygon: { name: 'Polygon' },
20
+ };
21
+ const ERC20_ABI = [
22
+ 'function balanceOf(address) view returns (uint256)',
23
+ 'function decimals() view returns (uint8)',
24
+ ];
25
+
26
+ function compactNumber(value) {
27
+ const num = Number(value || 0);
28
+ if (!Number.isFinite(num)) return '0';
29
+ if (num >= 1e9) return `${(num / 1e9).toFixed(2)}B`;
30
+ if (num >= 1e6) return `${(num / 1e6).toFixed(2)}M`;
31
+ if (num >= 1e3) return `${(num / 1e3).toFixed(1)}K`;
32
+ return num.toFixed(2);
33
+ }
34
+
35
+ function summarizeResult(result) {
36
+ if (!result) return 'No result';
37
+ if (typeof result === 'string') return result;
38
+ if (result.summary) return result.summary;
39
+ if (result.final) return result.final;
40
+ if (result.error) return result.error;
41
+ if (result.token && result.priceUsd) return `${result.token} at $${result.priceUsd}`;
42
+ return JSON.stringify(result).slice(0, 240);
43
+ }
44
+
45
+ async function fetchBestPair(query, fetchImpl = fetch) {
46
+ const response = await fetchImpl(`${DEXSCREENER_API}${encodeURIComponent(query)}`);
47
+ const data = await response.json();
48
+ const pairs = Array.isArray(data.pairs) ? data.pairs : [];
49
+ if (pairs.length === 0) return null;
50
+ return pairs.sort((a, b) => (b.liquidity?.usd || 0) - (a.liquidity?.usd || 0))[0];
51
+ }
52
+
53
+ async function getEthPrice(fetchImpl = fetch) {
54
+ try {
55
+ const response = await fetchImpl('https://api.coingecko.com/api/v3/simple/price?ids=ethereum&vs_currencies=usd');
56
+ const data = await response.json();
57
+ return Number(data.ethereum?.usd) || 3000;
58
+ } catch {
59
+ return 3000;
60
+ }
61
+ }
62
+
63
+ async function readTokenBalance(provider, address, tokenAddress) {
64
+ if (!tokenAddress) return 0;
65
+ try {
66
+ const contract = new ethers.Contract(tokenAddress, ERC20_ABI, provider);
67
+ const raw = await contract.balanceOf(address);
68
+ const decimals = await contract.decimals();
69
+ return Number(ethers.formatUnits(raw, decimals));
70
+ } catch {
71
+ return 0;
72
+ }
73
+ }
74
+
75
+ function requireWallet(walletName) {
76
+ const resolved = walletName || getConfig('activeWallet');
77
+ if (!resolved) {
78
+ throw new Error('No active wallet configured');
79
+ }
80
+ return loadWallet(resolved);
81
+ }
82
+
83
+ function defaultRegistry(deps = {}) {
84
+ const fetchImpl = deps.fetch || fetch;
85
+ const providerFactory = deps.providerFactory || ((chain) => new ethers.JsonRpcProvider(getRPC(chain)));
86
+
87
+ return {
88
+ price: {
89
+ description: 'Fetch a live token price and liquidity snapshot',
90
+ mutating: false,
91
+ handler: async ({ token, query }) => {
92
+ const resolved = token || query;
93
+ if (!resolved) throw new Error('price requires token or query');
94
+ const pair = await fetchBestPair(resolved, fetchImpl);
95
+ if (!pair) return { ok: false, token: resolved, summary: `No market data for ${resolved}` };
96
+ return {
97
+ ok: true,
98
+ token: pair.baseToken?.symbol || resolved.toUpperCase(),
99
+ name: pair.baseToken?.name || resolved,
100
+ chain: pair.chainId,
101
+ dex: pair.dexId,
102
+ priceUsd: Number(pair.priceUsd || 0),
103
+ change24h: Number(pair.priceChange?.h24 || 0),
104
+ liquidityUsd: Number(pair.liquidity?.usd || 0),
105
+ volume24hUsd: Number(pair.volume?.h24 || 0),
106
+ pairAddress: pair.pairAddress,
107
+ summary: `${pair.baseToken?.symbol || resolved} ${Number(pair.priceUsd || 0).toFixed(6)} USD, 24h ${Number(pair.priceChange?.h24 || 0).toFixed(2)}%, liq $${compactNumber(pair.liquidity?.usd)}`,
108
+ };
109
+ },
110
+ },
111
+ gas: {
112
+ description: 'Fetch current gas data for a chain',
113
+ mutating: false,
114
+ handler: async ({ chain }) => {
115
+ const resolvedChain = chain || getConfig('chain') || 'base';
116
+ const provider = providerFactory(resolvedChain);
117
+ const feeData = await provider.getFeeData();
118
+ const gasPriceWei = feeData.gasPrice || 0n;
119
+ const gasPriceGwei = Number(ethers.formatUnits(gasPriceWei, 'gwei'));
120
+ const ethPrice = await getEthPrice(fetchImpl);
121
+ return {
122
+ ok: true,
123
+ chain: resolvedChain,
124
+ gasPriceGwei,
125
+ maxFeeGwei: Number(ethers.formatUnits(feeData.maxFeePerGas || 0n, 'gwei')),
126
+ priorityFeeGwei: Number(ethers.formatUnits(feeData.maxPriorityFeePerGas || 0n, 'gwei')),
127
+ ethPriceUsd: ethPrice,
128
+ summary: `${resolvedChain} gas ${gasPriceGwei.toFixed(2)} gwei`,
129
+ };
130
+ },
131
+ },
132
+ 'wallet-balance': {
133
+ description: 'Read the active or named wallet native and USDC balances',
134
+ mutating: false,
135
+ handler: async ({ wallet, chain }) => {
136
+ const walletData = requireWallet(wallet);
137
+ const resolvedChain = chain || getConfig('chain') || walletData.chain || 'base';
138
+ const provider = providerFactory(resolvedChain);
139
+ const native = Number(ethers.formatEther(await provider.getBalance(walletData.address)));
140
+ const usdc = await readTokenBalance(provider, walletData.address, USDC_ADDRESSES[resolvedChain]);
141
+ return {
142
+ ok: true,
143
+ wallet: walletData.name,
144
+ address: walletData.address,
145
+ chain: resolvedChain,
146
+ native,
147
+ nativeSymbol: 'ETH',
148
+ usdc,
149
+ summary: `${walletData.name} on ${resolvedChain}: ${native.toFixed(6)} ETH and ${usdc.toFixed(2)} USDC`,
150
+ };
151
+ },
152
+ },
153
+ portfolio: {
154
+ description: 'Read wallet balances across supported chains',
155
+ mutating: false,
156
+ handler: async ({ wallet }) => {
157
+ const walletData = requireWallet(wallet);
158
+ const ethPrice = await getEthPrice(fetchImpl);
159
+ const chains = await Promise.all(
160
+ Object.entries(PORTFOLIO_CHAINS).map(async ([chainId, meta]) => {
161
+ try {
162
+ const provider = providerFactory(chainId);
163
+ const native = Number(ethers.formatEther(await provider.getBalance(walletData.address)));
164
+ const usdc = await readTokenBalance(provider, walletData.address, USDC_ADDRESSES[chainId]);
165
+ const totalUsd = native * ethPrice + usdc;
166
+ return { chain: chainId, name: meta.name, native, usdc, totalUsd };
167
+ } catch (error) {
168
+ return { chain: chainId, name: meta.name, native: 0, usdc: 0, totalUsd: 0, error: error.message };
169
+ }
170
+ }),
171
+ );
172
+ const totalUsd = chains.reduce((sum, item) => sum + (item.totalUsd || 0), 0);
173
+ return {
174
+ ok: true,
175
+ wallet: walletData.name,
176
+ address: walletData.address,
177
+ totalUsd,
178
+ chains,
179
+ summary: `${walletData.name} portfolio totals $${totalUsd.toFixed(2)} across ${chains.length} chains`,
180
+ };
181
+ },
182
+ },
183
+ market: {
184
+ description: 'Get top market movers or token comparison context',
185
+ mutating: false,
186
+ handler: async ({ query, token, chain, limit }) => {
187
+ const resolved = query || token || chain || getConfig('chain') || 'base';
188
+ const pair = await fetchBestPair(resolved, fetchImpl);
189
+ if (!pair) return { ok: false, summary: `No market snapshot for ${resolved}` };
190
+ return {
191
+ ok: true,
192
+ query: resolved,
193
+ token: pair.baseToken?.symbol || resolved,
194
+ chain: pair.chainId,
195
+ priceUsd: Number(pair.priceUsd || 0),
196
+ change24h: Number(pair.priceChange?.h24 || 0),
197
+ liquidityUsd: Number(pair.liquidity?.usd || 0),
198
+ volume24hUsd: Number(pair.volume?.h24 || 0),
199
+ limit: limit || 1,
200
+ summary: `Market snapshot ${pair.baseToken?.symbol || resolved}: $${Number(pair.priceUsd || 0).toFixed(6)}, vol $${compactNumber(pair.volume?.h24)}`,
201
+ };
202
+ },
203
+ },
204
+ watch: {
205
+ description: 'Take a quick watch snapshot for a token',
206
+ mutating: false,
207
+ handler: async ({ token, query }) => {
208
+ const resolved = token || query;
209
+ if (!resolved) throw new Error('watch requires token or query');
210
+ const pair = await fetchBestPair(resolved, fetchImpl);
211
+ if (!pair) return { ok: false, summary: `No watch data for ${resolved}` };
212
+ return {
213
+ ok: true,
214
+ token: pair.baseToken?.symbol || resolved,
215
+ priceUsd: Number(pair.priceUsd || 0),
216
+ change24h: Number(pair.priceChange?.h24 || 0),
217
+ liquidityUsd: Number(pair.liquidity?.usd || 0),
218
+ note: 'Watch tool returns a single snapshot inside the agent loop',
219
+ summary: `Watch snapshot ${pair.baseToken?.symbol || resolved}: $${Number(pair.priceUsd || 0).toFixed(6)}`,
220
+ };
221
+ },
222
+ },
223
+ swap: {
224
+ description: 'Execute a token swap',
225
+ mutating: true,
226
+ handler: deps.swapHandler || (async (args) => {
227
+ const { executeSwap } = await import('../trading/swap.js');
228
+ return executeSwap(args);
229
+ }),
230
+ },
231
+ send: {
232
+ description: 'Send ETH or ERC-20 tokens',
233
+ mutating: true,
234
+ handler: deps.sendHandler || (async (args) => {
235
+ const { sendFunds } = await import('../wallet/manager.js');
236
+ return sendFunds(args);
237
+ }),
238
+ },
239
+ 'script-run': {
240
+ description: 'Execute a saved automation script',
241
+ mutating: true,
242
+ handler: deps.scriptRunHandler || (async (args) => {
243
+ const { runScript } = await import('../scripts/engine.js');
244
+ return runScript(args.name, args);
245
+ }),
246
+ },
247
+ };
248
+ }
249
+
250
+ export function createToolRegistry(deps = {}) {
251
+ const base = defaultRegistry(deps);
252
+ return {
253
+ ...base,
254
+ ...(deps.overrides || {}),
255
+ };
256
+ }
257
+
258
+ export function listTools(registry) {
259
+ return Object.entries(registry).map(([name, tool]) => ({
260
+ name,
261
+ description: tool.description,
262
+ mutating: Boolean(tool.mutating),
263
+ }));
264
+ }
265
+
266
+ export function createToolExecutor({ registry = createToolRegistry(), allowActions = false, onEvent = () => {} } = {}) {
267
+ return async function executeTool(name, input = {}) {
268
+ const tool = registry[name];
269
+ if (!tool) {
270
+ return { ok: false, blocked: true, error: `Unknown tool: ${name}`, summary: `Unknown tool ${name}` };
271
+ }
272
+
273
+ if (tool.mutating && !allowActions) {
274
+ return {
275
+ ok: false,
276
+ blocked: true,
277
+ error: `Tool "${name}" is blocked in safe mode. Re-run with --allow-actions to enable mutating tools.`,
278
+ summary: `Blocked ${name} in safe mode`,
279
+ };
280
+ }
281
+
282
+ onEvent({ type: 'tool-start', tool: name, input, mutating: Boolean(tool.mutating) });
283
+ try {
284
+ const result = await tool.handler(input);
285
+ const normalized = typeof result === 'object' && result !== null ? result : { value: result };
286
+ const finalResult = {
287
+ ok: normalized.ok !== false,
288
+ ...normalized,
289
+ summary: summarizeResult(normalized),
290
+ };
291
+ onEvent({ type: 'tool-result', tool: name, result: finalResult });
292
+ return finalResult;
293
+ } catch (error) {
294
+ const failure = { ok: false, error: error.message, summary: error.message };
295
+ onEvent({ type: 'tool-error', tool: name, error: error.message });
296
+ return failure;
297
+ }
298
+ };
299
+ }
300
+
301
+ export const AGENT_TOOL_NAMES = [
302
+ 'price',
303
+ 'gas',
304
+ 'wallet-balance',
305
+ 'portfolio',
306
+ 'market',
307
+ 'watch',
308
+ 'swap',
309
+ 'send',
310
+ 'script-run',
311
+ ];
package/src/cli.js CHANGED
@@ -26,8 +26,12 @@ import { addKey, removeKey, listKeys } from './config/keys.js';
26
26
  import { parseIntent, startChat, adviseStrategy, analyzeToken, executeIntent } from './llm/intent.js';
27
27
  import { startAgentSigner, showAgentDocs } from './wallet/agent-signer.js';
28
28
  import { listSkills, installSkill, skillInfo, uninstallSkill } from './services/skills.js';
29
- import { runSetupWizard, checkFirstRun } from './setup/wizard.js';
29
+ import { runSetupWizard } from './setup/wizard.js';
30
+ import { displaySoul, hasSoul, resetSoul, runSoulSetup } from './soul/index.js';
31
+ import { clearMemories, exportMemories, getRecentMemories, searchMemories } from './memory/index.js';
32
+ import { getAgentStatus, planAgentGoal, runAgentTask } from './agent/index.js';
30
33
  import { createRequire } from 'module';
34
+ import { resolve } from 'path';
31
35
  const require = createRequire(import.meta.url);
32
36
  const { version: PKG_VERSION } = require('../package.json');
33
37
 
@@ -647,6 +651,101 @@ export function cli(argv) {
647
651
  .option('-m, --model <model>', 'Model name')
648
652
  .action((opts) => startChat(opts));
649
653
 
654
+ const soul = program
655
+ .command('soul')
656
+ .description('Identity and agent personality')
657
+ .action(async () => {
658
+ await runSoulSetup({ reset: !hasSoul() });
659
+ });
660
+
661
+ soul
662
+ .command('show')
663
+ .description('Show current soul configuration')
664
+ .action(() => displaySoul());
665
+
666
+ soul
667
+ .command('reset')
668
+ .description('Clear soul configuration and re-run setup')
669
+ .action(async () => {
670
+ resetSoul();
671
+ await runSoulSetup({ reset: true });
672
+ });
673
+
674
+ const memory = program
675
+ .command('memory')
676
+ .description('Persistent memory store');
677
+
678
+ memory
679
+ .command('show')
680
+ .description('Show recent persistent memories')
681
+ .option('-n, --limit <n>', 'Number of memories', '10')
682
+ .action(async (opts) => {
683
+ showMiniBanner();
684
+ showSection('MEMORY');
685
+ const memories = await getRecentMemories(parseInt(opts.limit, 10) || 10);
686
+ if (memories.length === 0) {
687
+ info('No persistent memories stored.');
688
+ console.log('');
689
+ return;
690
+ }
691
+
692
+ memories.forEach((memoryItem) => {
693
+ kvDisplay([
694
+ ['ID', memoryItem.id],
695
+ ['Category', memoryItem.category],
696
+ ['Source', memoryItem.source],
697
+ ['When', memoryItem.timestamp],
698
+ ['Content', memoryItem.content],
699
+ ]);
700
+ console.log('');
701
+ });
702
+ });
703
+
704
+ memory
705
+ .command('search <query...>')
706
+ .description('Search persistent memories')
707
+ .action(async (queryParts) => {
708
+ const query = queryParts.join(' ');
709
+ showMiniBanner();
710
+ showSection('MEMORY SEARCH');
711
+ info(`Query: ${query}`);
712
+ console.log('');
713
+
714
+ const matches = await searchMemories(query);
715
+ if (matches.length === 0) {
716
+ warn('No matching memories.');
717
+ console.log('');
718
+ return;
719
+ }
720
+
721
+ matches.slice(0, 10).forEach((memoryItem) => {
722
+ kvDisplay([
723
+ ['Category', memoryItem.category],
724
+ ['Source', memoryItem.source],
725
+ ['When', memoryItem.timestamp],
726
+ ['Content', memoryItem.content],
727
+ ]);
728
+ console.log('');
729
+ });
730
+ });
731
+
732
+ memory
733
+ .command('clear')
734
+ .description('Clear all persistent memories')
735
+ .action(async () => {
736
+ await clearMemories();
737
+ success('Persistent memory cleared.');
738
+ });
739
+
740
+ memory
741
+ .command('export [file]')
742
+ .description('Export persistent memories to JSON')
743
+ .action(async (file) => {
744
+ const target = resolve(file || `darksol-memory-export-${Date.now()}.json`);
745
+ await exportMemories(target);
746
+ success(`Memory exported to ${target}`);
747
+ });
748
+
650
749
  // ═══════════════════════════════════════
651
750
  // SETUP COMMAND
652
751
  // ═══════════════════════════════════════
@@ -666,7 +765,7 @@ export function cli(argv) {
666
765
  ai
667
766
  .command('chat')
668
767
  .description('Start interactive AI trading chat')
669
- .option('-p, --provider <name>', 'LLM provider (openai, anthropic, openrouter, ollama)')
768
+ .option('-p, --provider <name>', 'LLM provider (openai, anthropic, openrouter, minimax, ollama)')
670
769
  .option('-m, --model <model>', 'Model name')
671
770
  .action((opts) => startChat(opts));
672
771
 
@@ -785,6 +884,93 @@ export function cli(argv) {
785
884
  .command('agent')
786
885
  .description('Secure agent signer — PK-isolated wallet for AI agents');
787
886
 
887
+ agent
888
+ .command('task <goal...>')
889
+ .description('Run the agent loop against a goal')
890
+ .option('--max-steps <n>', 'Maximum loop steps', '10')
891
+ .option('--allow-actions', 'Allow mutating tools such as swap/send/script-run')
892
+ .action(async (goalParts, opts) => {
893
+ showMiniBanner();
894
+ showSection('AGENT TASK');
895
+ const goal = goalParts.join(' ').trim();
896
+ info(`Goal: ${goal}`);
897
+ info(`Mode: ${opts.allowActions ? 'actions enabled' : 'safe mode'}`);
898
+ console.log('');
899
+
900
+ const result = await runAgentTask(goal, {
901
+ maxSteps: parseInt(opts.maxSteps, 10),
902
+ allowActions: opts.allowActions,
903
+ onProgress: (event) => {
904
+ if (event.type === 'thought') {
905
+ info(`Step ${event.step}: ${event.action}`);
906
+ if (event.thought) console.log(` ${theme.dim(event.thought)}`);
907
+ }
908
+ if (event.type === 'observation') {
909
+ const summary = event.observation?.summary || event.observation?.error || '';
910
+ if (summary) console.log(` ${theme.dim(summary)}`);
911
+ console.log('');
912
+ }
913
+ },
914
+ });
915
+
916
+ showSection('AGENT RESULT');
917
+ kvDisplay([
918
+ ['Status', result.status],
919
+ ['Steps', `${result.stepsTaken}/${result.maxSteps}`],
920
+ ['Stop Reason', result.stopReason],
921
+ ]);
922
+ console.log('');
923
+ if (result.final) {
924
+ success(result.final);
925
+ console.log('');
926
+ }
927
+ });
928
+
929
+ agent
930
+ .command('plan <goal...>')
931
+ .description('Generate a concise agent plan for a goal')
932
+ .action(async (goalParts) => {
933
+ showMiniBanner();
934
+ showSection('AGENT PLAN');
935
+ const goal = goalParts.join(' ').trim();
936
+ const plan = await planAgentGoal(goal);
937
+ info(plan.summary);
938
+ console.log('');
939
+ plan.steps.forEach((step, index) => console.log(` ${theme.gold(String(index + 1).padStart(2, ' '))}. ${step}`));
940
+ console.log('');
941
+ });
942
+
943
+ agent
944
+ .command('status')
945
+ .description('Show the latest agent task or plan status')
946
+ .action(() => {
947
+ showMiniBanner();
948
+ showSection('AGENT STATUS');
949
+ const status = getAgentStatus();
950
+ if (!status || !status.status) {
951
+ warn('No agent runs recorded yet.');
952
+ console.log('');
953
+ return;
954
+ }
955
+
956
+ kvDisplay([
957
+ ['Status', status.status || '-'],
958
+ ['Goal', status.goal || '-'],
959
+ ['Summary', status.summary || '-'],
960
+ ['Steps', status.maxSteps ? `${status.stepsTaken || 0}/${status.maxSteps}` : String(status.stepsTaken || 0)],
961
+ ['Actions', status.allowActions ? 'enabled' : 'safe mode'],
962
+ ['Started', status.startedAt || '-'],
963
+ ['Completed', status.completedAt || '-'],
964
+ ['Updated', status.updatedAt || '-'],
965
+ ]);
966
+ if (Array.isArray(status.plan) && status.plan.length > 0) {
967
+ console.log('');
968
+ showSection('LAST PLAN');
969
+ status.plan.forEach((step) => console.log(` ${theme.dim(step)}`));
970
+ }
971
+ console.log('');
972
+ });
973
+
788
974
  agent
789
975
  .command('start [wallet]')
790
976
  .description('Start the agent signing proxy')
@@ -951,6 +1137,9 @@ export function cli(argv) {
951
1137
  ['Output', cfg.output],
952
1138
  ['Slippage', `${cfg.slippage}%`],
953
1139
  ['Gas Multiplier', `${cfg.gasMultiplier}x`],
1140
+ ['Soul User', cfg.soul?.userName || theme.dim('(not set)')],
1141
+ ['Agent Name', cfg.soul?.agentName || 'Darksol'],
1142
+ ['Tone', cfg.soul?.tone || theme.dim('(not set)')],
954
1143
  ['Mail', cfg.mailEmail || theme.dim('(not set)')],
955
1144
  ['Version', PKG_VERSION],
956
1145
  ['Config File', configPath()],
@@ -1203,8 +1392,10 @@ function showCommandList() {
1203
1392
  ['dca', 'Dollar-cost averaging orders'],
1204
1393
  ['ai chat', 'Standalone AI chat session'],
1205
1394
  ['ai execute', 'Parse + execute a trade via AI'],
1206
- ['agent start', 'Start secure agent signer'],
1395
+ ['agent task', 'Run bounded agent loop for a goal'],
1207
1396
  ['keys', 'API key vault'],
1397
+ ['soul', 'Identity and agent personality'],
1398
+ ['memory', 'Persistent cross-session memory'],
1208
1399
  ['script', 'Execution scripts & strategies'],
1209
1400
  ['market', 'Market intel & token data'],
1210
1401
  ['oracle', 'On-chain random oracle'],
@@ -84,6 +84,14 @@ export const SERVICES = {
84
84
  docsUrl: 'https://openrouter.ai/keys',
85
85
  validate: (key) => key.startsWith('sk-or-'),
86
86
  },
87
+ minimax: {
88
+ name: 'MiniMax',
89
+ category: 'llm',
90
+ description: 'MiniMax-M2.5 via OpenAI-compatible chat completions',
91
+ envVar: 'MINIMAX_API_KEY',
92
+ docsUrl: 'https://platform.minimax.io/docs/guides/models-intro',
93
+ validate: (key) => key.length > 10,
94
+ },
87
95
  ollama: {
88
96
  name: 'Ollama (Local)',
89
97
  category: 'llm',
@@ -420,7 +428,7 @@ export function hasKey(service) {
420
428
  */
421
429
  export function hasAnyLLM() {
422
430
  // Cloud providers — need real validated API keys
423
- if (['openai', 'anthropic', 'openrouter', 'bankr'].some(s => hasKey(s))) return true;
431
+ if (['openai', 'anthropic', 'openrouter', 'minimax', 'bankr'].some(s => hasKey(s))) return true;
424
432
  // Ollama — check if explicitly configured via hasKey (validates URL format)
425
433
  if (hasKey('ollama')) return true;
426
434
  return false;
@@ -19,6 +19,31 @@ const config = new Conf({
19
19
  },
20
20
  slippage: { type: 'number', default: 0.5 },
21
21
  gasMultiplier: { type: 'number', default: 1.1 },
22
+ soul: {
23
+ type: 'object',
24
+ default: {
25
+ userName: '',
26
+ agentName: 'Darksol',
27
+ tone: '',
28
+ createdAt: '',
29
+ },
30
+ },
31
+ agentState: {
32
+ type: 'object',
33
+ default: {
34
+ status: '',
35
+ goal: '',
36
+ summary: '',
37
+ plan: [],
38
+ stepsTaken: 0,
39
+ maxSteps: 0,
40
+ allowActions: false,
41
+ startedAt: null,
42
+ completedAt: null,
43
+ stopReason: '',
44
+ updatedAt: null,
45
+ },
46
+ },
22
47
  dca: {
23
48
  type: 'object',
24
49
  default: {
@@ -48,6 +73,10 @@ export function setConfig(key, value) {
48
73
  config.set(key, value);
49
74
  }
50
75
 
76
+ export function deleteConfig(key) {
77
+ config.delete(key);
78
+ }
79
+
51
80
  export function getAllConfig() {
52
81
  return config.store;
53
82
  }