@indiccoder/mentis-cli 1.0.9 → 1.1.1
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/.mentis/commands/ls.md +12 -0
- package/dist/commands/Command.js +6 -0
- package/dist/commands/CommandCreator.js +286 -0
- package/dist/commands/CommandManager.js +268 -0
- package/dist/commands/SlashCommandTool.js +160 -0
- package/dist/index.js +52 -2
- package/dist/repl/ReplManager.js +120 -4
- package/dist/ui/UIManager.js +2 -2
- package/dist/utils/ContextVisualizer.js +92 -0
- package/dist/utils/ConversationCompacter.js +101 -0
- package/dist/utils/ProjectInitializer.js +181 -0
- package/package.json +2 -2
- package/src/commands/Command.ts +40 -0
- package/src/commands/CommandCreator.ts +281 -0
- package/src/commands/CommandManager.ts +280 -0
- package/src/commands/SlashCommandTool.ts +152 -0
- package/src/index.ts +62 -2
- package/src/repl/ReplManager.ts +152 -4
- package/src/ui/UIManager.ts +2 -2
- package/src/utils/ContextVisualizer.ts +105 -0
- package/src/utils/ConversationCompacter.ts +128 -0
- package/src/utils/ProjectInitializer.ts +170 -0
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* SlashCommandTool - Tool for model-invoked custom command execution
|
|
4
|
+
* The model can call this tool when it determines a custom command should be run
|
|
5
|
+
*/
|
|
6
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
7
|
+
if (k2 === undefined) k2 = k;
|
|
8
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
9
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
10
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
11
|
+
}
|
|
12
|
+
Object.defineProperty(o, k2, desc);
|
|
13
|
+
}) : (function(o, m, k, k2) {
|
|
14
|
+
if (k2 === undefined) k2 = k;
|
|
15
|
+
o[k2] = m[k];
|
|
16
|
+
}));
|
|
17
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
18
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
19
|
+
}) : function(o, v) {
|
|
20
|
+
o["default"] = v;
|
|
21
|
+
});
|
|
22
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
23
|
+
var ownKeys = function(o) {
|
|
24
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
25
|
+
var ar = [];
|
|
26
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
27
|
+
return ar;
|
|
28
|
+
};
|
|
29
|
+
return ownKeys(o);
|
|
30
|
+
};
|
|
31
|
+
return function (mod) {
|
|
32
|
+
if (mod && mod.__esModule) return mod;
|
|
33
|
+
var result = {};
|
|
34
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
35
|
+
__setModuleDefault(result, mod);
|
|
36
|
+
return result;
|
|
37
|
+
};
|
|
38
|
+
})();
|
|
39
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
40
|
+
exports.ListCommandsTool = exports.SlashCommandTool = void 0;
|
|
41
|
+
const fs = __importStar(require("fs"));
|
|
42
|
+
const child_process_1 = require("child_process");
|
|
43
|
+
const util_1 = require("util");
|
|
44
|
+
const execAsync = (0, util_1.promisify)(child_process_1.exec);
|
|
45
|
+
class SlashCommandTool {
|
|
46
|
+
constructor(commandManager) {
|
|
47
|
+
this.name = 'slash_command';
|
|
48
|
+
this.description = 'Execute a custom slash command. Available commands can be seen in /help. Example: slash_command({ name: "review", arguments: ["src/file.ts"] })';
|
|
49
|
+
this.parameters = {
|
|
50
|
+
type: 'object',
|
|
51
|
+
properties: {
|
|
52
|
+
name: {
|
|
53
|
+
type: 'string',
|
|
54
|
+
description: 'The name of the custom command to execute'
|
|
55
|
+
},
|
|
56
|
+
arguments: {
|
|
57
|
+
type: 'array',
|
|
58
|
+
items: { type: 'string' },
|
|
59
|
+
description: 'Arguments to pass to the command'
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
required: ['name']
|
|
63
|
+
};
|
|
64
|
+
this.commandManager = commandManager;
|
|
65
|
+
}
|
|
66
|
+
async execute(args) {
|
|
67
|
+
const { name, arguments: cmdArgs = [] } = args;
|
|
68
|
+
if (!name) {
|
|
69
|
+
return 'Error: Command name is required';
|
|
70
|
+
}
|
|
71
|
+
const command = this.commandManager.getCommand(name);
|
|
72
|
+
if (!command) {
|
|
73
|
+
const availableCommands = this.commandManager.getAllCommands().map(c => c.name).join(', ');
|
|
74
|
+
return `Error: Command "${name}" not found. Available commands: ${availableCommands || 'none'}`;
|
|
75
|
+
}
|
|
76
|
+
try {
|
|
77
|
+
// Parse command and handle substitutions
|
|
78
|
+
const parsed = await this.commandManager.parseCommand(command, cmdArgs);
|
|
79
|
+
let content = parsed.content;
|
|
80
|
+
// Execute bash commands and collect results
|
|
81
|
+
const bashResults = [];
|
|
82
|
+
for (const bashCmd of parsed.bashCommands) {
|
|
83
|
+
try {
|
|
84
|
+
const result = await execAsync(bashCmd);
|
|
85
|
+
bashResults.push(`${bashCmd}\n${result.stdout}`);
|
|
86
|
+
}
|
|
87
|
+
catch (error) {
|
|
88
|
+
bashResults.push(`${bashCmd}\nError: ${error.message}`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
// Substitute bash outputs into content
|
|
92
|
+
let bashIndex = 0;
|
|
93
|
+
content = content.replace(/\[BASH_OUTPUT\]/g, () => {
|
|
94
|
+
return bashResults[bashIndex++] || '';
|
|
95
|
+
});
|
|
96
|
+
// Read file references
|
|
97
|
+
const fileContents = [];
|
|
98
|
+
for (const filePath of parsed.fileReferences) {
|
|
99
|
+
try {
|
|
100
|
+
const fileContent = fs.readFileSync(filePath, 'utf-8');
|
|
101
|
+
fileContents.push(`\n=== File: ${filePath} ===\n${fileContent}\n=== End of ${filePath} ===\n`);
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
fileContents.push(`\n=== File: ${filePath} ===\nError: ${error.message}\n=== End of ${filePath} ===\n`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
// Substitute file references
|
|
108
|
+
let fileIndex = 0;
|
|
109
|
+
content = content.replace(/@[^\s]+/g, () => {
|
|
110
|
+
return fileContents[fileIndex++] || '';
|
|
111
|
+
});
|
|
112
|
+
return `# Executing: /${name}${cmdArgs.length ? ' ' + cmdArgs.join(' ') : ''}\n\n${content}`;
|
|
113
|
+
}
|
|
114
|
+
catch (error) {
|
|
115
|
+
return `Error executing command "${name}": ${error.message}`;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
exports.SlashCommandTool = SlashCommandTool;
|
|
120
|
+
/**
|
|
121
|
+
* ListCommandsTool - Tool for listing custom commands
|
|
122
|
+
*/
|
|
123
|
+
class ListCommandsTool {
|
|
124
|
+
constructor(commandManager) {
|
|
125
|
+
this.name = 'list_commands';
|
|
126
|
+
this.description = 'List all available custom slash commands with their descriptions';
|
|
127
|
+
this.parameters = {
|
|
128
|
+
type: 'object',
|
|
129
|
+
properties: {},
|
|
130
|
+
required: []
|
|
131
|
+
};
|
|
132
|
+
this.commandManager = commandManager;
|
|
133
|
+
}
|
|
134
|
+
async execute() {
|
|
135
|
+
const commands = this.commandManager.getAllCommands();
|
|
136
|
+
if (commands.length === 0) {
|
|
137
|
+
return 'No custom commands available. Add commands to ~/.mentis/commands/ or .mentis/commands/';
|
|
138
|
+
}
|
|
139
|
+
let response = `# Custom Commands (${commands.length})\n\n`;
|
|
140
|
+
// Group by namespace
|
|
141
|
+
const grouped = new Map();
|
|
142
|
+
for (const cmd of commands) {
|
|
143
|
+
const ns = cmd.description.match(/\(([^)]+)\)/)?.[1] || cmd.type;
|
|
144
|
+
if (!grouped.has(ns)) {
|
|
145
|
+
grouped.set(ns, []);
|
|
146
|
+
}
|
|
147
|
+
grouped.get(ns).push(cmd);
|
|
148
|
+
}
|
|
149
|
+
for (const [namespace, cmds] of grouped) {
|
|
150
|
+
response += `## ${namespace}\n\n`;
|
|
151
|
+
for (const cmd of cmds) {
|
|
152
|
+
const params = cmd.frontmatter['argument-hint'] ? ` ${cmd.frontmatter['argument-hint']}` : '';
|
|
153
|
+
response += `**/${cmd.name}${params}**\n`;
|
|
154
|
+
response += `${cmd.description}\n\n`;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return response;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
exports.ListCommandsTool = ListCommandsTool;
|
package/dist/index.js
CHANGED
|
@@ -2,14 +2,64 @@
|
|
|
2
2
|
"use strict";
|
|
3
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
4
|
const ReplManager_1 = require("./repl/ReplManager");
|
|
5
|
+
function parseArgs() {
|
|
6
|
+
const args = process.argv.slice(2);
|
|
7
|
+
const options = {
|
|
8
|
+
resume: false,
|
|
9
|
+
yolo: false
|
|
10
|
+
};
|
|
11
|
+
let command = null;
|
|
12
|
+
for (const arg of args) {
|
|
13
|
+
switch (arg) {
|
|
14
|
+
case 'update':
|
|
15
|
+
command = 'update';
|
|
16
|
+
break;
|
|
17
|
+
case '--resume':
|
|
18
|
+
options.resume = true;
|
|
19
|
+
break;
|
|
20
|
+
case '--yolo':
|
|
21
|
+
options.yolo = true;
|
|
22
|
+
break;
|
|
23
|
+
case '-h':
|
|
24
|
+
case '--help':
|
|
25
|
+
console.log(`
|
|
26
|
+
Mentis CLI - AI Coding Assistant
|
|
27
|
+
|
|
28
|
+
Usage:
|
|
29
|
+
mentis Start interactive REPL
|
|
30
|
+
mentis update Update to latest version
|
|
31
|
+
mentis --resume Resume last session
|
|
32
|
+
mentis --yolo Auto-confirm mode (skip confirmations)
|
|
33
|
+
|
|
34
|
+
Options:
|
|
35
|
+
--resume Load latest checkpoint on start
|
|
36
|
+
--yolo Skip all confirmation prompts
|
|
37
|
+
-h, --help Show this help message
|
|
38
|
+
|
|
39
|
+
Commands (in REPL):
|
|
40
|
+
/help Show all available commands
|
|
41
|
+
/resume Resume last session
|
|
42
|
+
/init Initialize project with .mentis.md
|
|
43
|
+
/skills <list|show|create|validate> Manage Agent Skills
|
|
44
|
+
/commands <list|create|validate> Manage Custom Commands
|
|
45
|
+
`);
|
|
46
|
+
process.exit(0);
|
|
47
|
+
break;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return { command, options };
|
|
51
|
+
}
|
|
5
52
|
async function main() {
|
|
6
|
-
|
|
53
|
+
const { command, options } = parseArgs();
|
|
54
|
+
// Handle update command
|
|
55
|
+
if (command === 'update') {
|
|
7
56
|
const { UpdateManager } = require('./utils/UpdateManager');
|
|
8
57
|
const updater = new UpdateManager();
|
|
9
58
|
await updater.checkAndPerformUpdate(true);
|
|
10
59
|
return;
|
|
11
60
|
}
|
|
12
|
-
|
|
61
|
+
// Start REPL with options
|
|
62
|
+
const repl = new ReplManager_1.ReplManager(options);
|
|
13
63
|
await repl.start();
|
|
14
64
|
}
|
|
15
65
|
main().catch((error) => {
|
package/dist/repl/ReplManager.js
CHANGED
|
@@ -54,6 +54,11 @@ const McpClient_1 = require("../mcp/McpClient");
|
|
|
54
54
|
const CheckpointManager_1 = require("../checkpoint/CheckpointManager");
|
|
55
55
|
const SkillsManager_1 = require("../skills/SkillsManager");
|
|
56
56
|
const LoadSkillTool_1 = require("../skills/LoadSkillTool");
|
|
57
|
+
const ContextVisualizer_1 = require("../utils/ContextVisualizer");
|
|
58
|
+
const ProjectInitializer_1 = require("../utils/ProjectInitializer");
|
|
59
|
+
const ConversationCompacter_1 = require("../utils/ConversationCompacter");
|
|
60
|
+
const CommandManager_1 = require("../commands/CommandManager");
|
|
61
|
+
const SlashCommandTool_1 = require("../commands/SlashCommandTool");
|
|
57
62
|
const readline = __importStar(require("readline"));
|
|
58
63
|
const fs = __importStar(require("fs"));
|
|
59
64
|
const path = __importStar(require("path"));
|
|
@@ -62,17 +67,21 @@ const marked_1 = require("marked");
|
|
|
62
67
|
const marked_terminal_1 = __importDefault(require("marked-terminal"));
|
|
63
68
|
const HISTORY_FILE = path.join(os.homedir(), '.mentis_history');
|
|
64
69
|
class ReplManager {
|
|
65
|
-
constructor() {
|
|
70
|
+
constructor(options = { resume: false, yolo: false }) {
|
|
66
71
|
this.history = [];
|
|
67
72
|
this.mode = 'BUILD';
|
|
68
73
|
this.tools = [];
|
|
69
74
|
this.mcpClients = [];
|
|
70
75
|
this.currentModelName = 'Unknown';
|
|
71
76
|
this.activeSkill = null; // Track currently active skill for allowed-tools
|
|
77
|
+
this.options = options;
|
|
72
78
|
this.configManager = new ConfigManager_1.ConfigManager();
|
|
73
79
|
this.contextManager = new ContextManager_1.ContextManager();
|
|
74
80
|
this.checkpointManager = new CheckpointManager_1.CheckpointManager();
|
|
75
81
|
this.skillsManager = new SkillsManager_1.SkillsManager();
|
|
82
|
+
this.contextVisualizer = new ContextVisualizer_1.ContextVisualizer();
|
|
83
|
+
this.conversationCompacter = new ConversationCompacter_1.ConversationCompacter();
|
|
84
|
+
this.commandManager = new CommandManager_1.CommandManager();
|
|
76
85
|
this.shell = new PersistentShell_1.PersistentShell();
|
|
77
86
|
// Create tools array without skill tools first
|
|
78
87
|
this.tools = [
|
|
@@ -99,16 +108,20 @@ class ReplManager {
|
|
|
99
108
|
this.initializeSkills();
|
|
100
109
|
}
|
|
101
110
|
/**
|
|
102
|
-
* Initialize the skills system
|
|
111
|
+
* Initialize the skills and custom commands system
|
|
103
112
|
*/
|
|
104
113
|
async initializeSkills() {
|
|
114
|
+
// Initialize skills
|
|
105
115
|
this.skillsManager.ensureDirectoriesExist();
|
|
106
116
|
await this.skillsManager.discoverSkills();
|
|
117
|
+
// Initialize custom commands
|
|
118
|
+
this.commandManager.ensureDirectoriesExist();
|
|
119
|
+
await this.commandManager.discoverCommands();
|
|
107
120
|
// Add skill tools to the tools list
|
|
108
121
|
// Pass callback to LoadSkillTool to track active skill
|
|
109
122
|
this.tools.push(new LoadSkillTool_1.LoadSkillTool(this.skillsManager, (skill) => {
|
|
110
123
|
this.activeSkill = skill ? skill.name : null;
|
|
111
|
-
}), new LoadSkillTool_1.ListSkillsTool(this.skillsManager), new LoadSkillTool_1.ReadSkillFileTool(this.skillsManager));
|
|
124
|
+
}), new LoadSkillTool_1.ListSkillsTool(this.skillsManager), new LoadSkillTool_1.ReadSkillFileTool(this.skillsManager), new SlashCommandTool_1.SlashCommandTool(this.commandManager), new SlashCommandTool_1.ListCommandsTool(this.commandManager));
|
|
112
125
|
}
|
|
113
126
|
/**
|
|
114
127
|
* Check if a tool is allowed by the currently active skill
|
|
@@ -141,7 +154,9 @@ class ReplManager {
|
|
|
141
154
|
'git_pull': 'GitPull',
|
|
142
155
|
'load_skill': 'Read',
|
|
143
156
|
'list_skills': 'Read',
|
|
144
|
-
'read_skill_file': 'Read'
|
|
157
|
+
'read_skill_file': 'Read',
|
|
158
|
+
'slash_command': 'Read',
|
|
159
|
+
'list_commands': 'Read'
|
|
145
160
|
};
|
|
146
161
|
const mappedToolName = toolMapping[toolName] || toolName;
|
|
147
162
|
return skill.allowedTools.includes(mappedToolName);
|
|
@@ -183,6 +198,18 @@ class ReplManager {
|
|
|
183
198
|
mode: this.mode,
|
|
184
199
|
cwd: process.cwd()
|
|
185
200
|
});
|
|
201
|
+
// Auto-resume if --resume flag is set
|
|
202
|
+
if (this.options.resume) {
|
|
203
|
+
const cp = this.checkpointManager.load('latest');
|
|
204
|
+
if (cp) {
|
|
205
|
+
this.history = cp.history;
|
|
206
|
+
console.log(chalk_1.default.green(`\n✓ Resumed session from ${new Date(cp.timestamp).toLocaleString()}`));
|
|
207
|
+
console.log(chalk_1.default.dim(` Messages: ${this.history.length}\n`));
|
|
208
|
+
}
|
|
209
|
+
else {
|
|
210
|
+
console.log(chalk_1.default.yellow('\n⚠ No previous session found to resume.\n'));
|
|
211
|
+
}
|
|
212
|
+
}
|
|
186
213
|
// Load History
|
|
187
214
|
let commandHistory = [];
|
|
188
215
|
if (fs.existsSync(HISTORY_FILE)) {
|
|
@@ -256,11 +283,13 @@ class ReplManager {
|
|
|
256
283
|
console.log(' /use <provider> [model] - Quick switch (legacy)');
|
|
257
284
|
console.log(' /mcp <cmd> - Manage MCP servers');
|
|
258
285
|
console.log(' /skills <list|show|create|validate> - Manage Agent Skills');
|
|
286
|
+
console.log(' /commands <list|create|validate> - Manage Custom Commands');
|
|
259
287
|
console.log(' /resume - Resume last session');
|
|
260
288
|
console.log(' /checkpoint <save|load|list> [name] - Manage checkpoints');
|
|
261
289
|
console.log(' /search <query> - Search codebase');
|
|
262
290
|
console.log(' /run <cmd> - Run shell command');
|
|
263
291
|
console.log(' /commit [msg] - Git commit all changes');
|
|
292
|
+
console.log(' /init - Initialize project with .mentis.md');
|
|
264
293
|
break;
|
|
265
294
|
case '/plan':
|
|
266
295
|
this.mode = 'PLAN';
|
|
@@ -332,9 +361,15 @@ class ReplManager {
|
|
|
332
361
|
const updater = new UpdateManager();
|
|
333
362
|
await updater.checkAndPerformUpdate(true);
|
|
334
363
|
break;
|
|
364
|
+
case '/init':
|
|
365
|
+
await this.handleInitCommand();
|
|
366
|
+
break;
|
|
335
367
|
case '/skills':
|
|
336
368
|
await this.handleSkillsCommand(args);
|
|
337
369
|
break;
|
|
370
|
+
case '/commands':
|
|
371
|
+
await this.handleCommandsCommand(args);
|
|
372
|
+
break;
|
|
338
373
|
default:
|
|
339
374
|
console.log(chalk_1.default.red(`Unknown command: ${command}`));
|
|
340
375
|
}
|
|
@@ -342,6 +377,7 @@ class ReplManager {
|
|
|
342
377
|
async handleChat(input) {
|
|
343
378
|
const context = this.contextManager.getContextString();
|
|
344
379
|
const skillsContext = this.skillsManager.getSkillsContext();
|
|
380
|
+
const commandsContext = this.commandManager.getCommandsContext();
|
|
345
381
|
let fullInput = input;
|
|
346
382
|
let modeInstruction = '';
|
|
347
383
|
if (this.mode === 'PLAN') {
|
|
@@ -355,6 +391,10 @@ class ReplManager {
|
|
|
355
391
|
if (skillsContext) {
|
|
356
392
|
fullInput = `${skillsContext}\n\n${fullInput}`;
|
|
357
393
|
}
|
|
394
|
+
// Add commands context if available
|
|
395
|
+
if (commandsContext) {
|
|
396
|
+
fullInput = `${commandsContext}\n\n${fullInput}`;
|
|
397
|
+
}
|
|
358
398
|
if (context) {
|
|
359
399
|
fullInput = `${context}\n\nUser Question: ${fullInput}`;
|
|
360
400
|
}
|
|
@@ -494,8 +534,16 @@ class ReplManager {
|
|
|
494
534
|
const totalCost = this.estimateCost(input_tokens, output_tokens);
|
|
495
535
|
console.log(chalk_1.default.dim(`\n(Tokens: ${input_tokens} in / ${output_tokens} out | Est. Cost: $${totalCost.toFixed(5)})`));
|
|
496
536
|
}
|
|
537
|
+
// Display context bar
|
|
538
|
+
const contextBar = this.contextVisualizer.getContextBar(this.history);
|
|
539
|
+
console.log(chalk_1.default.dim(`\n${contextBar}`));
|
|
497
540
|
console.log('');
|
|
498
541
|
this.history.push({ role: 'assistant', content: response.content });
|
|
542
|
+
// Auto-compact prompt when context is at 80%
|
|
543
|
+
const usage = this.contextVisualizer.calculateUsage(this.history);
|
|
544
|
+
if (usage.percentage >= 80) {
|
|
545
|
+
this.history = await this.conversationCompacter.promptIfCompactNeeded(usage.percentage, this.history, this.modelClient, this.options.yolo);
|
|
546
|
+
}
|
|
499
547
|
}
|
|
500
548
|
}
|
|
501
549
|
catch (error) {
|
|
@@ -966,6 +1014,74 @@ class ReplManager {
|
|
|
966
1014
|
console.log(chalk_1.default.dim(`\nSupporting files: ${files.join(', ')}`));
|
|
967
1015
|
}
|
|
968
1016
|
}
|
|
1017
|
+
async handleInitCommand() {
|
|
1018
|
+
const initializer = new ProjectInitializer_1.ProjectInitializer();
|
|
1019
|
+
await initializer.run();
|
|
1020
|
+
}
|
|
1021
|
+
async handleCommandsCommand(args) {
|
|
1022
|
+
if (args.length < 1) {
|
|
1023
|
+
// Show commands list by default
|
|
1024
|
+
await this.handleCommandsCommand(['list']);
|
|
1025
|
+
return;
|
|
1026
|
+
}
|
|
1027
|
+
const action = args[0];
|
|
1028
|
+
switch (action) {
|
|
1029
|
+
case 'list':
|
|
1030
|
+
await this.handleCommandsList();
|
|
1031
|
+
break;
|
|
1032
|
+
case 'create':
|
|
1033
|
+
await this.handleCommandsCreate(args[1]);
|
|
1034
|
+
break;
|
|
1035
|
+
case 'validate':
|
|
1036
|
+
await this.handleCommandsValidate();
|
|
1037
|
+
break;
|
|
1038
|
+
default:
|
|
1039
|
+
console.log(chalk_1.default.red(`Unknown commands action: ${action}`));
|
|
1040
|
+
console.log(chalk_1.default.yellow('Available actions: list, create, validate'));
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
async handleCommandsList() {
|
|
1044
|
+
const commands = this.commandManager.getAllCommands();
|
|
1045
|
+
if (commands.length === 0) {
|
|
1046
|
+
console.log(chalk_1.default.yellow('No custom commands available.'));
|
|
1047
|
+
console.log(chalk_1.default.dim('Create commands with: /commands create'));
|
|
1048
|
+
console.log(chalk_1.default.dim('Add commands to: ~/.mentis/commands/ or .mentis/commands/'));
|
|
1049
|
+
return;
|
|
1050
|
+
}
|
|
1051
|
+
console.log(chalk_1.default.cyan(`\nCustom Commands (${commands.length}):\n`));
|
|
1052
|
+
// Group by namespace
|
|
1053
|
+
const grouped = new Map();
|
|
1054
|
+
for (const cmd of commands) {
|
|
1055
|
+
const ns = cmd.description.match(/\(([^)]+)\)/)?.[1] || cmd.type;
|
|
1056
|
+
if (!grouped.has(ns)) {
|
|
1057
|
+
grouped.set(ns, []);
|
|
1058
|
+
}
|
|
1059
|
+
grouped.get(ns).push(cmd);
|
|
1060
|
+
}
|
|
1061
|
+
for (const [namespace, cmds] of grouped) {
|
|
1062
|
+
console.log(chalk_1.default.bold(`\n${namespace}`));
|
|
1063
|
+
for (const cmd of cmds) {
|
|
1064
|
+
const params = cmd.frontmatter['argument-hint'] ? ` ${cmd.frontmatter['argument-hint']}` : '';
|
|
1065
|
+
console.log(` /${cmd.name}${params}`);
|
|
1066
|
+
console.log(` ${cmd.description.replace(/\s*\([^)]+\)/, '')}`);
|
|
1067
|
+
if (cmd.frontmatter['allowed-tools'] && cmd.frontmatter['allowed-tools'].length > 0) {
|
|
1068
|
+
console.log(chalk_1.default.dim(` Allowed tools: ${cmd.frontmatter['allowed-tools'].join(', ')}`));
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
console.log('');
|
|
1073
|
+
}
|
|
1074
|
+
async handleCommandsCreate(name) {
|
|
1075
|
+
const { CommandCreator } = await Promise.resolve().then(() => __importStar(require('../commands/CommandCreator')));
|
|
1076
|
+
const creator = new CommandCreator(this.commandManager);
|
|
1077
|
+
await creator.run(name);
|
|
1078
|
+
// Re-discover commands after creation
|
|
1079
|
+
await this.commandManager.discoverCommands();
|
|
1080
|
+
}
|
|
1081
|
+
async handleCommandsValidate() {
|
|
1082
|
+
const { validateCommands } = await Promise.resolve().then(() => __importStar(require('../commands/CommandCreator')));
|
|
1083
|
+
await validateCommands(this.commandManager);
|
|
1084
|
+
}
|
|
969
1085
|
estimateCost(input, output) {
|
|
970
1086
|
const config = this.configManager.getConfig();
|
|
971
1087
|
const provider = config.defaultProvider;
|
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.
|
|
22
|
+
console.log(chalk_1.default.gray(' v1.1.1 - AI Coding Agent'));
|
|
23
23
|
console.log('');
|
|
24
24
|
}
|
|
25
25
|
static renderDashboard(config) {
|
|
26
26
|
const { model, cwd } = config;
|
|
27
|
-
const version = 'v1.
|
|
27
|
+
const version = 'v1.1.1';
|
|
28
28
|
// Layout: Left (Status/Welcome) | Right (Tips/Activity)
|
|
29
29
|
// Total width ~80 chars.
|
|
30
30
|
// Left ~45, Right ~30.
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* ContextVisualizer - Auto context bar display
|
|
4
|
+
* Shows token usage as a colored progress bar
|
|
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.ContextVisualizer = void 0;
|
|
11
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
12
|
+
class ContextVisualizer {
|
|
13
|
+
constructor(maxTokens) {
|
|
14
|
+
this.maxTokens = 128000; // Default context window
|
|
15
|
+
if (maxTokens) {
|
|
16
|
+
this.maxTokens = maxTokens;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Calculate approximate token count from text
|
|
21
|
+
* Rough estimate: ~4 characters per token
|
|
22
|
+
*/
|
|
23
|
+
estimateTokens(text) {
|
|
24
|
+
if (!text)
|
|
25
|
+
return 0;
|
|
26
|
+
// Rough estimation: ~4 characters per token for English text
|
|
27
|
+
return Math.ceil(text.length / 4);
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Calculate total tokens from message history
|
|
31
|
+
*/
|
|
32
|
+
calculateUsage(history) {
|
|
33
|
+
let totalChars = 0;
|
|
34
|
+
for (const msg of history) {
|
|
35
|
+
if (msg.content) {
|
|
36
|
+
totalChars += msg.content.length;
|
|
37
|
+
}
|
|
38
|
+
if (msg.tool_calls) {
|
|
39
|
+
totalChars += JSON.stringify(msg.tool_calls).length;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
// Add overhead for system prompt, skills, etc.
|
|
43
|
+
totalChars += 2000;
|
|
44
|
+
// Rough estimation: ~4 characters per token
|
|
45
|
+
const tokens = Math.ceil(totalChars / 4);
|
|
46
|
+
const percentage = Math.min(100, Math.round((tokens / this.maxTokens) * 100));
|
|
47
|
+
return { tokens, percentage, maxTokens: this.maxTokens };
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Format the context bar for display
|
|
51
|
+
*/
|
|
52
|
+
formatBar(usage) {
|
|
53
|
+
const { percentage, tokens, maxTokens } = usage;
|
|
54
|
+
// Create progress bar (20 chars wide)
|
|
55
|
+
const filled = Math.round(percentage / 5);
|
|
56
|
+
const empty = 20 - filled;
|
|
57
|
+
let bar;
|
|
58
|
+
if (percentage < 60) {
|
|
59
|
+
bar = chalk_1.default.green('█'.repeat(filled)) + chalk_1.default.gray('░'.repeat(empty));
|
|
60
|
+
}
|
|
61
|
+
else if (percentage < 80) {
|
|
62
|
+
bar = chalk_1.default.yellow('█'.repeat(filled)) + chalk_1.default.gray('░'.repeat(empty));
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
bar = chalk_1.default.red('█'.repeat(filled)) + chalk_1.default.gray('░'.repeat(empty));
|
|
66
|
+
}
|
|
67
|
+
const tokensK = Math.round(tokens / 1000);
|
|
68
|
+
const maxTokensK = Math.round(maxTokens / 1000);
|
|
69
|
+
return `${bar} ${percentage}% | ${tokensK}k/${maxTokensK} tokens`;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Get the context bar string for current history
|
|
73
|
+
*/
|
|
74
|
+
getContextBar(history) {
|
|
75
|
+
const usage = this.calculateUsage(history);
|
|
76
|
+
return this.formatBar(usage);
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Check if context is at warning threshold (>=80%)
|
|
80
|
+
*/
|
|
81
|
+
shouldCompact(history) {
|
|
82
|
+
const usage = this.calculateUsage(history);
|
|
83
|
+
return usage.percentage >= 80;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Set custom max tokens (for different models)
|
|
87
|
+
*/
|
|
88
|
+
setMaxTokens(max) {
|
|
89
|
+
this.maxTokens = max;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
exports.ContextVisualizer = ContextVisualizer;
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* ConversationCompacter - Compact conversation to save tokens
|
|
4
|
+
* Summarizes conversation history while preserving important context
|
|
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.ConversationCompacter = void 0;
|
|
11
|
+
const inquirer_1 = __importDefault(require("inquirer"));
|
|
12
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
13
|
+
class ConversationCompacter {
|
|
14
|
+
/**
|
|
15
|
+
* Compact conversation history using AI
|
|
16
|
+
*/
|
|
17
|
+
async compact(history, modelClient, options) {
|
|
18
|
+
const { keepSystemMessages = true, focusTopic } = options || {};
|
|
19
|
+
// Preserve system-style messages
|
|
20
|
+
const preserved = [];
|
|
21
|
+
const toCompact = [];
|
|
22
|
+
for (const msg of history) {
|
|
23
|
+
if (msg.role === 'system' || msg.role === 'tool') {
|
|
24
|
+
preserved.push(msg);
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
toCompact.push(msg);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
if (toCompact.length === 0) {
|
|
31
|
+
return history;
|
|
32
|
+
}
|
|
33
|
+
// Create compaction prompt
|
|
34
|
+
let compactPrompt = 'Please summarize the following conversation into a concise overview. ';
|
|
35
|
+
compactPrompt += 'Include:\n';
|
|
36
|
+
compactPrompt += '- The main topic/problem being discussed\n';
|
|
37
|
+
compactPrompt += '- Key decisions made\n';
|
|
38
|
+
compactPrompt += '- Important technical details\n';
|
|
39
|
+
compactPrompt += '- Current status/next steps\n\n';
|
|
40
|
+
if (focusTopic) {
|
|
41
|
+
compactPrompt += `Focus primarily on content related to: ${focusTopic}\n\n`;
|
|
42
|
+
}
|
|
43
|
+
compactPrompt += 'Return ONLY the summary, no other text.\n\n---\n\n';
|
|
44
|
+
for (const msg of toCompact.slice(-10)) { // Last 10 messages for context
|
|
45
|
+
compactPrompt += `${msg.role.toUpperCase()}: ${msg.content}\n\n`;
|
|
46
|
+
}
|
|
47
|
+
try {
|
|
48
|
+
// Call AI to summarize
|
|
49
|
+
const summaryResponse = await modelClient.chat([{ role: 'user', content: compactPrompt }], []);
|
|
50
|
+
const summary = summaryResponse.content;
|
|
51
|
+
// Create new compacted history
|
|
52
|
+
const newHistory = [...preserved];
|
|
53
|
+
// Add summary as a system message for context
|
|
54
|
+
newHistory.push({
|
|
55
|
+
role: 'system',
|
|
56
|
+
content: `[Previous Conversation Summary]\n${summary}`
|
|
57
|
+
});
|
|
58
|
+
return newHistory;
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
console.error('Compaction failed:', error);
|
|
62
|
+
return history; // Return original if compaction fails
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Prompt user to compact when threshold is reached
|
|
67
|
+
*/
|
|
68
|
+
async promptIfCompactNeeded(percentage, history, modelClient, yolo = false) {
|
|
69
|
+
if (percentage < 80) {
|
|
70
|
+
return history;
|
|
71
|
+
}
|
|
72
|
+
console.log(chalk_1.default.yellow(`\n⚠️ Context is ${percentage}% full. Consider compacting to save tokens.`));
|
|
73
|
+
// Skip confirmation if yolo mode is enabled
|
|
74
|
+
if (!yolo) {
|
|
75
|
+
const { shouldCompact } = await inquirer_1.default.prompt([
|
|
76
|
+
{
|
|
77
|
+
type: 'confirm',
|
|
78
|
+
name: 'shouldCompact',
|
|
79
|
+
message: 'Compact conversation now?',
|
|
80
|
+
default: true
|
|
81
|
+
}
|
|
82
|
+
]);
|
|
83
|
+
if (!shouldCompact) {
|
|
84
|
+
return history;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
const focusTopic = yolo ? '' : await inquirer_1.default.prompt([
|
|
88
|
+
{
|
|
89
|
+
type: 'input',
|
|
90
|
+
name: 'focusTopic',
|
|
91
|
+
message: 'Focus on specific topic? (leave empty for general)',
|
|
92
|
+
default: ''
|
|
93
|
+
}
|
|
94
|
+
]).then(a => a.focusTopic);
|
|
95
|
+
return await this.compact(history, modelClient, {
|
|
96
|
+
keepSystemMessages: true,
|
|
97
|
+
focusTopic: focusTopic || undefined
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
exports.ConversationCompacter = ConversationCompacter;
|