@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.
- package/ARCHITECTURE.md +267 -0
- package/CONTRIBUTING.md +209 -0
- package/dist/commands/Command.js +15 -1
- package/dist/commands/CommandManager.js +30 -5
- package/dist/commands/__tests__/CommandManager.test.js +70 -0
- package/dist/index.js +23 -0
- package/dist/repl/ReplManager.js +19 -31
- package/dist/skills/Skill.js +17 -2
- package/dist/skills/SkillsManager.js +28 -3
- package/dist/skills/__tests__/SkillsManager.test.js +62 -0
- package/dist/ui/InputBox.js +127 -0
- package/dist/ui/UIManager.js +2 -2
- package/dist/utils/__mocks__/chalk.js +20 -0
- package/dist/utils/__tests__/ContextVisualizer.test.js +95 -0
- package/package.json +25 -2
- package/src/commands/Command.ts +64 -13
- package/src/commands/CommandManager.ts +26 -5
- package/src/commands/__tests__/CommandManager.test.ts +83 -0
- package/src/index.ts +33 -1
- package/src/repl/ReplManager.ts +19 -33
- package/src/skills/Skill.ts +91 -11
- package/src/skills/SkillsManager.ts +25 -3
- package/src/skills/__tests__/SkillsManager.test.ts +73 -0
- package/src/ui/InputBox.ts +145 -0
- package/src/ui/UIManager.ts +2 -2
- package/src/utils/__mocks__/chalk.ts +19 -0
- package/src/utils/__tests__/ContextVisualizer.test.ts +118 -0
|
@@ -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
|
+
}
|
package/src/ui/UIManager.ts
CHANGED
|
@@ -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.
|
|
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.
|
|
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
|
+
});
|