@indiccoder/mentis-cli 1.0.9 → 1.1.0
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/repl/ReplManager.js +106 -3
- package/dist/ui/UIManager.js +2 -2
- package/dist/utils/ContextVisualizer.js +92 -0
- package/dist/utils/ConversationCompacter.js +98 -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/repl/ReplManager.ts +131 -3
- package/src/ui/UIManager.ts +2 -2
- package/src/utils/ContextVisualizer.ts +105 -0
- package/src/utils/ConversationCompacter.ts +124 -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/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"));
|
|
@@ -73,6 +78,9 @@ class ReplManager {
|
|
|
73
78
|
this.contextManager = new ContextManager_1.ContextManager();
|
|
74
79
|
this.checkpointManager = new CheckpointManager_1.CheckpointManager();
|
|
75
80
|
this.skillsManager = new SkillsManager_1.SkillsManager();
|
|
81
|
+
this.contextVisualizer = new ContextVisualizer_1.ContextVisualizer();
|
|
82
|
+
this.conversationCompacter = new ConversationCompacter_1.ConversationCompacter();
|
|
83
|
+
this.commandManager = new CommandManager_1.CommandManager();
|
|
76
84
|
this.shell = new PersistentShell_1.PersistentShell();
|
|
77
85
|
// Create tools array without skill tools first
|
|
78
86
|
this.tools = [
|
|
@@ -99,16 +107,20 @@ class ReplManager {
|
|
|
99
107
|
this.initializeSkills();
|
|
100
108
|
}
|
|
101
109
|
/**
|
|
102
|
-
* Initialize the skills system
|
|
110
|
+
* Initialize the skills and custom commands system
|
|
103
111
|
*/
|
|
104
112
|
async initializeSkills() {
|
|
113
|
+
// Initialize skills
|
|
105
114
|
this.skillsManager.ensureDirectoriesExist();
|
|
106
115
|
await this.skillsManager.discoverSkills();
|
|
116
|
+
// Initialize custom commands
|
|
117
|
+
this.commandManager.ensureDirectoriesExist();
|
|
118
|
+
await this.commandManager.discoverCommands();
|
|
107
119
|
// Add skill tools to the tools list
|
|
108
120
|
// Pass callback to LoadSkillTool to track active skill
|
|
109
121
|
this.tools.push(new LoadSkillTool_1.LoadSkillTool(this.skillsManager, (skill) => {
|
|
110
122
|
this.activeSkill = skill ? skill.name : null;
|
|
111
|
-
}), new LoadSkillTool_1.ListSkillsTool(this.skillsManager), new LoadSkillTool_1.ReadSkillFileTool(this.skillsManager));
|
|
123
|
+
}), 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
124
|
}
|
|
113
125
|
/**
|
|
114
126
|
* Check if a tool is allowed by the currently active skill
|
|
@@ -141,7 +153,9 @@ class ReplManager {
|
|
|
141
153
|
'git_pull': 'GitPull',
|
|
142
154
|
'load_skill': 'Read',
|
|
143
155
|
'list_skills': 'Read',
|
|
144
|
-
'read_skill_file': 'Read'
|
|
156
|
+
'read_skill_file': 'Read',
|
|
157
|
+
'slash_command': 'Read',
|
|
158
|
+
'list_commands': 'Read'
|
|
145
159
|
};
|
|
146
160
|
const mappedToolName = toolMapping[toolName] || toolName;
|
|
147
161
|
return skill.allowedTools.includes(mappedToolName);
|
|
@@ -256,11 +270,13 @@ class ReplManager {
|
|
|
256
270
|
console.log(' /use <provider> [model] - Quick switch (legacy)');
|
|
257
271
|
console.log(' /mcp <cmd> - Manage MCP servers');
|
|
258
272
|
console.log(' /skills <list|show|create|validate> - Manage Agent Skills');
|
|
273
|
+
console.log(' /commands <list|create|validate> - Manage Custom Commands');
|
|
259
274
|
console.log(' /resume - Resume last session');
|
|
260
275
|
console.log(' /checkpoint <save|load|list> [name] - Manage checkpoints');
|
|
261
276
|
console.log(' /search <query> - Search codebase');
|
|
262
277
|
console.log(' /run <cmd> - Run shell command');
|
|
263
278
|
console.log(' /commit [msg] - Git commit all changes');
|
|
279
|
+
console.log(' /init - Initialize project with .mentis.md');
|
|
264
280
|
break;
|
|
265
281
|
case '/plan':
|
|
266
282
|
this.mode = 'PLAN';
|
|
@@ -332,9 +348,15 @@ class ReplManager {
|
|
|
332
348
|
const updater = new UpdateManager();
|
|
333
349
|
await updater.checkAndPerformUpdate(true);
|
|
334
350
|
break;
|
|
351
|
+
case '/init':
|
|
352
|
+
await this.handleInitCommand();
|
|
353
|
+
break;
|
|
335
354
|
case '/skills':
|
|
336
355
|
await this.handleSkillsCommand(args);
|
|
337
356
|
break;
|
|
357
|
+
case '/commands':
|
|
358
|
+
await this.handleCommandsCommand(args);
|
|
359
|
+
break;
|
|
338
360
|
default:
|
|
339
361
|
console.log(chalk_1.default.red(`Unknown command: ${command}`));
|
|
340
362
|
}
|
|
@@ -342,6 +364,7 @@ class ReplManager {
|
|
|
342
364
|
async handleChat(input) {
|
|
343
365
|
const context = this.contextManager.getContextString();
|
|
344
366
|
const skillsContext = this.skillsManager.getSkillsContext();
|
|
367
|
+
const commandsContext = this.commandManager.getCommandsContext();
|
|
345
368
|
let fullInput = input;
|
|
346
369
|
let modeInstruction = '';
|
|
347
370
|
if (this.mode === 'PLAN') {
|
|
@@ -355,6 +378,10 @@ class ReplManager {
|
|
|
355
378
|
if (skillsContext) {
|
|
356
379
|
fullInput = `${skillsContext}\n\n${fullInput}`;
|
|
357
380
|
}
|
|
381
|
+
// Add commands context if available
|
|
382
|
+
if (commandsContext) {
|
|
383
|
+
fullInput = `${commandsContext}\n\n${fullInput}`;
|
|
384
|
+
}
|
|
358
385
|
if (context) {
|
|
359
386
|
fullInput = `${context}\n\nUser Question: ${fullInput}`;
|
|
360
387
|
}
|
|
@@ -494,8 +521,16 @@ class ReplManager {
|
|
|
494
521
|
const totalCost = this.estimateCost(input_tokens, output_tokens);
|
|
495
522
|
console.log(chalk_1.default.dim(`\n(Tokens: ${input_tokens} in / ${output_tokens} out | Est. Cost: $${totalCost.toFixed(5)})`));
|
|
496
523
|
}
|
|
524
|
+
// Display context bar
|
|
525
|
+
const contextBar = this.contextVisualizer.getContextBar(this.history);
|
|
526
|
+
console.log(chalk_1.default.dim(`\n${contextBar}`));
|
|
497
527
|
console.log('');
|
|
498
528
|
this.history.push({ role: 'assistant', content: response.content });
|
|
529
|
+
// Auto-compact prompt when context is at 80%
|
|
530
|
+
const usage = this.contextVisualizer.calculateUsage(this.history);
|
|
531
|
+
if (usage.percentage >= 80) {
|
|
532
|
+
this.history = await this.conversationCompacter.promptIfCompactNeeded(usage.percentage, this.history, this.modelClient);
|
|
533
|
+
}
|
|
499
534
|
}
|
|
500
535
|
}
|
|
501
536
|
catch (error) {
|
|
@@ -966,6 +1001,74 @@ class ReplManager {
|
|
|
966
1001
|
console.log(chalk_1.default.dim(`\nSupporting files: ${files.join(', ')}`));
|
|
967
1002
|
}
|
|
968
1003
|
}
|
|
1004
|
+
async handleInitCommand() {
|
|
1005
|
+
const initializer = new ProjectInitializer_1.ProjectInitializer();
|
|
1006
|
+
await initializer.run();
|
|
1007
|
+
}
|
|
1008
|
+
async handleCommandsCommand(args) {
|
|
1009
|
+
if (args.length < 1) {
|
|
1010
|
+
// Show commands list by default
|
|
1011
|
+
await this.handleCommandsCommand(['list']);
|
|
1012
|
+
return;
|
|
1013
|
+
}
|
|
1014
|
+
const action = args[0];
|
|
1015
|
+
switch (action) {
|
|
1016
|
+
case 'list':
|
|
1017
|
+
await this.handleCommandsList();
|
|
1018
|
+
break;
|
|
1019
|
+
case 'create':
|
|
1020
|
+
await this.handleCommandsCreate(args[1]);
|
|
1021
|
+
break;
|
|
1022
|
+
case 'validate':
|
|
1023
|
+
await this.handleCommandsValidate();
|
|
1024
|
+
break;
|
|
1025
|
+
default:
|
|
1026
|
+
console.log(chalk_1.default.red(`Unknown commands action: ${action}`));
|
|
1027
|
+
console.log(chalk_1.default.yellow('Available actions: list, create, validate'));
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
async handleCommandsList() {
|
|
1031
|
+
const commands = this.commandManager.getAllCommands();
|
|
1032
|
+
if (commands.length === 0) {
|
|
1033
|
+
console.log(chalk_1.default.yellow('No custom commands available.'));
|
|
1034
|
+
console.log(chalk_1.default.dim('Create commands with: /commands create'));
|
|
1035
|
+
console.log(chalk_1.default.dim('Add commands to: ~/.mentis/commands/ or .mentis/commands/'));
|
|
1036
|
+
return;
|
|
1037
|
+
}
|
|
1038
|
+
console.log(chalk_1.default.cyan(`\nCustom Commands (${commands.length}):\n`));
|
|
1039
|
+
// Group by namespace
|
|
1040
|
+
const grouped = new Map();
|
|
1041
|
+
for (const cmd of commands) {
|
|
1042
|
+
const ns = cmd.description.match(/\(([^)]+)\)/)?.[1] || cmd.type;
|
|
1043
|
+
if (!grouped.has(ns)) {
|
|
1044
|
+
grouped.set(ns, []);
|
|
1045
|
+
}
|
|
1046
|
+
grouped.get(ns).push(cmd);
|
|
1047
|
+
}
|
|
1048
|
+
for (const [namespace, cmds] of grouped) {
|
|
1049
|
+
console.log(chalk_1.default.bold(`\n${namespace}`));
|
|
1050
|
+
for (const cmd of cmds) {
|
|
1051
|
+
const params = cmd.frontmatter['argument-hint'] ? ` ${cmd.frontmatter['argument-hint']}` : '';
|
|
1052
|
+
console.log(` /${cmd.name}${params}`);
|
|
1053
|
+
console.log(` ${cmd.description.replace(/\s*\([^)]+\)/, '')}`);
|
|
1054
|
+
if (cmd.frontmatter['allowed-tools'] && cmd.frontmatter['allowed-tools'].length > 0) {
|
|
1055
|
+
console.log(chalk_1.default.dim(` Allowed tools: ${cmd.frontmatter['allowed-tools'].join(', ')}`));
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
console.log('');
|
|
1060
|
+
}
|
|
1061
|
+
async handleCommandsCreate(name) {
|
|
1062
|
+
const { CommandCreator } = await Promise.resolve().then(() => __importStar(require('../commands/CommandCreator')));
|
|
1063
|
+
const creator = new CommandCreator(this.commandManager);
|
|
1064
|
+
await creator.run(name);
|
|
1065
|
+
// Re-discover commands after creation
|
|
1066
|
+
await this.commandManager.discoverCommands();
|
|
1067
|
+
}
|
|
1068
|
+
async handleCommandsValidate() {
|
|
1069
|
+
const { validateCommands } = await Promise.resolve().then(() => __importStar(require('../commands/CommandCreator')));
|
|
1070
|
+
await validateCommands(this.commandManager);
|
|
1071
|
+
}
|
|
969
1072
|
estimateCost(input, output) {
|
|
970
1073
|
const config = this.configManager.getConfig();
|
|
971
1074
|
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.0
|
|
22
|
+
console.log(chalk_1.default.gray(' v1.1.0 - AI Coding Agent'));
|
|
23
23
|
console.log('');
|
|
24
24
|
}
|
|
25
25
|
static renderDashboard(config) {
|
|
26
26
|
const { model, cwd } = config;
|
|
27
|
-
const version = 'v1.0
|
|
27
|
+
const version = 'v1.1.0';
|
|
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,98 @@
|
|
|
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) {
|
|
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
|
+
const { shouldCompact } = await inquirer_1.default.prompt([
|
|
74
|
+
{
|
|
75
|
+
type: 'confirm',
|
|
76
|
+
name: 'shouldCompact',
|
|
77
|
+
message: 'Compact conversation now?',
|
|
78
|
+
default: true
|
|
79
|
+
}
|
|
80
|
+
]);
|
|
81
|
+
if (!shouldCompact) {
|
|
82
|
+
return history;
|
|
83
|
+
}
|
|
84
|
+
const { focusTopic } = await inquirer_1.default.prompt([
|
|
85
|
+
{
|
|
86
|
+
type: 'input',
|
|
87
|
+
name: 'focusTopic',
|
|
88
|
+
message: 'Focus on specific topic? (leave empty for general)',
|
|
89
|
+
default: ''
|
|
90
|
+
}
|
|
91
|
+
]);
|
|
92
|
+
return await this.compact(history, modelClient, {
|
|
93
|
+
keepSystemMessages: true,
|
|
94
|
+
focusTopic: focusTopic || undefined
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
exports.ConversationCompacter = ConversationCompacter;
|