@mcp-use/cli 1.0.0 → 1.0.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.
Files changed (54) hide show
  1. package/dist/InputPrompt.d.ts +13 -0
  2. package/dist/InputPrompt.js +188 -0
  3. package/dist/MultilineInput.d.ts +13 -0
  4. package/dist/MultilineInput.js +154 -0
  5. package/dist/MultilineTextInput.d.ts +11 -0
  6. package/dist/MultilineTextInput.js +97 -0
  7. package/dist/PasteAwareInput.d.ts +13 -0
  8. package/dist/PasteAwareInput.js +183 -0
  9. package/dist/SimpleMultilineInput.d.ts +11 -0
  10. package/dist/SimpleMultilineInput.js +125 -0
  11. package/dist/app.d.ts +1 -5
  12. package/dist/app.js +291 -186
  13. package/dist/cli.js +2 -5
  14. package/dist/commands.d.ts +15 -30
  15. package/dist/commands.js +308 -568
  16. package/dist/components/AsciiLogo.d.ts +2 -0
  17. package/dist/components/AsciiLogo.js +7 -0
  18. package/dist/components/Footer.d.ts +5 -0
  19. package/dist/components/Footer.js +19 -0
  20. package/dist/components/InputPrompt.d.ts +13 -0
  21. package/dist/components/InputPrompt.js +188 -0
  22. package/dist/components/Messages.d.ts +21 -0
  23. package/dist/components/Messages.js +80 -0
  24. package/dist/components/ServerStatus.d.ts +7 -0
  25. package/dist/components/ServerStatus.js +36 -0
  26. package/dist/components/Spinner.d.ts +16 -0
  27. package/dist/components/Spinner.js +63 -0
  28. package/dist/components/ToolStatus.d.ts +8 -0
  29. package/dist/components/ToolStatus.js +33 -0
  30. package/dist/components/textInput.d.ts +1 -0
  31. package/dist/components/textInput.js +1 -0
  32. package/dist/logger.d.ts +10 -0
  33. package/dist/logger.js +48 -0
  34. package/dist/mcp-service.d.ts +5 -4
  35. package/dist/mcp-service.js +98 -207
  36. package/dist/services/agent-service.d.ts +56 -0
  37. package/dist/services/agent-service.js +203 -0
  38. package/dist/services/cli-service.d.ts +132 -0
  39. package/dist/services/cli-service.js +591 -0
  40. package/dist/services/index.d.ts +4 -0
  41. package/dist/services/index.js +4 -0
  42. package/dist/services/llm-service.d.ts +174 -0
  43. package/dist/services/llm-service.js +567 -0
  44. package/dist/services/mcp-config-service.d.ts +69 -0
  45. package/dist/services/mcp-config-service.js +426 -0
  46. package/dist/services/mcp-service.d.ts +1 -0
  47. package/dist/services/mcp-service.js +1 -0
  48. package/dist/services/utility-service.d.ts +47 -0
  49. package/dist/services/utility-service.js +208 -0
  50. package/dist/storage.js +4 -4
  51. package/dist/types.d.ts +30 -0
  52. package/dist/types.js +1 -0
  53. package/package.json +22 -8
  54. package/readme.md +68 -39
