@indiccoder/mentis-cli 1.1.4 → 1.1.5
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/.claude/settings.local.json +8 -0
- package/.mentis/session.json +15 -0
- package/.mentis/sessions/1769189035730.json +23 -0
- package/.mentis/sessions/1769189569160.json +23 -0
- package/.mentis/sessions/1769767538672.json +23 -0
- package/.mentis/sessions/1769767785155.json +23 -0
- package/.mentis/sessions/1769768745802.json +23 -0
- package/.mentis/sessions/1769769600884.json +31 -0
- package/.mentis/sessions/1769770030160.json +31 -0
- package/.mentis/sessions/1769770606004.json +78 -0
- package/.mentis/sessions/1769771084515.json +141 -0
- package/.mentis/sessions/1769881926630.json +57 -0
- package/README.md +17 -0
- package/dist/checkpoint/CheckpointManager.js +92 -0
- package/dist/debug_google.js +61 -0
- package/dist/debug_lite.js +49 -0
- package/dist/debug_lite_headers.js +57 -0
- package/dist/debug_search.js +16 -0
- package/dist/index.js +10 -0
- package/dist/mcp/JsonRpcClient.js +16 -0
- package/dist/mcp/McpConfig.js +132 -0
- package/dist/mcp/McpManager.js +189 -0
- package/dist/repl/PersistentShell.js +20 -1
- package/dist/repl/ReplManager.js +410 -138
- package/dist/tools/AskQuestionTool.js +172 -0
- package/dist/tools/EditFileTool.js +141 -0
- package/dist/tools/FileTools.js +7 -1
- package/dist/tools/PlanModeTool.js +53 -0
- package/dist/tools/WebSearchTool.js +190 -27
- package/dist/ui/DiffViewer.js +110 -0
- package/dist/ui/InputBox.js +16 -2
- package/dist/ui/MultiFileSelector.js +123 -0
- package/dist/ui/PlanModeUI.js +105 -0
- package/dist/ui/ToolExecutor.js +154 -0
- package/dist/ui/UIManager.js +12 -2
- package/docs/MCP_INTEGRATION.md +290 -0
- package/google_dump.html +18 -0
- package/lite_dump.html +176 -0
- package/lite_headers_dump.html +176 -0
- package/package.json +16 -5
- package/scripts/test_exa_mcp.ts +90 -0
- package/src/checkpoint/CheckpointManager.ts +102 -0
- package/src/debug_google.ts +30 -0
- package/src/debug_lite.ts +18 -0
- package/src/debug_lite_headers.ts +25 -0
- package/src/debug_search.ts +18 -0
- package/src/index.ts +12 -0
- package/src/mcp/JsonRpcClient.ts +19 -0
- package/src/mcp/McpConfig.ts +153 -0
- package/src/mcp/McpManager.ts +224 -0
- package/src/repl/PersistentShell.ts +24 -1
- package/src/repl/ReplManager.ts +1521 -1204
- package/src/tools/AskQuestionTool.ts +197 -0
- package/src/tools/EditFileTool.ts +172 -0
- package/src/tools/FileTools.ts +3 -0
- package/src/tools/PlanModeTool.ts +50 -0
- package/src/tools/WebSearchTool.ts +235 -63
- package/src/ui/DiffViewer.ts +117 -0
- package/src/ui/InputBox.ts +17 -2
- package/src/ui/MultiFileSelector.ts +135 -0
- package/src/ui/PlanModeUI.ts +121 -0
- package/src/ui/ToolExecutor.ts +182 -0
- package/src/ui/UIManager.ts +15 -2
- package/console.log(tick) +0 -0
package/dist/repl/ReplManager.js
CHANGED
|
@@ -45,13 +45,16 @@ const OpenAIClient_1 = require("../llm/OpenAIClient");
|
|
|
45
45
|
const ContextManager_1 = require("../context/ContextManager");
|
|
46
46
|
const UIManager_1 = require("../ui/UIManager");
|
|
47
47
|
const InputBox_1 = require("../ui/InputBox");
|
|
48
|
+
const DiffViewer_1 = require("../ui/DiffViewer");
|
|
49
|
+
const ToolExecutor_1 = require("../ui/ToolExecutor");
|
|
50
|
+
const PlanModeUI_1 = require("../ui/PlanModeUI");
|
|
48
51
|
const FileTools_1 = require("../tools/FileTools");
|
|
49
52
|
const SearchTools_1 = require("../tools/SearchTools");
|
|
50
53
|
const PersistentShellTool_1 = require("../tools/PersistentShellTool");
|
|
51
54
|
const PersistentShell_1 = require("./PersistentShell");
|
|
52
55
|
const WebSearchTool_1 = require("../tools/WebSearchTool");
|
|
53
56
|
const GitTools_1 = require("../tools/GitTools");
|
|
54
|
-
const
|
|
57
|
+
const McpManager_1 = require("../mcp/McpManager");
|
|
55
58
|
const CheckpointManager_1 = require("../checkpoint/CheckpointManager");
|
|
56
59
|
const SkillsManager_1 = require("../skills/SkillsManager");
|
|
57
60
|
const LoadSkillTool_1 = require("../skills/LoadSkillTool");
|
|
@@ -83,10 +86,14 @@ class ReplManager {
|
|
|
83
86
|
this.contextVisualizer = new ContextVisualizer_1.ContextVisualizer();
|
|
84
87
|
this.conversationCompacter = new ConversationCompacter_1.ConversationCompacter();
|
|
85
88
|
this.commandManager = new CommandManager_1.CommandManager();
|
|
89
|
+
this.mcpManager = new McpManager_1.McpManager();
|
|
86
90
|
this.shell = new PersistentShell_1.PersistentShell();
|
|
87
91
|
// Create tools array without skill tools first
|
|
88
92
|
this.tools = [
|
|
93
|
+
new FileTools_1.PlanModeTool(), // AI can suggest plan mode for complex tasks
|
|
94
|
+
new FileTools_1.AskQuestionTool(), // For plan mode questions
|
|
89
95
|
new FileTools_1.WriteFileTool(),
|
|
96
|
+
new FileTools_1.EditFileTool(),
|
|
90
97
|
new FileTools_1.ReadFileTool(),
|
|
91
98
|
new FileTools_1.ListDirTool(),
|
|
92
99
|
new SearchTools_1.SearchFileTool(), // grep
|
|
@@ -123,6 +130,24 @@ class ReplManager {
|
|
|
123
130
|
this.tools.push(new LoadSkillTool_1.LoadSkillTool(this.skillsManager, (skill) => {
|
|
124
131
|
this.activeSkill = skill ? skill.name : null;
|
|
125
132
|
}), new LoadSkillTool_1.ListSkillsTool(this.skillsManager), new LoadSkillTool_1.ReadSkillFileTool(this.skillsManager), new SlashCommandTool_1.SlashCommandTool(this.commandManager), new SlashCommandTool_1.ListCommandsTool(this.commandManager));
|
|
133
|
+
// Auto-connect to MCP servers
|
|
134
|
+
await this.mcpManager.autoConnect();
|
|
135
|
+
this.refreshToolsFromMcp();
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Refresh tools list from MCP connections
|
|
139
|
+
*/
|
|
140
|
+
refreshToolsFromMcp() {
|
|
141
|
+
// Remove existing MCP tools (keep core tools)
|
|
142
|
+
this.tools = this.tools.filter(tool => !tool.name.startsWith('mcp_') &&
|
|
143
|
+
!['load_skill', 'list_skills', 'read_skill_file', 'slash_command', 'list_commands'].includes(tool.name));
|
|
144
|
+
// Add MCP tools
|
|
145
|
+
const mcpTools = this.mcpManager.getAllTools();
|
|
146
|
+
this.tools.push(...mcpTools);
|
|
147
|
+
// Re-add skill tools
|
|
148
|
+
this.tools.push(new LoadSkillTool_1.LoadSkillTool(this.skillsManager, (skill) => {
|
|
149
|
+
this.activeSkill = skill ? skill.name : null;
|
|
150
|
+
}), new LoadSkillTool_1.ListSkillsTool(this.skillsManager), new LoadSkillTool_1.ReadSkillFileTool(this.skillsManager), new SlashCommandTool_1.SlashCommandTool(this.commandManager), new SlashCommandTool_1.ListCommandsTool(this.commandManager));
|
|
126
151
|
}
|
|
127
152
|
/**
|
|
128
153
|
* Check if a tool is allowed by the currently active skill
|
|
@@ -147,7 +172,7 @@ class ReplManager {
|
|
|
147
172
|
'list_dir': 'ListDir',
|
|
148
173
|
'search_file': 'SearchFile',
|
|
149
174
|
'run_shell': 'RunShell',
|
|
150
|
-
'
|
|
175
|
+
'search_web': 'WebSearch',
|
|
151
176
|
'git_status': 'GitStatus',
|
|
152
177
|
'git_diff': 'GitDiff',
|
|
153
178
|
'git_commit': 'GitCommit',
|
|
@@ -207,10 +232,17 @@ class ReplManager {
|
|
|
207
232
|
});
|
|
208
233
|
// Auto-resume if --resume flag is set
|
|
209
234
|
if (this.options.resume) {
|
|
210
|
-
|
|
235
|
+
// Prefer local (per-directory) session, fall back to global
|
|
236
|
+
const cwd = process.cwd();
|
|
237
|
+
let cp = this.checkpointManager.loadLocalSession(cwd);
|
|
238
|
+
let source = 'local';
|
|
239
|
+
if (!cp) {
|
|
240
|
+
cp = this.checkpointManager.load('latest');
|
|
241
|
+
source = 'global';
|
|
242
|
+
}
|
|
211
243
|
if (cp) {
|
|
212
244
|
this.history = cp.history;
|
|
213
|
-
console.log(chalk_1.default.green(`\n✓ Resumed session from ${new Date(cp.timestamp).toLocaleString()}`));
|
|
245
|
+
console.log(chalk_1.default.green(`\n✓ Resumed ${source} session from ${new Date(cp.timestamp).toLocaleString()}`));
|
|
214
246
|
console.log(chalk_1.default.dim(` Messages: ${this.history.length}\n`));
|
|
215
247
|
}
|
|
216
248
|
else {
|
|
@@ -256,7 +288,14 @@ class ReplManager {
|
|
|
256
288
|
await this.handleCommand(input);
|
|
257
289
|
continue;
|
|
258
290
|
}
|
|
259
|
-
|
|
291
|
+
// Wrap handleChat in try-catch to prevent REPL from entering inconsistent state
|
|
292
|
+
try {
|
|
293
|
+
await this.handleChat(input);
|
|
294
|
+
}
|
|
295
|
+
catch (error) {
|
|
296
|
+
console.error(chalk_1.default.red(`Error: ${error.message}`));
|
|
297
|
+
console.error(chalk_1.default.dim('The REPL has recovered. You can continue using the CLI.'));
|
|
298
|
+
}
|
|
260
299
|
}
|
|
261
300
|
}
|
|
262
301
|
async handleCommand(input) {
|
|
@@ -279,7 +318,6 @@ class ReplManager {
|
|
|
279
318
|
console.log(' /skills <list|show|create|validate> - Manage Agent Skills');
|
|
280
319
|
console.log(' /commands <list|create|validate> - Manage Custom Commands');
|
|
281
320
|
console.log(' /resume - Resume last session');
|
|
282
|
-
console.log(' /checkpoint <save|load|list> [name] - Manage checkpoints');
|
|
283
321
|
console.log(' /search <query> - Search codebase');
|
|
284
322
|
console.log(' /run <cmd> - Run shell command');
|
|
285
323
|
console.log(' /commit [msg] - Git commit all changes');
|
|
@@ -287,15 +325,15 @@ class ReplManager {
|
|
|
287
325
|
break;
|
|
288
326
|
case '/plan':
|
|
289
327
|
this.mode = 'PLAN';
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
this.mode = 'BUILD';
|
|
294
|
-
console.log(chalk_1.default.yellow('Switched to BUILD mode.'));
|
|
328
|
+
UIManager_1.UIManager.logBullet('Entered plan mode', 'magenta');
|
|
329
|
+
PlanModeUI_1.PlanModeUI.showPlanHeader();
|
|
330
|
+
PlanModeUI_1.PlanModeUI.showQAHistory();
|
|
295
331
|
break;
|
|
296
332
|
case '/build':
|
|
297
333
|
this.mode = 'BUILD';
|
|
298
|
-
|
|
334
|
+
UIManager_1.UIManager.logBullet('Entered build mode', 'green');
|
|
335
|
+
PlanModeUI_1.PlanModeUI.showPlanSummary();
|
|
336
|
+
UIManager_1.UIManager.logSystem('Mentis is building the solution...');
|
|
299
337
|
break;
|
|
300
338
|
case '/model':
|
|
301
339
|
await this.handleModelCommand(args);
|
|
@@ -313,9 +351,6 @@ class ReplManager {
|
|
|
313
351
|
case '/resume':
|
|
314
352
|
await this.handleResumeCommand();
|
|
315
353
|
break;
|
|
316
|
-
case '/checkpoint':
|
|
317
|
-
await this.handleCheckpointCommand(args);
|
|
318
|
-
break;
|
|
319
354
|
case '/clear':
|
|
320
355
|
this.history = [];
|
|
321
356
|
this.contextManager.clear();
|
|
@@ -344,10 +379,13 @@ class ReplManager {
|
|
|
344
379
|
await this.handleConfigCommand();
|
|
345
380
|
break;
|
|
346
381
|
case '/exit':
|
|
347
|
-
// Auto-save on exit
|
|
382
|
+
// Auto-save on exit (both local and global)
|
|
383
|
+
const cwd = process.cwd();
|
|
384
|
+
this.checkpointManager.saveLocalSession(cwd, this.history, this.contextManager.getFiles());
|
|
348
385
|
this.checkpointManager.save('latest', this.history, this.contextManager.getFiles());
|
|
349
386
|
this.shell.kill(); // Kill the shell process
|
|
350
|
-
|
|
387
|
+
this.mcpManager.disconnectAll(); // Disconnect all MCP connections
|
|
388
|
+
console.log(chalk_1.default.green('Session saved to .mentis/sessions/. Goodbye!'));
|
|
351
389
|
process.exit(0);
|
|
352
390
|
break;
|
|
353
391
|
case '/update':
|
|
@@ -372,6 +410,26 @@ class ReplManager {
|
|
|
372
410
|
console.log(chalk_1.default.red(`Unknown command: ${command}`));
|
|
373
411
|
}
|
|
374
412
|
}
|
|
413
|
+
getLoadingMessage() {
|
|
414
|
+
const messages = [
|
|
415
|
+
"Reticulating splines...",
|
|
416
|
+
"Consulting the silicon oracle...",
|
|
417
|
+
"Compiling neural pathways...",
|
|
418
|
+
"Optimizing flux capacitors...",
|
|
419
|
+
"Analyzing project structure...",
|
|
420
|
+
"Deciphering your intent...",
|
|
421
|
+
"Brewing digital coffee...",
|
|
422
|
+
"Checking for infinite loops...",
|
|
423
|
+
"Connecting to the matrix...",
|
|
424
|
+
"Calculating the answer (42?)...",
|
|
425
|
+
"Refactoring the universe...",
|
|
426
|
+
"Downloading more RAM...",
|
|
427
|
+
"Searching for bugs...",
|
|
428
|
+
"Asking the rubber duck...",
|
|
429
|
+
"Hyperspacing..."
|
|
430
|
+
];
|
|
431
|
+
return messages[Math.floor(Math.random() * messages.length)];
|
|
432
|
+
}
|
|
375
433
|
async handleChat(input) {
|
|
376
434
|
const context = this.contextManager.getContextString();
|
|
377
435
|
const skillsContext = this.skillsManager.getSkillsContext();
|
|
@@ -397,7 +455,8 @@ class ReplManager {
|
|
|
397
455
|
fullInput = `${context}\n\nUser Question: ${fullInput}`;
|
|
398
456
|
}
|
|
399
457
|
this.history.push({ role: 'user', content: fullInput });
|
|
400
|
-
|
|
458
|
+
const msg = this.getLoadingMessage();
|
|
459
|
+
let spinner = (0, ora_1.default)({ text: ` ${msg}`, color: 'cyan' }).start();
|
|
401
460
|
const controller = new AbortController();
|
|
402
461
|
// Setup cancellation listener
|
|
403
462
|
const keyListener = (str, key) => {
|
|
@@ -438,59 +497,63 @@ class ReplManager {
|
|
|
438
497
|
const toolName = toolCall.function.name;
|
|
439
498
|
const toolArgsStr = toolCall.function.arguments;
|
|
440
499
|
const toolArgs = JSON.parse(toolArgsStr);
|
|
441
|
-
//
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
displayArgs = displayArgs.substring(0, 100) + '...';
|
|
445
|
-
}
|
|
446
|
-
console.log(chalk_1.default.dim(` [Action] ${toolName}(${displayArgs})`));
|
|
447
|
-
// Safety check for write_file
|
|
500
|
+
// Show tool execution with visual feedback
|
|
501
|
+
ToolExecutor_1.ToolExecutor.showInline(toolName, toolArgs);
|
|
502
|
+
// Safety checks for write/edit operations
|
|
448
503
|
// Skip confirmation if tool is allowed by active skill
|
|
449
|
-
|
|
504
|
+
let approved = true;
|
|
505
|
+
const needsApproval = (toolName === 'write_file' || toolName === 'edit_file' || toolName === 'read_file')
|
|
506
|
+
&& !this.isToolAllowedBySkill(toolName === 'read_file' ? 'Read' : 'Write');
|
|
507
|
+
if (needsApproval) {
|
|
450
508
|
// Pause cancellation listener during user interaction
|
|
451
509
|
if (process.stdin.isTTY) {
|
|
452
510
|
process.stdin.removeListener('keypress', keyListener);
|
|
453
511
|
process.stdin.setRawMode(false);
|
|
454
|
-
process.stdin.pause();
|
|
512
|
+
process.stdin.pause();
|
|
513
|
+
}
|
|
514
|
+
// Handle write_file with diff preview
|
|
515
|
+
if (toolName === 'write_file') {
|
|
516
|
+
approved = await this.handleWriteApproval(toolArgs.filePath, toolArgs.content);
|
|
517
|
+
}
|
|
518
|
+
// Handle edit_file with diff preview
|
|
519
|
+
if (toolName === 'edit_file') {
|
|
520
|
+
approved = await this.handleEditApproval(toolArgs);
|
|
521
|
+
}
|
|
522
|
+
// Handle read_file with multi-file selector
|
|
523
|
+
if (toolName === 'read_file') {
|
|
524
|
+
approved = await this.handleReadApproval(toolArgs.filePath);
|
|
455
525
|
}
|
|
456
|
-
spinner.stop(); // Stop spinner to allow input
|
|
457
|
-
const { confirm } = await inquirer_1.default.prompt([
|
|
458
|
-
{
|
|
459
|
-
type: 'confirm',
|
|
460
|
-
name: 'confirm',
|
|
461
|
-
message: `Allow writing to ${chalk_1.default.yellow(toolArgs.filePath)}?`,
|
|
462
|
-
default: true
|
|
463
|
-
}
|
|
464
|
-
]);
|
|
465
526
|
// Resume cancellation listener
|
|
466
527
|
if (process.stdin.isTTY) {
|
|
467
528
|
process.stdin.setRawMode(true);
|
|
468
|
-
process.stdin.resume();
|
|
529
|
+
process.stdin.resume();
|
|
469
530
|
process.stdin.on('keypress', keyListener);
|
|
470
531
|
}
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
// If finished, lines following loop will start spinner.
|
|
482
|
-
continue;
|
|
483
|
-
}
|
|
484
|
-
spinner = (0, ora_1.default)('Executing...').start();
|
|
532
|
+
}
|
|
533
|
+
if (!approved) {
|
|
534
|
+
this.history.push({
|
|
535
|
+
role: 'tool',
|
|
536
|
+
tool_call_id: toolCall.id,
|
|
537
|
+
name: toolName,
|
|
538
|
+
content: 'Error: User rejected operation.'
|
|
539
|
+
});
|
|
540
|
+
console.log(chalk_1.default.red(' Action cancelled by user.'));
|
|
541
|
+
continue;
|
|
485
542
|
}
|
|
486
543
|
const tool = this.tools.find(t => t.name === toolName);
|
|
487
544
|
let result = '';
|
|
488
545
|
if (tool) {
|
|
489
546
|
try {
|
|
490
|
-
// Tools typically run synchronously or promise-based.
|
|
491
|
-
// Verify if we want Tools to be cancellable?
|
|
492
|
-
// For now, if aborted during tool, we let tool finish but stop loop.
|
|
493
547
|
result = await tool.execute(toolArgs);
|
|
548
|
+
// Record Q&A for ask_question tool in plan mode
|
|
549
|
+
if (toolName === 'ask_question' && this.mode === 'PLAN') {
|
|
550
|
+
PlanModeUI_1.PlanModeUI.recordQA(toolArgs.question, result);
|
|
551
|
+
}
|
|
552
|
+
// Handle plan mode switch approval
|
|
553
|
+
if (toolName === 'enter_plan_mode' && result.includes('User approved')) {
|
|
554
|
+
this.mode = 'PLAN';
|
|
555
|
+
UIManager_1.UIManager.logBullet('Auto-switched to plan mode', 'magenta');
|
|
556
|
+
}
|
|
494
557
|
}
|
|
495
558
|
catch (e) {
|
|
496
559
|
result = `Error: ${e.message}`;
|
|
@@ -499,9 +562,6 @@ class ReplManager {
|
|
|
499
562
|
else {
|
|
500
563
|
result = `Error: Tool ${toolName} not found.`;
|
|
501
564
|
}
|
|
502
|
-
if (spinner.isSpinning) {
|
|
503
|
-
spinner.stop();
|
|
504
|
-
}
|
|
505
565
|
this.history.push({
|
|
506
566
|
role: 'tool',
|
|
507
567
|
tool_call_id: toolCall.id,
|
|
@@ -511,7 +571,8 @@ class ReplManager {
|
|
|
511
571
|
}
|
|
512
572
|
if (controller.signal.aborted)
|
|
513
573
|
throw new Error('Request cancelled by user');
|
|
514
|
-
|
|
574
|
+
const msg = this.getLoadingMessage();
|
|
575
|
+
spinner = (0, ora_1.default)({ text: ` ${msg}`, color: 'cyan' }).start();
|
|
515
576
|
// Get next response
|
|
516
577
|
response = await this.modelClient.chat(this.history, this.tools.map(t => ({
|
|
517
578
|
type: 'function',
|
|
@@ -525,7 +586,7 @@ class ReplManager {
|
|
|
525
586
|
spinner.stop();
|
|
526
587
|
console.log('');
|
|
527
588
|
if (response.content) {
|
|
528
|
-
|
|
589
|
+
UIManager_1.UIManager.logBullet('Mentis:', 'magenta');
|
|
529
590
|
console.log((0, marked_1.marked)(response.content));
|
|
530
591
|
if (response.usage) {
|
|
531
592
|
const { input_tokens, output_tokens } = response.usage;
|
|
@@ -818,100 +879,251 @@ class ReplManager {
|
|
|
818
879
|
console.log(chalk_1.default.green(`Switched to ${provider} ${model ? `using model ${model}` : ''}`));
|
|
819
880
|
}
|
|
820
881
|
async handleMcpCommand(args) {
|
|
821
|
-
if (args.length
|
|
822
|
-
console.log(chalk_1.default.red('Usage: /mcp <connect|
|
|
882
|
+
if (args.length === 0) {
|
|
883
|
+
console.log(chalk_1.default.red('Usage: /mcp <list|connect|disconnect|add|remove|test|config> [args]'));
|
|
884
|
+
console.log(chalk_1.default.dim('\nExamples:'));
|
|
885
|
+
console.log(chalk_1.default.dim(' /mcp list - List all MCP servers'));
|
|
886
|
+
console.log(chalk_1.default.dim(' /mcp connect Exa\\ Search - Connect to Exa Search'));
|
|
887
|
+
console.log(chalk_1.default.dim(' /mcp disconnect all - Disconnect all servers'));
|
|
888
|
+
console.log(chalk_1.default.dim(' /mcp add MyServer npx -y @my/mcp-server'));
|
|
889
|
+
console.log(chalk_1.default.dim(' /mcp test Exa\\ Search - Test connection'));
|
|
890
|
+
console.log(chalk_1.default.dim(' /mcp config - Show configuration'));
|
|
823
891
|
return;
|
|
824
892
|
}
|
|
825
893
|
const action = args[0];
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
894
|
+
switch (action) {
|
|
895
|
+
case 'list':
|
|
896
|
+
await this.mcpManager.listServers();
|
|
897
|
+
break;
|
|
898
|
+
case 'connect':
|
|
899
|
+
if (args.length < 2) {
|
|
900
|
+
// Show interactive list of available servers
|
|
901
|
+
const availableServers = this.mcpManager.getAvailableServers();
|
|
902
|
+
if (availableServers.length === 0) {
|
|
903
|
+
console.log(chalk_1.default.yellow('No MCP servers configured. Use /mcp add to add one.'));
|
|
904
|
+
return;
|
|
905
|
+
}
|
|
906
|
+
const { serverName } = await inquirer_1.default.prompt([{
|
|
907
|
+
type: 'list',
|
|
908
|
+
name: 'serverName',
|
|
909
|
+
message: 'Select server to connect:',
|
|
910
|
+
choices: availableServers.map(s => s.name)
|
|
911
|
+
}]);
|
|
912
|
+
await this.mcpManager.connectToServer(serverName);
|
|
913
|
+
this.refreshToolsFromMcp();
|
|
914
|
+
}
|
|
915
|
+
else {
|
|
916
|
+
const serverName = args.slice(1).join(' ');
|
|
917
|
+
await this.mcpManager.connectToServer(serverName);
|
|
918
|
+
this.refreshToolsFromMcp();
|
|
919
|
+
}
|
|
920
|
+
break;
|
|
921
|
+
case 'disconnect':
|
|
922
|
+
if (args.length < 2) {
|
|
923
|
+
// Interactive disconnect
|
|
924
|
+
const connectedServers = this.mcpManager.getServerNames();
|
|
925
|
+
if (connectedServers.length === 0) {
|
|
926
|
+
console.log(chalk_1.default.yellow('No MCP connections to disconnect.'));
|
|
927
|
+
return;
|
|
928
|
+
}
|
|
929
|
+
const { serverName } = await inquirer_1.default.prompt([{
|
|
930
|
+
type: 'list',
|
|
931
|
+
name: 'serverName',
|
|
932
|
+
message: 'Select server to disconnect:',
|
|
933
|
+
choices: [...connectedServers, 'all']
|
|
934
|
+
}]);
|
|
935
|
+
if (serverName === 'all') {
|
|
936
|
+
this.mcpManager.disconnectAll();
|
|
937
|
+
this.refreshToolsFromMcp();
|
|
938
|
+
}
|
|
939
|
+
else {
|
|
940
|
+
await this.mcpManager.disconnectFromServer(serverName);
|
|
941
|
+
this.refreshToolsFromMcp();
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
else {
|
|
945
|
+
const serverName = args.slice(1).join(' ');
|
|
946
|
+
if (serverName === 'all') {
|
|
947
|
+
this.mcpManager.disconnectAll();
|
|
948
|
+
this.refreshToolsFromMcp();
|
|
949
|
+
}
|
|
950
|
+
else {
|
|
951
|
+
await this.mcpManager.disconnectFromServer(serverName);
|
|
952
|
+
this.refreshToolsFromMcp();
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
break;
|
|
956
|
+
case 'add':
|
|
957
|
+
if (args.length < 3) {
|
|
958
|
+
console.log(chalk_1.default.red('Usage: /mcp add <name> <command> [args...]'));
|
|
959
|
+
console.log(chalk_1.default.dim('\nExamples:'));
|
|
960
|
+
console.log(chalk_1.default.dim(' /mcp add "My Server" npx -y @my/mcp-server'));
|
|
961
|
+
console.log(chalk_1.default.dim(' /mcp add "Local Server" node /path/to/server.js'));
|
|
962
|
+
return;
|
|
963
|
+
}
|
|
964
|
+
const name = args[1];
|
|
965
|
+
const command = args[2];
|
|
966
|
+
const mcpArgs = args.slice(3);
|
|
967
|
+
const { description } = await inquirer_1.default.prompt([{
|
|
968
|
+
type: 'input',
|
|
969
|
+
name: 'description',
|
|
970
|
+
message: 'Description (optional):',
|
|
971
|
+
default: ''
|
|
972
|
+
}]);
|
|
973
|
+
await this.mcpManager.addServer(name, command, mcpArgs, description);
|
|
974
|
+
break;
|
|
975
|
+
case 'remove':
|
|
976
|
+
if (args.length < 2) {
|
|
977
|
+
// Interactive remove
|
|
978
|
+
const availableServers = this.mcpManager.getAvailableServers();
|
|
979
|
+
if (availableServers.length === 0) {
|
|
980
|
+
console.log(chalk_1.default.yellow('No MCP servers configured.'));
|
|
981
|
+
return;
|
|
982
|
+
}
|
|
983
|
+
const { serverName } = await inquirer_1.default.prompt([{
|
|
984
|
+
type: 'list',
|
|
985
|
+
name: 'serverName',
|
|
986
|
+
message: 'Select server to remove:',
|
|
987
|
+
choices: availableServers.map(s => s.name)
|
|
988
|
+
}]);
|
|
989
|
+
await this.mcpManager.removeServer(serverName);
|
|
990
|
+
}
|
|
991
|
+
else {
|
|
992
|
+
const serverName = args.slice(1).join(' ');
|
|
993
|
+
await this.mcpManager.removeServer(serverName);
|
|
994
|
+
}
|
|
995
|
+
break;
|
|
996
|
+
case 'test':
|
|
997
|
+
if (args.length < 2) {
|
|
998
|
+
// Interactive test
|
|
999
|
+
const connectedServers = this.mcpManager.getServerNames();
|
|
1000
|
+
if (connectedServers.length === 0) {
|
|
1001
|
+
console.log(chalk_1.default.yellow('No MCP connections to test.'));
|
|
1002
|
+
return;
|
|
1003
|
+
}
|
|
1004
|
+
const { serverName } = await inquirer_1.default.prompt([{
|
|
1005
|
+
type: 'list',
|
|
1006
|
+
name: 'serverName',
|
|
1007
|
+
message: 'Select server to test:',
|
|
1008
|
+
choices: connectedServers
|
|
1009
|
+
}]);
|
|
1010
|
+
await this.mcpManager.testConnection(serverName);
|
|
1011
|
+
}
|
|
1012
|
+
else {
|
|
1013
|
+
const serverName = args.slice(1).join(' ');
|
|
1014
|
+
await this.mcpManager.testConnection(serverName);
|
|
1015
|
+
}
|
|
1016
|
+
break;
|
|
1017
|
+
case 'config':
|
|
1018
|
+
const config = this.mcpManager.getConfig().getConfig();
|
|
1019
|
+
console.log(chalk_1.default.cyan('\nMCP Configuration:\n'));
|
|
1020
|
+
console.log(JSON.stringify(config, null, 2));
|
|
1021
|
+
console.log(chalk_1.default.dim(`\nConfig file: ${require('os').homedir()}/.mentis/mcp.json`));
|
|
1022
|
+
break;
|
|
1023
|
+
default:
|
|
1024
|
+
console.log(chalk_1.default.red(`Unknown MCP action: ${action}`));
|
|
1025
|
+
console.log(chalk_1.default.yellow('Available actions: list, connect, disconnect, add, remove, test, config'));
|
|
850
1026
|
}
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
1027
|
+
}
|
|
1028
|
+
async handleResumeCommand() {
|
|
1029
|
+
const cwd = process.cwd();
|
|
1030
|
+
const localSessions = this.checkpointManager.listLocalSessions(cwd);
|
|
1031
|
+
const globalCheckpoints = this.checkpointManager.list();
|
|
1032
|
+
if (localSessions.length === 0 && globalCheckpoints.length === 0) {
|
|
1033
|
+
console.log(chalk_1.default.yellow('No previous sessions found to resume.'));
|
|
1034
|
+
return;
|
|
1035
|
+
}
|
|
1036
|
+
// Header like Claude's "Resume Session"
|
|
1037
|
+
console.log('');
|
|
1038
|
+
console.log(chalk_1.default.cyan.bold('Resume Session'));
|
|
1039
|
+
console.log('');
|
|
1040
|
+
// Helper for relative time
|
|
1041
|
+
const relativeTime = (ts) => {
|
|
1042
|
+
const diff = Date.now() - ts;
|
|
1043
|
+
const mins = Math.floor(diff / 60000);
|
|
1044
|
+
const hours = Math.floor(diff / 3600000);
|
|
1045
|
+
const days = Math.floor(diff / 86400000);
|
|
1046
|
+
if (days > 0)
|
|
1047
|
+
return `${days} day${days > 1 ? 's' : ''} ago`;
|
|
1048
|
+
if (hours > 0)
|
|
1049
|
+
return `${hours} hour${hours > 1 ? 's' : ''} ago`;
|
|
1050
|
+
if (mins > 0)
|
|
1051
|
+
return `${mins} min${mins > 1 ? 's' : ''} ago`;
|
|
1052
|
+
return 'just now';
|
|
1053
|
+
};
|
|
1054
|
+
// Build choices with simple single-line formatting
|
|
1055
|
+
const choices = [];
|
|
1056
|
+
// Local sessions first
|
|
1057
|
+
for (const session of localSessions) {
|
|
1058
|
+
const timeAgo = relativeTime(session.timestamp);
|
|
1059
|
+
const shortPreview = session.preview.substring(0, 30).replace(/\n/g, ' ');
|
|
1060
|
+
choices.push({
|
|
1061
|
+
name: `${shortPreview}... (${timeAgo}, ${session.messageCount} msgs)`,
|
|
1062
|
+
value: { type: 'local', id: session.id }
|
|
1063
|
+
});
|
|
1064
|
+
}
|
|
1065
|
+
// Global checkpoints as fallback
|
|
1066
|
+
if (localSessions.length === 0 && globalCheckpoints.length > 0) {
|
|
1067
|
+
for (const name of globalCheckpoints) {
|
|
1068
|
+
const cp = this.checkpointManager.load(name);
|
|
1069
|
+
const timeAgo = cp ? relativeTime(cp.timestamp) : '';
|
|
1070
|
+
const msgCount = cp?.history?.length || 0;
|
|
1071
|
+
const preview = cp?.history?.find(m => m.role === 'user')?.content?.substring(0, 40)?.replace(/\n/g, ' ') || name;
|
|
1072
|
+
choices.push({
|
|
1073
|
+
name: `${preview}${preview.length >= 40 ? '...' : ''} — ${timeAgo} · ${msgCount} msgs`,
|
|
1074
|
+
value: { type: 'global', id: name }
|
|
859
1075
|
});
|
|
860
1076
|
}
|
|
861
1077
|
}
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
new FileTools_1.WriteFileTool(),
|
|
870
|
-
new FileTools_1.ReadFileTool(),
|
|
871
|
-
new FileTools_1.ListDirTool(),
|
|
872
|
-
new SearchTools_1.SearchFileTool(),
|
|
873
|
-
new PersistentShellTool_1.PersistentShellTool(this.shell),
|
|
874
|
-
new WebSearchTool_1.WebSearchTool(),
|
|
875
|
-
new GitTools_1.GitStatusTool(),
|
|
876
|
-
new GitTools_1.GitDiffTool(),
|
|
877
|
-
new GitTools_1.GitCommitTool(),
|
|
878
|
-
new GitTools_1.GitPushTool(),
|
|
879
|
-
new GitTools_1.GitPullTool()
|
|
880
|
-
];
|
|
1078
|
+
// Show hint
|
|
1079
|
+
console.log(chalk_1.default.dim(' ↑/↓ to navigate · Enter to select · Esc to cancel'));
|
|
1080
|
+
console.log('');
|
|
1081
|
+
// Guard against empty choices (would crash inquirer)
|
|
1082
|
+
if (choices.length === 0) {
|
|
1083
|
+
console.log(chalk_1.default.yellow('No sessions available. Start a conversation and /exit to save.'));
|
|
1084
|
+
return;
|
|
881
1085
|
}
|
|
882
|
-
|
|
883
|
-
|
|
1086
|
+
const { selected } = await inquirer_1.default.prompt([{
|
|
1087
|
+
type: 'rawlist',
|
|
1088
|
+
name: 'selected',
|
|
1089
|
+
message: 'Pick a session:',
|
|
1090
|
+
choices,
|
|
1091
|
+
pageSize: 10
|
|
1092
|
+
}]);
|
|
1093
|
+
if (selected.type === 'local') {
|
|
1094
|
+
await this.loadLocalCheckpoint(cwd, selected.id);
|
|
884
1095
|
}
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
if (!this.checkpointManager.exists('latest')) {
|
|
888
|
-
console.log(chalk_1.default.yellow('No previous session found to resume.'));
|
|
889
|
-
return;
|
|
1096
|
+
else if (selected.type === 'global') {
|
|
1097
|
+
await this.loadCheckpoint(selected.id);
|
|
890
1098
|
}
|
|
891
|
-
await this.loadCheckpoint('latest');
|
|
892
1099
|
}
|
|
893
|
-
async
|
|
894
|
-
|
|
895
|
-
|
|
1100
|
+
async loadLocalCheckpoint(cwd, sessionId) {
|
|
1101
|
+
const cp = this.checkpointManager.loadLocalSession(cwd, sessionId);
|
|
1102
|
+
if (!cp) {
|
|
1103
|
+
console.log(chalk_1.default.red('Session not found.'));
|
|
896
1104
|
return;
|
|
897
1105
|
}
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
console.log(chalk_1.default.
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
}
|
|
907
|
-
else if (action === 'list') {
|
|
908
|
-
const points = this.checkpointManager.list();
|
|
909
|
-
console.log(chalk_1.default.cyan('Available Checkpoints:'));
|
|
910
|
-
points.forEach(p => console.log(` - ${p}`));
|
|
1106
|
+
this.history = cp.history;
|
|
1107
|
+
this.contextManager.clear();
|
|
1108
|
+
// Restore context files
|
|
1109
|
+
if (cp.files && cp.files.length > 0) {
|
|
1110
|
+
console.log(chalk_1.default.dim('Restoring context files...'));
|
|
1111
|
+
for (const file of cp.files) {
|
|
1112
|
+
await this.contextManager.addFile(file);
|
|
1113
|
+
}
|
|
911
1114
|
}
|
|
912
|
-
|
|
913
|
-
|
|
1115
|
+
console.log(chalk_1.default.green(`✓ Resumed session (${new Date(cp.timestamp).toLocaleString()})`));
|
|
1116
|
+
console.log(chalk_1.default.dim(` Messages: ${this.history.length}`));
|
|
1117
|
+
// Re-display last assistant message if any
|
|
1118
|
+
const lastMsg = this.history[this.history.length - 1];
|
|
1119
|
+
if (lastMsg && lastMsg.role === 'assistant' && lastMsg.content) {
|
|
1120
|
+
console.log(chalk_1.default.blue('\nLast response:'));
|
|
1121
|
+
const preview = lastMsg.content.length > 200
|
|
1122
|
+
? lastMsg.content.substring(0, 200) + '...'
|
|
1123
|
+
: lastMsg.content;
|
|
1124
|
+
console.log(chalk_1.default.dim(preview));
|
|
914
1125
|
}
|
|
1126
|
+
console.log('');
|
|
915
1127
|
}
|
|
916
1128
|
async loadCheckpoint(name) {
|
|
917
1129
|
const cp = this.checkpointManager.load(name);
|
|
@@ -1080,6 +1292,66 @@ class ReplManager {
|
|
|
1080
1292
|
const { validateCommands } = await Promise.resolve().then(() => __importStar(require('../commands/CommandCreator')));
|
|
1081
1293
|
await validateCommands(this.commandManager);
|
|
1082
1294
|
}
|
|
1295
|
+
/**
|
|
1296
|
+
* Handle write_file approval with diff preview
|
|
1297
|
+
*/
|
|
1298
|
+
async handleWriteApproval(filePath, content) {
|
|
1299
|
+
// Check if file exists and show diff
|
|
1300
|
+
const fullPath = path.resolve(filePath);
|
|
1301
|
+
if (fs.existsSync(fullPath)) {
|
|
1302
|
+
const oldContent = fs.readFileSync(fullPath, 'utf-8');
|
|
1303
|
+
DiffViewer_1.DiffViewer.showDiff(filePath, oldContent, content);
|
|
1304
|
+
}
|
|
1305
|
+
else {
|
|
1306
|
+
console.log('');
|
|
1307
|
+
console.log(chalk_1.default.cyan(`📄 Creating new file: ${filePath}`));
|
|
1308
|
+
console.log(chalk_1.default.dim('─'.repeat(60)));
|
|
1309
|
+
console.log(chalk_1.default.green('New file content:'));
|
|
1310
|
+
const preview = content.split('\n').slice(0, 10).join('\n');
|
|
1311
|
+
console.log(chalk_1.default.dim(preview));
|
|
1312
|
+
if (content.split('\n').length > 10) {
|
|
1313
|
+
console.log(chalk_1.default.dim('...'));
|
|
1314
|
+
}
|
|
1315
|
+
console.log(chalk_1.default.dim('─'.repeat(60)));
|
|
1316
|
+
}
|
|
1317
|
+
const { confirm } = await inquirer_1.default.prompt([
|
|
1318
|
+
{
|
|
1319
|
+
type: 'confirm',
|
|
1320
|
+
name: 'confirm',
|
|
1321
|
+
message: `Allow writing to ${chalk_1.default.yellow(filePath)}?`,
|
|
1322
|
+
default: true
|
|
1323
|
+
}
|
|
1324
|
+
]);
|
|
1325
|
+
return confirm;
|
|
1326
|
+
}
|
|
1327
|
+
/**
|
|
1328
|
+
* Handle edit_file approval with diff preview
|
|
1329
|
+
*/
|
|
1330
|
+
async handleEditApproval(args) {
|
|
1331
|
+
// Get diff from EditFileTool (which shows preview)
|
|
1332
|
+
const editTool = this.tools.find(t => t.name === 'edit_file');
|
|
1333
|
+
if (editTool) {
|
|
1334
|
+
const diffResult = await editTool.execute(args);
|
|
1335
|
+
console.log(diffResult);
|
|
1336
|
+
}
|
|
1337
|
+
const { confirm } = await inquirer_1.default.prompt([
|
|
1338
|
+
{
|
|
1339
|
+
type: 'confirm',
|
|
1340
|
+
name: 'confirm',
|
|
1341
|
+
message: `Apply edit to ${chalk_1.default.yellow(args.file_path)}?`,
|
|
1342
|
+
default: true
|
|
1343
|
+
}
|
|
1344
|
+
]);
|
|
1345
|
+
return confirm;
|
|
1346
|
+
}
|
|
1347
|
+
/**
|
|
1348
|
+
* Handle read_file approval (currently auto-approves, but can be enhanced)
|
|
1349
|
+
*/
|
|
1350
|
+
async handleReadApproval(filePath) {
|
|
1351
|
+
// For now, auto-approve single file reads
|
|
1352
|
+
// In the future, could batch multiple reads into a single selector
|
|
1353
|
+
return true;
|
|
1354
|
+
}
|
|
1083
1355
|
estimateCost(input, output) {
|
|
1084
1356
|
const config = this.configManager.getConfig();
|
|
1085
1357
|
const provider = config.defaultProvider;
|