recoder-code 2.2.9 → 2.3.4

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.
@@ -153,13 +153,15 @@ class InteractiveRecoderChat {
153
153
 
154
154
  // Always initialize readline interface first to prevent errors
155
155
  this.initializeReadlineInterface();
156
-
156
+
157
+ // Handle API key prompt and initialization
157
158
  if (!this.apiKey) {
158
- this.promptForApiKey();
159
- return;
159
+ this.promptForApiKey().then(() => {
160
+ this.initializeWithApiKey();
161
+ });
162
+ } else {
163
+ this.initializeWithApiKey();
160
164
  }
161
-
162
- this.initializeWithApiKey();
163
165
  }
164
166
 
165
167
  /**
@@ -944,9 +946,10 @@ class InteractiveRecoderChat {
944
946
 
945
947
  async loadPlugins() {
946
948
  try {
947
- await this.pluginManager.loadPlugins();
949
+ // Load plugins silently for production mode
950
+ await this.pluginManager.loadPlugins(true); // true = silent mode
948
951
  } catch (error) {
949
- console.log(chalk.yellow(`⚠️ Plugin loading failed: ${error.message}`));
952
+ // Silently fail - don't show errors
950
953
  }
951
954
  }
952
955
 
@@ -1029,7 +1032,7 @@ class InteractiveRecoderChat {
1029
1032
  });
1030
1033
  }
1031
1034
 
1032
- promptForApiKey() {
1035
+ async promptForApiKey() {
1033
1036
  console.clear();
1034
1037
  console.log('');
1035
1038
  console.log(chalk.magenta('╭─────────────────────────────────────────────────────────────╮'));
@@ -1039,41 +1042,52 @@ class InteractiveRecoderChat {
1039
1042
  console.log(chalk.white('🔑 API key required to continue'));
1040
1043
  console.log(chalk.gray('Get your free API key at: ') + chalk.magenta('https://openrouter.ai'));
1041
1044
  console.log('');
1042
-
1043
- const keyPrompt = readline.createInterface({
1044
- input: process.stdin,
1045
- output: process.stdout
1046
- });
1047
-
1048
- keyPrompt.question(chalk.magenta('Enter your OpenRouter API key: '), (apiKey) => {
1049
- keyPrompt.close();
1050
-
1051
- if (!apiKey || apiKey.trim() === '') {
1052
- console.log(chalk.red('No API key provided. Exiting...'));
1053
- process.exit(1);
1054
- }
1055
-
1056
- // Save the API key to environment
1057
- process.env.OPENROUTER_API_KEY = apiKey.trim();
1058
- this.apiKey = apiKey.trim();
1059
-
1060
- // Continue initialization
1061
- this.initializeWithApiKey();
1045
+
1046
+ // Use existing readline interface instead of creating a new one
1047
+ return new Promise((resolve) => {
1048
+ this.rl.question(chalk.magenta('Enter your OpenRouter API key: '), (apiKey) => {
1049
+ if (!apiKey || apiKey.trim() === '') {
1050
+ console.log(chalk.red('No API key provided. Exiting...'));
1051
+ process.exit(1);
1052
+ }
1053
+
1054
+ // Save the API key to environment
1055
+ process.env.OPENROUTER_API_KEY = apiKey.trim();
1056
+ this.apiKey = apiKey.trim();
1057
+
1058
+ resolve();
1059
+ });
1062
1060
  });
1063
1061
  }
1064
1062
 
1065
1063
  initializeReadlineInterface() {
1064
+ // Don't re-initialize if already exists
1065
+ if (this.rl) {
1066
+ return;
1067
+ }
1068
+
1069
+ // Check if stdin is a TTY
1070
+ if (!process.stdin.isTTY) {
1071
+ console.error('Error: stdin is not a TTY. Please run in a proper terminal.');
1072
+ process.exit(1);
1073
+ }
1074
+
1066
1075
  // Initialize readline interface with enhanced options
1076
+ // Set terminal based on environment variable or default to true
1077
+ // If you see double characters, set RECODER_TERMINAL_MODE=false
1078
+ const terminalMode = process.env.RECODER_TERMINAL_MODE !== 'false';
1079
+
1067
1080
  this.rl = readline.createInterface({
1068
1081
  input: process.stdin,
1069
1082
  output: process.stdout,
1070
1083
  prompt: chalk.gray('> '),
1071
1084
  historySize: 100,
1072
1085
  removeHistoryDuplicates: true,
1086
+ terminal: terminalMode,
1073
1087
  completer: (line) => this.autoComplete(line)
1074
1088
  });
1075
1089
 
1076
- // Set up readline event handlers
1090
+ // Set up readline event handlers ONCE
1077
1091
  this.setupReadlineHandlers();
1078
1092
  }
1079
1093
 
@@ -1119,10 +1133,21 @@ class InteractiveRecoderChat {
1119
1133
  this.analyzeProjectContext().catch(error => {
1120
1134
  console.log(chalk.yellow(`⚠️ Project context analysis failed: ${error.message}`));
1121
1135
  });
1122
- this.loadPlugins().catch(error => {
1136
+ this.displayWelcome();
1137
+
1138
+ // Load plugins after welcome, then show prompt again
1139
+ this.loadPlugins().then(() => {
1140
+ // Re-show prompt after plugins finish loading
1141
+ if (this.rl) {
1142
+ console.log(''); // Add blank line
1143
+ this.rl.prompt();
1144
+ }
1145
+ }).catch(error => {
1123
1146
  console.log(chalk.yellow(`⚠️ Plugin loading failed: ${error.message}`));
1147
+ if (this.rl) {
1148
+ this.rl.prompt();
1149
+ }
1124
1150
  });
1125
- this.displayWelcome();
1126
1151
  }
1127
1152
 
1128
1153
  updatePrompt() {
@@ -1144,119 +1169,158 @@ class InteractiveRecoderChat {
1144
1169
  return;
1145
1170
  }
1146
1171
 
1147
- // Handle slash commands like Claude Code
1172
+ // Handle slash commands
1148
1173
  if (input.startsWith('/')) {
1149
1174
  this.handleSlashCommand(input);
1150
1175
  return;
1151
1176
  }
1152
1177
 
1153
1178
  try {
1154
- // Track performance metrics
1155
1179
  const startTime = Date.now();
1156
- let firstChunkTime = null;
1157
- let totalChunks = 0;
1158
-
1159
- // Start progressive loading with enhanced animations
1160
- this.startProgressiveLoading();
1161
-
1162
- // Create a RecoderCode instance to process the conversation
1180
+
1181
+ // Create RecoderCode instance
1163
1182
  const { RecoderCode } = require('./run.js');
1183
+ if (this.apiKey && !process.env.OPENROUTER_API_KEY) {
1184
+ process.env.OPENROUTER_API_KEY = this.apiKey;
1185
+ }
1164
1186
  const recoder = new RecoderCode();
1165
-
1166
- // Initialize streaming response system
1167
- const streamBuffer = {
1168
- content: '',
1169
- chunks: [],
1170
- isStreaming: false,
1171
- streamingStarted: false
1187
+
1188
+ // Override tool execution to show indicators
1189
+ const originalExecuteToolCall = recoder.executeToolCall.bind(recoder);
1190
+ recoder.executeToolCall = async (toolCall) => {
1191
+ const toolName = toolCall.function.name;
1192
+ process.stdout.write(chalk.dim(` ${toolName}...\r`));
1193
+ const result = await originalExecuteToolCall(toolCall);
1194
+ process.stdout.write(' \r');
1195
+ return result;
1172
1196
  };
1173
-
1174
- // Enhanced streaming response handler
1175
- const response = await this.processWithAdvancedStreaming({
1176
- recoder,
1177
- input,
1178
- startTime,
1179
- streamBuffer,
1180
- onFirstChunk: (time) => { firstChunkTime = time; },
1181
- onChunk: (chunk) => {
1182
- totalChunks++;
1183
- this.displayStreamingChunk(chunk, totalChunks);
1184
- }
1185
- });
1186
-
1187
- // Calculate comprehensive metrics
1188
- const totalDuration = Math.round((Date.now() - startTime) / 1000);
1189
- const timeToFirstChunk = firstChunkTime ? Math.round((firstChunkTime - startTime) / 1000) : null;
1190
-
1191
- // Track conversation history with enhanced metadata
1192
- this.conversationHistory.push({
1193
- timestamp: new Date(),
1194
- input: input,
1195
- response: response,
1196
- duration: totalDuration,
1197
- timeToFirstChunk,
1198
- totalChunks,
1199
- streamingUsed: streamBuffer.isStreaming
1200
- });
1201
- this.currentSession.messageCount++;
1202
- this.currentSession.lastActivity = Date.now();
1203
-
1204
- console.log('');
1205
-
1206
- // Show performance metrics and tool usage
1207
- this.showAdvancedMetrics({
1208
- duration: totalDuration,
1209
- timeToFirstChunk,
1210
- totalChunks,
1211
- streamingUsed: streamBuffer.isStreaming
1212
- });
1213
-
1214
- // Enhanced response display if not already streamed
1215
- if (!streamBuffer.streamingStarted) {
1216
- await this.displayAdvancedTypingResponse(response, startTime);
1217
- } else {
1218
- // Finalize streaming display
1219
- this.finalizeStreamingResponse(response, totalDuration);
1220
- }
1221
1197
 
1222
- // Ensure progressive loading is stopped
1223
- this.stopProgressiveLoading();
1224
- this.stopStreamingDisplay();
1198
+ // Simple thinking indicator
1199
+ process.stdout.write(chalk.dim(' thinking...\r'));
1225
1200
 
1226
- console.log('');
1201
+ // Get AI response (non-streaming for cleaner output)
1202
+ const response = await recoder.processConversation(input, false, false, null);
1203
+
1204
+ // Clear thinking indicator
1205
+ process.stdout.write(' \r');
1206
+
1207
+ // Display response and handle tool calls
1208
+ if (response) {
1209
+ // Check if response contains DeepSeek-style tool calls
1210
+ const hasToolCalls = response.includes('<|tool▁call▁begin|>');
1211
+
1212
+ if (hasToolCalls) {
1213
+ // Parse and execute tool calls
1214
+ const result = await this.parseAndExecuteToolCalls(response, recoder);
1215
+
1216
+ if (result.toolResults.length > 0) {
1217
+ // Show tool execution results
1218
+ console.log('');
1219
+ result.toolResults.forEach(tr => {
1220
+ console.log(chalk.dim(` ✓ ${tr.tool}`));
1221
+ });
1222
+
1223
+ // Get follow-up response with tool results
1224
+ const toolContext = result.toolResults.map(tr =>
1225
+ `Tool ${tr.tool} executed: ${tr.result.substring(0, 200)}`
1226
+ ).join('\n');
1227
+
1228
+ const followUp = await recoder.processConversation(
1229
+ `Based on these tool results:\n${toolContext}\n\nProvide a helpful summary of what was done.`,
1230
+ false, false, null
1231
+ );
1232
+
1233
+ const cleanFollowUp = followUp.replace(/<|tool[^|]*|>/g, '').trim();
1234
+ if (cleanFollowUp) {
1235
+ console.log('\n' + cleanFollowUp + '\n');
1236
+ }
1237
+ } else {
1238
+ // No tools executed, show cleaned response
1239
+ const cleanResponse = result.cleanText;
1240
+ if (cleanResponse) {
1241
+ console.log('\n' + cleanResponse + '\n');
1242
+ }
1243
+ }
1244
+ } else {
1245
+ // No tool calls, display normally
1246
+ console.log('\n' + response + '\n');
1247
+ }
1248
+
1249
+ // Track conversation
1250
+ this.conversationHistory.push({
1251
+ timestamp: new Date(),
1252
+ input: input,
1253
+ response: response
1254
+ });
1255
+ this.currentSession.messageCount++;
1256
+ this.currentSession.lastActivity = Date.now();
1257
+ }
1227
1258
 
1228
1259
  } catch (error) {
1229
- this.stopProgressiveLoading();
1230
- this.stopStreamingDisplay();
1231
- console.log('');
1232
- this.showEnhancedError(error);
1233
- console.log('');
1260
+ process.stdout.write(' \r');
1261
+ console.log(chalk.red('\n Error: ' + error.message + '\n'));
1234
1262
  }
1235
1263
 
1236
1264
  this.rl.prompt();
1237
1265
  }
1238
1266
 
1267
+ async parseAndExecuteToolCalls(response, recoder) {
1268
+ const toolResults = [];
1269
+ let cleanText = response;
1270
+
1271
+ // Parse DeepSeek-style tool calls: <|tool▁call▁begin|>toolname<|tool▁sep|>{"args"}<|tool▁call▁end|>
1272
+ const toolCallRegex = /<|tool▁call▁begin|>(\w+)<|tool▁sep|>({[^}]+})<|tool▁call▁end|>/g;
1273
+ let match;
1274
+
1275
+ while ((match = toolCallRegex.exec(response)) !== null) {
1276
+ const toolName = match[1];
1277
+ const argsJson = match[2];
1278
+
1279
+ try {
1280
+ const args = JSON.parse(argsJson);
1281
+
1282
+ // Execute the tool
1283
+ let result = '';
1284
+ if (toolName === 'bash') {
1285
+ result = await recoder.handleBashCommand(args);
1286
+ } else if (toolName === 'str_replace_editor') {
1287
+ result = await recoder.handleFileOperation(args);
1288
+ } else if (toolName === 'codebase_search') {
1289
+ result = await recoder.handleCodebaseSearch(args);
1290
+ } else if (toolName === 'web_fetch') {
1291
+ result = await recoder.handleWebFetch(args);
1292
+ }
1293
+
1294
+ toolResults.push({ tool: toolName, args, result });
1295
+
1296
+ // Remove the tool call from clean text
1297
+ cleanText = cleanText.replace(match[0], '');
1298
+ } catch (error) {
1299
+ console.log(chalk.yellow(` ⚠ Tool ${toolName} failed: ${error.message}`));
1300
+ }
1301
+ }
1302
+
1303
+ // Clean up tool markers
1304
+ cleanText = cleanText.replace(/<|tool▁calls▁begin|>/g, '');
1305
+ cleanText = cleanText.replace(/<|tool▁calls▁end|>/g, '');
1306
+ cleanText = cleanText.trim();
1307
+
1308
+ return { toolResults, cleanText };
1309
+ }
1310
+
1239
1311
  displayWelcome() {
1240
1312
  console.clear();
1241
-
1242
- // Enhanced welcome screen with gradient effects and animations
1243
- console.log('');
1244
-
1245
- // Animated gradient border
1246
- this.displayAnimatedBorder();
1247
-
1248
- // Beautiful gradient ASCII art
1249
- console.log('');
1250
- this.displayGradientLogo();
1313
+
1314
+ const projectName = this.getCurrentProjectContext().name;
1315
+ const modelName = this.model || 'deepseek/deepseek-chat-v3.1:free';
1316
+
1317
+ // Simple, clean header like Claude Code
1251
1318
  console.log('');
1252
-
1253
- // Dynamic status information
1254
- this.displayDynamicStatus();
1319
+ console.log(chalk.cyan.bold(' Recoder Code') + chalk.dim(` v${require('../package.json').version}`));
1320
+ console.log(chalk.dim(` ${modelName}`));
1321
+ console.log(chalk.dim(` ${process.cwd()}`));
1255
1322
  console.log('');
1256
-
1257
- // Enhanced status hints with themes
1258
- this.showEnhancedStatusHints();
1259
-
1323
+
1260
1324
  this.rl.prompt();
1261
1325
  }
1262
1326
 
@@ -4113,6 +4177,7 @@ ${projectInfo}`
4113
4177
 
4114
4178
  async processWithAdvancedStreaming({ recoder, input, startTime, streamBuffer, onFirstChunk, onChunk }) {
4115
4179
  try {
4180
+ console.log(chalk.gray('[DEBUG] Starting API call...'));
4116
4181
  // Attempt streaming first
4117
4182
  const response = await recoder.processConversation(input, false, true, {
4118
4183
  onChunk: (chunk) => {
@@ -4122,19 +4187,23 @@ ${projectInfo}`
4122
4187
  streamBuffer.streamingStarted = true;
4123
4188
  onFirstChunk(Date.now());
4124
4189
  }
4125
-
4190
+
4126
4191
  streamBuffer.content += chunk;
4127
4192
  streamBuffer.chunks.push(chunk);
4128
4193
  streamBuffer.isStreaming = true;
4129
4194
  onChunk(chunk);
4130
4195
  }
4131
4196
  });
4132
-
4133
- return response || streamBuffer.content;
4134
-
4197
+
4198
+ // If streaming was used, return the accumulated content
4199
+ // Otherwise return the response (which is the final message content)
4200
+ return streamBuffer.streamingStarted ? streamBuffer.content : response;
4201
+
4135
4202
  } catch (error) {
4136
4203
  // Fallback to non-streaming
4137
4204
  this.stopProgressiveLoading();
4205
+ console.log(chalk.red('\n❌ Streaming failed:', error.message));
4206
+ console.log(chalk.yellow('Trying non-streaming mode...'));
4138
4207
  return await recoder.processConversation(input, false, false, null);
4139
4208
  }
4140
4209
  }
package/cli/run.js CHANGED
@@ -92,10 +92,11 @@ class RecoderCode {
92
92
  console.log(chalk.white(' recoder-code --setup\n'));
93
93
  process.exit(1);
94
94
  }
95
- return; // Return early to allow interactive mode to handle
95
+ // Don't return early - initialize with empty key and it will be set later
96
+ this.apiKey = process.env.OPENROUTER_API_KEY || '';
96
97
  }
97
98
 
98
- // Initialize OpenAI client with OpenRouter configuration
99
+ // Always initialize OpenAI client (will use apiKey from config or empty string)
99
100
  this.openai = new OpenAI({
100
101
  baseURL: config.baseURL,
101
102
  apiKey: this.apiKey,
@@ -265,7 +266,7 @@ class RecoderCode {
265
266
  }
266
267
  }
267
268
 
268
- async callOpenRouter(messages, tools = null, useAutoRouter = false, streamResponse = false) {
269
+ async callOpenRouter(messages, tools = null, useAutoRouter = false, streamResponse = false, onChunkCallback = null) {
269
270
  const payload = {
270
271
  model: useAutoRouter ? 'openrouter/auto' : this.model,
271
272
  messages: messages,
@@ -301,13 +302,15 @@ class RecoderCode {
301
302
  if (tools) {
302
303
  payload.tools = tools;
303
304
  payload.tool_choice = 'auto';
305
+ // Disable streaming when tools are used
306
+ payload.stream = false;
304
307
  }
305
308
 
306
309
  try {
307
310
  if (process.env.RECODER_DEBUG === 'true') {
308
311
  console.log(`🔄 Using model: ${payload.model} ${payload.models ? `with fallbacks: ${payload.models.slice(1).join(', ')}` : ''}`);
309
312
  }
310
-
313
+
311
314
  if (streamResponse && !tools) {
312
315
  // Handle streaming response
313
316
  const stream = await this.openai.chat.completions.create(payload);
@@ -320,14 +323,19 @@ class RecoderCode {
320
323
  if (process.env.RECODER_DEBUG === 'true') {
321
324
  console.log('\nDEBUG - Chunk:', JSON.stringify(chunk, null, 2));
322
325
  }
323
-
326
+
324
327
  if (chunk.choices && chunk.choices.length > 0 && chunk.choices[0]) {
325
328
  const content = chunk.choices[0]?.delta?.content || '';
326
329
  if (content) {
327
- process.stdout.write(content);
330
+ // Call the callback if provided, otherwise write to stdout
331
+ if (onChunkCallback) {
332
+ onChunkCallback(content);
333
+ } else {
334
+ process.stdout.write(content);
335
+ }
328
336
  fullContent += content;
329
337
  }
330
-
338
+
331
339
  // Store the full response structure from the last chunk
332
340
  if (chunk.choices[0]?.finish_reason) {
333
341
  fullResponse = {
@@ -352,7 +360,7 @@ class RecoderCode {
352
360
  this.logRequest(payload, response);
353
361
  return response;
354
362
  }
355
-
363
+
356
364
  // If no response was captured from streaming, create a basic one
357
365
  if (!fullResponse) {
358
366
  fullResponse = {
@@ -367,8 +375,10 @@ class RecoderCode {
367
375
  usage: null
368
376
  };
369
377
  }
370
-
371
- console.log('\n'); // Add newline after streaming
378
+
379
+ if (!onChunkCallback) {
380
+ console.log('\n'); // Add newline after streaming only if we wrote to stdout
381
+ }
372
382
  this.logRequest(payload, fullResponse);
373
383
  return fullResponse;
374
384
  } else {
@@ -385,7 +395,23 @@ class RecoderCode {
385
395
  }
386
396
  } catch (error) {
387
397
  this.logRequest(payload, null, error);
388
-
398
+
399
+ // Show detailed error message to help user debug
400
+ console.log(chalk.red('\n❌ API Request Failed in callOpenRouter:'));
401
+
402
+ // Check for authentication errors specifically
403
+ if (error.status === 401 || error.message?.includes('401')) {
404
+ console.log(chalk.red(' Authentication failed - Invalid API key'));
405
+ console.log(chalk.yellow(' Please check your OPENROUTER_API_KEY'));
406
+ } else if (error.status === 402) {
407
+ console.log(chalk.red(' Insufficient credits'));
408
+ console.log(chalk.yellow(' Please add credits to your OpenRouter account'));
409
+ } else {
410
+ console.log(chalk.yellow(' Status:', error.status || 'N/A'));
411
+ console.log(chalk.yellow(' Message:', error.message || 'N/A'));
412
+ console.log(chalk.yellow(' Code:', error.code || 'N/A'));
413
+ }
414
+
389
415
  // Let unified error handler manage the error instead of re-throwing
390
416
  // Return null for graceful degradation
391
417
  return null;
@@ -694,7 +720,25 @@ Pending Tasks: ${projectStatus.todos.pending}
694
720
  Documentation: Plan=${projectStatus.files.plan ? '✓' : '✗'} Architecture=${projectStatus.files.architecture ? '✓' : '✗'} Notes=${projectStatus.files.notes ? '✓' : '✗'}`;
695
721
  }
696
722
 
697
- async processConversation(userMessage, useAutoRouter = false, enableStreaming = false, resumeSession = null) {
723
+ async processConversation(userMessage, useAutoRouter = false, enableStreaming = false, streamingOptions = null) {
724
+ // streamingOptions can be:
725
+ // - null/false: regular resumeSession string for backward compatibility
726
+ // - object with { onChunk: callback }: streaming with callback
727
+ // - true: enable streaming without callback
728
+
729
+ let resumeSession = null;
730
+ let onChunkCallback = null;
731
+
732
+ if (streamingOptions) {
733
+ if (typeof streamingOptions === 'object' && streamingOptions.onChunk) {
734
+ // New streaming mode with callback
735
+ onChunkCallback = streamingOptions.onChunk;
736
+ } else if (typeof streamingOptions === 'string') {
737
+ // Backward compatibility: string is resumeSession
738
+ resumeSession = streamingOptions;
739
+ }
740
+ }
741
+
698
742
  // If resuming a session, restore conversation history
699
743
  if (resumeSession) {
700
744
  const session = this.projectManager.loadSession(resumeSession);
@@ -702,9 +746,8 @@ Documentation: Plan=${projectStatus.files.plan ? '✓' : '✗'} Architecture=${p
702
746
  this.conversationHistory = [...session.conversationHistory];
703
747
  console.log(chalk.cyan(`🔄 Resumed session: ${resumeSession}`));
704
748
  console.log(chalk.gray(` Previous messages: ${this.conversationHistory.length}`));
705
- } else {
706
- console.log(chalk.yellow(`⚠️ Could not resume session: ${resumeSession}`));
707
749
  }
750
+ // Silently ignore session resume failures - don't warn users
708
751
  }
709
752
 
710
753
  // Check if this is a slash command
@@ -815,12 +858,11 @@ Project context: ${this.getProjectContextString()}${this.getRulesContextString()
815
858
 
816
859
  const messages = [systemMessage, ...this.conversationHistory];
817
860
 
818
- let response = await this.callOpenRouter(messages, config.tools, useAutoRouter, enableStreaming);
819
-
861
+ let response = await this.callOpenRouter(messages, config.tools, useAutoRouter, enableStreaming, onChunkCallback);
862
+
820
863
  // Add safety checks for response structure
821
864
  if (!response || !response.choices || !response.choices[0]) {
822
- // API error already handled by unified error handler, provide graceful fallback
823
- return 'Unable to process request - API service unavailable. Please check your API key and credits.';
865
+ return 'Unable to process request. Please check your API key and try again.';
824
866
  }
825
867
 
826
868
  while (response.choices[0].message && response.choices[0].message.tool_calls) {
@@ -838,7 +880,7 @@ Project context: ${this.getProjectContextString()}${this.getRulesContextString()
838
880
  }
839
881
 
840
882
  // Don't stream when there are tool calls, as we need to process them
841
- response = await this.callOpenRouter([systemMessage, ...this.conversationHistory], null, useAutoRouter, false);
883
+ response = await this.callOpenRouter([systemMessage, ...this.conversationHistory], null, useAutoRouter, false, null);
842
884
 
843
885
  // Add safety check again
844
886
  if (!response || !response.choices || !response.choices[0]) {
@@ -237,9 +237,26 @@ class UserOnboarding {
237
237
  */
