error-solution-ai-buddy 0.0.0-stage ā 1.0.0
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/LICENSE +21 -0
- package/README.md +291 -2
- package/backend/src/ai/ollama-service.js +264 -0
- package/backend/src/services/history-service.js +194 -0
- package/cli/error-listener.js +409 -0
- package/cli/formatter.js +244 -0
- package/cli/index.js +241 -0
- package/cli/package-lock.json +6 -0
- package/cli/package.json +3 -0
- package/package.json +53 -4
package/cli/formatter.js
ADDED
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Formatter - Pretty prints error explanations to the terminal
|
|
3
|
+
*
|
|
4
|
+
* This module handles all output formatting to make errors
|
|
5
|
+
* easy to read at a glance.
|
|
6
|
+
*
|
|
7
|
+
* WHY separate formatting:
|
|
8
|
+
* 1. Single responsibility (easy to maintain)
|
|
9
|
+
* 2. Easy to change styling without touching logic
|
|
10
|
+
* 3. Can support different output formats (terminal, JSON, etc.)
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import chalk from 'chalk';
|
|
14
|
+
import boxen from 'boxen';
|
|
15
|
+
|
|
16
|
+
export class Formatter {
|
|
17
|
+
constructor(options = {}) {
|
|
18
|
+
this.useColor = options.useColor !== false;
|
|
19
|
+
this.useEmoji = options.useEmoji !== false;
|
|
20
|
+
this.boxStyle = options.boxStyle || 'round';
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Print AI-generated explanation
|
|
25
|
+
*
|
|
26
|
+
* Expected format from AI:
|
|
27
|
+
* {
|
|
28
|
+
* what: "What the error is",
|
|
29
|
+
* why: "Why it happened",
|
|
30
|
+
* fix: "How to fix it",
|
|
31
|
+
* example: "Code example"
|
|
32
|
+
* }
|
|
33
|
+
*/
|
|
34
|
+
printAIExplanation(explanation) {
|
|
35
|
+
const header = this.useEmoji ? 'š¤ Error Buddy Explanation:' : '[Error Buddy]';
|
|
36
|
+
|
|
37
|
+
let content = '';
|
|
38
|
+
|
|
39
|
+
// What is this?
|
|
40
|
+
if (explanation.what) {
|
|
41
|
+
content += chalk.cyan.bold(this.useEmoji ? 'š What is this?' : '[What]') + '\n';
|
|
42
|
+
content += this.wrapText(explanation.what) + '\n\n';
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Why it happens?
|
|
46
|
+
if (explanation.why) {
|
|
47
|
+
content += chalk.yellow.bold(this.useEmoji ? 'ā Why it happens?' : '[Why]') + '\n';
|
|
48
|
+
content += this.wrapText(explanation.why) + '\n\n';
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// How to fix?
|
|
52
|
+
if (explanation.fix) {
|
|
53
|
+
content += chalk.green.bold(this.useEmoji ? 'ā
How to fix it:' : '[Fix]') + '\n';
|
|
54
|
+
content += this.wrapText(explanation.fix) + '\n';
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Code example
|
|
58
|
+
if (explanation.example) {
|
|
59
|
+
content += '\n' + chalk.magenta.bold(this.useEmoji ? 'š” Example:' : '[Example]') + '\n';
|
|
60
|
+
content += chalk.gray(this.formatCode(explanation.example));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Print in a box
|
|
64
|
+
const box = boxen(content.trim(), {
|
|
65
|
+
title: header,
|
|
66
|
+
titleAlignment: 'left',
|
|
67
|
+
padding: 1,
|
|
68
|
+
margin: { top: 0, bottom: 1, left: 0, right: 0 },
|
|
69
|
+
borderStyle: this.boxStyle,
|
|
70
|
+
borderColor: 'cyan',
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
console.log(box);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Print pattern-based explanation (when AI is unavailable)
|
|
78
|
+
*/
|
|
79
|
+
printPatternExplanation(analysis) {
|
|
80
|
+
if (!analysis.localExplanation) {
|
|
81
|
+
this.printGenericHelp(analysis);
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const explanation = analysis.localExplanation;
|
|
86
|
+
const header = this.useEmoji ? 'š Quick Explanation:' : '[Quick Help]';
|
|
87
|
+
|
|
88
|
+
let content = '';
|
|
89
|
+
|
|
90
|
+
// Language detected
|
|
91
|
+
if (analysis.language && analysis.language !== 'unknown') {
|
|
92
|
+
content += chalk.gray(`Language: ${this.formatLanguage(analysis.language)}`) + '\n';
|
|
93
|
+
if (analysis.errorType) {
|
|
94
|
+
content += chalk.gray(`Error: ${analysis.errorType}`) + '\n';
|
|
95
|
+
}
|
|
96
|
+
content += '\n';
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// What
|
|
100
|
+
content += chalk.cyan.bold(this.useEmoji ? 'š What:' : '[What]') + '\n';
|
|
101
|
+
content += this.wrapText(explanation.what) + '\n\n';
|
|
102
|
+
|
|
103
|
+
// Why
|
|
104
|
+
content += chalk.yellow.bold(this.useEmoji ? 'ā Why:' : '[Why]') + '\n';
|
|
105
|
+
content += this.wrapText(explanation.why) + '\n\n';
|
|
106
|
+
|
|
107
|
+
// Fix
|
|
108
|
+
content += chalk.green.bold(this.useEmoji ? 'ā
Fix:' : '[Fix]') + '\n';
|
|
109
|
+
content += this.wrapText(explanation.fix) + '\n';
|
|
110
|
+
|
|
111
|
+
// Example
|
|
112
|
+
if (explanation.example) {
|
|
113
|
+
content += '\n' + chalk.magenta.bold(this.useEmoji ? 'š” Example:' : '[Example]') + '\n';
|
|
114
|
+
content += chalk.gray(this.formatCode(explanation.example));
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Offline notice
|
|
118
|
+
content += '\n\n' + chalk.gray.italic('(Offline mode - start Ollama for AI explanations)');
|
|
119
|
+
|
|
120
|
+
const box = boxen(content.trim(), {
|
|
121
|
+
title: header,
|
|
122
|
+
titleAlignment: 'left',
|
|
123
|
+
padding: 1,
|
|
124
|
+
margin: { top: 0, bottom: 1, left: 0, right: 0 },
|
|
125
|
+
borderStyle: this.boxStyle,
|
|
126
|
+
borderColor: 'yellow',
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
console.log(box);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Print generic help when no pattern matches
|
|
134
|
+
*/
|
|
135
|
+
printGenericHelp(analysis) {
|
|
136
|
+
const header = this.useEmoji ? 'š Error Detected:' : '[Error]';
|
|
137
|
+
|
|
138
|
+
let content = '';
|
|
139
|
+
|
|
140
|
+
if (analysis.language && analysis.language !== 'unknown') {
|
|
141
|
+
content += chalk.gray(`Language: ${this.formatLanguage(analysis.language)}`) + '\n';
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (analysis.errorType) {
|
|
145
|
+
content += chalk.gray(`Type: ${analysis.errorType}`) + '\n';
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (analysis.file) {
|
|
149
|
+
content += chalk.gray(`File: ${analysis.file}`) + '\n';
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (analysis.line) {
|
|
153
|
+
content += chalk.gray(`Line: ${analysis.line}`) + '\n';
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
content += '\n';
|
|
157
|
+
content += chalk.cyan('Start Ollama for detailed AI explanations:') + '\n';
|
|
158
|
+
content += chalk.white(' 1. Install Ollama: https://ollama.ai') + '\n';
|
|
159
|
+
content += chalk.white(' 2. Run: ollama serve') + '\n';
|
|
160
|
+
content += chalk.white(' 3. Try your command again') + '\n';
|
|
161
|
+
|
|
162
|
+
const box = boxen(content.trim(), {
|
|
163
|
+
title: header,
|
|
164
|
+
titleAlignment: 'left',
|
|
165
|
+
padding: 1,
|
|
166
|
+
margin: { top: 0, bottom: 1, left: 0, right: 0 },
|
|
167
|
+
borderStyle: this.boxStyle,
|
|
168
|
+
borderColor: 'gray',
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
console.log(box);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Format language name nicely
|
|
176
|
+
*/
|
|
177
|
+
formatLanguage(lang) {
|
|
178
|
+
const names = {
|
|
179
|
+
javascript: 'JavaScript/Node.js',
|
|
180
|
+
java: 'Java',
|
|
181
|
+
csharp: 'C#/.NET',
|
|
182
|
+
python: 'Python',
|
|
183
|
+
unknown: 'Unknown',
|
|
184
|
+
};
|
|
185
|
+
return names[lang] || lang;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Wrap text to terminal width
|
|
190
|
+
*/
|
|
191
|
+
wrapText(text, maxWidth = 70) {
|
|
192
|
+
if (!text) return '';
|
|
193
|
+
|
|
194
|
+
const words = text.split(' ');
|
|
195
|
+
const lines = [];
|
|
196
|
+
let currentLine = '';
|
|
197
|
+
|
|
198
|
+
for (const word of words) {
|
|
199
|
+
if (currentLine.length + word.length + 1 <= maxWidth) {
|
|
200
|
+
currentLine += (currentLine ? ' ' : '') + word;
|
|
201
|
+
} else {
|
|
202
|
+
if (currentLine) lines.push(currentLine);
|
|
203
|
+
currentLine = word;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
if (currentLine) lines.push(currentLine);
|
|
207
|
+
|
|
208
|
+
return lines.join('\n');
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Format code block with indentation
|
|
213
|
+
*/
|
|
214
|
+
formatCode(code) {
|
|
215
|
+
if (!code) return '';
|
|
216
|
+
|
|
217
|
+
// Add consistent indentation
|
|
218
|
+
return code
|
|
219
|
+
.split('\n')
|
|
220
|
+
.map(line => ' ' + line)
|
|
221
|
+
.join('\n');
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Print a simple status message
|
|
226
|
+
*/
|
|
227
|
+
printStatus(message, type = 'info') {
|
|
228
|
+
const icons = {
|
|
229
|
+
info: this.useEmoji ? 'ā¹ļø ' : '[i] ',
|
|
230
|
+
success: this.useEmoji ? 'ā
' : '[+] ',
|
|
231
|
+
warning: this.useEmoji ? 'ā ļø ' : '[!] ',
|
|
232
|
+
error: this.useEmoji ? 'ā ' : '[x] ',
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
const colors = {
|
|
236
|
+
info: chalk.cyan,
|
|
237
|
+
success: chalk.green,
|
|
238
|
+
warning: chalk.yellow,
|
|
239
|
+
error: chalk.red,
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
console.log(colors[type](icons[type] + message));
|
|
243
|
+
}
|
|
244
|
+
}
|
package/cli/index.js
ADDED
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Error Solution AI Buddy - CLI Entry Point
|
|
5
|
+
*
|
|
6
|
+
* This is the main entry point for the CLI tool.
|
|
7
|
+
* It wraps commands (node, npm, java, dotnet) and intercepts errors.
|
|
8
|
+
*
|
|
9
|
+
* Usage: errbuddy <command> [args]
|
|
10
|
+
* Example: errbuddy node app.js
|
|
11
|
+
* errbuddy npm start
|
|
12
|
+
* errbuddy java MyApp
|
|
13
|
+
* errbuddy dotnet run
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { Command } from 'commander';
|
|
17
|
+
import { spawn } from 'child_process';
|
|
18
|
+
import { ErrorListener } from './error-listener.js';
|
|
19
|
+
import { Formatter } from './formatter.js';
|
|
20
|
+
import { AIService } from '../backend/src/ai/ollama-service.js';
|
|
21
|
+
import { HistoryService } from '../backend/src/services/history-service.js';
|
|
22
|
+
import chalk from 'chalk';
|
|
23
|
+
|
|
24
|
+
const program = new Command();
|
|
25
|
+
|
|
26
|
+
// Configuration
|
|
27
|
+
const CONFIG = {
|
|
28
|
+
aiEnabled: true,
|
|
29
|
+
ollamaModel: 'qwen2.5:0.5b', // Small model that works on low-RAM systems
|
|
30
|
+
ollamaUrl: 'http://localhost:11434',
|
|
31
|
+
showSpinner: true,
|
|
32
|
+
maxErrorLength: 2000, // Truncate very long errors
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
program
|
|
36
|
+
.name('errbuddy')
|
|
37
|
+
.description('AI-powered error explanations directly in your terminal')
|
|
38
|
+
.version('1.0.0')
|
|
39
|
+
.enablePositionalOptions()
|
|
40
|
+
.passThroughOptions()
|
|
41
|
+
.argument('<command>', 'Command to run (node, npm, java, dotnet, etc.)')
|
|
42
|
+
.argument('[args...]', 'Arguments to pass to the command')
|
|
43
|
+
.option('--no-ai', 'Disable AI explanations (show errors only)')
|
|
44
|
+
.option('--model <model>', 'Ollama model to use', 'qwen2.5:0.5b')
|
|
45
|
+
.option('--offline', 'Force offline mode (pattern matching only)')
|
|
46
|
+
.action(async (command, args, options) => {
|
|
47
|
+
await runCommand(command, args, options);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
// Help command with examples
|
|
51
|
+
program
|
|
52
|
+
.command('examples')
|
|
53
|
+
.description('Show usage examples')
|
|
54
|
+
.action(() => {
|
|
55
|
+
console.log(chalk.cyan('\nš Error Buddy Examples:\n'));
|
|
56
|
+
console.log(' ' + chalk.green('errbuddy node app.js'));
|
|
57
|
+
console.log(' ' + chalk.gray('ā Run Node.js app and explain any errors\n'));
|
|
58
|
+
console.log(' ' + chalk.green('errbuddy npm start'));
|
|
59
|
+
console.log(' ' + chalk.gray('ā Run npm script with error explanations\n'));
|
|
60
|
+
console.log(' ' + chalk.green('errbuddy java MyClass'));
|
|
61
|
+
console.log(' ' + chalk.gray('ā Run Java program with error explanations\n'));
|
|
62
|
+
console.log(' ' + chalk.green('errbuddy dotnet run'));
|
|
63
|
+
console.log(' ' + chalk.gray('ā Run .NET project with error explanations\n'));
|
|
64
|
+
console.log(' ' + chalk.green('errbuddy --no-ai node app.js'));
|
|
65
|
+
console.log(' ' + chalk.gray('ā Run without AI (pattern matching only)\n'));
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
// Config command
|
|
69
|
+
program
|
|
70
|
+
.command('config')
|
|
71
|
+
.description('Show current configuration')
|
|
72
|
+
.action(() => {
|
|
73
|
+
console.log(chalk.cyan('\nāļø Current Configuration:\n'));
|
|
74
|
+
console.log(` AI Enabled: ${CONFIG.aiEnabled ? chalk.green('Yes') : chalk.red('No')}`);
|
|
75
|
+
console.log(` Ollama Model: ${chalk.yellow(CONFIG.ollamaModel)}`);
|
|
76
|
+
console.log(` Ollama URL: ${chalk.gray(CONFIG.ollamaUrl)}`);
|
|
77
|
+
console.log('');
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Sanitize error text before sending to AI
|
|
82
|
+
*
|
|
83
|
+
* WHY: Privacy protection - we don't want to leak:
|
|
84
|
+
* - File paths (might contain project names, usernames)
|
|
85
|
+
* - Environment variables
|
|
86
|
+
* - API keys that might appear in errors
|
|
87
|
+
*/
|
|
88
|
+
function sanitizeError(errorText) {
|
|
89
|
+
let sanitized = errorText;
|
|
90
|
+
|
|
91
|
+
// Remove Windows-style absolute paths (C:\Users\...)
|
|
92
|
+
sanitized = sanitized.replace(/[A-Za-z]:\\(?:[^\\/:*?"<>|\r\n]+\\)*/g, '[path]\\');
|
|
93
|
+
|
|
94
|
+
// Remove Unix-style absolute paths (/home/user/...)
|
|
95
|
+
sanitized = sanitized.replace(/\/(?:home|Users|var|usr|opt)\/[^\s:]+/g, '[path]');
|
|
96
|
+
|
|
97
|
+
// Remove common secret patterns
|
|
98
|
+
sanitized = sanitized.replace(/(?:api[_-]?key|secret|password|token)\s*[:=]\s*['"]?[^'"\s]+['"]?/gi, '[REDACTED]');
|
|
99
|
+
|
|
100
|
+
// Truncate if too long
|
|
101
|
+
if (sanitized.length > CONFIG.maxErrorLength) {
|
|
102
|
+
sanitized = sanitized.substring(0, CONFIG.maxErrorLength) + '\n... (truncated)';
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return sanitized;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Main function that runs the wrapped command
|
|
110
|
+
*
|
|
111
|
+
* WHY we use spawn() instead of exec():
|
|
112
|
+
* - spawn() streams output in real-time (better UX)
|
|
113
|
+
* - exec() buffers everything (user waits too long)
|
|
114
|
+
*/
|
|
115
|
+
async function runCommand(command, args, options) {
|
|
116
|
+
const formatter = new Formatter();
|
|
117
|
+
const errorListener = new ErrorListener();
|
|
118
|
+
const aiService = new AIService(CONFIG.ollamaUrl, options.model || CONFIG.ollamaModel);
|
|
119
|
+
const history = new HistoryService();
|
|
120
|
+
|
|
121
|
+
// Check if Ollama is available (non-blocking)
|
|
122
|
+
let ollamaAvailable = false;
|
|
123
|
+
if (!options.offline && options.ai !== false) {
|
|
124
|
+
ollamaAvailable = await aiService.checkConnection();
|
|
125
|
+
if (!ollamaAvailable) {
|
|
126
|
+
console.log(chalk.yellow('ā ļø Ollama not detected. Using pattern matching only.'));
|
|
127
|
+
console.log(chalk.gray(' For AI explanations (free, runs locally):'));
|
|
128
|
+
console.log(chalk.gray(' 1. Install Ollama: https://ollama.com/download'));
|
|
129
|
+
console.log(chalk.gray(` 2. Download a model: ollama pull ${options.model || CONFIG.ollamaModel}`));
|
|
130
|
+
console.log(chalk.gray(' 3. Start it: ollama serve\n'));
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Spawn the child process
|
|
135
|
+
// stdio: ['inherit', 'pipe', 'pipe'] means:
|
|
136
|
+
// - stdin: inherited (user can type input)
|
|
137
|
+
// - stdout: piped (we capture and forward it)
|
|
138
|
+
// - stderr: piped (we capture, analyze, then forward it)
|
|
139
|
+
//
|
|
140
|
+
// We join command + args into a single shell string to avoid Node.js 24+
|
|
141
|
+
// DEP0190 warning ("Passing args to a child process with shell option true
|
|
142
|
+
// can lead to security vulnerabilities"). This is safe here because the
|
|
143
|
+
// user controls the input (they typed the command themselves).
|
|
144
|
+
const fullCommand = [command, ...args].join(' ');
|
|
145
|
+
const child = spawn(fullCommand, [], {
|
|
146
|
+
stdio: ['inherit', 'pipe', 'pipe'],
|
|
147
|
+
shell: true, // Required for Windows compatibility
|
|
148
|
+
env: { ...process.env, FORCE_COLOR: '1' } // Preserve colors
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
let stderrBuffer = '';
|
|
152
|
+
let errorDetected = false;
|
|
153
|
+
|
|
154
|
+
// Forward stdout immediately (no delay)
|
|
155
|
+
child.stdout.on('data', (data) => {
|
|
156
|
+
process.stdout.write(data);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
// Capture and analyze stderr
|
|
160
|
+
child.stderr.on('data', (data) => {
|
|
161
|
+
const text = data.toString();
|
|
162
|
+
|
|
163
|
+
// Always show the original error first
|
|
164
|
+
process.stderr.write(data);
|
|
165
|
+
|
|
166
|
+
// Buffer stderr for analysis
|
|
167
|
+
stderrBuffer += text;
|
|
168
|
+
|
|
169
|
+
// Check if this looks like an error
|
|
170
|
+
if (errorListener.detectError(text)) {
|
|
171
|
+
errorDetected = true;
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
// When process ends, analyze collected errors
|
|
176
|
+
child.on('close', async (exitCode) => {
|
|
177
|
+
if (errorDetected && stderrBuffer.trim()) {
|
|
178
|
+
// Analyze the error
|
|
179
|
+
const analysis = errorListener.analyzeError(stderrBuffer);
|
|
180
|
+
|
|
181
|
+
if (analysis.isError) {
|
|
182
|
+
console.log(''); // Blank line for readability
|
|
183
|
+
|
|
184
|
+
// Try AI explanation if available
|
|
185
|
+
if (ollamaAvailable && options.ai !== false) {
|
|
186
|
+
const explanation = await explainWithAI(stderrBuffer, analysis, aiService, formatter);
|
|
187
|
+
try { history.saveError({ ...analysis, fullText: stderrBuffer }, explanation, `${command} ${args.join(' ')}`); } catch {}
|
|
188
|
+
} else {
|
|
189
|
+
// Fall back to pattern-based explanation
|
|
190
|
+
formatter.printPatternExplanation(analysis);
|
|
191
|
+
try { history.saveError({ ...analysis, fullText: stderrBuffer }, analysis.localExplanation, `${command} ${args.join(' ')}`); } catch {}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Exit with the same code as the child process
|
|
197
|
+
process.exit(exitCode);
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
// Handle spawn errors (command not found, etc.)
|
|
201
|
+
child.on('error', (err) => {
|
|
202
|
+
console.error(chalk.red(`\nā Failed to run command: ${command}`));
|
|
203
|
+
console.error(chalk.gray(` ${err.message}`));
|
|
204
|
+
|
|
205
|
+
if (err.code === 'ENOENT') {
|
|
206
|
+
console.error(chalk.yellow(`\nš” Tip: Make sure "${command}" is installed and in your PATH.`));
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
process.exit(1);
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Get AI explanation for an error
|
|
215
|
+
*/
|
|
216
|
+
async function explainWithAI(errorText, analysis, aiService, formatter) {
|
|
217
|
+
const ora = (await import('ora')).default;
|
|
218
|
+
const spinner = ora({
|
|
219
|
+
text: 'Getting AI explanation...',
|
|
220
|
+
color: 'cyan'
|
|
221
|
+
}).start();
|
|
222
|
+
|
|
223
|
+
try {
|
|
224
|
+
// Sanitize the error before sending to AI
|
|
225
|
+
const sanitizedError = sanitizeError(errorText);
|
|
226
|
+
|
|
227
|
+
const explanation = await aiService.explainError(sanitizedError, analysis.language);
|
|
228
|
+
spinner.stop();
|
|
229
|
+
|
|
230
|
+
formatter.printAIExplanation(explanation);
|
|
231
|
+
return explanation;
|
|
232
|
+
} catch (err) {
|
|
233
|
+
spinner.stop();
|
|
234
|
+
console.log(chalk.yellow('ā ļø AI explanation failed. Showing pattern-based help:'));
|
|
235
|
+
formatter.printPatternExplanation(analysis);
|
|
236
|
+
return analysis.localExplanation;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Run the CLI
|
|
241
|
+
program.parse();
|
package/cli/package.json
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,55 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "error-solution-ai-buddy",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "AI-powered error explanation tool for developers - works directly in your terminal",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "cli/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"errbuddy": "./cli/index.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"cli",
|
|
12
|
+
"backend",
|
|
13
|
+
"README.md",
|
|
14
|
+
"LICENSE"
|
|
15
|
+
],
|
|
16
|
+
"scripts": {
|
|
17
|
+
"start": "node cli/index.js",
|
|
18
|
+
"test": "node --test tests/*.js",
|
|
19
|
+
"lint": "eslint cli/ backend/",
|
|
20
|
+
"demo": "node tests/samples/js-errors.js",
|
|
21
|
+
"prepublishOnly": "npm test"
|
|
22
|
+
},
|
|
23
|
+
"keywords": [
|
|
24
|
+
"error",
|
|
25
|
+
"debugging",
|
|
26
|
+
"ai",
|
|
27
|
+
"developer-tools",
|
|
28
|
+
"terminal",
|
|
29
|
+
"ollama",
|
|
30
|
+
"cli",
|
|
31
|
+
"llm",
|
|
32
|
+
"error-explainer",
|
|
33
|
+
"local-ai"
|
|
34
|
+
],
|
|
35
|
+
"author": "Sithija Harshana Jayasinghe",
|
|
36
|
+
"license": "MIT",
|
|
37
|
+
"repository": {
|
|
38
|
+
"type": "git",
|
|
39
|
+
"url": "git+https://github.com/sithija-jayasinghe/solution-ai-buddy-v1.git",
|
|
40
|
+
"directory": "error-solution-ai-buddy"
|
|
41
|
+
},
|
|
42
|
+
"homepage": "https://github.com/sithija-jayasinghe/solution-ai-buddy-v1#readme",
|
|
43
|
+
"bugs": {
|
|
44
|
+
"url": "https://github.com/sithija-jayasinghe/solution-ai-buddy-v1/issues"
|
|
45
|
+
},
|
|
46
|
+
"dependencies": {
|
|
47
|
+
"chalk": "^5.3.0",
|
|
48
|
+
"commander": "^11.1.0",
|
|
49
|
+
"ora": "^7.0.1",
|
|
50
|
+
"boxen": "^7.1.1"
|
|
51
|
+
},
|
|
52
|
+
"engines": {
|
|
53
|
+
"node": ">=18.0.0"
|
|
54
|
+
}
|
|
55
|
+
}
|