@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.
@@ -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
- if (process.argv.includes('update')) {
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
- const repl = new ReplManager_1.ReplManager();
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) => {
@@ -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;
@@ -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.5 - AI Coding Agent'));
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.0.8';
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;