package/dist/commands.js CHANGED
@@ -1,129 +1,20 @@
1
- import { ChatOpenAI } from '@langchain/openai';
2
- import { ChatAnthropic } from '@langchain/anthropic';
3
- import { ChatGoogleGenerativeAI } from '@langchain/google-genai';
4
- import { ChatMistralAI } from '@langchain/mistralai';
5
- import { SecureStorage } from './storage.js';
1
+ import { Logger } from './logger.js';
6
2
  export class CommandHandler {
7
- constructor() {
8
- Object.defineProperty(this, "currentLLMConfig", {
9
- enumerable: true,
10
- configurable: true,
11
- writable: true,
12
- value: null
13
- });
14
- Object.defineProperty(this, "sessionApiKeys", {
15
- enumerable: true,
16
- configurable: true,
17
- writable: true,
18
- value: {}
19
- });
20
- Object.defineProperty(this, "persistentConfig", {
3
+ constructor(deps) {
4
+ Object.defineProperty(this, "llmService", {
21
5
  enumerable: true,
22
6
  configurable: true,
23
7
  writable: true,
24
8
  value: void 0
25
9
  });
26
- Object.defineProperty(this, "sessionServers", {
27
- enumerable: true,
28
- configurable: true,
29
- writable: true,
30
- value: {}
31
- });
32
- Object.defineProperty(this, "availableModels", {
10
+ Object.defineProperty(this, "mcpService", {
33
11
  enumerable: true,
34
12
  configurable: true,
35
13
  writable: true,
36
- value: {
37
- openai: [
38
- 'gpt-4o',
39
- 'gpt-4o-mini',
40
- 'gpt-4-turbo',
41
- 'gpt-4',
42
- 'gpt-3.5-turbo'
43
- ],
44
- anthropic: [
45
- 'claude-3-5-sonnet-20241022',
46
- 'claude-3-5-haiku-20241022',
47
- 'claude-3-opus-20240229',
48
- 'claude-3-sonnet-20240229',
49
- 'claude-3-haiku-20240307'
50
- ],
51
- google: [
52
- 'gemini-1.5-pro',
53
- 'gemini-1.5-flash',
54
- 'gemini-pro'
55
- ],
56
- mistral: [
57
- 'mistral-large-latest',
58
- 'mistral-medium-latest',
59
- 'mistral-small-latest'
60
- ]
61
- }
14
+ value: void 0
62
15
  });
63
- // Load persistent config
64
- this.persistentConfig = SecureStorage.loadConfig();
65
- // Auto-detect available provider and set default
66
- this.initializeDefaultProvider();
67
- }
68
- initializeDefaultProvider() {
69
- // First, try to load the last used model from persistent config
70
- if (this.persistentConfig.lastModel) {
71
- const lastModel = this.persistentConfig.lastModel;
72
- const envVar = {
73
- openai: 'OPENAI_API_KEY',
74
- anthropic: 'ANTHROPIC_API_KEY',
75
- google: 'GOOGLE_API_KEY',
76
- mistral: 'MISTRAL_API_KEY'
77
- }[lastModel.provider];
78
- // Check if we still have the API key for the last used model
79
- if (envVar && this.getApiKey(envVar)) {
80
- this.currentLLMConfig = {
81
- provider: lastModel.provider,
82
- model: lastModel.model,
83
- temperature: lastModel.temperature || 0.7,
84
- maxTokens: lastModel.maxTokens
85
- };
86
- return;
87
- }
88
- }
89
- const providers = [
90
- { name: 'openai', envVar: 'OPENAI_API_KEY', defaultModel: 'gpt-4o-mini' },
91
- { name: 'anthropic', envVar: 'ANTHROPIC_API_KEY', defaultModel: 'claude-3-5-sonnet-20241022' },
92
- { name: 'google', envVar: 'GOOGLE_API_KEY', defaultModel: 'gemini-1.5-pro' },
93
- { name: 'mistral', envVar: 'MISTRAL_API_KEY', defaultModel: 'mistral-large-latest' }
94
- ];
95
- // Find first available provider
96
- for (const provider of providers) {
97
- if (this.getApiKey(provider.envVar)) {
98
- this.currentLLMConfig = {
99
- provider: provider.name,
100
- model: provider.defaultModel,
101
- temperature: 0.7
102
- };
103
- return;
104
- }
105
- }
106
- // No provider found - leave null, will show setup message
107
- this.currentLLMConfig = null;
108
- }
109
- getAvailableProviders() {
110
- const providers = [];
111
- if (this.getApiKey('OPENAI_API_KEY'))
112
- providers.push('openai');
113
- if (this.getApiKey('ANTHROPIC_API_KEY'))
114
- providers.push('anthropic');
115
- if (this.getApiKey('GOOGLE_API_KEY'))
116
- providers.push('google');
117
- if (this.getApiKey('MISTRAL_API_KEY'))
118
- providers.push('mistral');
119
- return providers;
120
- }
121
- getApiKey(envVar) {
122
- // Check session keys first, then persistent storage, then environment variables
123
- return this.sessionApiKeys[envVar] || this.persistentConfig.apiKeys[envVar] || process.env[envVar];
124
- }
125
- isAnyProviderAvailable() {
126
- return this.getAvailableProviders().length > 0;
16
+ this.llmService = deps.llmService;
17
+ this.mcpService = deps.mcpService;
127
18
  }
128
19
  async handleCommand(input) {
129
20
  const parts = input.trim().split(/\s+/);
@@ -152,10 +43,16 @@ export class CommandHandler {
152
43
  return this.handleListTools();
153
44
  case '/test-server':
154
45
  return this.handleTestServer(args);
46
+ case '/logs':
47
+ return this.handleLogs(args);
48
+ case '/clearlogs':
49
+ return this.handleClearLogs();
50
+ case '/history':
51
+ return this.handleHistory();
155
52
  default:
156
53
  return {
157
54
  type: 'error',
158
- message: `Unknown command: ${command}. Type /help for available commands.`
55
+ message: `Unknown command: ${command}. Type /help for available commands.`,
159
56
  };
160
57
  }
161
58
  }
@@ -185,6 +82,11 @@ Available slash commands:
185
82
  /config tokens <value> - Set max tokens
186
83
  /help - Show this help
187
84
 
85
+ šŸ› ļø Debugging & History:
86
+ /logs [path|tail] - View debug logs (written to ~/.mcp-use-cli/debug.log)
87
+ /clearlogs - Clear debug logs
88
+ /history - Info about input history navigation (↑↓ arrows)
89
+
188
90
  šŸ“‹ Quick Start Examples:
189
91
  /model openai gpt-4o-mini
190
92
  /server add # Interactive server setup
@@ -193,142 +95,123 @@ Available slash commands:
193
95
  `.trim();
194
96
  return {
195
97
  type: 'info',
196
- message: helpText
98
+ message: helpText,
197
99
  };
198
100
  }
199
101
  handleModel(args) {
200
102
  if (args.length < 2) {
201
- const availableProviders = this.getAvailableProviders();
103
+ const availableProviders = this.llmService.getAvailableProviders();
202
104
  if (availableProviders.length === 0) {
203
105
  return {
204
106
  type: 'info',
205
- message: 'Usage: /model <provider> <model>\n\nPopular models to try:\n• /model openai gpt-4o-mini\n• /model anthropic claude-3-5-sonnet-20241022\n• /model google gemini-1.5-pro\n\nThe CLI will prompt for your API key when needed.\nUse /models to see all available options.'
107
+ message: 'Usage: /model <provider> <model>\n\nPopular models to try:\n• /model openai gpt-4o-mini\n• /model anthropic claude-3-5-sonnet-20241022\n• /model google gemini-1.5-pro\n\nThe CLI will prompt for your API key when needed.\nUse /models to see all available options.',
206
108
  };
207
109
  }
208
110
  return {
209
111
  type: 'error',
210
- message: `Usage: /model <provider> <model>\nExample: /model openai gpt-4o\n\nAvailable providers: ${availableProviders.join(', ')}`
112
+ message: `Usage: /model <provider> <model>\nExample: /model openai gpt-4o\n\nAvailable providers: ${availableProviders.join(', ')}`,
211
113
  };
212
114
  }
213
115
  const provider = args[0];
214
116
  const model = args[1];
215
- if (!provider || !this.availableModels[provider]) {
117
+ if (!provider || !model) {
216
118
  return {
217
119
  type: 'error',
218
- message: `Unknown provider: ${provider}\nAvailable providers: ${Object.keys(this.availableModels).join(', ')}`
120
+ message: 'Both provider and model are required',
219
121
  };
220
122
  }
221
- // Check if the provider's API key is available
222
- const availableProviders = this.getAvailableProviders();
223
- if (!availableProviders.includes(provider)) {
224
- const envVarMap = {
225
- openai: 'OPENAI_API_KEY',
226
- anthropic: 'ANTHROPIC_API_KEY',
227
- google: 'GOOGLE_API_KEY',
228
- mistral: 'MISTRAL_API_KEY'
229
- };
230
- // Prompt for API key instead of showing error
123
+ const availableModels = this.llmService.getAvailableModels();
124
+ if (!availableModels[provider]) {
231
125
  return {
232
- type: 'prompt_key',
233
- message: `Please enter your ${provider.toUpperCase()} API key:`,
234
- data: {
235
- provider,
236
- model,
237
- envVar: envVarMap[provider]
238
- }
126
+ type: 'error',
127
+ message: `Unknown provider: ${provider}\nAvailable providers: ${Object.keys(availableModels).join(', ')}`,
239
128
  };
240
129
  }
241
- if (!model || !this.availableModels[provider].includes(model)) {
130
+ // Try to set the model
131
+ const result = this.llmService.setModel(provider, model);
132
+ if (!result.success) {
133
+ if (result.requiresApiKey) {
134
+ // Prompt for API key instead of showing error
135
+ return {
136
+ type: 'prompt_key',
137
+ message: `Please enter your ${provider.toUpperCase()} API key:`,
138
+ data: {
139
+ provider,
140
+ model,
141
+ envVar: result.envVar,
142
+ },
143
+ };
144
+ }
242
145
  return {
243
146
  type: 'error',
244
- message: `Model ${model} not available for ${provider}\nUse /models ${provider} to see available models`
147
+ message: result.message,
245
148
  };
246
149
  }
247
- this.currentLLMConfig = {
248
- provider,
249
- model,
250
- temperature: this.currentLLMConfig?.temperature || 0.7,
251
- maxTokens: this.currentLLMConfig?.maxTokens
252
- };
253
- // Save the current model to persistent storage
254
- this.persistentConfig.lastModel = this.currentLLMConfig;
255
- SecureStorage.saveConfig(this.persistentConfig);
256
150
  return {
257
151
  type: 'success',
258
- message: `āœ… Switched to ${provider} ${model}`,
259
- data: { llmConfig: this.currentLLMConfig }
152
+ message: `āœ… ${result.message}`,
153
+ data: { llmConfig: this.llmService.getCurrentConfig() },
260
154
  };
261
155
  }
262
156
  handleListModels(args) {
157
+ const currentConfig = this.llmService.getCurrentConfig();
263
158
  if (args.length === 0) {
264
159
  let modelList = 'šŸ“‹ Available models by provider:\n\n';
265
- Object.entries(this.availableModels).forEach(([provider, models]) => {
160
+ const availableModels = this.llmService.getAvailableModels();
161
+ Object.entries(availableModels).forEach(([provider, models]) => {
266
162
  modelList += `šŸ”ø ${provider}:\n`;
267
163
  models.forEach(model => {
268
- const current = provider === this.currentLLMConfig?.provider && model === this.currentLLMConfig?.model ? ' ← current' : '';
164
+ const current = provider === currentConfig?.provider &&
165
+ model === currentConfig?.model
166
+ ? ' ← current'
167
+ : '';
269
168
  modelList += ` • ${model}${current}\n`;
270
169
  });
271
170
  modelList += '\n';
272
171
  });
273
172
  return {
274
173
  type: 'info',
275
- message: modelList.trim()
174
+ message: modelList.trim(),
276
175
  };
277
176
  }
278
177
  const provider = args[0];
279
- if (!this.availableModels[provider]) {
178
+ if (!provider) {
280
179
  return {
281
180
  type: 'error',
282
- message: `Unknown provider: ${provider}\nAvailable providers: ${Object.keys(this.availableModels).join(', ')}`
181
+ message: 'Provider is required',
182
+ };
183
+ }
184
+ const availableModels = this.llmService.getAvailableModels();
185
+ if (!availableModels[provider]) {
186
+ return {
187
+ type: 'error',
188
+ message: `Unknown provider: ${provider}\nAvailable providers: ${Object.keys(availableModels).join(', ')}`,
283
189
  };
284
190
  }
285
191
  let modelList = `šŸ“‹ Available ${provider} models:\n\n`;
286
- this.availableModels[provider].forEach(model => {
287
- const current = provider === this.currentLLMConfig?.provider && model === this.currentLLMConfig?.model ? ' ← current' : '';
192
+ const models = this.llmService.getAvailableModels(provider);
193
+ models.forEach(model => {
194
+ const current = provider === currentConfig?.provider && model === currentConfig?.model
195
+ ? ' ← current'
196
+ : '';
288
197
  modelList += `• ${model}${current}\n`;
289
198
  });
199
+ modelList += `\n Don't see your model/provider? Submit a PR to add it at https://github.com/mcp-use/mcp-use-cli/`;
290
200
  return {
291
201
  type: 'info',
292
- message: modelList.trim()
202
+ message: modelList.trim(),
293
203
  };
294
204
  }
295
205
  handleStatus() {
296
- const availableProviders = this.getAvailableProviders();
206
+ const availableProviders = this.llmService.getAvailableProviders();
207
+ const currentConfig = this.llmService.getCurrentConfig();
208
+ const apiKeyStatus = this.llmService.getApiKeyStatus();
297
209
  let statusText = 'šŸ¤– Current Configuration:\n\n';
298
210
  // API Keys status
299
211
  statusText += 'šŸ”‘ API Keys:\n';
300
- const allProviders = ['openai', 'anthropic', 'google', 'mistral'];
301
- allProviders.forEach(provider => {
302
- const envVarMap = {
303
- openai: 'OPENAI_API_KEY',
304
- anthropic: 'ANTHROPIC_API_KEY',
305
- google: 'GOOGLE_API_KEY',
306
- mistral: 'MISTRAL_API_KEY'
307
- };
308
- const envVar = envVarMap[provider];
309
- const hasEnvKey = !!process.env[envVar];
310
- const hasSessionKey = !!this.sessionApiKeys[envVar];
311
- const hasPersistentKey = !!this.persistentConfig.apiKeys[envVar];
312
- if (hasSessionKey) {
313
- const key = this.sessionApiKeys[envVar];
314
- if (key) {
315
- const maskedKey = this.maskApiKey(key);
316
- statusText += ` • ${provider}: ${maskedKey} (session)\n`;
317
- }
318
- }
319
- else if (hasPersistentKey) {
320
- const key = this.persistentConfig.apiKeys[envVar];
321
- if (key) {
322
- const maskedKey = this.maskApiKey(key);
323
- statusText += ` • ${provider}: ${maskedKey} (stored)\n`;
324
- }
325
- }
326
- else if (hasEnvKey) {
327
- const key = process.env[envVar];
328
- if (key) {
329
- const maskedKey = this.maskApiKey(key);
330
- statusText += ` • ${provider}: ${maskedKey} (env)\n`;
331
- }
212
+ Object.entries(apiKeyStatus).forEach(([provider, status]) => {
213
+ if (status.status === 'set') {
214
+ statusText += ` • ${provider}: ${status.masked} (${status.source})\n`;
332
215
  }
333
216
  else {
334
217
  statusText += ` • ${provider}: āŒ not set\n`;
@@ -336,7 +219,7 @@ Available slash commands:
336
219
  });
337
220
  statusText += '\n';
338
221
  // Current model
339
- if (!this.currentLLMConfig) {
222
+ if (!currentConfig) {
340
223
  if (availableProviders.length === 0) {
341
224
  statusText += 'āš ļø No model selected\n';
342
225
  statusText += '\nChoose a model to get started:\n';
@@ -352,24 +235,24 @@ Available slash commands:
352
235
  }
353
236
  }
354
237
  else {
355
- const config = this.currentLLMConfig;
356
238
  statusText += `šŸŽÆ Active Model:\n`;
357
- statusText += ` Provider: ${config.provider}\n`;
358
- statusText += ` Model: ${config.model}\n`;
359
- statusText += ` Temperature: ${config.temperature || 0.7}\n`;
360
- statusText += ` Max Tokens: ${config.maxTokens || 'default'}\n`;
361
- statusText += '\nUse /model to switch models or /config to adjust settings';
239
+ statusText += ` Provider: ${currentConfig.provider}\n`;
240
+ statusText += ` Model: ${currentConfig.model}\n`;
241
+ statusText += ` Temperature: ${currentConfig.temperature || 0.7}\n`;
242
+ statusText += ` Max Tokens: ${currentConfig.maxTokens || 'default'}\n`;
243
+ statusText +=
244
+ '\nUse /model to switch models or /config to adjust settings';
362
245
  }
363
246
  return {
364
247
  type: 'info',
365
- message: statusText
248
+ message: statusText,
366
249
  };
367
250
  }
368
251
  handleConfig(args) {
369
252
  if (args.length < 2) {
370
253
  return {
371
254
  type: 'error',
372
- message: 'Usage: /config <setting> <value>\nAvailable settings: temp, tokens'
255
+ message: 'Usage: /config <setting> <value>\nAvailable settings: temp, tokens',
373
256
  };
374
257
  }
375
258
  const setting = args[0];
@@ -377,50 +260,62 @@ Available slash commands:
377
260
  if (!value) {
378
261
  return {
379
262
  type: 'error',
380
- message: 'Value is required'
263
+ message: 'Value is required',
381
264
  };
382
265
  }
383
- if (!this.currentLLMConfig) {
266
+ if (!this.llmService.getCurrentConfig()) {
384
267
  return {
385
268
  type: 'error',
386
- message: 'No model configured. Use /model to select a provider and model first.'
269
+ message: 'No model configured. Use /model to select a provider and model first.',
387
270
  };
388
271
  }
389
272
  switch (setting) {
390
273
  case 'temp':
391
274
  case 'temperature':
392
275
  const temp = parseFloat(value);
393
- if (isNaN(temp) || temp < 0 || temp > 2) {
276
+ if (isNaN(temp)) {
277
+ return {
278
+ type: 'error',
279
+ message: 'Temperature must be a number',
280
+ };
281
+ }
282
+ const tempResult = this.llmService.setTemperature(temp);
283
+ if (!tempResult.success) {
394
284
  return {
395
285
  type: 'error',
396
- message: 'Temperature must be a number between 0.0 and 2.0'
286
+ message: tempResult.message,
397
287
  };
398
288
  }
399
- this.currentLLMConfig.temperature = temp;
400
289
  return {
401
290
  type: 'success',
402
- message: `āœ… Temperature set to ${temp}`,
403
- data: { llmConfig: this.currentLLMConfig }
291
+ message: `āœ… ${tempResult.message}`,
292
+ data: { llmConfig: this.llmService.getCurrentConfig() },
404
293
  };
405
294
  case 'tokens':
406
295
  case 'max-tokens':
407
296
  const tokens = parseInt(value);
408
- if (isNaN(tokens) || tokens < 1) {
297
+ if (isNaN(tokens)) {
409
298
  return {
410
299
  type: 'error',
411
- message: 'Max tokens must be a positive integer'
300
+ message: 'Max tokens must be a number',
301
+ };
302
+ }
303
+ const tokensResult = this.llmService.setMaxTokens(tokens);
304
+ if (!tokensResult.success) {
305
+ return {
306
+ type: 'error',
307
+ message: tokensResult.message,
412
308
  };
413
309
  }
414
- this.currentLLMConfig.maxTokens = tokens;
415
310
  return {
416
311
  type: 'success',
417
- message: `āœ… Max tokens set to ${tokens}`,
418
- data: { llmConfig: this.currentLLMConfig }
312
+ message: `āœ… ${tokensResult.message}`,
313
+ data: { llmConfig: this.llmService.getCurrentConfig() },
419
314
  };
420
315
  default:
421
316
  return {
422
317
  type: 'error',
423
- message: `Unknown setting: ${setting}\nAvailable settings: temp, tokens`
318
+ message: `Unknown setting: ${setting}\nAvailable settings: temp, tokens`,
424
319
  };
425
320
  }
426
321
  }
@@ -428,7 +323,7 @@ Available slash commands:
428
323
  if (args.length < 2) {
429
324
  return {
430
325
  type: 'error',
431
- message: 'Usage: /setkey <provider> <api_key>\n\nSupported providers: openai, anthropic, google, mistral\n\nExample:\n/setkey openai sk-1234567890abcdef...'
326
+ message: 'Usage: /setkey <provider> <api_key>\n\nSupported providers: openai, anthropic, google, mistral\n\nExample:\n/setkey openai sk-1234567890abcdef...',
432
327
  };
433
328
  }
434
329
  const provider = args[0]?.toLowerCase();
@@ -436,7 +331,7 @@ Available slash commands:
436
331
  if (!provider || !apiKey) {
437
332
  return {
438
333
  type: 'error',
439
- message: 'Both provider and API key are required'
334
+ message: 'Both provider and API key are required',
440
335
  };
441
336
  }
442
337
  // Validate provider
@@ -444,216 +339,97 @@ Available slash commands:
444
339
  if (!validProviders.includes(provider)) {
445
340
  return {
446
341
  type: 'error',
447
- message: `Invalid provider: ${provider}\nSupported providers: ${validProviders.join(', ')}`
342
+ message: `Invalid provider: ${provider}\nSupported providers: ${validProviders.join(', ')}`,
448
343
  };
449
344
  }
450
- // Basic API key validation
451
- const validationResult = this.validateApiKey(provider, apiKey);
452
- if (!validationResult.valid) {
345
+ // Check if we should auto-select this provider
346
+ const shouldAutoSelect = !this.llmService.getCurrentConfig();
347
+ // Set the API key
348
+ const result = this.llmService.setApiKey(provider, apiKey, shouldAutoSelect);
349
+ if (!result.success) {
453
350
  return {
454
351
  type: 'error',
455
- message: validationResult.message
456
- };
457
- }
458
- // Map provider to environment variable name
459
- const envVarMap = {
460
- openai: 'OPENAI_API_KEY',
461
- anthropic: 'ANTHROPIC_API_KEY',
462
- google: 'GOOGLE_API_KEY',
463
- mistral: 'MISTRAL_API_KEY'
464
- };
465
- const envVar = envVarMap[provider];
466
- // Store the API key in persistent storage
467
- this.persistentConfig.apiKeys[envVar] = apiKey;
468
- SecureStorage.saveConfig(this.persistentConfig);
469
- // Check if we can auto-select this provider
470
- const shouldAutoSelect = !this.currentLLMConfig;
471
- if (shouldAutoSelect) {
472
- const defaultModels = {
473
- openai: 'gpt-4o-mini',
474
- anthropic: 'claude-3-5-sonnet-20241022',
475
- google: 'gemini-1.5-pro',
476
- mistral: 'mistral-large-latest'
477
- };
478
- this.currentLLMConfig = {
479
- provider: provider,
480
- model: defaultModels[provider],
481
- temperature: 0.7
352
+ message: result.message,
482
353
  };
483
354
  }
484
- const maskedKey = this.maskApiKey(apiKey);
355
+ const maskedKey = this.llmService.maskApiKey(apiKey);
485
356
  let message = `āœ… ${provider} API key set (${maskedKey})`;
486
- if (shouldAutoSelect) {
487
- message += `\nšŸ¤– Auto-selected ${this.currentLLMConfig.provider}/${this.currentLLMConfig.model}`;
357
+ if (result.autoSelected) {
358
+ message += `\nšŸ¤– Auto-selected ${result.autoSelected.provider}/${result.autoSelected.model}`;
488
359
  }
489
360
  return {
490
361
  type: 'success',
491
362
  message,
492
- data: shouldAutoSelect ? { llmConfig: this.currentLLMConfig } : undefined
363
+ data: result.autoSelected ? { llmConfig: result.autoSelected } : undefined,
493
364
  };
494
365
  }
495
- validateApiKey(provider, apiKey) {
496
- if (!apiKey || apiKey.trim().length === 0) {
497
- return { valid: false, message: 'API key cannot be empty' };
498
- }
499
- // Basic format validation for each provider
500
- switch (provider) {
501
- case 'openai':
502
- if (!apiKey.startsWith('sk-') || apiKey.length < 20) {
503
- return { valid: false, message: 'OpenAI API keys should start with "sk-" and be at least 20 characters long' };
504
- }
505
- break;
506
- case 'anthropic':
507
- if (!apiKey.startsWith('ant_') && !apiKey.startsWith('sk-ant-') || apiKey.length < 20) {
508
- return { valid: false, message: 'Anthropic API keys should start with "ant_" or "sk-ant-" and be at least 20 characters long' };
509
- }
510
- break;
511
- case 'google':
512
- if (apiKey.length < 20) {
513
- return { valid: false, message: 'Google API keys should be at least 20 characters long' };
514
- }
515
- break;
516
- case 'mistral':
517
- if (apiKey.length < 20) {
518
- return { valid: false, message: 'Mistral API keys should be at least 20 characters long' };
519
- }
520
- break;
521
- }
522
- return { valid: true, message: '' };
523
- }
524
- maskApiKey(apiKey) {
525
- if (apiKey.length <= 8) {
526
- return '*'.repeat(apiKey.length);
527
- }
528
- const start = apiKey.substring(0, 4);
529
- const end = apiKey.substring(apiKey.length - 4);
530
- const middle = '*'.repeat(Math.min(12, apiKey.length - 8));
531
- return `${start}${middle}${end}`;
532
- }
533
366
  createLLM() {
534
- if (!this.currentLLMConfig) {
535
- throw new Error('No LLM configured. Use /model command to select a provider and model.');
536
- }
537
- const config = this.currentLLMConfig;
538
- const baseConfig = {
539
- temperature: config.temperature || 0.7,
540
- maxTokens: config.maxTokens
541
- };
542
- switch (config.provider) {
543
- case 'openai':
544
- const openaiKey = this.getApiKey('OPENAI_API_KEY');
545
- if (!openaiKey) {
546
- throw new Error('OPENAI_API_KEY is required for OpenAI models. Use /setkey openai <your-key>');
547
- }
548
- return new ChatOpenAI({
549
- modelName: config.model,
550
- openAIApiKey: openaiKey,
551
- ...baseConfig
552
- });
553
- case 'anthropic':
554
- const anthropicKey = this.getApiKey('ANTHROPIC_API_KEY');
555
- if (!anthropicKey) {
556
- throw new Error('ANTHROPIC_API_KEY is required for Anthropic models. Use /setkey anthropic <your-key>');
557
- }
558
- return new ChatAnthropic({
559
- modelName: config.model,
560
- anthropicApiKey: anthropicKey,
561
- ...baseConfig
562
- });
563
- case 'google':
564
- const googleKey = this.getApiKey('GOOGLE_API_KEY');
565
- if (!googleKey) {
566
- throw new Error('GOOGLE_API_KEY is required for Google models. Use /setkey google <your-key>');
567
- }
568
- return new ChatGoogleGenerativeAI({
569
- model: config.model,
570
- apiKey: googleKey,
571
- ...baseConfig
572
- });
573
- case 'mistral':
574
- const mistralKey = this.getApiKey('MISTRAL_API_KEY');
575
- if (!mistralKey) {
576
- throw new Error('MISTRAL_API_KEY is required for Mistral models. Use /setkey mistral <your-key>');
577
- }
578
- return new ChatMistralAI({
579
- modelName: config.model,
580
- apiKey: mistralKey,
581
- ...baseConfig
582
- });
583
- default:
584
- throw new Error(`Unsupported provider: ${config.provider}`);
585
- }
367
+ return this.llmService.createLLM();
586
368
  }
587
369
  getCurrentConfig() {
588
- return this.currentLLMConfig ? { ...this.currentLLMConfig } : null;
370
+ return this.llmService.getCurrentConfig();
589
371
  }
590
372
  getCurrentStoredConfig() {
591
- return this.persistentConfig;
373
+ // Return a minimal StoredConfig for backward compatibility
374
+ return {
375
+ apiKeys: {},
376
+ mcpServers: this.mcpService.getConfiguredServers(),
377
+ lastModel: this.llmService.getCurrentConfig(),
378
+ };
592
379
  }
593
380
  getSessionServers() {
594
- return this.sessionServers;
381
+ return this.mcpService.getSessionServers();
595
382
  }
596
383
  handleListTools() {
597
384
  return {
598
385
  type: 'info',
599
386
  message: 'šŸ”§ Checking available MCP tools...\n\nThis command will show tools available from connected MCP servers.\nNote: This requires the MCP service to provide tool listing functionality.',
600
- data: { checkTools: true }
387
+ data: { checkTools: true },
601
388
  };
602
389
  }
603
390
  handleTestServer(args) {
604
391
  if (args.length === 0) {
605
- const configuredServers = Object.keys(this.persistentConfig.mcpServers || {});
392
+ const configuredServers = Object.keys(this.mcpService.getConfiguredServers());
606
393
  if (configuredServers.length === 0) {
607
394
  return {
608
395
  type: 'error',
609
- message: 'No servers configured to test.\n\nUsage: /test-server <server_name>\n\nUse /server add to configure servers first.'
396
+ message: 'No servers configured to test.\n\nUsage: /test-server <server_name>\n\nUse /server add to configure servers first.',
610
397
  };
611
398
  }
612
399
  return {
613
400
  type: 'info',
614
- message: `Usage: /test-server <server_name>\n\nConfigured servers: ${configuredServers.join(', ')}\n\nThis command will test if the server package can be started manually.`
401
+ message: `Usage: /test-server <server_name>\n\nConfigured servers: ${configuredServers.join(', ')}\n\nThis command will test if the server package can be started manually.`,
615
402
  };
616
403
  }
617
404
  const serverName = args[0];
618
405
  if (!serverName) {
619
406
  return {
620
407
  type: 'error',
621
- message: 'Server name is required.\n\nUsage: /test-server <server_name>'
408
+ message: 'Server name is required.\n\nUsage: /test-server <server_name>',
622
409
  };
623
410
  }
624
- const serverConfig = this.persistentConfig.mcpServers?.[serverName];
625
- if (!serverConfig) {
626
- const configuredServers = Object.keys(this.persistentConfig.mcpServers || {});
411
+ const result = this.mcpService.getServerTestCommand(serverName);
412
+ if (!result.success) {
627
413
  return {
628
414
  type: 'error',
629
- message: `Server "${serverName}" is not configured.\n\nConfigured servers: ${configuredServers.length > 0 ? configuredServers.join(', ') : 'none'}`
415
+ message: result.message,
630
416
  };
631
417
  }
632
- const command = serverConfig.command;
633
- const args_str = serverConfig.args ? serverConfig.args.join(' ') : '';
634
- const full_command = `${command} ${args_str}`.trim();
635
418
  return {
636
419
  type: 'info',
637
- message: `🧪 Testing server "${serverName}"...\n\nCommand: ${full_command}\n\nāš ļø Note: This will attempt to run the server command manually.\nCheck the console for output and errors.\n\nšŸ’” Try running this command manually in your terminal:\n${full_command}`,
638
- data: { testServer: true, serverName, command: full_command }
420
+ message: `🧪 Testing server "${serverName}"...\n\nCommand: ${result.command}\n\nāš ļø Note: This will attempt to run the server command manually.\nCheck the console for output and errors.\n\nšŸ’” Try running this command manually in your terminal:\n${result.command}`,
421
+ data: { testServer: true, serverName, command: result.command },
639
422
  };
640
423
  }
641
424
  isCommand(input) {
642
425
  return input.trim().startsWith('/');
643
426
  }
644
427
  handleClearKeys() {
645
- // Clear both session and persistent keys
646
- this.sessionApiKeys = {};
647
- this.persistentConfig.apiKeys = {};
648
- this.persistentConfig.lastModel = undefined;
649
- // Clear the current LLM config since we have no keys
650
- this.currentLLMConfig = null;
651
- // Save the cleared config
652
- SecureStorage.saveConfig(this.persistentConfig);
428
+ this.llmService.clearApiKeys();
653
429
  return {
654
430
  type: 'success',
655
431
  message: 'āœ… All API keys cleared from storage.\n\nUse /setkey or /model to set up a new provider.',
656
- data: { llmConfig: null }
432
+ data: { llmConfig: null },
657
433
  };
658
434
  }
659
435
  // Method to handle server configuration input
@@ -663,181 +439,118 @@ Available slash commands:
663
439
  case 'name_or_json':
664
440
  // Check if input looks like JSON
665
441
  const trimmedInput = input.trim();
666
- if (trimmedInput.startsWith('{') && trimmedInput.includes('mcpServers')) {
667
- try {
668
- const parsedConfig = JSON.parse(trimmedInput);
669
- // Validate JSON structure
670
- if (!parsedConfig.mcpServers || typeof parsedConfig.mcpServers !== 'object') {
671
- return {
672
- type: 'error',
673
- message: 'Invalid JSON format. Expected format:\n{\n "mcpServers": {\n "servername": {\n "command": "...",\n "args": [...]\n }\n }\n}'
674
- };
675
- }
676
- const servers = parsedConfig.mcpServers;
677
- const serverNames = Object.keys(servers);
678
- if (serverNames.length === 0) {
679
- return {
680
- type: 'error',
681
- message: 'No servers found in JSON configuration.'
682
- };
683
- }
684
- // Check for conflicts with existing servers
685
- const existingServers = this.persistentConfig.mcpServers || {};
686
- const conflicts = serverNames.filter(name => existingServers[name]);
687
- if (conflicts.length > 0) {
688
- return {
689
- type: 'error',
690
- message: `Server(s) already exist: ${conflicts.join(', ')}. Please use different names.`
691
- };
692
- }
693
- // Validate each server config
694
- for (const [name, serverConfig] of Object.entries(servers)) {
695
- const server = serverConfig;
696
- if (!server.command || typeof server.command !== 'string') {
697
- return {
698
- type: 'error',
699
- message: `Server "${name}" missing required "command" field.`
700
- };
701
- }
702
- }
703
- // All validation passed, save the servers
704
- if (!this.persistentConfig.mcpServers) {
705
- this.persistentConfig.mcpServers = {};
706
- }
707
- // Add all servers from JSON
708
- Object.assign(this.persistentConfig.mcpServers, servers);
709
- SecureStorage.saveConfig(this.persistentConfig);
710
- // Auto-connect all newly configured servers
711
- Object.assign(this.sessionServers, servers);
712
- const addedCount = serverNames.length;
713
- const serverList = serverNames.map(name => `• ${name}`).join('\n');
714
- return {
715
- type: 'success',
716
- message: `āœ… Configured and connected ${addedCount} server(s)!\n\n${serverList}\n\nšŸ”„ Agent will be reinitialized with these servers - attempting to establish connections...\nUse /tools to verify the server tools are available.`,
717
- data: { serversAdded: true, serverConnected: true, serverNames }
718
- };
719
- }
720
- catch (error) {
442
+ if (trimmedInput.startsWith('{') &&
443
+ trimmedInput.includes('mcpServers')) {
444
+ const result = this.mcpService.addServerFromJSON(trimmedInput);
445
+ if (!result.success) {
721
446
  return {
722
447
  type: 'error',
723
- message: `Invalid JSON format: ${error instanceof Error ? error.message : 'Parse error'}\n\nPlease check your JSON syntax and try again.`
448
+ message: result.message,
724
449
  };
725
450
  }
726
- }
727
- // Not JSON, treat as server name for interactive setup
728
- if (!trimmedInput) {
729
451
  return {
730
- type: 'error',
731
- message: 'Server name cannot be empty.'
452
+ type: 'success',
453
+ message: `${result.message}.`,
454
+ data: result.data,
732
455
  };
733
456
  }
734
- // Check if server name already exists
735
- if (this.persistentConfig.mcpServers?.[trimmedInput]) {
457
+ // Not JSON, treat as server name for interactive setup
458
+ const validation = this.mcpService.validateServerName(trimmedInput);
459
+ if (!validation.valid) {
736
460
  return {
737
461
  type: 'error',
738
- message: `Server "${trimmedInput}" already exists. Use a different name.`
462
+ message: validation.message,
739
463
  };
740
464
  }
741
465
  config.name = trimmedInput;
742
466
  return {
743
467
  type: 'prompt_server_config',
744
468
  message: `Server name: ${config.name}\n\nEnter the command to run this server (e.g., "npx", "node", "python"):`,
745
- data: { step: 'command', config }
469
+ data: { step: 'command', config },
746
470
  };
747
471
  case 'name':
748
- if (!input.trim()) {
749
- return {
750
- type: 'error',
751
- message: 'Server name cannot be empty.'
752
- };
753
- }
754
- // Check if server name already exists
755
- if (this.persistentConfig.mcpServers?.[input.trim()]) {
472
+ const nameValidation = this.mcpService.validateServerName(input.trim());
473
+ if (!nameValidation.valid) {
756
474
  return {
757
475
  type: 'error',
758
- message: `Server "${input.trim()}" already exists. Use a different name.`
476
+ message: nameValidation.message,
759
477
  };
760
478
  }
761
479
  config.name = input.trim();
762
480
  return {
763
481
  type: 'prompt_server_config',
764
482
  message: `Server name: ${config.name}\n\nEnter the command to run this server (e.g., "npx", "node", "python"):`,
765
- data: { step: 'command', config }
483
+ data: { step: 'command', config },
766
484
  };
767
485
  case 'command':
768
486
  if (!input.trim()) {
769
487
  return {
770
488
  type: 'error',
771
- message: 'Command cannot be empty.'
489
+ message: 'Command cannot be empty.',
772
490
  };
773
491
  }
774
492
  config.command = input.trim();
775
493
  return {
776
494
  type: 'prompt_server_config',
777
495
  message: `Server name: ${config.name}\nCommand: ${config.command}\n\nEnter arguments (space-separated, or press Enter for none):\nExample: "-y @modelcontextprotocol/server-filesystem /tmp"`,
778
- data: { step: 'args', config }
496
+ data: { step: 'args', config },
779
497
  };
780
498
  case 'args':
781
499
  config.args = input.trim() ? input.trim().split(/\s+/) : [];
782
500
  return {
783
501
  type: 'prompt_server_config',
784
502
  message: `Server name: ${config.name}\nCommand: ${config.command}\nArgs: ${config.args.length > 0 ? config.args.join(' ') : 'none'}\n\nEnter environment variables (KEY=VALUE format, one per line, or press Enter for none):\nExample: "DEBUG=1" or press Enter to skip:`,
785
- data: { step: 'env', config }
503
+ data: { step: 'env', config },
786
504
  };
787
505
  case 'env':
788
- config.env = {};
789
- if (input.trim()) {
790
- const envLines = input.trim().split('\n');
791
- for (const line of envLines) {
792
- const [key, ...valueParts] = line.split('=');
793
- if (key && valueParts.length > 0) {
794
- config.env[key.trim()] = valueParts.join('=').trim();
795
- }
796
- }
797
- }
506
+ config.env = this.mcpService.parseEnvironmentVariables(input);
798
507
  return {
799
508
  type: 'prompt_server_config',
800
- message: `Server Configuration Summary:\n\nName: ${config.name}\nCommand: ${config.command}\nArgs: ${config.args.length > 0 ? config.args.join(' ') : 'none'}\nEnv: ${Object.keys(config.env).length > 0 ? Object.entries(config.env).map(([k, v]) => `${k}=${v}`).join(', ') : 'none'}\n\nConfirm to add this server? (y/n):`,
801
- data: { step: 'confirm', config }
509
+ message: `Server Configuration Summary:\n\nName: ${config.name}\nCommand: ${config.command}\nArgs: ${config.args.length > 0 ? config.args.join(' ') : 'none'}\nEnv: ${Object.keys(config.env).length > 0
510
+ ? Object.entries(config.env)
511
+ .map(([k, v]) => `${k}=${v}`)
512
+ .join(', ')
513
+ : 'none'}\n\nConfirm to add this server? (y/n):`,
514
+ data: { step: 'confirm', config },
802
515
  };
803
516
  case 'confirm':
804
- if (input.trim().toLowerCase() === 'y' || input.trim().toLowerCase() === 'yes') {
517
+ if (input.trim().toLowerCase() === 'y' ||
518
+ input.trim().toLowerCase() === 'yes') {
805
519
  const serverConfig = {
806
520
  command: config.command,
807
521
  args: config.args,
808
- env: config.env
522
+ env: config.env,
809
523
  };
810
- // Add server to persistent configuration (servers are configured but not automatically connected)
811
- if (!this.persistentConfig.mcpServers) {
812
- this.persistentConfig.mcpServers = {};
524
+ const result = this.mcpService.addServer(config.name, serverConfig);
525
+ if (!result.success) {
526
+ return {
527
+ type: 'error',
528
+ message: result.message,
529
+ };
813
530
  }
814
- this.persistentConfig.mcpServers[config.name] = serverConfig;
815
- // Save configuration
816
- SecureStorage.saveConfig(this.persistentConfig);
817
- // Auto-connect the newly configured server
818
- this.sessionServers[config.name] = serverConfig;
819
531
  return {
820
532
  type: 'success',
821
- message: `āœ… Server "${config.name}" configured and connected!\n\nšŸ”„ Agent will be reinitialized with this server - attempting to establish connection...\nUse /tools to verify the server tools are available.`,
822
- data: { serverAdded: true, serverConnected: true, serverName: config.name }
533
+ message: `${result.message}.`,
534
+ data: result.data,
823
535
  };
824
536
  }
825
- else if (input.trim().toLowerCase() === 'n' || input.trim().toLowerCase() === 'no') {
537
+ else if (input.trim().toLowerCase() === 'n' ||
538
+ input.trim().toLowerCase() === 'no') {
826
539
  return {
827
540
  type: 'info',
828
- message: 'Server configuration cancelled.'
541
+ message: 'Server configuration cancelled.',
829
542
  };
830
543
  }
831
544
  else {
832
545
  return {
833
546
  type: 'error',
834
- message: 'Please enter "y" for yes or "n" for no.'
547
+ message: 'Please enter "y" for yes or "n" for no.',
835
548
  };
836
549
  }
837
550
  default:
838
551
  return {
839
552
  type: 'error',
840
- message: 'Invalid server configuration step.'
553
+ message: 'Invalid server configuration step.',
841
554
  };
842
555
  }
843
556
  }
@@ -845,180 +558,207 @@ Available slash commands:
845
558
  if (args.length === 0) {
846
559
  return {
847
560
  type: 'info',
848
- message: 'Server management commands:\n\n/server add - Configure a new server (stored but not connected)\n/server connect <name> - Connect to a configured server by name\n/server disconnect <name> - Disconnect from a connected server\n/servers - List configured servers and connection status\n\nUse /server <command> for specific help.'
561
+ message: 'Server management commands:\n\n/server add - Configure a new server (stored but not connected)\n/server connect <name> - Connect to a configured server by name\n/server disconnect <name> - Disconnect from a connected server\n/servers - List configured servers and connection status\n\nUse /server <command> for specific help.',
849
562
  };
850
563
  }
851
564
  if (args[0] === 'add') {
852
565
  return {
853
566
  type: 'prompt_server_config',
854
567
  message: 'Let\'s configure a new MCP server!\n\nYou can either:\n1. Enter a server name for interactive setup\n2. Paste a complete JSON configuration\n\nExample JSON:\n{\n "mcpServers": {\n "myserver": {\n "command": "npx",\n "args": ["-y", "@example/server"]\n }\n }\n}\n\nEnter server name or paste JSON:',
855
- data: { step: 'name_or_json' }
568
+ data: { step: 'name_or_json' },
856
569
  };
857
570
  }
858
571
  if (args[0] === 'connect') {
859
572
  if (args.length < 2) {
860
- const configuredServers = Object.keys({ ...this.persistentConfig.mcpServers });
573
+ const configuredServers = Object.keys(this.mcpService.getConfiguredServers());
861
574
  if (configuredServers.length === 0) {
862
575
  return {
863
576
  type: 'error',
864
- message: 'No servers configured. Use /server add to configure servers first.\n\nUsage: /server connect <server_name>'
577
+ message: 'No servers configured. Use /server add to configure servers first.\n\nUsage: /server connect <server_name>',
865
578
  };
866
579
  }
867
580
  return {
868
581
  type: 'error',
869
- message: `Usage: /server connect <server_name>\n\nConfigured servers: ${configuredServers.join(', ')}`
582
+ message: `Usage: /server connect <server_name>\n\nConfigured servers: ${configuredServers.join(', ')}`,
870
583
  };
871
584
  }
872
585
  const serverName = args[1];
873
586
  if (!serverName) {
874
587
  return {
875
588
  type: 'error',
876
- message: 'Server name is required.\n\nUsage: /server connect <server_name>'
589
+ message: 'Server name is required.\n\nUsage: /server connect <server_name>',
877
590
  };
878
591
  }
879
592
  return this.handleConnectServer(serverName);
880
593
  }
881
594
  if (args[0] === 'disconnect') {
882
595
  if (args.length < 2) {
883
- const connectedServers = Object.keys(this.sessionServers);
596
+ const connectedServers = Object.keys(this.mcpService.getSessionServers());
884
597
  if (connectedServers.length === 0) {
885
598
  return {
886
599
  type: 'info',
887
- message: 'No servers currently connected.\n\nUsage: /server disconnect <server_name>'
600
+ message: 'No servers currently connected.\n\nUsage: /server disconnect <server_name>',
888
601
  };
889
602
  }
890
603
  return {
891
604
  type: 'error',
892
- message: `Usage: /server disconnect <server_name>\n\nConnected servers: ${connectedServers.join(', ')}`
605
+ message: `Usage: /server disconnect <server_name>\n\nConnected servers: ${connectedServers.join(', ')}`,
893
606
  };
894
607
  }
895
608
  const serverName = args[1];
896
609
  if (!serverName) {
897
610
  return {
898
611
  type: 'error',
899
- message: 'Server name is required.\n\nUsage: /server disconnect <server_name>'
612
+ message: 'Server name is required.\n\nUsage: /server disconnect <server_name>',
900
613
  };
901
614
  }
902
615
  return this.handleDisconnectServer(serverName);
903
616
  }
904
617
  return {
905
618
  type: 'error',
906
- message: 'Usage: /server <command>\n\nCommands:\n add - Configure server\n connect <name> - Connect to server\n disconnect <name> - Disconnect server\n\nExample: /server connect airbnb'
619
+ message: 'Usage: /server <command>\n\nCommands:\n add - Configure server\n connect <name> - Connect to server\n disconnect <name> - Disconnect server\n\nExample: /server connect airbnb',
907
620
  };
908
621
  }
909
622
  handleListServers() {
910
- const persistentServers = this.persistentConfig.mcpServers || {};
911
- const connectedServers = this.sessionServers || {};
912
- let serverList = 'šŸ“‹ MCP Server Status:\n\n';
913
- // Built-in filesystem server (always connected)
914
- serverList += 'šŸ”ø filesystem:\n';
915
- serverList += ' Status: 🟢 Connected (built-in)\n';
916
- serverList += ' Command: npx\n';
917
- serverList += ' Args: -y @modelcontextprotocol/server-filesystem /tmp\n\n';
918
- // Custom configured servers
919
- const configuredServerNames = Object.keys(persistentServers);
920
- if (configuredServerNames.length === 0) {
921
- serverList += 'No custom servers configured.\n\nUse /server add to configure servers, then /server connect <name> to connect.';
922
- }
923
- else {
924
- for (const [name, config] of Object.entries(persistentServers)) {
925
- const isConnected = connectedServers[name] !== undefined;
926
- const status = isConnected ? '🟢 Connected' : 'šŸ”“ Disconnected';
927
- const action = isConnected ? '/server disconnect' : '/server connect';
928
- serverList += `šŸ”ø ${name}:\n`;
929
- serverList += ` Status: ${status}\n`;
930
- serverList += ` Command: ${config.command}\n`;
931
- if (config.args && config.args.length > 0) {
932
- serverList += ` Args: ${config.args.join(' ')}\n`;
933
- }
934
- if (config.env && Object.keys(config.env).length > 0) {
935
- serverList += ` Env: ${Object.entries(config.env).map(([k, v]) => `${k}=${v}`).join(', ')}\n`;
936
- }
937
- serverList += ` Action: ${action} ${name}\n\n`;
938
- }
623
+ const serverStatus = this.mcpService.getServerStatus();
624
+ if (serverStatus.length === 0) {
625
+ return {
626
+ type: 'info',
627
+ message: 'No custom servers configured.\n\nUse /server add to configure servers, then /server connect <name> to connect.',
628
+ };
939
629
  }
940
630
  return {
941
- type: 'info',
942
- message: serverList.trim()
631
+ type: 'list_servers',
632
+ message: 'MCP Server Status:',
633
+ data: { servers: serverStatus },
943
634
  };
944
635
  }
945
636
  handleConnectServer(serverName) {
946
- // Check if server is configured
947
- const configuredServer = this.persistentConfig.mcpServers?.[serverName];
948
- if (!configuredServer) {
949
- const availableServers = Object.keys(this.persistentConfig.mcpServers || {});
637
+ const result = this.mcpService.connectServer(serverName);
638
+ if (!result.success) {
950
639
  return {
951
640
  type: 'error',
952
- message: `Server "${serverName}" is not configured.\n\nConfigured servers: ${availableServers.length > 0 ? availableServers.join(', ') : 'none'}\n\nUse /server add to configure new servers.`
641
+ message: result.message,
953
642
  };
954
643
  }
955
- // Check if already connected (in session servers)
956
- if (this.sessionServers[serverName]) {
957
- return {
958
- type: 'info',
959
- message: `āœ… Server "${serverName}" is already connected.`
960
- };
961
- }
962
- // Connect the server (add to session servers)
963
- this.sessionServers[serverName] = configuredServer;
964
644
  return {
965
645
  type: 'success',
966
- message: `āœ… Connected to server "${serverName}"!\n\nšŸ”„ Agent will be reinitialized with this server - attempting to establish connection...\nUse /tools to verify the server tools are available.`,
967
- data: { serverConnected: true, serverName }
646
+ message: `${result.message}.`,
647
+ data: result.data,
968
648
  };
969
649
  }
970
650
  handleDisconnectServer(serverName) {
971
- // Check if server is connected
972
- if (!this.sessionServers[serverName]) {
973
- const connectedServers = Object.keys(this.sessionServers);
651
+ const result = this.mcpService.disconnectServer(serverName);
652
+ if (!result.success) {
974
653
  return {
975
654
  type: 'error',
976
- message: `Server "${serverName}" is not connected.\n\nConnected servers: ${connectedServers.length > 0 ? connectedServers.join(', ') : 'none'}`
655
+ message: result.message,
977
656
  };
978
657
  }
979
- // Disconnect the server (remove from session servers)
980
- delete this.sessionServers[serverName];
981
658
  return {
982
659
  type: 'success',
983
- message: `āœ… Disconnected from server "${serverName}".\n\nšŸ”„ Agent will be reinitialized without this server.`,
984
- data: { serverDisconnected: true, serverName }
660
+ message: `${result.message}.`,
661
+ data: result.data,
985
662
  };
986
663
  }
987
664
  // Method to handle API key input and complete model selection
988
665
  handleApiKeyInput(apiKey, provider, model) {
989
- // Validate the API key
990
- const validationResult = this.validateApiKey(provider, apiKey);
991
- if (!validationResult.valid) {
666
+ // Set the API key
667
+ const keyResult = this.llmService.setApiKey(provider, apiKey, false);
668
+ if (!keyResult.success) {
992
669
  return {
993
670
  type: 'error',
994
- message: validationResult.message
671
+ message: keyResult.message,
995
672
  };
996
673
  }
997
- // Map provider to environment variable name
998
- const envVarMap = {
999
- openai: 'OPENAI_API_KEY',
1000
- anthropic: 'ANTHROPIC_API_KEY',
1001
- google: 'GOOGLE_API_KEY',
1002
- mistral: 'MISTRAL_API_KEY'
1003
- };
1004
- const envVar = envVarMap[provider];
1005
- // Store the API key in persistent storage
1006
- this.persistentConfig.apiKeys[envVar] = apiKey;
1007
- // Set the model configuration
1008
- this.currentLLMConfig = {
1009
- provider: provider,
1010
- model,
1011
- temperature: this.currentLLMConfig?.temperature || 0.7,
1012
- maxTokens: this.currentLLMConfig?.maxTokens
1013
- };
1014
- // Save both the API key and the current model to persistent storage
1015
- this.persistentConfig.lastModel = this.currentLLMConfig;
1016
- SecureStorage.saveConfig(this.persistentConfig);
1017
- const maskedKey = this.maskApiKey(apiKey);
674
+ // Now set the model
675
+ const modelResult = this.llmService.setModel(provider, model);
676
+ if (!modelResult.success) {
677
+ return {
678
+ type: 'error',
679
+ message: modelResult.message,
680
+ };
681
+ }
682
+ const maskedKey = this.llmService.maskApiKey(apiKey);
1018
683
  return {
1019
684
  type: 'success',
1020
685
  message: `āœ… ${provider} API key set (${maskedKey})\nšŸ¤– Switched to ${provider}/${model}`,
1021
- data: { llmConfig: this.currentLLMConfig }
686
+ data: { llmConfig: this.llmService.getCurrentConfig() },
687
+ };
688
+ }
689
+ handleLogs(args) {
690
+ const logPath = Logger.getLogPath();
691
+ if (args.length === 0) {
692
+ return {
693
+ type: 'info',
694
+ message: `šŸ“‹ Debug logs are written to:\n${logPath}\n\nCommands:\n /logs path - Show log file path\n /logs tail - Show recent log entries\n /clearlogs - Clear all logs\n\nTo view logs in real-time:\n tail -f ${logPath}`,
695
+ };
696
+ }
697
+ const subcommand = args[0];
698
+ switch (subcommand) {
699
+ case 'path':
700
+ return {
701
+ type: 'info',
702
+ message: `šŸ“ Log file location:\n${logPath}`,
703
+ };
704
+ case 'tail':
705
+ try {
706
+ const fs = require('fs');
707
+ if (!fs.existsSync(logPath)) {
708
+ return {
709
+ type: 'info',
710
+ message: 'šŸ“‹ No log file found yet. Logs will be created when the app starts logging.',
711
+ };
712
+ }
713
+ const logContent = fs.readFileSync(logPath, 'utf8');
714
+ const lines = logContent
715
+ .split('\n')
716
+ .filter((line) => line.trim());
717
+ const recentLines = lines.slice(-20); // Show last 20 lines
718
+ if (recentLines.length === 0) {
719
+ return {
720
+ type: 'info',
721
+ message: 'šŸ“‹ Log file is empty.',
722
+ };
723
+ }
724
+ return {
725
+ type: 'info',
726
+ message: `šŸ“‹ Recent log entries (last ${recentLines.length} lines):\n\n${recentLines.join('\n')}`,
727
+ };
728
+ }
729
+ catch (error) {
730
+ return {
731
+ type: 'error',
732
+ message: `āŒ Failed to read logs: ${error instanceof Error ? error.message : 'Unknown error'}`,
733
+ };
734
+ }
735
+ default:
736
+ return {
737
+ type: 'error',
738
+ message: `Unknown logs subcommand: ${subcommand}. Use /logs for help.`,
739
+ };
740
+ }
741
+ }
742
+ handleClearLogs() {
743
+ try {
744
+ Logger.clearLogs();
745
+ Logger.info('Logs cleared by user command');
746
+ return {
747
+ type: 'success',
748
+ message: 'āœ… Debug logs cleared successfully.',
749
+ };
750
+ }
751
+ catch (error) {
752
+ return {
753
+ type: 'error',
754
+ message: `āŒ Failed to clear logs: ${error instanceof Error ? error.message : 'Unknown error'}`,
755
+ };
756
+ }
757
+ }
758
+ handleHistory() {
759
+ return {
760
+ type: 'info',
761
+ message: `šŸ“œ Input History Navigation:\n\nšŸ”¼ Arrow Up - Navigate to previous inputs\nšŸ”½ Arrow Down - Navigate to newer inputs\n\nšŸ’” Tips:\n• Your input history is automatically saved during the session\n• Use ↑ to recall previous commands and messages\n• Use ↓ to navigate back to newer inputs\n• History is reset when you restart the CLI\n\nšŸŽÆ Try it now: Press the up arrow key in the input box!`,
1022
762
  };
1023
763
  }
1024
764
  }