@indiccoder/mentis-cli 1.1.2 → 1.1.4

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.
@@ -0,0 +1,145 @@
1
+ /**
2
+ * InputBox - Simple clean input with top line only
3
+ * Bottom line appears after submission (cross-platform compatible)
4
+ */
5
+
6
+ import readline from 'readline';
7
+ import chalk from 'chalk';
8
+
9
+ export interface InputBoxOptions {
10
+ placeholder?: string;
11
+ showHint?: boolean;
12
+ hint?: string;
13
+ }
14
+
15
+ export class InputBox {
16
+ private history: string[] = [];
17
+ private historySize: number = 1000;
18
+
19
+ constructor(history: string[] = []) {
20
+ this.history = history;
21
+ }
22
+
23
+ /**
24
+ * Get terminal width
25
+ */
26
+ private getTerminalWidth(): number {
27
+ return process.stdout.columns || 80;
28
+ }
29
+
30
+ /**
31
+ * Create horizontal line
32
+ */
33
+ private createLine(): string {
34
+ const width = this.getTerminalWidth();
35
+ return chalk.gray('─'.repeat(width));
36
+ }
37
+
38
+ /**
39
+ * Get user input with horizontal lines around it
40
+ */
41
+ async prompt(options: InputBoxOptions = {}): Promise<string> {
42
+ const { showHint = false, hint } = options;
43
+
44
+ // Display top horizontal line
45
+ console.log(this.createLine());
46
+
47
+ // Display hint if provided
48
+ if (showHint && hint) {
49
+ console.log(chalk.dim(` ${hint}`));
50
+ }
51
+
52
+ return new Promise<string>((resolve) => {
53
+ // Create readline with simple prompt
54
+ const rl = readline.createInterface({
55
+ input: process.stdin,
56
+ output: process.stdout,
57
+ prompt: chalk.cyan('> '),
58
+ history: this.history,
59
+ historySize: this.historySize,
60
+ completer: this.completer.bind(this)
61
+ });
62
+
63
+ rl.prompt();
64
+
65
+ rl.on('line', (line) => {
66
+ // Display bottom horizontal line after input
67
+ console.log(this.createLine());
68
+ rl.close();
69
+ resolve(line);
70
+ });
71
+
72
+ // Handle Ctrl+C
73
+ rl.on('SIGINT', () => {
74
+ console.log(this.createLine());
75
+ rl.close();
76
+ resolve('/exit');
77
+ });
78
+ });
79
+ }
80
+
81
+ /**
82
+ * Simple tab completer for commands
83
+ */
84
+ private completer(line: string) {
85
+ const commands = [
86
+ '/help', '/clear', '/exit', '/update', '/config',
87
+ '/init', '/resume', '/skills', '/commands', '/checkpoint',
88
+ '/model', '/use', '/mcp', '/search', '/run', '/commit'
89
+ ];
90
+
91
+ const hits = commands.filter(c => c.startsWith(line));
92
+ return [hits.length ? hits : commands, line];
93
+ }
94
+
95
+ /**
96
+ * Add input to history
97
+ */
98
+ addToHistory(input: string): void {
99
+ if (!input || input === this.history[0]) return;
100
+ this.history.unshift(input);
101
+ if (this.history.length > this.historySize) {
102
+ this.history = this.history.slice(0, this.historySize);
103
+ }
104
+ }
105
+
106
+ /**
107
+ * Get current history
108
+ */
109
+ getHistory(): string[] {
110
+ return this.history;
111
+ }
112
+
113
+ /**
114
+ * Display separator and context info before input
115
+ */
116
+ public displayFrame(contextInfo?: { messageCount: number; contextPercent: number }): void {
117
+ console.log('');
118
+
119
+ // Context bar with message count and percentage
120
+ if (contextInfo) {
121
+ const { messageCount, contextPercent } = contextInfo;
122
+ const color = contextPercent < 60 ? chalk.green : contextPercent < 80 ? chalk.yellow : chalk.red;
123
+ const bar = this.createProgressBar(contextPercent);
124
+ console.log(chalk.dim(` ${bar} ${messageCount} msgs ${color(contextPercent + '%')}`));
125
+ }
126
+ }
127
+
128
+ /**
129
+ * Display separator (alias for displayFrame)
130
+ */
131
+ public displaySeparator(contextInfo?: { messageCount: number; contextPercent: number }): void {
132
+ this.displayFrame(contextInfo);
133
+ }
134
+
135
+ /**
136
+ * Create a visual progress bar
137
+ */
138
+ private createProgressBar(percentage: number): string {
139
+ const width = 15;
140
+ const filled = Math.round(percentage / 100 * width);
141
+ const empty = width - filled;
142
+ const color = percentage < 60 ? chalk.green : percentage < 80 ? chalk.yellow : chalk.red;
143
+ return color('█'.repeat(filled)) + chalk.dim('░'.repeat(empty));
144
+ }
145
+ }
@@ -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.2 - AI Coding Agent'));
17
+ console.log(chalk.gray(' v1.1.3 - 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.2';
23
+ const version = 'v1.1.3';
24
24
 
25
25
  // Layout: Left (Status/Welcome) | Right (Tips/Activity)
26
26
  // Total width ~80 chars.
@@ -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
+ });