@indiccoder/mentis-cli 1.1.3 → 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.
Files changed (82) hide show
  1. package/.claude/settings.local.json +8 -0
  2. package/.mentis/session.json +15 -0
  3. package/.mentis/sessions/1769189035730.json +23 -0
  4. package/.mentis/sessions/1769189569160.json +23 -0
  5. package/.mentis/sessions/1769767538672.json +23 -0
  6. package/.mentis/sessions/1769767785155.json +23 -0
  7. package/.mentis/sessions/1769768745802.json +23 -0
  8. package/.mentis/sessions/1769769600884.json +31 -0
  9. package/.mentis/sessions/1769770030160.json +31 -0
  10. package/.mentis/sessions/1769770606004.json +78 -0
  11. package/.mentis/sessions/1769771084515.json +141 -0
  12. package/.mentis/sessions/1769881926630.json +57 -0
  13. package/ARCHITECTURE.md +267 -0
  14. package/CONTRIBUTING.md +209 -0
  15. package/README.md +17 -0
  16. package/dist/checkpoint/CheckpointManager.js +92 -0
  17. package/dist/commands/Command.js +15 -1
  18. package/dist/commands/CommandManager.js +30 -5
  19. package/dist/commands/__tests__/CommandManager.test.js +70 -0
  20. package/dist/debug_google.js +61 -0
  21. package/dist/debug_lite.js +49 -0
  22. package/dist/debug_lite_headers.js +57 -0
  23. package/dist/debug_search.js +16 -0
  24. package/dist/index.js +33 -0
  25. package/dist/mcp/JsonRpcClient.js +16 -0
  26. package/dist/mcp/McpConfig.js +132 -0
  27. package/dist/mcp/McpManager.js +189 -0
  28. package/dist/repl/PersistentShell.js +20 -1
  29. package/dist/repl/ReplManager.js +410 -138
  30. package/dist/skills/Skill.js +17 -2
  31. package/dist/skills/SkillsManager.js +28 -3
  32. package/dist/skills/__tests__/SkillsManager.test.js +62 -0
  33. package/dist/tools/AskQuestionTool.js +172 -0
  34. package/dist/tools/EditFileTool.js +141 -0
  35. package/dist/tools/FileTools.js +7 -1
  36. package/dist/tools/PlanModeTool.js +53 -0
  37. package/dist/tools/WebSearchTool.js +190 -27
  38. package/dist/ui/DiffViewer.js +110 -0
  39. package/dist/ui/InputBox.js +16 -2
  40. package/dist/ui/MultiFileSelector.js +123 -0
  41. package/dist/ui/PlanModeUI.js +105 -0
  42. package/dist/ui/ToolExecutor.js +154 -0
  43. package/dist/ui/UIManager.js +12 -2
  44. package/dist/utils/__mocks__/chalk.js +20 -0
  45. package/dist/utils/__tests__/ContextVisualizer.test.js +95 -0
  46. package/docs/MCP_INTEGRATION.md +290 -0
  47. package/google_dump.html +18 -0
  48. package/lite_dump.html +176 -0
  49. package/lite_headers_dump.html +176 -0
  50. package/package.json +34 -2
  51. package/scripts/test_exa_mcp.ts +90 -0
  52. package/src/checkpoint/CheckpointManager.ts +102 -0
  53. package/src/commands/Command.ts +64 -13
  54. package/src/commands/CommandManager.ts +26 -5
  55. package/src/commands/__tests__/CommandManager.test.ts +83 -0
  56. package/src/debug_google.ts +30 -0
  57. package/src/debug_lite.ts +18 -0
  58. package/src/debug_lite_headers.ts +25 -0
  59. package/src/debug_search.ts +18 -0
  60. package/src/index.ts +45 -1
  61. package/src/mcp/JsonRpcClient.ts +19 -0
  62. package/src/mcp/McpConfig.ts +153 -0
  63. package/src/mcp/McpManager.ts +224 -0
  64. package/src/repl/PersistentShell.ts +24 -1
  65. package/src/repl/ReplManager.ts +1521 -1204
  66. package/src/skills/Skill.ts +91 -11
  67. package/src/skills/SkillsManager.ts +25 -3
  68. package/src/skills/__tests__/SkillsManager.test.ts +73 -0
  69. package/src/tools/AskQuestionTool.ts +197 -0
  70. package/src/tools/EditFileTool.ts +172 -0
  71. package/src/tools/FileTools.ts +3 -0
  72. package/src/tools/PlanModeTool.ts +50 -0
  73. package/src/tools/WebSearchTool.ts +235 -63
  74. package/src/ui/DiffViewer.ts +117 -0
  75. package/src/ui/InputBox.ts +17 -2
  76. package/src/ui/MultiFileSelector.ts +135 -0
  77. package/src/ui/PlanModeUI.ts +121 -0
  78. package/src/ui/ToolExecutor.ts +182 -0
  79. package/src/ui/UIManager.ts +15 -2
  80. package/src/utils/__mocks__/chalk.ts +19 -0
  81. package/src/utils/__tests__/ContextVisualizer.test.ts +118 -0
  82. package/console.log(tick) +0 -0