238
238
  async runOnboarding() {
239
239
  this.showWelcome();
240
-
240
+
241
241
  const userName = await this.getUserName();
242
- const apiKey = await this.getApiKey();
242
+
243
+ // Check if API key already exists in .env file
244
+ const envFile = path.join(require('os').homedir(), '.recoder-code', '.env');
245
+ let apiKey = null;
246
+
247
+ if (fs.existsSync(envFile)) {
248
+ const envContent = fs.readFileSync(envFile, 'utf8');
249
+ const match = envContent.match(/OPENROUTER_API_KEY=(.+)/);
250
+ if (match && match[1] && match[1].startsWith('sk-or-v1-')) {
251
+ apiKey = match[1].trim();
252
+ console.log(chalk.green('\n✅ Found existing API key in .env file'));
253
+ console.log(chalk.gray(' Using: ' + apiKey.substring(0, 20) + '...'));
254
+ }
255
+ }
256
+
257
+ if (!apiKey) {
258
+ apiKey = await this.getApiKey();
259
+ }
243
260
 
244
261
  if (!apiKey) {
245
262
  console.log(chalk.red('\n❌ API key is required to use Recoder Code.'));
package/config.js CHANGED
@@ -6,12 +6,13 @@ const fs = require('fs');
6
6
  const userEnvPath = path.join(os.homedir(), '.recoder-code', '.env');
7
7
  const projectEnvPath = path.join(process.cwd(), '.env');
8
8
 
9
+ // Suppress dotenv logs with quiet option
9
10
  if (fs.existsSync(userEnvPath)) {
10
- require('dotenv').config({ path: userEnvPath });
11
+ require('dotenv').config({ path: userEnvPath, override: false, quiet: true });
11
12
  } else if (fs.existsSync(projectEnvPath)) {
12
- require('dotenv').config({ path: projectEnvPath });
13
+ require('dotenv').config({ path: projectEnvPath, override: false, quiet: true });
13
14
  } else {
14
- require('dotenv').config();
15
+ require('dotenv').config({ override: false, quiet: true });
15
16
  }
16
17
 
17
18
  module.exports = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "recoder-code",
3
- "version": "2.2.9",
3
+ "version": "2.3.4",
4
4
  "description": "🚀 AI-powered development platform - Chat with 32+ models, build projects, automate workflows. Free models included!",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -34,7 +34,7 @@
34
34
  "production:check": "npm run lint && npm run typecheck && npm run security:audit",
35
35
  "production:bundle": "npm run production:check && npm run build:production",
36
36
  "production:deploy": "npm run production:bundle && npm publish --access public",
37
- "postinstall": "node -e \"console.log('\\\\n🚀 Recoder-Code v2.2.9 installed!\\\\n\\\\n✅ Quick Start: recoder-code\\\\n💡 Get API key: https://openrouter.ai\\\\n')\"",
37
+ "postinstall": "node -e \"console.log('\\\\n🚀 Recoder-Code v2.3.3 installed!\\\\n\\\\n✅ Quick Start: recoder-code\\\\n💡 Get API key: https://openrouter.ai\\\\n')\"",
38
38
  "prepare": "echo 'Package prepared for distribution'",
39
39
  "prepublishOnly": "npm run prepare && npm run security:audit"
40
40
  },