error-solution-ai-buddy 0.0.0-stage ā 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +195 -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/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.1",
|
|
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
|
+
}
|