vibecodingmachine-core 2025.12.25-1541 → 2026.1.3-2209
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/ERROR_REPORTING_API.md +212 -0
- package/ERROR_REPORTING_USAGE.md +380 -0
- package/__tests__/utils/git-branch-manager.test.js +61 -0
- package/package.json +1 -1
- package/src/database/user-database-client.js +26 -0
- package/src/database/user-schema.js +7 -0
- package/src/ide-integration/applescript-manager.cjs +26 -6
- package/src/ide-integration/applescript-manager.js +6 -5
- package/src/ide-integration/applescript-utils.js +26 -18
- package/src/index.cjs +4 -0
- package/src/index.js +4 -0
- package/src/localization/translations/en.js +2 -0
- package/src/localization/translations/es.js +2 -0
- package/src/requirement-numbering.js +164 -0
- package/src/utils/error-reporter.js +109 -0
- package/src/utils/git-branch-manager.js +278 -0
- package/src/utils/requirement-helpers.js +10 -1
- package/src/utils/requirements-parser.js +25 -0
|
@@ -335,7 +335,7 @@ class AppleScriptManager {
|
|
|
335
335
|
};
|
|
336
336
|
}
|
|
337
337
|
|
|
338
|
-
const ideName = ide === 'windsurf' ? 'Windsurf' : ide === 'cursor' ? 'Cursor' : ide === 'antigravity' ? 'Antigravity' : ide === 'kiro' ? 'AWS Kiro' : 'Unknown IDE';
|
|
338
|
+
const ideName = ide === 'windsurf' ? 'Windsurf' : ide === 'cursor' ? 'Cursor' : ide === 'antigravity' ? 'Antigravity' : ide === 'kiro' ? 'AWS Kiro' : (ide === 'claude' || ide === 'claude-code') ? 'Claude' : 'Unknown IDE';
|
|
339
339
|
|
|
340
340
|
this.logger.log(t('auto.direct.ide.detected', { ide: ideName }));
|
|
341
341
|
|
|
@@ -452,11 +452,11 @@ class AppleScriptManager {
|
|
|
452
452
|
on error
|
|
453
453
|
set processName to "Kiro"
|
|
454
454
|
end try
|
|
455
|
-
|
|
455
|
+
|
|
456
456
|
tell process processName
|
|
457
457
|
set frontmost to true
|
|
458
458
|
delay 1.0
|
|
459
|
-
|
|
459
|
+
|
|
460
460
|
-- Attempt to focus chat via standard AI IDE shortcuts
|
|
461
461
|
-- Try Cmd+L (Cursor/Windsurf standard)
|
|
462
462
|
try
|
|
@@ -469,22 +469,42 @@ class AppleScriptManager {
|
|
|
469
469
|
-- Use clipboard for more reliable text entry than keystrokes
|
|
470
470
|
set the clipboard to "${text.replace(/"/g, '\\"')}"
|
|
471
471
|
delay 0.3
|
|
472
|
-
|
|
472
|
+
|
|
473
473
|
-- Paste
|
|
474
474
|
key code 9 using {command down}
|
|
475
475
|
delay 0.5
|
|
476
|
-
|
|
476
|
+
|
|
477
477
|
-- Send Enter
|
|
478
478
|
key code 36
|
|
479
479
|
delay 0.5
|
|
480
480
|
end tell
|
|
481
481
|
end tell
|
|
482
482
|
`;
|
|
483
|
+
} else if (ide === 'claude' || ide === 'claude-code') {
|
|
484
|
+
// Claude CLI text sending - simplified Terminal approach
|
|
485
|
+
this.logger.log('🚀 [Claude] Sending text to Claude terminal...');
|
|
486
|
+
|
|
487
|
+
appleScript = `
|
|
488
|
+
tell application "System Events"
|
|
489
|
+
tell process "Terminal"
|
|
490
|
+
set frontmost to true
|
|
491
|
+
delay 0.5
|
|
492
|
+
|
|
493
|
+
-- Type the text
|
|
494
|
+
keystroke "${text.replace(/"/g, '\\"')}"
|
|
495
|
+
delay 1.0
|
|
496
|
+
|
|
497
|
+
-- Submit with return
|
|
498
|
+
keystroke return
|
|
499
|
+
delay 0.3
|
|
500
|
+
end tell
|
|
501
|
+
end tell
|
|
502
|
+
`;
|
|
483
503
|
} else {
|
|
484
504
|
return {
|
|
485
505
|
success: false,
|
|
486
506
|
error: `Unsupported IDE for AppleScript: ${ide}`,
|
|
487
|
-
note: 'AppleScript is only supported for Cursor, Windsurf, Antigravity, VS Code,
|
|
507
|
+
note: 'AppleScript is only supported for Cursor, Windsurf, Antigravity, VS Code, AWS Kiro, and Claude'
|
|
488
508
|
};
|
|
489
509
|
}
|
|
490
510
|
|
|
@@ -714,13 +714,14 @@ class AppleScriptManager {
|
|
|
714
714
|
case 'kiro':
|
|
715
715
|
return await this.openKiro(repoPath);
|
|
716
716
|
case 'claude':
|
|
717
|
+
case 'claude-code':
|
|
717
718
|
return await this.openClaude(repoPath);
|
|
718
719
|
case 'gemini':
|
|
719
720
|
return await this.openGemini(repoPath);
|
|
720
721
|
default:
|
|
721
722
|
return {
|
|
722
723
|
success: false,
|
|
723
|
-
error: `Unknown IDE: ${ide}. Supported: cursor, windsurf, antigravity, vscode, claude, gemini`
|
|
724
|
+
error: `Unknown IDE: ${ide}. Supported: cursor, windsurf, antigravity, vscode, claude, claude-code, gemini`
|
|
724
725
|
};
|
|
725
726
|
}
|
|
726
727
|
}
|
|
@@ -879,7 +880,7 @@ class AppleScriptManager {
|
|
|
879
880
|
};
|
|
880
881
|
}
|
|
881
882
|
|
|
882
|
-
const ideName = ide === 'windsurf' ? 'Windsurf' : ide === 'cursor' ? 'Cursor' : ide === 'antigravity' ? 'Antigravity' : ide === 'claude' ? 'Claude' : ide === 'vscode' ? 'VS Code' : ide === 'kiro' ? 'AWS Kiro' : 'Unknown IDE';
|
|
883
|
+
const ideName = ide === 'windsurf' ? 'Windsurf' : ide === 'cursor' ? 'Cursor' : ide === 'antigravity' ? 'Antigravity' : (ide === 'claude' || ide === 'claude-code') ? 'Claude' : ide === 'vscode' ? 'VS Code' : ide === 'kiro' ? 'AWS Kiro' : 'Unknown IDE';
|
|
883
884
|
|
|
884
885
|
this.logger.log(`🚀 [${ideName}] Starting text send on ${this.platform} platform`);
|
|
885
886
|
this.logger.log(`🚀 [${ideName}] Text to send: "${text}"`);
|
|
@@ -1035,7 +1036,7 @@ class AppleScriptManager {
|
|
|
1035
1036
|
end tell
|
|
1036
1037
|
end tell
|
|
1037
1038
|
`;
|
|
1038
|
-
} else if (ide === 'claude') {
|
|
1039
|
+
} else if (ide === 'claude' || ide === 'claude-code') {
|
|
1039
1040
|
// Use a different approach for Claude - find existing Claude terminal and send text
|
|
1040
1041
|
const targetRepoPath = repoPath || '/Users/jesse/code/mediawink/vibecodingmachine';
|
|
1041
1042
|
this.logger.log(`🔍 [Claude] Using repo path for terminal detection: "${targetRepoPath}" (passed: "${repoPath}")`);
|
|
@@ -1353,7 +1354,7 @@ class AppleScriptManager {
|
|
|
1353
1354
|
this.logger.log('AppleScript interaction failed:', error.message);
|
|
1354
1355
|
|
|
1355
1356
|
// For Claude, don't fall back to simulated response - return actual failure
|
|
1356
|
-
if (ide === 'claude') {
|
|
1357
|
+
if (ide === 'claude' || ide === 'claude-code') {
|
|
1357
1358
|
return {
|
|
1358
1359
|
success: false,
|
|
1359
1360
|
method: 'applescript',
|
|
@@ -2544,7 +2545,7 @@ end tell
|
|
|
2544
2545
|
const ideName = ide.toLowerCase();
|
|
2545
2546
|
|
|
2546
2547
|
// Handle Claude CLI separately since it's a terminal application
|
|
2547
|
-
if (ideName === 'claude') {
|
|
2548
|
+
if (ideName === 'claude' || ideName === 'claude-code') {
|
|
2548
2549
|
return await this.closeClaudeCLI();
|
|
2549
2550
|
}
|
|
2550
2551
|
|
|
@@ -54,21 +54,21 @@ end tell`;
|
|
|
54
54
|
try
|
|
55
55
|
-- Use Cmd+L to focus AI panel (faster and more reliable)
|
|
56
56
|
key code 37 using {command down} -- Cmd+L
|
|
57
|
-
delay
|
|
57
|
+
delay 2.0
|
|
58
58
|
|
|
59
59
|
-- Open new chat session with Cmd+T to prevent crashes
|
|
60
60
|
key code 17 using {command down} -- Cmd+T
|
|
61
|
-
delay
|
|
61
|
+
delay 3.0
|
|
62
62
|
|
|
63
63
|
-- Skip clearing text to avoid selecting file content
|
|
64
64
|
-- The chat input should be empty when opening new chat session
|
|
65
65
|
|
|
66
66
|
-- Wait additional time for chat input field to be fully ready
|
|
67
|
-
delay 1.
|
|
67
|
+
delay 1.5
|
|
68
68
|
|
|
69
69
|
-- Type the message
|
|
70
70
|
keystroke "${escapedText}"
|
|
71
|
-
delay
|
|
71
|
+
delay 2.0
|
|
72
72
|
|
|
73
73
|
-- Send with Cmd+Enter (standard for chat interfaces)
|
|
74
74
|
key code 36 using {command down}
|
|
@@ -86,33 +86,41 @@ end tell`;
|
|
|
86
86
|
on error
|
|
87
87
|
-- STRATEGY 2: Command Palette Approach (Fallback 1) - NO AI PANEL TOGGLE
|
|
88
88
|
try
|
|
89
|
-
-- Step 1:
|
|
89
|
+
-- Step 1: Press Escape first to ensure we're starting clean
|
|
90
|
+
key code 53 -- Escape
|
|
91
|
+
delay 0.5
|
|
92
|
+
|
|
93
|
+
-- Step 2: Open Command Palette (Cmd+Shift+P)
|
|
90
94
|
key code 35 using {command down, shift down} -- Cmd+Shift+P
|
|
91
|
-
delay 0
|
|
95
|
+
delay 1.0
|
|
92
96
|
|
|
93
|
-
-- Step
|
|
97
|
+
-- Step 3: Type "View: Focus into Secondary Side Bar" to focus AI panel
|
|
94
98
|
keystroke "View: Focus into Secondary Side Bar"
|
|
95
|
-
delay 0
|
|
99
|
+
delay 1.0
|
|
96
100
|
|
|
97
|
-
-- Step
|
|
101
|
+
-- Step 4: Press Enter to execute the command
|
|
98
102
|
key code 36 -- Enter
|
|
99
|
-
delay 2.
|
|
103
|
+
delay 2.5
|
|
100
104
|
|
|
101
|
-
-- Step
|
|
105
|
+
-- Step 5: CRITICAL - Press Escape to close command palette
|
|
106
|
+
key code 53 -- Escape
|
|
107
|
+
delay 0.5
|
|
108
|
+
|
|
109
|
+
-- Step 6: Open new chat session with Cmd+T to prevent crashes
|
|
102
110
|
key code 17 using {command down} -- Cmd+T
|
|
103
|
-
delay
|
|
111
|
+
delay 3.0
|
|
104
112
|
|
|
105
|
-
-- Step
|
|
113
|
+
-- Step 7: Skip clearing text to avoid selecting file content
|
|
106
114
|
-- The chat input should be empty when opening new chat session
|
|
107
115
|
|
|
108
|
-
-- Step
|
|
109
|
-
delay 1.
|
|
116
|
+
-- Step 8: Wait additional time for chat input field to be fully ready
|
|
117
|
+
delay 1.5
|
|
110
118
|
|
|
111
|
-
-- Step
|
|
119
|
+
-- Step 9: Type the message
|
|
112
120
|
keystroke "${escapedText}"
|
|
113
|
-
delay
|
|
121
|
+
delay 2.0
|
|
114
122
|
|
|
115
|
-
-- Step
|
|
123
|
+
-- Step 10: Send with Cmd+Enter (standard for chat interfaces)
|
|
116
124
|
key code 36 using {command down}
|
|
117
125
|
delay 0.5
|
|
118
126
|
|
package/src/index.cjs
CHANGED
|
@@ -20,6 +20,8 @@ const auditLogger = require('./utils/audit-logger.cjs');
|
|
|
20
20
|
const { GCloudAuth } = require('./utils/gcloud-auth.cjs');
|
|
21
21
|
const requirementHelpers = require('./utils/requirement-helpers.js');
|
|
22
22
|
const requirementsParser = require('./utils/requirements-parser.js');
|
|
23
|
+
const requirementNumbering = require('./requirement-numbering.js');
|
|
24
|
+
const gitBranchManager = require('./utils/git-branch-manager.js');
|
|
23
25
|
const updateChecker = require('./utils/update-checker.js');
|
|
24
26
|
const electronUpdateChecker = require('./utils/electron-update-checker.js');
|
|
25
27
|
const localization = require('./localization/index.js');
|
|
@@ -45,6 +47,8 @@ module.exports = {
|
|
|
45
47
|
...auditLogger,
|
|
46
48
|
...requirementHelpers,
|
|
47
49
|
...requirementsParser,
|
|
50
|
+
...requirementNumbering,
|
|
51
|
+
...gitBranchManager,
|
|
48
52
|
...updateChecker,
|
|
49
53
|
...electronUpdateChecker,
|
|
50
54
|
...localization
|
package/src/index.js
CHANGED
|
@@ -12,6 +12,7 @@ export * from './utils/repo-helpers.js';
|
|
|
12
12
|
export * from './utils/config-helpers.js';
|
|
13
13
|
export * from './utils/requirement-helpers.js';
|
|
14
14
|
export * from './utils/requirements-parser.js';
|
|
15
|
+
export * from './requirement-numbering.js';
|
|
15
16
|
|
|
16
17
|
// Compliance
|
|
17
18
|
const CompliancePrompt = require('./compliance/compliance-prompt.js');
|
|
@@ -24,3 +25,6 @@ export { ChatInterface } from './ui/ChatInterface.js';
|
|
|
24
25
|
|
|
25
26
|
// Localization
|
|
26
27
|
export * from './localization/index.js';
|
|
28
|
+
|
|
29
|
+
// Error Reporting
|
|
30
|
+
export * from './utils/error-reporter.js';
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
const fs = require('fs-extra');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Get metadata file path for storing requirement counter
|
|
6
|
+
* @param {string} repoPath - Path to repository
|
|
7
|
+
* @returns {string} Path to metadata file
|
|
8
|
+
*/
|
|
9
|
+
function getMetadataPath(repoPath) {
|
|
10
|
+
return path.join(repoPath, '.vibecodingmachine', 'requirement-metadata.json');
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Get the next requirement number for this repository
|
|
15
|
+
* @param {string} repoPath - Path to repository
|
|
16
|
+
* @returns {Promise<number>} Next requirement number
|
|
17
|
+
*/
|
|
18
|
+
async function getNextRequirementNumber(repoPath) {
|
|
19
|
+
const metadataPath = getMetadataPath(repoPath);
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
await fs.ensureFile(metadataPath);
|
|
23
|
+
const metadata = await fs.readJson(metadataPath).catch(() => ({ lastRequirementNumber: 0 }));
|
|
24
|
+
|
|
25
|
+
const nextNumber = (metadata.lastRequirementNumber || 0) + 1;
|
|
26
|
+
|
|
27
|
+
// Save the incremented number
|
|
28
|
+
metadata.lastRequirementNumber = nextNumber;
|
|
29
|
+
await fs.writeJson(metadataPath, metadata, { spaces: 2 });
|
|
30
|
+
|
|
31
|
+
return nextNumber;
|
|
32
|
+
} catch (error) {
|
|
33
|
+
// Default to 1 if there's any error
|
|
34
|
+
console.error('Error getting next requirement number:', error.message);
|
|
35
|
+
return 1;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Set the last requirement number (used during initialization)
|
|
41
|
+
* @param {string} repoPath - Path to repository
|
|
42
|
+
* @param {number} lastNumber - Last requirement number
|
|
43
|
+
*/
|
|
44
|
+
async function setLastRequirementNumber(repoPath, lastNumber) {
|
|
45
|
+
const metadataPath = getMetadataPath(repoPath);
|
|
46
|
+
|
|
47
|
+
await fs.ensureFile(metadataPath);
|
|
48
|
+
const metadata = await fs.readJson(metadataPath).catch(() => ({}));
|
|
49
|
+
metadata.lastRequirementNumber = lastNumber;
|
|
50
|
+
await fs.writeJson(metadataPath, metadata, { spaces: 2 });
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Extract requirement number from requirement title
|
|
55
|
+
* @param {string} title - Requirement title
|
|
56
|
+
* @returns {number|null} Requirement number or null if not found
|
|
57
|
+
*/
|
|
58
|
+
function extractRequirementNumber(title) {
|
|
59
|
+
const match = title.match(/^R(\d+):/);
|
|
60
|
+
return match ? parseInt(match[1], 10) : null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Number all existing requirements in a requirements file
|
|
65
|
+
* @param {string} reqPath - Path to requirements file
|
|
66
|
+
* @param {string} repoPath - Path to repository
|
|
67
|
+
* @returns {Promise<number>} Number of requirements numbered
|
|
68
|
+
*/
|
|
69
|
+
async function numberAllRequirements(reqPath, repoPath) {
|
|
70
|
+
if (!await fs.pathExists(reqPath)) {
|
|
71
|
+
return 0;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const content = await fs.readFile(reqPath, 'utf8');
|
|
75
|
+
const lines = content.split('\n');
|
|
76
|
+
const newLines = [];
|
|
77
|
+
|
|
78
|
+
let requirementCount = 0;
|
|
79
|
+
let maxExistingNumber = 0;
|
|
80
|
+
|
|
81
|
+
// First pass: collect existing numbers and count requirements
|
|
82
|
+
for (const line of lines) {
|
|
83
|
+
if (line.trim().startsWith('###')) {
|
|
84
|
+
const title = line.trim().substring(3).trim();
|
|
85
|
+
const existingNumber = extractRequirementNumber(title);
|
|
86
|
+
|
|
87
|
+
if (existingNumber) {
|
|
88
|
+
maxExistingNumber = Math.max(maxExistingNumber, existingNumber);
|
|
89
|
+
} else {
|
|
90
|
+
// This requirement needs numbering
|
|
91
|
+
requirementCount++;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Start numbering from max existing + 1
|
|
97
|
+
let currentNumber = maxExistingNumber + 1;
|
|
98
|
+
let numbered = 0;
|
|
99
|
+
|
|
100
|
+
// Second pass: add numbers to requirements that don't have them
|
|
101
|
+
for (const line of lines) {
|
|
102
|
+
if (line.trim().startsWith('###')) {
|
|
103
|
+
const title = line.trim().substring(3).trim();
|
|
104
|
+
const existingNumber = extractRequirementNumber(title);
|
|
105
|
+
|
|
106
|
+
if (!existingNumber) {
|
|
107
|
+
// Add number to this requirement
|
|
108
|
+
newLines.push(`### R${currentNumber}: ${title}`);
|
|
109
|
+
currentNumber++;
|
|
110
|
+
numbered++;
|
|
111
|
+
} else {
|
|
112
|
+
// Keep existing numbered requirement as-is
|
|
113
|
+
newLines.push(line);
|
|
114
|
+
}
|
|
115
|
+
} else {
|
|
116
|
+
newLines.push(line);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Save the updated file
|
|
121
|
+
await fs.writeFile(reqPath, newLines.join('\n'));
|
|
122
|
+
|
|
123
|
+
// Update metadata with the highest number used
|
|
124
|
+
await setLastRequirementNumber(repoPath, currentNumber - 1);
|
|
125
|
+
|
|
126
|
+
return numbered;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Create a git branch name from requirement number and title
|
|
131
|
+
* @param {number} reqNumber - Requirement number
|
|
132
|
+
* @param {string} title - Requirement title
|
|
133
|
+
* @returns {string} Branch name (e.g., "R1_email_system")
|
|
134
|
+
*/
|
|
135
|
+
function createBranchName(reqNumber, title) {
|
|
136
|
+
// Remove requirement number from title if present
|
|
137
|
+
let cleanTitle = title.replace(/^R\d+:\s*/, '').trim();
|
|
138
|
+
|
|
139
|
+
// Take first few words (up to 3-4 words, max 30 chars)
|
|
140
|
+
const words = cleanTitle.split(/\s+/);
|
|
141
|
+
let branchSuffix = '';
|
|
142
|
+
|
|
143
|
+
for (const word of words) {
|
|
144
|
+
const candidate = branchSuffix ? `${branchSuffix}_${word}` : word;
|
|
145
|
+
if (candidate.length > 30) break;
|
|
146
|
+
branchSuffix = candidate;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Clean up branch suffix: lowercase, remove special chars
|
|
150
|
+
branchSuffix = branchSuffix
|
|
151
|
+
.toLowerCase()
|
|
152
|
+
.replace(/[^a-z0-9]+/g, '_')
|
|
153
|
+
.replace(/^_+|_+$/g, '');
|
|
154
|
+
|
|
155
|
+
return branchSuffix ? `R${reqNumber}_${branchSuffix}` : `R${reqNumber}`;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
module.exports = {
|
|
159
|
+
getNextRequirementNumber,
|
|
160
|
+
setLastRequirementNumber,
|
|
161
|
+
extractRequirementNumber,
|
|
162
|
+
numberAllRequirements,
|
|
163
|
+
createBranchName
|
|
164
|
+
};
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Global Error Reporter for VibeCodingMachine
|
|
3
|
+
*
|
|
4
|
+
* Automatically reports errors to the admin database for tracking and fixing.
|
|
5
|
+
* This helps the admin know about issues and use VibeCodingMachine to fix itself.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const UserDatabase = require('../database/user-schema');
|
|
9
|
+
|
|
10
|
+
class ErrorReporter {
|
|
11
|
+
constructor() {
|
|
12
|
+
this.db = new UserDatabase();
|
|
13
|
+
this.enabled = true;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Set authentication token for error reporting
|
|
18
|
+
*/
|
|
19
|
+
setAuthToken(token) {
|
|
20
|
+
this.db.setAuthToken(token);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Enable or disable error reporting
|
|
25
|
+
*/
|
|
26
|
+
setEnabled(enabled) {
|
|
27
|
+
this.enabled = enabled;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Report an error to the admin database
|
|
32
|
+
*/
|
|
33
|
+
async reportError(error, context = {}) {
|
|
34
|
+
if (!this.enabled) {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
const errorData = {
|
|
40
|
+
message: error.message || String(error),
|
|
41
|
+
stack: error.stack || '',
|
|
42
|
+
name: error.name || 'Error',
|
|
43
|
+
code: error.code || '',
|
|
44
|
+
context: {
|
|
45
|
+
...context,
|
|
46
|
+
cwd: process.cwd(),
|
|
47
|
+
argv: process.argv.slice(2).filter(arg =>
|
|
48
|
+
!arg.includes('password') &&
|
|
49
|
+
!arg.includes('token') &&
|
|
50
|
+
!arg.includes('secret')
|
|
51
|
+
)
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
await this.db.reportError(errorData);
|
|
56
|
+
return true;
|
|
57
|
+
} catch (reportError) {
|
|
58
|
+
// Silently fail - don't block the application
|
|
59
|
+
console.warn('Failed to report error:', reportError.message);
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Wrap a function to automatically report errors
|
|
66
|
+
*/
|
|
67
|
+
wrapFunction(fn, context = {}) {
|
|
68
|
+
return async (...args) => {
|
|
69
|
+
try {
|
|
70
|
+
return await fn(...args);
|
|
71
|
+
} catch (error) {
|
|
72
|
+
await this.reportError(error, {
|
|
73
|
+
...context,
|
|
74
|
+
functionName: fn.name,
|
|
75
|
+
args: args.filter(arg =>
|
|
76
|
+
typeof arg !== 'object' ||
|
|
77
|
+
!JSON.stringify(arg).match(/password|token|secret/i)
|
|
78
|
+
)
|
|
79
|
+
});
|
|
80
|
+
throw error;
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Create a global error handler for uncaught exceptions
|
|
87
|
+
*/
|
|
88
|
+
setupGlobalHandlers() {
|
|
89
|
+
process.on('uncaughtException', async (error) => {
|
|
90
|
+
await this.reportError(error, {
|
|
91
|
+
type: 'uncaughtException'
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
process.on('unhandledRejection', async (error) => {
|
|
96
|
+
await this.reportError(error, {
|
|
97
|
+
type: 'unhandledRejection'
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Singleton instance
|
|
104
|
+
const errorReporter = new ErrorReporter();
|
|
105
|
+
|
|
106
|
+
module.exports = {
|
|
107
|
+
ErrorReporter,
|
|
108
|
+
errorReporter
|
|
109
|
+
};
|