explain-my-error 1.0.9 → 1.0.11
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 +33 -2
- package/dist/cli.js +345 -22
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +175 -16
- package/dist/index.js +358 -26
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/skills/SKILL.md +41 -4
package/README.md
CHANGED
|
@@ -1,13 +1,19 @@
|
|
|
1
|
-
# explain-my-error
|
|
2
|
-
|
|
3
1
|

|
|
4
2
|
|
|
3
|
+
# explain-my-error
|
|
4
|
+
|
|
5
5
|
Turn confusing programming errors into clear fixes directly in your terminal.
|
|
6
6
|
|
|
7
7
|
`explain-my-error` returns:
|
|
8
8
|
|
|
9
9
|
- A plain-English explanation
|
|
10
10
|
- Common root causes
|
|
11
|
+
- Likely root cause based on context
|
|
12
|
+
- 2-3 alternative hypotheses with confidence
|
|
13
|
+
- Ranked fix plans (fast patch / proper fix / long-term fix)
|
|
14
|
+
- Framework-specific recipes (React/Next.js/Node/Express/TypeScript)
|
|
15
|
+
- Copy-paste remediation commands
|
|
16
|
+
- Verify checklist
|
|
11
17
|
- A practical fix
|
|
12
18
|
- A code example
|
|
13
19
|
- An ELI5 summary
|
|
@@ -90,6 +96,26 @@ explain-my-error explain "TypeError: Cannot read property 'map' of undefined"
|
|
|
90
96
|
eme explain "ReferenceError: x is not defined"
|
|
91
97
|
```
|
|
92
98
|
|
|
99
|
+
### Context-aware input
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
eme explain "TypeError: Cannot read property 'map' of undefined" \
|
|
103
|
+
--framework react \
|
|
104
|
+
--runtime "node 20" \
|
|
105
|
+
--stack "at App (src/App.tsx:12:5)" \
|
|
106
|
+
--code "items.map(item => item.id)"
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
eme explain --stack-file ./error.log --code-file ./src/App.tsx --framework nextjs
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### JSON mode
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
eme explain "ReferenceError: x is not defined" --json
|
|
117
|
+
```
|
|
118
|
+
|
|
93
119
|
### Piped input
|
|
94
120
|
|
|
95
121
|
```bash
|
|
@@ -109,6 +135,11 @@ explain-my-error [command]
|
|
|
109
135
|
Commands:
|
|
110
136
|
|
|
111
137
|
- `explain [error...]` Explain a programming error
|
|
138
|
+
- `--json` Return structured JSON output
|
|
139
|
+
- `--stack`, `--stack-file` Add stack trace context
|
|
140
|
+
- `--code`, `--code-file` Add code snippet context
|
|
141
|
+
- `--runtime` Add runtime context
|
|
142
|
+
- `--framework` Add framework context
|
|
112
143
|
- `--help` Show CLI help
|
|
113
144
|
- `--version` Show CLI version
|
|
114
145
|
|
package/dist/cli.js
CHANGED
|
@@ -18,13 +18,45 @@ import axios from "axios";
|
|
|
18
18
|
|
|
19
19
|
// src/types/error.ts
|
|
20
20
|
import { z } from "zod";
|
|
21
|
+
var hypothesisSchema = z.object({
|
|
22
|
+
hypothesis: z.string().min(1),
|
|
23
|
+
confidence: z.number().min(0).max(1),
|
|
24
|
+
why: z.string().min(1)
|
|
25
|
+
});
|
|
26
|
+
var fixPlanSchema = z.object({
|
|
27
|
+
plan: z.enum(["fast_patch", "proper_fix", "long_term_fix"]),
|
|
28
|
+
summary: z.string().min(1),
|
|
29
|
+
steps: z.array(z.string().min(1)).min(1),
|
|
30
|
+
tradeoffs: z.object({
|
|
31
|
+
risk: z.string().min(1),
|
|
32
|
+
performance: z.string().min(1),
|
|
33
|
+
maintainability: z.string().min(1)
|
|
34
|
+
})
|
|
35
|
+
});
|
|
36
|
+
var frameworkRecipeSchema = z.object({
|
|
37
|
+
framework: z.string().min(1),
|
|
38
|
+
when_to_use: z.string().min(1),
|
|
39
|
+
steps: z.array(z.string().min(1)).min(1),
|
|
40
|
+
code_snippet: z.string().min(1)
|
|
41
|
+
});
|
|
42
|
+
var remediationBlockSchema = z.object({
|
|
43
|
+
title: z.string().min(1),
|
|
44
|
+
commands: z.array(z.string().min(1)).min(1),
|
|
45
|
+
snippet: z.string().optional()
|
|
46
|
+
});
|
|
21
47
|
var explainedErrorSchema = z.object({
|
|
22
48
|
title: z.string().min(1),
|
|
23
49
|
explanation: z.string().min(1),
|
|
24
50
|
common_causes: z.array(z.string().min(1)).min(1),
|
|
25
51
|
fix: z.string().min(1),
|
|
26
52
|
code_example: z.string().min(1),
|
|
27
|
-
eli5: z.string().min(1)
|
|
53
|
+
eli5: z.string().min(1),
|
|
54
|
+
likely_root_cause: z.string().min(1),
|
|
55
|
+
hypotheses: z.array(hypothesisSchema).min(1).max(3),
|
|
56
|
+
fix_plans: z.array(fixPlanSchema).min(3),
|
|
57
|
+
framework_recipes: z.array(frameworkRecipeSchema).min(1),
|
|
58
|
+
remediation_blocks: z.array(remediationBlockSchema).min(1),
|
|
59
|
+
verify_checklist: z.array(z.string().min(1)).min(1)
|
|
28
60
|
});
|
|
29
61
|
|
|
30
62
|
// src/services/ai.ts
|
|
@@ -125,23 +157,160 @@ function stringifyField(value) {
|
|
|
125
157
|
}
|
|
126
158
|
return String(value);
|
|
127
159
|
}
|
|
160
|
+
function toStringArray(value) {
|
|
161
|
+
if (Array.isArray(value)) {
|
|
162
|
+
return value.map((item) => stringifyField(item)).map((item) => item.trim()).filter(Boolean);
|
|
163
|
+
}
|
|
164
|
+
return stringifyField(value).split(/\n|,/).map((item) => item.trim()).filter(Boolean);
|
|
165
|
+
}
|
|
166
|
+
function toConfidence(value) {
|
|
167
|
+
if (typeof value === "number") {
|
|
168
|
+
return Math.max(0, Math.min(1, value));
|
|
169
|
+
}
|
|
170
|
+
const parsed = Number(stringifyField(value));
|
|
171
|
+
if (!Number.isNaN(parsed)) {
|
|
172
|
+
return Math.max(0, Math.min(1, parsed));
|
|
173
|
+
}
|
|
174
|
+
return 0.5;
|
|
175
|
+
}
|
|
128
176
|
function normalizeResponseShape(payload) {
|
|
129
177
|
if (!payload || typeof payload !== "object") {
|
|
130
178
|
return payload;
|
|
131
179
|
}
|
|
132
180
|
const data = payload;
|
|
133
|
-
const
|
|
134
|
-
const
|
|
181
|
+
const commonCauses = toStringArray(data.common_causes);
|
|
182
|
+
const hypothesesRaw = Array.isArray(data.hypotheses) ? data.hypotheses : [];
|
|
183
|
+
const hypotheses = hypothesesRaw.map((item) => {
|
|
184
|
+
const row = item;
|
|
185
|
+
return {
|
|
186
|
+
hypothesis: stringifyField(row?.hypothesis),
|
|
187
|
+
confidence: toConfidence(row?.confidence),
|
|
188
|
+
why: stringifyField(row?.why)
|
|
189
|
+
};
|
|
190
|
+
}).filter((item) => item.hypothesis && item.why).slice(0, 3);
|
|
191
|
+
const fixPlansRaw = Array.isArray(data.fix_plans) ? data.fix_plans : [];
|
|
192
|
+
const normalizedFixPlans = fixPlansRaw.map((item, index) => {
|
|
193
|
+
const row = item;
|
|
194
|
+
const defaultPlan = index === 0 ? "fast_patch" : index === 1 ? "proper_fix" : "long_term_fix";
|
|
195
|
+
const planText = stringifyField(row?.plan || defaultPlan).toLowerCase();
|
|
196
|
+
const plan = planText === "fast_patch" || planText === "proper_fix" || planText === "long_term_fix" ? planText : defaultPlan;
|
|
197
|
+
const tradeoffs = row?.tradeoffs ?? {};
|
|
198
|
+
return {
|
|
199
|
+
plan,
|
|
200
|
+
summary: stringifyField(row?.summary),
|
|
201
|
+
steps: toStringArray(row?.steps),
|
|
202
|
+
tradeoffs: {
|
|
203
|
+
risk: stringifyField(tradeoffs.risk || "Unknown"),
|
|
204
|
+
performance: stringifyField(tradeoffs.performance || "Unknown"),
|
|
205
|
+
maintainability: stringifyField(tradeoffs.maintainability || "Unknown")
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
}).filter((item) => item.summary && item.steps.length > 0);
|
|
209
|
+
const fixPlans = normalizedFixPlans.length >= 3 ? normalizedFixPlans.slice(0, 3) : [
|
|
210
|
+
{
|
|
211
|
+
plan: "fast_patch",
|
|
212
|
+
summary: stringifyField(data.fix),
|
|
213
|
+
steps: [stringifyField(data.fix) || "Apply a defensive guard around the failing line."],
|
|
214
|
+
tradeoffs: {
|
|
215
|
+
risk: "May hide deeper data contract issues.",
|
|
216
|
+
performance: "Minimal runtime overhead.",
|
|
217
|
+
maintainability: "Good short-term, revisit for root-cause cleanup."
|
|
218
|
+
}
|
|
219
|
+
},
|
|
220
|
+
{
|
|
221
|
+
plan: "proper_fix",
|
|
222
|
+
summary: "Fix data flow and initialization path.",
|
|
223
|
+
steps: [
|
|
224
|
+
"Trace where the failing value is created.",
|
|
225
|
+
"Initialize value before usage and add proper null checks.",
|
|
226
|
+
"Add a regression test for this scenario."
|
|
227
|
+
],
|
|
228
|
+
tradeoffs: {
|
|
229
|
+
risk: "Moderate code changes.",
|
|
230
|
+
performance: "Neutral to slightly improved.",
|
|
231
|
+
maintainability: "Better long-term readability and reliability."
|
|
232
|
+
}
|
|
233
|
+
},
|
|
234
|
+
{
|
|
235
|
+
plan: "long_term_fix",
|
|
236
|
+
summary: "Enforce stronger contracts and prevention tooling.",
|
|
237
|
+
steps: [
|
|
238
|
+
"Enable stricter type checks and lint rules.",
|
|
239
|
+
"Introduce runtime validation for external inputs.",
|
|
240
|
+
"Create reusable utility guards in shared modules."
|
|
241
|
+
],
|
|
242
|
+
tradeoffs: {
|
|
243
|
+
risk: "Requires broader refactor.",
|
|
244
|
+
performance: "Potential minor validation overhead.",
|
|
245
|
+
maintainability: "Highest long-term stability."
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
];
|
|
249
|
+
const frameworkRecipesRaw = Array.isArray(data.framework_recipes) ? data.framework_recipes : [];
|
|
250
|
+
const frameworkRecipes = frameworkRecipesRaw.map((item) => {
|
|
251
|
+
const row = item;
|
|
252
|
+
return {
|
|
253
|
+
framework: stringifyField(row?.framework),
|
|
254
|
+
when_to_use: stringifyField(row?.when_to_use),
|
|
255
|
+
steps: toStringArray(row?.steps),
|
|
256
|
+
code_snippet: stringifyField(row?.code_snippet)
|
|
257
|
+
};
|
|
258
|
+
}).filter(
|
|
259
|
+
(item) => item.framework && item.when_to_use && item.steps.length > 0 && item.code_snippet
|
|
260
|
+
);
|
|
261
|
+
const remediationBlocksRaw = Array.isArray(data.remediation_blocks) ? data.remediation_blocks : [];
|
|
262
|
+
const remediationBlocks = remediationBlocksRaw.map((item) => {
|
|
263
|
+
const row = item;
|
|
264
|
+
return {
|
|
265
|
+
title: stringifyField(row?.title),
|
|
266
|
+
commands: toStringArray(row?.commands),
|
|
267
|
+
snippet: stringifyField(row?.snippet) || void 0
|
|
268
|
+
};
|
|
269
|
+
}).filter((item) => item.title && item.commands.length > 0);
|
|
270
|
+
const verifyChecklist = toStringArray(data.verify_checklist);
|
|
271
|
+
const title = stringifyField(data.title);
|
|
272
|
+
const explanation = stringifyField(data.explanation);
|
|
273
|
+
const codeExample = stringifyField(data.code_example);
|
|
274
|
+
const fix = stringifyField(data.fix);
|
|
275
|
+
const likelyRootCause = stringifyField(data.likely_root_cause) || commonCauses[0] || "Insufficient context from error text.";
|
|
135
276
|
return {
|
|
136
|
-
title:
|
|
137
|
-
explanation:
|
|
138
|
-
common_causes: commonCauses,
|
|
139
|
-
fix:
|
|
140
|
-
code_example:
|
|
141
|
-
eli5: stringifyField(data.eli5)
|
|
277
|
+
title: title || "Unknown error",
|
|
278
|
+
explanation: explanation || "Unable to derive explanation from model response.",
|
|
279
|
+
common_causes: commonCauses.length > 0 ? commonCauses : ["Insufficient context in provided error text."],
|
|
280
|
+
fix: fix || "Add guards and trace value initialization before the failing line.",
|
|
281
|
+
code_example: codeExample || "// Add null checks around the failing line.",
|
|
282
|
+
eli5: stringifyField(data.eli5) || "Your code expected one thing, but got something different.",
|
|
283
|
+
likely_root_cause: likelyRootCause,
|
|
284
|
+
hypotheses: hypotheses.length > 0 ? hypotheses : [
|
|
285
|
+
{
|
|
286
|
+
hypothesis: likelyRootCause,
|
|
287
|
+
confidence: 0.6,
|
|
288
|
+
why: "Derived from error pattern and available context."
|
|
289
|
+
}
|
|
290
|
+
],
|
|
291
|
+
fix_plans: fixPlans,
|
|
292
|
+
framework_recipes: frameworkRecipes.length > 0 ? frameworkRecipes : [
|
|
293
|
+
{
|
|
294
|
+
framework: "Node.js",
|
|
295
|
+
when_to_use: "Default runtime fallback when framework is unknown.",
|
|
296
|
+
steps: [
|
|
297
|
+
"Validate all external data before use.",
|
|
298
|
+
"Guard nullable values before method calls.",
|
|
299
|
+
"Add test coverage for this failure path."
|
|
300
|
+
],
|
|
301
|
+
code_snippet: "const safeValue = input ?? defaultValue;"
|
|
302
|
+
}
|
|
303
|
+
],
|
|
304
|
+
remediation_blocks: remediationBlocks.length > 0 ? remediationBlocks : [
|
|
305
|
+
{
|
|
306
|
+
title: "Quick remediation commands",
|
|
307
|
+
commands: ["npm run test", "npm run lint"]
|
|
308
|
+
}
|
|
309
|
+
],
|
|
310
|
+
verify_checklist: verifyChecklist.length > 0 ? verifyChecklist : ["Re-run failing command.", "Confirm no regression in related flows."]
|
|
142
311
|
};
|
|
143
312
|
}
|
|
144
|
-
async function explainErrorWithAI(errorMessage) {
|
|
313
|
+
async function explainErrorWithAI(errorMessage, context = {}) {
|
|
145
314
|
const apiKey = process.env.GROQ_API_KEY;
|
|
146
315
|
if (!apiKey) {
|
|
147
316
|
throw new Error(
|
|
@@ -159,14 +328,44 @@ async function explainErrorWithAI(errorMessage) {
|
|
|
159
328
|
].join("\n")
|
|
160
329
|
);
|
|
161
330
|
}
|
|
162
|
-
const prompt =
|
|
331
|
+
const prompt = `You are a senior software engineer helping debug errors with context.
|
|
332
|
+
Return STRICT JSON only (no markdown) with fields:
|
|
333
|
+
- title
|
|
334
|
+
- explanation
|
|
335
|
+
- common_causes (string[])
|
|
336
|
+
- fix
|
|
337
|
+
- code_example
|
|
338
|
+
- eli5
|
|
339
|
+
- likely_root_cause
|
|
340
|
+
- hypotheses (2-3 items): [{ hypothesis, confidence (0..1), why }]
|
|
341
|
+
- fix_plans (exactly 3): [
|
|
342
|
+
{ plan: "fast_patch", summary, steps[], tradeoffs: { risk, performance, maintainability } },
|
|
343
|
+
{ plan: "proper_fix", summary, steps[], tradeoffs: { risk, performance, maintainability } },
|
|
344
|
+
{ plan: "long_term_fix", summary, steps[], tradeoffs: { risk, performance, maintainability } }
|
|
345
|
+
]
|
|
346
|
+
- framework_recipes (1+): [{ framework, when_to_use, steps[], code_snippet }]
|
|
347
|
+
- remediation_blocks (1+): [{ title, commands[], snippet? }]
|
|
348
|
+
- verify_checklist (string[])
|
|
349
|
+
|
|
350
|
+
Use provided runtime/framework/code/stack context to produce context-aware likely root cause and framework-specific fixes.`;
|
|
351
|
+
const contextBlock = [
|
|
352
|
+
`Error message:
|
|
353
|
+
${errorMessage}`,
|
|
354
|
+
`Runtime:
|
|
355
|
+
${context.runtime ?? "unknown"}`,
|
|
356
|
+
`Framework:
|
|
357
|
+
${context.framework ?? "unknown"}`,
|
|
358
|
+
`Stack trace:
|
|
359
|
+
${context.stackTrace ?? "not provided"}`,
|
|
360
|
+
`Code snippet:
|
|
361
|
+
${context.codeSnippet ?? "not provided"}`
|
|
362
|
+
].join("\n\n");
|
|
163
363
|
const requestBody = {
|
|
164
364
|
messages: [
|
|
165
365
|
{ role: "system", content: prompt },
|
|
166
366
|
{
|
|
167
367
|
role: "user",
|
|
168
|
-
content:
|
|
169
|
-
${errorMessage}
|
|
368
|
+
content: `${contextBlock}
|
|
170
369
|
|
|
171
370
|
Return strict JSON only.`
|
|
172
371
|
}
|
|
@@ -229,8 +428,42 @@ function block(title, body) {
|
|
|
229
428
|
const rows = body.split("\n").map((row) => pc.white(row));
|
|
230
429
|
return [pc.cyan(line()), pc.bold(pc.cyan(title)), ...rows, pc.cyan(line())].join("\n");
|
|
231
430
|
}
|
|
431
|
+
function list(items) {
|
|
432
|
+
return items.map((item, index) => `${index + 1}. ${item}`).join("\n");
|
|
433
|
+
}
|
|
232
434
|
function formatExplainedError(result) {
|
|
233
|
-
const commonCauses = result.common_causes
|
|
435
|
+
const commonCauses = list(result.common_causes);
|
|
436
|
+
const hypotheses = result.hypotheses.map(
|
|
437
|
+
(item, index) => `${index + 1}. ${item.hypothesis}
|
|
438
|
+
confidence: ${(item.confidence * 100).toFixed(0)}%
|
|
439
|
+
why: ${item.why}`
|
|
440
|
+
).join("\n\n");
|
|
441
|
+
const fixPlans = result.fix_plans.map((plan) => {
|
|
442
|
+
const label = plan.plan === "fast_patch" ? "Fast Patch" : plan.plan === "proper_fix" ? "Proper Fix" : "Long-Term Fix";
|
|
443
|
+
return [
|
|
444
|
+
`${pc.bold(pc.green(label))}: ${plan.summary}`,
|
|
445
|
+
...plan.steps.map((step, index) => ` ${index + 1}. ${step}`),
|
|
446
|
+
` tradeoffs -> risk: ${plan.tradeoffs.risk}; performance: ${plan.tradeoffs.performance}; maintainability: ${plan.tradeoffs.maintainability}`
|
|
447
|
+
].join("\n");
|
|
448
|
+
}).join("\n\n");
|
|
449
|
+
const frameworkRecipes = result.framework_recipes.map(
|
|
450
|
+
(recipe) => [
|
|
451
|
+
`${pc.bold(pc.cyan(recipe.framework))}: ${recipe.when_to_use}`,
|
|
452
|
+
...recipe.steps.map((step, index) => ` ${index + 1}. ${step}`),
|
|
453
|
+
" snippet:",
|
|
454
|
+
recipe.code_snippet.split("\n").map((lineText) => ` ${lineText}`).join("\n")
|
|
455
|
+
].join("\n")
|
|
456
|
+
).join("\n\n");
|
|
457
|
+
const remediationBlocks = result.remediation_blocks.map((blockItem) => {
|
|
458
|
+
const snippet = blockItem.snippet?.trim() ? `
|
|
459
|
+
snippet:
|
|
460
|
+
${blockItem.snippet.split("\n").map((lineText) => ` ${lineText}`).join("\n")}` : "";
|
|
461
|
+
return [
|
|
462
|
+
`${pc.bold(pc.green(blockItem.title))}`,
|
|
463
|
+
...blockItem.commands.map((command) => ` $ ${command}`)
|
|
464
|
+
].join("\n") + snippet;
|
|
465
|
+
}).join("\n\n");
|
|
466
|
+
const verifyChecklist = list(result.verify_checklist);
|
|
234
467
|
const hero = [
|
|
235
468
|
pc.cyan(line("=")),
|
|
236
469
|
framedLine(pc.bold(pc.cyan("EXPLAIN MY ERROR"))),
|
|
@@ -241,16 +474,27 @@ function formatExplainedError(result) {
|
|
|
241
474
|
hero,
|
|
242
475
|
"",
|
|
243
476
|
`${pc.bold(pc.cyan("ERROR"))}: ${pc.bold(pc.white(result.title))}`,
|
|
477
|
+
`${pc.bold(pc.yellow("LIKELY ROOT CAUSE"))}: ${pc.white(result.likely_root_cause)}`,
|
|
244
478
|
"",
|
|
245
479
|
block("EXPLANATION", result.explanation),
|
|
246
480
|
"",
|
|
247
481
|
block("COMMON CAUSES", commonCauses),
|
|
248
482
|
"",
|
|
483
|
+
block("HYPOTHESES (WITH CONFIDENCE)", hypotheses),
|
|
484
|
+
"",
|
|
485
|
+
block("RANKED FIX PLANS", fixPlans),
|
|
486
|
+
"",
|
|
249
487
|
`${pc.bold(pc.green("FIX"))}
|
|
250
488
|
${pc.white(result.fix)}`,
|
|
251
489
|
"",
|
|
252
490
|
block("CODE EXAMPLE", result.code_example),
|
|
253
491
|
"",
|
|
492
|
+
block("FRAMEWORK-SPECIFIC RECIPES", frameworkRecipes),
|
|
493
|
+
"",
|
|
494
|
+
block("COPY-PASTE REMEDIATION", remediationBlocks),
|
|
495
|
+
"",
|
|
496
|
+
block("VERIFY CHECKLIST", verifyChecklist),
|
|
497
|
+
"",
|
|
254
498
|
block("ELI5", result.eli5),
|
|
255
499
|
"",
|
|
256
500
|
pc.dim('Tip: run `eme explain "<error>"` for quick mode.')
|
|
@@ -279,7 +523,16 @@ var logger = {
|
|
|
279
523
|
};
|
|
280
524
|
|
|
281
525
|
// src/commands/explain.ts
|
|
282
|
-
|
|
526
|
+
function isDepsObject(value) {
|
|
527
|
+
if (!value || typeof value !== "object") {
|
|
528
|
+
return false;
|
|
529
|
+
}
|
|
530
|
+
const candidate = value;
|
|
531
|
+
return "explainError" in candidate || "createSpinner" in candidate || "formatOutput" in candidate || "log" in candidate;
|
|
532
|
+
}
|
|
533
|
+
async function runExplainCommand(errorMessage, optionsOrDeps = { output: "pretty" }, depsArg = {}) {
|
|
534
|
+
const options = isDepsObject(optionsOrDeps) ? { output: "pretty" } : optionsOrDeps;
|
|
535
|
+
const deps = isDepsObject(optionsOrDeps) ? optionsOrDeps : depsArg;
|
|
283
536
|
const explainError = deps.explainError ?? explainErrorWithAI;
|
|
284
537
|
const createSpinner = deps.createSpinner ?? ((text) => ora(text).start());
|
|
285
538
|
const formatOutput = deps.formatOutput ?? formatExplainedError;
|
|
@@ -290,10 +543,14 @@ async function runExplainCommand(errorMessage, deps = {}) {
|
|
|
290
543
|
}
|
|
291
544
|
const spinner = createSpinner("Analyzing your error...");
|
|
292
545
|
try {
|
|
293
|
-
const result = await explainError(errorMessage.trim());
|
|
546
|
+
const result = await explainError(errorMessage.trim(), options.context);
|
|
294
547
|
spinner.succeed("Explanation ready.");
|
|
295
548
|
log.info("");
|
|
296
|
-
|
|
549
|
+
if (options.output === "json") {
|
|
550
|
+
log.info(JSON.stringify(result, null, 2));
|
|
551
|
+
} else {
|
|
552
|
+
log.info(formatOutput(result));
|
|
553
|
+
}
|
|
297
554
|
} catch (error) {
|
|
298
555
|
spinner.fail("Could not explain this error.");
|
|
299
556
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
@@ -321,6 +578,46 @@ async function readStdin() {
|
|
|
321
578
|
}
|
|
322
579
|
return Buffer.concat(chunks).toString("utf8").trim();
|
|
323
580
|
}
|
|
581
|
+
async function safeReadText(filePath) {
|
|
582
|
+
if (!filePath) {
|
|
583
|
+
return void 0;
|
|
584
|
+
}
|
|
585
|
+
try {
|
|
586
|
+
return await readFile(filePath, "utf8");
|
|
587
|
+
} catch {
|
|
588
|
+
return void 0;
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
async function detectFrameworkFromPackageJson() {
|
|
592
|
+
try {
|
|
593
|
+
const raw = await readFile("package.json", "utf8");
|
|
594
|
+
const pkg = JSON.parse(raw);
|
|
595
|
+
const deps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
|
|
596
|
+
if ("next" in deps) return "Next.js";
|
|
597
|
+
if ("react" in deps) return "React";
|
|
598
|
+
if ("express" in deps) return "Express";
|
|
599
|
+
if ("typescript" in deps) return "TypeScript";
|
|
600
|
+
if ("fastify" in deps) return "Fastify";
|
|
601
|
+
if ("nestjs" in deps || "@nestjs/core" in deps) return "NestJS";
|
|
602
|
+
} catch {
|
|
603
|
+
}
|
|
604
|
+
return void 0;
|
|
605
|
+
}
|
|
606
|
+
async function buildExplainOptions(options) {
|
|
607
|
+
const stackFromFile = await safeReadText(options.stackFile);
|
|
608
|
+
const codeFromFile = await safeReadText(options.codeFile);
|
|
609
|
+
const detectedFramework = await detectFrameworkFromPackageJson();
|
|
610
|
+
const context = {
|
|
611
|
+
stackTrace: options.stack ?? stackFromFile,
|
|
612
|
+
codeSnippet: options.code ?? codeFromFile,
|
|
613
|
+
runtime: options.runtime ?? `Node ${process.version}`,
|
|
614
|
+
framework: options.framework ?? detectedFramework ?? "unknown"
|
|
615
|
+
};
|
|
616
|
+
return {
|
|
617
|
+
output: options.json ? "json" : "pretty",
|
|
618
|
+
context
|
|
619
|
+
};
|
|
620
|
+
}
|
|
324
621
|
async function promptForError() {
|
|
325
622
|
if (!process.stdin.isTTY) {
|
|
326
623
|
return "";
|
|
@@ -427,32 +724,58 @@ Input modes:
|
|
|
427
724
|
3) Pipe from files/commands
|
|
428
725
|
$ cat error.txt | explain-my-error
|
|
429
726
|
$ pnpm run build 2>&1 | eme
|
|
727
|
+
|
|
728
|
+
Power features:
|
|
729
|
+
- Context-aware analysis: pass stack traces, code snippets, runtime, and framework.
|
|
730
|
+
- Structured output: use --json for machine-readable responses.
|
|
731
|
+
- Auto framework detection: best-effort detection from local package.json.
|
|
430
732
|
`
|
|
431
733
|
);
|
|
432
|
-
program.command("explain").description("Explain a programming error message").argument("[error...]", "Error message to analyze").addHelpText(
|
|
734
|
+
program.command("explain").description("Explain a programming error message").argument("[error...]", "Error message to analyze").option("--json", "Output structured JSON").option("--stack <stackTrace>", "Additional stack trace context").option("--stack-file <path>", "Read stack trace from a file").option("--code <codeSnippet>", "Additional code snippet context").option("--code-file <path>", "Read code snippet from a file").option("--runtime <runtime>", "Runtime context (example: node 20, bun 1.1)").option("--framework <framework>", "Framework context (example: react, nextjs, express)").addHelpText(
|
|
433
735
|
"after",
|
|
434
736
|
`
|
|
737
|
+
Context options:
|
|
738
|
+
--stack inline stack trace text
|
|
739
|
+
--stack-file file path for stack trace
|
|
740
|
+
--code inline code snippet text
|
|
741
|
+
--code-file file path for code snippet
|
|
742
|
+
--runtime runtime label (default: current Node version)
|
|
743
|
+
--framework framework label (auto-detected when possible)
|
|
744
|
+
|
|
745
|
+
Output modes:
|
|
746
|
+
default pretty output includes:
|
|
747
|
+
- likely root cause
|
|
748
|
+
- 2-3 hypotheses with confidence
|
|
749
|
+
- ranked fix plans (fast/proper/long-term) with tradeoffs
|
|
750
|
+
- framework-specific recipes
|
|
751
|
+
- remediation commands and verify checklist
|
|
752
|
+
|
|
753
|
+
--json output includes the same data in machine-readable JSON.
|
|
754
|
+
|
|
435
755
|
Examples:
|
|
436
756
|
$ explain-my-error explain "SyntaxError: Unexpected token }"
|
|
437
757
|
$ eme explain "Module not found: Can't resolve 'axios'"
|
|
438
758
|
$ cat error.txt | explain-my-error explain
|
|
759
|
+
$ eme explain "TypeError: Cannot read property 'map' of undefined" --framework react --runtime "node 20"
|
|
760
|
+
$ eme explain --stack-file ./error.log --code-file ./src/app.ts --json
|
|
439
761
|
`
|
|
440
|
-
).action(async (errorParts) => {
|
|
762
|
+
).action(async (errorParts, options) => {
|
|
441
763
|
const inlineError = errorParts.join(" ").trim();
|
|
442
764
|
const pipedError = inlineError ? "" : await readStdinFn();
|
|
443
765
|
const promptedError = !inlineError && !pipedError ? await promptForErrorFn() : "";
|
|
444
766
|
const finalError = inlineError || pipedError || promptedError;
|
|
767
|
+
const explainOptions = await buildExplainOptions(options);
|
|
445
768
|
const hasApiKey = await ensureApiKeyFn();
|
|
446
769
|
if (!hasApiKey) {
|
|
447
770
|
log.warn("GROQ_API_KEY is required. Set it and run again.");
|
|
448
771
|
return;
|
|
449
772
|
}
|
|
450
|
-
await runExplain(finalError);
|
|
773
|
+
await runExplain(finalError, explainOptions);
|
|
451
774
|
});
|
|
452
775
|
program.action(async () => {
|
|
453
776
|
if (stdinIsTTY()) {
|
|
454
777
|
const promptedError = await promptForErrorFn();
|
|
455
|
-
await runExplain(promptedError);
|
|
778
|
+
await runExplain(promptedError, await buildExplainOptions({}));
|
|
456
779
|
return;
|
|
457
780
|
}
|
|
458
781
|
const pipedError = await readStdinFn();
|
|
@@ -465,7 +788,7 @@ Examples:
|
|
|
465
788
|
log.warn("GROQ_API_KEY is required. Set it and run again.");
|
|
466
789
|
return;
|
|
467
790
|
}
|
|
468
|
-
await runExplain(pipedError);
|
|
791
|
+
await runExplain(pipedError, await buildExplainOptions({}));
|
|
469
792
|
});
|
|
470
793
|
await program.parseAsync(argv);
|
|
471
794
|
}
|