recoder-code 2.2.1 → 2.2.3
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 +9 -7
- package/cli/enhanced-setup.js +3 -1
- package/cli/file-watcher-manager.js +216 -0
- package/cli/interactive.js +1527 -85
- package/cli/logger.js +181 -0
- package/cli/mcp-client.js +32 -9
- package/cli/ml-training-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/task-manager.js
CHANGED
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
const fs = require('fs');
|
|
4
4
|
const path = require('path');
|
|
5
|
-
|
|
5
|
+
// Handle chalk properly
|
|
6
|
+
const chalkModule = require('chalk');
|
|
7
|
+
const chalk = chalkModule.default || chalkModule;
|
|
6
8
|
const { EventEmitter } = require('events');
|
|
7
9
|
|
|
8
10
|
/**
|
|
@@ -482,7 +484,7 @@ class TaskManager extends EventEmitter {
|
|
|
482
484
|
console.log(chalk.gray('🤖 Starting automation engine...'));
|
|
483
485
|
|
|
484
486
|
// Set up file watchers for automated triggers
|
|
485
|
-
if (this.contextEngine && this.contextEngine.fileWatcher) {
|
|
487
|
+
if (this.contextEngine && this.contextEngine.fileWatcher && typeof this.contextEngine.on === 'function') {
|
|
486
488
|
this.contextEngine.on('fileChanged', (filePath, eventType) => {
|
|
487
489
|
this.handleFileChangeAutomation(filePath, eventType);
|
|
488
490
|
});
|
|
@@ -1080,13 +1082,19 @@ class TaskManager extends EventEmitter {
|
|
|
1080
1082
|
this.setupGitHooksAutomation();
|
|
1081
1083
|
|
|
1082
1084
|
// Dependency change automation
|
|
1083
|
-
this.setupDependencyAutomation
|
|
1085
|
+
if (typeof this.setupDependencyAutomation === 'function') {
|
|
1086
|
+
this.setupDependencyAutomation();
|
|
1087
|
+
}
|
|
1084
1088
|
|
|
1085
1089
|
// Performance monitoring automation
|
|
1086
|
-
this.setupPerformanceAutomation
|
|
1090
|
+
if (typeof this.setupPerformanceAutomation === 'function') {
|
|
1091
|
+
this.setupPerformanceAutomation();
|
|
1092
|
+
}
|
|
1087
1093
|
|
|
1088
1094
|
// Security monitoring automation
|
|
1089
|
-
this.setupSecurityAutomation
|
|
1095
|
+
if (typeof this.setupSecurityAutomation === 'function') {
|
|
1096
|
+
this.setupSecurityAutomation();
|
|
1097
|
+
}
|
|
1090
1098
|
}
|
|
1091
1099
|
|
|
1092
1100
|
/**
|
|
@@ -7,7 +7,9 @@
|
|
|
7
7
|
|
|
8
8
|
const fs = require('fs').promises;
|
|
9
9
|
const path = require('path');
|
|
10
|
-
|
|
10
|
+
// Handle chalk properly
|
|
11
|
+
const chalkModule = require('chalk');
|
|
12
|
+
const chalk = chalkModule.default || chalkModule;;
|
|
11
13
|
const crypto = require('crypto');
|
|
12
14
|
const WebSocket = require('ws');
|
|
13
15
|
const EventEmitter = require('events');
|
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
// Handle chalk properly
|
|
6
|
+
const chalkModule = require('chalk');
|
|
7
|
+
const chalk = chalkModule.default || chalkModule;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Production-Ready Todo Management System for Recoder Code
|
|
11
|
+
* Provides visual todo management with checkboxes and status tracking
|
|
12
|
+
*/
|
|
13
|
+
class TodoManager {
|
|
14
|
+
constructor(options = {}) {
|
|
15
|
+
this.todoFile = options.todoFile || path.join(process.cwd(), '.recoder-todos.json');
|
|
16
|
+
this.todos = [];
|
|
17
|
+
this.currentUser = options.user || this.getCurrentUser();
|
|
18
|
+
this.loadTodos();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
getCurrentUser() {
|
|
22
|
+
return process.env.USER || process.env.USERNAME || 'developer';
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Load todos from file
|
|
27
|
+
*/
|
|
28
|
+
loadTodos() {
|
|
29
|
+
try {
|
|
30
|
+
if (fs.existsSync(this.todoFile)) {
|
|
31
|
+
const data = JSON.parse(fs.readFileSync(this.todoFile, 'utf8'));
|
|
32
|
+
this.todos = data.todos || [];
|
|
33
|
+
}
|
|
34
|
+
} catch (error) {
|
|
35
|
+
console.log(chalk.yellow(`⚠️ Could not load todos: ${error.message}`));
|
|
36
|
+
this.todos = [];
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Save todos to file
|
|
42
|
+
*/
|
|
43
|
+
saveTodos() {
|
|
44
|
+
try {
|
|
45
|
+
const data = {
|
|
46
|
+
todos: this.todos,
|
|
47
|
+
lastUpdated: new Date().toISOString(),
|
|
48
|
+
user: this.currentUser
|
|
49
|
+
};
|
|
50
|
+
fs.writeFileSync(this.todoFile, JSON.stringify(data, null, 2));
|
|
51
|
+
} catch (error) {
|
|
52
|
+
console.log(chalk.red(`❌ Could not save todos: ${error.message}`));
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Add a new todo
|
|
58
|
+
*/
|
|
59
|
+
addTodo(content, priority = 'medium', category = 'general') {
|
|
60
|
+
const todo = {
|
|
61
|
+
id: this.generateId(),
|
|
62
|
+
content: content,
|
|
63
|
+
status: 'pending',
|
|
64
|
+
priority: priority,
|
|
65
|
+
category: category,
|
|
66
|
+
createdAt: new Date().toISOString(),
|
|
67
|
+
createdBy: this.currentUser,
|
|
68
|
+
updatedAt: new Date().toISOString()
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
this.todos.push(todo);
|
|
72
|
+
this.saveTodos();
|
|
73
|
+
return todo.id;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Update todo status
|
|
78
|
+
*/
|
|
79
|
+
updateTodoStatus(id, status) {
|
|
80
|
+
const todo = this.todos.find(t => t.id === id);
|
|
81
|
+
if (todo) {
|
|
82
|
+
todo.status = status;
|
|
83
|
+
todo.updatedAt = new Date().toISOString();
|
|
84
|
+
this.saveTodos();
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Mark todo as completed
|
|
92
|
+
*/
|
|
93
|
+
completeTodo(id) {
|
|
94
|
+
return this.updateTodoStatus(id, 'completed');
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Mark todo as in progress
|
|
99
|
+
*/
|
|
100
|
+
startTodo(id) {
|
|
101
|
+
return this.updateTodoStatus(id, 'in_progress');
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Delete a todo
|
|
106
|
+
*/
|
|
107
|
+
deleteTodo(id) {
|
|
108
|
+
const index = this.todos.findIndex(t => t.id === id);
|
|
109
|
+
if (index !== -1) {
|
|
110
|
+
this.todos.splice(index, 1);
|
|
111
|
+
this.saveTodos();
|
|
112
|
+
return true;
|
|
113
|
+
}
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Display todos with visual interface like shown
|
|
119
|
+
*/
|
|
120
|
+
displayTodos(showCompleted = true) {
|
|
121
|
+
console.log(chalk.cyan.bold('Update Todos'));
|
|
122
|
+
|
|
123
|
+
if (this.todos.length === 0) {
|
|
124
|
+
console.log(chalk.gray(' ⎿ No todos found. Add one with: /todo add <task>'));
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Group todos by status
|
|
129
|
+
const pending = this.todos.filter(t => t.status === 'pending');
|
|
130
|
+
const inProgress = this.todos.filter(t => t.status === 'in_progress');
|
|
131
|
+
const completed = this.todos.filter(t => t.status === 'completed');
|
|
132
|
+
|
|
133
|
+
// Display in progress first
|
|
134
|
+
if (inProgress.length > 0) {
|
|
135
|
+
console.log(chalk.yellow(' ⎿ ◐ In Progress:'));
|
|
136
|
+
inProgress.forEach(todo => {
|
|
137
|
+
console.log(chalk.yellow(` ◐ ${todo.content}`));
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Display pending todos
|
|
142
|
+
if (pending.length > 0) {
|
|
143
|
+
console.log(chalk.white(' ⎿ ☐ Pending:'));
|
|
144
|
+
pending.forEach(todo => {
|
|
145
|
+
const priorityIcon = this.getPriorityIcon(todo.priority);
|
|
146
|
+
console.log(chalk.white(` ☐ ${priorityIcon} ${todo.content}`));
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Display completed todos if requested
|
|
151
|
+
if (showCompleted && completed.length > 0) {
|
|
152
|
+
console.log(chalk.green(' ⎿ ☒ Completed:'));
|
|
153
|
+
completed.forEach(todo => {
|
|
154
|
+
console.log(chalk.green(` ☒ ${todo.content}`));
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Get priority icon
|
|
161
|
+
*/
|
|
162
|
+
getPriorityIcon(priority) {
|
|
163
|
+
switch (priority) {
|
|
164
|
+
case 'high': return '🔴';
|
|
165
|
+
case 'medium': return '🟡';
|
|
166
|
+
case 'low': return '🟢';
|
|
167
|
+
default: return '⚪';
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Display todos by category
|
|
173
|
+
*/
|
|
174
|
+
displayTodosByCategory() {
|
|
175
|
+
const categories = {};
|
|
176
|
+
this.todos.forEach(todo => {
|
|
177
|
+
if (!categories[todo.category]) {
|
|
178
|
+
categories[todo.category] = [];
|
|
179
|
+
}
|
|
180
|
+
categories[todo.category].push(todo);
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
Object.keys(categories).forEach(category => {
|
|
184
|
+
console.log(chalk.cyan.bold(`\n📋 ${category.toUpperCase()}`));
|
|
185
|
+
categories[category].forEach(todo => {
|
|
186
|
+
const statusIcon = this.getStatusIcon(todo.status);
|
|
187
|
+
const priorityIcon = this.getPriorityIcon(todo.priority);
|
|
188
|
+
console.log(` ${statusIcon} ${priorityIcon} ${todo.content}`);
|
|
189
|
+
});
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Get status icon
|
|
195
|
+
*/
|
|
196
|
+
getStatusIcon(status) {
|
|
197
|
+
switch (status) {
|
|
198
|
+
case 'completed': return chalk.green('☒');
|
|
199
|
+
case 'in_progress': return chalk.yellow('◐');
|
|
200
|
+
case 'pending': return chalk.white('☐');
|
|
201
|
+
default: return '⚪';
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Generate unique ID
|
|
207
|
+
*/
|
|
208
|
+
generateId() {
|
|
209
|
+
return Date.now().toString(36) + Math.random().toString(36).substr(2);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Get statistics
|
|
214
|
+
*/
|
|
215
|
+
getStats() {
|
|
216
|
+
const total = this.todos.length;
|
|
217
|
+
const completed = this.todos.filter(t => t.status === 'completed').length;
|
|
218
|
+
const inProgress = this.todos.filter(t => t.status === 'in_progress').length;
|
|
219
|
+
const pending = this.todos.filter(t => t.status === 'pending').length;
|
|
220
|
+
|
|
221
|
+
return {
|
|
222
|
+
total,
|
|
223
|
+
completed,
|
|
224
|
+
inProgress,
|
|
225
|
+
pending,
|
|
226
|
+
completionRate: total > 0 ? Math.round((completed / total) * 100) : 0
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Display statistics
|
|
232
|
+
*/
|
|
233
|
+
displayStats() {
|
|
234
|
+
const stats = this.getStats();
|
|
235
|
+
console.log(chalk.cyan.bold('📊 Todo Statistics'));
|
|
236
|
+
console.log(chalk.white(`Total: ${stats.total}`));
|
|
237
|
+
console.log(chalk.green(`Completed: ${stats.completed}`));
|
|
238
|
+
console.log(chalk.yellow(`In Progress: ${stats.inProgress}`));
|
|
239
|
+
console.log(chalk.gray(`Pending: ${stats.pending}`));
|
|
240
|
+
console.log(chalk.blue(`Completion Rate: ${stats.completionRate}%`));
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Clear completed todos
|
|
245
|
+
*/
|
|
246
|
+
clearCompleted() {
|
|
247
|
+
const beforeCount = this.todos.length;
|
|
248
|
+
this.todos = this.todos.filter(t => t.status !== 'completed');
|
|
249
|
+
const removedCount = beforeCount - this.todos.length;
|
|
250
|
+
|
|
251
|
+
if (removedCount > 0) {
|
|
252
|
+
this.saveTodos();
|
|
253
|
+
console.log(chalk.green(`✅ Cleared ${removedCount} completed todos`));
|
|
254
|
+
} else {
|
|
255
|
+
console.log(chalk.gray('No completed todos to clear'));
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Search todos
|
|
261
|
+
*/
|
|
262
|
+
searchTodos(query) {
|
|
263
|
+
const results = this.todos.filter(todo =>
|
|
264
|
+
todo.content.toLowerCase().includes(query.toLowerCase()) ||
|
|
265
|
+
todo.category.toLowerCase().includes(query.toLowerCase())
|
|
266
|
+
);
|
|
267
|
+
|
|
268
|
+
if (results.length === 0) {
|
|
269
|
+
console.log(chalk.gray(`No todos found matching: ${query}`));
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
console.log(chalk.cyan.bold(`🔍 Search Results for "${query}":`));
|
|
274
|
+
results.forEach(todo => {
|
|
275
|
+
const statusIcon = this.getStatusIcon(todo.status);
|
|
276
|
+
const priorityIcon = this.getPriorityIcon(todo.priority);
|
|
277
|
+
console.log(` ${statusIcon} ${priorityIcon} ${todo.content}`);
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Import todos from existing TodoWrite format
|
|
283
|
+
*/
|
|
284
|
+
importFromTodoWrite(todoWriteTodos) {
|
|
285
|
+
todoWriteTodos.forEach(todo => {
|
|
286
|
+
this.addTodo(todo.content, 'medium', 'imported');
|
|
287
|
+
if (todo.status !== 'pending') {
|
|
288
|
+
this.updateTodoStatus(this.todos[this.todos.length - 1].id, todo.status);
|
|
289
|
+
}
|
|
290
|
+
});
|
|
291
|
+
console.log(chalk.green(`✅ Imported ${todoWriteTodos.length} todos`));
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
module.exports = TodoManager;
|
|
296
|
+
|
|
297
|
+
// CLI interface when run directly
|
|
298
|
+
if (require.main === module) {
|
|
299
|
+
const todoManager = new TodoManager();
|
|
300
|
+
const command = process.argv[2];
|
|
301
|
+
const args = process.argv.slice(3);
|
|
302
|
+
|
|
303
|
+
switch (command) {
|
|
304
|
+
case 'add':
|
|
305
|
+
if (args.length === 0) {
|
|
306
|
+
console.log('Usage: todo-manager.js add <content> [priority] [category]');
|
|
307
|
+
process.exit(1);
|
|
308
|
+
}
|
|
309
|
+
const content = args[0];
|
|
310
|
+
const priority = args[1] || 'medium';
|
|
311
|
+
const category = args[2] || 'general';
|
|
312
|
+
const id = todoManager.addTodo(content, priority, category);
|
|
313
|
+
console.log(chalk.green(`✅ Added todo: ${id}`));
|
|
314
|
+
break;
|
|
315
|
+
|
|
316
|
+
case 'list':
|
|
317
|
+
todoManager.displayTodos();
|
|
318
|
+
break;
|
|
319
|
+
|
|
320
|
+
case 'complete':
|
|
321
|
+
if (args.length === 0) {
|
|
322
|
+
console.log('Usage: todo-manager.js complete <id>');
|
|
323
|
+
process.exit(1);
|
|
324
|
+
}
|
|
325
|
+
if (todoManager.completeTodo(args[0])) {
|
|
326
|
+
console.log(chalk.green('✅ Todo marked as completed'));
|
|
327
|
+
} else {
|
|
328
|
+
console.log(chalk.red('❌ Todo not found'));
|
|
329
|
+
}
|
|
330
|
+
break;
|
|
331
|
+
|
|
332
|
+
case 'start':
|
|
333
|
+
if (args.length === 0) {
|
|
334
|
+
console.log('Usage: todo-manager.js start <id>');
|
|
335
|
+
process.exit(1);
|
|
336
|
+
}
|
|
337
|
+
if (todoManager.startTodo(args[0])) {
|
|
338
|
+
console.log(chalk.yellow('◐ Todo marked as in progress'));
|
|
339
|
+
} else {
|
|
340
|
+
console.log(chalk.red('❌ Todo not found'));
|
|
341
|
+
}
|
|
342
|
+
break;
|
|
343
|
+
|
|
344
|
+
case 'delete':
|
|
345
|
+
if (args.length === 0) {
|
|
346
|
+
console.log('Usage: todo-manager.js delete <id>');
|
|
347
|
+
process.exit(1);
|
|
348
|
+
}
|
|
349
|
+
if (todoManager.deleteTodo(args[0])) {
|
|
350
|
+
console.log(chalk.green('✅ Todo deleted'));
|
|
351
|
+
} else {
|
|
352
|
+
console.log(chalk.red('❌ Todo not found'));
|
|
353
|
+
}
|
|
354
|
+
break;
|
|
355
|
+
|
|
356
|
+
case 'stats':
|
|
357
|
+
todoManager.displayStats();
|
|
358
|
+
break;
|
|
359
|
+
|
|
360
|
+
case 'clear':
|
|
361
|
+
todoManager.clearCompleted();
|
|
362
|
+
break;
|
|
363
|
+
|
|
364
|
+
case 'search':
|
|
365
|
+
if (args.length === 0) {
|
|
366
|
+
console.log('Usage: todo-manager.js search <query>');
|
|
367
|
+
process.exit(1);
|
|
368
|
+
}
|
|
369
|
+
todoManager.searchTodos(args[0]);
|
|
370
|
+
break;
|
|
371
|
+
|
|
372
|
+
case 'categories':
|
|
373
|
+
todoManager.displayTodosByCategory();
|
|
374
|
+
break;
|
|
375
|
+
|
|
376
|
+
default:
|
|
377
|
+
console.log(chalk.cyan.bold('Recoder Code - Todo Manager'));
|
|
378
|
+
console.log('');
|
|
379
|
+
console.log('Usage:');
|
|
380
|
+
console.log(' todo-manager.js add <content> [priority] [category]');
|
|
381
|
+
console.log(' todo-manager.js list');
|
|
382
|
+
console.log(' todo-manager.js complete <id>');
|
|
383
|
+
console.log(' todo-manager.js start <id>');
|
|
384
|
+
console.log(' todo-manager.js delete <id>');
|
|
385
|
+
console.log(' todo-manager.js stats');
|
|
386
|
+
console.log(' todo-manager.js clear');
|
|
387
|
+
console.log(' todo-manager.js search <query>');
|
|
388
|
+
console.log(' todo-manager.js categories');
|
|
389
|
+
break;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const chalkModule = require('chalk');
|
|
4
|
+
const chalk = chalkModule.default || chalkModule;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Unified Error Handler for Recoder Code
|
|
8
|
+
* Coordinates all error handling to prevent conflicts
|
|
9
|
+
*/
|
|
10
|
+
class UnifiedErrorHandler {
|
|
11
|
+
constructor() {
|
|
12
|
+
this.isInitialized = false;
|
|
13
|
+
this.errorHandlers = new Map();
|
|
14
|
+
this.errorStats = {
|
|
15
|
+
api: 0,
|
|
16
|
+
mcp: 0,
|
|
17
|
+
network: 0,
|
|
18
|
+
general: 0
|
|
19
|
+
};
|
|
20
|
+
this.suppressedErrors = new Set([
|
|
21
|
+
'ENOTFOUND your-resource.openai.azure.com', // Expected MCP connection failure
|
|
22
|
+
'ECONNREFUSED', // Expected for localhost services
|
|
23
|
+
'socket hang up', // Expected for unavailable services
|
|
24
|
+
'EMFILE: too many open files', // File watcher resource exhaustion
|
|
25
|
+
'spawn EBADF' // Process spawn errors for unavailable tools
|
|
26
|
+
]);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Initialize unified error handling (call once)
|
|
31
|
+
*/
|
|
32
|
+
initialize() {
|
|
33
|
+
if (this.isInitialized) return;
|
|
34
|
+
|
|
35
|
+
// Remove any existing handlers to prevent conflicts
|
|
36
|
+
process.removeAllListeners('unhandledRejection');
|
|
37
|
+
process.removeAllListeners('uncaughtException');
|
|
38
|
+
|
|
39
|
+
// Set up unified handlers
|
|
40
|
+
process.on('unhandledRejection', (reason, promise) => {
|
|
41
|
+
this.handleUnhandledRejection(reason, promise);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
process.on('uncaughtException', (error) => {
|
|
45
|
+
this.handleUncaughtException(error);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
this.isInitialized = true;
|
|
49
|
+
console.log(chalk.gray('🛡️ Unified error handler initialized'));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Register a specific error handler for a domain
|
|
54
|
+
*/
|
|
55
|
+
registerHandler(domain, handler) {
|
|
56
|
+
this.errorHandlers.set(domain, handler);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Handle unhandled promise rejections
|
|
61
|
+
*/
|
|
62
|
+
handleUnhandledRejection(reason, promise) {
|
|
63
|
+
const errorInfo = this.analyzeError(reason);
|
|
64
|
+
|
|
65
|
+
// Try domain-specific handlers first
|
|
66
|
+
if (this.tryDomainHandler(errorInfo.domain, reason)) {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Handle based on error type
|
|
71
|
+
switch (errorInfo.type) {
|
|
72
|
+
case 'api':
|
|
73
|
+
this.handleAPIError(reason);
|
|
74
|
+
break;
|
|
75
|
+
case 'mcp':
|
|
76
|
+
this.handleMCPError(reason);
|
|
77
|
+
break;
|
|
78
|
+
case 'network':
|
|
79
|
+
this.handleNetworkError(reason);
|
|
80
|
+
break;
|
|
81
|
+
default:
|
|
82
|
+
this.handleGenericError(reason, 'unhandled_rejection');
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Handle uncaught exceptions
|
|
88
|
+
*/
|
|
89
|
+
handleUncaughtException(error) {
|
|
90
|
+
const errorInfo = this.analyzeError(error);
|
|
91
|
+
|
|
92
|
+
// Try domain-specific handlers first
|
|
93
|
+
if (this.tryDomainHandler(errorInfo.domain, error)) {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Log critical error
|
|
98
|
+
console.error(chalk.red('🚨 Critical Error:'), error.message);
|
|
99
|
+
if (process.env.RECODER_DEBUG === 'true') {
|
|
100
|
+
console.error(error.stack);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Don't exit process - maintain availability
|
|
104
|
+
this.errorStats.general++;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Analyze error to determine type and handling strategy
|
|
109
|
+
*/
|
|
110
|
+
analyzeError(error) {
|
|
111
|
+
const message = error?.message || error?.toString() || '';
|
|
112
|
+
const code = error?.code || '';
|
|
113
|
+
const status = error?.response?.status || error?.status;
|
|
114
|
+
|
|
115
|
+
// API errors
|
|
116
|
+
if (status || message.includes('credits') || message.includes('OpenRouter')) {
|
|
117
|
+
return { type: 'api', domain: 'openrouter', severity: 'medium' };
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// MCP connection errors
|
|
121
|
+
if (message.includes('your-resource.openai.azure.com') ||
|
|
122
|
+
message.includes('localhost:8080') ||
|
|
123
|
+
code === 'ENOTFOUND' ||
|
|
124
|
+
code === 'ECONNREFUSED' ||
|
|
125
|
+
message.includes('socket hang up')) {
|
|
126
|
+
return { type: 'mcp', domain: 'mcp', severity: 'low' };
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Network errors
|
|
130
|
+
if (code === 'ENOTFOUND' || code === 'ECONNREFUSED' || message.includes('network')) {
|
|
131
|
+
return { type: 'network', domain: 'network', severity: 'medium' };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return { type: 'generic', domain: 'general', severity: 'high' };
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Try domain-specific handler
|
|
139
|
+
*/
|
|
140
|
+
tryDomainHandler(domain, error) {
|
|
141
|
+
const handler = this.errorHandlers.get(domain);
|
|
142
|
+
if (handler) {
|
|
143
|
+
try {
|
|
144
|
+
handler(error);
|
|
145
|
+
return true;
|
|
146
|
+
} catch (handlerError) {
|
|
147
|
+
console.log(chalk.yellow(`⚠️ Error handler for ${domain} failed:`, handlerError.message));
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return false;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Handle API errors (402, 401, etc.)
|
|
155
|
+
*/
|
|
156
|
+
handleAPIError(error) {
|
|
157
|
+
const status = error?.response?.status || error?.status;
|
|
158
|
+
|
|
159
|
+
if (status) {
|
|
160
|
+
switch (status) {
|
|
161
|
+
case 401:
|
|
162
|
+
console.error(chalk.red('🔑 Authentication failed - check your API key'));
|
|
163
|
+
break;
|
|
164
|
+
case 402:
|
|
165
|
+
console.error(chalk.yellow('💳 Insufficient credits - add credits to your account'));
|
|
166
|
+
break;
|
|
167
|
+
case 429:
|
|
168
|
+
console.error(chalk.yellow('⏱️ Rate limited - please wait before trying again'));
|
|
169
|
+
break;
|
|
170
|
+
default:
|
|
171
|
+
console.error(chalk.red(`🌐 API Error (${status}):`, error.message));
|
|
172
|
+
}
|
|
173
|
+
} else {
|
|
174
|
+
console.error(chalk.red('🌐 API Error:'), error.message);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
this.errorStats.api++;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Handle MCP connection errors (graceful degradation)
|
|
182
|
+
*/
|
|
183
|
+
handleMCPError(error) {
|
|
184
|
+
const message = error?.message || error?.toString();
|
|
185
|
+
|
|
186
|
+
// Check if this is a suppressed (expected) error
|
|
187
|
+
if (this.suppressedErrors.has(message) ||
|
|
188
|
+
this.suppressedErrors.some(suppressed => message.includes(suppressed))) {
|
|
189
|
+
// Silently track but don't display
|
|
190
|
+
this.errorStats.mcp++;
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Log non-critical MCP errors quietly
|
|
195
|
+
if (process.env.RECODER_DEBUG === 'true') {
|
|
196
|
+
console.log(chalk.gray('🔌 MCP service unavailable:'), message);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
this.errorStats.mcp++;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Handle network errors
|
|
204
|
+
*/
|
|
205
|
+
handleNetworkError(error) {
|
|
206
|
+
const message = error?.message || error?.toString();
|
|
207
|
+
|
|
208
|
+
// Check if this is a suppressed error first
|
|
209
|
+
if (this.suppressedErrors.has(message) ||
|
|
210
|
+
this.suppressedErrors.some(suppressed => message.includes(suppressed))) {
|
|
211
|
+
this.errorStats.network++;
|
|
212
|
+
return; // Silently handle
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
console.log(chalk.yellow('🌐 Network issue detected - some features may be limited'));
|
|
216
|
+
if (process.env.RECODER_DEBUG === 'true') {
|
|
217
|
+
console.log(chalk.gray('Details:'), error.message);
|
|
218
|
+
}
|
|
219
|
+
this.errorStats.network++;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Handle generic errors
|
|
224
|
+
*/
|
|
225
|
+
handleGenericError(error, type = 'generic') {
|
|
226
|
+
console.error(chalk.red('⚠️ Error:'), error?.message || error);
|
|
227
|
+
if (process.env.RECODER_DEBUG === 'true' && error?.stack) {
|
|
228
|
+
console.error(chalk.gray('Stack:'), error.stack);
|
|
229
|
+
}
|
|
230
|
+
this.errorStats.general++;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Get error statistics
|
|
235
|
+
*/
|
|
236
|
+
getStats() {
|
|
237
|
+
return { ...this.errorStats };
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Reset error statistics
|
|
242
|
+
*/
|
|
243
|
+
resetStats() {
|
|
244
|
+
this.errorStats = { api: 0, mcp: 0, network: 0, general: 0 };
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Check if system is healthy (low error rates)
|
|
249
|
+
*/
|
|
250
|
+
isHealthy() {
|
|
251
|
+
const total = Object.values(this.errorStats).reduce((a, b) => a + b, 0);
|
|
252
|
+
return total < 10 && this.errorStats.general < 3;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Global singleton instance
|
|
257
|
+
const unifiedErrorHandler = new UnifiedErrorHandler();
|
|
258
|
+
|
|
259
|
+
module.exports = UnifiedErrorHandler;
|
|
260
|
+
module.exports.instance = unifiedErrorHandler;
|
package/cli/user-experience.js
CHANGED
|
@@ -7,7 +7,9 @@
|
|
|
7
7
|
|
|
8
8
|
const { Command } = require('commander');
|
|
9
9
|
const inquirer = require('inquirer');
|
|
10
|
-
|
|
10
|
+
// Handle chalk properly
|
|
11
|
+
const chalkModule = require('chalk');
|
|
12
|
+
const chalk = chalkModule.default || chalkModule;
|
|
11
13
|
const axios = require('axios');
|
|
12
14
|
const fs = require('fs');
|
|
13
15
|
const path = require('path');
|