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.
@@ -0,0 +1,409 @@
1
+ /**
2
+ * Error Listener - Detects and analyzes errors from stderr
3
+ *
4
+ * This module is responsible for:
5
+ * 1. Detecting if output contains an error (vs. just warnings/info)
6
+ * 2. Identifying the programming language
7
+ * 3. Extracting useful context (error type, line number, file)
8
+ * 4. Providing pattern-based explanations (fallback when AI unavailable)
9
+ */
10
+
11
+ /**
12
+ * Error patterns for each language
13
+ *
14
+ * WHY we use patterns:
15
+ * 1. Fast detection without AI
16
+ * 2. Fallback when Ollama is offline
17
+ * 3. Helps identify language for better AI prompts
18
+ */
19
+ const ERROR_PATTERNS = {
20
+ javascript: {
21
+ // Pattern: matches the error type and extracts it
22
+ patterns: [
23
+ /^(\w*Error): (.+)$/m, // TypeError: message
24
+ /^(\w*Exception): (.+)$/m, // ReferenceException: message
25
+ /SyntaxError: (.+)/, // Syntax errors
26
+ /Cannot find module '(.+)'/, // Module not found
27
+ /is not defined/, // Undefined variable
28
+ /is not a function/, // Calling non-function
29
+ /Cannot read propert(?:y|ies) of (undefined|null)/, // Null/undefined access
30
+ /ENOENT|EACCES|ECONNREFUSED/, // System errors
31
+ ],
32
+ stackTraceIndicator: /at\s+(?:\S+\s+)?\(?.*:\d+:\d+\)?/,
33
+ fileLinePattern: /at\s+.*\(?(.*):(\d+):(\d+)\)?/,
34
+ },
35
+
36
+ java: {
37
+ patterns: [
38
+ /^Exception in thread ".*" (\w+(?:\.\w+)*): (.*)$/m, // Exception in thread "main"
39
+ /^(\w+(?:\.\w+)*Exception): (.*)$/m, // NullPointerException:
40
+ /^(\w+(?:\.\w+)*Error): (.*)$/m, // OutOfMemoryError:
41
+ /^Caused by: (\w+(?:\.\w+)*): (.*)$/m, // Caused by:
42
+ /cannot find symbol/, // Compile error
43
+ /error: (.+)/, // Generic error
44
+ /incompatible types/, // Type mismatch
45
+ ],
46
+ stackTraceIndicator: /at\s+[\w.$]+\([\w.]+:\d+\)/,
47
+ fileLinePattern: /at\s+[\w.$]+\(([\w.]+):(\d+)\)/,
48
+ },
49
+
50
+ csharp: {
51
+ patterns: [
52
+ /^Unhandled exception\. (\w+(?:\.\w+)*): (.*)$/m, // Unhandled exception.
53
+ /^(\w+(?:\.\w+)*Exception): (.*)$/m, // NullReferenceException:
54
+ /error CS\d+: (.+)/, // Compiler error
55
+ /Object reference not set/, // NullRef
56
+ /Index was outside the bounds/, // Array bounds
57
+ /The type or namespace name .* could not be found/, // Missing type
58
+ ],
59
+ stackTraceIndicator: /at\s+[\w.<>]+\(.*\)\s+in\s+.*:line\s+\d+/,
60
+ fileLinePattern: /in\s+(.*):line\s+(\d+)/,
61
+ }
62
+ };
63
+
64
+ /**
65
+ * Common error explanations (used when AI is unavailable)
66
+ *
67
+ * WHY we have local explanations:
68
+ * - Works offline
69
+ * - Instant response (no network latency)
70
+ * - Fallback if Ollama fails
71
+ */
72
+ const COMMON_ERRORS = {
73
+ javascript: {
74
+ 'TypeError': {
75
+ 'Cannot read propert': {
76
+ what: 'You tried to access a property on something that is undefined or null.',
77
+ why: 'The variable you\'re accessing doesn\'t exist or hasn\'t been assigned a value yet.',
78
+ fix: 'Add a null check before accessing properties.',
79
+ example: `// Instead of: data.users.map(...)
80
+ // Do this:
81
+ if (data && data.users) {
82
+ data.users.map(...)
83
+ }`
84
+ },
85
+ 'is not a function': {
86
+ what: 'You tried to call something as a function, but it\'s not a function.',
87
+ why: 'The variable might be undefined, or you imported/accessed the wrong thing.',
88
+ fix: 'Check that you\'re calling the right function and it\'s properly imported.',
89
+ example: `// Check if it's a function first:
90
+ if (typeof myFunc === 'function') {
91
+ myFunc();
92
+ }`
93
+ }
94
+ },
95
+ 'ReferenceError': {
96
+ 'is not defined': {
97
+ what: 'You\'re using a variable that doesn\'t exist in the current scope.',
98
+ why: 'Either the variable was never declared, or it\'s spelled wrong, or it\'s out of scope.',
99
+ fix: 'Make sure the variable is declared (const, let, var) before using it.',
100
+ example: `// Wrong:
101
+ console.log(myVar);
102
+
103
+ // Right:
104
+ const myVar = 'hello';
105
+ console.log(myVar);`
106
+ }
107
+ },
108
+ 'SyntaxError': {
109
+ 'default': {
110
+ what: 'Your code has a syntax mistake that JavaScript cannot understand.',
111
+ why: 'Missing brackets, quotes, semicolons, or other syntax issues.',
112
+ fix: 'Check the line number mentioned and look for missing or extra characters.',
113
+ example: `// Common issues:
114
+ // - Missing closing bracket: { or }
115
+ // - Missing closing parenthesis: ( or )
116
+ // - Missing quotes: " or '
117
+ // - Extra/missing comma in arrays/objects`
118
+ }
119
+ },
120
+ 'ENOENT': {
121
+ 'default': {
122
+ what: 'A file or directory you tried to access doesn\'t exist.',
123
+ why: 'The path is wrong, or the file was deleted/moved.',
124
+ fix: 'Check that the file path is correct and the file exists.',
125
+ example: `// Check if file exists first:
126
+ const fs = require('fs');
127
+ if (fs.existsSync('./myfile.txt')) {
128
+ // read file
129
+ }`
130
+ }
131
+ },
132
+ 'ECONNREFUSED': {
133
+ 'default': {
134
+ what: 'Your code tried to connect to a server that refused the connection.',
135
+ why: 'The server might not be running, wrong port, or firewall blocking.',
136
+ fix: 'Make sure the server is running and the port/host are correct.',
137
+ example: `// Common checks:
138
+ // 1. Is the database/server running?
139
+ // 2. Is the port correct? (e.g., 3000, 5432)
140
+ // 3. Is it localhost or 127.0.0.1?`
141
+ }
142
+ }
143
+ },
144
+
145
+ java: {
146
+ 'NullPointerException': {
147
+ 'default': {
148
+ what: 'You tried to use an object that is null (doesn\'t exist).',
149
+ why: 'A variable wasn\'t initialized, or a method returned null.',
150
+ fix: 'Add null checks before using objects.',
151
+ example: `// Instead of: user.getName()
152
+ // Do this:
153
+ if (user != null) {
154
+ user.getName();
155
+ }
156
+
157
+ // Or use Optional:
158
+ Optional.ofNullable(user).map(User::getName);`
159
+ }
160
+ },
161
+ 'ArrayIndexOutOfBoundsException': {
162
+ 'default': {
163
+ what: 'You tried to access an array index that doesn\'t exist.',
164
+ why: 'Arrays are 0-indexed, so an array of size 5 has indices 0-4.',
165
+ fix: 'Check array length before accessing an index.',
166
+ example: `// Wrong: accessing index 5 in array of size 5
167
+ int[] arr = new int[5];
168
+ arr[5] = 10; // Error! Valid indices are 0-4
169
+
170
+ // Right:
171
+ if (index >= 0 && index < arr.length) {
172
+ arr[index] = 10;
173
+ }`
174
+ }
175
+ },
176
+ 'ClassNotFoundException': {
177
+ 'default': {
178
+ what: 'Java cannot find a class you\'re trying to use.',
179
+ why: 'The class isn\'t in your classpath, or the name is misspelled.',
180
+ fix: 'Check your dependencies (pom.xml/build.gradle) and class name.',
181
+ example: `// Check:
182
+ // 1. Is the dependency in pom.xml?
183
+ // 2. Did you run 'mvn install'?
184
+ // 3. Is the class name spelled correctly?
185
+ // 4. Is the import statement correct?`
186
+ }
187
+ },
188
+ 'cannot find symbol': {
189
+ 'default': {
190
+ what: 'Java compiler cannot find a variable, method, or class you\'re using.',
191
+ why: 'Typo in name, missing import, or wrong scope.',
192
+ fix: 'Check spelling and make sure imports are correct.',
193
+ example: `// Common causes:
194
+ // 1. Typo: myVarible instead of myVariable
195
+ // 2. Missing import statement
196
+ // 3. Variable declared in different scope
197
+ // 4. Method doesn't exist in that class`
198
+ }
199
+ }
200
+ },
201
+
202
+ csharp: {
203
+ 'NullReferenceException': {
204
+ 'default': {
205
+ what: 'You tried to use an object that is null.',
206
+ why: 'A variable wasn\'t initialized, or a method returned null.',
207
+ fix: 'Add null checks or use null-conditional operators.',
208
+ example: `// Instead of: user.Name
209
+ // Use null-conditional:
210
+ var name = user?.Name;
211
+
212
+ // Or null check:
213
+ if (user != null)
214
+ {
215
+ var name = user.Name;
216
+ }`
217
+ }
218
+ },
219
+ 'IndexOutOfRangeException': {
220
+ 'default': {
221
+ what: 'You tried to access an array/list index that doesn\'t exist.',
222
+ why: 'The index is negative or greater than the collection size.',
223
+ fix: 'Check collection length before accessing an index.',
224
+ example: `// Wrong:
225
+ var items = new int[5];
226
+ var x = items[5]; // Error!
227
+
228
+ // Right:
229
+ if (index >= 0 && index < items.Length)
230
+ {
231
+ var x = items[index];
232
+ }`
233
+ }
234
+ },
235
+ 'CS0103': {
236
+ 'default': {
237
+ what: 'The name you\'re using doesn\'t exist in the current context.',
238
+ why: 'Variable not declared, typo, or missing using statement.',
239
+ fix: 'Check variable spelling and add any missing using statements.',
240
+ example: `// Did you forget to add:
241
+ using System.Linq;
242
+ using YourNamespace;
243
+
244
+ // Or declare the variable:
245
+ var myVariable = "value";`
246
+ }
247
+ },
248
+ 'CS0246': {
249
+ 'default': {
250
+ what: 'C# cannot find a type or namespace you\'re trying to use.',
251
+ why: 'Missing using statement, missing NuGet package, or typo.',
252
+ fix: 'Add the using statement or install the required NuGet package.',
253
+ example: `// Add using statement:
254
+ using Newtonsoft.Json;
255
+
256
+ // Or install package:
257
+ // dotnet add package Newtonsoft.Json`
258
+ }
259
+ }
260
+ }
261
+ };
262
+
263
+ export class ErrorListener {
264
+ constructor() {
265
+ this.patterns = ERROR_PATTERNS;
266
+ this.explanations = COMMON_ERRORS;
267
+ }
268
+
269
+ /**
270
+ * Quick check if text looks like an error
271
+ *
272
+ * WHY: We don't want to trigger AI for every output.
273
+ * Only analyze when there's likely an actual error.
274
+ */
275
+ detectError(text) {
276
+ const errorIndicators = [
277
+ /error/i,
278
+ /exception/i,
279
+ /failed/i,
280
+ /cannot/i,
281
+ /unable to/i,
282
+ /fatal/i,
283
+ /ENOENT|EACCES|ECONNREFUSED/,
284
+ /at\s+[\w.]+\(.*:\d+/, // Stack trace pattern
285
+ ];
286
+
287
+ return errorIndicators.some(pattern => pattern.test(text));
288
+ }
289
+
290
+ /**
291
+ * Analyze the error and extract useful information
292
+ */
293
+ analyzeError(errorText) {
294
+ const analysis = {
295
+ isError: false,
296
+ language: this.detectLanguage(errorText),
297
+ errorType: null,
298
+ errorMessage: null,
299
+ file: null,
300
+ line: null,
301
+ column: null,
302
+ localExplanation: null,
303
+ };
304
+
305
+ if (!analysis.language) {
306
+ // Try to still detect if it's an error
307
+ analysis.isError = this.detectError(errorText);
308
+ analysis.language = 'unknown';
309
+ return analysis;
310
+ }
311
+
312
+ const langPatterns = this.patterns[analysis.language];
313
+
314
+ // Try to match error patterns
315
+ for (const pattern of langPatterns.patterns) {
316
+ const match = errorText.match(pattern);
317
+ if (match) {
318
+ analysis.isError = true;
319
+ analysis.errorType = match[1] || null;
320
+ analysis.errorMessage = match[2] || match[1] || null;
321
+ break;
322
+ }
323
+ }
324
+
325
+ // Extract file and line number
326
+ if (langPatterns.fileLinePattern) {
327
+ const fileMatch = errorText.match(langPatterns.fileLinePattern);
328
+ if (fileMatch) {
329
+ analysis.file = fileMatch[1];
330
+ analysis.line = fileMatch[2];
331
+ analysis.column = fileMatch[3] || null;
332
+ }
333
+ }
334
+
335
+ // Get local explanation if available
336
+ analysis.localExplanation = this.getLocalExplanation(analysis, errorText);
337
+
338
+ return analysis;
339
+ }
340
+
341
+ /**
342
+ * Detect programming language from error output
343
+ */
344
+ detectLanguage(errorText) {
345
+ // JavaScript/Node.js indicators
346
+ if (
347
+ /(TypeError|ReferenceError|SyntaxError)[:\s]/i.test(errorText) ||
348
+ /at\s+\S+\s+\(.*\.js:\d+:\d+\)/.test(errorText) ||
349
+ /at\s+.*\.js:\d+:\d+/.test(errorText) ||
350
+ /node_modules/.test(errorText) ||
351
+ /ENOENT|EACCES|ECONNREFUSED/.test(errorText) ||
352
+ /Cannot find module/.test(errorText)
353
+ ) {
354
+ return 'javascript';
355
+ }
356
+
357
+ // Java indicators
358
+ if (
359
+ /at\s+[\w.$]+\([\w.]+\.java:\d+\)/.test(errorText) ||
360
+ /Exception in thread/.test(errorText) ||
361
+ /\.java:\d+:/.test(errorText) ||
362
+ /at\s+java\./.test(errorText)
363
+ ) {
364
+ return 'java';
365
+ }
366
+
367
+ // C# indicators
368
+ if (
369
+ /at\s+[\w.<>]+\(.*\)\s+in\s+.*:line\s+\d+/.test(errorText) ||
370
+ /error CS\d+/.test(errorText) ||
371
+ /\.cs\(\d+,\d+\)/.test(errorText) ||
372
+ /Unhandled exception\. System\./.test(errorText) ||
373
+ /at\s+System\./.test(errorText)
374
+ ) {
375
+ return 'csharp';
376
+ }
377
+
378
+ return null;
379
+ }
380
+
381
+ /**
382
+ * Get local explanation for an error (used when AI is unavailable)
383
+ */
384
+ getLocalExplanation(analysis, errorText) {
385
+ if (!analysis.language || !this.explanations[analysis.language]) {
386
+ return null;
387
+ }
388
+
389
+ const langExplanations = this.explanations[analysis.language];
390
+
391
+ // Try to find matching explanation
392
+ for (const [errorType, subErrors] of Object.entries(langExplanations)) {
393
+ if (errorText.includes(errorType) || analysis.errorType?.includes(errorType)) {
394
+ // Check for specific sub-patterns
395
+ for (const [subPattern, explanation] of Object.entries(subErrors)) {
396
+ if (subPattern === 'default' || errorText.includes(subPattern)) {
397
+ return explanation;
398
+ }
399
+ }
400
+ // Return default if exists
401
+ if (subErrors.default) {
402
+ return subErrors.default;
403
+ }
404
+ }
405
+ }
406
+
407
+ return null;
408
+ }
409
+ }
@@ -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
+ }