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.
- package/cli/interactive.js +773 -74
- package/cli/model-manager.js +3 -1
- package/cli/run.js +1 -1
- package/package.json +1 -1
package/cli/interactive.js
CHANGED
|
@@ -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
|
-
|
|
127
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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.
|
|
380
|
+
this.rl.setPrompt(chalk.gray('... '));
|
|
407
381
|
} else {
|
|
408
|
-
this.rl.setPrompt(convIndicator + chalk.
|
|
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
|
-
//
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
997
|
-
|
|
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.
|
|
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.
|
|
1018
|
-
console.log(chalk.
|
|
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.
|
|
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(`
|
|
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.
|
|
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
|
-
//
|
|
2929
|
-
if (this.
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
|
|
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
|
|
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;
|
package/cli/model-manager.js
CHANGED
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
|
|
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": {
|