@indiccoder/mentis-cli 1.0.3

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 (46) hide show
  1. package/README.md +90 -0
  2. package/console.log(tick) +0 -0
  3. package/debug_fs.js +12 -0
  4. package/dist/checkpoint/CheckpointManager.js +53 -0
  5. package/dist/config/ConfigManager.js +55 -0
  6. package/dist/context/ContextManager.js +55 -0
  7. package/dist/context/RepoMapper.js +112 -0
  8. package/dist/index.js +12 -0
  9. package/dist/llm/AnthropicClient.js +70 -0
  10. package/dist/llm/ModelInterface.js +2 -0
  11. package/dist/llm/OpenAIClient.js +58 -0
  12. package/dist/mcp/JsonRpcClient.js +117 -0
  13. package/dist/mcp/McpClient.js +59 -0
  14. package/dist/repl/PersistentShell.js +75 -0
  15. package/dist/repl/ReplManager.js +813 -0
  16. package/dist/tools/FileTools.js +100 -0
  17. package/dist/tools/GitTools.js +127 -0
  18. package/dist/tools/PersistentShellTool.js +30 -0
  19. package/dist/tools/SearchTools.js +83 -0
  20. package/dist/tools/Tool.js +2 -0
  21. package/dist/tools/WebSearchTool.js +60 -0
  22. package/dist/ui/UIManager.js +40 -0
  23. package/package.json +63 -0
  24. package/screenshot_1765779883482_9b30.png +0 -0
  25. package/scripts/test_features.ts +48 -0
  26. package/scripts/test_glm.ts +53 -0
  27. package/scripts/test_models.ts +38 -0
  28. package/src/checkpoint/CheckpointManager.ts +61 -0
  29. package/src/config/ConfigManager.ts +77 -0
  30. package/src/context/ContextManager.ts +63 -0
  31. package/src/context/RepoMapper.ts +119 -0
  32. package/src/index.ts +12 -0
  33. package/src/llm/ModelInterface.ts +47 -0
  34. package/src/llm/OpenAIClient.ts +64 -0
  35. package/src/mcp/JsonRpcClient.ts +103 -0
  36. package/src/mcp/McpClient.ts +75 -0
  37. package/src/repl/PersistentShell.ts +85 -0
  38. package/src/repl/ReplManager.ts +842 -0
  39. package/src/tools/FileTools.ts +89 -0
  40. package/src/tools/GitTools.ts +113 -0
  41. package/src/tools/PersistentShellTool.ts +32 -0
  42. package/src/tools/SearchTools.ts +74 -0
  43. package/src/tools/Tool.ts +6 -0
  44. package/src/tools/WebSearchTool.ts +63 -0
  45. package/src/ui/UIManager.ts +41 -0
  46. package/tsconfig.json +21 -0
