recoder-code 2.2.0 → 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.magenta('▶ ') + chalk.white.bold('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.magenta('┃ ') + chalk.gray('... '));
380
+ this.rl.setPrompt(chalk.gray('... '));
407
381
  } else {
408
- this.rl.setPrompt(convIndicator + chalk.magenta(' ') + chalk.white.bold('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,48 +957,554 @@ 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
- // Stunning purple/gray/black UI with recoder.xyz branding
1185
+ // Beautiful welcome screen with neon purple/white/gray theme
985
1186
  console.log('');
986
- console.log(chalk.magenta.bold('╭─────────────────────────────────────────────────────────────────╮'));
987
- console.log(chalk.magenta.bold('│') + chalk.black.bgMagenta.bold(' RECODER.XYZ ') + chalk.magenta.bold('│'));
988
- console.log(chalk.magenta.bold('') + chalk.gray(' AI-Powered Development Terminal ') + chalk.magenta.bold('│'));
989
- console.log(chalk.magenta.bold('╰─────────────────────────────────────────────────────────────────╯'));
1187
+ console.log(chalk.magenta('╭─────────────────────────────────────────────────────────────╮'));
1188
+ console.log(chalk.magenta('│') + chalk.white.bold(' ✨ Welcome to Recoder Code ') + chalk.magenta('│'));
1189
+ console.log(chalk.magenta('╰─────────────────────────────────────────────────────────────╯'));
990
1190
  console.log('');
991
1191
 
992
- // ASCII Art Logo
1192
+ // Stylized ASCII art in neon purple
993
1193
  console.log(chalk.magenta.bold('██████╗ ███████╗ ██████╗ ██████╗ ██████╗ ███████╗██████╗'));
994
1194
  console.log(chalk.magenta.bold('██╔══██╗██╔════╝██╔════╝██╔═══██╗██╔══██╗██╔════╝██╔══██╗'));
995
1195
  console.log(chalk.magenta.bold('██████╔╝█████╗ ██║ ██║ ██║██║ ██║█████╗ ██████╔╝'));
996
- console.log(chalk.gray('██╔══██╗██╔══╝ ██║ ██║ ██║██║ ██║██╔══╝ ██╔══██╗'));
997
- console.log(chalk.gray('██║ ██║███████╗╚██████╗╚██████╔╝██████╔╝███████╗██║ ██║'));
1196
+ console.log(chalk.white('██╔══██╗██╔══╝ ██║ ██║ ██║██║ ██║██╔══╝ ██╔══██╗'));
1197
+ console.log(chalk.white('██║ ██║███████╗╚██████╗╚██████╔╝██████╔╝███████╗██║ ██║'));
998
1198
  console.log(chalk.gray('╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚═════╝ ╚═════╝ ╚══════╝╚═╝ ╚═╝'));
999
1199
  console.log('');
1000
1200
 
1001
- // Status Bar
1002
- console.log(chalk.magenta(' ') + chalk.white.bold('Ready') + chalk.gray(' │ ') + chalk.gray(`${process.cwd()}`));
1003
- console.log(chalk.gray('─'.repeat(65)));
1201
+ // Status line
1202
+ console.log(chalk.gray('Press Enter to continue'));
1004
1203
  console.log('');
1005
1204
 
1006
- // Show session suggestions if available
1007
- this.showSessionSuggestions();
1205
+ // Add bottom status hints like Claude Code
1206
+ this.showStatusHints();
1008
1207
 
1009
- // Quick actions
1010
- console.log(chalk.magenta('⚡ ') + chalk.white.bold('Quick Actions:'));
1011
- console.log(chalk.gray(' • ') + chalk.cyan('/help') + chalk.gray(' - Show available commands'));
1012
- console.log(chalk.gray(' • ') + chalk.cyan('/status') + chalk.gray(' - System status & configuration'));
1013
- console.log(chalk.gray(' • ') + chalk.cyan('/models') + chalk.gray(' - Switch AI models'));
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('╰─────────────────────────────────────────╯'));
1014
1216
  console.log('');
1015
-
1016
- console.log(chalk.magenta('💬 ') + chalk.white.bold('Start coding with AI assistance!'));
1017
- console.log(chalk.gray('✨ Try: ') + chalk.cyan('"build a React app"') + chalk.gray(' or ') + chalk.cyan('"debug my Python code"'));
1217
+ console.log(chalk.white(error.message));
1018
1218
  console.log('');
1019
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
+
1020
1332
  this.rl.prompt();
1021
1333
  }
1022
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
+
1023
1508
  showSessionSuggestions() {
1024
1509
  try {
1025
1510
  // Check if project exists
@@ -2945,12 +3430,15 @@ class InteractiveRecoderChat {
2945
3430
 
2946
3431
  console.log(chalk.white(assistantMessage));
2947
3432
 
2948
- // Optional voice output for AI responses
2949
- if (this.voiceManager.textToSpeechEnabled) {
2950
- // Speak the response (in background, don't wait)
2951
- this.voiceManager.speakText(assistantMessage).catch(err => {
2952
- // Ignore TTS errors to not interrupt the conversation
2953
- });
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
2954
3442
  }
2955
3443
 
2956
3444
  // Update conversation-specific stats
@@ -3417,9 +3905,200 @@ ${projectInfo}`
3417
3905
  }
3418
3906
  }
3419
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
+
3420
4095
  async cleanup() {
3421
4096
  this.logger.info('Starting cleanup process');
3422
4097
 
4098
+ // Clean up streaming and loading animations
4099
+ this.stopProgressiveLoading();
4100
+ this.stopStreamingDisplay();
4101
+
3423
4102
  if (this.fileWatcher) {
3424
4103
  this.fileWatcher.close();
3425
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.2.0",
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": {