@@ -0,0 +1,121 @@
1
+ import inquirer from 'inquirer';
2
+ import chalk from 'chalk';
3
+
4
+ export interface QAEntry {
5
+ question: string;
6
+ answer: string;
7
+ timestamp: Date;
8
+ }
9
+
10
+ /**
11
+ * Plan Mode UI - Shows Q&A history and handles plan → build transition
12
+ */
13
+ export class PlanModeUI {
14
+ private static qaHistory: QAEntry[] = [];
15
+
16
+ /**
17
+ * Record a Q&A entry
18
+ */
19
+ static recordQA(question: string, answer: string): void {
20
+ this.qaHistory.push({
21
+ question,
22
+ answer,
23
+ timestamp: new Date()
24
+ });
25
+ }
26
+
27
+ /**
28
+ * Show the current Q&A history
29
+ */
30
+ static showQAHistory(): void {
31
+ if (this.qaHistory.length === 0) {
32
+ console.log(chalk.dim(' No questions asked yet.'));
33
+ return;
34
+ }
35
+
36
+ console.log('');
37
+ console.log(chalk.cyan('📋 Requirements gathered:'));
38
+ console.log(chalk.gray('─'.repeat(60)));
39
+
40
+ for (let i = 0; i < this.qaHistory.length; i++) {
41
+ const entry = this.qaHistory[i];
42
+ console.log(chalk.bold(`${i + 1}. ${entry.question}`));
43
+ console.log(chalk.dim(` Answer: ${entry.answer}`));
44
+ console.log('');
45
+ }
46
+
47
+ console.log(chalk.gray('─'.repeat(60)));
48
+ }
49
+
50
+ /**
51
+ * Ask if ready to switch to build mode
52
+ */
53
+ static async askReadyToBuild(): Promise<boolean> {
54
+ console.log('');
55
+
56
+ const { ready } = await inquirer.prompt([
57
+ {
58
+ type: 'confirm',
59
+ name: 'ready',
60
+ message: chalk.cyan('🚀 Ready to switch to BUILD mode and implement?'),
61
+ default: true
62
+ }
63
+ ]);
64
+
65
+ return ready;
66
+ }
67
+
68
+ /**
69
+ * Show plan mode header/summary
70
+ */
71
+ static showPlanHeader(): void {
72
+ console.log('');
73
+ console.log(chalk.cyan.bold('🎯 PLAN MODE'));
74
+ console.log(chalk.dim(' Gathering requirements and planning the solution...'));
75
+ console.log(chalk.dim(' Type your requirements, answer questions, then switch to /build to implement.'));
76
+ console.log('');
77
+ }
78
+
79
+ /**
80
+ * Show suggestion to switch to build mode
81
+ */
82
+ static suggestBuildMode(): void {
83
+ console.log('');
84
+ console.log(chalk.yellow('💡 Tip: Type ') + chalk.bold('/build') + chalk.yellow(' to start implementing when ready.'));
85
+ }
86
+
87
+ /**
88
+ * Clear Q&A history (e.g., when starting a new session)
89
+ */
90
+ static clearHistory(): void {
91
+ this.qaHistory = [];
92
+ }
93
+
94
+ /**
95
+ * Get Q&A history
96
+ */
97
+ static getHistory(): QAEntry[] {
98
+ return [...this.qaHistory];
99
+ }
100
+
101
+ /**
102
+ * Show a summary of the plan
103
+ */
104
+ static showPlanSummary(): void {
105
+ if (this.qaHistory.length === 0) {
106
+ return;
107
+ }
108
+
109
+ console.log('');
110
+ console.log(chalk.cyan('📝 Plan Summary:'));
111
+ console.log(chalk.gray('─'.repeat(60)));
112
+ console.log(chalk.dim(`Questions answered: ${this.qaHistory.length}`));
113
+
114
+ // Show key answers as bullet points
115
+ for (const entry of this.qaHistory) {
116
+ console.log(chalk.dim(` • ${entry.question}: ${entry.answer}`));
117
+ }
118
+
119
+ console.log(chalk.gray('─'.repeat(60)));
120
+ }
121
+ }
@@ -0,0 +1,182 @@
1
+ import chalk from 'chalk';
2
+ import ora, { Ora } from 'ora';
3
+
4
+ export interface ToolExecution {
5
+ toolName: string;
6
+ args: Record<string, any>;
7
+ status: 'running' | 'completed' | 'failed';
8
+ result?: string;
9
+ error?: string;
10
+ }
11
+
12
+ /**
13
+ * Visual feedback for tool execution
14
+ * Shows colored icons, spinners, and grouped display
15
+ */
16
+ export class ToolExecutor {
17
+ private static executions: ToolExecution[] = [];
18
+ private static spinners: Map<string, Ora> = new Map();
19
+
20
+ /**
21
+ * Get colored text for a tool name
22
+ */
23
+ private static colorToolName(toolName: string, color: string): string {
24
+ switch (color) {
25
+ case 'blue': return chalk.blue(toolName);
26
+ case 'yellow': return chalk.yellow(toolName);
27
+ case 'cyan': return chalk.cyan(toolName);
28
+ case 'magenta': return chalk.magenta(toolName);
29
+ case 'green': return chalk.green(toolName);
30
+ case 'red': return chalk.red(toolName);
31
+ case 'gray': return chalk.gray(toolName);
32
+ default: return toolName;
33
+ }
34
+ }
35
+
36
+ /**
37
+ * Start a tool execution with visual feedback
38
+ */
39
+ static startExecution(toolName: string, args: Record<string, any>): void {
40
+ const execution: ToolExecution = {
41
+ toolName,
42
+ args,
43
+ status: 'running'
44
+ };
45
+
46
+ this.executions.push(execution);
47
+
48
+ // Get icon and color for tool type
49
+ const { icon, color } = this.getToolStyle(toolName);
50
+
51
+ // Format args for display (truncate long values)
52
+ const argsDisplay = this.formatArgs(args);
53
+
54
+ // Start spinner
55
+ const spinner = ora({
56
+ text: `${icon} ${this.colorToolName(toolName, color)} ${argsDisplay}`,
57
+ color: color as 'cyan' | 'yellow' | 'red' | 'green' | 'blue' | 'magenta' | 'white' | 'gray'
58
+ });
59
+ spinner.start();
60
+
61
+ this.spinners.set(toolName, spinner);
62
+ }
63
+
64
+ /**
65
+ * Complete a tool execution successfully
66
+ */
67
+ static completeExecution(toolName: string, result: string): void {
68
+ const execution = this.executions.find(e => e.toolName === toolName);
69
+ if (execution) {
70
+ execution.status = 'completed';
71
+ execution.result = result;
72
+ }
73
+
74
+ const spinner = this.spinners.get(toolName);
75
+ if (spinner) {
76
+ const { icon } = this.getToolStyle(toolName);
77
+ spinner.succeed(`${icon} ${chalk.green(toolName)} completed`);
78
+ this.spinners.delete(toolName);
79
+ }
80
+ }
81
+
82
+ /**
83
+ * Mark a tool execution as failed
84
+ */
85
+ static failExecution(toolName: string, error: string): void {
86
+ const execution = this.executions.find(e => e.toolName === toolName);
87
+ if (execution) {
88
+ execution.status = 'failed';
89
+ execution.error = error;
90
+ }
91
+
92
+ const spinner = this.spinners.get(toolName);
93
+ if (spinner) {
94
+ const { icon } = this.getToolStyle(toolName);
95
+ spinner.fail(`${icon} ${chalk.red(toolName)} failed: ${error}`);
96
+ this.spinners.delete(toolName);
97
+ }
98
+ }
99
+
100
+ /**
101
+ * Show grouped summary of all tool executions
102
+ */
103
+ static showSummary(): void {
104
+ if (this.executions.length === 0) {
105
+ return;
106
+ }
107
+
108
+ console.log('');
109
+ console.log(chalk.gray('─'.repeat(60)));
110
+ console.log(chalk.cyan('🔧 Tool Executions'));
111
+ console.log(chalk.gray('─'.repeat(60)));
112
+
113
+ for (const execution of this.executions) {
114
+ const { icon, color } = this.getToolStyle(execution.toolName);
115
+ const statusIcon = execution.status === 'completed' ? '✓' : execution.status === 'failed' ? '✗' : '…';
116
+
117
+ console.log(
118
+ `${icon} ${this.colorToolName(execution.toolName, color)} ${chalk.dim(statusIcon)}`
119
+ );
120
+ }
121
+
122
+ console.log(chalk.gray('─'.repeat(60)));
123
+ console.log('');
124
+
125
+ // Reset for next batch
126
+ this.executions = [];
127
+ }
128
+
129
+ /**
130
+ * Get visual style for a tool type
131
+ */
132
+ private static getToolStyle(toolName: string): { icon: string; color: string } {
133
+ const styles: Record<string, { icon: string; color: string }> = {
134
+ 'read_file': { icon: '📖', color: 'blue' },
135
+ 'write_file': { icon: '📄', color: 'yellow' },
136
+ 'edit_file': { icon: '✏️', color: 'yellow' },
137
+ 'list_dir': { icon: '📁', color: 'cyan' },
138
+ 'search_files': { icon: '🔍', color: 'magenta' },
139
+ 'search_web': { icon: '🌐', color: 'blue' },
140
+ 'run_command': { icon: '💻', color: 'green' },
141
+ 'git_commit': { icon: '📝', color: 'green' }
142
+ };
143
+
144
+ return styles[toolName] || { icon: '🔧', color: 'gray' };
145
+ }
146
+
147
+ /**
148
+ * Format arguments for display
149
+ */
150
+ private static formatArgs(args: Record<string, any>): string {
151
+ const parts: string[] = [];
152
+
153
+ for (const [key, value] of Object.entries(args)) {
154
+ const strValue = String(value);
155
+ // Truncate long values
156
+ const display = strValue.length > 30 ? strValue.slice(0, 30) + '...' : strValue;
157
+ parts.push(`${key}=${chalk.dim(display)}`);
158
+ }
159
+
160
+ return parts.length > 0 ? chalk.dim(`(${parts.join(', ')})`) : '';
161
+ }
162
+
163
+ /**
164
+ * Show a simple inline tool usage message
165
+ */
166
+ static showInline(toolName: string, args: Record<string, any>): void {
167
+ const { icon, color } = this.getToolStyle(toolName);
168
+ const argsDisplay = this.formatArgs(args);
169
+ console.log(chalk.dim(` ${icon} ${this.colorToolName(toolName, color)} ${argsDisplay}`));
170
+ }
171
+
172
+ /**
173
+ * Clear all pending executions
174
+ */
175
+ static clear(): void {
176
+ for (const spinner of this.spinners.values()) {
177
+ spinner.stop();
178
+ }
179
+ this.spinners.clear();
180
+ this.executions = [];
181
+ }
182
+ }
@@ -14,13 +14,13 @@ export class UIManager {
14
14
  whitespaceBreak: true,
15
15
  });