@@ -0,0 +1,813 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.ReplManager = void 0;
40
+ const inquirer_1 = __importDefault(require("inquirer"));
41
+ const chalk_1 = __importDefault(require("chalk"));
42
+ const ora_1 = __importDefault(require("ora"));
43
+ const ConfigManager_1 = require("../config/ConfigManager");
44
+ const OpenAIClient_1 = require("../llm/OpenAIClient");
45
+ const ContextManager_1 = require("../context/ContextManager");
46
+ const UIManager_1 = require("../ui/UIManager");
47
+ const FileTools_1 = require("../tools/FileTools");
48
+ const SearchTools_1 = require("../tools/SearchTools");
49
+ const PersistentShellTool_1 = require("../tools/PersistentShellTool");
50
+ const PersistentShell_1 = require("./PersistentShell");
51
+ const WebSearchTool_1 = require("../tools/WebSearchTool");
52
+ const GitTools_1 = require("../tools/GitTools");
53
+ const McpClient_1 = require("../mcp/McpClient");
54
+ const CheckpointManager_1 = require("../checkpoint/CheckpointManager");
55
+ const readline = __importStar(require("readline"));
56
+ const fs = __importStar(require("fs"));
57
+ const path = __importStar(require("path"));
58
+ const os = __importStar(require("os"));
59
+ const marked_1 = require("marked");
60
+ const marked_terminal_1 = __importDefault(require("marked-terminal"));
61
+ const HISTORY_FILE = path.join(os.homedir(), '.mentis_history');
62
+ class ReplManager {
63
+ constructor() {
64
+ this.history = [];
65
+ this.mode = 'BUILD';
66
+ this.tools = [];
67
+ this.mcpClients = [];
68
+ this.currentModelName = 'Unknown';
69
+ this.configManager = new ConfigManager_1.ConfigManager();
70
+ this.contextManager = new ContextManager_1.ContextManager();
71
+ this.checkpointManager = new CheckpointManager_1.CheckpointManager();
72
+ this.shell = new PersistentShell_1.PersistentShell();
73
+ this.tools = [
74
+ new FileTools_1.WriteFileTool(),
75
+ new FileTools_1.ReadFileTool(),
76
+ new FileTools_1.ListDirTool(),
77
+ new SearchTools_1.SearchFileTool(), // grep
78
+ new WebSearchTool_1.WebSearchTool(),
79
+ new GitTools_1.GitStatusTool(),
80
+ new GitTools_1.GitDiffTool(),
81
+ new GitTools_1.GitCommitTool(),
82
+ new GitTools_1.GitPushTool(),
83
+ new GitTools_1.GitPullTool(),
84
+ new PersistentShellTool_1.PersistentShellTool(this.shell) // /run
85
+ ];
86
+ // Configure Markdown Renderer
87
+ marked_1.marked.setOptions({
88
+ // @ts-ignore
89
+ renderer: new marked_terminal_1.default()
90
+ });
91
+ // Default to Ollama if not specified, assuming compatible endpoint
92
+ this.initializeClient();
93
+ }
94
+ initializeClient() {
95
+ const config = this.configManager.getConfig();
96
+ const provider = config.defaultProvider || 'ollama';
97
+ let baseUrl;
98
+ let apiKey;
99
+ let model;
100
+ if (provider === 'gemini') {
101
+ baseUrl = 'https://generativelanguage.googleapis.com/v1beta/openai/';
102
+ apiKey = config.gemini?.apiKey || '';
103
+ model = config.gemini?.model || 'gemini-2.5-flash';
104
+ }
105
+ else if (provider === 'openai') {
106
+ baseUrl = config.openai?.baseUrl || 'https://api.openai.com/v1';
107
+ apiKey = config.openai?.apiKey || '';
108
+ model = config.openai?.model || 'gpt-4o';
109
+ }
110
+ else if (provider === 'glm') {
111
+ // Use the "Coding Plan" endpoint which supports glm-4.6 and this specific key type
112
+ baseUrl = config.glm?.baseUrl || 'https://api.z.ai/api/coding/paas/v4/';
113
+ apiKey = config.glm?.apiKey || '';
114
+ model = config.glm?.model || 'glm-4.6';
115
+ }
116
+ else { // Default to Ollama
117
+ baseUrl = config.ollama?.baseUrl || 'http://localhost:11434/v1';
118
+ apiKey = 'ollama'; // Ollama typically doesn't use an API key in the same way
119
+ model = config.ollama?.model || 'llama3:latest';
120
+ }
121
+ this.currentModelName = model;
122
+ this.modelClient = new OpenAIClient_1.OpenAIClient(baseUrl, apiKey, model);
123
+ // console.log(chalk.dim(`Initialized ${provider} client with model ${model}`));
124
+ }
125
+ async start() {
126
+ UIManager_1.UIManager.displayLogo();
127
+ UIManager_1.UIManager.displayWelcome();
128
+ // Load History
129
+ let commandHistory = [];
130
+ if (fs.existsSync(HISTORY_FILE)) {
131
+ try {
132
+ commandHistory = fs.readFileSync(HISTORY_FILE, 'utf-8').split('\n').filter(Boolean).reverse(); // readline expects newest first? No, newest is usually 0? Check.
133
+ // readline.history is [newest, ..., oldest]
134
+ // If I read from file where newest is at bottom (standard append), I need to reverse it.
135
+ // Let's assume standard file: line 1 (old), line 2 (new).
136
+ // So split -> reverse -> history.
137
+ }
138
+ catch (e) { }
139
+ }
140
+ while (true) {
141
+ UIManager_1.UIManager.printSeparator();
142
+ // console.log(chalk.dim(` /help for help | Model: ${chalk.cyan(this.currentModelName)}`));
143
+ // Removed redundancy to keep CLI clean, prompt has info? No, prompt is minimal.
144
+ const modeLabel = this.mode === 'PLAN' ? chalk_1.default.magenta('PLAN') : chalk_1.default.blue('BUILD');
145
+ const promptText = `${modeLabel} ${chalk_1.default.cyan('>')}`;
146
+ // Use readline for basic input to support history
147
+ const answer = await new Promise((resolve) => {
148
+ const rl = readline.createInterface({
149
+ input: process.stdin,
150
+ output: process.stdout,
151
+ history: commandHistory,
152
+ historySize: 1000,
153
+ prompt: promptText + ' '
154
+ });
155
+ rl.prompt();
156
+ rl.on('line', (line) => {
157
+ rl.close();
158
+ resolve(line);
159
+ });
160
+ });
161
+ // Update history manually or grab from rl?
162
+ // rl.history gets updated when user hits enter.
163
+ // But we closed rl. We should manually save the input to our tracking array and file.
164
+ const input = answer.trim();
165
+ if (input) {
166
+ // Update in-memory history (for next readline instance)
167
+ // Readline history has newest at 0.
168
+ // Avoid duplicates if needed, but standard shell keeps them.
169
+ if (commandHistory[0] !== input) {
170
+ commandHistory.unshift(input);
171
+ }
172
+ // Append to file (as standard log, so append at end)
173
+ try {
174
+ fs.appendFileSync(HISTORY_FILE, input + '\n');
175
+ }
176
+ catch (e) { }
177
+ }
178
+ if (!answer.trim())
179
+ continue; // Skip empty but allow it to close readline loop
180
+ if (input.startsWith('/')) {
181
+ await this.handleCommand(input);
182
+ continue;
183
+ }
184
+ await this.handleChat(input);
185
+ }
186
+ }
187
+ async handleCommand(input) {
188
+ const [command, ...args] = input.split(' ');
189
+ switch (command) {
190
+ case '/help':
191
+ console.log(chalk_1.default.yellow('Available commands:'));
192
+ console.log(' /help - Show this help message');
193
+ console.log(' /clear - Clear chat history');
194
+ console.log(' /exit - Exit the application');
195
+ console.log(' /config - Configure settings');
196
+ console.log(' /add <file> - Add file to context');
197
+ console.log(' /drop <file> - Remove file from context');
198
+ console.log(' /plan - Switch to PLAN mode');
199
+ console.log(' /build - Switch to BUILD mode');
200
+ console.log(' /plan - Switch to PLAN mode');
201
+ console.log(' /build - Switch to BUILD mode');
202
+ console.log(' /model - Interactively select Provider & Model');
203
+ console.log(' /use <provider> [model] - Quick switch (legacy)');
204
+ console.log(' /mcp <cmd> - Manage MCP servers');
205
+ console.log(' /resume - Resume last session');
206
+ console.log(' /checkpoint <save|load|list> [name] - Manage checkpoints');
207
+ console.log(' /search <query> - Search codebase');
208
+ console.log(' /run <cmd> - Run shell command');
209
+ console.log(' /commit [msg] - Git commit all changes');
210
+ break;
211
+ case '/plan':
212
+ this.mode = 'PLAN';
213
+ console.log(chalk_1.default.blue('Switched to PLAN mode.'));
214
+ break;
215
+ case '/build':
216
+ this.mode = 'BUILD';
217
+ console.log(chalk_1.default.yellow('Switched to BUILD mode.'));
218
+ break;
219
+ case '/build':
220
+ this.mode = 'BUILD';
221
+ console.log(chalk_1.default.yellow('Switched to BUILD mode.'));
222
+ break;
223
+ case '/model':
224
+ await this.handleModelCommand(args);
225
+ break;
226
+ case '/connect':
227
+ console.log(chalk_1.default.dim('Tip: Use /model for an interactive menu.'));
228
+ await this.handleConnectCommand(args);
229
+ break;
230
+ case '/use':
231
+ await this.handleUseCommand(args);
232
+ break;
233
+ case '/mcp':
234
+ await this.handleMcpCommand(args);
235
+ break;
236
+ case '/resume':
237
+ await this.handleResumeCommand();
238
+ break;
239
+ case '/checkpoint':
240
+ await this.handleCheckpointCommand(args);
241
+ break;
242
+ case '/clear':
243
+ this.history = [];
244
+ this.contextManager.clear();
245
+ UIManager_1.UIManager.displayLogo(); // Redraw logo on clear
246
+ console.log(chalk_1.default.yellow('Chat history and context cleared.'));
247
+ break;
248
+ case '/add':
249
+ if (args.length === 0) {
250
+ console.log(chalk_1.default.red('Usage: /add <file_path>'));
251
+ }
252
+ else {
253
+ const result = await this.contextManager.addFile(args[0]);
254
+ console.log(chalk_1.default.yellow(result));
255
+ }
256
+ break;
257
+ case '/drop':
258
+ if (args.length === 0) {
259
+ console.log(chalk_1.default.red('Usage: /drop <file_path>'));
260
+ }
261
+ else {
262
+ const result = await this.contextManager.removeFile(args[0]);
263
+ console.log(chalk_1.default.yellow(result));
264
+ }
265
+ break;
266
+ case '/config':
267
+ await this.handleConfigCommand();
268
+ break;
269
+ case '/exit':
270
+ // Auto-save on exit
271
+ this.checkpointManager.save('latest', this.history, this.contextManager.getFiles());
272
+ this.shell.kill(); // Kill the shell process
273
+ console.log(chalk_1.default.green('Session saved. Goodbye!'));
274
+ process.exit(0);
275
+ break;
276
+ default:
277
+ console.log(chalk_1.default.red(`Unknown command: ${command}`));
278
+ }
279
+ }
280
+ async handleChat(input) {
281
+ const context = this.contextManager.getContextString();
282
+ let fullInput = input;
283
+ let modeInstruction = '';
284
+ if (this.mode === 'PLAN') {
285
+ modeInstruction = '\n[SYSTEM: You are in PLAN mode. Focus on high-level architecture, requirements analysis, and creating a sturdy plan. Do not write full code implementation yet, just scaffolds or pseudocode if needed.]';
286
+ }
287
+ else {
288
+ modeInstruction = '\n[SYSTEM: You are in BUILD mode. Focus on implementing working code that solves the user request efficiently.]';
289
+ }
290
+ fullInput = `${input}${modeInstruction}`;
291
+ if (context) {
292
+ fullInput = `${context}\n\nUser Question: ${fullInput}`;
293
+ }
294
+ this.history.push({ role: 'user', content: fullInput });
295
+ let spinner = (0, ora_1.default)('Thinking... (Press Esc to cancel)').start();
296
+ const controller = new AbortController();
297
+ // Setup cancellation listener
298
+ const keyListener = (str, key) => {
299
+ if (key.name === 'escape') {
300
+ controller.abort();
301
+ }
302
+ };
303
+ if (process.stdin.isTTY) {
304
+ readline.emitKeypressEvents(process.stdin);
305
+ process.stdin.setRawMode(true);
306
+ process.stdin.on('keypress', keyListener);
307
+ }
308
+ try {
309
+ // First call
310
+ let response = await this.modelClient.chat(this.history, this.tools.map(t => ({
311
+ type: 'function',
312
+ function: {
313
+ name: t.name,
314
+ description: t.description,
315
+ parameters: t.parameters
316
+ }
317
+ })), controller.signal);
318
+ // Loop for tool calls
319
+ while (response.tool_calls && response.tool_calls.length > 0) {
320
+ if (controller.signal.aborted)
321
+ throw new Error('Request cancelled by user');
322
+ spinner.stop();
323
+ // Add the assistant's request to use tool to history
324
+ this.history.push({
325
+ role: 'assistant',
326
+ content: response.content,
327
+ tool_calls: response.tool_calls
328
+ });
329
+ // Execute tools
330
+ for (const toolCall of response.tool_calls) {
331
+ if (controller.signal.aborted)
332
+ break;
333
+ const toolName = toolCall.function.name;
334
+ const toolArgsStr = toolCall.function.arguments;
335
+ const toolArgs = JSON.parse(toolArgsStr);
336
+ // Truncate long arguments
337
+ let displayArgs = toolArgsStr;
338
+ if (displayArgs.length > 100) {
339
+ displayArgs = displayArgs.substring(0, 100) + '...';
340
+ }
341
+ console.log(chalk_1.default.dim(` [Action] ${toolName}(${displayArgs})`));
342
+ // Safety check for write_file
343
+ if (toolName === 'write_file') {
344
+ // Pause cancellation listener during user interaction
345
+ if (process.stdin.isTTY) {
346
+ process.stdin.removeListener('keypress', keyListener);
347
+ process.stdin.setRawMode(false);
348
+ process.stdin.pause(); // Explicitly pause before inquirer
349
+ }
350
+ spinner.stop(); // Stop spinner to allow input
351
+ const { confirm } = await inquirer_1.default.prompt([
352
+ {
353
+ type: 'confirm',
354
+ name: 'confirm',
355
+ message: `Allow writing to ${chalk_1.default.yellow(toolArgs.filePath)}?`,
356
+ default: true
357
+ }
358
+ ]);
359
+ // Resume cancellation listener
360
+ if (process.stdin.isTTY) {
361
+ process.stdin.setRawMode(true);
362
+ process.stdin.resume(); // Explicitly resume
363
+ process.stdin.on('keypress', keyListener);
364
+ }
365
+ if (!confirm) {
366
+ this.history.push({
367
+ role: 'tool',
368
+ tool_call_id: toolCall.id,
369
+ name: toolName,
370
+ content: 'Error: User rejected write operation.'
371
+ });
372
+ console.log(chalk_1.default.red(' Action cancelled by user.'));
373
+ // Do not restart spinner here. Let the outer loop logic or next step handle it.
374
+ // If we continue, we go to next tool or finish loop.
375
+ // If finished, lines following loop will start spinner.
376
+ continue;
377
+ }
378
+ spinner = (0, ora_1.default)('Executing...').start();
379
+ }
380
+ const tool = this.tools.find(t => t.name === toolName);
381
+ let result = '';
382
+ if (tool) {
383
+ try {
384
+ // Tools typically run synchronously or promise-based.
385
+ // Verify if we want Tools to be cancellable?
386
+ // For now, if aborted during tool, we let tool finish but stop loop.
387
+ result = await tool.execute(toolArgs);
388
+ }
389
+ catch (e) {
390
+ result = `Error: ${e.message}`;
391
+ }
392
+ }
393
+ else {
394
+ result = `Error: Tool ${toolName} not found.`;
395
+ }
396
+ if (spinner.isSpinning) {
397
+ spinner.stop();
398
+ }
399
+ this.history.push({
400
+ role: 'tool',
401
+ tool_call_id: toolCall.id,
402
+ name: toolName,
403
+ content: result
404
+ });
405
+ }
406
+ if (controller.signal.aborted)
407
+ throw new Error('Request cancelled by user');
408
+ spinner = (0, ora_1.default)('Thinking (processing tools)...').start();
409
+ // Get next response
410
+ response = await this.modelClient.chat(this.history, this.tools.map(t => ({
411
+ type: 'function',
412
+ function: {
413
+ name: t.name,
414
+ description: t.description,
415
+ parameters: t.parameters
416
+ }
417
+ })), controller.signal);
418
+ }
419
+ spinner.stop();
420
+ console.log('');
421
+ if (response.content) {
422
+ console.log(chalk_1.default.bold.blue('Mentis:'));
423
+ console.log((0, marked_1.marked)(response.content));
424
+ if (response.usage) {
425
+ const { input_tokens, output_tokens } = response.usage;
426
+ const totalCost = this.estimateCost(input_tokens, output_tokens);
427
+ console.log(chalk_1.default.dim(`\n(Tokens: ${input_tokens} in / ${output_tokens} out | Est. Cost: $${totalCost.toFixed(5)})`));
428
+ }
429
+ console.log('');
430
+ this.history.push({ role: 'assistant', content: response.content });
431
+ }
432
+ }
433
+ catch (error) {
434
+ spinner.stop();
435
+ if (error.message === 'Request cancelled by user') {
436
+ console.log(chalk_1.default.yellow('\nRequest cancelled by user.'));
437
+ }
438
+ else {
439
+ spinner.fail('Error getting response from model.');
440
+ console.error(error.message);
441
+ }
442
+ }
443
+ finally {
444
+ if (process.stdin.isTTY) {
445
+ process.stdin.removeListener('keypress', keyListener);
446
+ process.stdin.setRawMode(false);
447
+ process.stdin.pause(); // Reset flow
448
+ }
449
+ }
450
+ }
451
+ async handleConfigCommand() {
452
+ const config = this.configManager.getConfig();
453
+ const { action } = await inquirer_1.default.prompt([
454
+ {
455
+ type: 'list',
456
+ name: 'action',
457
+ message: 'Configuration',
458
+ prefix: '',
459
+ choices: [
460
+ 'Show Current Configuration',
461
+ 'Set Active Provider',
462
+ 'Set API Key (for active provider)',
463
+ 'Set Base URL (for active provider)',
464
+ 'Back'
465
+ ]
466
+ }
467
+ ]);
468
+ if (action === 'Back')
469
+ return;
470
+ if (action === 'Show Current Configuration') {
471
+ console.log(JSON.stringify(config, null, 2));
472
+ return;
473
+ }
474
+ if (action === 'Set Active Provider') {
475
+ const { provider } = await inquirer_1.default.prompt([{
476
+ type: 'list',
477
+ name: 'provider',
478
+ message: 'Select Provider:',
479
+ choices: ['Gemini', 'Ollama', 'OpenAI', 'GLM']
480
+ }]);
481
+ const key = provider.toLowerCase();
482
+ this.configManager.updateConfig({ defaultProvider: key });
483
+ console.log(chalk_1.default.green(`Active provider set to: ${provider}`));
484
+ this.initializeClient();
485
+ return;
486
+ }
487
+ const currentProvider = config.defaultProvider;
488
+ if (action === 'Set API Key (for active provider)') {
489
+ if (currentProvider === 'ollama') {
490
+ console.log(chalk_1.default.yellow('Ollama typically does not require an API key.'));
491
+ }
492
+ const { value } = await inquirer_1.default.prompt([{
493
+ type: 'password',
494
+ name: 'value',
495
+ message: `Enter API Key for ${currentProvider}:`,
496
+ mask: '*'
497
+ }]);
498
+ const updates = {};
499
+ updates[currentProvider] = { ...(config[currentProvider] || {}), apiKey: value };
500
+ this.configManager.updateConfig(updates);
501
+ console.log(chalk_1.default.green(`API Key updated for ${currentProvider}.`));
502
+ this.initializeClient();
503
+ }
504
+ if (action === 'Set Base URL (for active provider)') {
505
+ const defaultUrl = config[currentProvider]?.baseUrl || '';
506
+ const { value } = await inquirer_1.default.prompt([{
507
+ type: 'input',
508
+ name: 'value',
509
+ message: `Enter Base URL for ${currentProvider}:`,
510
+ default: defaultUrl
511
+ }]);
512
+ const updates = {};
513
+ updates[currentProvider] = { ...(config[currentProvider] || {}), baseUrl: value };
514
+ this.configManager.updateConfig(updates);
515
+ console.log(chalk_1.default.green(`Base URL updated for ${currentProvider}.`));
516
+ this.initializeClient();
517
+ }
518
+ }
519
+ async handleModelCommand(args) {
520
+ const config = this.configManager.getConfig();
521
+ const provider = config.defaultProvider || 'ollama';
522
+ // If argument provided, use it directly
523
+ if (args.length > 0) {
524
+ const modelName = args[0];
525
+ const updates = {};
526
+ updates[provider] = { ...(config[provider] || {}), model: modelName };
527
+ this.configManager.updateConfig(updates);
528
+ this.initializeClient(); // Re-init with new model
529
+ console.log(chalk_1.default.green(`\nModel set to ${chalk_1.default.bold(modelName)} for ${provider}!`));
530
+ return;
531
+ }
532
+ let models = [];
533
+ if (provider === 'gemini') {
534
+ models = ['gemini-2.5-flash', 'gemini-1.5-pro', 'gemini-1.0-pro', 'Other...'];
535
+ }
536
+ else if (provider === 'ollama') {
537
+ models = ['llama3:latest', 'deepseek-r1:latest', 'mistral:latest', 'Other...'];
538
+ }
539
+ else if (provider === 'openai') {
540
+ models = ['gpt-4o', 'gpt-4o-mini', 'gpt-4-turbo', 'Other...'];
541
+ }
542
+ else if (provider === 'glm') {
543
+ models = ['glm-4.6', 'glm-4-plus', 'glm-4', 'glm-4-air', 'glm-4-flash', 'Other...'];
544
+ }
545
+ else if (provider === 'anthropic') {
546
+ models = ['claude-3-5-sonnet-20241022', 'claude-3-opus-20240229', 'claude-3-sonnet-20240229', 'claude-3-haiku-20240307', 'glm-4.6', 'Other...'];
547
+ }
548
+ else {
549
+ models = ['Other...'];
550
+ }
551
+ console.log(chalk_1.default.blue(`Configuring model for active provider: ${chalk_1.default.bold(provider)}`));
552
+ let { model } = await inquirer_1.default.prompt([
553
+ {
554
+ type: 'list',
555
+ name: 'model',
556
+ message: 'Select Model:',
557
+ choices: models,
558
+ }
559
+ ]);
560
+ if (model === 'Other...') {
561
+ const { customModel } = await inquirer_1.default.prompt([{
562
+ type: 'input',
563
+ name: 'customModel',
564
+ message: 'Enter model name:'
565
+ }]);
566
+ model = customModel;
567
+ }
568
+ const updates = {};
569
+ updates[provider] = { ...(config[provider] || {}), model: model };
570
+ this.configManager.updateConfig(updates);
571
+ this.initializeClient();
572
+ console.log(chalk_1.default.green(`\nModel set to ${model} for ${provider}!`));
573
+ }
574
+ async handleConnectCommand(args) {
575
+ if (args.length < 1) {
576
+ console.log(chalk_1.default.red('Usage: /connect <provider> [key_or_url]'));
577
+ return;
578
+ }
579
+ const provider = args[0].toLowerCase();
580
+ const value = args[1]; // Optional for ollama (defaults), required for others usually
581
+ const config = this.configManager.getConfig();
582
+ if (provider === 'gemini') {
583
+ if (!value) {
584
+ console.log(chalk_1.default.red('Error: API Key required for Gemini. usage: /connect gemini <api_key>'));
585
+ return;
586
+ }
587
+ this.configManager.updateConfig({
588
+ gemini: { ...config.gemini, apiKey: value },
589
+ defaultProvider: 'gemini'
590
+ });
591
+ console.log(chalk_1.default.green(`Connected to Gemini with key: ${value.substring(0, 8)}...`));
592
+ }
593
+ else if (provider === 'ollama') {
594
+ const url = value || 'http://localhost:11434/v1';
595
+ this.configManager.updateConfig({
596
+ ollama: { ...config.ollama, baseUrl: url },
597
+ defaultProvider: 'ollama'
598
+ });
599
+ console.log(chalk_1.default.green(`Connected to Ollama at ${url}`));
600
+ }
601
+ else if (provider === 'openai') { // Support OpenAI since client supports it
602
+ if (!value) {
603
+ console.log(chalk_1.default.red('Error: API Key required for OpenAI. usage: /connect openai <api_key>'));
604
+ return;
605
+ }
606
+ this.configManager.updateConfig({
607
+ openai: { ...config.openai, apiKey: value },
608
+ defaultProvider: 'openai' // We might need to handle 'openai' in initializeClient if we add it officially
609
+ });
610
+ console.log(chalk_1.default.green(`Connected to OpenAI.`));
611
+ }
612
+ else if (provider === 'glm') {
613
+ if (!value) {
614
+ console.log(chalk_1.default.red('Error: API Key required for GLM. usage: /connect glm <api_key>'));
615
+ return;
616
+ }
617
+ this.configManager.updateConfig({
618
+ glm: { ...config.glm, apiKey: value },
619
+ defaultProvider: 'glm'
620
+ });
621
+ console.log(chalk_1.default.green(`Connected to GLM (ZhipuAI).`));
622
+ }
623
+ else {
624
+ console.log(chalk_1.default.red(`Unknown provider: ${provider}. Use 'gemini', 'ollama', 'openai', or 'glm'.`));
625
+ return;
626
+ }
627
+ this.initializeClient();
628
+ }
629
+ async handleUseCommand(args) {
630
+ if (args.length < 1) {
631
+ console.log(chalk_1.default.red('Usage: /use <provider> [model_name]'));
632
+ return;
633
+ }
634
+ const provider = args[0].toLowerCase();
635
+ const model = args[1]; // Optional
636
+ const config = this.configManager.getConfig();
637
+ if (provider === 'gemini') {
638
+ const updates = { defaultProvider: 'gemini' };
639
+ if (model) {
640
+ updates.gemini = { ...config.gemini, model: model };
641
+ }
642
+ this.configManager.updateConfig(updates);
643
+ }
644
+ else if (provider === 'ollama') {
645
+ const updates = { defaultProvider: 'ollama' };
646
+ if (model) {
647
+ updates.ollama = { ...config.ollama, model: model };
648
+ }
649
+ this.configManager.updateConfig(updates);
650
+ }
651
+ else if (provider === 'glm') {
652
+ const updates = { defaultProvider: 'glm' };
653
+ if (model) {
654
+ updates.glm = { ...config.glm, model: model };
655
+ }
656
+ this.configManager.updateConfig(updates);
657
+ // Auto switch if connecting to a new provider
658
+ if (provider === 'gemini') {
659
+ updates.defaultProvider = 'gemini';
660
+ this.configManager.updateConfig(updates);
661
+ }
662
+ else if (provider === 'ollama') {
663
+ updates.defaultProvider = 'ollama';
664
+ this.configManager.updateConfig(updates);
665
+ }
666
+ }
667
+ else {
668
+ console.log(chalk_1.default.red(`Unknown provider: ${provider}`));
669
+ return;
670
+ }
671
+ this.initializeClient();
672
+ console.log(chalk_1.default.green(`Switched to ${provider} ${model ? `using model ${model}` : ''}`));
673
+ }
674
+ async handleMcpCommand(args) {
675
+ if (args.length < 1) {
676
+ console.log(chalk_1.default.red('Usage: /mcp <connect|list|disconnect> [args]'));
677
+ return;
678
+ }
679
+ const action = args[0];
680
+ if (action === 'connect') {
681
+ const commandParts = args.slice(1);
682
+ if (commandParts.length === 0) {
683
+ console.log(chalk_1.default.red('Usage: /mcp connect <command> [args...]'));
684
+ return;
685
+ }
686
+ // Example: /mcp connect npx -y @modelcontextprotocol/server-memory
687
+ // On Windows, npx might be npx.cmd
688
+ const cmd = process.platform === 'win32' && commandParts[0] === 'npx' ? 'npx.cmd' : commandParts[0];
689
+ const cmdArgs = commandParts.slice(1);
690
+ const spinner = (0, ora_1.default)(`Connecting to MCP server: ${cmd} ${cmdArgs.join(' ')}...`).start();
691
+ try {
692
+ const client = new McpClient_1.McpClient(cmd, cmdArgs);
693
+ await client.initialize();
694
+ const mcpTools = await client.listTools();
695
+ this.mcpClients.push(client);
696
+ this.tools.push(...mcpTools);
697
+ spinner.succeed(chalk_1.default.green(`Connected to ${client.serverName}!`));
698
+ console.log(chalk_1.default.green(`Added ${mcpTools.length} tools:`));
699
+ mcpTools.forEach(t => console.log(chalk_1.default.dim(` - ${t.name}: ${t.description.substring(0, 50)}...`)));
700
+ }
701
+ catch (e) {
702
+ spinner.fail(chalk_1.default.red(`Failed to connect: ${e.message}`));
703
+ }
704
+ }
705
+ else if (action === 'list') {
706
+ if (this.mcpClients.length === 0) {
707
+ console.log('No active MCP connections.');
708
+ }
709
+ else {
710
+ console.log(chalk_1.default.cyan('Active MCP Connections:'));
711
+ this.mcpClients.forEach((client, idx) => {
712
+ console.log(`${idx + 1}. ${client.serverName}`);
713
+ });
714
+ }
715
+ }
716
+ else if (action === 'disconnect') {
717
+ // Basic disconnect all for now or by index if we wanted
718
+ console.log(chalk_1.default.yellow('Disconnecting all MCP clients...'));
719
+ this.mcpClients.forEach(c => c.disconnect());
720
+ this.mcpClients = [];
721
+ // Re-init core tools
722
+ this.tools = [
723
+ new FileTools_1.WriteFileTool(),
724
+ new FileTools_1.ReadFileTool(),
725
+ new FileTools_1.ListDirTool(),
726
+ new SearchTools_1.SearchFileTool(),
727
+ new PersistentShellTool_1.PersistentShellTool(this.shell),
728
+ new WebSearchTool_1.WebSearchTool(),
729
+ new GitTools_1.GitStatusTool(),
730
+ new GitTools_1.GitDiffTool(),
731
+ new GitTools_1.GitCommitTool(),
732
+ new GitTools_1.GitPushTool(),
733
+ new GitTools_1.GitPullTool()
734
+ ];
735
+ }
736
+ else {
737
+ console.log(chalk_1.default.red(`Unknown MCP action: ${action}`));
738
+ }
739
+ }
740
+ async handleResumeCommand() {
741
+ if (!this.checkpointManager.exists('latest')) {
742
+ console.log(chalk_1.default.yellow('No previous session found to resume.'));
743
+ return;
744
+ }
745
+ await this.loadCheckpoint('latest');
746
+ }
747
+ async handleCheckpointCommand(args) {
748
+ if (args.length < 1) {
749
+ console.log(chalk_1.default.red('Usage: /checkpoint <save|load|list> [name]'));
750
+ return;
751
+ }
752
+ const action = args[0];
753
+ const name = args[1] || 'default';
754
+ if (action === 'save') {
755
+ this.checkpointManager.save(name, this.history, this.contextManager.getFiles());
756
+ console.log(chalk_1.default.green(`Checkpoint '${name}' saved.`));
757
+ }
758
+ else if (action === 'load') {
759
+ await this.loadCheckpoint(name);
760
+ }
761
+ else if (action === 'list') {
762
+ const points = this.checkpointManager.list();
763
+ console.log(chalk_1.default.cyan('Available Checkpoints:'));
764
+ points.forEach(p => console.log(` - ${p}`));
765
+ }
766
+ else {
767
+ console.log(chalk_1.default.red(`Unknown action: ${action}`));
768
+ }
769
+ }
770
+ async loadCheckpoint(name) {
771
+ const cp = this.checkpointManager.load(name);
772
+ if (!cp) {
773
+ console.log(chalk_1.default.red(`Checkpoint '${name}' not found.`));
774
+ return;
775
+ }
776
+ this.history = cp.history;
777
+ this.contextManager.clear();
778
+ // Restore context files
779
+ if (cp.files && cp.files.length > 0) {
780
+ console.log(chalk_1.default.dim('Restoring context files...'));
781
+ for (const file of cp.files) {
782
+ await this.contextManager.addFile(file);
783
+ }
784
+ }
785
+ console.log(chalk_1.default.green(`Resumed session '${name}' (${new Date(cp.timestamp).toLocaleString()})`));
786
+ // Re-display last assistant message if any
787
+ const lastMsg = this.history[this.history.length - 1];
788
+ if (lastMsg && lastMsg.role === 'assistant' && lastMsg.content) {
789
+ console.log(chalk_1.default.blue('\nLast message:'));
790
+ console.log(lastMsg.content);
791
+ }
792
+ }
793
+ estimateCost(input, output) {
794
+ const config = this.configManager.getConfig();
795
+ const provider = config.defaultProvider;
796
+ let rateIn = 0;
797
+ let rateOut = 0;
798
+ if (provider === 'openai') {
799
+ rateIn = 5.00 / 1000000;
800
+ rateOut = 15.00 / 1000000;
801
+ }
802
+ else if (provider === 'gemini') {
803
+ rateIn = 0.35 / 1000000;
804
+ rateOut = 0.70 / 1000000;
805
+ }
806
+ else if (provider === 'glm') {
807
+ rateIn = 14.00 / 1000000; // Approximate for GLM-4
808
+ rateOut = 14.00 / 1000000;
809
+ }
810
+ return (input * rateIn) + (output * rateOut);
811
+ }
812
+ }
813
+ exports.ReplManager = ReplManager;