@indiccoder/mentis-cli 1.1.1 → 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.
- package/dist/index.js +12 -2
- package/dist/repl/ReplManager.js +30 -32
- package/dist/ui/InputBox.js +127 -0
- package/dist/ui/UIManager.js +2 -2
- package/package.json +3 -1
- package/src/index.ts +14 -2
- package/src/repl/ReplManager.ts +33 -34
- package/src/ui/InputBox.ts +145 -0
- package/src/ui/UIManager.ts +2 -2
package/dist/index.js
CHANGED
|
@@ -6,10 +6,12 @@ function parseArgs() {
|
|
|
6
6
|
const args = process.argv.slice(2);
|
|
7
7
|
const options = {
|
|
8
8
|
resume: false,
|
|
9
|
-
yolo: false
|
|
9
|
+
yolo: false,
|
|
10
|
+
headless: false
|
|
10
11
|
};
|
|
11
12
|
let command = null;
|
|
12
|
-
for (
|
|
13
|
+
for (let i = 0; i < args.length; i++) {
|
|
14
|
+
const arg = args[i];
|
|
13
15
|
switch (arg) {
|
|
14
16
|
case 'update':
|
|
15
17
|
command = 'update';
|
|
@@ -20,6 +22,11 @@ function parseArgs() {
|
|
|
20
22
|
case '--yolo':
|
|
21
23
|
options.yolo = true;
|
|
22
24
|
break;
|
|
25
|
+
case '-p':
|
|
26
|
+
case '--prompt':
|
|
27
|
+
options.headless = true;
|
|
28
|
+
options.headlessPrompt = args[++i] || '';
|
|
29
|
+
break;
|
|
23
30
|
case '-h':
|
|
24
31
|
case '--help':
|
|
25
32
|
console.log(`
|
|
@@ -30,14 +37,17 @@ Usage:
|
|
|
30
37
|
mentis update Update to latest version
|
|
31
38
|
mentis --resume Resume last session
|
|
32
39
|
mentis --yolo Auto-confirm mode (skip confirmations)
|
|
40
|
+
mentis -p "<prompt>" Headless mode (non-interactive)
|
|
33
41
|
|
|
34
42
|
Options:
|
|
35
43
|
--resume Load latest checkpoint on start
|
|
36
44
|
--yolo Skip all confirmation prompts
|
|
45
|
+
-p, --prompt <text> Headless mode with prompt
|
|
37
46
|
-h, --help Show this help message
|
|
38
47
|
|
|
39
48
|
Commands (in REPL):
|
|
40
49
|
/help Show all available commands
|
|
50
|
+
/clear Clear conversation context
|
|
41
51
|
/resume Resume last session
|
|
42
52
|
/init Initialize project with .mentis.md
|
|
43
53
|
/skills <list|show|create|validate> Manage Agent Skills
|
package/dist/repl/ReplManager.js
CHANGED
|
@@ -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");
|
|
@@ -67,7 +68,7 @@ const marked_1 = require("marked");
|
|
|
67
68
|
const marked_terminal_1 = __importDefault(require("marked-terminal"));
|
|
68
69
|
const HISTORY_FILE = path.join(os.homedir(), '.mentis_history');
|
|
69
70
|
class ReplManager {
|
|
70
|
-
constructor(options = { resume: false, yolo: false }) {
|
|
71
|
+
constructor(options = { resume: false, yolo: false, headless: false }) {
|
|
71
72
|
this.history = [];
|
|
72
73
|
this.mode = 'BUILD';
|
|
73
74
|
this.tools = [];
|
|
@@ -193,6 +194,12 @@ class ReplManager {
|
|
|
193
194
|
// console.log(chalk.dim(`Initialized ${provider} client with model ${model}`));
|
|
194
195
|
}
|
|
195
196
|
async start() {
|
|
197
|
+
// Headless mode: non-interactive, process prompt and exit
|
|
198
|
+
if (this.options.headless && this.options.headlessPrompt) {
|
|
199
|
+
await this.handleChat(this.options.headlessPrompt);
|
|
200
|
+
process.exit(0);
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
196
203
|
UIManager_1.UIManager.renderDashboard({
|
|
197
204
|
model: this.currentModelName,
|
|
198
205
|
mode: this.mode,
|
|
@@ -218,46 +225,33 @@ class ReplManager {
|
|
|
218
225
|
}
|
|
219
226
|
catch (e) { }
|
|
220
227
|
}
|
|
228
|
+
// Initialize InputBox with history
|
|
229
|
+
const inputBox = new InputBox_1.InputBox(commandHistory);
|
|
221
230
|
while (true) {
|
|
222
|
-
//
|
|
223
|
-
|
|
224
|
-
//
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
historySize: 1000,
|
|
234
|
-
prompt: promptText
|
|
235
|
-
});
|
|
236
|
-
rl.prompt();
|
|
237
|
-
rl.on('line', (line) => {
|
|
238
|
-
rl.close();
|
|
239
|
-
resolve(line);
|
|
240
|
-
});
|
|
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'
|
|
241
242
|
});
|
|
242
|
-
// Update history manually or grab from rl?
|
|
243
|
-
// rl.history gets updated when user hits enter.
|
|
244
|
-
// But we closed rl. We should manually save the input to our tracking array and file.
|
|
245
243
|
const input = answer.trim();
|
|
246
244
|
if (input) {
|
|
247
|
-
// Update
|
|
248
|
-
|
|
249
|
-
//
|
|
250
|
-
if (commandHistory[0] !== input) {
|
|
251
|
-
commandHistory.unshift(input);
|
|
252
|
-
}
|
|
253
|
-
// Append to file (as standard log, so append at end)
|
|
245
|
+
// Update history via InputBox
|
|
246
|
+
inputBox.addToHistory(input);
|
|
247
|
+
// Append to file
|
|
254
248
|
try {
|
|
255
249
|
fs.appendFileSync(HISTORY_FILE, input + '\n');
|
|
256
250
|
}
|
|
257
251
|
catch (e) { }
|
|
258
252
|
}
|
|
259
|
-
if (!
|
|
260
|
-
continue;
|
|
253
|
+
if (!input)
|
|
254
|
+
continue;
|
|
261
255
|
if (input.startsWith('/')) {
|
|
262
256
|
await this.handleCommand(input);
|
|
263
257
|
continue;
|
|
@@ -361,6 +355,10 @@ class ReplManager {
|
|
|
361
355
|
const updater = new UpdateManager();
|
|
362
356
|
await updater.checkAndPerformUpdate(true);
|
|
363
357
|
break;
|
|
358
|
+
case '/clear':
|
|
359
|
+
this.history = [];
|
|
360
|
+
console.log(chalk_1.default.green('\n✓ Context cleared\n'));
|
|
361
|
+
break;
|
|
364
362
|
case '/init':
|
|
365
363
|
await this.handleInitCommand();
|
|
366
364
|
break;
|
|
@@ -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;
|
package/dist/ui/UIManager.js
CHANGED
|
@@ -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.
|
|
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.
|
|
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.
|
|
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",
|
package/src/index.ts
CHANGED
|
@@ -4,18 +4,22 @@ import { ReplManager } from './repl/ReplManager';
|
|
|
4
4
|
interface CliOptions {
|
|
5
5
|
resume: boolean;
|
|
6
6
|
yolo: boolean;
|
|
7
|
+
headless: boolean;
|
|
8
|
+
headlessPrompt?: string;
|
|
7
9
|
}
|
|
8
10
|
|
|
9
11
|
function parseArgs(): { command: string | null, options: CliOptions } {
|
|
10
12
|
const args = process.argv.slice(2);
|
|
11
13
|
const options: CliOptions = {
|
|
12
14
|
resume: false,
|
|
13
|
-
yolo: false
|
|
15
|
+
yolo: false,
|
|
16
|
+
headless: false
|
|
14
17
|
};
|
|
15
18
|
|
|
16
19
|
let command: string | null = null;
|
|
17
20
|
|
|
18
|
-
for (
|
|
21
|
+
for (let i = 0; i < args.length; i++) {
|
|
22
|
+
const arg = args[i];
|
|
19
23
|
switch (arg) {
|
|
20
24
|
case 'update':
|
|
21
25
|
command = 'update';
|
|
@@ -26,6 +30,11 @@ function parseArgs(): { command: string | null, options: CliOptions } {
|
|
|
26
30
|
case '--yolo':
|
|
27
31
|
options.yolo = true;
|
|
28
32
|
break;
|
|
33
|
+
case '-p':
|
|
34
|
+
case '--prompt':
|
|
35
|
+
options.headless = true;
|
|
36
|
+
options.headlessPrompt = args[++i] || '';
|
|
37
|
+
break;
|
|
29
38
|
case '-h':
|
|
30
39
|
case '--help':
|
|
31
40
|
console.log(`
|
|
@@ -36,14 +45,17 @@ Usage:
|
|
|
36
45
|
mentis update Update to latest version
|
|
37
46
|
mentis --resume Resume last session
|
|
38
47
|
mentis --yolo Auto-confirm mode (skip confirmations)
|
|
48
|
+
mentis -p "<prompt>" Headless mode (non-interactive)
|
|
39
49
|
|
|
40
50
|
Options:
|
|
41
51
|
--resume Load latest checkpoint on start
|
|
42
52
|
--yolo Skip all confirmation prompts
|
|
53
|
+
-p, --prompt <text> Headless mode with prompt
|
|
43
54
|
-h, --help Show this help message
|
|
44
55
|
|
|
45
56
|
Commands (in REPL):
|
|
46
57
|
/help Show all available commands
|
|
58
|
+
/clear Clear conversation context
|
|
47
59
|
/resume Resume last session
|
|
48
60
|
/init Initialize project with .mentis.md
|
|
49
61
|
/skills <list|show|create|validate> Manage Agent Skills
|
package/src/repl/ReplManager.ts
CHANGED
|
@@ -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';
|
|
@@ -36,6 +37,8 @@ const HISTORY_FILE = path.join(os.homedir(), '.mentis_history');
|
|
|
36
37
|
export interface CliOptions {
|
|
37
38
|
resume: boolean;
|
|
38
39
|
yolo: boolean;
|
|
40
|
+
headless: boolean;
|
|
41
|
+
headlessPrompt?: string;
|
|
39
42
|
}
|
|
40
43
|
|
|
41
44
|
export class ReplManager {
|
|
@@ -56,7 +59,7 @@ export class ReplManager {
|
|
|
56
59
|
private activeSkill: string | null = null; // Track currently active skill for allowed-tools
|
|
57
60
|
private options: CliOptions;
|
|
58
61
|
|
|
59
|
-
constructor(options: CliOptions = { resume: false, yolo: false }) {
|
|
62
|
+
constructor(options: CliOptions = { resume: false, yolo: false, headless: false }) {
|
|
60
63
|
this.options = options;
|
|
61
64
|
this.configManager = new ConfigManager();
|
|
62
65
|
this.contextManager = new ContextManager();
|
|
@@ -194,6 +197,13 @@ export class ReplManager {
|
|
|
194
197
|
}
|
|
195
198
|
|
|
196
199
|
public async start() {
|
|
200
|
+
// Headless mode: non-interactive, process prompt and exit
|
|
201
|
+
if (this.options.headless && this.options.headlessPrompt) {
|
|
202
|
+
await this.handleChat(this.options.headlessPrompt);
|
|
203
|
+
process.exit(0);
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
|
|
197
207
|
UIManager.renderDashboard({
|
|
198
208
|
model: this.currentModelName,
|
|
199
209
|
mode: this.mode,
|
|
@@ -220,53 +230,38 @@ export class ReplManager {
|
|
|
220
230
|
} catch (e) { }
|
|
221
231
|
}
|
|
222
232
|
|
|
233
|
+
// Initialize InputBox with history
|
|
234
|
+
const inputBox = new InputBox(commandHistory);
|
|
235
|
+
|
|
223
236
|
while (true) {
|
|
224
|
-
//
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
// Hint (Claude style puts it below, we put it above for standard terminal compatibility)
|
|
228
|
-
console.log(chalk.dim(' ? for shortcuts'));
|
|
229
|
-
|
|
230
|
-
const promptText = `> `; // Clean prompt
|
|
231
|
-
|
|
232
|
-
// Use readline for basic input to support history
|
|
233
|
-
const answer = await new Promise<string>((resolve) => {
|
|
234
|
-
const rl = readline.createInterface({
|
|
235
|
-
input: process.stdin,
|
|
236
|
-
output: process.stdout,
|
|
237
|
-
history: commandHistory,
|
|
238
|
-
historySize: 1000,
|
|
239
|
-
prompt: promptText
|
|
240
|
-
});
|
|
237
|
+
// Calculate context usage for display
|
|
238
|
+
const usage = this.contextVisualizer.calculateUsage(this.history);
|
|
241
239
|
|
|
242
|
-
|
|
240
|
+
// Display enhanced input frame
|
|
241
|
+
inputBox.displayFrame({
|
|
242
|
+
messageCount: this.history.length,
|
|
243
|
+
contextPercent: usage.percentage
|
|
244
|
+
});
|
|
243
245
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
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'
|
|
248
250
|
});
|
|
249
251
|
|
|
250
|
-
// Update history manually or grab from rl?
|
|
251
|
-
// rl.history gets updated when user hits enter.
|
|
252
|
-
// But we closed rl. We should manually save the input to our tracking array and file.
|
|
253
252
|
const input = answer.trim();
|
|
254
253
|
|
|
255
254
|
if (input) {
|
|
256
|
-
// Update
|
|
257
|
-
|
|
258
|
-
// Avoid duplicates if needed, but standard shell keeps them.
|
|
259
|
-
if (commandHistory[0] !== input) {
|
|
260
|
-
commandHistory.unshift(input);
|
|
261
|
-
}
|
|
255
|
+
// Update history via InputBox
|
|
256
|
+
inputBox.addToHistory(input);
|
|
262
257
|
|
|
263
|
-
// Append to file
|
|
258
|
+
// Append to file
|
|
264
259
|
try {
|
|
265
260
|
fs.appendFileSync(HISTORY_FILE, input + '\n');
|
|
266
261
|
} catch (e) { }
|
|
267
262
|
}
|
|
268
263
|
|
|
269
|
-
if (!
|
|
264
|
+
if (!input) continue;
|
|
270
265
|
|
|
271
266
|
if (input.startsWith('/')) {
|
|
272
267
|
await this.handleCommand(input);
|
|
@@ -371,6 +366,10 @@ export class ReplManager {
|
|
|
371
366
|
const updater = new UpdateManager();
|
|
372
367
|
await updater.checkAndPerformUpdate(true);
|
|
373
368
|
break;
|
|
369
|
+
case '/clear':
|
|
370
|
+
this.history = [];
|
|
371
|
+
console.log(chalk.green('\n✓ Context cleared\n'));
|
|
372
|
+
break;
|
|
374
373
|
case '/init':
|
|
375
374
|
await this.handleInitCommand();
|
|
376
375
|
break;
|
|
@@ -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.
|