recoder-code 2.1.4 → 2.2.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.
@@ -87,8 +87,10 @@ class InteractiveRecoderChat {
87
87
  // Initialize plugin manager
88
88
  this.pluginManager = new PluginManager();
89
89
 
90
- // Initialize voice manager
90
+ // Initialize voice manager (silently disabled)
91
91
  this.voiceManager = new VoiceManager();
92
+ // Silently disable TTS without console output
93
+ this.voiceManager.ttsEnabled = false;
92
94
 
93
95
  // Initialize collaboration manager
94
96
  this.collaborationManager = new CollaborationManager();
@@ -115,6 +117,14 @@ class InteractiveRecoderChat {
115
117
  this.visionAnalyzer = null;
116
118
  this.nlProcessor = null;
117
119
 
120
+ // Enhanced conversation tracking
121
+ this.conversationHistory = [];
122
+ this.currentSession = {
123
+ startTime: Date.now(),
124
+ messageCount: 0,
125
+ lastActivity: Date.now()
126
+ };
127
+
118
128
  this.initializeAdvancedSystems().catch(error => {
119
129
  console.log(chalk.yellow(`⚠️ Advanced features initialization failed: ${error.message}`));
120
130
  });
@@ -122,46 +132,15 @@ class InteractiveRecoderChat {
122
132
  // Initialize project manager for persistence
123
133
  this.projectManager = new ProjectManager();
124
134
 
135
+ // Always initialize readline interface first to prevent errors
136
+ this.initializeReadlineInterface();
137
+
125
138
  if (!this.apiKey) {
126
- console.log(chalk.red('❌ Error: OPENROUTER_API_KEY environment variable is required'));
127
- console.log(chalk.yellow('Please set your OpenRouter API key:'));
128
- console.log(chalk.gray('export OPENROUTER_API_KEY=your_api_key_here'));
129
- process.exit(1);
139
+ this.promptForApiKey();
140
+ return;
130
141
  }
131
142
 
132
- // Initialize OpenAI client with OpenRouter configuration
133
- this.openai = new OpenAI({
134
- baseURL: config.baseURL,
135
- apiKey: this.apiKey,
136
- defaultHeaders: {
137
- 'HTTP-Referer': config.siteUrl,
138
- 'X-Title': config.siteName,
139
- },
140
- });
141
-
142
- // Initialize readline interface with enhanced options
143
- this.rl = readline.createInterface({
144
- input: process.stdin,
145
- output: process.stdout,
146
- prompt: chalk.cyan('🤖 You: '),
147
- historySize: 100,
148
- removeHistoryDuplicates: true,
149
- completer: this.autoComplete.bind(this)
150
- });
151
-
152
- // Set up custom prompt and keyboard shortcuts
153
- this.updatePrompt();
154
- this.setupKeyboardShortcuts();
155
-
156
- this.setupEventHandlers();
157
- this.initializeFileWatcher();
158
- this.analyzeProjectContext().catch(error => {
159
- console.log(chalk.yellow(`⚠️ Project context analysis failed: ${error.message}`));
160
- });
161
- this.loadPlugins().catch(error => {
162
- console.log(chalk.yellow(`⚠️ Plugin loading failed: ${error.message}`));
163
- });
164
- this.displayWelcome();
143
+ this.initializeWithApiKey();
165
144
  }
166
145
 
167
146
  /**
@@ -169,7 +148,7 @@ class InteractiveRecoderChat {
169
148
  */
170
149
  async initializeAdvancedSystems() {
171
150
  try {
172
- console.log(chalk.gray('🚀 Initializing advanced features...'));
151
+ // Silently initialize advanced features (no console.log for clean startup)
173
152
 
174
153
  // Initialize context engine for codebase understanding
175
154
  this.contextEngine = new ContextEngine();
@@ -177,7 +156,6 @@ class InteractiveRecoderChat {
177
156
 
178
157
  // Initialize MCP client for model communication
179
158
  this.mcpClient = new MCPClient();
180
- await this.mcpClient.initialize();
181
159
 
182
160
  // Initialize model manager with MCP and context engine
183
161
  this.modelManager = new ModelManager(this.mcpClient, this.contextEngine);
@@ -212,11 +190,7 @@ class InteractiveRecoderChat {
212
190
  // Enable auto model switching based on context
213
191
  this.enableIntelligentModelSwitching();
214
192
 
215
- console.log(chalk.green('✅ Advanced features initialized and integrated'));
216
- console.log(chalk.gray(` 🤖 Model Manager: ${this.modelManager.models.size} models available`));
217
- console.log(chalk.gray(` 📋 Rules Engine: Auto-discovered project rules`));
218
- console.log(chalk.gray(` 🎯 Task Manager: Automation triggers active`));
219
- console.log(chalk.gray(` 🔧 Dev Tools: Native integrations enabled`));
193
+ // Advanced features initialized silently
220
194
 
221
195
  } catch (error) {
222
196
  console.log(chalk.yellow(`⚠️ Some advanced features may be limited: ${error.message}`));
@@ -403,9 +377,9 @@ class InteractiveRecoderChat {
403
377
  const convIndicator = this.currentConversationId === 'main' ? '' : chalk.gray(`[${currentConv.name}] `);
404
378
 
405
379
  if (this.multiLineMode) {
406
- this.rl.setPrompt(chalk.yellow('📝 ... '));
380
+ this.rl.setPrompt(chalk.gray('... '));
407
381
  } else {
408
- this.rl.setPrompt(convIndicator + chalk.cyan('🤖 You: '));
382
+ this.rl.setPrompt(convIndicator + chalk.gray('> '));
409
383
  }
410
384
  }
411
385
 
@@ -793,6 +767,11 @@ class InteractiveRecoderChat {
793
767
  case 14: // Ctrl+N (New/Clear conversation)
794
768
  this.handleCtrlN();
795
769
  break;
770
+
771
+ case 27: // ESC (Exit)
772
+ console.log(chalk.yellow('\n👋 Goodbye!'));
773
+ process.exit(0);
774
+ break;
796
775
  }
797
776
  }
798
777
  }
@@ -978,63 +957,589 @@ class InteractiveRecoderChat {
978
957
  });
979
958
  }
980
959
 
960
+ promptForApiKey() {
961
+ console.clear();
962
+ console.log('');
963
+ console.log(chalk.magenta('╭─────────────────────────────────────────────────────────────╮'));
964
+ console.log(chalk.magenta('│') + chalk.white.bold(' ✨ Welcome to Recoder Code ') + chalk.magenta('│'));
965
+ console.log(chalk.magenta('╰─────────────────────────────────────────────────────────────╯'));
966
+ console.log('');
967
+ console.log(chalk.white('🔑 API key required to continue'));
968
+ console.log(chalk.gray('Get your free API key at: ') + chalk.magenta('https://openrouter.ai'));
969
+ console.log('');
970
+
971
+ const keyPrompt = readline.createInterface({
972
+ input: process.stdin,
973
+ output: process.stdout
974
+ });
975
+
976
+ keyPrompt.question(chalk.magenta('Enter your OpenRouter API key: '), (apiKey) => {
977
+ keyPrompt.close();
978
+
979
+ if (!apiKey || apiKey.trim() === '') {
980
+ console.log(chalk.red('No API key provided. Exiting...'));
981
+ process.exit(1);
982
+ }
983
+
984
+ // Save the API key to environment
985
+ process.env.OPENROUTER_API_KEY = apiKey.trim();
986
+ this.apiKey = apiKey.trim();
987
+
988
+ // Continue initialization
989
+ this.initializeWithApiKey();
990
+ });
991
+ }
992
+
993
+ initializeReadlineInterface() {
994
+ // Initialize readline interface with enhanced options
995
+ this.rl = readline.createInterface({
996
+ input: process.stdin,
997
+ output: process.stdout,
998
+ prompt: chalk.gray('> '),
999
+ historySize: 100,
1000
+ removeHistoryDuplicates: true,
1001
+ completer: (line) => this.autoComplete(line)
1002
+ });
1003
+
1004
+ // Set up readline event handlers
1005
+ this.setupReadlineHandlers();
1006
+ }
1007
+
1008
+ setupReadlineHandlers() {
1009
+ if (!this.rl) return;
1010
+
1011
+ this.rl.on('line', async (input) => {
1012
+ await this.handleUserInput(input.trim());
1013
+ });
1014
+
1015
+ this.rl.on('close', () => {
1016
+ console.log('\nGoodbye!');
1017
+ process.exit(0);
1018
+ });
1019
+
1020
+ // Handle advanced keyboard shortcuts
1021
+ process.stdin.on('keypress', (str, key) => {
1022
+ if (key && key.ctrl) {
1023
+ this.handleCtrlKeys(key);
1024
+ }
1025
+ });
1026
+ }
1027
+
1028
+ initializeWithApiKey() {
1029
+ // Initialize OpenAI client with OpenRouter configuration
1030
+ this.openai = new OpenAI({
1031
+ baseURL: config.baseURL,
1032
+ apiKey: this.apiKey,
1033
+ defaultHeaders: {
1034
+ 'HTTP-Referer': config.siteUrl,
1035
+ 'X-Title': config.siteName,
1036
+ },
1037
+ });
1038
+
1039
+ // If readline interface wasn't already initialized, do it now
1040
+ if (!this.rl) {
1041
+ this.initializeReadlineInterface();
1042
+ }
1043
+
1044
+ // Set up custom prompt and keyboard shortcuts
1045
+ this.updatePrompt();
1046
+ this.setupKeyboardShortcuts();
1047
+
1048
+ this.setupEventHandlers();
1049
+ this.initializeFileWatcher();
1050
+ this.analyzeProjectContext().catch(error => {
1051
+ console.log(chalk.yellow(`⚠️ Project context analysis failed: ${error.message}`));
1052
+ });
1053
+ this.loadPlugins().catch(error => {
1054
+ console.log(chalk.yellow(`⚠️ Plugin loading failed: ${error.message}`));
1055
+ });
1056
+ this.displayWelcome();
1057
+ }
1058
+
1059
+ updatePrompt() {
1060
+ // Neon purple prompt with white text
1061
+ if (this.rl) {
1062
+ this.rl.setPrompt(chalk.magenta('> '));
1063
+ }
1064
+ }
1065
+
1066
+ setupEventHandlers() {
1067
+ // Set up readline event handlers for user input
1068
+ this.rl.on('line', async (input) => {
1069
+ await this.handleUserInput(input.trim());
1070
+ });
1071
+
1072
+ this.rl.on('close', () => {
1073
+ console.log('\nGoodbye!');
1074
+ process.exit(0);
1075
+ });
1076
+
1077
+ // Handle advanced keyboard shortcuts
1078
+ process.stdin.on('keypress', (str, key) => {
1079
+ if (key) {
1080
+ this.handleKeyboardShortcuts(key);
1081
+ }
1082
+ });
1083
+
1084
+ // Start the prompt
1085
+ this.rl.prompt();
1086
+ }
1087
+
1088
+ async handleUserInput(input) {
1089
+ if (!input) {
1090
+ this.rl.prompt();
1091
+ return;
1092
+ }
1093
+
1094
+ // Handle slash commands like Claude Code
1095
+ if (input.startsWith('/')) {
1096
+ this.handleSlashCommand(input);
1097
+ return;
1098
+ }
1099
+
1100
+ try {
1101
+ // Track performance metrics
1102
+ const startTime = Date.now();
1103
+ let firstChunkTime = null;
1104
+ let totalChunks = 0;
1105
+
1106
+ // Start progressive loading with enhanced animations
1107
+ this.startProgressiveLoading();
1108
+
1109
+ // Create a RecoderCode instance to process the conversation
1110
+ const { RecoderCode } = require('./run.js');
1111
+ const recoder = new RecoderCode();
1112
+
1113
+ // Initialize streaming response system
1114
+ const streamBuffer = {
1115
+ content: '',
1116
+ chunks: [],
1117
+ isStreaming: false,
1118
+ streamingStarted: false
1119
+ };
1120
+
1121
+ // Enhanced streaming response handler
1122
+ const response = await this.processWithAdvancedStreaming({
1123
+ recoder,
1124
+ input,
1125
+ startTime,
1126
+ streamBuffer,
1127
+ onFirstChunk: (time) => { firstChunkTime = time; },
1128
+ onChunk: (chunk) => {
1129
+ totalChunks++;
1130
+ this.displayStreamingChunk(chunk, totalChunks);
1131
+ }
1132
+ });
1133
+
1134
+ // Calculate comprehensive metrics
1135
+ const totalDuration = Math.round((Date.now() - startTime) / 1000);
1136
+ const timeToFirstChunk = firstChunkTime ? Math.round((firstChunkTime - startTime) / 1000) : null;
1137
+
1138
+ // Track conversation history with enhanced metadata
1139
+ this.conversationHistory.push({
1140
+ timestamp: new Date(),
1141
+ input: input,
1142
+ response: response,
1143
+ duration: totalDuration,
1144
+ timeToFirstChunk,
1145
+ totalChunks,
1146
+ streamingUsed: streamBuffer.isStreaming
1147
+ });
1148
+ this.currentSession.messageCount++;
1149
+ this.currentSession.lastActivity = Date.now();
1150
+
1151
+ console.log('');
1152
+
1153
+ // Show performance metrics and tool usage
1154
+ this.showAdvancedMetrics({
1155
+ duration: totalDuration,
1156
+ timeToFirstChunk,
1157
+ totalChunks,
1158
+ streamingUsed: streamBuffer.isStreaming
1159
+ });
1160
+
1161
+ // Enhanced response display if not already streamed
1162
+ if (!streamBuffer.streamingStarted) {
1163
+ await this.displayAdvancedTypingResponse(response, startTime);
1164
+ } else {
1165
+ // Finalize streaming display
1166
+ this.finalizeStreamingResponse(response, totalDuration);
1167
+ }
1168
+
1169
+ console.log('');
1170
+
1171
+ } catch (error) {
1172
+ this.stopProgressiveLoading();
1173
+ this.stopStreamingDisplay();
1174
+ console.log('');
1175
+ this.showEnhancedError(error);
1176
+ console.log('');
1177
+ }
1178
+
1179
+ this.rl.prompt();
1180
+ }
1181
+
981
1182
  displayWelcome() {
982
1183
  console.clear();
983
1184
 
984
- // Simple welcome message like Claude Code
985
- console.log('');
986
- console.log(chalk.bold('✱ Welcome to Recoder-Code!'));
1185
+ // Beautiful welcome screen with neon purple/white/gray theme
987
1186
  console.log('');
988
- console.log(chalk.gray('/help for help, /status for your current setup'));
1187
+ console.log(chalk.magenta('╭─────────────────────────────────────────────────────────────╮'));
1188
+ console.log(chalk.magenta('│') + chalk.white.bold(' ✨ Welcome to Recoder Code ') + chalk.magenta('│'));
1189
+ console.log(chalk.magenta('╰─────────────────────────────────────────────────────────────╯'));
989
1190
  console.log('');
990
- console.log(chalk.gray(`cwd: ${process.cwd()}`));
1191
+
1192
+ // Stylized ASCII art in neon purple
1193
+ console.log(chalk.magenta.bold('██████╗ ███████╗ ██████╗ ██████╗ ██████╗ ███████╗██████╗'));
1194
+ console.log(chalk.magenta.bold('██╔══██╗██╔════╝██╔════╝██╔═══██╗██╔══██╗██╔════╝██╔══██╗'));
1195
+ console.log(chalk.magenta.bold('██████╔╝█████╗ ██║ ██║ ██║██║ ██║█████╗ ██████╔╝'));
1196
+ console.log(chalk.white('██╔══██╗██╔══╝ ██║ ██║ ██║██║ ██║██╔══╝ ██╔══██╗'));
1197
+ console.log(chalk.white('██║ ██║███████╗╚██████╗╚██████╔╝██████╔╝███████╗██║ ██║'));
1198
+ console.log(chalk.gray('╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚═════╝ ╚═════╝ ╚══════╝╚═╝ ╚═╝'));
991
1199
  console.log('');
1200
+
1201
+ // Status line
1202
+ console.log(chalk.gray('Press Enter to continue'));
992
1203
  console.log('');
993
- // Show session suggestions if available
994
- this.showSessionSuggestions();
995
1204
 
996
- console.log(chalk.green('💬 Start chatting! Just type what you want to do.'));
997
- console.log(chalk.white('✨ Examples: build a todo app, fix my login error, deploy this project'));
1205
+ // Add bottom status hints like Claude Code
1206
+ this.showStatusHints();
1207
+
1208
+ this.rl.prompt();
1209
+ }
1210
+
1211
+ showError(error) {
1212
+ // Beautiful error display like Claude Code
1213
+ console.log(chalk.red('╭─────────────────────────────────────────╮'));
1214
+ console.log(chalk.red('│') + chalk.white.bold(' ❌ Error ') + chalk.red('│'));
1215
+ console.log(chalk.red('╰─────────────────────────────────────────╯'));
1216
+ console.log('');
1217
+ console.log(chalk.white(error.message));
998
1218
  console.log('');
999
1219
 
1220
+ // Show helpful suggestions based on error type
1221
+ if (error.message.includes('429')) {
1222
+ console.log(chalk.gray('💡 Rate limit reached. Try again in a few seconds.'));
1223
+ } else if (error.message.includes('API key')) {
1224
+ console.log(chalk.gray('💡 Check your API key with /status command.'));
1225
+ } else if (error.message.includes('timeout')) {
1226
+ console.log(chalk.gray('💡 Request timed out. Try a simpler query.'));
1227
+ } else {
1228
+ console.log(chalk.gray('💡 Type /help for available commands.'));
1229
+ }
1230
+ }
1231
+
1232
+ showStatusHints() {
1233
+ // Show helpful hints at bottom like Claude Code with project context
1234
+ const projectName = path.basename(process.cwd());
1235
+ console.log(chalk.gray(`! for bash mode • / for commands • tab to autocomplete ESC to exit`));
1236
+ console.log(chalk.gray(`📁 ${projectName} • ✨ Recoder Code ready • Ctrl+S save • Ctrl+H help`));
1237
+ }
1238
+
1239
+ startProcessingAnimation() {
1240
+ const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
1241
+ let frameIndex = 0;
1242
+
1243
+ this.processingInterval = setInterval(() => {
1244
+ const frame = frames[frameIndex % frames.length];
1245
+ process.stdout.write(`\r${chalk.magenta(frame)} ${chalk.gray('Processing...')}`);
1246
+ frameIndex++;
1247
+ }, 80);
1248
+ }
1249
+
1250
+ stopProcessingAnimation() {
1251
+ if (this.processingInterval) {
1252
+ clearInterval(this.processingInterval);
1253
+ this.processingInterval = null;
1254
+ process.stdout.write('\r'); // Clear the line
1255
+ }
1256
+ }
1257
+
1258
+ showToolUsage() {
1259
+ // Show tool usage indicators like Claude Code
1260
+ const tools = ['Read', 'Analyze', 'Generate'];
1261
+ tools.forEach(tool => {
1262
+ console.log(chalk.gray('● ') + chalk.white(`${tool}(project_structure)...`));
1263
+ });
1264
+ }
1265
+
1266
+ formatResponse(response) {
1267
+ // Format response with rich styling like Claude Code
1268
+ if (!response) return '';
1269
+
1270
+ // Add bullet points and styling
1271
+ const formatted = response
1272
+ .split('\n')
1273
+ .map(line => {
1274
+ if (line.trim().startsWith('-') || line.trim().startsWith('*')) {
1275
+ return chalk.gray('● ') + chalk.white(line.replace(/^[\s\-\*]+/, ''));
1276
+ }
1277
+ return chalk.white(line);
1278
+ })
1279
+ .join('\n');
1280
+
1281
+ return formatted;
1282
+ }
1283
+
1284
+ async typeResponse(text) {
1285
+ // Type out response character by character like Claude Code
1286
+ const lines = text.split('\n');
1287
+ for (const line of lines) {
1288
+ for (let i = 0; i < line.length; i++) {
1289
+ process.stdout.write(line[i]);
1290
+ await new Promise(resolve => setTimeout(resolve, 15)); // 15ms per character
1291
+ }
1292
+ console.log(''); // New line after each line
1293
+ }
1294
+ }
1295
+
1296
+ handleSlashCommand(input) {
1297
+ const command = input.slice(1).toLowerCase();
1298
+
1299
+ switch (command) {
1300
+ case 'help':
1301
+ this.showHelp();
1302
+ break;
1303
+ case 'clear':
1304
+ console.clear();
1305
+ this.displayWelcome();
1306
+ return; // Don't show prompt again
1307
+ case 'exit':
1308
+ case 'quit':
1309
+ console.log(chalk.magenta('\nGoodbye! ✨'));
1310
+ process.exit(0);
1311
+ break;
1312
+ case 'status':
1313
+ this.showStatus();
1314
+ break;
1315
+ case 'history':
1316
+ this.showHistory();
1317
+ break;
1318
+ case 'session':
1319
+ this.showSession();
1320
+ break;
1321
+ case 'watch':
1322
+ this.showWatchedFiles();
1323
+ break;
1324
+ case 'save':
1325
+ this.saveCurrentSession();
1326
+ break;
1327
+ default:
1328
+ console.log(chalk.red(`Unknown command: ${input}`));
1329
+ console.log(chalk.gray('Type /help for available commands'));
1330
+ }
1331
+
1000
1332
  this.rl.prompt();
1001
1333
  }
1002
1334
 
1335
+ showHelp() {
1336
+ console.log('');
1337
+ console.log(chalk.magenta('╭─────────────────────────────────────────╮'));
1338
+ console.log(chalk.magenta('│') + chalk.white.bold(' Available Commands ') + chalk.magenta('│'));
1339
+ console.log(chalk.magenta('╰─────────────────────────────────────────╯'));
1340
+ console.log('');
1341
+ console.log(chalk.gray('/help ') + chalk.white('Show this help message'));
1342
+ console.log(chalk.gray('/clear ') + chalk.white('Clear the screen'));
1343
+ console.log(chalk.gray('/status ') + chalk.white('Show system status'));
1344
+ console.log(chalk.gray('/history ') + chalk.white('Show conversation history'));
1345
+ console.log(chalk.gray('/session ') + chalk.white('Show current session info'));
1346
+ console.log(chalk.gray('/watch ') + chalk.white('Show watched files'));
1347
+ console.log(chalk.gray('/save ') + chalk.white('Save current session'));
1348
+ console.log(chalk.gray('/exit ') + chalk.white('Exit Recoder Code'));
1349
+ console.log('');
1350
+ }
1351
+
1352
+ showStatus() {
1353
+ console.log('');
1354
+ console.log(chalk.magenta('╭─────────────────────────────────────────╮'));
1355
+ console.log(chalk.magenta('│') + chalk.white.bold(' System Status ') + chalk.magenta('│'));
1356
+ console.log(chalk.magenta('╰─────────────────────────────────────────╯'));
1357
+ console.log('');
1358
+ console.log(chalk.gray('Model: ') + chalk.white(this.model || 'Default'));
1359
+ console.log(chalk.gray('Session: ') + chalk.white(this.sessionId.slice(0, 8) + '...'));
1360
+ console.log(chalk.gray('Directory:') + chalk.white(process.cwd().replace(process.env.HOME, '~')));
1361
+ console.log('');
1362
+ }
1363
+
1364
+ showHistory() {
1365
+ console.log('');
1366
+ console.log(chalk.magenta('╭─────────────────────────────────────────╮'));
1367
+ console.log(chalk.magenta('│') + chalk.white.bold(' Conversation History ') + chalk.magenta('│'));
1368
+ console.log(chalk.magenta('╰─────────────────────────────────────────╯'));
1369
+ console.log('');
1370
+
1371
+ if (this.conversationHistory.length === 0) {
1372
+ console.log(chalk.gray('No conversation history yet.'));
1373
+ } else {
1374
+ const recent = this.conversationHistory.slice(-5); // Show last 5 exchanges
1375
+ recent.forEach((entry, index) => {
1376
+ const time = entry.timestamp.toLocaleTimeString();
1377
+ console.log(chalk.gray(`[${time}] `) + chalk.white(entry.input.slice(0, 50) + '...'));
1378
+ console.log(chalk.gray(` → Response in ${entry.duration}s`));
1379
+ if (index < recent.length - 1) console.log('');
1380
+ });
1381
+ }
1382
+ console.log('');
1383
+ }
1384
+
1385
+ showSession() {
1386
+ console.log('');
1387
+ console.log(chalk.magenta('╭─────────────────────────────────────────╮'));
1388
+ console.log(chalk.magenta('│') + chalk.white.bold(' Current Session ') + chalk.magenta('│'));
1389
+ console.log(chalk.magenta('╰─────────────────────────────────────────╯'));
1390
+ console.log('');
1391
+
1392
+ const sessionDuration = Math.round((Date.now() - this.currentSession.startTime) / 1000 / 60);
1393
+ const lastActivity = Math.round((Date.now() - this.currentSession.lastActivity) / 1000);
1394
+
1395
+ console.log(chalk.gray('Session ID: ') + chalk.white(this.sessionId.slice(0, 8) + '...'));
1396
+ console.log(chalk.gray('Duration: ') + chalk.white(`${sessionDuration} minutes`));
1397
+ console.log(chalk.gray('Messages: ') + chalk.white(this.currentSession.messageCount));
1398
+ console.log(chalk.gray('Last Activity: ') + chalk.white(`${lastActivity}s ago`));
1399
+ console.log(chalk.gray('Model: ') + chalk.white(this.model || 'Default'));
1400
+ console.log('');
1401
+ }
1402
+
1403
+ smartAutoComplete(line) {
1404
+ // Smart autocomplete for commands and context-aware suggestions
1405
+ const completions = [];
1406
+
1407
+ if (line.startsWith('/')) {
1408
+ // Slash command completions
1409
+ const commands = ['help', 'clear', 'status', 'history', 'session', 'exit'];
1410
+ const matches = commands.filter(cmd => cmd.startsWith(line.slice(1)));
1411
+ completions.push(...matches.map(cmd => '/' + cmd));
1412
+ } else if (line.startsWith('!')) {
1413
+ // Bash command completions
1414
+ const bashCommands = ['ls', 'cd', 'pwd', 'cat', 'grep', 'find', 'git', 'npm', 'node'];
1415
+ const matches = bashCommands.filter(cmd => cmd.startsWith(line.slice(1)));
1416
+ completions.push(...matches.map(cmd => '!' + cmd));
1417
+ } else {
1418
+ // Context-aware suggestions based on project
1419
+ const suggestions = [
1420
+ 'explain this project structure',
1421
+ 'help me debug this code',
1422
+ 'create a README file',
1423
+ 'optimize this function',
1424
+ 'add error handling',
1425
+ 'write unit tests',
1426
+ 'refactor this code'
1427
+ ];
1428
+ const matches = suggestions.filter(s => s.toLowerCase().includes(line.toLowerCase()));
1429
+ completions.push(...matches);
1430
+ }
1431
+
1432
+ return [completions, line];
1433
+ }
1434
+
1435
+ showWatchedFiles() {
1436
+ console.log('');
1437
+ console.log(chalk.magenta('╭─────────────────────────────────────────╮'));
1438
+ console.log(chalk.magenta('│') + chalk.white.bold(' Watched Files ') + chalk.magenta('│'));
1439
+ console.log(chalk.magenta('╰─────────────────────────────────────────╯'));
1440
+ console.log('');
1441
+
1442
+ if (this.watchedFiles && this.watchedFiles.size > 0) {
1443
+ this.watchedFiles.forEach(file => {
1444
+ console.log(chalk.gray('📁 ') + chalk.white(file));
1445
+ });
1446
+ } else {
1447
+ console.log(chalk.gray('No files being watched.'));
1448
+ console.log(chalk.gray('Files will be automatically watched during conversations.'));
1449
+ }
1450
+ console.log('');
1451
+ }
1452
+
1453
+ saveCurrentSession() {
1454
+ console.log('');
1455
+ console.log(chalk.magenta('╭─────────────────────────────────────────╮'));
1456
+ console.log(chalk.magenta('│') + chalk.white.bold(' Session Saved ') + chalk.magenta('│'));
1457
+ console.log(chalk.magenta('╰─────────────────────────────────────────╯'));
1458
+ console.log('');
1459
+
1460
+ const sessionData = {
1461
+ id: this.sessionId,
1462
+ startTime: this.currentSession.startTime,
1463
+ history: this.conversationHistory,
1464
+ messageCount: this.currentSession.messageCount,
1465
+ lastActivity: this.currentSession.lastActivity,
1466
+ savedAt: Date.now()
1467
+ };
1468
+
1469
+ try {
1470
+ const sessionsDir = path.join(process.cwd(), 'sessions');
1471
+ if (!fs.existsSync(sessionsDir)) {
1472
+ fs.mkdirSync(sessionsDir, { recursive: true });
1473
+ }
1474
+
1475
+ const filename = `session-${this.sessionId}.json`;
1476
+ const filepath = path.join(sessionsDir, filename);
1477
+ fs.writeFileSync(filepath, JSON.stringify(sessionData, null, 2));
1478
+
1479
+ console.log(chalk.white(`Session saved to: ${filename}`));
1480
+ console.log(chalk.gray(`${this.conversationHistory.length} messages preserved`));
1481
+ } catch (error) {
1482
+ console.log(chalk.red(`Failed to save session: ${error.message}`));
1483
+ }
1484
+ console.log('');
1485
+ }
1486
+
1487
+ handleKeyboardShortcuts(key) {
1488
+ // Advanced keyboard shortcuts like in modern IDEs
1489
+ if (key.name === 'escape') {
1490
+ console.log(chalk.magenta('\n✨ Goodbye!'));
1491
+ process.exit(0);
1492
+ } else if (key.ctrl && key.name === 'l') {
1493
+ // Ctrl+L: Clear and restart
1494
+ console.clear();
1495
+ this.displayWelcome();
1496
+ } else if (key.ctrl && key.name === 's') {
1497
+ // Ctrl+S: Quick save session
1498
+ this.rl.write('\r/save\r');
1499
+ } else if (key.ctrl && key.name === 'h') {
1500
+ // Ctrl+H: Show help
1501
+ this.rl.write('\r/help\r');
1502
+ } else if (key.ctrl && key.name === 'u') {
1503
+ // Ctrl+U: Show status
1504
+ this.rl.write('\r/status\r');
1505
+ }
1506
+ }
1507
+
1003
1508
  showSessionSuggestions() {
1004
1509
  try {
1005
1510
  // Check if project exists
1006
1511
  const projectStatus = this.projectManager.getProjectStatus();
1007
1512
 
1008
1513
  if (projectStatus.exists) {
1009
- console.log(chalk.cyan(`📂 Project: ${projectStatus.context.name}`));
1010
- console.log(chalk.gray(` Todos: ${projectStatus.todos.pending} pending, ${projectStatus.todos.completed} completed`));
1514
+ console.log(chalk.magenta('📂 ') + chalk.white.bold(`Project: ${projectStatus.context.name}`));
1515
+ console.log(chalk.gray(` Todos: ${projectStatus.todos.pending} pending, ${projectStatus.todos.completed} completed`));
1011
1516
  }
1012
1517
 
1013
1518
  // Check for intelligent auto-resume candidate
1014
1519
  const resumeCandidate = this.projectManager.shouldOfferAutoResume();
1015
1520
 
1016
1521
  if (resumeCandidate) {
1017
- console.log(chalk.yellow('🔄 Resume Previous Session?'));
1018
- console.log(chalk.cyan(` Session: ${resumeCandidate.id} (${resumeCandidate.activityDescription})`));
1019
- console.log(chalk.gray(` Messages: ${resumeCandidate.messageCount} | Model: ${resumeCandidate.model}`));
1522
+ console.log(chalk.magenta('🔄 ') + chalk.white.bold('Resume Previous Session?'));
1523
+ console.log(chalk.gray(` Session: ${resumeCandidate.id} (${resumeCandidate.activityDescription})`));
1524
+ console.log(chalk.gray(` Messages: ${resumeCandidate.messageCount} | Model: ${resumeCandidate.model}`));
1020
1525
 
1021
1526
  if (resumeCandidate.lastUserMessage) {
1022
- console.log(chalk.gray(` Last: "${resumeCandidate.lastUserMessage}"`));
1527
+ console.log(chalk.gray(` Last: "${resumeCandidate.lastUserMessage}"`));
1023
1528
  }
1024
1529
 
1025
- console.log(chalk.gray(' 💡 Type /resume to continue or start fresh'));
1530
+ console.log(chalk.gray(' 💡 Type ') + chalk.cyan('/resume') + chalk.gray(' to continue or start fresh'));
1026
1531
  console.log('');
1027
1532
  } else {
1028
1533
  // Get recent sessions if no auto-resume candidate
1029
1534
  const recentSessions = this.projectManager.listSessions({ limit: 3 });
1030
1535
 
1031
1536
  if (recentSessions.length > 0) {
1032
- console.log(chalk.yellow('🕒 Recent Sessions:'));
1537
+ console.log(chalk.magenta('🕒 ') + chalk.white.bold('Recent Sessions:'));
1033
1538
  recentSessions.forEach((session, index) => {
1034
1539
  const timeAgo = this.getTimeAgo(new Date(session.modified));
1035
- console.log(chalk.gray(` ${index + 1}. ${session.id} - ${timeAgo} (${session.messageCount} messages)`));
1540
+ console.log(chalk.gray(` ${session.id} - ${timeAgo} (${session.messageCount} messages)`));
1036
1541
  });
1037
- console.log(chalk.gray(' 💡 Use /resume <id> to continue a session'));
1542
+ console.log(chalk.gray(' 💡 Use ') + chalk.cyan('/resume <id>') + chalk.gray(' to continue a session'));
1038
1543
  console.log('');
1039
1544
  }
1040
1545
  }
@@ -2905,7 +3410,7 @@ class InteractiveRecoderChat {
2905
3410
  // Update legacy reference
2906
3411
  this.conversationHistory = currentConv.history;
2907
3412
 
2908
- console.log(chalk.blue('\n🤖 Recoder: '), { end: '' });
3413
+ console.log(chalk.magenta('\n🤖 ') + chalk.white.bold('Recoder: '), { end: '' });
2909
3414
 
2910
3415
  try {
2911
3416
  // Start API call timer
@@ -2925,19 +3430,22 @@ class InteractiveRecoderChat {
2925
3430
 
2926
3431
  console.log(chalk.white(assistantMessage));
2927
3432
 
2928
- // Optional voice output for AI responses
2929
- if (this.voiceManager.textToSpeechEnabled) {
2930
- // Speak the response (in background, don't wait)
2931
- this.voiceManager.speakText(assistantMessage).catch(err => {
2932
- // Ignore TTS errors to not interrupt the conversation
2933
- });
3433
+ // Only speak if user specifically requested it
3434
+ if (this.shouldSpeak || userInput.toLowerCase().includes('speak') || userInput.toLowerCase().includes('read loud')) {
3435
+ if (this.voiceManager.textToSpeechEnabled) {
3436
+ console.log(chalk.gray('🔊 Speaking response...'));
3437
+ this.voiceManager.speakText(assistantMessage).catch(err => {
3438
+ // Ignore TTS errors to not interrupt the conversation
3439
+ });
3440
+ }
3441
+ this.shouldSpeak = false; // Reset flag
2934
3442
  }
2935
3443
 
2936
3444
  // Update conversation-specific stats
2937
3445
  if (response.usage) {
2938
3446
  currentConv.tokens += response.usage.total_tokens || 0;
2939
3447
  this.totalTokens += response.usage.total_tokens || 0;
2940
- console.log(chalk.gray(`\n💰 ${response.usage.prompt_tokens} + ${response.usage.completion_tokens} = ${response.usage.total_tokens} tokens`));
3448
+ console.log(chalk.gray(`\n💎 ${response.usage.prompt_tokens} + ${response.usage.completion_tokens} = `) + chalk.magenta(`${response.usage.total_tokens} tokens`));
2941
3449
 
2942
3450
  // Log API call metrics
2943
3451
  const apiDuration = this.logger.endTimer('api_call');
@@ -3397,9 +3905,200 @@ ${projectInfo}`
3397
3905
  }
3398
3906
  }
3399
3907
 
3908
+ // Advanced streaming and progressive loading methods
3909
+ startProgressiveLoading() {
3910
+ if (this.progressiveLoader) {
3911
+ clearInterval(this.progressiveLoader.interval);
3912
+ }
3913
+
3914
+ const stages = [
3915
+ { text: 'Initializing...', color: 'cyan', icon: '⚡' },
3916
+ { text: 'Connecting to AI...', color: 'blue', icon: '🔗' },
3917
+ { text: 'Processing request...', color: 'magenta', icon: '🧠' },
3918
+ { text: 'Generating response...', color: 'yellow', icon: '✨' }
3919
+ ];
3920
+
3921
+ let currentStage = 0;
3922
+ let dots = 0;
3923
+
3924
+ this.progressiveLoader = {
3925
+ interval: setInterval(() => {
3926
+ const stage = stages[currentStage % stages.length];
3927
+ const dotString = '.'.repeat((dots % 3) + 1).padEnd(3);
3928
+
3929
+ process.stdout.write('\r' + ' '.repeat(50) + '\r');
3930
+ process.stdout.write(
3931
+ chalk[stage.color](stage.icon + ' ' + stage.text + dotString)
3932
+ );
3933
+
3934
+ dots++;
3935
+ if (dots % 6 === 0) {
3936
+ currentStage++;
3937
+ }
3938
+ }, 200),
3939
+ stage: 0
3940
+ };
3941
+ }
3942
+
3943
+ stopProgressiveLoading(loader = this.progressiveLoader) {
3944
+ if (loader && loader.interval) {
3945
+ clearInterval(loader.interval);
3946
+ process.stdout.write('\r' + ' '.repeat(50) + '\r');
3947
+ }
3948
+ this.progressiveLoader = null;
3949
+ }
3950
+
3951
+ async processWithAdvancedStreaming({ recoder, input, startTime, streamBuffer, onFirstChunk, onChunk }) {
3952
+ try {
3953
+ // Attempt streaming first
3954
+ const response = await recoder.processConversation(input, false, true, {
3955
+ onChunk: (chunk) => {
3956
+ if (!streamBuffer.streamingStarted) {
3957
+ this.stopProgressiveLoading();
3958
+ this.startStreamingDisplay();
3959
+ streamBuffer.streamingStarted = true;
3960
+ onFirstChunk(Date.now());
3961
+ }
3962
+
3963
+ streamBuffer.content += chunk;
3964
+ streamBuffer.chunks.push(chunk);
3965
+ streamBuffer.isStreaming = true;
3966
+ onChunk(chunk);
3967
+ }
3968
+ });
3969
+
3970
+ return response || streamBuffer.content;
3971
+
3972
+ } catch (error) {
3973
+ // Fallback to non-streaming
3974
+ this.stopProgressiveLoading();
3975
+ return await recoder.processConversation(input, false, false, null);
3976
+ }
3977
+ }
3978
+
3979
+ startStreamingDisplay() {
3980
+ if (!this.streamingActive) {
3981
+ console.log('\n' + chalk.cyan('🔥 Streaming response...'));
3982
+ this.streamingActive = true;
3983
+ this.streamCursor = 0;
3984
+ }
3985
+ }
3986
+
3987
+ displayStreamingChunk(chunk, chunkNumber) {
3988
+ if (!this.streamingActive) return;
3989
+
3990
+ // Add subtle chunk indicators for debugging
3991
+ if (process.env.RECODER_DEBUG === 'true') {
3992
+ process.stdout.write(chalk.gray(`[${chunkNumber}]`));
3993
+ }
3994
+
3995
+ // Display chunk with subtle animation
3996
+ const chars = chunk.split('');
3997
+ chars.forEach((char, index) => {
3998
+ setTimeout(() => {
3999
+ process.stdout.write(chalk.white(char));
4000
+ }, index * 2); // Very fast typing effect
4001
+ });
4002
+ }
4003
+
4004
+ stopStreamingDisplay() {
4005
+ this.streamingActive = false;
4006
+ this.streamCursor = 0;
4007
+ }
4008
+
4009
+ finalizeStreamingResponse(response, duration) {
4010
+ this.stopStreamingDisplay();
4011
+ console.log('\n');
4012
+ console.log(chalk.gray(`✅ Stream complete (${duration}s)`));
4013
+ }
4014
+
4015
+ async displayAdvancedTypingResponse(response, startTime) {
4016
+ if (!response) return;
4017
+
4018
+ const formattedResponse = this.formatResponse(response);
4019
+ const duration = Math.round((Date.now() - startTime) / 1000);
4020
+
4021
+ // Enhanced typing animation with word-by-word display
4022
+ const words = formattedResponse.split(' ');
4023
+ let currentLine = '';
4024
+
4025
+ for (const word of words) {
4026
+ currentLine += word + ' ';
4027
+
4028
+ // Display word
4029
+ process.stdout.write(chalk.white(word + ' '));
4030
+
4031
+ // Variable delay based on word length and content
4032
+ let delay = 30; // Base delay
4033
+ if (word.includes('\n')) delay += 100; // Pause at line breaks
4034
+ if (word.length > 10) delay += 20; // Slower for long words
4035
+ if (word.includes('.') || word.includes('!') || word.includes('?')) delay += 150; // Pause at sentences
4036
+
4037
+ await new Promise(resolve => setTimeout(resolve, delay));
4038
+ }
4039
+
4040
+ console.log('\n' + chalk.gray(`(${duration}s • advanced typing)`));
4041
+ }
4042
+
4043
+ showAdvancedMetrics({ duration, timeToFirstChunk, totalChunks, streamingUsed }) {
4044
+ const metrics = [];
4045
+
4046
+ if (streamingUsed) {
4047
+ metrics.push(chalk.green(`🚀 Streamed ${totalChunks} chunks`));
4048
+ if (timeToFirstChunk) {
4049
+ metrics.push(chalk.blue(`⚡ First response: ${timeToFirstChunk}s`));
4050
+ }
4051
+ } else {
4052
+ metrics.push(chalk.yellow('📄 Standard response'));
4053
+ }
4054
+
4055
+ metrics.push(chalk.gray(`⏱️ Total: ${duration}s`));
4056
+
4057
+ // Show tool usage indicators
4058
+ this.showToolUsage();
4059
+
4060
+ console.log(chalk.gray('Performance: ') + metrics.join(' • '));
4061
+ }
4062
+
4063
+ showEnhancedError(error) {
4064
+ // Enhanced error display with better formatting
4065
+ console.log(chalk.red('╭─────────────────────────────────────────╮'));
4066
+ console.log(chalk.red('│') + chalk.white.bold(' ❌ Error ') + chalk.red('│'));
4067
+ console.log(chalk.red('╰─────────────────────────────────────────╯'));
4068
+ console.log('');
4069
+
4070
+ // Error details
4071
+ console.log(chalk.white(error.message));
4072
+ console.log('');
4073
+
4074
+ // Enhanced contextual suggestions
4075
+ if (error.message.includes('429')) {
4076
+ console.log(chalk.yellow('💡 Rate limit: Wait 10-30 seconds before retrying'));
4077
+ } else if (error.message.includes('API key')) {
4078
+ console.log(chalk.yellow('💡 Set your API key: export OPENROUTER_API_KEY=your_key'));
4079
+ } else if (error.message.includes('network') || error.message.includes('timeout')) {
4080
+ console.log(chalk.yellow('💡 Network issue: Check connection and try again'));
4081
+ } else if (error.message.includes('model')) {
4082
+ console.log(chalk.yellow('💡 Model issue: Try switching models with /model command'));
4083
+ } else {
4084
+ console.log(chalk.yellow('💡 Try: /help for available commands or /reset to start fresh'));
4085
+ }
4086
+
4087
+ // Debug information if enabled
4088
+ if (process.env.RECODER_DEBUG === 'true') {
4089
+ console.log('');
4090
+ console.log(chalk.gray('Debug details:'));
4091
+ console.log(chalk.gray(error.stack?.split('\n').slice(0, 3).join('\n')));
4092
+ }
4093
+ }
4094
+
3400
4095
  async cleanup() {
3401
4096
  this.logger.info('Starting cleanup process');
3402
4097
 
4098
+ // Clean up streaming and loading animations
4099
+ this.stopProgressiveLoading();
4100
+ this.stopStreamingDisplay();
4101
+
3403
4102
  if (this.fileWatcher) {
3404
4103
  this.fileWatcher.close();
3405
4104
  this.fileWatcher = null;
@@ -2,7 +2,9 @@
2
2
 
3
3
  const fs = require('fs');
4
4
  const path = require('path');
5
- const chalk = require('chalk');
5
+ // Handle chalk properly
6
+ const chalkModule = require('chalk');
7
+ const chalk = chalkModule.default || chalkModule;
6
8
  const inquirer = require('inquirer');
7
9
 
8
10
  /**
package/cli/run.js CHANGED
@@ -1193,7 +1193,7 @@ async function main() {
1193
1193
  }
1194
1194
 
1195
1195
  if (args.length === 0) {
1196
- // Launch interactive mode when no arguments provided
1196
+ // Launch interactive mode when no arguments provided (skip API key check for interactive mode)
1197
1197
  const InteractiveChat = require('./interactive.js');
1198
1198
  const chat = new InteractiveChat();
1199
1199
  chat.start();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "recoder-code",
3
- "version": "2.1.4",
3
+ "version": "2.2.1",
4
4
  "description": "Complete AI-powered development platform with ML model training, plugin registry, real-time collaboration, monitoring, infrastructure automation, and enterprise deployment capabilities",
5
5
  "main": "index.js",
6
6
  "scripts": {