@indiccoder/mentis-cli 1.1.2 → 1.1.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.
@@ -44,6 +44,7 @@ const ConfigManager_1 = require("../config/ConfigManager");
44
44
  const OpenAIClient_1 = require("../llm/OpenAIClient");
45
45
  const ContextManager_1 = require("../context/ContextManager");
46
46
  const UIManager_1 = require("../ui/UIManager");
47
+ const InputBox_1 = require("../ui/InputBox");
47
48
  const FileTools_1 = require("../tools/FileTools");
48
49
  const SearchTools_1 = require("../tools/SearchTools");
49
50
  const PersistentShellTool_1 = require("../tools/PersistentShellTool");
@@ -224,46 +225,33 @@ class ReplManager {
224
225
  }
225
226
  catch (e) { }
226
227
  }
228
+ // Initialize InputBox with history
229
+ const inputBox = new InputBox_1.InputBox(commandHistory);
227
230
  while (true) {
228
- // Minimalist Separator
229
- console.log(chalk_1.default.gray('────────────────────────────────────────────────────────────────────────────────'));
230
- // Hint (Claude style puts it below, we put it above for standard terminal compatibility)
231
- console.log(chalk_1.default.dim(' ? for shortcuts'));
232
- const promptText = `> `; // Clean prompt
233
- // Use readline for basic input to support history
234
- const answer = await new Promise((resolve) => {
235
- const rl = readline.createInterface({
236
- input: process.stdin,
237
- output: process.stdout,
238
- history: commandHistory,
239
- historySize: 1000,
240
- prompt: promptText
241
- });
242
- rl.prompt();
243
- rl.on('line', (line) => {
244
- rl.close();
245
- resolve(line);
246
- });
231
+ // Calculate context usage for display
232
+ const usage = this.contextVisualizer.calculateUsage(this.history);
233
+ // Display enhanced input frame
234
+ inputBox.displayFrame({
235
+ messageCount: this.history.length,
236
+ contextPercent: usage.percentage
237
+ });
238
+ // Get styled input
239
+ const answer = await inputBox.prompt({
240
+ showHint: this.history.length === 0,
241
+ hint: 'Type your message or /help for commands'
247
242
  });
248
- // Update history manually or grab from rl?
249
- // rl.history gets updated when user hits enter.
250
- // But we closed rl. We should manually save the input to our tracking array and file.
251
243
  const input = answer.trim();
252
244
  if (input) {
253
- // Update in-memory history (for next readline instance)
254
- // Readline history has newest at 0.
255
- // Avoid duplicates if needed, but standard shell keeps them.
256
- if (commandHistory[0] !== input) {
257
- commandHistory.unshift(input);
258
- }
259
- // Append to file (as standard log, so append at end)
245
+ // Update history via InputBox
246
+ inputBox.addToHistory(input);
247
+ // Append to file
260
248
  try {
261
249
  fs.appendFileSync(HISTORY_FILE, input + '\n');
262
250
  }
263
251
  catch (e) { }
264
252
  }
265
- if (!answer.trim())
266
- continue; // Skip empty but allow it to close readline loop
253
+ if (!input)
254
+ continue;
267
255
  if (input.startsWith('/')) {
268
256
  await this.handleCommand(input);
269
257
  continue;
@@ -0,0 +1,127 @@
1
+ "use strict";
2
+ /**
3
+ * InputBox - Simple clean input with top line only
4
+ * Bottom line appears after submission (cross-platform compatible)
5
+ */
6
+ var __importDefault = (this && this.__importDefault) || function (mod) {
7
+ return (mod && mod.__esModule) ? mod : { "default": mod };
8
+ };
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.InputBox = void 0;
11
+ const readline_1 = __importDefault(require("readline"));
12
+ const chalk_1 = __importDefault(require("chalk"));
13
+ class InputBox {
14
+ constructor(history = []) {
15
+ this.history = [];
16
+ this.historySize = 1000;
17
+ this.history = history;
18
+ }
19
+ /**
20
+ * Get terminal width
21
+ */
22
+ getTerminalWidth() {
23
+ return process.stdout.columns || 80;
24
+ }
25
+ /**
26
+ * Create horizontal line
27
+ */
28
+ createLine() {
29
+ const width = this.getTerminalWidth();
30
+ return chalk_1.default.gray('─'.repeat(width));
31
+ }
32
+ /**
33
+ * Get user input with horizontal lines around it
34
+ */
35
+ async prompt(options = {}) {
36
+ const { showHint = false, hint } = options;
37
+ // Display top horizontal line
38
+ console.log(this.createLine());
39
+ // Display hint if provided
40
+ if (showHint && hint) {
41
+ console.log(chalk_1.default.dim(` ${hint}`));
42
+ }
43
+ return new Promise((resolve) => {
44
+ // Create readline with simple prompt
45
+ const rl = readline_1.default.createInterface({
46
+ input: process.stdin,
47
+ output: process.stdout,
48
+ prompt: chalk_1.default.cyan('> '),
49
+ history: this.history,
50
+ historySize: this.historySize,
51
+ completer: this.completer.bind(this)
52
+ });
53
+ rl.prompt();
54
+ rl.on('line', (line) => {
55
+ // Display bottom horizontal line after input
56
+ console.log(this.createLine());
57
+ rl.close();
58
+ resolve(line);
59
+ });
60
+ // Handle Ctrl+C
61
+ rl.on('SIGINT', () => {
62
+ console.log(this.createLine());
63
+ rl.close();
64
+ resolve('/exit');
65
+ });
66
+ });
67
+ }
68
+ /**
69
+ * Simple tab completer for commands
70
+ */
71
+ completer(line) {
72
+ const commands = [
73
+ '/help', '/clear', '/exit', '/update', '/config',
74
+ '/init', '/resume', '/skills', '/commands', '/checkpoint',
75
+ '/model', '/use', '/mcp', '/search', '/run', '/commit'
76
+ ];
77
+ const hits = commands.filter(c => c.startsWith(line));
78
+ return [hits.length ? hits : commands, line];
79
+ }
80
+ /**
81
+ * Add input to history
82
+ */
83
+ addToHistory(input) {
84
+ if (!input || input === this.history[0])
85
+ return;
86
+ this.history.unshift(input);
87
+ if (this.history.length > this.historySize) {
88
+ this.history = this.history.slice(0, this.historySize);
89
+ }
90
+ }
91
+ /**
92
+ * Get current history
93
+ */
94
+ getHistory() {
95
+ return this.history;
96
+ }
97
+ /**
98
+ * Display separator and context info before input
99
+ */
100
+ displayFrame(contextInfo) {
101
+ console.log('');
102
+ // Context bar with message count and percentage
103
+ if (contextInfo) {
104
+ const { messageCount, contextPercent } = contextInfo;
105
+ const color = contextPercent < 60 ? chalk_1.default.green : contextPercent < 80 ? chalk_1.default.yellow : chalk_1.default.red;
106
+ const bar = this.createProgressBar(contextPercent);
107
+ console.log(chalk_1.default.dim(` ${bar} ${messageCount} msgs ${color(contextPercent + '%')}`));
108
+ }
109
+ }
110
+ /**
111
+ * Display separator (alias for displayFrame)
112
+ */
113
+ displaySeparator(contextInfo) {
114
+ this.displayFrame(contextInfo);
115
+ }
116
+ /**
117
+ * Create a visual progress bar
118
+ */
119
+ createProgressBar(percentage) {
120
+ const width = 15;
121
+ const filled = Math.round(percentage / 100 * width);
122
+ const empty = width - filled;
123
+ const color = percentage < 60 ? chalk_1.default.green : percentage < 80 ? chalk_1.default.yellow : chalk_1.default.red;
124
+ return color('█'.repeat(filled)) + chalk_1.default.dim('░'.repeat(empty));
125
+ }
126
+ }
127
+ exports.InputBox = InputBox;
@@ -19,12 +19,12 @@ class UIManager {
19
19
  whitespaceBreak: true,
20
20
  });
21
21
  console.log(gradient_string_1.default.pastel.multiline(logoText));
22
- console.log(chalk_1.default.gray(' v1.1.2 - AI Coding Agent'));
22
+ console.log(chalk_1.default.gray(' v1.1.3 - AI Coding Agent'));
23
23
  console.log('');
24
24
  }
25
25
  static renderDashboard(config) {
26
26
  const { model, cwd } = config;
27
- const version = 'v1.1.2';
27
+ const version = 'v1.1.3';
28
28
  // Layout: Left (Status/Welcome) | Right (Tips/Activity)
29
29
  // Total width ~80 chars.
30
30
  // Left ~45, Right ~30.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@indiccoder/mentis-cli",
3
- "version": "1.1.2",
3
+ "version": "1.1.3",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -33,6 +33,7 @@
33
33
  "axios": "^1.13.2",
34
34
  "boxen": "^8.0.1",
35
35
  "chalk": "^5.6.2",
36
+ "cli-cursor": "^5.0.0",
36
37
  "cli-highlight": "^2.1.11",
37
38
  "commander": "^14.0.2",
38
39
  "dotenv": "^17.2.3",
@@ -54,6 +55,7 @@
54
55
  "yaml": "^2.7.0"
55
56
  },
56
57
  "devDependencies": {
58
+ "@types/cli-cursor": "^2.1.0",
57
59
  "@types/figlet": "^1.7.0",
58
60
  "@types/fs-extra": "^11.0.4",
59
61
  "@types/gradient-string": "^1.1.6",
@@ -7,6 +7,7 @@ import { OpenAIClient } from '../llm/OpenAIClient';
7
7
 
8
8
  import { ContextManager } from '../context/ContextManager';
9
9
  import { UIManager } from '../ui/UIManager';
10
+ import { InputBox } from '../ui/InputBox';
10
11
  import { WriteFileTool, ReadFileTool, ListDirTool } from '../tools/FileTools';
11
12
  import { SearchFileTool } from '../tools/SearchTools';
12
13
  import { PersistentShellTool } from '../tools/PersistentShellTool';
@@ -229,53 +230,38 @@ export class ReplManager {
229
230
  } catch (e) { }
230
231
  }
231
232
 
233
+ // Initialize InputBox with history
234
+ const inputBox = new InputBox(commandHistory);
235
+
232
236
  while (true) {
233
- // Minimalist Separator
234
- console.log(chalk.gray('────────────────────────────────────────────────────────────────────────────────'));
235
-
236
- // Hint (Claude style puts it below, we put it above for standard terminal compatibility)
237
- console.log(chalk.dim(' ? for shortcuts'));
238
-
239
- const promptText = `> `; // Clean prompt
240
-
241
- // Use readline for basic input to support history
242
- const answer = await new Promise<string>((resolve) => {
243
- const rl = readline.createInterface({
244
- input: process.stdin,
245
- output: process.stdout,
246
- history: commandHistory,
247
- historySize: 1000,
248
- prompt: promptText
249
- });
237
+ // Calculate context usage for display
238
+ const usage = this.contextVisualizer.calculateUsage(this.history);
250
239
 
251
- rl.prompt();
240
+ // Display enhanced input frame
241
+ inputBox.displayFrame({
242
+ messageCount: this.history.length,
243
+ contextPercent: usage.percentage
244
+ });
252
245
 
253
- rl.on('line', (line) => {
254
- rl.close();
255
- resolve(line);
256
- });
246
+ // Get styled input
247
+ const answer = await inputBox.prompt({
248
+ showHint: this.history.length === 0,
249
+ hint: 'Type your message or /help for commands'
257
250
  });
258
251
 
259
- // Update history manually or grab from rl?
260
- // rl.history gets updated when user hits enter.
261
- // But we closed rl. We should manually save the input to our tracking array and file.
262
252
  const input = answer.trim();
263
253
 
264
254
  if (input) {
265
- // Update in-memory history (for next readline instance)
266
- // Readline history has newest at 0.
267
- // Avoid duplicates if needed, but standard shell keeps them.
268
- if (commandHistory[0] !== input) {
269
- commandHistory.unshift(input);
270
- }
255
+ // Update history via InputBox
256
+ inputBox.addToHistory(input);
271
257
 
272
- // Append to file (as standard log, so append at end)
258
+ // Append to file
273
259
  try {
274
260
  fs.appendFileSync(HISTORY_FILE, input + '\n');
275
261
  } catch (e) { }
276
262
  }
277
263
 
278
- if (!answer.trim()) continue; // Skip empty but allow it to close readline loop
264
+ if (!input) continue;
279
265
 
280
266
  if (input.startsWith('/')) {
281
267
  await this.handleCommand(input);
@@ -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.