recoder-code 2.2.0 → 2.2.2
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/README.md +66 -10
- package/cli/collaboration-manager.js +3 -1
- package/cli/dev-tools-integration.js +3 -1
- package/cli/enhanced-setup.js +3 -1
- package/cli/file-watcher-manager.js +216 -0
- package/cli/interactive.js +2258 -165
- package/cli/logger.js +181 -0
- package/cli/mcp-client.js +32 -9
- package/cli/ml-training-manager.js +3 -1
- package/cli/model-manager.js +3 -1
- package/cli/natural-language-processor.js +3 -1
- package/cli/project-manager.js +3 -1
- package/cli/rules-engine.js +9 -4
- package/cli/run.js +90 -13
- package/cli/setup-wizard.js +3 -1
- package/cli/slash-commands.js +132 -5
- package/cli/task-manager.js +13 -5
- package/cli/team-collaboration.js +3 -1
- package/cli/todo-manager.js +391 -0
- package/cli/unified-error-handler.js +260 -0
- package/cli/user-experience.js +3 -1
- package/cli/user-onboarding.js +251 -0
- package/cli/vision-analyzer.js +3 -1
- package/index.js +88 -13
- package/package.json +7 -1
- package/plugins/enhanced-plugin-manager.js +31 -11
- package/scripts/performance-benchmark.js +3 -1
- package/scripts/security-audit.js +3 -1
- package/setup.js +3 -1
package/cli/interactive.js
CHANGED
|
@@ -25,6 +25,7 @@ const TaskManager = require('./task-manager.js');
|
|
|
25
25
|
const DevToolsIntegration = require('./dev-tools-integration.js');
|
|
26
26
|
const VisionAnalyzer = require('./vision-analyzer.js');
|
|
27
27
|
const NaturalLanguageProcessor = require('./natural-language-processor.js');
|
|
28
|
+
const TodoManager = require('./todo-manager.js');
|
|
28
29
|
|
|
29
30
|
// Create logs and sessions directories
|
|
30
31
|
const logsDir = path.join(process.cwd(), 'logs');
|
|
@@ -35,10 +36,17 @@ const sessionsDir = path.join(process.cwd(), 'sessions');
|
|
|
35
36
|
}
|
|
36
37
|
});
|
|
37
38
|
|
|
39
|
+
// Initialize unified error handling for interactive mode
|
|
40
|
+
const UnifiedErrorHandler = require('./unified-error-handler.js');
|
|
41
|
+
const errorHandler = UnifiedErrorHandler.instance;
|
|
42
|
+
errorHandler.initialize();
|
|
43
|
+
|
|
38
44
|
class InteractiveRecoderChat {
|
|
39
45
|
constructor() {
|
|
40
|
-
|
|
41
|
-
this.
|
|
46
|
+
// Load user configuration
|
|
47
|
+
this.userConfig = this.loadUserConfig();
|
|
48
|
+
this.apiKey = this.userConfig.apiKey || config.apiKey;
|
|
49
|
+
this.model = this.userConfig.model || config.model;
|
|
42
50
|
this.maxTokens = config.maxTokens;
|
|
43
51
|
this.temperature = config.temperature;
|
|
44
52
|
|
|
@@ -87,8 +95,10 @@ class InteractiveRecoderChat {
|
|
|
87
95
|
// Initialize plugin manager
|
|
88
96
|
this.pluginManager = new PluginManager();
|
|
89
97
|
|
|
90
|
-
// Initialize voice manager
|
|
98
|
+
// Initialize voice manager (silently disabled)
|
|
91
99
|
this.voiceManager = new VoiceManager();
|
|
100
|
+
// Silently disable TTS without console output
|
|
101
|
+
this.voiceManager.ttsEnabled = false;
|
|
92
102
|
|
|
93
103
|
// Initialize collaboration manager
|
|
94
104
|
this.collaborationManager = new CollaborationManager();
|
|
@@ -104,6 +114,12 @@ class InteractiveRecoderChat {
|
|
|
104
114
|
sessionId: this.sessionId
|
|
105
115
|
});
|
|
106
116
|
|
|
117
|
+
// Initialize todo manager for production-ready task tracking
|
|
118
|
+
this.todoManager = new TodoManager({ user: process.env.USER || 'developer' });
|
|
119
|
+
|
|
120
|
+
// Add comprehensive promise rejection handling
|
|
121
|
+
this.setupGlobalErrorHandlers();
|
|
122
|
+
|
|
107
123
|
// Initialize advanced systems
|
|
108
124
|
this.contextEngine = null;
|
|
109
125
|
this.slashCommands = null;
|
|
@@ -115,6 +131,14 @@ class InteractiveRecoderChat {
|
|
|
115
131
|
this.visionAnalyzer = null;
|
|
116
132
|
this.nlProcessor = null;
|
|
117
133
|
|
|
134
|
+
// Enhanced conversation tracking
|
|
135
|
+
this.conversationHistory = [];
|
|
136
|
+
this.currentSession = {
|
|
137
|
+
startTime: Date.now(),
|
|
138
|
+
messageCount: 0,
|
|
139
|
+
lastActivity: Date.now()
|
|
140
|
+
};
|
|
141
|
+
|
|
118
142
|
this.initializeAdvancedSystems().catch(error => {
|
|
119
143
|
console.log(chalk.yellow(`⚠️ Advanced features initialization failed: ${error.message}`));
|
|
120
144
|
});
|
|
@@ -122,46 +146,15 @@ class InteractiveRecoderChat {
|
|
|
122
146
|
// Initialize project manager for persistence
|
|
123
147
|
this.projectManager = new ProjectManager();
|
|
124
148
|
|
|
149
|
+
// Always initialize readline interface first to prevent errors
|
|
150
|
+
this.initializeReadlineInterface();
|
|
151
|
+
|
|
125
152
|
if (!this.apiKey) {
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
console.log(chalk.gray('export OPENROUTER_API_KEY=your_api_key_here'));
|
|
129
|
-
process.exit(1);
|
|
153
|
+
this.promptForApiKey();
|
|
154
|
+
return;
|
|
130
155
|
}
|
|
131
156
|
|
|
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.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();
|
|
157
|
+
this.initializeWithApiKey();
|
|
165
158
|
}
|
|
166
159
|
|
|
167
160
|
/**
|
|
@@ -169,54 +162,69 @@ class InteractiveRecoderChat {
|
|
|
169
162
|
*/
|
|
170
163
|
async initializeAdvancedSystems() {
|
|
171
164
|
try {
|
|
172
|
-
console.log
|
|
165
|
+
// Silently initialize advanced features (no console.log for clean startup)
|
|
173
166
|
|
|
174
167
|
// Initialize context engine for codebase understanding
|
|
175
168
|
this.contextEngine = new ContextEngine();
|
|
176
|
-
|
|
169
|
+
try {
|
|
170
|
+
await this.contextEngine.initialize();
|
|
171
|
+
} catch (contextError) {
|
|
172
|
+
console.log(chalk.yellow(`⚠️ Context engine initialization failed: ${contextError.message}`));
|
|
173
|
+
}
|
|
177
174
|
|
|
178
175
|
// Initialize MCP client for model communication
|
|
179
176
|
this.mcpClient = new MCPClient();
|
|
180
|
-
await this.mcpClient.initialize();
|
|
181
177
|
|
|
182
178
|
// Initialize model manager with MCP and context engine
|
|
183
179
|
this.modelManager = new ModelManager(this.mcpClient, this.contextEngine);
|
|
184
180
|
|
|
185
181
|
// Initialize rules engine with project context
|
|
186
182
|
this.rulesEngine = new RulesEngine(this.projectManager);
|
|
187
|
-
|
|
183
|
+
try {
|
|
184
|
+
await this.rulesEngine.autoDiscoverProjectRules();
|
|
185
|
+
} catch (rulesError) {
|
|
186
|
+
console.log(chalk.yellow(`⚠️ Rules engine initialization failed: ${rulesError.message}`));
|
|
187
|
+
}
|
|
188
188
|
|
|
189
189
|
// Initialize task manager with full integration
|
|
190
190
|
this.taskManager = new TaskManager(this.projectManager, this.contextEngine, this.rulesEngine);
|
|
191
|
-
|
|
191
|
+
try {
|
|
192
|
+
this.taskManager.setupSmartAutomationTriggers();
|
|
193
|
+
} catch (automationError) {
|
|
194
|
+
console.log(chalk.yellow(`⚠️ Automation setup failed: ${automationError.message}`));
|
|
195
|
+
}
|
|
192
196
|
|
|
193
197
|
// Initialize development tools integration
|
|
194
198
|
this.devTools = new DevToolsIntegration(this.contextEngine, this.projectManager, this.taskManager);
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
+
try {
|
|
200
|
+
await this.devTools.setupIDEIntegration();
|
|
201
|
+
await this.devTools.setupGitIntegration();
|
|
202
|
+
await this.devTools.setupDatabaseIntegration();
|
|
203
|
+
await this.devTools.setupPerformanceMonitoring();
|
|
204
|
+
} catch (devToolsError) {
|
|
205
|
+
console.log(chalk.yellow(`⚠️ Dev tools setup failed: ${devToolsError.message}`));
|
|
206
|
+
}
|
|
199
207
|
|
|
200
208
|
// Initialize vision analyzer with model manager and context engine
|
|
201
209
|
this.visionAnalyzer = new VisionAnalyzer(this.modelManager, this.contextEngine);
|
|
202
210
|
|
|
203
|
-
// Initialize slash commands system with all dependencies including vision analyzer
|
|
204
|
-
this.slashCommands = new SlashCommandsEngine(this.contextEngine, this.projectManager, this.visionAnalyzer);
|
|
211
|
+
// Initialize slash commands system with all dependencies including vision analyzer and todo manager
|
|
212
|
+
this.slashCommands = new SlashCommandsEngine(this.contextEngine, this.projectManager, this.visionAnalyzer, this.todoManager);
|
|
205
213
|
|
|
206
214
|
// Initialize natural language processor with access to all systems
|
|
207
215
|
this.nlProcessor = new NaturalLanguageProcessor(this);
|
|
208
216
|
|
|
209
217
|
// Set up system integrations
|
|
210
|
-
|
|
218
|
+
try {
|
|
219
|
+
await this.setupSystemIntegrations();
|
|
220
|
+
} catch (integrationError) {
|
|
221
|
+
console.log(chalk.yellow(`⚠️ System integrations setup failed: ${integrationError.message}`));
|
|
222
|
+
}
|
|
211
223
|
|
|
212
224
|
// Enable auto model switching based on context
|
|
213
225
|
this.enableIntelligentModelSwitching();
|
|
214
226
|
|
|
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`));
|
|
227
|
+
// Advanced features initialized silently
|
|
220
228
|
|
|
221
229
|
} catch (error) {
|
|
222
230
|
console.log(chalk.yellow(`⚠️ Some advanced features may be limited: ${error.message}`));
|
|
@@ -403,9 +411,9 @@ class InteractiveRecoderChat {
|
|
|
403
411
|
const convIndicator = this.currentConversationId === 'main' ? '' : chalk.gray(`[${currentConv.name}] `);
|
|
404
412
|
|
|
405
413
|
if (this.multiLineMode) {
|
|
406
|
-
this.rl.setPrompt(chalk.
|
|
414
|
+
this.rl.setPrompt(chalk.gray('... '));
|
|
407
415
|
} else {
|
|
408
|
-
this.rl.setPrompt(convIndicator + chalk.
|
|
416
|
+
this.rl.setPrompt(convIndicator + chalk.gray('> '));
|
|
409
417
|
}
|
|
410
418
|
}
|
|
411
419
|
|
|
@@ -793,6 +801,11 @@ class InteractiveRecoderChat {
|
|
|
793
801
|
case 14: // Ctrl+N (New/Clear conversation)
|
|
794
802
|
this.handleCtrlN();
|
|
795
803
|
break;
|
|
804
|
+
|
|
805
|
+
case 27: // ESC (Exit)
|
|
806
|
+
console.log(chalk.yellow('\n👋 Goodbye!'));
|
|
807
|
+
process.exit(0);
|
|
808
|
+
break;
|
|
796
809
|
}
|
|
797
810
|
}
|
|
798
811
|
}
|
|
@@ -978,111 +991,723 @@ class InteractiveRecoderChat {
|
|
|
978
991
|
});
|
|
979
992
|
}
|
|
980
993
|
|
|
981
|
-
|
|
994
|
+
promptForApiKey() {
|
|
982
995
|
console.clear();
|
|
983
|
-
|
|
984
|
-
// Stunning purple/gray/black UI with recoder.xyz branding
|
|
985
|
-
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('╰─────────────────────────────────────────────────────────────────╯'));
|
|
990
|
-
console.log('');
|
|
991
|
-
|
|
992
|
-
// ASCII Art Logo
|
|
993
|
-
console.log(chalk.magenta.bold('██████╗ ███████╗ ██████╗ ██████╗ ██████╗ ███████╗██████╗'));
|
|
994
|
-
console.log(chalk.magenta.bold('██╔══██╗██╔════╝██╔════╝██╔═══██╗██╔══██╗██╔════╝██╔══██╗'));
|
|
995
|
-
console.log(chalk.magenta.bold('██████╔╝█████╗ ██║ ██║ ██║██║ ██║█████╗ ██████╔╝'));
|
|
996
|
-
console.log(chalk.gray('██╔══██╗██╔══╝ ██║ ██║ ██║██║ ██║██╔══╝ ██╔══██╗'));
|
|
997
|
-
console.log(chalk.gray('██║ ██║███████╗╚██████╗╚██████╔╝██████╔╝███████╗██║ ██║'));
|
|
998
|
-
console.log(chalk.gray('╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚═════╝ ╚═════╝ ╚══════╝╚═╝ ╚═╝'));
|
|
999
996
|
console.log('');
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
console.log(chalk.magenta('
|
|
1003
|
-
console.log(chalk.gray('─'.repeat(65)));
|
|
997
|
+
console.log(chalk.magenta('╭─────────────────────────────────────────────────────────────╮'));
|
|
998
|
+
console.log(chalk.magenta('│') + chalk.white.bold(' ✨ Welcome to Recoder Code ') + chalk.magenta('│'));
|
|
999
|
+
console.log(chalk.magenta('╰─────────────────────────────────────────────────────────────╯'));
|
|
1004
1000
|
console.log('');
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
this.showSessionSuggestions();
|
|
1008
|
-
|
|
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'));
|
|
1001
|
+
console.log(chalk.white('🔑 API key required to continue'));
|
|
1002
|
+
console.log(chalk.gray('Get your free API key at: ') + chalk.magenta('https://openrouter.ai'));
|
|
1014
1003
|
console.log('');
|
|
1015
1004
|
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1005
|
+
const keyPrompt = readline.createInterface({
|
|
1006
|
+
input: process.stdin,
|
|
1007
|
+
output: process.stdout
|
|
1008
|
+
});
|
|
1019
1009
|
|
|
1010
|
+
keyPrompt.question(chalk.magenta('Enter your OpenRouter API key: '), (apiKey) => {
|
|
1011
|
+
keyPrompt.close();
|
|
1012
|
+
|
|
1013
|
+
if (!apiKey || apiKey.trim() === '') {
|
|
1014
|
+
console.log(chalk.red('No API key provided. Exiting...'));
|
|
1015
|
+
process.exit(1);
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
// Save the API key to environment
|
|
1019
|
+
process.env.OPENROUTER_API_KEY = apiKey.trim();
|
|
1020
|
+
this.apiKey = apiKey.trim();
|
|
1021
|
+
|
|
1022
|
+
// Continue initialization
|
|
1023
|
+
this.initializeWithApiKey();
|
|
1024
|
+
});
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
initializeReadlineInterface() {
|
|
1028
|
+
// Initialize readline interface with enhanced options
|
|
1029
|
+
this.rl = readline.createInterface({
|
|
1030
|
+
input: process.stdin,
|
|
1031
|
+
output: process.stdout,
|
|
1032
|
+
prompt: chalk.gray('> '),
|
|
1033
|
+
historySize: 100,
|
|
1034
|
+
removeHistoryDuplicates: true,
|
|
1035
|
+
completer: (line) => this.autoComplete(line)
|
|
1036
|
+
});
|
|
1037
|
+
|
|
1038
|
+
// Set up readline event handlers
|
|
1039
|
+
this.setupReadlineHandlers();
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
setupReadlineHandlers() {
|
|
1043
|
+
if (!this.rl) return;
|
|
1044
|
+
|
|
1045
|
+
this.rl.on('line', async (input) => {
|
|
1046
|
+
await this.handleUserInput(input.trim());
|
|
1047
|
+
});
|
|
1048
|
+
|
|
1049
|
+
this.rl.on('close', () => {
|
|
1050
|
+
console.log('\nGoodbye!');
|
|
1051
|
+
process.exit(0);
|
|
1052
|
+
});
|
|
1053
|
+
|
|
1054
|
+
// Handle advanced keyboard shortcuts
|
|
1055
|
+
process.stdin.on('keypress', (str, key) => {
|
|
1056
|
+
if (key && key.ctrl) {
|
|
1057
|
+
this.handleCtrlKeys(key);
|
|
1058
|
+
}
|
|
1059
|
+
});
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
initializeWithApiKey() {
|
|
1063
|
+
// Initialize OpenAI client with OpenRouter configuration
|
|
1064
|
+
this.openai = new OpenAI({
|
|
1065
|
+
baseURL: config.baseURL,
|
|
1066
|
+
apiKey: this.apiKey,
|
|
1067
|
+
defaultHeaders: {
|
|
1068
|
+
'HTTP-Referer': config.siteUrl,
|
|
1069
|
+
'X-Title': config.siteName,
|
|
1070
|
+
},
|
|
1071
|
+
});
|
|
1072
|
+
|
|
1073
|
+
// If readline interface wasn't already initialized, do it now
|
|
1074
|
+
if (!this.rl) {
|
|
1075
|
+
this.initializeReadlineInterface();
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
// Set up custom prompt and keyboard shortcuts
|
|
1079
|
+
this.updatePrompt();
|
|
1080
|
+
this.setupKeyboardShortcuts();
|
|
1081
|
+
|
|
1082
|
+
this.setupEventHandlers();
|
|
1083
|
+
this.initializeFileWatcher();
|
|
1084
|
+
this.analyzeProjectContext().catch(error => {
|
|
1085
|
+
console.log(chalk.yellow(`⚠️ Project context analysis failed: ${error.message}`));
|
|
1086
|
+
});
|
|
1087
|
+
this.loadPlugins().catch(error => {
|
|
1088
|
+
console.log(chalk.yellow(`⚠️ Plugin loading failed: ${error.message}`));
|
|
1089
|
+
});
|
|
1090
|
+
this.displayWelcome();
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
updatePrompt() {
|
|
1094
|
+
// Neon purple prompt with white text
|
|
1095
|
+
if (this.rl) {
|
|
1096
|
+
this.rl.setPrompt(chalk.magenta('> '));
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
setupEventHandlers() {
|
|
1101
|
+
// Set up readline event handlers for user input
|
|
1102
|
+
this.rl.on('line', async (input) => {
|
|
1103
|
+
await this.handleUserInput(input.trim());
|
|
1104
|
+
});
|
|
1105
|
+
|
|
1106
|
+
this.rl.on('close', () => {
|
|
1107
|
+
console.log('\nGoodbye!');
|
|
1108
|
+
process.exit(0);
|
|
1109
|
+
});
|
|
1110
|
+
|
|
1111
|
+
// Handle advanced keyboard shortcuts
|
|
1112
|
+
process.stdin.on('keypress', (str, key) => {
|
|
1113
|
+
if (key) {
|
|
1114
|
+
this.handleKeyboardShortcuts(key);
|
|
1115
|
+
}
|
|
1116
|
+
});
|
|
1117
|
+
|
|
1118
|
+
// Start the prompt
|
|
1020
1119
|
this.rl.prompt();
|
|
1021
1120
|
}
|
|
1022
1121
|
|
|
1023
|
-
|
|
1122
|
+
async handleUserInput(input) {
|
|
1123
|
+
if (!input) {
|
|
1124
|
+
this.rl.prompt();
|
|
1125
|
+
return;
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
// Handle slash commands like Claude Code
|
|
1129
|
+
if (input.startsWith('/')) {
|
|
1130
|
+
this.handleSlashCommand(input);
|
|
1131
|
+
return;
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1024
1134
|
try {
|
|
1025
|
-
//
|
|
1026
|
-
const
|
|
1135
|
+
// Track performance metrics
|
|
1136
|
+
const startTime = Date.now();
|
|
1137
|
+
let firstChunkTime = null;
|
|
1138
|
+
let totalChunks = 0;
|
|
1027
1139
|
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
console.log(chalk.gray(` • Todos: ${projectStatus.todos.pending} pending, ${projectStatus.todos.completed} completed`));
|
|
1031
|
-
}
|
|
1140
|
+
// Start progressive loading with enhanced animations
|
|
1141
|
+
this.startProgressiveLoading();
|
|
1032
1142
|
|
|
1033
|
-
//
|
|
1034
|
-
const
|
|
1143
|
+
// Create a RecoderCode instance to process the conversation
|
|
1144
|
+
const { RecoderCode } = require('./run.js');
|
|
1145
|
+
const recoder = new RecoderCode();
|
|
1035
1146
|
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1147
|
+
// Initialize streaming response system
|
|
1148
|
+
const streamBuffer = {
|
|
1149
|
+
content: '',
|
|
1150
|
+
chunks: [],
|
|
1151
|
+
isStreaming: false,
|
|
1152
|
+
streamingStarted: false
|
|
1153
|
+
};
|
|
1154
|
+
|
|
1155
|
+
// Enhanced streaming response handler
|
|
1156
|
+
const response = await this.processWithAdvancedStreaming({
|
|
1157
|
+
recoder,
|
|
1158
|
+
input,
|
|
1159
|
+
startTime,
|
|
1160
|
+
streamBuffer,
|
|
1161
|
+
onFirstChunk: (time) => { firstChunkTime = time; },
|
|
1162
|
+
onChunk: (chunk) => {
|
|
1163
|
+
totalChunks++;
|
|
1164
|
+
this.displayStreamingChunk(chunk, totalChunks);
|
|
1043
1165
|
}
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1166
|
+
});
|
|
1167
|
+
|
|
1168
|
+
// Calculate comprehensive metrics
|
|
1169
|
+
const totalDuration = Math.round((Date.now() - startTime) / 1000);
|
|
1170
|
+
const timeToFirstChunk = firstChunkTime ? Math.round((firstChunkTime - startTime) / 1000) : null;
|
|
1171
|
+
|
|
1172
|
+
// Track conversation history with enhanced metadata
|
|
1173
|
+
this.conversationHistory.push({
|
|
1174
|
+
timestamp: new Date(),
|
|
1175
|
+
input: input,
|
|
1176
|
+
response: response,
|
|
1177
|
+
duration: totalDuration,
|
|
1178
|
+
timeToFirstChunk,
|
|
1179
|
+
totalChunks,
|
|
1180
|
+
streamingUsed: streamBuffer.isStreaming
|
|
1181
|
+
});
|
|
1182
|
+
this.currentSession.messageCount++;
|
|
1183
|
+
this.currentSession.lastActivity = Date.now();
|
|
1184
|
+
|
|
1185
|
+
console.log('');
|
|
1186
|
+
|
|
1187
|
+
// Show performance metrics and tool usage
|
|
1188
|
+
this.showAdvancedMetrics({
|
|
1189
|
+
duration: totalDuration,
|
|
1190
|
+
timeToFirstChunk,
|
|
1191
|
+
totalChunks,
|
|
1192
|
+
streamingUsed: streamBuffer.isStreaming
|
|
1193
|
+
});
|
|
1194
|
+
|
|
1195
|
+
// Enhanced response display if not already streamed
|
|
1196
|
+
if (!streamBuffer.streamingStarted) {
|
|
1197
|
+
await this.displayAdvancedTypingResponse(response, startTime);
|
|
1047
1198
|
} else {
|
|
1048
|
-
//
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
if (recentSessions.length > 0) {
|
|
1052
|
-
console.log(chalk.magenta('🕒 ') + chalk.white.bold('Recent Sessions:'));
|
|
1053
|
-
recentSessions.forEach((session, index) => {
|
|
1054
|
-
const timeAgo = this.getTimeAgo(new Date(session.modified));
|
|
1055
|
-
console.log(chalk.gray(` • ${session.id} - ${timeAgo} (${session.messageCount} messages)`));
|
|
1056
|
-
});
|
|
1057
|
-
console.log(chalk.gray(' 💡 Use ') + chalk.cyan('/resume <id>') + chalk.gray(' to continue a session'));
|
|
1058
|
-
console.log('');
|
|
1059
|
-
}
|
|
1199
|
+
// Finalize streaming display
|
|
1200
|
+
this.finalizeStreamingResponse(response, totalDuration);
|
|
1060
1201
|
}
|
|
1061
1202
|
|
|
1203
|
+
console.log('');
|
|
1204
|
+
|
|
1062
1205
|
} catch (error) {
|
|
1063
|
-
|
|
1206
|
+
this.stopProgressiveLoading();
|
|
1207
|
+
this.stopStreamingDisplay();
|
|
1208
|
+
console.log('');
|
|
1209
|
+
this.showEnhancedError(error);
|
|
1210
|
+
console.log('');
|
|
1064
1211
|
}
|
|
1212
|
+
|
|
1213
|
+
this.rl.prompt();
|
|
1065
1214
|
}
|
|
1066
1215
|
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
const seconds = Math.floor((now - date) / 1000);
|
|
1216
|
+
displayWelcome() {
|
|
1217
|
+
console.clear();
|
|
1070
1218
|
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1219
|
+
// Enhanced welcome screen with gradient effects and animations
|
|
1220
|
+
console.log('');
|
|
1221
|
+
|
|
1222
|
+
// Animated gradient border
|
|
1223
|
+
this.displayAnimatedBorder();
|
|
1224
|
+
|
|
1225
|
+
// Beautiful gradient ASCII art
|
|
1226
|
+
console.log('');
|
|
1227
|
+
this.displayGradientLogo();
|
|
1228
|
+
console.log('');
|
|
1229
|
+
|
|
1230
|
+
// Dynamic status information
|
|
1231
|
+
this.displayDynamicStatus();
|
|
1232
|
+
console.log('');
|
|
1233
|
+
|
|
1234
|
+
// Enhanced status hints with themes
|
|
1235
|
+
this.showEnhancedStatusHints();
|
|
1236
|
+
|
|
1237
|
+
this.rl.prompt();
|
|
1075
1238
|
}
|
|
1076
1239
|
|
|
1077
|
-
|
|
1078
|
-
const [
|
|
1079
|
-
const
|
|
1240
|
+
displayAnimatedBorder() {
|
|
1241
|
+
const colors = ['magenta', 'cyan', 'blue', 'magenta'];
|
|
1242
|
+
const border = '╭─────────────────────────────────────────────────────────────╮';
|
|
1243
|
+
const title = '│ ✨ Welcome to Recoder Code - AI Terminal │';
|
|
1244
|
+
const bottom = '╰─────────────────────────────────────────────────────────────╯';
|
|
1080
1245
|
|
|
1081
|
-
|
|
1082
|
-
|
|
1246
|
+
console.log(chalk[colors[0]](border));
|
|
1247
|
+
console.log(chalk[colors[1]](title));
|
|
1248
|
+
console.log(chalk[colors[2]](bottom));
|
|
1249
|
+
}
|
|
1250
|
+
|
|
1251
|
+
displayGradientLogo() {
|
|
1252
|
+
// Beautiful gradient ASCII art with purple to cyan transition
|
|
1253
|
+
const lines = [
|
|
1254
|
+
'██████╗ ███████╗ ██████╗ ██████╗ ██████╗ ███████╗██████╗',
|
|
1255
|
+
'██╔══██╗██╔════╝██╔════╝██╔═══██╗██╔══██╗██╔════╝██╔══██╗',
|
|
1256
|
+
'██████╔╝█████╗ ██║ ██║ ██║██║ ██║█████╗ ██████╔╝',
|
|
1257
|
+
'██╔══██╗██╔══╝ ██║ ██║ ██║██║ ██║██╔══╝ ██╔══██╗',
|
|
1258
|
+
'██║ ██║███████╗╚██████╗╚██████╔╝██████╔╝███████╗██║ ██║',
|
|
1259
|
+
'╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚═════╝ ╚═════╝ ╚══════╝╚═╝ ╚═╝'
|
|
1260
|
+
];
|
|
1083
1261
|
|
|
1262
|
+
const gradientColors = ['magenta', 'magenta', 'cyan', 'blue', 'blue', 'gray'];
|
|
1263
|
+
|
|
1264
|
+
lines.forEach((line, index) => {
|
|
1265
|
+
console.log(chalk[gradientColors[index]].bold(line));
|
|
1266
|
+
});
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1269
|
+
displayDynamicStatus() {
|
|
1270
|
+
const currentTime = new Date().toLocaleTimeString();
|
|
1271
|
+
const projectContext = this.getCurrentProjectContext();
|
|
1272
|
+
|
|
1273
|
+
console.log(chalk.gray('┌─ Session Info ─────────────────────────────────────────────┐'));
|
|
1274
|
+
console.log(chalk.gray('│') + chalk.white(` 🕐 Started: ${currentTime}`) +
|
|
1275
|
+
chalk.gray(' │'));
|
|
1276
|
+
console.log(chalk.gray('│') + chalk.cyan(` 📁 Project: ${projectContext.name || 'recoder-code'}`) +
|
|
1277
|
+
chalk.gray(' │'));
|
|
1278
|
+
console.log(chalk.gray('│') + chalk.green(` 🤖 Model: ${this.model || 'claude-sonnet-4'}`) +
|
|
1279
|
+
chalk.gray(' │'));
|
|
1280
|
+
console.log(chalk.gray('└────────────────────────────────────────────────────────────┘'));
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1283
|
+
getCurrentProjectContext() {
|
|
1084
1284
|
try {
|
|
1085
|
-
|
|
1285
|
+
const packageJson = require(process.cwd() + '/package.json');
|
|
1286
|
+
return {
|
|
1287
|
+
name: packageJson.name || 'Unknown Project',
|
|
1288
|
+
version: packageJson.version || '1.0.0'
|
|
1289
|
+
};
|
|
1290
|
+
} catch (error) {
|
|
1291
|
+
return { name: 'recoder-code', version: '2.0.0' };
|
|
1292
|
+
}
|
|
1293
|
+
}
|
|
1294
|
+
|
|
1295
|
+
showEnhancedStatusHints() {
|
|
1296
|
+
console.log(chalk.gray('┌─ Quick Commands ───────────────────────────────────────────┐'));
|
|
1297
|
+
console.log(chalk.gray('│') + chalk.yellow(' ! ') + chalk.white('bash mode') +
|
|
1298
|
+
chalk.gray(' • ') + chalk.yellow('/ ') + chalk.white('commands') +
|
|
1299
|
+
chalk.gray(' • ') + chalk.yellow('tab ') + chalk.white('autocomplete') +
|
|
1300
|
+
chalk.gray(' ') + chalk.red('ESC ') + chalk.white('exit') +
|
|
1301
|
+
chalk.gray(' │'));
|
|
1302
|
+
console.log(chalk.gray('└────────────────────────────────────────────────────────────┘'));
|
|
1303
|
+
|
|
1304
|
+
console.log(chalk.gray('┌─ Features ─────────────────────────────────────────────────┐'));
|
|
1305
|
+
console.log(chalk.gray('│') + chalk.magenta(' 📁 ') + chalk.white(this.getCurrentProjectContext().name) +
|
|
1306
|
+
chalk.gray(' • ') + chalk.green('✨ ') + chalk.white('AI Ready') +
|
|
1307
|
+
chalk.gray(' • ') + chalk.cyan('Ctrl+S ') + chalk.white('save') +
|
|
1308
|
+
chalk.gray(' • ') + chalk.blue('Ctrl+H ') + chalk.white('help') +
|
|
1309
|
+
chalk.gray(' │'));
|
|
1310
|
+
console.log(chalk.gray('└────────────────────────────────────────────────────────────┘'));
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1313
|
+
showError(error) {
|
|
1314
|
+
// Beautiful error display like Claude Code
|
|
1315
|
+
console.log(chalk.red('╭─────────────────────────────────────────╮'));
|
|
1316
|
+
console.log(chalk.red('│') + chalk.white.bold(' ❌ Error ') + chalk.red('│'));
|
|
1317
|
+
console.log(chalk.red('╰─────────────────────────────────────────╯'));
|
|
1318
|
+
console.log('');
|
|
1319
|
+
console.log(chalk.white(error.message));
|
|
1320
|
+
console.log('');
|
|
1321
|
+
|
|
1322
|
+
// Show helpful suggestions based on error type
|
|
1323
|
+
if (error.message.includes('429')) {
|
|
1324
|
+
console.log(chalk.gray('💡 Rate limit reached. Try again in a few seconds.'));
|
|
1325
|
+
} else if (error.message.includes('API key')) {
|
|
1326
|
+
console.log(chalk.gray('💡 Check your API key with /status command.'));
|
|
1327
|
+
} else if (error.message.includes('timeout')) {
|
|
1328
|
+
console.log(chalk.gray('💡 Request timed out. Try a simpler query.'));
|
|
1329
|
+
} else {
|
|
1330
|
+
console.log(chalk.gray('💡 Type /help for available commands.'));
|
|
1331
|
+
}
|
|
1332
|
+
}
|
|
1333
|
+
|
|
1334
|
+
showStatusHints() {
|
|
1335
|
+
// Show helpful hints at bottom like Claude Code with project context
|
|
1336
|
+
const projectName = path.basename(process.cwd());
|
|
1337
|
+
console.log(chalk.gray(`! for bash mode • / for commands • tab to autocomplete ESC to exit`));
|
|
1338
|
+
console.log(chalk.gray(`📁 ${projectName} • ✨ Recoder Code ready • Ctrl+S save • Ctrl+H help`));
|
|
1339
|
+
}
|
|
1340
|
+
|
|
1341
|
+
startProcessingAnimation() {
|
|
1342
|
+
const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
1343
|
+
let frameIndex = 0;
|
|
1344
|
+
|
|
1345
|
+
this.processingInterval = setInterval(() => {
|
|
1346
|
+
const frame = frames[frameIndex % frames.length];
|
|
1347
|
+
process.stdout.write(`\r${chalk.magenta(frame)} ${chalk.gray('Processing...')}`);
|
|
1348
|
+
frameIndex++;
|
|
1349
|
+
}, 80);
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1352
|
+
stopProcessingAnimation() {
|
|
1353
|
+
if (this.processingInterval) {
|
|
1354
|
+
clearInterval(this.processingInterval);
|
|
1355
|
+
this.processingInterval = null;
|
|
1356
|
+
process.stdout.write('\r'); // Clear the line
|
|
1357
|
+
}
|
|
1358
|
+
}
|
|
1359
|
+
|
|
1360
|
+
showToolUsage() {
|
|
1361
|
+
// Show tool usage indicators like Claude Code
|
|
1362
|
+
const tools = ['Read', 'Analyze', 'Generate'];
|
|
1363
|
+
tools.forEach(tool => {
|
|
1364
|
+
console.log(chalk.gray('● ') + chalk.white(`${tool}(project_structure)...`));
|
|
1365
|
+
});
|
|
1366
|
+
}
|
|
1367
|
+
|
|
1368
|
+
formatResponse(response) {
|
|
1369
|
+
// Format response with rich styling like Claude Code
|
|
1370
|
+
if (!response) return '';
|
|
1371
|
+
|
|
1372
|
+
// Add bullet points and styling
|
|
1373
|
+
const formatted = response
|
|
1374
|
+
.split('\n')
|
|
1375
|
+
.map(line => {
|
|
1376
|
+
if (line.trim().startsWith('-') || line.trim().startsWith('*')) {
|
|
1377
|
+
return chalk.gray('● ') + chalk.white(line.replace(/^[\s\-\*]+/, ''));
|
|
1378
|
+
}
|
|
1379
|
+
return chalk.white(line);
|
|
1380
|
+
})
|
|
1381
|
+
.join('\n');
|
|
1382
|
+
|
|
1383
|
+
return formatted;
|
|
1384
|
+
}
|
|
1385
|
+
|
|
1386
|
+
async typeResponse(text) {
|
|
1387
|
+
// Type out response character by character like Claude Code
|
|
1388
|
+
const lines = text.split('\n');
|
|
1389
|
+
for (const line of lines) {
|
|
1390
|
+
for (let i = 0; i < line.length; i++) {
|
|
1391
|
+
process.stdout.write(line[i]);
|
|
1392
|
+
await new Promise(resolve => setTimeout(resolve, 15)); // 15ms per character
|
|
1393
|
+
}
|
|
1394
|
+
console.log(''); // New line after each line
|
|
1395
|
+
}
|
|
1396
|
+
}
|
|
1397
|
+
|
|
1398
|
+
handleSlashCommand(input) {
|
|
1399
|
+
const command = input.slice(1).toLowerCase();
|
|
1400
|
+
|
|
1401
|
+
switch (command) {
|
|
1402
|
+
case 'help':
|
|
1403
|
+
this.showHelp();
|
|
1404
|
+
break;
|
|
1405
|
+
case 'clear':
|
|
1406
|
+
console.clear();
|
|
1407
|
+
this.displayWelcome();
|
|
1408
|
+
return; // Don't show prompt again
|
|
1409
|
+
case 'exit':
|
|
1410
|
+
case 'quit':
|
|
1411
|
+
console.log(chalk.magenta('\nGoodbye! ✨'));
|
|
1412
|
+
process.exit(0);
|
|
1413
|
+
break;
|
|
1414
|
+
case 'status':
|
|
1415
|
+
this.showStatus();
|
|
1416
|
+
break;
|
|
1417
|
+
case 'history':
|
|
1418
|
+
this.showHistory();
|
|
1419
|
+
break;
|
|
1420
|
+
case 'session':
|
|
1421
|
+
this.showSession();
|
|
1422
|
+
break;
|
|
1423
|
+
case 'watch':
|
|
1424
|
+
this.showWatchedFiles();
|
|
1425
|
+
break;
|
|
1426
|
+
case 'save':
|
|
1427
|
+
this.saveCurrentSession();
|
|
1428
|
+
break;
|
|
1429
|
+
case 'analyze':
|
|
1430
|
+
this.performCodeAnalysis();
|
|
1431
|
+
break;
|
|
1432
|
+
case 'git':
|
|
1433
|
+
this.showGitStatus();
|
|
1434
|
+
break;
|
|
1435
|
+
case 'review':
|
|
1436
|
+
this.performCodeReview();
|
|
1437
|
+
break;
|
|
1438
|
+
case 'optimize':
|
|
1439
|
+
this.performOptimizationAnalysis();
|
|
1440
|
+
break;
|
|
1441
|
+
case 'security':
|
|
1442
|
+
this.performSecurityAnalysis();
|
|
1443
|
+
break;
|
|
1444
|
+
case 'theme':
|
|
1445
|
+
this.cycleTheme();
|
|
1446
|
+
break;
|
|
1447
|
+
default:
|
|
1448
|
+
console.log(chalk.red(`Unknown command: ${input}`));
|
|
1449
|
+
console.log(chalk.gray('Type /help for available commands'));
|
|
1450
|
+
}
|
|
1451
|
+
|
|
1452
|
+
this.rl.prompt();
|
|
1453
|
+
}
|
|
1454
|
+
|
|
1455
|
+
showHelp() {
|
|
1456
|
+
console.log('');
|
|
1457
|
+
console.log(chalk.magenta('╭─────────────────────────────────────────╮'));
|
|
1458
|
+
console.log(chalk.magenta('│') + chalk.white.bold(' 🚀 Available Commands ') + chalk.magenta('│'));
|
|
1459
|
+
console.log(chalk.magenta('╰─────────────────────────────────────────╯'));
|
|
1460
|
+
console.log('');
|
|
1461
|
+
|
|
1462
|
+
console.log(chalk.cyan('📋 Basic Commands:'));
|
|
1463
|
+
console.log(chalk.gray(' /help ') + chalk.white('Show this help message'));
|
|
1464
|
+
console.log(chalk.gray(' /clear ') + chalk.white('Clear the screen'));
|
|
1465
|
+
console.log(chalk.gray(' /status ') + chalk.white('Show system status'));
|
|
1466
|
+
console.log(chalk.gray(' /exit ') + chalk.white('Exit the terminal'));
|
|
1467
|
+
console.log('');
|
|
1468
|
+
|
|
1469
|
+
console.log(chalk.green('🔍 Analysis Commands:'));
|
|
1470
|
+
console.log(chalk.gray(' /analyze ') + chalk.white('Perform intelligent code analysis'));
|
|
1471
|
+
console.log(chalk.gray(' /review ') + chalk.white('AI-powered code review'));
|
|
1472
|
+
console.log(chalk.gray(' /security') + chalk.white('Security vulnerability analysis'));
|
|
1473
|
+
console.log(chalk.gray(' /optimize') + chalk.white('Performance optimization suggestions'));
|
|
1474
|
+
console.log('');
|
|
1475
|
+
|
|
1476
|
+
console.log(chalk.blue('🌿 Git Commands:'));
|
|
1477
|
+
console.log(chalk.gray(' /git ') + chalk.white('Show git repository status'));
|
|
1478
|
+
console.log('');
|
|
1479
|
+
|
|
1480
|
+
console.log(chalk.magenta('🎨 Customization:'));
|
|
1481
|
+
console.log(chalk.gray(' /theme ') + chalk.white('Cycle through visual themes'));
|
|
1482
|
+
console.log('');
|
|
1483
|
+
|
|
1484
|
+
console.log(chalk.yellow('💾 Session Management:'));
|
|
1485
|
+
console.log(chalk.gray(' /history ') + chalk.white('Show conversation history'));
|
|
1486
|
+
console.log(chalk.gray(' /session ') + chalk.white('Show current session info'));
|
|
1487
|
+
console.log(chalk.gray(' /save ') + chalk.white('Save current session'));
|
|
1488
|
+
console.log(chalk.gray(' /watch ') + chalk.white('Show watched files'));
|
|
1489
|
+
console.log('');
|
|
1490
|
+
}
|
|
1491
|
+
|
|
1492
|
+
showStatus() {
|
|
1493
|
+
console.log('');
|
|
1494
|
+
console.log(chalk.magenta('╭─────────────────────────────────────────╮'));
|
|
1495
|
+
console.log(chalk.magenta('│') + chalk.white.bold(' System Status ') + chalk.magenta('│'));
|
|
1496
|
+
console.log(chalk.magenta('╰─────────────────────────────────────────╯'));
|
|
1497
|
+
console.log('');
|
|
1498
|
+
console.log(chalk.gray('Model: ') + chalk.white(this.model || 'Default'));
|
|
1499
|
+
console.log(chalk.gray('Session: ') + chalk.white(this.sessionId.slice(0, 8) + '...'));
|
|
1500
|
+
console.log(chalk.gray('Directory:') + chalk.white(process.cwd().replace(process.env.HOME, '~')));
|
|
1501
|
+
console.log('');
|
|
1502
|
+
}
|
|
1503
|
+
|
|
1504
|
+
showHistory() {
|
|
1505
|
+
console.log('');
|
|
1506
|
+
console.log(chalk.magenta('╭─────────────────────────────────────────╮'));
|
|
1507
|
+
console.log(chalk.magenta('│') + chalk.white.bold(' Conversation History ') + chalk.magenta('│'));
|
|
1508
|
+
console.log(chalk.magenta('╰─────────────────────────────────────────╯'));
|
|
1509
|
+
console.log('');
|
|
1510
|
+
|
|
1511
|
+
if (this.conversationHistory.length === 0) {
|
|
1512
|
+
console.log(chalk.gray('No conversation history yet.'));
|
|
1513
|
+
} else {
|
|
1514
|
+
const recent = this.conversationHistory.slice(-5); // Show last 5 exchanges
|
|
1515
|
+
recent.forEach((entry, index) => {
|
|
1516
|
+
const time = entry.timestamp.toLocaleTimeString();
|
|
1517
|
+
console.log(chalk.gray(`[${time}] `) + chalk.white(entry.input.slice(0, 50) + '...'));
|
|
1518
|
+
console.log(chalk.gray(` → Response in ${entry.duration}s`));
|
|
1519
|
+
if (index < recent.length - 1) console.log('');
|
|
1520
|
+
});
|
|
1521
|
+
}
|
|
1522
|
+
console.log('');
|
|
1523
|
+
}
|
|
1524
|
+
|
|
1525
|
+
showSession() {
|
|
1526
|
+
console.log('');
|
|
1527
|
+
console.log(chalk.magenta('╭─────────────────────────────────────────╮'));
|
|
1528
|
+
console.log(chalk.magenta('│') + chalk.white.bold(' Current Session ') + chalk.magenta('│'));
|
|
1529
|
+
console.log(chalk.magenta('╰─────────────────────────────────────────╯'));
|
|
1530
|
+
console.log('');
|
|
1531
|
+
|
|
1532
|
+
const sessionDuration = Math.round((Date.now() - this.currentSession.startTime) / 1000 / 60);
|
|
1533
|
+
const lastActivity = Math.round((Date.now() - this.currentSession.lastActivity) / 1000);
|
|
1534
|
+
|
|
1535
|
+
console.log(chalk.gray('Session ID: ') + chalk.white(this.sessionId.slice(0, 8) + '...'));
|
|
1536
|
+
console.log(chalk.gray('Duration: ') + chalk.white(`${sessionDuration} minutes`));
|
|
1537
|
+
console.log(chalk.gray('Messages: ') + chalk.white(this.currentSession.messageCount));
|
|
1538
|
+
console.log(chalk.gray('Last Activity: ') + chalk.white(`${lastActivity}s ago`));
|
|
1539
|
+
console.log(chalk.gray('Model: ') + chalk.white(this.model || 'Default'));
|
|
1540
|
+
console.log('');
|
|
1541
|
+
}
|
|
1542
|
+
|
|
1543
|
+
smartAutoComplete(line) {
|
|
1544
|
+
// Smart autocomplete for commands and context-aware suggestions
|
|
1545
|
+
const completions = [];
|
|
1546
|
+
|
|
1547
|
+
if (line.startsWith('/')) {
|
|
1548
|
+
// Slash command completions
|
|
1549
|
+
const commands = ['help', 'clear', 'status', 'history', 'session', 'exit'];
|
|
1550
|
+
const matches = commands.filter(cmd => cmd.startsWith(line.slice(1)));
|
|
1551
|
+
completions.push(...matches.map(cmd => '/' + cmd));
|
|
1552
|
+
} else if (line.startsWith('!')) {
|
|
1553
|
+
// Bash command completions
|
|
1554
|
+
const bashCommands = ['ls', 'cd', 'pwd', 'cat', 'grep', 'find', 'git', 'npm', 'node'];
|
|
1555
|
+
const matches = bashCommands.filter(cmd => cmd.startsWith(line.slice(1)));
|
|
1556
|
+
completions.push(...matches.map(cmd => '!' + cmd));
|
|
1557
|
+
} else {
|
|
1558
|
+
// Context-aware suggestions based on project
|
|
1559
|
+
const suggestions = [
|
|
1560
|
+
'explain this project structure',
|
|
1561
|
+
'help me debug this code',
|
|
1562
|
+
'create a README file',
|
|
1563
|
+
'optimize this function',
|
|
1564
|
+
'add error handling',
|
|
1565
|
+
'write unit tests',
|
|
1566
|
+
'refactor this code'
|
|
1567
|
+
];
|
|
1568
|
+
const matches = suggestions.filter(s => s.toLowerCase().includes(line.toLowerCase()));
|
|
1569
|
+
completions.push(...matches);
|
|
1570
|
+
}
|
|
1571
|
+
|
|
1572
|
+
return [completions, line];
|
|
1573
|
+
}
|
|
1574
|
+
|
|
1575
|
+
showWatchedFiles() {
|
|
1576
|
+
console.log('');
|
|
1577
|
+
console.log(chalk.magenta('╭─────────────────────────────────────────╮'));
|
|
1578
|
+
console.log(chalk.magenta('│') + chalk.white.bold(' Watched Files ') + chalk.magenta('│'));
|
|
1579
|
+
console.log(chalk.magenta('╰─────────────────────────────────────────╯'));
|
|
1580
|
+
console.log('');
|
|
1581
|
+
|
|
1582
|
+
if (this.watchedFiles && this.watchedFiles.size > 0) {
|
|
1583
|
+
this.watchedFiles.forEach(file => {
|
|
1584
|
+
console.log(chalk.gray('📁 ') + chalk.white(file));
|
|
1585
|
+
});
|
|
1586
|
+
} else {
|
|
1587
|
+
console.log(chalk.gray('No files being watched.'));
|
|
1588
|
+
console.log(chalk.gray('Files will be automatically watched during conversations.'));
|
|
1589
|
+
}
|
|
1590
|
+
console.log('');
|
|
1591
|
+
}
|
|
1592
|
+
|
|
1593
|
+
saveCurrentSession() {
|
|
1594
|
+
console.log('');
|
|
1595
|
+
console.log(chalk.magenta('╭─────────────────────────────────────────╮'));
|
|
1596
|
+
console.log(chalk.magenta('│') + chalk.white.bold(' Session Saved ') + chalk.magenta('│'));
|
|
1597
|
+
console.log(chalk.magenta('╰─────────────────────────────────────────╯'));
|
|
1598
|
+
console.log('');
|
|
1599
|
+
|
|
1600
|
+
const sessionData = {
|
|
1601
|
+
id: this.sessionId,
|
|
1602
|
+
startTime: this.currentSession.startTime,
|
|
1603
|
+
history: this.conversationHistory,
|
|
1604
|
+
messageCount: this.currentSession.messageCount,
|
|
1605
|
+
lastActivity: this.currentSession.lastActivity,
|
|
1606
|
+
savedAt: Date.now()
|
|
1607
|
+
};
|
|
1608
|
+
|
|
1609
|
+
try {
|
|
1610
|
+
const sessionsDir = path.join(process.cwd(), 'sessions');
|
|
1611
|
+
if (!fs.existsSync(sessionsDir)) {
|
|
1612
|
+
fs.mkdirSync(sessionsDir, { recursive: true });
|
|
1613
|
+
}
|
|
1614
|
+
|
|
1615
|
+
const filename = `session-${this.sessionId}.json`;
|
|
1616
|
+
const filepath = path.join(sessionsDir, filename);
|
|
1617
|
+
fs.writeFileSync(filepath, JSON.stringify(sessionData, null, 2));
|
|
1618
|
+
|
|
1619
|
+
console.log(chalk.white(`Session saved to: ${filename}`));
|
|
1620
|
+
console.log(chalk.gray(`${this.conversationHistory.length} messages preserved`));
|
|
1621
|
+
} catch (error) {
|
|
1622
|
+
console.log(chalk.red(`Failed to save session: ${error.message}`));
|
|
1623
|
+
}
|
|
1624
|
+
console.log('');
|
|
1625
|
+
}
|
|
1626
|
+
|
|
1627
|
+
handleKeyboardShortcuts(key) {
|
|
1628
|
+
// Advanced keyboard shortcuts like in modern IDEs
|
|
1629
|
+
if (key.name === 'escape') {
|
|
1630
|
+
console.log(chalk.magenta('\n✨ Goodbye!'));
|
|
1631
|
+
process.exit(0);
|
|
1632
|
+
} else if (key.ctrl && key.name === 'l') {
|
|
1633
|
+
// Ctrl+L: Clear and restart
|
|
1634
|
+
console.clear();
|
|
1635
|
+
this.displayWelcome();
|
|
1636
|
+
} else if (key.ctrl && key.name === 's') {
|
|
1637
|
+
// Ctrl+S: Quick save session
|
|
1638
|
+
this.rl.write('\r/save\r');
|
|
1639
|
+
} else if (key.ctrl && key.name === 'h') {
|
|
1640
|
+
// Ctrl+H: Show help
|
|
1641
|
+
this.rl.write('\r/help\r');
|
|
1642
|
+
} else if (key.ctrl && key.name === 'u') {
|
|
1643
|
+
// Ctrl+U: Show status
|
|
1644
|
+
this.rl.write('\r/status\r');
|
|
1645
|
+
}
|
|
1646
|
+
}
|
|
1647
|
+
|
|
1648
|
+
showSessionSuggestions() {
|
|
1649
|
+
try {
|
|
1650
|
+
// Check if project exists
|
|
1651
|
+
const projectStatus = this.projectManager.getProjectStatus();
|
|
1652
|
+
|
|
1653
|
+
if (projectStatus.exists) {
|
|
1654
|
+
console.log(chalk.magenta('📂 ') + chalk.white.bold(`Project: ${projectStatus.context.name}`));
|
|
1655
|
+
console.log(chalk.gray(` • Todos: ${projectStatus.todos.pending} pending, ${projectStatus.todos.completed} completed`));
|
|
1656
|
+
}
|
|
1657
|
+
|
|
1658
|
+
// Check for intelligent auto-resume candidate
|
|
1659
|
+
const resumeCandidate = this.projectManager.shouldOfferAutoResume();
|
|
1660
|
+
|
|
1661
|
+
if (resumeCandidate) {
|
|
1662
|
+
console.log(chalk.magenta('🔄 ') + chalk.white.bold('Resume Previous Session?'));
|
|
1663
|
+
console.log(chalk.gray(` • Session: ${resumeCandidate.id} (${resumeCandidate.activityDescription})`));
|
|
1664
|
+
console.log(chalk.gray(` • Messages: ${resumeCandidate.messageCount} | Model: ${resumeCandidate.model}`));
|
|
1665
|
+
|
|
1666
|
+
if (resumeCandidate.lastUserMessage) {
|
|
1667
|
+
console.log(chalk.gray(` • Last: "${resumeCandidate.lastUserMessage}"`));
|
|
1668
|
+
}
|
|
1669
|
+
|
|
1670
|
+
console.log(chalk.gray(' 💡 Type ') + chalk.cyan('/resume') + chalk.gray(' to continue or start fresh'));
|
|
1671
|
+
console.log('');
|
|
1672
|
+
} else {
|
|
1673
|
+
// Get recent sessions if no auto-resume candidate
|
|
1674
|
+
const recentSessions = this.projectManager.listSessions({ limit: 3 });
|
|
1675
|
+
|
|
1676
|
+
if (recentSessions.length > 0) {
|
|
1677
|
+
console.log(chalk.magenta('🕒 ') + chalk.white.bold('Recent Sessions:'));
|
|
1678
|
+
recentSessions.forEach((session, index) => {
|
|
1679
|
+
const timeAgo = this.getTimeAgo(new Date(session.modified));
|
|
1680
|
+
console.log(chalk.gray(` • ${session.id} - ${timeAgo} (${session.messageCount} messages)`));
|
|
1681
|
+
});
|
|
1682
|
+
console.log(chalk.gray(' 💡 Use ') + chalk.cyan('/resume <id>') + chalk.gray(' to continue a session'));
|
|
1683
|
+
console.log('');
|
|
1684
|
+
}
|
|
1685
|
+
}
|
|
1686
|
+
|
|
1687
|
+
} catch (error) {
|
|
1688
|
+
// Silently ignore errors in session suggestions
|
|
1689
|
+
}
|
|
1690
|
+
}
|
|
1691
|
+
|
|
1692
|
+
getTimeAgo(date) {
|
|
1693
|
+
const now = new Date();
|
|
1694
|
+
const seconds = Math.floor((now - date) / 1000);
|
|
1695
|
+
|
|
1696
|
+
if (seconds < 60) return 'just now';
|
|
1697
|
+
if (seconds < 3600) return `${Math.floor(seconds / 60)}m ago`;
|
|
1698
|
+
if (seconds < 86400) return `${Math.floor(seconds / 3600)}h ago`;
|
|
1699
|
+
return `${Math.floor(seconds / 86400)}d ago`;
|
|
1700
|
+
}
|
|
1701
|
+
|
|
1702
|
+
async handleCommand(command) {
|
|
1703
|
+
const [cmd, ...args] = command.slice(1).split(' ');
|
|
1704
|
+
const startTime = Date.now();
|
|
1705
|
+
|
|
1706
|
+
// Log command execution
|
|
1707
|
+
this.logger.debug(`Executing command: ${cmd}`, { args });
|
|
1708
|
+
|
|
1709
|
+
try {
|
|
1710
|
+
switch (cmd.toLowerCase()) {
|
|
1086
1711
|
case 'help':
|
|
1087
1712
|
this.showHelp();
|
|
1088
1713
|
break;
|
|
@@ -2945,12 +3570,15 @@ class InteractiveRecoderChat {
|
|
|
2945
3570
|
|
|
2946
3571
|
console.log(chalk.white(assistantMessage));
|
|
2947
3572
|
|
|
2948
|
-
//
|
|
2949
|
-
if (this.
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
3573
|
+
// Only speak if user specifically requested it
|
|
3574
|
+
if (this.shouldSpeak || userInput.toLowerCase().includes('speak') || userInput.toLowerCase().includes('read loud')) {
|
|
3575
|
+
if (this.voiceManager.textToSpeechEnabled) {
|
|
3576
|
+
console.log(chalk.gray('🔊 Speaking response...'));
|
|
3577
|
+
this.voiceManager.speakText(assistantMessage).catch(err => {
|
|
3578
|
+
// Ignore TTS errors to not interrupt the conversation
|
|
3579
|
+
});
|
|
3580
|
+
}
|
|
3581
|
+
this.shouldSpeak = false; // Reset flag
|
|
2954
3582
|
}
|
|
2955
3583
|
|
|
2956
3584
|
// Update conversation-specific stats
|
|
@@ -3417,40 +4045,1505 @@ ${projectInfo}`
|
|
|
3417
4045
|
}
|
|
3418
4046
|
}
|
|
3419
4047
|
|
|
3420
|
-
|
|
3421
|
-
|
|
4048
|
+
// Advanced streaming and progressive loading methods
|
|
4049
|
+
startProgressiveLoading() {
|
|
4050
|
+
if (this.progressiveLoader) {
|
|
4051
|
+
clearInterval(this.progressiveLoader.interval);
|
|
4052
|
+
}
|
|
3422
4053
|
|
|
3423
|
-
|
|
3424
|
-
|
|
3425
|
-
|
|
3426
|
-
|
|
4054
|
+
const stages = [
|
|
4055
|
+
{ text: 'Initializing...', color: 'cyan', icon: '⚡' },
|
|
4056
|
+
{ text: 'Connecting to AI...', color: 'blue', icon: '🔗' },
|
|
4057
|
+
{ text: 'Processing request...', color: 'magenta', icon: '🧠' },
|
|
4058
|
+
{ text: 'Generating response...', color: 'yellow', icon: '✨' }
|
|
4059
|
+
];
|
|
4060
|
+
|
|
4061
|
+
let currentStage = 0;
|
|
4062
|
+
let dots = 0;
|
|
4063
|
+
|
|
4064
|
+
this.progressiveLoader = {
|
|
4065
|
+
interval: setInterval(() => {
|
|
4066
|
+
const stage = stages[currentStage % stages.length];
|
|
4067
|
+
const dotString = '.'.repeat((dots % 3) + 1).padEnd(3);
|
|
4068
|
+
|
|
4069
|
+
process.stdout.write('\r' + ' '.repeat(50) + '\r');
|
|
4070
|
+
process.stdout.write(
|
|
4071
|
+
chalk[stage.color](stage.icon + ' ' + stage.text + dotString)
|
|
4072
|
+
);
|
|
4073
|
+
|
|
4074
|
+
dots++;
|
|
4075
|
+
if (dots % 6 === 0) {
|
|
4076
|
+
currentStage++;
|
|
4077
|
+
}
|
|
4078
|
+
}, 200),
|
|
4079
|
+
stage: 0
|
|
4080
|
+
};
|
|
4081
|
+
}
|
|
4082
|
+
|
|
4083
|
+
stopProgressiveLoading(loader = this.progressiveLoader) {
|
|
4084
|
+
if (loader && loader.interval) {
|
|
4085
|
+
clearInterval(loader.interval);
|
|
4086
|
+
process.stdout.write('\r' + ' '.repeat(50) + '\r');
|
|
4087
|
+
}
|
|
4088
|
+
this.progressiveLoader = null;
|
|
4089
|
+
}
|
|
4090
|
+
|
|
4091
|
+
async processWithAdvancedStreaming({ recoder, input, startTime, streamBuffer, onFirstChunk, onChunk }) {
|
|
4092
|
+
try {
|
|
4093
|
+
// Attempt streaming first
|
|
4094
|
+
const response = await recoder.processConversation(input, false, true, {
|
|
4095
|
+
onChunk: (chunk) => {
|
|
4096
|
+
if (!streamBuffer.streamingStarted) {
|
|
4097
|
+
this.stopProgressiveLoading();
|
|
4098
|
+
this.startStreamingDisplay();
|
|
4099
|
+
streamBuffer.streamingStarted = true;
|
|
4100
|
+
onFirstChunk(Date.now());
|
|
4101
|
+
}
|
|
4102
|
+
|
|
4103
|
+
streamBuffer.content += chunk;
|
|
4104
|
+
streamBuffer.chunks.push(chunk);
|
|
4105
|
+
streamBuffer.isStreaming = true;
|
|
4106
|
+
onChunk(chunk);
|
|
4107
|
+
}
|
|
4108
|
+
});
|
|
4109
|
+
|
|
4110
|
+
return response || streamBuffer.content;
|
|
4111
|
+
|
|
4112
|
+
} catch (error) {
|
|
4113
|
+
// Fallback to non-streaming
|
|
4114
|
+
this.stopProgressiveLoading();
|
|
4115
|
+
return await recoder.processConversation(input, false, false, null);
|
|
4116
|
+
}
|
|
4117
|
+
}
|
|
4118
|
+
|
|
4119
|
+
startStreamingDisplay() {
|
|
4120
|
+
if (!this.streamingActive) {
|
|
4121
|
+
console.log('\n' + chalk.cyan('🔥 Streaming response...'));
|
|
4122
|
+
this.streamingActive = true;
|
|
4123
|
+
this.streamCursor = 0;
|
|
3427
4124
|
}
|
|
4125
|
+
}
|
|
4126
|
+
|
|
4127
|
+
displayStreamingChunk(chunk, chunkNumber) {
|
|
4128
|
+
if (!this.streamingActive) return;
|
|
3428
4129
|
|
|
3429
|
-
//
|
|
3430
|
-
if (
|
|
3431
|
-
|
|
3432
|
-
this.logger.debug('Voice manager cleaned up');
|
|
4130
|
+
// Add subtle chunk indicators for debugging
|
|
4131
|
+
if (process.env.RECODER_DEBUG === 'true') {
|
|
4132
|
+
process.stdout.write(chalk.gray(`[${chunkNumber}]`));
|
|
3433
4133
|
}
|
|
3434
4134
|
|
|
3435
|
-
//
|
|
3436
|
-
|
|
3437
|
-
|
|
3438
|
-
|
|
4135
|
+
// Display chunk with subtle animation
|
|
4136
|
+
const chars = chunk.split('');
|
|
4137
|
+
chars.forEach((char, index) => {
|
|
4138
|
+
setTimeout(() => {
|
|
4139
|
+
process.stdout.write(chalk.white(char));
|
|
4140
|
+
}, index * 2); // Very fast typing effect
|
|
4141
|
+
});
|
|
4142
|
+
}
|
|
4143
|
+
|
|
4144
|
+
stopStreamingDisplay() {
|
|
4145
|
+
this.streamingActive = false;
|
|
4146
|
+
this.streamCursor = 0;
|
|
4147
|
+
}
|
|
4148
|
+
|
|
4149
|
+
finalizeStreamingResponse(response, duration) {
|
|
4150
|
+
this.stopStreamingDisplay();
|
|
4151
|
+
console.log('\n');
|
|
4152
|
+
console.log(chalk.gray(`✅ Stream complete (${duration}s)`));
|
|
4153
|
+
}
|
|
4154
|
+
|
|
4155
|
+
async displayAdvancedTypingResponse(response, startTime) {
|
|
4156
|
+
if (!response) return;
|
|
4157
|
+
|
|
4158
|
+
const formattedResponse = this.formatResponse(response);
|
|
4159
|
+
const duration = Math.round((Date.now() - startTime) / 1000);
|
|
4160
|
+
|
|
4161
|
+
// Enhanced typing animation with word-by-word display
|
|
4162
|
+
const words = formattedResponse.split(' ');
|
|
4163
|
+
let currentLine = '';
|
|
4164
|
+
|
|
4165
|
+
for (const word of words) {
|
|
4166
|
+
currentLine += word + ' ';
|
|
4167
|
+
|
|
4168
|
+
// Display word
|
|
4169
|
+
process.stdout.write(chalk.white(word + ' '));
|
|
4170
|
+
|
|
4171
|
+
// Variable delay based on word length and content
|
|
4172
|
+
let delay = 30; // Base delay
|
|
4173
|
+
if (word.includes('\n')) delay += 100; // Pause at line breaks
|
|
4174
|
+
if (word.length > 10) delay += 20; // Slower for long words
|
|
4175
|
+
if (word.includes('.') || word.includes('!') || word.includes('?')) delay += 150; // Pause at sentences
|
|
4176
|
+
|
|
4177
|
+
await new Promise(resolve => setTimeout(resolve, delay));
|
|
3439
4178
|
}
|
|
3440
4179
|
|
|
3441
|
-
|
|
3442
|
-
|
|
3443
|
-
|
|
3444
|
-
|
|
4180
|
+
console.log('\n' + chalk.gray(`(${duration}s • advanced typing)`));
|
|
4181
|
+
}
|
|
4182
|
+
|
|
4183
|
+
showAdvancedMetrics({ duration, timeToFirstChunk, totalChunks, streamingUsed }) {
|
|
4184
|
+
const metrics = [];
|
|
4185
|
+
|
|
4186
|
+
if (streamingUsed) {
|
|
4187
|
+
metrics.push(chalk.green(`🚀 Streamed ${totalChunks} chunks`));
|
|
4188
|
+
if (timeToFirstChunk) {
|
|
4189
|
+
metrics.push(chalk.blue(`⚡ First response: ${timeToFirstChunk}s`));
|
|
4190
|
+
}
|
|
4191
|
+
} else {
|
|
4192
|
+
metrics.push(chalk.yellow('📄 Standard response'));
|
|
3445
4193
|
}
|
|
3446
4194
|
|
|
3447
|
-
|
|
3448
|
-
|
|
3449
|
-
|
|
3450
|
-
|
|
4195
|
+
metrics.push(chalk.gray(`⏱️ Total: ${duration}s`));
|
|
4196
|
+
|
|
4197
|
+
// Show tool usage indicators
|
|
4198
|
+
this.showToolUsage();
|
|
4199
|
+
|
|
4200
|
+
console.log(chalk.gray('Performance: ') + metrics.join(' • '));
|
|
4201
|
+
}
|
|
4202
|
+
|
|
4203
|
+
showEnhancedError(error) {
|
|
4204
|
+
// Enhanced error display with better formatting
|
|
4205
|
+
console.log(chalk.red('╭─────────────────────────────────────────╮'));
|
|
4206
|
+
console.log(chalk.red('│') + chalk.white.bold(' ❌ Error ') + chalk.red('│'));
|
|
4207
|
+
console.log(chalk.red('╰─────────────────────────────────────────╯'));
|
|
4208
|
+
console.log('');
|
|
4209
|
+
|
|
4210
|
+
// Error details
|
|
4211
|
+
console.log(chalk.white(error.message));
|
|
4212
|
+
console.log('');
|
|
4213
|
+
|
|
4214
|
+
// Enhanced contextual suggestions
|
|
4215
|
+
if (error.message.includes('429')) {
|
|
4216
|
+
console.log(chalk.yellow('💡 Rate limit: Wait 10-30 seconds before retrying'));
|
|
4217
|
+
} else if (error.message.includes('API key')) {
|
|
4218
|
+
console.log(chalk.yellow('💡 Set your API key: export OPENROUTER_API_KEY=your_key'));
|
|
4219
|
+
} else if (error.message.includes('network') || error.message.includes('timeout')) {
|
|
4220
|
+
console.log(chalk.yellow('💡 Network issue: Check connection and try again'));
|
|
4221
|
+
} else if (error.message.includes('model')) {
|
|
4222
|
+
console.log(chalk.yellow('💡 Model issue: Try switching models with /model command'));
|
|
4223
|
+
} else {
|
|
4224
|
+
console.log(chalk.yellow('💡 Try: /help for available commands or /reset to start fresh'));
|
|
4225
|
+
}
|
|
4226
|
+
|
|
4227
|
+
// Debug information if enabled
|
|
4228
|
+
if (process.env.RECODER_DEBUG === 'true') {
|
|
4229
|
+
console.log('');
|
|
4230
|
+
console.log(chalk.gray('Debug details:'));
|
|
4231
|
+
console.log(chalk.gray(error.stack?.split('\n').slice(0, 3).join('\n')));
|
|
3451
4232
|
}
|
|
3452
4233
|
}
|
|
3453
4234
|
|
|
4235
|
+
setupGlobalErrorHandlers() {
|
|
4236
|
+
// Register domain-specific handlers with unified error handler
|
|
4237
|
+
errorHandler.registerHandler('interactive', (error) => {
|
|
4238
|
+
if (this.logger) {
|
|
4239
|
+
this.logger.error('Interactive mode error', {
|
|
4240
|
+
error: error?.toString(),
|
|
4241
|
+
stack: error?.stack
|
|
4242
|
+
});
|
|
4243
|
+
}
|
|
4244
|
+
|
|
4245
|
+
// In debug mode, show detailed error for interactive context
|
|
4246
|
+
if (process.env.RECODER_DEBUG === 'true') {
|
|
4247
|
+
console.log(chalk.blue('\n🔍 Interactive Context Error:'));
|
|
4248
|
+
console.log(chalk.gray(error?.toString()));
|
|
4249
|
+
if (error?.stack) {
|
|
4250
|
+
console.log(chalk.gray(error.stack.split('\n').slice(0, 5).join('\n')));
|
|
4251
|
+
}
|
|
4252
|
+
}
|
|
4253
|
+
});
|
|
4254
|
+
|
|
4255
|
+
// Register MCP error handler for graceful degradation
|
|
4256
|
+
errorHandler.registerHandler('mcp', (error) => {
|
|
4257
|
+
// MCP errors are non-critical, just log for debugging
|
|
4258
|
+
if (process.env.RECODER_DEBUG === 'true') {
|
|
4259
|
+
console.log(chalk.gray('🔌 MCP connection issue (non-critical):'), error?.message);
|
|
4260
|
+
}
|
|
4261
|
+
});
|
|
4262
|
+
|
|
4263
|
+
// Handle warnings specifically
|
|
4264
|
+
process.on('warning', (warning) => {
|
|
4265
|
+
if (warning.name === 'PromiseRejectionHandledWarning' ||
|
|
4266
|
+
warning.name === 'UnhandledPromiseRejectionWarning') {
|
|
4267
|
+
if (this.logger) {
|
|
4268
|
+
this.logger.warn('Promise warning', { name: warning.name, message: warning.message });
|
|
4269
|
+
}
|
|
4270
|
+
}
|
|
4271
|
+
});
|
|
4272
|
+
}
|
|
4273
|
+
|
|
4274
|
+
// ========== INTELLIGENT FEATURES ==========
|
|
4275
|
+
|
|
4276
|
+
performCodeAnalysis() {
|
|
4277
|
+
console.log(chalk.cyan('🔍 Analyzing Codebase...'));
|
|
4278
|
+
console.log('');
|
|
4279
|
+
|
|
4280
|
+
const analysis = this.analyzeCurrentProject();
|
|
4281
|
+
|
|
4282
|
+
console.log(chalk.magenta('╭─────────────────────────────────────────╮'));
|
|
4283
|
+
console.log(chalk.magenta('│') + chalk.white.bold(' 📊 Code Analysis Results ') + chalk.magenta('│'));
|
|
4284
|
+
console.log(chalk.magenta('╰─────────────────────────────────────────╯'));
|
|
4285
|
+
console.log('');
|
|
4286
|
+
|
|
4287
|
+
console.log(chalk.cyan('📁 Project Structure:'));
|
|
4288
|
+
console.log(chalk.white(` • Files: ${analysis.fileCount}`));
|
|
4289
|
+
console.log(chalk.white(` • Languages: ${analysis.languages.join(', ')}`));
|
|
4290
|
+
console.log(chalk.white(` • Main Language: ${analysis.mainLanguage}`));
|
|
4291
|
+
console.log('');
|
|
4292
|
+
|
|
4293
|
+
console.log(chalk.green('✅ Strengths:'));
|
|
4294
|
+
analysis.strengths.forEach(strength => {
|
|
4295
|
+
console.log(chalk.white(` • ${strength}`));
|
|
4296
|
+
});
|
|
4297
|
+
console.log('');
|
|
4298
|
+
|
|
4299
|
+
console.log(chalk.yellow('💡 Suggestions:'));
|
|
4300
|
+
analysis.suggestions.forEach(suggestion => {
|
|
4301
|
+
console.log(chalk.white(` • ${suggestion}`));
|
|
4302
|
+
});
|
|
4303
|
+
console.log('');
|
|
4304
|
+
}
|
|
4305
|
+
|
|
4306
|
+
analyzeCurrentProject() {
|
|
4307
|
+
const fs = require('fs');
|
|
4308
|
+
const path = require('path');
|
|
4309
|
+
|
|
4310
|
+
try {
|
|
4311
|
+
const analysis = {
|
|
4312
|
+
fileCount: 0,
|
|
4313
|
+
languages: new Set(),
|
|
4314
|
+
fileTypes: {},
|
|
4315
|
+
dependencies: [],
|
|
4316
|
+
devDependencies: [],
|
|
4317
|
+
strengths: [],
|
|
4318
|
+
issues: [],
|
|
4319
|
+
suggestions: []
|
|
4320
|
+
};
|
|
4321
|
+
|
|
4322
|
+
// Scan actual files in project
|
|
4323
|
+
const scanDirectory = (dir, depth = 0) => {
|
|
4324
|
+
if (depth > 3) return; // Limit depth
|
|
4325
|
+
|
|
4326
|
+
try {
|
|
4327
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
4328
|
+
|
|
4329
|
+
for (const entry of entries) {
|
|
4330
|
+
const fullPath = path.join(dir, entry.name);
|
|
4331
|
+
|
|
4332
|
+
// Skip node_modules, .git, etc.
|
|
4333
|
+
if (entry.name.startsWith('.') || entry.name === 'node_modules') continue;
|
|
4334
|
+
|
|
4335
|
+
if (entry.isFile()) {
|
|
4336
|
+
analysis.fileCount++;
|
|
4337
|
+
const ext = path.extname(entry.name).toLowerCase();
|
|
4338
|
+
|
|
4339
|
+
// Count file types
|
|
4340
|
+
analysis.fileTypes[ext] = (analysis.fileTypes[ext] || 0) + 1;
|
|
4341
|
+
|
|
4342
|
+
// Detect languages
|
|
4343
|
+
const languageMap = {
|
|
4344
|
+
'.js': 'JavaScript',
|
|
4345
|
+
'.ts': 'TypeScript',
|
|
4346
|
+
'.py': 'Python',
|
|
4347
|
+
'.java': 'Java',
|
|
4348
|
+
'.cpp': 'C++',
|
|
4349
|
+
'.c': 'C',
|
|
4350
|
+
'.cs': 'C#',
|
|
4351
|
+
'.go': 'Go',
|
|
4352
|
+
'.rs': 'Rust',
|
|
4353
|
+
'.php': 'PHP',
|
|
4354
|
+
'.rb': 'Ruby',
|
|
4355
|
+
'.json': 'JSON',
|
|
4356
|
+
'.md': 'Markdown',
|
|
4357
|
+
'.html': 'HTML',
|
|
4358
|
+
'.css': 'CSS',
|
|
4359
|
+
'.scss': 'SCSS',
|
|
4360
|
+
'.yaml': 'YAML',
|
|
4361
|
+
'.yml': 'YAML'
|
|
4362
|
+
};
|
|
4363
|
+
|
|
4364
|
+
if (languageMap[ext]) {
|
|
4365
|
+
analysis.languages.add(languageMap[ext]);
|
|
4366
|
+
}
|
|
4367
|
+
} else if (entry.isDirectory()) {
|
|
4368
|
+
scanDirectory(fullPath, depth + 1);
|
|
4369
|
+
}
|
|
4370
|
+
}
|
|
4371
|
+
} catch (error) {
|
|
4372
|
+
// Skip directories we can't read
|
|
4373
|
+
}
|
|
4374
|
+
};
|
|
4375
|
+
|
|
4376
|
+
// Scan from current directory
|
|
4377
|
+
scanDirectory(process.cwd());
|
|
4378
|
+
|
|
4379
|
+
// Analyze package.json if it exists
|
|
4380
|
+
try {
|
|
4381
|
+
const packagePath = path.join(process.cwd(), 'package.json');
|
|
4382
|
+
if (fs.existsSync(packagePath)) {
|
|
4383
|
+
const packageJson = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
|
|
4384
|
+
analysis.dependencies = Object.keys(packageJson.dependencies || {});
|
|
4385
|
+
analysis.devDependencies = Object.keys(packageJson.devDependencies || {});
|
|
4386
|
+
|
|
4387
|
+
// Analyze dependencies for insights
|
|
4388
|
+
if (analysis.dependencies.includes('express')) analysis.strengths.push('Express.js web framework detected');
|
|
4389
|
+
if (analysis.dependencies.includes('react')) analysis.strengths.push('React frontend framework detected');
|
|
4390
|
+
if (analysis.dependencies.includes('chalk')) analysis.strengths.push('Terminal styling with chalk');
|
|
4391
|
+
if (analysis.devDependencies.includes('jest')) analysis.strengths.push('Jest testing framework configured');
|
|
4392
|
+
if (analysis.devDependencies.includes('eslint')) analysis.strengths.push('ESLint code linting configured');
|
|
4393
|
+
}
|
|
4394
|
+
} catch (error) {
|
|
4395
|
+
analysis.issues.push('Could not read package.json');
|
|
4396
|
+
}
|
|
4397
|
+
|
|
4398
|
+
// Generate insights based on actual analysis
|
|
4399
|
+
const languageArray = Array.from(analysis.languages);
|
|
4400
|
+
const mainLanguage = this.determineMainLanguage(analysis.fileTypes);
|
|
4401
|
+
|
|
4402
|
+
if (analysis.fileCount === 0) {
|
|
4403
|
+
analysis.issues.push('No files found in project');
|
|
4404
|
+
} else if (analysis.fileCount > 1000) {
|
|
4405
|
+
analysis.issues.push('Large project detected - consider optimization');
|
|
4406
|
+
}
|
|
4407
|
+
|
|
4408
|
+
if (!languageArray.includes('TypeScript') && languageArray.includes('JavaScript')) {
|
|
4409
|
+
analysis.suggestions.push('Consider migrating to TypeScript for better type safety');
|
|
4410
|
+
}
|
|
4411
|
+
|
|
4412
|
+
if (!analysis.fileTypes['.md']) {
|
|
4413
|
+
analysis.suggestions.push('Add README.md and documentation');
|
|
4414
|
+
}
|
|
4415
|
+
|
|
4416
|
+
if (analysis.dependencies.length === 0) {
|
|
4417
|
+
analysis.suggestions.push('Consider using npm/yarn for dependency management');
|
|
4418
|
+
}
|
|
4419
|
+
|
|
4420
|
+
if (!analysis.devDependencies.includes('jest') && !analysis.devDependencies.includes('mocha')) {
|
|
4421
|
+
analysis.suggestions.push('Add testing framework (Jest/Mocha)');
|
|
4422
|
+
}
|
|
4423
|
+
|
|
4424
|
+
return {
|
|
4425
|
+
fileCount: analysis.fileCount,
|
|
4426
|
+
languages: languageArray,
|
|
4427
|
+
mainLanguage: mainLanguage,
|
|
4428
|
+
fileTypes: analysis.fileTypes,
|
|
4429
|
+
dependencies: analysis.dependencies,
|
|
4430
|
+
devDependencies: analysis.devDependencies,
|
|
4431
|
+
strengths: analysis.strengths.length > 0 ? analysis.strengths : ['Project structure analyzed'],
|
|
4432
|
+
issues: analysis.issues,
|
|
4433
|
+
suggestions: analysis.suggestions.length > 0 ? analysis.suggestions : ['Project appears well-structured']
|
|
4434
|
+
};
|
|
4435
|
+
|
|
4436
|
+
} catch (error) {
|
|
4437
|
+
return {
|
|
4438
|
+
fileCount: 0,
|
|
4439
|
+
languages: ['Unknown'],
|
|
4440
|
+
mainLanguage: 'Unknown',
|
|
4441
|
+
fileTypes: {},
|
|
4442
|
+
dependencies: [],
|
|
4443
|
+
devDependencies: [],
|
|
4444
|
+
strengths: ['Basic analysis attempted'],
|
|
4445
|
+
issues: [`Analysis error: ${error.message}`],
|
|
4446
|
+
suggestions: ['Ensure proper file permissions']
|
|
4447
|
+
};
|
|
4448
|
+
}
|
|
4449
|
+
}
|
|
4450
|
+
|
|
4451
|
+
determineMainLanguage(fileTypes) {
|
|
4452
|
+
let maxCount = 0;
|
|
4453
|
+
let mainExt = '';
|
|
4454
|
+
|
|
4455
|
+
for (const [ext, count] of Object.entries(fileTypes)) {
|
|
4456
|
+
if (count > maxCount && ext !== '.md' && ext !== '.json') {
|
|
4457
|
+
maxCount = count;
|
|
4458
|
+
mainExt = ext;
|
|
4459
|
+
}
|
|
4460
|
+
}
|
|
4461
|
+
|
|
4462
|
+
const languageMap = {
|
|
4463
|
+
'.js': 'JavaScript',
|
|
4464
|
+
'.ts': 'TypeScript',
|
|
4465
|
+
'.py': 'Python',
|
|
4466
|
+
'.java': 'Java',
|
|
4467
|
+
'.cpp': 'C++',
|
|
4468
|
+
'.go': 'Go',
|
|
4469
|
+
'.rs': 'Rust'
|
|
4470
|
+
};
|
|
4471
|
+
|
|
4472
|
+
return languageMap[mainExt] || 'Mixed';
|
|
4473
|
+
}
|
|
4474
|
+
|
|
4475
|
+
showGitStatus() {
|
|
4476
|
+
console.log(chalk.cyan('📊 Git Repository Status'));
|
|
4477
|
+
console.log('');
|
|
4478
|
+
|
|
4479
|
+
const { execSync } = require('child_process');
|
|
4480
|
+
|
|
4481
|
+
try {
|
|
4482
|
+
// Check if git is available
|
|
4483
|
+
execSync('git --version', { stdio: 'ignore' });
|
|
4484
|
+
|
|
4485
|
+
// Check if we're in a git repository
|
|
4486
|
+
execSync('git rev-parse --git-dir', { stdio: 'ignore' });
|
|
4487
|
+
|
|
4488
|
+
console.log(chalk.magenta('╭─────────────────────────────────────────╮'));
|
|
4489
|
+
console.log(chalk.magenta('│') + chalk.white.bold(' 🌿 Git Repository Analysis ') + chalk.magenta('│'));
|
|
4490
|
+
console.log(chalk.magenta('╰─────────────────────────────────────────╯'));
|
|
4491
|
+
console.log('');
|
|
4492
|
+
|
|
4493
|
+
// Get comprehensive git information
|
|
4494
|
+
const gitInfo = this.getGitInformation();
|
|
4495
|
+
|
|
4496
|
+
// Display branch information
|
|
4497
|
+
console.log(chalk.green(`🌿 Current Branch: ${gitInfo.currentBranch}`));
|
|
4498
|
+
if (gitInfo.upstreamBranch) {
|
|
4499
|
+
console.log(chalk.blue(`🔗 Upstream: ${gitInfo.upstreamBranch}`));
|
|
4500
|
+
}
|
|
4501
|
+
|
|
4502
|
+
// Show commits ahead/behind
|
|
4503
|
+
if (gitInfo.aheadBehind.ahead > 0 || gitInfo.aheadBehind.behind > 0) {
|
|
4504
|
+
console.log(chalk.yellow(`📊 Sync Status: ${gitInfo.aheadBehind.ahead} ahead, ${gitInfo.aheadBehind.behind} behind`));
|
|
4505
|
+
}
|
|
4506
|
+
console.log('');
|
|
4507
|
+
|
|
4508
|
+
// Show working directory status
|
|
4509
|
+
if (gitInfo.statusFiles.length > 0) {
|
|
4510
|
+
console.log(chalk.yellow('📝 Working Directory Changes:'));
|
|
4511
|
+
|
|
4512
|
+
const statusCounts = { modified: 0, added: 0, deleted: 0, untracked: 0 };
|
|
4513
|
+
|
|
4514
|
+
gitInfo.statusFiles.forEach(file => {
|
|
4515
|
+
let statusIcon = '❓';
|
|
4516
|
+
let statusColor = 'white';
|
|
4517
|
+
|
|
4518
|
+
if (file.status.includes('M')) {
|
|
4519
|
+
statusIcon = '📝';
|
|
4520
|
+
statusColor = 'yellow';
|
|
4521
|
+
statusCounts.modified++;
|
|
4522
|
+
} else if (file.status.includes('A')) {
|
|
4523
|
+
statusIcon = '➕';
|
|
4524
|
+
statusColor = 'green';
|
|
4525
|
+
statusCounts.added++;
|
|
4526
|
+
} else if (file.status.includes('D')) {
|
|
4527
|
+
statusIcon = '🗑️';
|
|
4528
|
+
statusColor = 'red';
|
|
4529
|
+
statusCounts.deleted++;
|
|
4530
|
+
} else if (file.status.includes('??')) {
|
|
4531
|
+
statusIcon = '❔';
|
|
4532
|
+
statusColor = 'gray';
|
|
4533
|
+
statusCounts.untracked++;
|
|
4534
|
+
}
|
|
4535
|
+
|
|
4536
|
+
console.log(chalk[statusColor](` ${statusIcon} ${file.file}`));
|
|
4537
|
+
});
|
|
4538
|
+
|
|
4539
|
+
console.log('');
|
|
4540
|
+
console.log(chalk.cyan('📊 Summary:'));
|
|
4541
|
+
if (statusCounts.modified > 0) console.log(chalk.yellow(` 📝 ${statusCounts.modified} modified`));
|
|
4542
|
+
if (statusCounts.added > 0) console.log(chalk.green(` ➕ ${statusCounts.added} added`));
|
|
4543
|
+
if (statusCounts.deleted > 0) console.log(chalk.red(` 🗑️ ${statusCounts.deleted} deleted`));
|
|
4544
|
+
if (statusCounts.untracked > 0) console.log(chalk.gray(` ❔ ${statusCounts.untracked} untracked`));
|
|
4545
|
+
|
|
4546
|
+
} else {
|
|
4547
|
+
console.log(chalk.green('✅ Working directory clean'));
|
|
4548
|
+
}
|
|
4549
|
+
|
|
4550
|
+
console.log('');
|
|
4551
|
+
|
|
4552
|
+
// Show recent commits with more detail
|
|
4553
|
+
console.log(chalk.blue('📚 Recent Commits:'));
|
|
4554
|
+
gitInfo.recentCommits.forEach((commit, index) => {
|
|
4555
|
+
const timeAgo = this.getTimeAgo(commit.date);
|
|
4556
|
+
console.log(chalk.gray(` ${index + 1}. ${commit.hash} ${commit.message}`));
|
|
4557
|
+
console.log(chalk.gray(` by ${commit.author} ${timeAgo}`));
|
|
4558
|
+
});
|
|
4559
|
+
|
|
4560
|
+
console.log('');
|
|
4561
|
+
|
|
4562
|
+
// Show repository statistics
|
|
4563
|
+
console.log(chalk.cyan('📈 Repository Stats:'));
|
|
4564
|
+
console.log(chalk.white(` 🏷️ Total Tags: ${gitInfo.stats.totalTags}`));
|
|
4565
|
+
console.log(chalk.white(` 🌿 Total Branches: ${gitInfo.stats.totalBranches}`));
|
|
4566
|
+
console.log(chalk.white(` 📝 Total Commits: ${gitInfo.stats.totalCommits}`));
|
|
4567
|
+
console.log(chalk.white(` 👥 Contributors: ${gitInfo.stats.contributors}`));
|
|
4568
|
+
|
|
4569
|
+
} catch (error) {
|
|
4570
|
+
if (error.message.includes('not a git repository')) {
|
|
4571
|
+
console.log(chalk.red('❌ Not a git repository'));
|
|
4572
|
+
console.log(chalk.gray('Initialize with: git init'));
|
|
4573
|
+
} else if (error.message.includes('git --version')) {
|
|
4574
|
+
console.log(chalk.red('❌ Git not installed or not in PATH'));
|
|
4575
|
+
} else {
|
|
4576
|
+
console.log(chalk.red(`❌ Git error: ${error.message}`));
|
|
4577
|
+
}
|
|
4578
|
+
}
|
|
4579
|
+
console.log('');
|
|
4580
|
+
}
|
|
4581
|
+
|
|
4582
|
+
getGitInformation() {
|
|
4583
|
+
const { execSync } = require('child_process');
|
|
4584
|
+
|
|
4585
|
+
try {
|
|
4586
|
+
// Get current branch
|
|
4587
|
+
const currentBranch = execSync('git branch --show-current', { encoding: 'utf8' }).trim();
|
|
4588
|
+
|
|
4589
|
+
// Get upstream branch
|
|
4590
|
+
let upstreamBranch = null;
|
|
4591
|
+
try {
|
|
4592
|
+
upstreamBranch = execSync('git rev-parse --abbrev-ref @{upstream}', { encoding: 'utf8' }).trim();
|
|
4593
|
+
} catch (e) {
|
|
4594
|
+
// No upstream branch
|
|
4595
|
+
}
|
|
4596
|
+
|
|
4597
|
+
// Get ahead/behind count
|
|
4598
|
+
let aheadBehind = { ahead: 0, behind: 0 };
|
|
4599
|
+
if (upstreamBranch) {
|
|
4600
|
+
try {
|
|
4601
|
+
const aheadBehindOutput = execSync(`git rev-list --count --left-right ${upstreamBranch}...HEAD`, { encoding: 'utf8' }).trim();
|
|
4602
|
+
const [behind, ahead] = aheadBehindOutput.split('\t').map(Number);
|
|
4603
|
+
aheadBehind = { ahead, behind };
|
|
4604
|
+
} catch (e) {
|
|
4605
|
+
// Can't determine ahead/behind
|
|
4606
|
+
}
|
|
4607
|
+
}
|
|
4608
|
+
|
|
4609
|
+
// Get status files
|
|
4610
|
+
const statusOutput = execSync('git status --porcelain', { encoding: 'utf8' });
|
|
4611
|
+
const statusFiles = statusOutput.split('\n')
|
|
4612
|
+
.filter(line => line.trim())
|
|
4613
|
+
.map(line => ({
|
|
4614
|
+
status: line.substring(0, 2),
|
|
4615
|
+
file: line.substring(3)
|
|
4616
|
+
}));
|
|
4617
|
+
|
|
4618
|
+
// Get recent commits with detailed info
|
|
4619
|
+
const commitOutput = execSync('git log --oneline --format="%H|%s|%an|%ad" --date=iso -10', { encoding: 'utf8' });
|
|
4620
|
+
const recentCommits = commitOutput.split('\n')
|
|
4621
|
+
.filter(line => line.trim())
|
|
4622
|
+
.map(line => {
|
|
4623
|
+
const [hash, message, author, date] = line.split('|');
|
|
4624
|
+
return {
|
|
4625
|
+
hash: hash.substring(0, 8),
|
|
4626
|
+
message,
|
|
4627
|
+
author,
|
|
4628
|
+
date: new Date(date)
|
|
4629
|
+
};
|
|
4630
|
+
});
|
|
4631
|
+
|
|
4632
|
+
// Get repository statistics
|
|
4633
|
+
const totalTags = execSync('git tag --list | wc -l', { encoding: 'utf8' }).trim();
|
|
4634
|
+
const totalBranches = execSync('git branch -a | wc -l', { encoding: 'utf8' }).trim();
|
|
4635
|
+
const totalCommits = execSync('git rev-list --count HEAD', { encoding: 'utf8' }).trim();
|
|
4636
|
+
const contributors = execSync('git shortlog -sn | wc -l', { encoding: 'utf8' }).trim();
|
|
4637
|
+
|
|
4638
|
+
return {
|
|
4639
|
+
currentBranch,
|
|
4640
|
+
upstreamBranch,
|
|
4641
|
+
aheadBehind,
|
|
4642
|
+
statusFiles,
|
|
4643
|
+
recentCommits,
|
|
4644
|
+
stats: {
|
|
4645
|
+
totalTags: parseInt(totalTags) || 0,
|
|
4646
|
+
totalBranches: parseInt(totalBranches) || 0,
|
|
4647
|
+
totalCommits: parseInt(totalCommits) || 0,
|
|
4648
|
+
contributors: parseInt(contributors) || 0
|
|
4649
|
+
}
|
|
4650
|
+
};
|
|
4651
|
+
|
|
4652
|
+
} catch (error) {
|
|
4653
|
+
throw error;
|
|
4654
|
+
}
|
|
4655
|
+
}
|
|
4656
|
+
|
|
4657
|
+
getTimeAgo(date) {
|
|
4658
|
+
const now = new Date();
|
|
4659
|
+
const diffMs = now - date;
|
|
4660
|
+
const diffMins = Math.floor(diffMs / 60000);
|
|
4661
|
+
const diffHours = Math.floor(diffMs / 3600000);
|
|
4662
|
+
const diffDays = Math.floor(diffMs / 86400000);
|
|
4663
|
+
|
|
4664
|
+
if (diffMins < 60) return `${diffMins} minutes ago`;
|
|
4665
|
+
if (diffHours < 24) return `${diffHours} hours ago`;
|
|
4666
|
+
if (diffDays < 30) return `${diffDays} days ago`;
|
|
4667
|
+
return date.toLocaleDateString();
|
|
4668
|
+
}
|
|
4669
|
+
|
|
4670
|
+
performCodeReview() {
|
|
4671
|
+
console.log(chalk.cyan('🔍 Performing AI Code Review...'));
|
|
4672
|
+
console.log('');
|
|
4673
|
+
|
|
4674
|
+
console.log(chalk.magenta('╭─────────────────────────────────────────╮'));
|
|
4675
|
+
console.log(chalk.magenta('│') + chalk.white.bold(' 🤖 AI Code Review ') + chalk.magenta('│'));
|
|
4676
|
+
console.log(chalk.magenta('╰─────────────────────────────────────────╯'));
|
|
4677
|
+
console.log('');
|
|
4678
|
+
|
|
4679
|
+
const review = this.generateRealCodeReview();
|
|
4680
|
+
|
|
4681
|
+
// Display scores with color coding
|
|
4682
|
+
const getScoreColor = (score) => {
|
|
4683
|
+
if (score >= 90) return 'green';
|
|
4684
|
+
if (score >= 75) return 'yellow';
|
|
4685
|
+
return 'red';
|
|
4686
|
+
};
|
|
4687
|
+
|
|
4688
|
+
console.log(chalk[getScoreColor(review.scores.quality)](`✅ Code Quality: ${review.scores.quality}/100`));
|
|
4689
|
+
console.log(chalk[getScoreColor(review.scores.maintainability)](`📊 Maintainability: ${review.scores.maintainability}/100`));
|
|
4690
|
+
console.log(chalk[getScoreColor(review.scores.security)](`🛡️ Security: ${review.scores.security}/100`));
|
|
4691
|
+
console.log(chalk[getScoreColor(review.scores.testCoverage)](`🧪 Test Coverage: ${review.scores.testCoverage}/100`));
|
|
4692
|
+
console.log('');
|
|
4693
|
+
|
|
4694
|
+
// Display architecture analysis
|
|
4695
|
+
if (review.architecture.length > 0) {
|
|
4696
|
+
console.log(chalk.blue('🏗️ Architecture Analysis:'));
|
|
4697
|
+
review.architecture.forEach(item => {
|
|
4698
|
+
console.log(chalk.white(` • ${item.type}: ${item.description}`));
|
|
4699
|
+
if (item.file) {
|
|
4700
|
+
console.log(chalk.gray(` File: ${item.file}`));
|
|
4701
|
+
}
|
|
4702
|
+
});
|
|
4703
|
+
console.log('');
|
|
4704
|
+
}
|
|
4705
|
+
|
|
4706
|
+
// Display code quality issues
|
|
4707
|
+
if (review.qualityIssues.length > 0) {
|
|
4708
|
+
console.log(chalk.yellow('⚠️ Code Quality Issues:'));
|
|
4709
|
+
review.qualityIssues.forEach(issue => {
|
|
4710
|
+
console.log(chalk.white(` • ${issue.type}: ${issue.description}`));
|
|
4711
|
+
console.log(chalk.gray(` File: ${issue.file}:${issue.line}`));
|
|
4712
|
+
});
|
|
4713
|
+
console.log('');
|
|
4714
|
+
}
|
|
4715
|
+
|
|
4716
|
+
// Display maintainability metrics
|
|
4717
|
+
if (review.maintainabilityMetrics.length > 0) {
|
|
4718
|
+
console.log(chalk.cyan('📈 Maintainability Metrics:'));
|
|
4719
|
+
review.maintainabilityMetrics.forEach(metric => {
|
|
4720
|
+
console.log(chalk.white(` • ${metric.metric}: ${metric.value} (${metric.assessment})`));
|
|
4721
|
+
if (metric.suggestion) {
|
|
4722
|
+
console.log(chalk.gray(` Suggestion: ${metric.suggestion}`));
|
|
4723
|
+
}
|
|
4724
|
+
});
|
|
4725
|
+
console.log('');
|
|
4726
|
+
}
|
|
4727
|
+
|
|
4728
|
+
// Display AI recommendations
|
|
4729
|
+
if (review.recommendations.length > 0) {
|
|
4730
|
+
console.log(chalk.cyan('💡 AI-Powered Recommendations:'));
|
|
4731
|
+
review.recommendations.forEach(rec => {
|
|
4732
|
+
console.log(chalk.white(` • ${rec.category}: ${rec.suggestion}`));
|
|
4733
|
+
if (rec.priority) {
|
|
4734
|
+
console.log(chalk.gray(` Priority: ${rec.priority}`));
|
|
4735
|
+
}
|
|
4736
|
+
});
|
|
4737
|
+
console.log('');
|
|
4738
|
+
}
|
|
4739
|
+
|
|
4740
|
+
// Display overall assessment
|
|
4741
|
+
const overallScore = Math.round((review.scores.quality + review.scores.maintainability + review.scores.security + review.scores.testCoverage) / 4);
|
|
4742
|
+
console.log(chalk[getScoreColor(overallScore)](`🎯 Overall Code Health: ${overallScore}/100`));
|
|
4743
|
+
}
|
|
4744
|
+
|
|
4745
|
+
generateRealCodeReview() {
|
|
4746
|
+
const fs = require('fs');
|
|
4747
|
+
const path = require('path');
|
|
4748
|
+
|
|
4749
|
+
const review = {
|
|
4750
|
+
scores: {
|
|
4751
|
+
quality: 100,
|
|
4752
|
+
maintainability: 100,
|
|
4753
|
+
security: 100,
|
|
4754
|
+
testCoverage: 50
|
|
4755
|
+
},
|
|
4756
|
+
architecture: [],
|
|
4757
|
+
qualityIssues: [],
|
|
4758
|
+
maintainabilityMetrics: [],
|
|
4759
|
+
recommendations: []
|
|
4760
|
+
};
|
|
4761
|
+
|
|
4762
|
+
// Code quality patterns to analyze
|
|
4763
|
+
const qualityPatterns = [
|
|
4764
|
+
{ pattern: /function\s+\w+\s*\([^)]*\)\s*\{[\s\S]{500,}?\}/, type: 'Long Function', description: 'Function exceeds recommended length' },
|
|
4765
|
+
{ pattern: /if\s*\([^)]+\)\s*\{[\s\S]*?if\s*\([^)]+\)\s*\{[\s\S]*?if\s*\([^)]+\)/, type: 'Deep Nesting', description: 'Excessive nesting depth detected' },
|
|
4766
|
+
{ pattern: /var\s+\w+/, type: 'Var Usage', description: 'Use const/let instead of var' },
|
|
4767
|
+
{ pattern: /==\s*(?!null)/, type: 'Loose Equality', description: 'Use strict equality (===) instead' },
|
|
4768
|
+
{ pattern: /console\.log/, type: 'Debug Code', description: 'Remove console.log from production code' },
|
|
4769
|
+
{ pattern: /\.catch\s*\(\s*\(\s*\)\s*=>\s*\{\s*\}\s*\)/, type: 'Empty Catch', description: 'Empty catch block - handle errors properly' }
|
|
4770
|
+
];
|
|
4771
|
+
|
|
4772
|
+
// Maintainability patterns
|
|
4773
|
+
const maintainabilityPatterns = [
|
|
4774
|
+
{ pattern: /\/\*[\s\S]*?\*\/|\/\/.*$/gm, type: 'Comments', description: 'Code documentation' },
|
|
4775
|
+
{ pattern: /class\s+\w+/, type: 'Classes', description: 'Object-oriented structure' },
|
|
4776
|
+
{ pattern: /export\s+/, type: 'Modules', description: 'Modular architecture' },
|
|
4777
|
+
{ pattern: /import\s+.*from/, type: 'Dependencies', description: 'Dependency management' }
|
|
4778
|
+
];
|
|
4779
|
+
|
|
4780
|
+
// Architecture patterns
|
|
4781
|
+
const architecturePatterns = [
|
|
4782
|
+
{ pattern: /class\s+\w+Manager/, type: 'Manager Pattern', description: 'Centralized management logic' },
|
|
4783
|
+
{ pattern: /\.on\(|\.emit\(/, type: 'Event-Driven', description: 'Event-driven architecture' },
|
|
4784
|
+
{ pattern: /async\s+function|await\s+/, type: 'Async Pattern', description: 'Asynchronous programming patterns' },
|
|
4785
|
+
{ pattern: /module\.exports\s*=/, type: 'Module System', description: 'CommonJS module pattern' }
|
|
4786
|
+
];
|
|
4787
|
+
|
|
4788
|
+
let totalLines = 0;
|
|
4789
|
+
let totalFunctions = 0;
|
|
4790
|
+
let totalClasses = 0;
|
|
4791
|
+
let commentLines = 0;
|
|
4792
|
+
let testFiles = 0;
|
|
4793
|
+
|
|
4794
|
+
// Analyze codebase
|
|
4795
|
+
const analyzeDirectory = (dir, depth = 0) => {
|
|
4796
|
+
if (depth > 3) return;
|
|
4797
|
+
|
|
4798
|
+
try {
|
|
4799
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
4800
|
+
|
|
4801
|
+
for (const entry of entries) {
|
|
4802
|
+
const fullPath = path.join(dir, entry.name);
|
|
4803
|
+
|
|
4804
|
+
if (entry.name.startsWith('.') || entry.name === 'node_modules') continue;
|
|
4805
|
+
|
|
4806
|
+
if (entry.isFile() && this.isCodeFile(entry.name)) {
|
|
4807
|
+
this.analyzeCodeFile(fullPath, qualityPatterns, maintainabilityPatterns, architecturePatterns, review);
|
|
4808
|
+
|
|
4809
|
+
// Count metrics
|
|
4810
|
+
const content = fs.readFileSync(fullPath, 'utf8');
|
|
4811
|
+
const lines = content.split('\n');
|
|
4812
|
+
totalLines += lines.length;
|
|
4813
|
+
|
|
4814
|
+
// Count functions and classes
|
|
4815
|
+
totalFunctions += (content.match(/function\s+\w+|=>\s*\{|\w+\s*:\s*function/g) || []).length;
|
|
4816
|
+
totalClasses += (content.match(/class\s+\w+/g) || []).length;
|
|
4817
|
+
|
|
4818
|
+
// Count comments
|
|
4819
|
+
commentLines += (content.match(/\/\*[\s\S]*?\*\/|\/\/.*$/gm) || []).length;
|
|
4820
|
+
|
|
4821
|
+
// Check if it's a test file
|
|
4822
|
+
if (entry.name.includes('test') || entry.name.includes('spec')) {
|
|
4823
|
+
testFiles++;
|
|
4824
|
+
}
|
|
4825
|
+
|
|
4826
|
+
} else if (entry.isDirectory()) {
|
|
4827
|
+
analyzeDirectory(fullPath, depth + 1);
|
|
4828
|
+
}
|
|
4829
|
+
}
|
|
4830
|
+
} catch (error) {
|
|
4831
|
+
// Skip directories we can't read
|
|
4832
|
+
}
|
|
4833
|
+
};
|
|
4834
|
+
|
|
4835
|
+
analyzeDirectory(process.cwd());
|
|
4836
|
+
|
|
4837
|
+
// Calculate scores based on findings
|
|
4838
|
+
const qualityIssueCount = review.qualityIssues.length;
|
|
4839
|
+
review.scores.quality = Math.max(0, 100 - (qualityIssueCount * 5));
|
|
4840
|
+
|
|
4841
|
+
// Maintainability score based on structure and documentation
|
|
4842
|
+
const commentRatio = totalLines > 0 ? (commentLines / totalLines) * 100 : 0;
|
|
4843
|
+
const avgFunctionsPerFile = totalFunctions > 0 ? totalFunctions / (totalLines / 50) : 0;
|
|
4844
|
+
|
|
4845
|
+
review.scores.maintainability = Math.max(0, 100 -
|
|
4846
|
+
(avgFunctionsPerFile > 10 ? 20 : 0) -
|
|
4847
|
+
(commentRatio < 10 ? 15 : 0) -
|
|
4848
|
+
(totalClasses === 0 ? 10 : 0));
|
|
4849
|
+
|
|
4850
|
+
// Test coverage estimation
|
|
4851
|
+
review.scores.testCoverage = Math.min(100, testFiles * 20);
|
|
4852
|
+
|
|
4853
|
+
// Add maintainability metrics
|
|
4854
|
+
review.maintainabilityMetrics.push({
|
|
4855
|
+
metric: 'Comment Ratio',
|
|
4856
|
+
value: `${commentRatio.toFixed(1)}%`,
|
|
4857
|
+
assessment: commentRatio > 15 ? 'Good' : commentRatio > 5 ? 'Fair' : 'Poor',
|
|
4858
|
+
suggestion: commentRatio < 10 ? 'Add more documentation' : null
|
|
4859
|
+
});
|
|
4860
|
+
|
|
4861
|
+
review.maintainabilityMetrics.push({
|
|
4862
|
+
metric: 'Total Lines of Code',
|
|
4863
|
+
value: totalLines.toString(),
|
|
4864
|
+
assessment: totalLines < 5000 ? 'Manageable' : totalLines < 15000 ? 'Large' : 'Very Large'
|
|
4865
|
+
});
|
|
4866
|
+
|
|
4867
|
+
review.maintainabilityMetrics.push({
|
|
4868
|
+
metric: 'Functions Count',
|
|
4869
|
+
value: totalFunctions.toString(),
|
|
4870
|
+
assessment: 'Active'
|
|
4871
|
+
});
|
|
4872
|
+
|
|
4873
|
+
review.maintainabilityMetrics.push({
|
|
4874
|
+
metric: 'Classes Count',
|
|
4875
|
+
value: totalClasses.toString(),
|
|
4876
|
+
assessment: totalClasses > 0 ? 'Object-oriented' : 'Functional'
|
|
4877
|
+
});
|
|
4878
|
+
|
|
4879
|
+
// Generate AI-powered recommendations
|
|
4880
|
+
if (qualityIssueCount > 0) {
|
|
4881
|
+
review.recommendations.push({
|
|
4882
|
+
category: 'Code Quality',
|
|
4883
|
+
suggestion: 'Address code quality issues to improve maintainability',
|
|
4884
|
+
priority: 'High'
|
|
4885
|
+
});
|
|
4886
|
+
}
|
|
4887
|
+
|
|
4888
|
+
if (review.scores.testCoverage < 70) {
|
|
4889
|
+
review.recommendations.push({
|
|
4890
|
+
category: 'Testing',
|
|
4891
|
+
suggestion: 'Increase test coverage with unit and integration tests',
|
|
4892
|
+
priority: 'High'
|
|
4893
|
+
});
|
|
4894
|
+
}
|
|
4895
|
+
|
|
4896
|
+
if (commentRatio < 10) {
|
|
4897
|
+
review.recommendations.push({
|
|
4898
|
+
category: 'Documentation',
|
|
4899
|
+
suggestion: 'Add more inline documentation and comments',
|
|
4900
|
+
priority: 'Medium'
|
|
4901
|
+
});
|
|
4902
|
+
}
|
|
4903
|
+
|
|
4904
|
+
review.recommendations.push({
|
|
4905
|
+
category: 'Architecture',
|
|
4906
|
+
suggestion: 'Consider implementing design patterns for better code organization',
|
|
4907
|
+
priority: 'Medium'
|
|
4908
|
+
});
|
|
4909
|
+
|
|
4910
|
+
review.recommendations.push({
|
|
4911
|
+
category: 'Performance',
|
|
4912
|
+
suggestion: 'Profile critical code paths and optimize bottlenecks',
|
|
4913
|
+
priority: 'Low'
|
|
4914
|
+
});
|
|
4915
|
+
|
|
4916
|
+
review.recommendations.push({
|
|
4917
|
+
category: 'Security',
|
|
4918
|
+
suggestion: 'Implement input validation and security best practices',
|
|
4919
|
+
priority: 'High'
|
|
4920
|
+
});
|
|
4921
|
+
|
|
4922
|
+
return review;
|
|
4923
|
+
}
|
|
4924
|
+
|
|
4925
|
+
analyzeCodeFile(filePath, qualityPatterns, maintainabilityPatterns, architecturePatterns, review) {
|
|
4926
|
+
const fs = require('fs');
|
|
4927
|
+
|
|
4928
|
+
try {
|
|
4929
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
4930
|
+
const lines = content.split('\n');
|
|
4931
|
+
|
|
4932
|
+
// Check for quality issues
|
|
4933
|
+
for (const patternInfo of qualityPatterns) {
|
|
4934
|
+
if (patternInfo.pattern.test(content)) {
|
|
4935
|
+
review.qualityIssues.push({
|
|
4936
|
+
type: patternInfo.type,
|
|
4937
|
+
description: patternInfo.description,
|
|
4938
|
+
file: filePath.replace(process.cwd(), '.'),
|
|
4939
|
+
line: this.findPatternLine(lines, patternInfo.pattern)
|
|
4940
|
+
});
|
|
4941
|
+
}
|
|
4942
|
+
}
|
|
4943
|
+
|
|
4944
|
+
// Check architecture patterns
|
|
4945
|
+
for (const patternInfo of architecturePatterns) {
|
|
4946
|
+
if (patternInfo.pattern.test(content)) {
|
|
4947
|
+
review.architecture.push({
|
|
4948
|
+
type: patternInfo.type,
|
|
4949
|
+
description: patternInfo.description,
|
|
4950
|
+
file: filePath.replace(process.cwd(), '.')
|
|
4951
|
+
});
|
|
4952
|
+
}
|
|
4953
|
+
}
|
|
4954
|
+
|
|
4955
|
+
} catch (error) {
|
|
4956
|
+
// Skip files we can't read
|
|
4957
|
+
}
|
|
4958
|
+
}
|
|
4959
|
+
|
|
4960
|
+
findPatternLine(lines, pattern) {
|
|
4961
|
+
for (let i = 0; i < lines.length; i++) {
|
|
4962
|
+
if (pattern.test(lines[i])) {
|
|
4963
|
+
return i + 1;
|
|
4964
|
+
}
|
|
4965
|
+
}
|
|
4966
|
+
return 1;
|
|
4967
|
+
}
|
|
4968
|
+
|
|
4969
|
+
performSecurityAnalysis() {
|
|
4970
|
+
console.log(chalk.cyan('🛡️ Performing Security Analysis...'));
|
|
4971
|
+
console.log('');
|
|
4972
|
+
|
|
4973
|
+
console.log(chalk.magenta('╭─────────────────────────────────────────╮'));
|
|
4974
|
+
console.log(chalk.magenta('│') + chalk.white.bold(' 🛡️ Security Vulnerability Scan ') + chalk.magenta('│'));
|
|
4975
|
+
console.log(chalk.magenta('╰─────────────────────────────────────────╯'));
|
|
4976
|
+
console.log('');
|
|
4977
|
+
|
|
4978
|
+
const securityReport = this.performRealSecurityScan();
|
|
4979
|
+
|
|
4980
|
+
// Display security score
|
|
4981
|
+
let scoreColor = 'green';
|
|
4982
|
+
if (securityReport.score < 70) scoreColor = 'red';
|
|
4983
|
+
else if (securityReport.score < 85) scoreColor = 'yellow';
|
|
4984
|
+
|
|
4985
|
+
console.log(chalk[scoreColor](`🛡️ Security Score: ${securityReport.score}/100`));
|
|
4986
|
+
console.log('');
|
|
4987
|
+
|
|
4988
|
+
// Display vulnerabilities by severity
|
|
4989
|
+
if (securityReport.vulnerabilities.critical.length > 0) {
|
|
4990
|
+
console.log(chalk.red('🚨 Critical Vulnerabilities:'));
|
|
4991
|
+
securityReport.vulnerabilities.critical.forEach(vuln => {
|
|
4992
|
+
console.log(chalk.red(` • ${vuln.type}: ${vuln.description}`));
|
|
4993
|
+
console.log(chalk.gray(` File: ${vuln.file}:${vuln.line}`));
|
|
4994
|
+
});
|
|
4995
|
+
console.log('');
|
|
4996
|
+
}
|
|
4997
|
+
|
|
4998
|
+
if (securityReport.vulnerabilities.high.length > 0) {
|
|
4999
|
+
console.log(chalk.red('⚠️ High Risk Issues:'));
|
|
5000
|
+
securityReport.vulnerabilities.high.forEach(vuln => {
|
|
5001
|
+
console.log(chalk.yellow(` • ${vuln.type}: ${vuln.description}`));
|
|
5002
|
+
console.log(chalk.gray(` File: ${vuln.file}:${vuln.line}`));
|
|
5003
|
+
});
|
|
5004
|
+
console.log('');
|
|
5005
|
+
}
|
|
5006
|
+
|
|
5007
|
+
if (securityReport.vulnerabilities.medium.length > 0) {
|
|
5008
|
+
console.log(chalk.yellow('⚠️ Medium Risk Issues:'));
|
|
5009
|
+
securityReport.vulnerabilities.medium.forEach(vuln => {
|
|
5010
|
+
console.log(chalk.yellow(` • ${vuln.type}: ${vuln.description}`));
|
|
5011
|
+
console.log(chalk.gray(` File: ${vuln.file}:${vuln.line}`));
|
|
5012
|
+
});
|
|
5013
|
+
console.log('');
|
|
5014
|
+
}
|
|
5015
|
+
|
|
5016
|
+
if (securityReport.vulnerabilities.low.length > 0) {
|
|
5017
|
+
console.log(chalk.cyan('ℹ️ Low Risk Issues:'));
|
|
5018
|
+
securityReport.vulnerabilities.low.forEach(vuln => {
|
|
5019
|
+
console.log(chalk.cyan(` • ${vuln.type}: ${vuln.description}`));
|
|
5020
|
+
console.log(chalk.gray(` File: ${vuln.file}:${vuln.line}`));
|
|
5021
|
+
});
|
|
5022
|
+
console.log('');
|
|
5023
|
+
}
|
|
5024
|
+
|
|
5025
|
+
// Show dependency vulnerabilities
|
|
5026
|
+
if (securityReport.dependencyIssues.length > 0) {
|
|
5027
|
+
console.log(chalk.yellow('📦 Dependency Vulnerabilities:'));
|
|
5028
|
+
securityReport.dependencyIssues.forEach(dep => {
|
|
5029
|
+
console.log(chalk.yellow(` • ${dep.name}@${dep.version}: ${dep.issue}`));
|
|
5030
|
+
console.log(chalk.gray(` Severity: ${dep.severity}, Fix: ${dep.fix}`));
|
|
5031
|
+
});
|
|
5032
|
+
console.log('');
|
|
5033
|
+
}
|
|
5034
|
+
|
|
5035
|
+
// Show recommendations
|
|
5036
|
+
if (securityReport.recommendations.length > 0) {
|
|
5037
|
+
console.log(chalk.cyan('💡 Security Recommendations:'));
|
|
5038
|
+
securityReport.recommendations.forEach(rec => {
|
|
5039
|
+
console.log(chalk.white(` • ${rec}`));
|
|
5040
|
+
});
|
|
5041
|
+
console.log('');
|
|
5042
|
+
}
|
|
5043
|
+
|
|
5044
|
+
// Summary
|
|
5045
|
+
const totalIssues = securityReport.vulnerabilities.critical.length +
|
|
5046
|
+
securityReport.vulnerabilities.high.length +
|
|
5047
|
+
securityReport.vulnerabilities.medium.length +
|
|
5048
|
+
securityReport.vulnerabilities.low.length;
|
|
5049
|
+
|
|
5050
|
+
if (totalIssues === 0) {
|
|
5051
|
+
console.log(chalk.green('✅ No security vulnerabilities detected in code scan'));
|
|
5052
|
+
} else {
|
|
5053
|
+
console.log(chalk.yellow(`📊 Total Issues Found: ${totalIssues}`));
|
|
5054
|
+
}
|
|
5055
|
+
}
|
|
5056
|
+
|
|
5057
|
+
performRealSecurityScan() {
|
|
5058
|
+
const fs = require('fs');
|
|
5059
|
+
const path = require('path');
|
|
5060
|
+
|
|
5061
|
+
const securityReport = {
|
|
5062
|
+
score: 100,
|
|
5063
|
+
vulnerabilities: {
|
|
5064
|
+
critical: [],
|
|
5065
|
+
high: [],
|
|
5066
|
+
medium: [],
|
|
5067
|
+
low: []
|
|
5068
|
+
},
|
|
5069
|
+
dependencyIssues: [],
|
|
5070
|
+
recommendations: []
|
|
5071
|
+
};
|
|
5072
|
+
|
|
5073
|
+
// Security patterns to detect
|
|
5074
|
+
const securityPatterns = {
|
|
5075
|
+
critical: [
|
|
5076
|
+
{ pattern: /password\s*=\s*['"][^'"]+['"]/, type: 'Hardcoded Password', description: 'Hardcoded password detected' },
|
|
5077
|
+
{ pattern: /api[_-]?key\s*=\s*['"][^'"]+['"]/, type: 'Hardcoded API Key', description: 'Hardcoded API key detected' },
|
|
5078
|
+
{ pattern: /secret\s*=\s*['"][^'"]+['"]/, type: 'Hardcoded Secret', description: 'Hardcoded secret detected' },
|
|
5079
|
+
{ pattern: /eval\s*\(/, type: 'Code Injection', description: 'Use of eval() detected - potential code injection risk' }
|
|
5080
|
+
],
|
|
5081
|
+
high: [
|
|
5082
|
+
{ pattern: /exec\s*\(.*\$/, type: 'Command Injection', description: 'Potential command injection vulnerability' },
|
|
5083
|
+
{ pattern: /innerHTML\s*=/, type: 'XSS Risk', description: 'Use of innerHTML - potential XSS vulnerability' },
|
|
5084
|
+
{ pattern: /document\.write\s*\(/, type: 'XSS Risk', description: 'Use of document.write - potential XSS vulnerability' },
|
|
5085
|
+
{ pattern: /sql\s*=.*\+/, type: 'SQL Injection', description: 'Potential SQL injection vulnerability' }
|
|
5086
|
+
],
|
|
5087
|
+
medium: [
|
|
5088
|
+
{ pattern: /console\.log\s*\(.*password/, type: 'Information Disclosure', description: 'Password logged to console' },
|
|
5089
|
+
{ pattern: /console\.log\s*\(.*token/, type: 'Information Disclosure', description: 'Token logged to console' },
|
|
5090
|
+
{ pattern: /http:\/\//, type: 'Insecure Protocol', description: 'Use of insecure HTTP protocol' },
|
|
5091
|
+
{ pattern: /Math\.random\s*\(\)/, type: 'Weak Cryptography', description: 'Use of Math.random() for security purposes' }
|
|
5092
|
+
],
|
|
5093
|
+
low: [
|
|
5094
|
+
{ pattern: /TODO.*security/, type: 'Security TODO', description: 'Security-related TODO comment' },
|
|
5095
|
+
{ pattern: /FIXME.*security/, type: 'Security FIXME', description: 'Security-related FIXME comment' },
|
|
5096
|
+
{ pattern: /\.trim\s*\(\)\.length/, type: 'Input Validation', description: 'Consider more robust input validation' }
|
|
5097
|
+
]
|
|
5098
|
+
};
|
|
5099
|
+
|
|
5100
|
+
// Scan files for security issues
|
|
5101
|
+
const scanDirectory = (dir, depth = 0) => {
|
|
5102
|
+
if (depth > 3) return;
|
|
5103
|
+
|
|
5104
|
+
try {
|
|
5105
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
5106
|
+
|
|
5107
|
+
for (const entry of entries) {
|
|
5108
|
+
const fullPath = path.join(dir, entry.name);
|
|
5109
|
+
|
|
5110
|
+
if (entry.name.startsWith('.') || entry.name === 'node_modules') continue;
|
|
5111
|
+
|
|
5112
|
+
if (entry.isFile() && this.isCodeFile(entry.name)) {
|
|
5113
|
+
this.scanFileForSecurity(fullPath, securityPatterns, securityReport);
|
|
5114
|
+
} else if (entry.isDirectory()) {
|
|
5115
|
+
scanDirectory(fullPath, depth + 1);
|
|
5116
|
+
}
|
|
5117
|
+
}
|
|
5118
|
+
} catch (error) {
|
|
5119
|
+
// Skip directories we can't read
|
|
5120
|
+
}
|
|
5121
|
+
};
|
|
5122
|
+
|
|
5123
|
+
scanDirectory(process.cwd());
|
|
5124
|
+
|
|
5125
|
+
// Check dependencies for known vulnerabilities
|
|
5126
|
+
this.checkDependencyVulnerabilities(securityReport);
|
|
5127
|
+
|
|
5128
|
+
// Calculate final score
|
|
5129
|
+
const criticalCount = securityReport.vulnerabilities.critical.length;
|
|
5130
|
+
const highCount = securityReport.vulnerabilities.high.length;
|
|
5131
|
+
const mediumCount = securityReport.vulnerabilities.medium.length;
|
|
5132
|
+
const lowCount = securityReport.vulnerabilities.low.length;
|
|
5133
|
+
|
|
5134
|
+
securityReport.score = Math.max(0, 100 - (criticalCount * 25) - (highCount * 15) - (mediumCount * 10) - (lowCount * 5));
|
|
5135
|
+
|
|
5136
|
+
// Generate recommendations based on findings
|
|
5137
|
+
if (criticalCount > 0) {
|
|
5138
|
+
securityReport.recommendations.push('🚨 Address critical vulnerabilities immediately');
|
|
5139
|
+
}
|
|
5140
|
+
if (highCount > 0) {
|
|
5141
|
+
securityReport.recommendations.push('⚠️ Review and fix high-risk security issues');
|
|
5142
|
+
}
|
|
5143
|
+
|
|
5144
|
+
securityReport.recommendations.push('🔒 Use environment variables for sensitive data');
|
|
5145
|
+
securityReport.recommendations.push('🛡️ Implement input validation and sanitization');
|
|
5146
|
+
securityReport.recommendations.push('📦 Keep dependencies updated regularly');
|
|
5147
|
+
securityReport.recommendations.push('🔍 Consider using a security linter (ESLint security plugin)');
|
|
5148
|
+
|
|
5149
|
+
return securityReport;
|
|
5150
|
+
}
|
|
5151
|
+
|
|
5152
|
+
isCodeFile(filename) {
|
|
5153
|
+
const codeExtensions = ['.js', '.ts', '.py', '.java', '.cpp', '.c', '.cs', '.go', '.rs', '.php', '.rb'];
|
|
5154
|
+
return codeExtensions.some(ext => filename.toLowerCase().endsWith(ext));
|
|
5155
|
+
}
|
|
5156
|
+
|
|
5157
|
+
scanFileForSecurity(filePath, patterns, report) {
|
|
5158
|
+
const fs = require('fs');
|
|
5159
|
+
|
|
5160
|
+
try {
|
|
5161
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
5162
|
+
const lines = content.split('\n');
|
|
5163
|
+
|
|
5164
|
+
for (const [severity, patternList] of Object.entries(patterns)) {
|
|
5165
|
+
for (const patternInfo of patternList) {
|
|
5166
|
+
for (let i = 0; i < lines.length; i++) {
|
|
5167
|
+
if (patternInfo.pattern.test(lines[i])) {
|
|
5168
|
+
report.vulnerabilities[severity].push({
|
|
5169
|
+
type: patternInfo.type,
|
|
5170
|
+
description: patternInfo.description,
|
|
5171
|
+
file: filePath.replace(process.cwd(), '.'),
|
|
5172
|
+
line: i + 1,
|
|
5173
|
+
code: lines[i].trim()
|
|
5174
|
+
});
|
|
5175
|
+
}
|
|
5176
|
+
}
|
|
5177
|
+
}
|
|
5178
|
+
}
|
|
5179
|
+
} catch (error) {
|
|
5180
|
+
// Skip files we can't read
|
|
5181
|
+
}
|
|
5182
|
+
}
|
|
5183
|
+
|
|
5184
|
+
checkDependencyVulnerabilities(report) {
|
|
5185
|
+
const fs = require('fs');
|
|
5186
|
+
const path = require('path');
|
|
5187
|
+
|
|
5188
|
+
try {
|
|
5189
|
+
const packagePath = path.join(process.cwd(), 'package.json');
|
|
5190
|
+
if (!fs.existsSync(packagePath)) return;
|
|
5191
|
+
|
|
5192
|
+
const packageJson = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
|
|
5193
|
+
const dependencies = { ...packageJson.dependencies, ...packageJson.devDependencies };
|
|
5194
|
+
|
|
5195
|
+
// Check for known vulnerable packages (simplified check)
|
|
5196
|
+
const knownVulnerablePackages = {
|
|
5197
|
+
'lodash': { versions: ['< 4.17.21'], issue: 'Prototype pollution vulnerability', severity: 'High', fix: 'Update to latest version' },
|
|
5198
|
+
'node-forge': { versions: ['< 1.0.0'], issue: 'RSA PKCS#1 signature verification issue', severity: 'High', fix: 'Update to 1.0.0+' },
|
|
5199
|
+
'axios': { versions: ['< 0.21.2'], issue: 'Server-side request forgery', severity: 'Medium', fix: 'Update to 0.21.2+' }
|
|
5200
|
+
};
|
|
5201
|
+
|
|
5202
|
+
for (const [depName, version] of Object.entries(dependencies)) {
|
|
5203
|
+
if (knownVulnerablePackages[depName]) {
|
|
5204
|
+
const vuln = knownVulnerablePackages[depName];
|
|
5205
|
+
report.dependencyIssues.push({
|
|
5206
|
+
name: depName,
|
|
5207
|
+
version: version,
|
|
5208
|
+
issue: vuln.issue,
|
|
5209
|
+
severity: vuln.severity,
|
|
5210
|
+
fix: vuln.fix
|
|
5211
|
+
});
|
|
5212
|
+
}
|
|
5213
|
+
}
|
|
5214
|
+
} catch (error) {
|
|
5215
|
+
// Could not check dependencies
|
|
5216
|
+
}
|
|
5217
|
+
}
|
|
5218
|
+
|
|
5219
|
+
performOptimizationAnalysis() {
|
|
5220
|
+
console.log(chalk.cyan('⚡ Analyzing Performance...'));
|
|
5221
|
+
console.log('');
|
|
5222
|
+
|
|
5223
|
+
console.log(chalk.magenta('╭─────────────────────────────────────────╮'));
|
|
5224
|
+
console.log(chalk.magenta('│') + chalk.white.bold(' ⚡ Performance Analysis ') + chalk.magenta('│'));
|
|
5225
|
+
console.log(chalk.magenta('╰─────────────────────────────────────────╯'));
|
|
5226
|
+
console.log('');
|
|
5227
|
+
|
|
5228
|
+
const perfReport = this.performRealPerformanceAnalysis();
|
|
5229
|
+
|
|
5230
|
+
// Display performance score
|
|
5231
|
+
let scoreColor = 'green';
|
|
5232
|
+
if (perfReport.score < 60) scoreColor = 'red';
|
|
5233
|
+
else if (perfReport.score < 80) scoreColor = 'yellow';
|
|
5234
|
+
|
|
5235
|
+
console.log(chalk[scoreColor](`⚡ Performance Score: ${perfReport.score}/100`));
|
|
5236
|
+
console.log('');
|
|
5237
|
+
|
|
5238
|
+
// Display bottlenecks
|
|
5239
|
+
if (perfReport.bottlenecks.length > 0) {
|
|
5240
|
+
console.log(chalk.yellow('🐌 Performance Bottlenecks Detected:'));
|
|
5241
|
+
perfReport.bottlenecks.forEach(bottleneck => {
|
|
5242
|
+
console.log(chalk.white(` • ${bottleneck.type}: ${bottleneck.description}`));
|
|
5243
|
+
console.log(chalk.gray(` File: ${bottleneck.file}:${bottleneck.line}`));
|
|
5244
|
+
});
|
|
5245
|
+
console.log('');
|
|
5246
|
+
}
|
|
5247
|
+
|
|
5248
|
+
// Display file size issues
|
|
5249
|
+
if (perfReport.fileSizeIssues.length > 0) {
|
|
5250
|
+
console.log(chalk.yellow('📁 Large File Issues:'));
|
|
5251
|
+
perfReport.fileSizeIssues.forEach(issue => {
|
|
5252
|
+
console.log(chalk.white(` • ${issue.file}: ${issue.sizeKB}KB (${issue.description})`));
|
|
5253
|
+
});
|
|
5254
|
+
console.log('');
|
|
5255
|
+
}
|
|
5256
|
+
|
|
5257
|
+
// Display dependency analysis
|
|
5258
|
+
if (perfReport.dependencyImpact.heavyDependencies.length > 0) {
|
|
5259
|
+
console.log(chalk.yellow('📦 Heavy Dependencies:'));
|
|
5260
|
+
perfReport.dependencyImpact.heavyDependencies.forEach(dep => {
|
|
5261
|
+
console.log(chalk.white(` • ${dep}: Large bundle size impact`));
|
|
5262
|
+
});
|
|
5263
|
+
console.log('');
|
|
5264
|
+
}
|
|
5265
|
+
|
|
5266
|
+
// Display optimization suggestions
|
|
5267
|
+
if (perfReport.optimizations.length > 0) {
|
|
5268
|
+
console.log(chalk.cyan('🚀 Optimization Suggestions:'));
|
|
5269
|
+
perfReport.optimizations.forEach(opt => {
|
|
5270
|
+
console.log(chalk.white(` • ${opt}`));
|
|
5271
|
+
});
|
|
5272
|
+
console.log('');
|
|
5273
|
+
}
|
|
5274
|
+
|
|
5275
|
+
// Show memory usage patterns
|
|
5276
|
+
if (perfReport.memoryPatterns.length > 0) {
|
|
5277
|
+
console.log(chalk.blue('🧠 Memory Usage Patterns:'));
|
|
5278
|
+
perfReport.memoryPatterns.forEach(pattern => {
|
|
5279
|
+
console.log(chalk.white(` • ${pattern.type}: ${pattern.description}`));
|
|
5280
|
+
if (pattern.file) {
|
|
5281
|
+
console.log(chalk.gray(` File: ${pattern.file}:${pattern.line}`));
|
|
5282
|
+
}
|
|
5283
|
+
});
|
|
5284
|
+
console.log('');
|
|
5285
|
+
}
|
|
5286
|
+
|
|
5287
|
+
// Summary
|
|
5288
|
+
const totalIssues = perfReport.bottlenecks.length + perfReport.fileSizeIssues.length;
|
|
5289
|
+
if (totalIssues === 0) {
|
|
5290
|
+
console.log(chalk.green('✅ No major performance issues detected'));
|
|
5291
|
+
} else {
|
|
5292
|
+
console.log(chalk.yellow(`📊 Total Performance Issues: ${totalIssues}`));
|
|
5293
|
+
}
|
|
5294
|
+
}
|
|
5295
|
+
|
|
5296
|
+
performRealPerformanceAnalysis() {
|
|
5297
|
+
const fs = require('fs');
|
|
5298
|
+
const path = require('path');
|
|
5299
|
+
|
|
5300
|
+
const perfReport = {
|
|
5301
|
+
score: 100,
|
|
5302
|
+
bottlenecks: [],
|
|
5303
|
+
fileSizeIssues: [],
|
|
5304
|
+
dependencyImpact: {
|
|
5305
|
+
heavyDependencies: [],
|
|
5306
|
+
totalDependencies: 0
|
|
5307
|
+
},
|
|
5308
|
+
memoryPatterns: [],
|
|
5309
|
+
optimizations: []
|
|
5310
|
+
};
|
|
5311
|
+
|
|
5312
|
+
// Performance anti-patterns to detect
|
|
5313
|
+
const performancePatterns = [
|
|
5314
|
+
{ pattern: /for\s*\(.*\.length.*\)/, type: 'Loop Optimization', description: 'Loop with .length in condition - cache length outside loop' },
|
|
5315
|
+
{ pattern: /document\.getElementById.*\(/, type: 'DOM Query', description: 'Repeated DOM queries - consider caching elements' },
|
|
5316
|
+
{ pattern: /\$\(.*\)\..*\$\(.*\)/, type: 'jQuery Chaining', description: 'Multiple jQuery selectors - consider chaining or caching' },
|
|
5317
|
+
{ pattern: /setInterval\s*\(.*1\)/, type: 'High Frequency Timer', description: 'Very high frequency interval detected' },
|
|
5318
|
+
{ pattern: /JSON\.parse\s*\(.*JSON\.stringify/, type: 'Deep Clone', description: 'Inefficient deep cloning - consider structured clone or lodash' },
|
|
5319
|
+
{ pattern: /new\s+Array\s*\(/, type: 'Array Constructor', description: 'Use array literal [] instead of new Array()' },
|
|
5320
|
+
{ pattern: /\.innerHTML\s*\+=/, type: 'DOM Manipulation', description: 'Inefficient DOM updates - consider batch operations' },
|
|
5321
|
+
{ pattern: /require\s*\(.*\).*require\s*\(.*\)/, type: 'Module Loading', description: 'Multiple requires in same scope - consider combining imports' }
|
|
5322
|
+
];
|
|
5323
|
+
|
|
5324
|
+
// Memory leak patterns
|
|
5325
|
+
const memoryPatterns = [
|
|
5326
|
+
{ pattern: /addEventListener.*(?!removeEventListener)/, type: 'Event Listener Leak', description: 'Event listener without cleanup' },
|
|
5327
|
+
{ pattern: /setInterval.*(?!clearInterval)/, type: 'Timer Leak', description: 'setInterval without clearInterval' },
|
|
5328
|
+
{ pattern: /setTimeout.*(?!clearTimeout)/, type: 'Timer Leak', description: 'setTimeout pattern that might not clear' },
|
|
5329
|
+
{ pattern: /closure.*function.*closure/, type: 'Closure Chain', description: 'Potential closure memory retention' }
|
|
5330
|
+
];
|
|
5331
|
+
|
|
5332
|
+
// Scan files for performance issues
|
|
5333
|
+
const scanDirectory = (dir, depth = 0) => {
|
|
5334
|
+
if (depth > 3) return;
|
|
5335
|
+
|
|
5336
|
+
try {
|
|
5337
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
5338
|
+
|
|
5339
|
+
for (const entry of entries) {
|
|
5340
|
+
const fullPath = path.join(dir, entry.name);
|
|
5341
|
+
|
|
5342
|
+
if (entry.name.startsWith('.') || entry.name === 'node_modules') continue;
|
|
5343
|
+
|
|
5344
|
+
if (entry.isFile()) {
|
|
5345
|
+
// Check file size
|
|
5346
|
+
const stats = fs.statSync(fullPath);
|
|
5347
|
+
const sizeKB = Math.round(stats.size / 1024);
|
|
5348
|
+
|
|
5349
|
+
if (sizeKB > 500 && this.isCodeFile(entry.name)) {
|
|
5350
|
+
perfReport.fileSizeIssues.push({
|
|
5351
|
+
file: fullPath.replace(process.cwd(), '.'),
|
|
5352
|
+
sizeKB: sizeKB,
|
|
5353
|
+
description: sizeKB > 1000 ? 'Very large file - consider splitting' : 'Large file - review for optimization'
|
|
5354
|
+
});
|
|
5355
|
+
}
|
|
5356
|
+
|
|
5357
|
+
// Scan code files for performance patterns
|
|
5358
|
+
if (this.isCodeFile(entry.name)) {
|
|
5359
|
+
this.scanFileForPerformance(fullPath, performancePatterns, memoryPatterns, perfReport);
|
|
5360
|
+
}
|
|
5361
|
+
} else if (entry.isDirectory()) {
|
|
5362
|
+
scanDirectory(fullPath, depth + 1);
|
|
5363
|
+
}
|
|
5364
|
+
}
|
|
5365
|
+
} catch (error) {
|
|
5366
|
+
// Skip directories we can't read
|
|
5367
|
+
}
|
|
5368
|
+
};
|
|
5369
|
+
|
|
5370
|
+
scanDirectory(process.cwd());
|
|
5371
|
+
|
|
5372
|
+
// Analyze dependencies
|
|
5373
|
+
this.analyzeDependencyPerformance(perfReport);
|
|
5374
|
+
|
|
5375
|
+
// Calculate score based on findings
|
|
5376
|
+
const bottleneckCount = perfReport.bottlenecks.length;
|
|
5377
|
+
const fileSizeIssueCount = perfReport.fileSizeIssues.length;
|
|
5378
|
+
const memoryIssueCount = perfReport.memoryPatterns.length;
|
|
5379
|
+
|
|
5380
|
+
perfReport.score = Math.max(0, 100 - (bottleneckCount * 10) - (fileSizeIssueCount * 5) - (memoryIssueCount * 8));
|
|
5381
|
+
|
|
5382
|
+
// Generate optimization recommendations
|
|
5383
|
+
if (bottleneckCount > 0) {
|
|
5384
|
+
perfReport.optimizations.push('🔧 Address performance bottlenecks in critical code paths');
|
|
5385
|
+
}
|
|
5386
|
+
if (fileSizeIssueCount > 0) {
|
|
5387
|
+
perfReport.optimizations.push('📦 Consider code splitting for large files');
|
|
5388
|
+
}
|
|
5389
|
+
if (memoryIssueCount > 0) {
|
|
5390
|
+
perfReport.optimizations.push('🧠 Implement proper memory management and cleanup');
|
|
5391
|
+
}
|
|
5392
|
+
|
|
5393
|
+
perfReport.optimizations.push('⚡ Use Web Workers for CPU-intensive tasks');
|
|
5394
|
+
perfReport.optimizations.push('🗂️ Implement lazy loading for non-critical resources');
|
|
5395
|
+
perfReport.optimizations.push('📊 Add performance monitoring and profiling');
|
|
5396
|
+
perfReport.optimizations.push('🔄 Consider using virtual scrolling for large lists');
|
|
5397
|
+
|
|
5398
|
+
return perfReport;
|
|
5399
|
+
}
|
|
5400
|
+
|
|
5401
|
+
scanFileForPerformance(filePath, performancePatterns, memoryPatterns, report) {
|
|
5402
|
+
const fs = require('fs');
|
|
5403
|
+
|
|
5404
|
+
try {
|
|
5405
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
5406
|
+
const lines = content.split('\n');
|
|
5407
|
+
|
|
5408
|
+
// Check for performance bottlenecks
|
|
5409
|
+
for (const patternInfo of performancePatterns) {
|
|
5410
|
+
for (let i = 0; i < lines.length; i++) {
|
|
5411
|
+
if (patternInfo.pattern.test(lines[i])) {
|
|
5412
|
+
report.bottlenecks.push({
|
|
5413
|
+
type: patternInfo.type,
|
|
5414
|
+
description: patternInfo.description,
|
|
5415
|
+
file: filePath.replace(process.cwd(), '.'),
|
|
5416
|
+
line: i + 1,
|
|
5417
|
+
code: lines[i].trim()
|
|
5418
|
+
});
|
|
5419
|
+
}
|
|
5420
|
+
}
|
|
5421
|
+
}
|
|
5422
|
+
|
|
5423
|
+
// Check for memory patterns
|
|
5424
|
+
for (const patternInfo of memoryPatterns) {
|
|
5425
|
+
for (let i = 0; i < lines.length; i++) {
|
|
5426
|
+
if (patternInfo.pattern.test(lines[i])) {
|
|
5427
|
+
report.memoryPatterns.push({
|
|
5428
|
+
type: patternInfo.type,
|
|
5429
|
+
description: patternInfo.description,
|
|
5430
|
+
file: filePath.replace(process.cwd(), '.'),
|
|
5431
|
+
line: i + 1,
|
|
5432
|
+
code: lines[i].trim()
|
|
5433
|
+
});
|
|
5434
|
+
}
|
|
5435
|
+
}
|
|
5436
|
+
}
|
|
5437
|
+
} catch (error) {
|
|
5438
|
+
// Skip files we can't read
|
|
5439
|
+
}
|
|
5440
|
+
}
|
|
5441
|
+
|
|
5442
|
+
analyzeDependencyPerformance(report) {
|
|
5443
|
+
const fs = require('fs');
|
|
5444
|
+
const path = require('path');
|
|
5445
|
+
|
|
5446
|
+
try {
|
|
5447
|
+
const packagePath = path.join(process.cwd(), 'package.json');
|
|
5448
|
+
if (!fs.existsSync(packagePath)) return;
|
|
5449
|
+
|
|
5450
|
+
const packageJson = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
|
|
5451
|
+
const dependencies = { ...packageJson.dependencies, ...packageJson.devDependencies };
|
|
5452
|
+
|
|
5453
|
+
report.dependencyImpact.totalDependencies = Object.keys(dependencies).length;
|
|
5454
|
+
|
|
5455
|
+
// Known heavy dependencies that impact performance
|
|
5456
|
+
const heavyDependencies = ['lodash', 'moment', 'jquery', 'axios', 'react', 'vue', 'angular'];
|
|
5457
|
+
|
|
5458
|
+
for (const depName of Object.keys(dependencies)) {
|
|
5459
|
+
if (heavyDependencies.includes(depName)) {
|
|
5460
|
+
report.dependencyImpact.heavyDependencies.push(depName);
|
|
5461
|
+
}
|
|
5462
|
+
}
|
|
5463
|
+
|
|
5464
|
+
// Add recommendations based on dependency analysis
|
|
5465
|
+
if (dependencies['lodash']) {
|
|
5466
|
+
report.optimizations.push('Consider using native ES6 methods instead of lodash where possible');
|
|
5467
|
+
}
|
|
5468
|
+
if (dependencies['moment']) {
|
|
5469
|
+
report.optimizations.push('Consider using date-fns or dayjs instead of moment.js for smaller bundle size');
|
|
5470
|
+
}
|
|
5471
|
+
|
|
5472
|
+
} catch (error) {
|
|
5473
|
+
// Skip dependency analysis if package.json is not readable
|
|
5474
|
+
}
|
|
5475
|
+
}
|
|
5476
|
+
|
|
5477
|
+
cycleTheme() {
|
|
5478
|
+
if (!this.currentTheme) this.currentTheme = 'neon-purple';
|
|
5479
|
+
|
|
5480
|
+
const themes = ['neon-purple', 'matrix-green', 'ocean-blue', 'sunset-orange'];
|
|
5481
|
+
const currentIndex = themes.indexOf(this.currentTheme);
|
|
5482
|
+
const nextIndex = (currentIndex + 1) % themes.length;
|
|
5483
|
+
this.currentTheme = themes[nextIndex];
|
|
5484
|
+
|
|
5485
|
+
console.log(chalk.cyan(`🎨 Theme switched to: ${this.currentTheme}`));
|
|
5486
|
+
console.log(chalk.gray('New color scheme will apply to future messages'));
|
|
5487
|
+
console.log('');
|
|
5488
|
+
}
|
|
5489
|
+
|
|
5490
|
+
async cleanup() {
|
|
5491
|
+
this.logger.info('Starting cleanup process');
|
|
5492
|
+
|
|
5493
|
+
// Clean up streaming and loading animations
|
|
5494
|
+
this.stopProgressiveLoading();
|
|
5495
|
+
this.stopStreamingDisplay();
|
|
5496
|
+
|
|
5497
|
+
if (this.fileWatcher) {
|
|
5498
|
+
this.fileWatcher.close();
|
|
5499
|
+
this.fileWatcher = null;
|
|
5500
|
+
this.logger.debug('File watcher closed');
|
|
5501
|
+
}
|
|
5502
|
+
|
|
5503
|
+
// Cleanup voice manager
|
|
5504
|
+
if (this.voiceManager) {
|
|
5505
|
+
this.voiceManager.cleanup();
|
|
5506
|
+
this.logger.debug('Voice manager cleaned up');
|
|
5507
|
+
}
|
|
5508
|
+
|
|
5509
|
+
// Cleanup collaboration manager
|
|
5510
|
+
if (this.collaborationManager) {
|
|
5511
|
+
this.collaborationManager.cleanup();
|
|
5512
|
+
this.logger.debug('Collaboration manager cleaned up');
|
|
5513
|
+
}
|
|
5514
|
+
|
|
5515
|
+
// Cleanup plugins
|
|
5516
|
+
if (this.pluginManager) {
|
|
5517
|
+
await this.pluginManager.cleanup();
|
|
5518
|
+
this.logger.debug('Plugins cleaned up');
|
|
5519
|
+
}
|
|
5520
|
+
|
|
5521
|
+
// End session logging
|
|
5522
|
+
if (this.logger) {
|
|
5523
|
+
this.logger.endSession();
|
|
5524
|
+
this.logger.info('Session cleanup completed');
|
|
5525
|
+
}
|
|
5526
|
+
}
|
|
5527
|
+
|
|
5528
|
+
/**
|
|
5529
|
+
* Load user configuration from onboarding
|
|
5530
|
+
*/
|
|
5531
|
+
loadUserConfig() {
|
|
5532
|
+
const path = require('path');
|
|
5533
|
+
const fs = require('fs');
|
|
5534
|
+
const configFile = path.join(require('os').homedir(), '.recoder-code', 'config.json');
|
|
5535
|
+
|
|
5536
|
+
try {
|
|
5537
|
+
if (fs.existsSync(configFile)) {
|
|
5538
|
+
return JSON.parse(fs.readFileSync(configFile, 'utf8'));
|
|
5539
|
+
}
|
|
5540
|
+
} catch (error) {
|
|
5541
|
+
// Return empty config if can't load
|
|
5542
|
+
}
|
|
5543
|
+
|
|
5544
|
+
return {};
|
|
5545
|
+
}
|
|
5546
|
+
|
|
3454
5547
|
start() {
|
|
3455
5548
|
this.rl.prompt();
|
|
3456
5549
|
}
|