@darksol/terminal 0.9.0 → 0.9.2
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 +8 -1
- 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 +120 -2
- package/src/config/keys.js +9 -1
- package/src/config/store.js +16 -0
- package/src/llm/engine.js +14 -8
- package/src/llm/models.js +67 -0
- package/src/setup/wizard.js +81 -9
- package/src/web/commands.js +183 -7
package/src/cli.js
CHANGED
|
@@ -29,8 +29,10 @@ import { listSkills, installSkill, skillInfo, uninstallSkill } from './services/
|
|
|
29
29
|
import { runSetupWizard } from './setup/wizard.js';
|
|
30
30
|
import { displaySoul, hasSoul, resetSoul, runSoulSetup } from './soul/index.js';
|
|
31
31
|
import { clearMemories, exportMemories, getRecentMemories, searchMemories } from './memory/index.js';
|
|
32
|
+
import { getAgentStatus, planAgentGoal, runAgentTask } from './agent/index.js';
|
|
32
33
|
import { createRequire } from 'module';
|
|
33
34
|
import { resolve } from 'path';
|
|
35
|
+
import { getConfiguredModel, getProviderDefaultModel } from './llm/models.js';
|
|
34
36
|
const require = createRequire(import.meta.url);
|
|
35
37
|
const { version: PKG_VERSION } = require('../package.json');
|
|
36
38
|
|
|
@@ -764,7 +766,7 @@ export function cli(argv) {
|
|
|
764
766
|
ai
|
|
765
767
|
.command('chat')
|
|
766
768
|
.description('Start interactive AI trading chat')
|
|
767
|
-
.option('-p, --provider <name>', 'LLM provider (openai, anthropic, openrouter, ollama)')
|
|
769
|
+
.option('-p, --provider <name>', 'LLM provider (openai, anthropic, openrouter, minimax, ollama)')
|
|
768
770
|
.option('-m, --model <model>', 'Model name')
|
|
769
771
|
.action((opts) => startChat(opts));
|
|
770
772
|
|
|
@@ -883,6 +885,93 @@ export function cli(argv) {
|
|
|
883
885
|
.command('agent')
|
|
884
886
|
.description('Secure agent signer — PK-isolated wallet for AI agents');
|
|
885
887
|
|
|
888
|
+
agent
|
|
889
|
+
.command('task <goal...>')
|
|
890
|
+
.description('Run the agent loop against a goal')
|
|
891
|
+
.option('--max-steps <n>', 'Maximum loop steps', '10')
|
|
892
|
+
.option('--allow-actions', 'Allow mutating tools such as swap/send/script-run')
|
|
893
|
+
.action(async (goalParts, opts) => {
|
|
894
|
+
showMiniBanner();
|
|
895
|
+
showSection('AGENT TASK');
|
|
896
|
+
const goal = goalParts.join(' ').trim();
|
|
897
|
+
info(`Goal: ${goal}`);
|
|
898
|
+
info(`Mode: ${opts.allowActions ? 'actions enabled' : 'safe mode'}`);
|
|
899
|
+
console.log('');
|
|
900
|
+
|
|
901
|
+
const result = await runAgentTask(goal, {
|
|
902
|
+
maxSteps: parseInt(opts.maxSteps, 10),
|
|
903
|
+
allowActions: opts.allowActions,
|
|
904
|
+
onProgress: (event) => {
|
|
905
|
+
if (event.type === 'thought') {
|
|
906
|
+
info(`Step ${event.step}: ${event.action}`);
|
|
907
|
+
if (event.thought) console.log(` ${theme.dim(event.thought)}`);
|
|
908
|
+
}
|
|
909
|
+
if (event.type === 'observation') {
|
|
910
|
+
const summary = event.observation?.summary || event.observation?.error || '';
|
|
911
|
+
if (summary) console.log(` ${theme.dim(summary)}`);
|
|
912
|
+
console.log('');
|
|
913
|
+
}
|
|
914
|
+
},
|
|
915
|
+
});
|
|
916
|
+
|
|
917
|
+
showSection('AGENT RESULT');
|
|
918
|
+
kvDisplay([
|
|
919
|
+
['Status', result.status],
|
|
920
|
+
['Steps', `${result.stepsTaken}/${result.maxSteps}`],
|
|
921
|
+
['Stop Reason', result.stopReason],
|
|
922
|
+
]);
|
|
923
|
+
console.log('');
|
|
924
|
+
if (result.final) {
|
|
925
|
+
success(result.final);
|
|
926
|
+
console.log('');
|
|
927
|
+
}
|
|
928
|
+
});
|
|
929
|
+
|
|
930
|
+
agent
|
|
931
|
+
.command('plan <goal...>')
|
|
932
|
+
.description('Generate a concise agent plan for a goal')
|
|
933
|
+
.action(async (goalParts) => {
|
|
934
|
+
showMiniBanner();
|
|
935
|
+
showSection('AGENT PLAN');
|
|
936
|
+
const goal = goalParts.join(' ').trim();
|
|
937
|
+
const plan = await planAgentGoal(goal);
|
|
938
|
+
info(plan.summary);
|
|
939
|
+
console.log('');
|
|
940
|
+
plan.steps.forEach((step, index) => console.log(` ${theme.gold(String(index + 1).padStart(2, ' '))}. ${step}`));
|
|
941
|
+
console.log('');
|
|
942
|
+
});
|
|
943
|
+
|
|
944
|
+
agent
|
|
945
|
+
.command('status')
|
|
946
|
+
.description('Show the latest agent task or plan status')
|
|
947
|
+
.action(() => {
|
|
948
|
+
showMiniBanner();
|
|
949
|
+
showSection('AGENT STATUS');
|
|
950
|
+
const status = getAgentStatus();
|
|
951
|
+
if (!status || !status.status) {
|
|
952
|
+
warn('No agent runs recorded yet.');
|
|
953
|
+
console.log('');
|
|
954
|
+
return;
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
kvDisplay([
|
|
958
|
+
['Status', status.status || '-'],
|
|
959
|
+
['Goal', status.goal || '-'],
|
|
960
|
+
['Summary', status.summary || '-'],
|
|
961
|
+
['Steps', status.maxSteps ? `${status.stepsTaken || 0}/${status.maxSteps}` : String(status.stepsTaken || 0)],
|
|
962
|
+
['Actions', status.allowActions ? 'enabled' : 'safe mode'],
|
|
963
|
+
['Started', status.startedAt || '-'],
|
|
964
|
+
['Completed', status.completedAt || '-'],
|
|
965
|
+
['Updated', status.updatedAt || '-'],
|
|
966
|
+
]);
|
|
967
|
+
if (Array.isArray(status.plan) && status.plan.length > 0) {
|
|
968
|
+
console.log('');
|
|
969
|
+
showSection('LAST PLAN');
|
|
970
|
+
status.plan.forEach((step) => console.log(` ${theme.dim(step)}`));
|
|
971
|
+
}
|
|
972
|
+
console.log('');
|
|
973
|
+
});
|
|
974
|
+
|
|
886
975
|
agent
|
|
887
976
|
.command('start [wallet]')
|
|
888
977
|
.description('Start the agent signing proxy')
|
|
@@ -1049,6 +1138,8 @@ export function cli(argv) {
|
|
|
1049
1138
|
['Output', cfg.output],
|
|
1050
1139
|
['Slippage', `${cfg.slippage}%`],
|
|
1051
1140
|
['Gas Multiplier', `${cfg.gasMultiplier}x`],
|
|
1141
|
+
['LLM Provider', cfg.llm?.provider || theme.dim('(not set)')],
|
|
1142
|
+
['LLM Model', getConfiguredModel(cfg.llm?.provider || 'openai') || theme.dim('(default)')],
|
|
1052
1143
|
['Soul User', cfg.soul?.userName || theme.dim('(not set)')],
|
|
1053
1144
|
['Agent Name', cfg.soul?.agentName || 'Darksol'],
|
|
1054
1145
|
['Tone', cfg.soul?.tone || theme.dim('(not set)')],
|
|
@@ -1065,6 +1156,33 @@ export function cli(argv) {
|
|
|
1065
1156
|
console.log('');
|
|
1066
1157
|
});
|
|
1067
1158
|
|
|
1159
|
+
config
|
|
1160
|
+
.command('model [model]')
|
|
1161
|
+
.description('Set the LLM model')
|
|
1162
|
+
.option('-p, --provider <provider>', 'LLM provider (defaults to current provider)')
|
|
1163
|
+
.action((model, opts) => {
|
|
1164
|
+
const provider = opts.provider || getConfig('llm.provider') || 'openai';
|
|
1165
|
+
if (!model) {
|
|
1166
|
+
const current = getConfiguredModel(provider);
|
|
1167
|
+
const fallback = getProviderDefaultModel(provider);
|
|
1168
|
+
info(`Current model for ${provider}: ${current || '(not set)'}`);
|
|
1169
|
+
if (fallback) {
|
|
1170
|
+
info(`Provider default: ${fallback}`);
|
|
1171
|
+
}
|
|
1172
|
+
return;
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
if (opts.provider) {
|
|
1176
|
+
setConfig('llm.provider', provider);
|
|
1177
|
+
setConfig('llmProvider', provider);
|
|
1178
|
+
}
|
|
1179
|
+
setConfig('llm.model', model);
|
|
1180
|
+
if (provider === 'ollama') {
|
|
1181
|
+
setConfig('ollamaModel', model);
|
|
1182
|
+
}
|
|
1183
|
+
success(`LLM model for ${provider}: ${model}`);
|
|
1184
|
+
});
|
|
1185
|
+
|
|
1068
1186
|
config
|
|
1069
1187
|
.command('set <key> <value>')
|
|
1070
1188
|
.description('Set config value')
|
|
@@ -1304,7 +1422,7 @@ function showCommandList() {
|
|
|
1304
1422
|
['dca', 'Dollar-cost averaging orders'],
|
|
1305
1423
|
['ai chat', 'Standalone AI chat session'],
|
|
1306
1424
|
['ai execute', 'Parse + execute a trade via AI'],
|
|
1307
|
-
['agent
|
|
1425
|
+
['agent task', 'Run bounded agent loop for a goal'],
|
|
1308
1426
|
['keys', 'API key vault'],
|
|
1309
1427
|
['soul', 'Identity and agent personality'],
|
|
1310
1428
|
['memory', 'Persistent cross-session memory'],
|
package/src/config/keys.js
CHANGED
|
@@ -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;
|
package/src/config/store.js
CHANGED
|
@@ -28,6 +28,22 @@ const config = new Conf({
|
|
|
28
28
|
createdAt: '',
|
|
29
29
|
},
|
|
30
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
|
+
},
|
|
31
47
|
dca: {
|
|
32
48
|
type: 'object',
|
|
33
49
|
default: {
|
package/src/llm/engine.js
CHANGED
|
@@ -3,18 +3,19 @@ import { getKeyFromEnv, getKey } from '../config/keys.js';
|
|
|
3
3
|
import { getConfig } from '../config/store.js';
|
|
4
4
|
import { SessionMemory, extractMemories, searchMemories } from '../memory/index.js';
|
|
5
5
|
import { formatSystemPrompt as formatSoulSystemPrompt } from '../soul/index.js';
|
|
6
|
+
import { getProviderDefaultModel } from './models.js';
|
|
6
7
|
|
|
7
8
|
const PROVIDERS = {
|
|
8
9
|
openai: {
|
|
9
10
|
url: 'https://api.openai.com/v1/chat/completions',
|
|
10
|
-
defaultModel: '
|
|
11
|
+
defaultModel: getProviderDefaultModel('openai'),
|
|
11
12
|
authHeader: (key) => ({ Authorization: `Bearer ${key}` }),
|
|
12
13
|
parseResponse: (data) => data.choices?.[0]?.message?.content,
|
|
13
14
|
parseUsage: (data) => data.usage,
|
|
14
15
|
},
|
|
15
16
|
anthropic: {
|
|
16
17
|
url: 'https://api.anthropic.com/v1/messages',
|
|
17
|
-
defaultModel: '
|
|
18
|
+
defaultModel: getProviderDefaultModel('anthropic'),
|
|
18
19
|
authHeader: (key) => ({ 'x-api-key': key, 'anthropic-version': '2023-06-01' }),
|
|
19
20
|
buildBody: (model, messages, systemPrompt) => ({
|
|
20
21
|
model,
|
|
@@ -30,7 +31,7 @@ const PROVIDERS = {
|
|
|
30
31
|
},
|
|
31
32
|
openrouter: {
|
|
32
33
|
url: 'https://openrouter.ai/api/v1/chat/completions',
|
|
33
|
-
defaultModel: '
|
|
34
|
+
defaultModel: getProviderDefaultModel('openrouter'),
|
|
34
35
|
authHeader: (key) => ({
|
|
35
36
|
Authorization: `Bearer ${key}`,
|
|
36
37
|
'HTTP-Referer': 'https://darksol.net',
|
|
@@ -39,16 +40,23 @@ const PROVIDERS = {
|
|
|
39
40
|
parseResponse: (data) => data.choices?.[0]?.message?.content,
|
|
40
41
|
parseUsage: (data) => data.usage,
|
|
41
42
|
},
|
|
43
|
+
minimax: {
|
|
44
|
+
url: 'https://api.minimax.io/v1/chat/completions',
|
|
45
|
+
defaultModel: getProviderDefaultModel('minimax'),
|
|
46
|
+
authHeader: (key) => ({ Authorization: `Bearer ${key}` }),
|
|
47
|
+
parseResponse: (data) => data.choices?.[0]?.message?.content,
|
|
48
|
+
parseUsage: (data) => data.usage,
|
|
49
|
+
},
|
|
42
50
|
ollama: {
|
|
43
51
|
url: null,
|
|
44
|
-
defaultModel: '
|
|
52
|
+
defaultModel: getProviderDefaultModel('ollama'),
|
|
45
53
|
authHeader: () => ({}),
|
|
46
54
|
parseResponse: (data) => data.choices?.[0]?.message?.content || data.message?.content,
|
|
47
55
|
parseUsage: () => ({ input: 0, output: 0 }),
|
|
48
56
|
},
|
|
49
57
|
bankr: {
|
|
50
58
|
url: 'https://llm.bankr.bot/v1/chat/completions',
|
|
51
|
-
defaultModel: '
|
|
59
|
+
defaultModel: getProviderDefaultModel('bankr'),
|
|
52
60
|
authHeader: (key) => ({ 'X-API-Key': key }),
|
|
53
61
|
parseResponse: (data) => data.choices?.[0]?.message?.content,
|
|
54
62
|
parseUsage: (data) => data.usage,
|
|
@@ -92,9 +100,7 @@ export class LLMEngine {
|
|
|
92
100
|
throw new Error(`Unknown LLM provider: ${this.provider}. Supported: ${Object.keys(PROVIDERS).join(', ')}`);
|
|
93
101
|
}
|
|
94
102
|
|
|
95
|
-
|
|
96
|
-
this.model = providerConfig.defaultModel;
|
|
97
|
-
}
|
|
103
|
+
this.model = this.model || providerConfig.defaultModel || getProviderDefaultModel(this.provider);
|
|
98
104
|
|
|
99
105
|
if (this.provider === 'ollama') {
|
|
100
106
|
const host = this.apiKey || getConfig('llm.ollamaHost') || 'http://localhost:11434';
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { getConfig } from '../config/store.js';
|
|
2
|
+
|
|
3
|
+
export const MODEL_CATALOG = {
|
|
4
|
+
openai: {
|
|
5
|
+
defaultModel: 'gpt-5.4',
|
|
6
|
+
choices: [
|
|
7
|
+
{ value: 'gpt-5.4', label: 'gpt-5.4', desc: 'flagship, complex reasoning' },
|
|
8
|
+
{ value: 'gpt-5-mini', label: 'gpt-5-mini', desc: 'fast, lower cost' },
|
|
9
|
+
{ value: 'gpt-4o', label: 'gpt-4o', desc: 'previous gen, still good' },
|
|
10
|
+
{ value: 'o3', label: 'o3', desc: 'reasoning model' },
|
|
11
|
+
],
|
|
12
|
+
},
|
|
13
|
+
anthropic: {
|
|
14
|
+
defaultModel: 'claude-sonnet-4-6',
|
|
15
|
+
choices: [
|
|
16
|
+
{ value: 'claude-opus-4-6', label: 'claude-opus-4-6', desc: 'most intelligent, agents+coding' },
|
|
17
|
+
{ value: 'claude-sonnet-4-6', label: 'claude-sonnet-4-6', desc: 'best speed/intelligence balance' },
|
|
18
|
+
{ value: 'claude-haiku-4-5', label: 'claude-haiku-4-5', desc: 'fastest, near-frontier' },
|
|
19
|
+
],
|
|
20
|
+
},
|
|
21
|
+
openrouter: {
|
|
22
|
+
defaultModel: 'anthropic/claude-sonnet-4-6',
|
|
23
|
+
choices: [
|
|
24
|
+
{ value: 'anthropic/claude-sonnet-4-6', label: 'anthropic/claude-sonnet-4-6', desc: 'popular pick' },
|
|
25
|
+
{ value: 'openai/gpt-5.4', label: 'openai/gpt-5.4', desc: 'popular pick' },
|
|
26
|
+
{ value: 'google/gemini-2.5-pro', label: 'google/gemini-2.5-pro', desc: 'popular pick' },
|
|
27
|
+
{ value: 'meta-llama/llama-4-maverick', label: 'meta-llama/llama-4-maverick', desc: 'popular pick' },
|
|
28
|
+
{ value: 'deepseek/deepseek-r1', label: 'deepseek/deepseek-r1', desc: 'popular pick' },
|
|
29
|
+
],
|
|
30
|
+
allowCustom: true,
|
|
31
|
+
},
|
|
32
|
+
minimax: {
|
|
33
|
+
defaultModel: 'MiniMax-M2.5',
|
|
34
|
+
choices: [
|
|
35
|
+
{ value: 'MiniMax-M2.5', label: 'MiniMax-M2.5', desc: 'flagship, 204K context, ~60 tps' },
|
|
36
|
+
{ value: 'MiniMax-M2.5-highspeed', label: 'MiniMax-M2.5-highspeed', desc: 'same perf, ~100 tps' },
|
|
37
|
+
{ value: 'MiniMax-M2.1', label: 'MiniMax-M2.1', desc: 'code-focused' },
|
|
38
|
+
{ value: 'MiniMax-M2.1-highspeed', label: 'MiniMax-M2.1-highspeed', desc: 'code-focused, faster' },
|
|
39
|
+
{ value: 'MiniMax-M2', label: 'MiniMax-M2', desc: 'agentic, advanced reasoning' },
|
|
40
|
+
],
|
|
41
|
+
},
|
|
42
|
+
ollama: {
|
|
43
|
+
defaultModel: 'llama3.1',
|
|
44
|
+
textInput: true,
|
|
45
|
+
},
|
|
46
|
+
bankr: {
|
|
47
|
+
defaultModel: 'claude-sonnet-4.6',
|
|
48
|
+
managed: true,
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export function getProviderDefaultModel(provider) {
|
|
53
|
+
return MODEL_CATALOG[provider]?.defaultModel || null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function getConfiguredProvider(fallback = 'openai') {
|
|
57
|
+
return getConfig('llm.provider') || fallback;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function getConfiguredModel(provider = getConfiguredProvider()) {
|
|
61
|
+
const configured = getConfig('llm.model');
|
|
62
|
+
return configured || getProviderDefaultModel(provider);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function getModelSelectionMeta(provider = getConfiguredProvider()) {
|
|
66
|
+
return MODEL_CATALOG[provider] || { defaultModel: null };
|
|
67
|
+
}
|
package/src/setup/wizard.js
CHANGED
|
@@ -8,6 +8,7 @@ import { hasSoul, runSoulSetup } from '../soul/index.js';
|
|
|
8
8
|
import { createServer } from 'http';
|
|
9
9
|
import open from 'open';
|
|
10
10
|
import crypto from 'crypto';
|
|
11
|
+
import { getModelSelectionMeta } from '../llm/models.js';
|
|
11
12
|
|
|
12
13
|
// ══════════════════════════════════════════════════
|
|
13
14
|
// FIRST-RUN SETUP WIZARD
|
|
@@ -52,11 +53,12 @@ export async function runSetupWizard(opts = {}) {
|
|
|
52
53
|
name: 'provider',
|
|
53
54
|
message: theme.gold('Choose your AI provider:'),
|
|
54
55
|
choices: [
|
|
55
|
-
{ name: '
|
|
56
|
-
{ name: '
|
|
57
|
-
{ name: '
|
|
58
|
-
{ name: '
|
|
59
|
-
{ name: '
|
|
56
|
+
{ name: 'OpenAI (GPT-4o, GPT-5) - API key or OAuth', value: 'openai' },
|
|
57
|
+
{ name: 'Anthropic (Claude Opus, Sonnet) - API key or OAuth', value: 'anthropic' },
|
|
58
|
+
{ name: 'OpenRouter (any model, one key) - API key', value: 'openrouter' },
|
|
59
|
+
{ name: 'MiniMax (MiniMax-M2.5) - API key', value: 'minimax' },
|
|
60
|
+
{ name: 'Ollama (local models, free, private) - no key needed', value: 'ollama' },
|
|
61
|
+
{ name: 'Skip for now', value: 'skip' },
|
|
60
62
|
],
|
|
61
63
|
}]);
|
|
62
64
|
|
|
@@ -112,14 +114,17 @@ export async function runSetupWizard(opts = {}) {
|
|
|
112
114
|
}
|
|
113
115
|
|
|
114
116
|
/**
|
|
115
|
-
* Setup a cloud provider (OpenAI, Anthropic, OpenRouter)
|
|
117
|
+
* Setup a cloud provider (OpenAI, Anthropic, OpenRouter, MiniMax)
|
|
116
118
|
*/
|
|
117
119
|
async function setupCloudProvider(provider) {
|
|
120
|
+
await selectAndSaveModel(provider);
|
|
121
|
+
|
|
118
122
|
const supportsOAuth = ['openai', 'anthropic'].includes(provider);
|
|
119
123
|
const providerName = {
|
|
120
124
|
openai: 'OpenAI',
|
|
121
125
|
anthropic: 'Anthropic',
|
|
122
126
|
openrouter: 'OpenRouter',
|
|
127
|
+
minimax: 'MiniMax',
|
|
123
128
|
}[provider];
|
|
124
129
|
|
|
125
130
|
if (supportsOAuth) {
|
|
@@ -156,6 +161,7 @@ async function setupAPIKey(provider) {
|
|
|
156
161
|
openai: 'OpenAI',
|
|
157
162
|
anthropic: 'Anthropic',
|
|
158
163
|
openrouter: 'OpenRouter',
|
|
164
|
+
minimax: 'MiniMax',
|
|
159
165
|
}[provider];
|
|
160
166
|
|
|
161
167
|
const { key } = await inquirer.prompt([{
|
|
@@ -173,6 +179,7 @@ async function setupAPIKey(provider) {
|
|
|
173
179
|
success(`${providerName} key saved (encrypted)`);
|
|
174
180
|
|
|
175
181
|
// Set as default provider
|
|
182
|
+
setConfig('llm.provider', provider);
|
|
176
183
|
setConfig('llmProvider', provider);
|
|
177
184
|
info(`Default AI provider set to ${provider}`);
|
|
178
185
|
}
|
|
@@ -193,22 +200,80 @@ async function setupOllama() {
|
|
|
193
200
|
default: 'http://localhost:11434',
|
|
194
201
|
}]);
|
|
195
202
|
|
|
203
|
+
setConfig('llm.ollamaHost', host);
|
|
196
204
|
setConfig('ollamaHost', host);
|
|
197
205
|
|
|
198
206
|
const { model } = await inquirer.prompt([{
|
|
199
207
|
type: 'input',
|
|
200
208
|
name: 'model',
|
|
201
209
|
message: theme.gold('Default model:'),
|
|
202
|
-
default: '
|
|
210
|
+
default: getModelSelectionMeta('ollama').defaultModel,
|
|
211
|
+
validate: (v) => v.trim().length > 0 || 'Model is required',
|
|
203
212
|
}]);
|
|
204
213
|
|
|
205
|
-
|
|
214
|
+
saveModelConfig(model.trim(), 'ollama');
|
|
215
|
+
setConfig('llm.provider', 'ollama');
|
|
206
216
|
setConfig('llmProvider', 'ollama');
|
|
207
217
|
|
|
208
218
|
success(`Ollama configured: ${host} / ${model}`);
|
|
209
219
|
info('Make sure Ollama is running: ollama serve');
|
|
210
220
|
}
|
|
211
221
|
|
|
222
|
+
async function selectAndSaveModel(provider) {
|
|
223
|
+
const meta = getModelSelectionMeta(provider);
|
|
224
|
+
if (!meta || meta.managed) return null;
|
|
225
|
+
|
|
226
|
+
if (meta.textInput) {
|
|
227
|
+
const { model } = await inquirer.prompt([{
|
|
228
|
+
type: 'input',
|
|
229
|
+
name: 'model',
|
|
230
|
+
message: theme.gold('Model:'),
|
|
231
|
+
default: meta.defaultModel,
|
|
232
|
+
validate: (v) => v.trim().length > 0 || 'Model is required',
|
|
233
|
+
}]);
|
|
234
|
+
saveModelConfig(model.trim(), provider);
|
|
235
|
+
return model.trim();
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const choices = (meta.choices || []).map((choice) => ({
|
|
239
|
+
name: `${choice.value} - ${choice.desc}`,
|
|
240
|
+
value: choice.value,
|
|
241
|
+
}));
|
|
242
|
+
|
|
243
|
+
if (meta.allowCustom) {
|
|
244
|
+
choices.push({ name: 'Custom model string', value: '__custom__' });
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const { selectedModel } = await inquirer.prompt([{
|
|
248
|
+
type: 'list',
|
|
249
|
+
name: 'selectedModel',
|
|
250
|
+
message: theme.gold('Choose model:'),
|
|
251
|
+
choices,
|
|
252
|
+
default: meta.defaultModel,
|
|
253
|
+
}]);
|
|
254
|
+
|
|
255
|
+
if (selectedModel === '__custom__') {
|
|
256
|
+
const { customModel } = await inquirer.prompt([{
|
|
257
|
+
type: 'input',
|
|
258
|
+
name: 'customModel',
|
|
259
|
+
message: theme.gold('Custom model string:'),
|
|
260
|
+
validate: (v) => v.trim().length > 0 || 'Model is required',
|
|
261
|
+
}]);
|
|
262
|
+
saveModelConfig(customModel.trim(), provider);
|
|
263
|
+
return customModel.trim();
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
saveModelConfig(selectedModel, provider);
|
|
267
|
+
return selectedModel;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function saveModelConfig(model, provider) {
|
|
271
|
+
setConfig('llm.model', model);
|
|
272
|
+
if (provider === 'ollama') {
|
|
273
|
+
setConfig('ollamaModel', model);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
212
277
|
/**
|
|
213
278
|
* Show instructions for getting API keys
|
|
214
279
|
*/
|
|
@@ -231,8 +296,14 @@ function showKeyInstructions(provider) {
|
|
|
231
296
|
console.log(theme.dim(' 3. Copy the key (starts with sk-ant-)'));
|
|
232
297
|
console.log(theme.dim(' 4. Paste it below'));
|
|
233
298
|
console.log('');
|
|
234
|
-
console.log(theme.dim('
|
|
299
|
+
console.log(theme.dim(' If you have a Claude Pro/Team subscription,'));
|
|
235
300
|
console.log(theme.dim(' you can use OAuth instead.'));
|
|
301
|
+
} else if (provider === 'minimax') {
|
|
302
|
+
showSection('GET A MINIMAX API KEY');
|
|
303
|
+
console.log(theme.dim(' 1. Go to https://platform.minimax.io/docs/guides/models-intro'));
|
|
304
|
+
console.log(theme.dim(' 2. Open your MiniMax developer console / API key page'));
|
|
305
|
+
console.log(theme.dim(' 3. Create or copy an API key'));
|
|
306
|
+
console.log(theme.dim(' 4. Paste it below'));
|
|
236
307
|
}
|
|
237
308
|
|
|
238
309
|
console.log('');
|
|
@@ -432,6 +503,7 @@ async function executeOAuthFlow(provider, clientId, clientSecret) {
|
|
|
432
503
|
if (tokenData.refresh_token) {
|
|
433
504
|
addKeyDirect(`${provider}_refresh`, tokenData.refresh_token);
|
|
434
505
|
}
|
|
506
|
+
setConfig('llm.provider', provider);
|
|
435
507
|
setConfig('llmProvider', provider);
|
|
436
508
|
|
|
437
509
|
res.writeHead(200, { 'Content-Type': 'text/html' });
|