16
16
  console.log(gradient.pastel.multiline(logoText));
17
- console.log(chalk.gray(' v1.1.3 - AI Coding Agent'));
17
+ console.log(chalk.gray(' v1.1.6 - AI Coding Agent'));
18
18
  console.log('');
19
19
  }
20
20
 
21
21
  public static renderDashboard(config: { model: string, mode: string, cwd: string }) {
22
22
  const { model, cwd } = config;
23
- const version = 'v1.1.3';
23
+ const version = 'v1.1.6';
24
24
 
25
25
  // Layout: Left (Status/Welcome) | Right (Tips/Activity)
26
26
  // Total width ~80 chars.
@@ -84,4 +84,17 @@ export class UIManager {
84
84
  public static printSeparator() {
85
85
  console.log(chalk.gray('──────────────────────────────────────────────────'));
86
86
  }
87
+
88
+ public static logBullet(text: string, color: 'cyan' | 'green' | 'yellow' | 'red' | 'blue' | 'magenta' | 'white' = 'white') {
89
+ const bullet = color === 'white' ? '●' : chalk[color]('●');
90
+ console.log(` ${bullet} ${text}`);
91
+ }
92
+
93
+ public static logSystem(text: string) {
94
+ console.log(chalk.dim(` ${text}`));
95
+ }
96
+
97
+ public static logTransition(text: string) {
98
+ console.log(` ${chalk.red('+')} ${chalk.red(text)}`);
99
+ }
87
100
  }
@@ -0,0 +1,19 @@
1
+ // Manual mock for chalk to avoid ESM issues in Jest
2
+ module.exports = {
3
+ dim: (str: string) => str,
4
+ green: (str: string) => str,
5
+ yellow: (str: string) => str,
6
+ red: (str: string) => str,
7
+ gray: (str: string) => str,
8
+ bold: (str: string) => str,
9
+ cyan: (str: string) => str,
10
+ default: {
11
+ dim: (str: string) => str,
12
+ green: (str: string) => str,
13
+ yellow: (str: string) => str,
14
+ red: (str: string) => str,
15
+ gray: (str: string) => str,
16
+ bold: (str: string) => str,
17
+ cyan: (str: string) => str
18
+ }
19
+ };
@@ -0,0 +1,118 @@
1
+ /**
2
+ * Tests for ContextVisualizer
3
+ */
4
+
5
+ import { ContextVisualizer } from '../ContextVisualizer';
6
+ import { ChatMessage } from '../../llm/ModelInterface';
7
+
8
+ describe('ContextVisualizer', () => {
9
+ let visualizer: ContextVisualizer;
10
+
11
+ beforeEach(() => {
12
+ visualizer = new ContextVisualizer();
13
+ });
14
+
15
+ describe('calculateUsage', () => {
16
+ it('should handle empty history', () => {
17
+ const history: ChatMessage[] = [];
18
+ const usage = visualizer.calculateUsage(history);
19
+
20
+ // Includes 2000 char overhead = 500 tokens
21
+ expect(usage.tokens).toBe(500);
22
+ expect(usage.maxTokens).toBe(128000);
23
+ expect(usage).toHaveProperty('percentage');
24
+ expect(usage).toHaveProperty('tokens');
25
+ expect(usage).toHaveProperty('maxTokens');
26
+ });
27
+
28
+ it('should calculate tokens for messages', () => {
29
+ const history: ChatMessage[] = [
30
+ { role: 'system', content: 'You are a helpful assistant.' },
31
+ { role: 'user', content: 'Hello' },
32
+ { role: 'assistant', content: 'Hi there!' }
33
+ ];
34
+
35
+ const usage = visualizer.calculateUsage(history);
36
+
37
+ expect(usage.tokens).toBeGreaterThan(500);
38
+ expect(usage.tokens).toBeLessThan(1000);
39
+ expect(usage.maxTokens).toBe(128000);
40
+ });
41
+
42
+ it('should handle large messages', () => {
43
+ const largeContent = 'x'.repeat(10000);
44
+ const history: ChatMessage[] = [
45
+ { role: 'user', content: largeContent }
46
+ ];
47
+
48
+ const usage = visualizer.calculateUsage(history);
49
+
50
+ expect(usage.tokens).toBeGreaterThan(1500);
51
+ });
52
+ });
53
+
54
+ describe('formatBar', () => {
55
+ it('should format bar at low usage', () => {
56
+ const usage = { tokens: 1000, percentage: 5, maxTokens: 128000 };
57
+ const bar = visualizer.formatBar(usage);
58
+
59
+ // Check that bar contains expected data (without chalk dependency)
60
+ expect(bar).toContain('5');
61
+ expect(bar).toContain('1k');
62
+ expect(bar).toContain('128');
63
+ });
64
+
65
+ it('should format bar at medium usage', () => {
66
+ const usage = { tokens: 50000, percentage: 40, maxTokens: 128000 };
67
+ const bar = visualizer.formatBar(usage);
68
+
69
+ expect(bar).toContain('40');
70
+ expect(bar).toContain('50k');
71
+ });
72
+
73
+ it('should format bar at high usage', () => {
74
+ const usage = { tokens: 100000, percentage: 80, maxTokens: 128000 };
75
+ const bar = visualizer.formatBar(usage);
76
+
77
+ expect(bar).toContain('80');
78
+ expect(bar).toContain('100k');
79
+ });
80
+ });
81
+
82
+ describe('shouldCompact', () => {
83
+ it('should return false for low percentage', () => {
84
+ const history: ChatMessage[] = [
85
+ { role: 'user', content: 'small message' }
86
+ ];
87
+
88
+ const shouldCompact = visualizer.shouldCompact(history);
89
+ expect(shouldCompact).toBe(false);
90
+ });
91
+
92
+ it('should return true at 80% threshold', () => {
93
+ // Create enough content to exceed 80%
94
+ // 80% of 128000 tokens = 102400 tokens = ~409600 chars
95
+ // Subtract 2000 overhead = ~407400 chars needed
96
+ const largeContent = 'x'.repeat(410000);
97
+ const history: ChatMessage[] = [
98
+ { role: 'system', content: largeContent },
99
+ { role: 'user', content: largeContent }
100
+ ];
101
+
102
+ const shouldCompact = visualizer.shouldCompact(history);
103
+ expect(shouldCompact).toBe(true);
104
+ });
105
+ });
106
+
107
+ describe('setMaxTokens', () => {
108
+ it('should update max tokens', () => {
109
+ visualizer.setMaxTokens(32000);
110
+
111
+ const history: ChatMessage[] = [];
112
+ const usage = visualizer.calculateUsage(history);
113
+
114
+ expect(usage.maxTokens).toBe(32000);
115
+ expect(usage.percentage).toBeGreaterThan(1); // Should be higher percentage with smaller max
116
+ });
117
+ });
118
+ });
package/console.log(tick) DELETED
File without changes