explain-my-error 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/CHANGELOG.md +18 -0
- package/README.md +116 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +319 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +53 -0
- package/dist/index.js +327 -0
- package/dist/index.js.map +1 -0
- package/package.json +69 -0
- package/skills/SKILL.md +79 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- Initial production-ready CLI for explaining programming errors with Groq.
|
|
13
|
+
- Interactive input mode, inline argument mode, and piped input support.
|
|
14
|
+
- Open Agent Skill definition in `skills/SKILL.md`.
|
|
15
|
+
- Test suite with Vitest for CLI and command flows.
|
|
16
|
+
- Production build pipeline using tsup + Biome + TypeScript checks.
|
|
17
|
+
- GitHub Actions CI workflow for automated quality checks.
|
|
18
|
+
|
package/README.md
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# explain-my-error
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+
|
|
5
|
+
Turn confusing programming errors into clear fixes directly in your terminal.
|
|
6
|
+
|
|
7
|
+
`explain-my-error` returns:
|
|
8
|
+
|
|
9
|
+
- A plain-English explanation
|
|
10
|
+
- Common root causes
|
|
11
|
+
- A practical fix
|
|
12
|
+
- A code example
|
|
13
|
+
- An ELI5 summary
|
|
14
|
+
|
|
15
|
+
Alias included: `eme`
|
|
16
|
+
|
|
17
|
+
## Install
|
|
18
|
+
|
|
19
|
+
Install in a project:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm i explain-my-error
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Install globally (recommended for CLI usage from anywhere):
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npm i -g explain-my-error
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Usage
|
|
32
|
+
|
|
33
|
+
### Interactive mode
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
explain-my-error
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
eme
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Inline message
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
explain-my-error explain "TypeError: Cannot read property 'map' of undefined"
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
eme explain "ReferenceError: x is not defined"
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Piped input
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
cat error.txt | explain-my-error
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
npm run build 2>&1 | eme
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Command reference
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
explain-my-error [command]
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Commands:
|
|
70
|
+
|
|
71
|
+
- `explain [error...]` Explain a programming error
|
|
72
|
+
- `--help` Show CLI help
|
|
73
|
+
- `--version` Show CLI version
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
## Example output
|
|
77
|
+
|
|
78
|
+
```text
|
|
79
|
+
========================================================================
|
|
80
|
+
| EXPLAIN MY ERROR |
|
|
81
|
+
| AI powered debugging for humans |
|
|
82
|
+
========================================================================
|
|
83
|
+
|
|
84
|
+
ERROR: TypeError: Cannot read property 'map' of undefined
|
|
85
|
+
|
|
86
|
+
------------------------------------------------------------------------
|
|
87
|
+
EXPLANATION
|
|
88
|
+
This happens when .map() is called on a variable that is undefined.
|
|
89
|
+
------------------------------------------------------------------------
|
|
90
|
+
|
|
91
|
+
------------------------------------------------------------------------
|
|
92
|
+
COMMON CAUSES
|
|
93
|
+
1. API data not loaded
|
|
94
|
+
2. State not initialized
|
|
95
|
+
3. Incorrect variable reference
|
|
96
|
+
------------------------------------------------------------------------
|
|
97
|
+
|
|
98
|
+
FIX
|
|
99
|
+
Ensure the array exists before calling map.
|
|
100
|
+
|
|
101
|
+
------------------------------------------------------------------------
|
|
102
|
+
CODE EXAMPLE
|
|
103
|
+
const items = data?.items ?? [];
|
|
104
|
+
items.map(...)
|
|
105
|
+
------------------------------------------------------------------------
|
|
106
|
+
|
|
107
|
+
------------------------------------------------------------------------
|
|
108
|
+
ELI5
|
|
109
|
+
Your code expected a box of toys (an array), but the toy box was empty.
|
|
110
|
+
------------------------------------------------------------------------
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Open Agent Skill
|
|
114
|
+
|
|
115
|
+
- Skill spec: `skills/SKILL.md`
|
|
116
|
+
- Skill function: `runExplainErrorSkill(input)`
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/cli-entry.ts
|
|
4
|
+
import "dotenv/config";
|
|
5
|
+
|
|
6
|
+
// src/cli.ts
|
|
7
|
+
import { createInterface } from "readline/promises";
|
|
8
|
+
import { Command } from "commander";
|
|
9
|
+
import pc3 from "picocolors";
|
|
10
|
+
|
|
11
|
+
// src/commands/explain.ts
|
|
12
|
+
import ora from "ora";
|
|
13
|
+
|
|
14
|
+
// src/services/ai.ts
|
|
15
|
+
import axios from "axios";
|
|
16
|
+
|
|
17
|
+
// src/types/error.ts
|
|
18
|
+
import { z } from "zod";
|
|
19
|
+
var explainedErrorSchema = z.object({
|
|
20
|
+
title: z.string().min(1),
|
|
21
|
+
explanation: z.string().min(1),
|
|
22
|
+
common_causes: z.array(z.string().min(1)).min(1),
|
|
23
|
+
fix: z.string().min(1),
|
|
24
|
+
code_example: z.string().min(1),
|
|
25
|
+
eli5: z.string().min(1)
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
// src/services/ai.ts
|
|
29
|
+
var GROQ_API_URL = "https://api.groq.com/openai/v1/chat/completions";
|
|
30
|
+
var PRIMARY_GROQ_MODEL = "llama3-70b-8192";
|
|
31
|
+
var FALLBACK_GROQ_MODEL = process.env.GROQ_FALLBACK_MODEL ?? "llama-3.3-70b-versatile";
|
|
32
|
+
function extractJson(content) {
|
|
33
|
+
const trimmed = content.trim();
|
|
34
|
+
try {
|
|
35
|
+
return JSON.parse(trimmed);
|
|
36
|
+
} catch {
|
|
37
|
+
const match = trimmed.match(/\{[\s\S]*\}/);
|
|
38
|
+
if (!match) {
|
|
39
|
+
throw new Error("AI did not return valid JSON.");
|
|
40
|
+
}
|
|
41
|
+
return JSON.parse(match[0]);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
function stringifyField(value) {
|
|
45
|
+
if (typeof value === "string") {
|
|
46
|
+
return value;
|
|
47
|
+
}
|
|
48
|
+
if (value == null) {
|
|
49
|
+
return "";
|
|
50
|
+
}
|
|
51
|
+
if (typeof value === "object") {
|
|
52
|
+
try {
|
|
53
|
+
return JSON.stringify(value, null, 2);
|
|
54
|
+
} catch {
|
|
55
|
+
return String(value);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return String(value);
|
|
59
|
+
}
|
|
60
|
+
function normalizeResponseShape(payload) {
|
|
61
|
+
if (!payload || typeof payload !== "object") {
|
|
62
|
+
return payload;
|
|
63
|
+
}
|
|
64
|
+
const data = payload;
|
|
65
|
+
const rawCauses = data.common_causes;
|
|
66
|
+
const commonCauses = Array.isArray(rawCauses) ? rawCauses.map((item) => stringifyField(item)).filter(Boolean) : stringifyField(rawCauses).split(/\n|,/).map((item) => item.trim()).filter(Boolean);
|
|
67
|
+
return {
|
|
68
|
+
title: stringifyField(data.title),
|
|
69
|
+
explanation: stringifyField(data.explanation),
|
|
70
|
+
common_causes: commonCauses,
|
|
71
|
+
fix: stringifyField(data.fix),
|
|
72
|
+
code_example: stringifyField(data.code_example),
|
|
73
|
+
eli5: stringifyField(data.eli5)
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
async function explainErrorWithAI(errorMessage) {
|
|
77
|
+
const apiKey = process.env.GROQ_API_KEY;
|
|
78
|
+
if (!apiKey) {
|
|
79
|
+
throw new Error("Missing GROQ_API_KEY environment variable.");
|
|
80
|
+
}
|
|
81
|
+
const prompt = "You are a senior software engineer. Explain the programming error and return JSON with fields: title, explanation, common_causes, fix, code_example, eli5.";
|
|
82
|
+
const requestBody = {
|
|
83
|
+
messages: [
|
|
84
|
+
{ role: "system", content: prompt },
|
|
85
|
+
{
|
|
86
|
+
role: "user",
|
|
87
|
+
content: `Error message:
|
|
88
|
+
${errorMessage}
|
|
89
|
+
|
|
90
|
+
Return strict JSON only.`
|
|
91
|
+
}
|
|
92
|
+
],
|
|
93
|
+
temperature: 0.2
|
|
94
|
+
};
|
|
95
|
+
const requestConfig = {
|
|
96
|
+
headers: {
|
|
97
|
+
Authorization: `Bearer ${apiKey}`,
|
|
98
|
+
"Content-Type": "application/json"
|
|
99
|
+
},
|
|
100
|
+
timeout: 3e4
|
|
101
|
+
};
|
|
102
|
+
let response;
|
|
103
|
+
try {
|
|
104
|
+
response = await axios.post(
|
|
105
|
+
GROQ_API_URL,
|
|
106
|
+
{ model: PRIMARY_GROQ_MODEL, ...requestBody },
|
|
107
|
+
requestConfig
|
|
108
|
+
);
|
|
109
|
+
} catch (error) {
|
|
110
|
+
if (axios.isAxiosError(error)) {
|
|
111
|
+
const providerMessage = typeof error.response?.data?.error?.message === "string" ? error.response.data.error.message : error.message;
|
|
112
|
+
const isModelDecommissioned = providerMessage.toLowerCase().includes("decommissioned") || providerMessage.toLowerCase().includes("no longer supported");
|
|
113
|
+
if (isModelDecommissioned) {
|
|
114
|
+
response = await axios.post(
|
|
115
|
+
GROQ_API_URL,
|
|
116
|
+
{ model: FALLBACK_GROQ_MODEL, ...requestBody },
|
|
117
|
+
requestConfig
|
|
118
|
+
);
|
|
119
|
+
} else {
|
|
120
|
+
throw new Error(`Groq API request failed: ${providerMessage}`);
|
|
121
|
+
}
|
|
122
|
+
} else {
|
|
123
|
+
throw error;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
const content = response.data?.choices?.[0]?.message?.content;
|
|
127
|
+
if (typeof content !== "string" || !content.trim()) {
|
|
128
|
+
throw new Error("AI response was empty.");
|
|
129
|
+
}
|
|
130
|
+
const parsed = extractJson(content);
|
|
131
|
+
const normalized = normalizeResponseShape(parsed);
|
|
132
|
+
return explainedErrorSchema.parse(normalized);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// src/utils/formatter.ts
|
|
136
|
+
import pc from "picocolors";
|
|
137
|
+
var CARD_WIDTH = 72;
|
|
138
|
+
function line(char = "-") {
|
|
139
|
+
return char.repeat(CARD_WIDTH);
|
|
140
|
+
}
|
|
141
|
+
function pad(text) {
|
|
142
|
+
return text.length > CARD_WIDTH - 4 ? `${text.slice(0, CARD_WIDTH - 7)}...` : text;
|
|
143
|
+
}
|
|
144
|
+
function framedLine(text) {
|
|
145
|
+
return `| ${pad(text).padEnd(CARD_WIDTH - 4)} |`;
|
|
146
|
+
}
|
|
147
|
+
function block(title, body) {
|
|
148
|
+
const rows = body.split("\n").map((row) => pc.white(row));
|
|
149
|
+
return [pc.cyan(line()), pc.bold(pc.cyan(title)), ...rows, pc.cyan(line())].join("\n");
|
|
150
|
+
}
|
|
151
|
+
function formatExplainedError(result) {
|
|
152
|
+
const commonCauses = result.common_causes.map((cause, index) => pc.white(`${index + 1}. ${cause}`)).join("\n");
|
|
153
|
+
const hero = [
|
|
154
|
+
pc.cyan(line("=")),
|
|
155
|
+
framedLine(pc.bold(pc.cyan("EXPLAIN MY ERROR"))),
|
|
156
|
+
framedLine(pc.dim("AI powered debugging for humans")),
|
|
157
|
+
pc.cyan(line("="))
|
|
158
|
+
].join("\n");
|
|
159
|
+
return [
|
|
160
|
+
hero,
|
|
161
|
+
"",
|
|
162
|
+
`${pc.bold(pc.cyan("ERROR"))}: ${pc.bold(pc.white(result.title))}`,
|
|
163
|
+
"",
|
|
164
|
+
block("EXPLANATION", result.explanation),
|
|
165
|
+
"",
|
|
166
|
+
block("COMMON CAUSES", commonCauses),
|
|
167
|
+
"",
|
|
168
|
+
`${pc.bold(pc.green("FIX"))}
|
|
169
|
+
${pc.white(result.fix)}`,
|
|
170
|
+
"",
|
|
171
|
+
block("CODE EXAMPLE", result.code_example),
|
|
172
|
+
"",
|
|
173
|
+
block("ELI5", result.eli5),
|
|
174
|
+
"",
|
|
175
|
+
pc.dim('Tip: run `eme explain "<error>"` for quick mode.')
|
|
176
|
+
].join("\n");
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// src/utils/logger.ts
|
|
180
|
+
import pc2 from "picocolors";
|
|
181
|
+
var logger = {
|
|
182
|
+
info(message) {
|
|
183
|
+
process.stdout.write(`${pc2.white(message)}
|
|
184
|
+
`);
|
|
185
|
+
},
|
|
186
|
+
success(message) {
|
|
187
|
+
process.stdout.write(`${pc2.green(`OK: ${message}`)}
|
|
188
|
+
`);
|
|
189
|
+
},
|
|
190
|
+
warn(message) {
|
|
191
|
+
process.stderr.write(`${pc2.yellow(`WARN: ${message}`)}
|
|
192
|
+
`);
|
|
193
|
+
},
|
|
194
|
+
error(message) {
|
|
195
|
+
process.stderr.write(`${pc2.red(`ERROR: ${message}`)}
|
|
196
|
+
`);
|
|
197
|
+
}
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
// src/commands/explain.ts
|
|
201
|
+
async function runExplainCommand(errorMessage, deps = {}) {
|
|
202
|
+
const explainError = deps.explainError ?? explainErrorWithAI;
|
|
203
|
+
const createSpinner = deps.createSpinner ?? ((text) => ora(text).start());
|
|
204
|
+
const formatOutput = deps.formatOutput ?? formatExplainedError;
|
|
205
|
+
const log = deps.log ?? logger;
|
|
206
|
+
if (!errorMessage?.trim()) {
|
|
207
|
+
log.warn("Please provide an error message.");
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
const spinner = createSpinner("Analyzing your error...");
|
|
211
|
+
try {
|
|
212
|
+
const result = await explainError(errorMessage.trim());
|
|
213
|
+
spinner.succeed("Explanation ready.");
|
|
214
|
+
log.info("");
|
|
215
|
+
log.info(formatOutput(result));
|
|
216
|
+
} catch (error) {
|
|
217
|
+
spinner.fail("Could not explain this error.");
|
|
218
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
219
|
+
log.error(message);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// src/cli.ts
|
|
224
|
+
async function readStdin() {
|
|
225
|
+
if (process.stdin.isTTY) {
|
|
226
|
+
return "";
|
|
227
|
+
}
|
|
228
|
+
const chunks = [];
|
|
229
|
+
for await (const chunk of process.stdin) {
|
|
230
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
231
|
+
}
|
|
232
|
+
return Buffer.concat(chunks).toString("utf8").trim();
|
|
233
|
+
}
|
|
234
|
+
async function promptForError() {
|
|
235
|
+
if (!process.stdin.isTTY) {
|
|
236
|
+
return "";
|
|
237
|
+
}
|
|
238
|
+
const rl = createInterface({
|
|
239
|
+
input: process.stdin,
|
|
240
|
+
output: process.stdout
|
|
241
|
+
});
|
|
242
|
+
try {
|
|
243
|
+
logger.info(pc3.cyan("Paste an error message and press Enter."));
|
|
244
|
+
logger.info(pc3.dim('Example: TypeError: Cannot read property "map" of undefined'));
|
|
245
|
+
while (true) {
|
|
246
|
+
const answer = await rl.question(pc3.bold(pc3.cyan("\n> Error message: ")));
|
|
247
|
+
const trimmed = answer.trim();
|
|
248
|
+
if (trimmed) {
|
|
249
|
+
return trimmed;
|
|
250
|
+
}
|
|
251
|
+
logger.warn("Error message cannot be empty. Please try again.");
|
|
252
|
+
}
|
|
253
|
+
} finally {
|
|
254
|
+
rl.close();
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
async function runCli(argv = process.argv, deps = {}) {
|
|
258
|
+
const runExplain = deps.runExplain ?? runExplainCommand;
|
|
259
|
+
const readStdinFn = deps.readStdin ?? readStdin;
|
|
260
|
+
const promptForErrorFn = deps.promptForError ?? promptForError;
|
|
261
|
+
const stdinIsTTY = deps.stdinIsTTY ?? (() => Boolean(process.stdin.isTTY));
|
|
262
|
+
const log = deps.log ?? logger;
|
|
263
|
+
const program = new Command();
|
|
264
|
+
program.name("explain-my-error").description("Explain programming errors with AI").version("1.0.0").addHelpText(
|
|
265
|
+
"after",
|
|
266
|
+
`
|
|
267
|
+
Input modes:
|
|
268
|
+
1) Interactive (default)
|
|
269
|
+
$ explain-my-error
|
|
270
|
+
$ eme
|
|
271
|
+
|
|
272
|
+
2) Inline argument
|
|
273
|
+
$ explain-my-error explain "TypeError: Cannot read property 'map' of undefined"
|
|
274
|
+
$ eme explain "ReferenceError: x is not defined"
|
|
275
|
+
|
|
276
|
+
3) Pipe from files/commands
|
|
277
|
+
$ cat error.txt | explain-my-error
|
|
278
|
+
$ pnpm run build 2>&1 | eme
|
|
279
|
+
`
|
|
280
|
+
);
|
|
281
|
+
program.command("explain").description("Explain a programming error message").argument("[error...]", "Error message to analyze").addHelpText(
|
|
282
|
+
"after",
|
|
283
|
+
`
|
|
284
|
+
Examples:
|
|
285
|
+
$ explain-my-error explain "SyntaxError: Unexpected token }"
|
|
286
|
+
$ eme explain "Module not found: Can't resolve 'axios'"
|
|
287
|
+
$ cat error.txt | explain-my-error explain
|
|
288
|
+
`
|
|
289
|
+
).action(async (errorParts) => {
|
|
290
|
+
const inlineError = errorParts.join(" ").trim();
|
|
291
|
+
const pipedError = inlineError ? "" : await readStdinFn();
|
|
292
|
+
const promptedError = !inlineError && !pipedError ? await promptForErrorFn() : "";
|
|
293
|
+
const finalError = inlineError || pipedError || promptedError;
|
|
294
|
+
await runExplain(finalError);
|
|
295
|
+
});
|
|
296
|
+
program.action(async () => {
|
|
297
|
+
if (stdinIsTTY()) {
|
|
298
|
+
const promptedError = await promptForErrorFn();
|
|
299
|
+
await runExplain(promptedError);
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
const pipedError = await readStdinFn();
|
|
303
|
+
if (!pipedError) {
|
|
304
|
+
log.warn('No input detected. Use: explain-my-error explain "<error message>"');
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
await runExplain(pipedError);
|
|
308
|
+
});
|
|
309
|
+
await program.parseAsync(argv);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// src/cli-entry.ts
|
|
313
|
+
runCli().catch((error) => {
|
|
314
|
+
const message = error instanceof Error ? error.message : "Unexpected CLI failure";
|
|
315
|
+
process.stderr.write(`${message}
|
|
316
|
+
`);
|
|
317
|
+
process.exit(1);
|
|
318
|
+
});
|
|
319
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/cli-entry.ts","../src/cli.ts","../src/commands/explain.ts","../src/services/ai.ts","../src/types/error.ts","../src/utils/formatter.ts","../src/utils/logger.ts"],"sourcesContent":["#!/usr/bin/env node\nimport \"dotenv/config\";\nimport { runCli } from \"./cli.js\";\n\nrunCli().catch((error: unknown) => {\n const message = error instanceof Error ? error.message : \"Unexpected CLI failure\";\n process.stderr.write(`${message}\\n`);\n process.exit(1);\n});\n","import { createInterface } from \"node:readline/promises\";\nimport { Command } from \"commander\";\nimport pc from \"picocolors\";\nimport { runExplainCommand } from \"./commands/explain.js\";\nimport { logger } from \"./utils/logger.js\";\n\nasync function readStdin(): Promise<string> {\n if (process.stdin.isTTY) {\n return \"\";\n }\n\n const chunks: Buffer[] = [];\n for await (const chunk of process.stdin) {\n chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));\n }\n return Buffer.concat(chunks).toString(\"utf8\").trim();\n}\n\nasync function promptForError(): Promise<string> {\n if (!process.stdin.isTTY) {\n return \"\";\n }\n\n const rl = createInterface({\n input: process.stdin,\n output: process.stdout,\n });\n\n try {\n logger.info(pc.cyan(\"Paste an error message and press Enter.\"));\n logger.info(pc.dim('Example: TypeError: Cannot read property \"map\" of undefined'));\n while (true) {\n const answer = await rl.question(pc.bold(pc.cyan(\"\\n> Error message: \")));\n const trimmed = answer.trim();\n\n if (trimmed) {\n return trimmed;\n }\n\n logger.warn(\"Error message cannot be empty. Please try again.\");\n }\n } finally {\n rl.close();\n }\n}\n\ntype CliLogger = {\n warn(message: string): void;\n};\n\ntype RunCliDeps = {\n runExplain?: (errorMessage: string) => Promise<void>;\n readStdin?: () => Promise<string>;\n promptForError?: () => Promise<string>;\n stdinIsTTY?: () => boolean;\n log?: CliLogger;\n};\n\nexport async function runCli(argv: string[] = process.argv, deps: RunCliDeps = {}): Promise<void> {\n const runExplain = deps.runExplain ?? runExplainCommand;\n const readStdinFn = deps.readStdin ?? readStdin;\n const promptForErrorFn = deps.promptForError ?? promptForError;\n const stdinIsTTY = deps.stdinIsTTY ?? (() => Boolean(process.stdin.isTTY));\n const log = deps.log ?? logger;\n\n const program = new Command();\n\n program\n .name(\"explain-my-error\")\n .description(\"Explain programming errors with AI\")\n .version(\"1.0.0\")\n .addHelpText(\n \"after\",\n `\nInput modes:\n 1) Interactive (default)\n $ explain-my-error\n $ eme\n\n 2) Inline argument\n $ explain-my-error explain \"TypeError: Cannot read property 'map' of undefined\"\n $ eme explain \"ReferenceError: x is not defined\"\n\n 3) Pipe from files/commands\n $ cat error.txt | explain-my-error\n $ pnpm run build 2>&1 | eme\n`,\n );\n\n program\n .command(\"explain\")\n .description(\"Explain a programming error message\")\n .argument(\"[error...]\", \"Error message to analyze\")\n .addHelpText(\n \"after\",\n `\nExamples:\n $ explain-my-error explain \"SyntaxError: Unexpected token }\"\n $ eme explain \"Module not found: Can't resolve 'axios'\"\n $ cat error.txt | explain-my-error explain\n`,\n )\n .action(async (errorParts: string[]) => {\n const inlineError = errorParts.join(\" \").trim();\n const pipedError = inlineError ? \"\" : await readStdinFn();\n const promptedError = !inlineError && !pipedError ? await promptForErrorFn() : \"\";\n const finalError = inlineError || pipedError || promptedError;\n await runExplain(finalError);\n });\n\n program.action(async () => {\n if (stdinIsTTY()) {\n const promptedError = await promptForErrorFn();\n await runExplain(promptedError);\n return;\n }\n\n const pipedError = await readStdinFn();\n if (!pipedError) {\n log.warn('No input detected. Use: explain-my-error explain \"<error message>\"');\n return;\n }\n await runExplain(pipedError);\n });\n\n await program.parseAsync(argv);\n}\n","import ora from \"ora\";\nimport { explainErrorWithAI } from \"../services/ai.js\";\nimport type { ExplainedError } from \"../types/error.js\";\nimport { formatExplainedError } from \"../utils/formatter.js\";\nimport { logger } from \"../utils/logger.js\";\n\ntype SpinnerLike = {\n succeed(text: string): void;\n fail(text: string): void;\n};\n\ntype ExplainLogger = {\n info(message: string): void;\n warn(message: string): void;\n error(message: string): void;\n};\n\ntype RunExplainDeps = {\n explainError?: (errorMessage: string) => Promise<ExplainedError>;\n createSpinner?: (text: string) => SpinnerLike;\n formatOutput?: (result: ExplainedError) => string;\n log?: ExplainLogger;\n};\n\nexport async function runExplainCommand(\n errorMessage: string,\n deps: RunExplainDeps = {},\n): Promise<void> {\n const explainError = deps.explainError ?? explainErrorWithAI;\n const createSpinner = deps.createSpinner ?? ((text: string) => ora(text).start());\n const formatOutput = deps.formatOutput ?? formatExplainedError;\n const log = deps.log ?? logger;\n\n if (!errorMessage?.trim()) {\n log.warn(\"Please provide an error message.\");\n return;\n }\n\n const spinner = createSpinner(\"Analyzing your error...\");\n\n try {\n const result = await explainError(errorMessage.trim());\n spinner.succeed(\"Explanation ready.\");\n log.info(\"\");\n log.info(formatOutput(result));\n } catch (error) {\n spinner.fail(\"Could not explain this error.\");\n const message = error instanceof Error ? error.message : \"Unknown error\";\n log.error(message);\n }\n}\n","import axios from \"axios\";\nimport { type ExplainedError, explainedErrorSchema } from \"../types/error.js\";\n\nconst GROQ_API_URL = \"https://api.groq.com/openai/v1/chat/completions\";\nconst PRIMARY_GROQ_MODEL = \"llama3-70b-8192\";\nconst FALLBACK_GROQ_MODEL = process.env.GROQ_FALLBACK_MODEL ?? \"llama-3.3-70b-versatile\";\n\ntype GroqChatResponse = {\n choices?: Array<{ message?: { content?: string } }>;\n};\n\nfunction extractJson(content: string): unknown {\n const trimmed = content.trim();\n\n try {\n return JSON.parse(trimmed);\n } catch {\n const match = trimmed.match(/\\{[\\s\\S]*\\}/);\n if (!match) {\n throw new Error(\"AI did not return valid JSON.\");\n }\n return JSON.parse(match[0]);\n }\n}\n\nfunction stringifyField(value: unknown): string {\n if (typeof value === \"string\") {\n return value;\n }\n if (value == null) {\n return \"\";\n }\n if (typeof value === \"object\") {\n try {\n return JSON.stringify(value, null, 2);\n } catch {\n return String(value);\n }\n }\n return String(value);\n}\n\nfunction normalizeResponseShape(payload: unknown): unknown {\n if (!payload || typeof payload !== \"object\") {\n return payload;\n }\n\n const data = payload as Record<string, unknown>;\n\n const rawCauses = data.common_causes;\n const commonCauses = Array.isArray(rawCauses)\n ? rawCauses.map((item) => stringifyField(item)).filter(Boolean)\n : stringifyField(rawCauses)\n .split(/\\n|,/)\n .map((item) => item.trim())\n .filter(Boolean);\n\n return {\n title: stringifyField(data.title),\n explanation: stringifyField(data.explanation),\n common_causes: commonCauses,\n fix: stringifyField(data.fix),\n code_example: stringifyField(data.code_example),\n eli5: stringifyField(data.eli5),\n };\n}\n\nexport async function explainErrorWithAI(errorMessage: string): Promise<ExplainedError> {\n const apiKey = process.env.GROQ_API_KEY;\n if (!apiKey) {\n throw new Error(\"Missing GROQ_API_KEY environment variable.\");\n }\n\n const prompt =\n \"You are a senior software engineer. Explain the programming error and return JSON with fields: title, explanation, common_causes, fix, code_example, eli5.\";\n\n const requestBody = {\n messages: [\n { role: \"system\" as const, content: prompt },\n {\n role: \"user\" as const,\n content: `Error message:\\n${errorMessage}\\n\\nReturn strict JSON only.`,\n },\n ],\n temperature: 0.2,\n };\n\n const requestConfig = {\n headers: {\n Authorization: `Bearer ${apiKey}`,\n \"Content-Type\": \"application/json\",\n },\n timeout: 30000,\n };\n\n let response: { data?: GroqChatResponse };\n try {\n response = await axios.post(\n GROQ_API_URL,\n { model: PRIMARY_GROQ_MODEL, ...requestBody },\n requestConfig,\n );\n } catch (error) {\n if (axios.isAxiosError(error)) {\n const providerMessage =\n typeof error.response?.data?.error?.message === \"string\"\n ? error.response.data.error.message\n : error.message;\n\n const isModelDecommissioned =\n providerMessage.toLowerCase().includes(\"decommissioned\") ||\n providerMessage.toLowerCase().includes(\"no longer supported\");\n\n if (isModelDecommissioned) {\n response = await axios.post(\n GROQ_API_URL,\n { model: FALLBACK_GROQ_MODEL, ...requestBody },\n requestConfig,\n );\n } else {\n throw new Error(`Groq API request failed: ${providerMessage}`);\n }\n } else {\n throw error;\n }\n }\n\n const content = response.data?.choices?.[0]?.message?.content;\n if (typeof content !== \"string\" || !content.trim()) {\n throw new Error(\"AI response was empty.\");\n }\n\n const parsed = extractJson(content);\n const normalized = normalizeResponseShape(parsed);\n return explainedErrorSchema.parse(normalized);\n}\n","import { z } from \"zod\";\n\nexport const explainedErrorSchema = z.object({\n title: z.string().min(1),\n explanation: z.string().min(1),\n common_causes: z.array(z.string().min(1)).min(1),\n fix: z.string().min(1),\n code_example: z.string().min(1),\n eli5: z.string().min(1),\n});\n\nexport type ExplainedError = z.infer<typeof explainedErrorSchema>;\n","import pc from \"picocolors\";\nimport type { ExplainedError } from \"../types/error.js\";\n\nconst CARD_WIDTH = 72;\n\nfunction line(char = \"-\"): string {\n return char.repeat(CARD_WIDTH);\n}\n\nfunction pad(text: string): string {\n return text.length > CARD_WIDTH - 4 ? `${text.slice(0, CARD_WIDTH - 7)}...` : text;\n}\n\nfunction framedLine(text: string): string {\n return `| ${pad(text).padEnd(CARD_WIDTH - 4)} |`;\n}\n\nfunction block(title: string, body: string): string {\n const rows = body.split(\"\\n\").map((row) => pc.white(row));\n return [pc.cyan(line()), pc.bold(pc.cyan(title)), ...rows, pc.cyan(line())].join(\"\\n\");\n}\n\nexport function formatExplainedError(result: ExplainedError): string {\n const commonCauses = result.common_causes\n .map((cause, index) => pc.white(`${index + 1}. ${cause}`))\n .join(\"\\n\");\n const hero = [\n pc.cyan(line(\"=\")),\n framedLine(pc.bold(pc.cyan(\"EXPLAIN MY ERROR\"))),\n framedLine(pc.dim(\"AI powered debugging for humans\")),\n pc.cyan(line(\"=\")),\n ].join(\"\\n\");\n\n return [\n hero,\n \"\",\n `${pc.bold(pc.cyan(\"ERROR\"))}: ${pc.bold(pc.white(result.title))}`,\n \"\",\n block(\"EXPLANATION\", result.explanation),\n \"\",\n block(\"COMMON CAUSES\", commonCauses),\n \"\",\n `${pc.bold(pc.green(\"FIX\"))}\\n${pc.white(result.fix)}`,\n \"\",\n block(\"CODE EXAMPLE\", result.code_example),\n \"\",\n block(\"ELI5\", result.eli5),\n \"\",\n pc.dim('Tip: run `eme explain \"<error>\"` for quick mode.'),\n ].join(\"\\n\");\n}\n","import pc from \"picocolors\";\n\nexport const logger = {\n info(message: string): void {\n process.stdout.write(`${pc.white(message)}\\n`);\n },\n success(message: string): void {\n process.stdout.write(`${pc.green(`OK: ${message}`)}\\n`);\n },\n warn(message: string): void {\n process.stderr.write(`${pc.yellow(`WARN: ${message}`)}\\n`);\n },\n error(message: string): void {\n process.stderr.write(`${pc.red(`ERROR: ${message}`)}\\n`);\n },\n};\n"],"mappings":"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","names":["pc","pc","pc"]}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
type CliLogger = {
|
|
4
|
+
warn(message: string): void;
|
|
5
|
+
};
|
|
6
|
+
type RunCliDeps = {
|
|
7
|
+
runExplain?: (errorMessage: string) => Promise<void>;
|
|
8
|
+
readStdin?: () => Promise<string>;
|
|
9
|
+
promptForError?: () => Promise<string>;
|
|
10
|
+
stdinIsTTY?: () => boolean;
|
|
11
|
+
log?: CliLogger;
|
|
12
|
+
};
|
|
13
|
+
declare function runCli(argv?: string[], deps?: RunCliDeps): Promise<void>;
|
|
14
|
+
|
|
15
|
+
declare const explainedErrorSchema: z.ZodObject<{
|
|
16
|
+
title: z.ZodString;
|
|
17
|
+
explanation: z.ZodString;
|
|
18
|
+
common_causes: z.ZodArray<z.ZodString, "many">;
|
|
19
|
+
fix: z.ZodString;
|
|
20
|
+
code_example: z.ZodString;
|
|
21
|
+
eli5: z.ZodString;
|
|
22
|
+
}, "strip", z.ZodTypeAny, {
|
|
23
|
+
title: string;
|
|
24
|
+
explanation: string;
|
|
25
|
+
common_causes: string[];
|
|
26
|
+
fix: string;
|
|
27
|
+
code_example: string;
|
|
28
|
+
eli5: string;
|
|
29
|
+
}, {
|
|
30
|
+
title: string;
|
|
31
|
+
explanation: string;
|
|
32
|
+
common_causes: string[];
|
|
33
|
+
fix: string;
|
|
34
|
+
code_example: string;
|
|
35
|
+
eli5: string;
|
|
36
|
+
}>;
|
|
37
|
+
type ExplainedError = z.infer<typeof explainedErrorSchema>;
|
|
38
|
+
|
|
39
|
+
declare function explainError(errorMessage: string): Promise<ExplainedError>;
|
|
40
|
+
|
|
41
|
+
declare function explainErrorWithAI(errorMessage: string): Promise<ExplainedError>;
|
|
42
|
+
|
|
43
|
+
declare const explainErrorSkillInputSchema: z.ZodObject<{
|
|
44
|
+
error: z.ZodString;
|
|
45
|
+
}, "strip", z.ZodTypeAny, {
|
|
46
|
+
error: string;
|
|
47
|
+
}, {
|
|
48
|
+
error: string;
|
|
49
|
+
}>;
|
|
50
|
+
type ExplainErrorSkillInput = z.infer<typeof explainErrorSkillInputSchema>;
|
|
51
|
+
declare function runExplainErrorSkill(input: ExplainErrorSkillInput): Promise<ExplainedError>;
|
|
52
|
+
|
|
53
|
+
export { type ExplainedError, explainError, explainErrorWithAI, runCli, runExplainErrorSkill };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
// src/cli.ts
|
|
2
|
+
import { createInterface } from "readline/promises";
|
|
3
|
+
import { Command } from "commander";
|
|
4
|
+
import pc3 from "picocolors";
|
|
5
|
+
|
|
6
|
+
// src/commands/explain.ts
|
|
7
|
+
import ora from "ora";
|
|
8
|
+
|
|
9
|
+
// src/services/ai.ts
|
|
10
|
+
import axios from "axios";
|
|
11
|
+
|
|
12
|
+
// src/types/error.ts
|
|
13
|
+
import { z } from "zod";
|
|
14
|
+
var explainedErrorSchema = z.object({
|
|
15
|
+
title: z.string().min(1),
|
|
16
|
+
explanation: z.string().min(1),
|
|
17
|
+
common_causes: z.array(z.string().min(1)).min(1),
|
|
18
|
+
fix: z.string().min(1),
|
|
19
|
+
code_example: z.string().min(1),
|
|
20
|
+
eli5: z.string().min(1)
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
// src/services/ai.ts
|
|
24
|
+
var GROQ_API_URL = "https://api.groq.com/openai/v1/chat/completions";
|
|
25
|
+
var PRIMARY_GROQ_MODEL = "llama3-70b-8192";
|
|
26
|
+
var FALLBACK_GROQ_MODEL = process.env.GROQ_FALLBACK_MODEL ?? "llama-3.3-70b-versatile";
|
|
27
|
+
function extractJson(content) {
|
|
28
|
+
const trimmed = content.trim();
|
|
29
|
+
try {
|
|
30
|
+
return JSON.parse(trimmed);
|
|
31
|
+
} catch {
|
|
32
|
+
const match = trimmed.match(/\{[\s\S]*\}/);
|
|
33
|
+
if (!match) {
|
|
34
|
+
throw new Error("AI did not return valid JSON.");
|
|
35
|
+
}
|
|
36
|
+
return JSON.parse(match[0]);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
function stringifyField(value) {
|
|
40
|
+
if (typeof value === "string") {
|
|
41
|
+
return value;
|
|
42
|
+
}
|
|
43
|
+
if (value == null) {
|
|
44
|
+
return "";
|
|
45
|
+
}
|
|
46
|
+
if (typeof value === "object") {
|
|
47
|
+
try {
|
|
48
|
+
return JSON.stringify(value, null, 2);
|
|
49
|
+
} catch {
|
|
50
|
+
return String(value);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return String(value);
|
|
54
|
+
}
|
|
55
|
+
function normalizeResponseShape(payload) {
|
|
56
|
+
if (!payload || typeof payload !== "object") {
|
|
57
|
+
return payload;
|
|
58
|
+
}
|
|
59
|
+
const data = payload;
|
|
60
|
+
const rawCauses = data.common_causes;
|
|
61
|
+
const commonCauses = Array.isArray(rawCauses) ? rawCauses.map((item) => stringifyField(item)).filter(Boolean) : stringifyField(rawCauses).split(/\n|,/).map((item) => item.trim()).filter(Boolean);
|
|
62
|
+
return {
|
|
63
|
+
title: stringifyField(data.title),
|
|
64
|
+
explanation: stringifyField(data.explanation),
|
|
65
|
+
common_causes: commonCauses,
|
|
66
|
+
fix: stringifyField(data.fix),
|
|
67
|
+
code_example: stringifyField(data.code_example),
|
|
68
|
+
eli5: stringifyField(data.eli5)
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
async function explainErrorWithAI(errorMessage) {
|
|
72
|
+
const apiKey = process.env.GROQ_API_KEY;
|
|
73
|
+
if (!apiKey) {
|
|
74
|
+
throw new Error("Missing GROQ_API_KEY environment variable.");
|
|
75
|
+
}
|
|
76
|
+
const prompt = "You are a senior software engineer. Explain the programming error and return JSON with fields: title, explanation, common_causes, fix, code_example, eli5.";
|
|
77
|
+
const requestBody = {
|
|
78
|
+
messages: [
|
|
79
|
+
{ role: "system", content: prompt },
|
|
80
|
+
{
|
|
81
|
+
role: "user",
|
|
82
|
+
content: `Error message:
|
|
83
|
+
${errorMessage}
|
|
84
|
+
|
|
85
|
+
Return strict JSON only.`
|
|
86
|
+
}
|
|
87
|
+
],
|
|
88
|
+
temperature: 0.2
|
|
89
|
+
};
|
|
90
|
+
const requestConfig = {
|
|
91
|
+
headers: {
|
|
92
|
+
Authorization: `Bearer ${apiKey}`,
|
|
93
|
+
"Content-Type": "application/json"
|
|
94
|
+
},
|
|
95
|
+
timeout: 3e4
|
|
96
|
+
};
|
|
97
|
+
let response;
|
|
98
|
+
try {
|
|
99
|
+
response = await axios.post(
|
|
100
|
+
GROQ_API_URL,
|
|
101
|
+
{ model: PRIMARY_GROQ_MODEL, ...requestBody },
|
|
102
|
+
requestConfig
|
|
103
|
+
);
|
|
104
|
+
} catch (error) {
|
|
105
|
+
if (axios.isAxiosError(error)) {
|
|
106
|
+
const providerMessage = typeof error.response?.data?.error?.message === "string" ? error.response.data.error.message : error.message;
|
|
107
|
+
const isModelDecommissioned = providerMessage.toLowerCase().includes("decommissioned") || providerMessage.toLowerCase().includes("no longer supported");
|
|
108
|
+
if (isModelDecommissioned) {
|
|
109
|
+
response = await axios.post(
|
|
110
|
+
GROQ_API_URL,
|
|
111
|
+
{ model: FALLBACK_GROQ_MODEL, ...requestBody },
|
|
112
|
+
requestConfig
|
|
113
|
+
);
|
|
114
|
+
} else {
|
|
115
|
+
throw new Error(`Groq API request failed: ${providerMessage}`);
|
|
116
|
+
}
|
|
117
|
+
} else {
|
|
118
|
+
throw error;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
const content = response.data?.choices?.[0]?.message?.content;
|
|
122
|
+
if (typeof content !== "string" || !content.trim()) {
|
|
123
|
+
throw new Error("AI response was empty.");
|
|
124
|
+
}
|
|
125
|
+
const parsed = extractJson(content);
|
|
126
|
+
const normalized = normalizeResponseShape(parsed);
|
|
127
|
+
return explainedErrorSchema.parse(normalized);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// src/utils/formatter.ts
|
|
131
|
+
import pc from "picocolors";
|
|
132
|
+
var CARD_WIDTH = 72;
|
|
133
|
+
function line(char = "-") {
|
|
134
|
+
return char.repeat(CARD_WIDTH);
|
|
135
|
+
}
|
|
136
|
+
function pad(text) {
|
|
137
|
+
return text.length > CARD_WIDTH - 4 ? `${text.slice(0, CARD_WIDTH - 7)}...` : text;
|
|
138
|
+
}
|
|
139
|
+
function framedLine(text) {
|
|
140
|
+
return `| ${pad(text).padEnd(CARD_WIDTH - 4)} |`;
|
|
141
|
+
}
|
|
142
|
+
function block(title, body) {
|
|
143
|
+
const rows = body.split("\n").map((row) => pc.white(row));
|
|
144
|
+
return [pc.cyan(line()), pc.bold(pc.cyan(title)), ...rows, pc.cyan(line())].join("\n");
|
|
145
|
+
}
|
|
146
|
+
function formatExplainedError(result) {
|
|
147
|
+
const commonCauses = result.common_causes.map((cause, index) => pc.white(`${index + 1}. ${cause}`)).join("\n");
|
|
148
|
+
const hero = [
|
|
149
|
+
pc.cyan(line("=")),
|
|
150
|
+
framedLine(pc.bold(pc.cyan("EXPLAIN MY ERROR"))),
|
|
151
|
+
framedLine(pc.dim("AI powered debugging for humans")),
|
|
152
|
+
pc.cyan(line("="))
|
|
153
|
+
].join("\n");
|
|
154
|
+
return [
|
|
155
|
+
hero,
|
|
156
|
+
"",
|
|
157
|
+
`${pc.bold(pc.cyan("ERROR"))}: ${pc.bold(pc.white(result.title))}`,
|
|
158
|
+
"",
|
|
159
|
+
block("EXPLANATION", result.explanation),
|
|
160
|
+
"",
|
|
161
|
+
block("COMMON CAUSES", commonCauses),
|
|
162
|
+
"",
|
|
163
|
+
`${pc.bold(pc.green("FIX"))}
|
|
164
|
+
${pc.white(result.fix)}`,
|
|
165
|
+
"",
|
|
166
|
+
block("CODE EXAMPLE", result.code_example),
|
|
167
|
+
"",
|
|
168
|
+
block("ELI5", result.eli5),
|
|
169
|
+
"",
|
|
170
|
+
pc.dim('Tip: run `eme explain "<error>"` for quick mode.')
|
|
171
|
+
].join("\n");
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// src/utils/logger.ts
|
|
175
|
+
import pc2 from "picocolors";
|
|
176
|
+
var logger = {
|
|
177
|
+
info(message) {
|
|
178
|
+
process.stdout.write(`${pc2.white(message)}
|
|
179
|
+
`);
|
|
180
|
+
},
|
|
181
|
+
success(message) {
|
|
182
|
+
process.stdout.write(`${pc2.green(`OK: ${message}`)}
|
|
183
|
+
`);
|
|
184
|
+
},
|
|
185
|
+
warn(message) {
|
|
186
|
+
process.stderr.write(`${pc2.yellow(`WARN: ${message}`)}
|
|
187
|
+
`);
|
|
188
|
+
},
|
|
189
|
+
error(message) {
|
|
190
|
+
process.stderr.write(`${pc2.red(`ERROR: ${message}`)}
|
|
191
|
+
`);
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
// src/commands/explain.ts
|
|
196
|
+
async function runExplainCommand(errorMessage, deps = {}) {
|
|
197
|
+
const explainError2 = deps.explainError ?? explainErrorWithAI;
|
|
198
|
+
const createSpinner = deps.createSpinner ?? ((text) => ora(text).start());
|
|
199
|
+
const formatOutput = deps.formatOutput ?? formatExplainedError;
|
|
200
|
+
const log = deps.log ?? logger;
|
|
201
|
+
if (!errorMessage?.trim()) {
|
|
202
|
+
log.warn("Please provide an error message.");
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
const spinner = createSpinner("Analyzing your error...");
|
|
206
|
+
try {
|
|
207
|
+
const result = await explainError2(errorMessage.trim());
|
|
208
|
+
spinner.succeed("Explanation ready.");
|
|
209
|
+
log.info("");
|
|
210
|
+
log.info(formatOutput(result));
|
|
211
|
+
} catch (error) {
|
|
212
|
+
spinner.fail("Could not explain this error.");
|
|
213
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
214
|
+
log.error(message);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// src/cli.ts
|
|
219
|
+
async function readStdin() {
|
|
220
|
+
if (process.stdin.isTTY) {
|
|
221
|
+
return "";
|
|
222
|
+
}
|
|
223
|
+
const chunks = [];
|
|
224
|
+
for await (const chunk of process.stdin) {
|
|
225
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
226
|
+
}
|
|
227
|
+
return Buffer.concat(chunks).toString("utf8").trim();
|
|
228
|
+
}
|
|
229
|
+
async function promptForError() {
|
|
230
|
+
if (!process.stdin.isTTY) {
|
|
231
|
+
return "";
|
|
232
|
+
}
|
|
233
|
+
const rl = createInterface({
|
|
234
|
+
input: process.stdin,
|
|
235
|
+
output: process.stdout
|
|
236
|
+
});
|
|
237
|
+
try {
|
|
238
|
+
logger.info(pc3.cyan("Paste an error message and press Enter."));
|
|
239
|
+
logger.info(pc3.dim('Example: TypeError: Cannot read property "map" of undefined'));
|
|
240
|
+
while (true) {
|
|
241
|
+
const answer = await rl.question(pc3.bold(pc3.cyan("\n> Error message: ")));
|
|
242
|
+
const trimmed = answer.trim();
|
|
243
|
+
if (trimmed) {
|
|
244
|
+
return trimmed;
|
|
245
|
+
}
|
|
246
|
+
logger.warn("Error message cannot be empty. Please try again.");
|
|
247
|
+
}
|
|
248
|
+
} finally {
|
|
249
|
+
rl.close();
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
async function runCli(argv = process.argv, deps = {}) {
|
|
253
|
+
const runExplain = deps.runExplain ?? runExplainCommand;
|
|
254
|
+
const readStdinFn = deps.readStdin ?? readStdin;
|
|
255
|
+
const promptForErrorFn = deps.promptForError ?? promptForError;
|
|
256
|
+
const stdinIsTTY = deps.stdinIsTTY ?? (() => Boolean(process.stdin.isTTY));
|
|
257
|
+
const log = deps.log ?? logger;
|
|
258
|
+
const program = new Command();
|
|
259
|
+
program.name("explain-my-error").description("Explain programming errors with AI").version("1.0.0").addHelpText(
|
|
260
|
+
"after",
|
|
261
|
+
`
|
|
262
|
+
Input modes:
|
|
263
|
+
1) Interactive (default)
|
|
264
|
+
$ explain-my-error
|
|
265
|
+
$ eme
|
|
266
|
+
|
|
267
|
+
2) Inline argument
|
|
268
|
+
$ explain-my-error explain "TypeError: Cannot read property 'map' of undefined"
|
|
269
|
+
$ eme explain "ReferenceError: x is not defined"
|
|
270
|
+
|
|
271
|
+
3) Pipe from files/commands
|
|
272
|
+
$ cat error.txt | explain-my-error
|
|
273
|
+
$ pnpm run build 2>&1 | eme
|
|
274
|
+
`
|
|
275
|
+
);
|
|
276
|
+
program.command("explain").description("Explain a programming error message").argument("[error...]", "Error message to analyze").addHelpText(
|
|
277
|
+
"after",
|
|
278
|
+
`
|
|
279
|
+
Examples:
|
|
280
|
+
$ explain-my-error explain "SyntaxError: Unexpected token }"
|
|
281
|
+
$ eme explain "Module not found: Can't resolve 'axios'"
|
|
282
|
+
$ cat error.txt | explain-my-error explain
|
|
283
|
+
`
|
|
284
|
+
).action(async (errorParts) => {
|
|
285
|
+
const inlineError = errorParts.join(" ").trim();
|
|
286
|
+
const pipedError = inlineError ? "" : await readStdinFn();
|
|
287
|
+
const promptedError = !inlineError && !pipedError ? await promptForErrorFn() : "";
|
|
288
|
+
const finalError = inlineError || pipedError || promptedError;
|
|
289
|
+
await runExplain(finalError);
|
|
290
|
+
});
|
|
291
|
+
program.action(async () => {
|
|
292
|
+
if (stdinIsTTY()) {
|
|
293
|
+
const promptedError = await promptForErrorFn();
|
|
294
|
+
await runExplain(promptedError);
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
const pipedError = await readStdinFn();
|
|
298
|
+
if (!pipedError) {
|
|
299
|
+
log.warn('No input detected. Use: explain-my-error explain "<error message>"');
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
await runExplain(pipedError);
|
|
303
|
+
});
|
|
304
|
+
await program.parseAsync(argv);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// src/explain.ts
|
|
308
|
+
async function explainError(errorMessage) {
|
|
309
|
+
return explainErrorWithAI(errorMessage);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// src/skills/explainError.skill.ts
|
|
313
|
+
import { z as z2 } from "zod";
|
|
314
|
+
var explainErrorSkillInputSchema = z2.object({
|
|
315
|
+
error: z2.string().min(1, "error is required")
|
|
316
|
+
});
|
|
317
|
+
async function runExplainErrorSkill(input) {
|
|
318
|
+
const parsedInput = explainErrorSkillInputSchema.parse(input);
|
|
319
|
+
return explainErrorWithAI(parsedInput.error);
|
|
320
|
+
}
|
|
321
|
+
export {
|
|
322
|
+
explainError,
|
|
323
|
+
explainErrorWithAI,
|
|
324
|
+
runCli,
|
|
325
|
+
runExplainErrorSkill
|
|
326
|
+
};
|
|
327
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/cli.ts","../src/commands/explain.ts","../src/services/ai.ts","../src/types/error.ts","../src/utils/formatter.ts","../src/utils/logger.ts","../src/explain.ts","../src/skills/explainError.skill.ts"],"sourcesContent":["import { createInterface } from \"node:readline/promises\";\nimport { Command } from \"commander\";\nimport pc from \"picocolors\";\nimport { runExplainCommand } from \"./commands/explain.js\";\nimport { logger } from \"./utils/logger.js\";\n\nasync function readStdin(): Promise<string> {\n if (process.stdin.isTTY) {\n return \"\";\n }\n\n const chunks: Buffer[] = [];\n for await (const chunk of process.stdin) {\n chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));\n }\n return Buffer.concat(chunks).toString(\"utf8\").trim();\n}\n\nasync function promptForError(): Promise<string> {\n if (!process.stdin.isTTY) {\n return \"\";\n }\n\n const rl = createInterface({\n input: process.stdin,\n output: process.stdout,\n });\n\n try {\n logger.info(pc.cyan(\"Paste an error message and press Enter.\"));\n logger.info(pc.dim('Example: TypeError: Cannot read property \"map\" of undefined'));\n while (true) {\n const answer = await rl.question(pc.bold(pc.cyan(\"\\n> Error message: \")));\n const trimmed = answer.trim();\n\n if (trimmed) {\n return trimmed;\n }\n\n logger.warn(\"Error message cannot be empty. Please try again.\");\n }\n } finally {\n rl.close();\n }\n}\n\ntype CliLogger = {\n warn(message: string): void;\n};\n\ntype RunCliDeps = {\n runExplain?: (errorMessage: string) => Promise<void>;\n readStdin?: () => Promise<string>;\n promptForError?: () => Promise<string>;\n stdinIsTTY?: () => boolean;\n log?: CliLogger;\n};\n\nexport async function runCli(argv: string[] = process.argv, deps: RunCliDeps = {}): Promise<void> {\n const runExplain = deps.runExplain ?? runExplainCommand;\n const readStdinFn = deps.readStdin ?? readStdin;\n const promptForErrorFn = deps.promptForError ?? promptForError;\n const stdinIsTTY = deps.stdinIsTTY ?? (() => Boolean(process.stdin.isTTY));\n const log = deps.log ?? logger;\n\n const program = new Command();\n\n program\n .name(\"explain-my-error\")\n .description(\"Explain programming errors with AI\")\n .version(\"1.0.0\")\n .addHelpText(\n \"after\",\n `\nInput modes:\n 1) Interactive (default)\n $ explain-my-error\n $ eme\n\n 2) Inline argument\n $ explain-my-error explain \"TypeError: Cannot read property 'map' of undefined\"\n $ eme explain \"ReferenceError: x is not defined\"\n\n 3) Pipe from files/commands\n $ cat error.txt | explain-my-error\n $ pnpm run build 2>&1 | eme\n`,\n );\n\n program\n .command(\"explain\")\n .description(\"Explain a programming error message\")\n .argument(\"[error...]\", \"Error message to analyze\")\n .addHelpText(\n \"after\",\n `\nExamples:\n $ explain-my-error explain \"SyntaxError: Unexpected token }\"\n $ eme explain \"Module not found: Can't resolve 'axios'\"\n $ cat error.txt | explain-my-error explain\n`,\n )\n .action(async (errorParts: string[]) => {\n const inlineError = errorParts.join(\" \").trim();\n const pipedError = inlineError ? \"\" : await readStdinFn();\n const promptedError = !inlineError && !pipedError ? await promptForErrorFn() : \"\";\n const finalError = inlineError || pipedError || promptedError;\n await runExplain(finalError);\n });\n\n program.action(async () => {\n if (stdinIsTTY()) {\n const promptedError = await promptForErrorFn();\n await runExplain(promptedError);\n return;\n }\n\n const pipedError = await readStdinFn();\n if (!pipedError) {\n log.warn('No input detected. Use: explain-my-error explain \"<error message>\"');\n return;\n }\n await runExplain(pipedError);\n });\n\n await program.parseAsync(argv);\n}\n","import ora from \"ora\";\nimport { explainErrorWithAI } from \"../services/ai.js\";\nimport type { ExplainedError } from \"../types/error.js\";\nimport { formatExplainedError } from \"../utils/formatter.js\";\nimport { logger } from \"../utils/logger.js\";\n\ntype SpinnerLike = {\n succeed(text: string): void;\n fail(text: string): void;\n};\n\ntype ExplainLogger = {\n info(message: string): void;\n warn(message: string): void;\n error(message: string): void;\n};\n\ntype RunExplainDeps = {\n explainError?: (errorMessage: string) => Promise<ExplainedError>;\n createSpinner?: (text: string) => SpinnerLike;\n formatOutput?: (result: ExplainedError) => string;\n log?: ExplainLogger;\n};\n\nexport async function runExplainCommand(\n errorMessage: string,\n deps: RunExplainDeps = {},\n): Promise<void> {\n const explainError = deps.explainError ?? explainErrorWithAI;\n const createSpinner = deps.createSpinner ?? ((text: string) => ora(text).start());\n const formatOutput = deps.formatOutput ?? formatExplainedError;\n const log = deps.log ?? logger;\n\n if (!errorMessage?.trim()) {\n log.warn(\"Please provide an error message.\");\n return;\n }\n\n const spinner = createSpinner(\"Analyzing your error...\");\n\n try {\n const result = await explainError(errorMessage.trim());\n spinner.succeed(\"Explanation ready.\");\n log.info(\"\");\n log.info(formatOutput(result));\n } catch (error) {\n spinner.fail(\"Could not explain this error.\");\n const message = error instanceof Error ? error.message : \"Unknown error\";\n log.error(message);\n }\n}\n","import axios from \"axios\";\nimport { type ExplainedError, explainedErrorSchema } from \"../types/error.js\";\n\nconst GROQ_API_URL = \"https://api.groq.com/openai/v1/chat/completions\";\nconst PRIMARY_GROQ_MODEL = \"llama3-70b-8192\";\nconst FALLBACK_GROQ_MODEL = process.env.GROQ_FALLBACK_MODEL ?? \"llama-3.3-70b-versatile\";\n\ntype GroqChatResponse = {\n choices?: Array<{ message?: { content?: string } }>;\n};\n\nfunction extractJson(content: string): unknown {\n const trimmed = content.trim();\n\n try {\n return JSON.parse(trimmed);\n } catch {\n const match = trimmed.match(/\\{[\\s\\S]*\\}/);\n if (!match) {\n throw new Error(\"AI did not return valid JSON.\");\n }\n return JSON.parse(match[0]);\n }\n}\n\nfunction stringifyField(value: unknown): string {\n if (typeof value === \"string\") {\n return value;\n }\n if (value == null) {\n return \"\";\n }\n if (typeof value === \"object\") {\n try {\n return JSON.stringify(value, null, 2);\n } catch {\n return String(value);\n }\n }\n return String(value);\n}\n\nfunction normalizeResponseShape(payload: unknown): unknown {\n if (!payload || typeof payload !== \"object\") {\n return payload;\n }\n\n const data = payload as Record<string, unknown>;\n\n const rawCauses = data.common_causes;\n const commonCauses = Array.isArray(rawCauses)\n ? rawCauses.map((item) => stringifyField(item)).filter(Boolean)\n : stringifyField(rawCauses)\n .split(/\\n|,/)\n .map((item) => item.trim())\n .filter(Boolean);\n\n return {\n title: stringifyField(data.title),\n explanation: stringifyField(data.explanation),\n common_causes: commonCauses,\n fix: stringifyField(data.fix),\n code_example: stringifyField(data.code_example),\n eli5: stringifyField(data.eli5),\n };\n}\n\nexport async function explainErrorWithAI(errorMessage: string): Promise<ExplainedError> {\n const apiKey = process.env.GROQ_API_KEY;\n if (!apiKey) {\n throw new Error(\"Missing GROQ_API_KEY environment variable.\");\n }\n\n const prompt =\n \"You are a senior software engineer. Explain the programming error and return JSON with fields: title, explanation, common_causes, fix, code_example, eli5.\";\n\n const requestBody = {\n messages: [\n { role: \"system\" as const, content: prompt },\n {\n role: \"user\" as const,\n content: `Error message:\\n${errorMessage}\\n\\nReturn strict JSON only.`,\n },\n ],\n temperature: 0.2,\n };\n\n const requestConfig = {\n headers: {\n Authorization: `Bearer ${apiKey}`,\n \"Content-Type\": \"application/json\",\n },\n timeout: 30000,\n };\n\n let response: { data?: GroqChatResponse };\n try {\n response = await axios.post(\n GROQ_API_URL,\n { model: PRIMARY_GROQ_MODEL, ...requestBody },\n requestConfig,\n );\n } catch (error) {\n if (axios.isAxiosError(error)) {\n const providerMessage =\n typeof error.response?.data?.error?.message === \"string\"\n ? error.response.data.error.message\n : error.message;\n\n const isModelDecommissioned =\n providerMessage.toLowerCase().includes(\"decommissioned\") ||\n providerMessage.toLowerCase().includes(\"no longer supported\");\n\n if (isModelDecommissioned) {\n response = await axios.post(\n GROQ_API_URL,\n { model: FALLBACK_GROQ_MODEL, ...requestBody },\n requestConfig,\n );\n } else {\n throw new Error(`Groq API request failed: ${providerMessage}`);\n }\n } else {\n throw error;\n }\n }\n\n const content = response.data?.choices?.[0]?.message?.content;\n if (typeof content !== \"string\" || !content.trim()) {\n throw new Error(\"AI response was empty.\");\n }\n\n const parsed = extractJson(content);\n const normalized = normalizeResponseShape(parsed);\n return explainedErrorSchema.parse(normalized);\n}\n","import { z } from \"zod\";\n\nexport const explainedErrorSchema = z.object({\n title: z.string().min(1),\n explanation: z.string().min(1),\n common_causes: z.array(z.string().min(1)).min(1),\n fix: z.string().min(1),\n code_example: z.string().min(1),\n eli5: z.string().min(1),\n});\n\nexport type ExplainedError = z.infer<typeof explainedErrorSchema>;\n","import pc from \"picocolors\";\nimport type { ExplainedError } from \"../types/error.js\";\n\nconst CARD_WIDTH = 72;\n\nfunction line(char = \"-\"): string {\n return char.repeat(CARD_WIDTH);\n}\n\nfunction pad(text: string): string {\n return text.length > CARD_WIDTH - 4 ? `${text.slice(0, CARD_WIDTH - 7)}...` : text;\n}\n\nfunction framedLine(text: string): string {\n return `| ${pad(text).padEnd(CARD_WIDTH - 4)} |`;\n}\n\nfunction block(title: string, body: string): string {\n const rows = body.split(\"\\n\").map((row) => pc.white(row));\n return [pc.cyan(line()), pc.bold(pc.cyan(title)), ...rows, pc.cyan(line())].join(\"\\n\");\n}\n\nexport function formatExplainedError(result: ExplainedError): string {\n const commonCauses = result.common_causes\n .map((cause, index) => pc.white(`${index + 1}. ${cause}`))\n .join(\"\\n\");\n const hero = [\n pc.cyan(line(\"=\")),\n framedLine(pc.bold(pc.cyan(\"EXPLAIN MY ERROR\"))),\n framedLine(pc.dim(\"AI powered debugging for humans\")),\n pc.cyan(line(\"=\")),\n ].join(\"\\n\");\n\n return [\n hero,\n \"\",\n `${pc.bold(pc.cyan(\"ERROR\"))}: ${pc.bold(pc.white(result.title))}`,\n \"\",\n block(\"EXPLANATION\", result.explanation),\n \"\",\n block(\"COMMON CAUSES\", commonCauses),\n \"\",\n `${pc.bold(pc.green(\"FIX\"))}\\n${pc.white(result.fix)}`,\n \"\",\n block(\"CODE EXAMPLE\", result.code_example),\n \"\",\n block(\"ELI5\", result.eli5),\n \"\",\n pc.dim('Tip: run `eme explain \"<error>\"` for quick mode.'),\n ].join(\"\\n\");\n}\n","import pc from \"picocolors\";\n\nexport const logger = {\n info(message: string): void {\n process.stdout.write(`${pc.white(message)}\\n`);\n },\n success(message: string): void {\n process.stdout.write(`${pc.green(`OK: ${message}`)}\\n`);\n },\n warn(message: string): void {\n process.stderr.write(`${pc.yellow(`WARN: ${message}`)}\\n`);\n },\n error(message: string): void {\n process.stderr.write(`${pc.red(`ERROR: ${message}`)}\\n`);\n },\n};\n","import { explainErrorWithAI } from \"./services/ai.js\";\nimport type { ExplainedError } from \"./types/error.js\";\n\nexport async function explainError(errorMessage: string): Promise<ExplainedError> {\n return explainErrorWithAI(errorMessage);\n}\n","import { z } from \"zod\";\nimport { explainErrorWithAI } from \"../services/ai.js\";\nimport type { ExplainedError } from \"../types/error.js\";\n\nconst explainErrorSkillInputSchema = z.object({\n error: z.string().min(1, \"error is required\"),\n});\n\nexport type ExplainErrorSkillInput = z.infer<typeof explainErrorSkillInputSchema>;\n\nexport async function runExplainErrorSkill(input: ExplainErrorSkillInput): Promise<ExplainedError> {\n const parsedInput = explainErrorSkillInputSchema.parse(input);\n return explainErrorWithAI(parsedInput.error);\n}\n"],"mappings":"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","names":["pc","pc","explainError","pc","z","z"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "explain-my-error",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "AI-powered CLI to explain programming errors with fixes and ELI5 output.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"explain-my-error": "./dist/cli.js",
|
|
8
|
+
"eme": "./dist/cli.js"
|
|
9
|
+
},
|
|
10
|
+
"main": "./dist/index.js",
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"files": [
|
|
13
|
+
"dist",
|
|
14
|
+
"skills/SKILL.md",
|
|
15
|
+
"README.md",
|
|
16
|
+
"CHANGELOG.md"
|
|
17
|
+
],
|
|
18
|
+
"keywords": [
|
|
19
|
+
"cli",
|
|
20
|
+
"errors",
|
|
21
|
+
"debugging",
|
|
22
|
+
"ai",
|
|
23
|
+
"groq",
|
|
24
|
+
"agent-skill"
|
|
25
|
+
],
|
|
26
|
+
"author": "Awais Ali",
|
|
27
|
+
"license": "MIT",
|
|
28
|
+
"repository": {
|
|
29
|
+
"type": "git",
|
|
30
|
+
"url": "git+https://github.com/awaisaly/explain-my-error.git"
|
|
31
|
+
},
|
|
32
|
+
"homepage": "https://github.com/awaisaly/explain-my-error#readme",
|
|
33
|
+
"bugs": {
|
|
34
|
+
"url": "https://github.com/awaisaly/explain-my-error/issues"
|
|
35
|
+
},
|
|
36
|
+
"engines": {
|
|
37
|
+
"node": ">=20"
|
|
38
|
+
},
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"axios": "^1.8.4",
|
|
41
|
+
"commander": "^13.1.0",
|
|
42
|
+
"dotenv": "^16.4.7",
|
|
43
|
+
"ora": "^8.1.1",
|
|
44
|
+
"picocolors": "^1.1.1",
|
|
45
|
+
"zod": "^3.23.8"
|
|
46
|
+
},
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"@biomejs/biome": "^1.9.4",
|
|
49
|
+
"@types/node": "^22.13.10",
|
|
50
|
+
"tsup": "^8.3.5",
|
|
51
|
+
"typescript": "^5.8.2",
|
|
52
|
+
"vitest": "^2.1.8"
|
|
53
|
+
},
|
|
54
|
+
"scripts": {
|
|
55
|
+
"build": "tsup",
|
|
56
|
+
"dev": "tsup --watch",
|
|
57
|
+
"start": "node dist/cli.js",
|
|
58
|
+
"test": "vitest run",
|
|
59
|
+
"test:watch": "vitest",
|
|
60
|
+
"lint": "biome check .",
|
|
61
|
+
"format": "biome check --write .",
|
|
62
|
+
"typecheck": "tsc --noEmit",
|
|
63
|
+
"check": "pnpm lint && pnpm typecheck && pnpm test && pnpm build",
|
|
64
|
+
"release": "pnpm run check && pnpm publish",
|
|
65
|
+
"release:patch": "pnpm run check && pnpm version patch && pnpm publish",
|
|
66
|
+
"release:minor": "pnpm run check && pnpm version minor && pnpm publish",
|
|
67
|
+
"release:major": "pnpm run check && pnpm version major && pnpm publish"
|
|
68
|
+
}
|
|
69
|
+
}
|
package/skills/SKILL.md
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# explain_error
|
|
2
|
+
|
|
3
|
+
## Metadata
|
|
4
|
+
|
|
5
|
+
```yaml
|
|
6
|
+
name: explain_error
|
|
7
|
+
description: Explains programming errors and provides fixes.
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
- Name: `explain_error`
|
|
11
|
+
- Description: `Explains programming errors and provides fixes.`
|
|
12
|
+
|
|
13
|
+
## Description
|
|
14
|
+
|
|
15
|
+
This skill takes a raw programming error string and returns a structured explanation that is easy for both humans and agents to consume.
|
|
16
|
+
|
|
17
|
+
## Input schema
|
|
18
|
+
|
|
19
|
+
```json
|
|
20
|
+
{
|
|
21
|
+
"type": "object",
|
|
22
|
+
"properties": {
|
|
23
|
+
"error": {
|
|
24
|
+
"type": "string",
|
|
25
|
+
"description": "Programming error message"
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
"required": ["error"]
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Output schema
|
|
33
|
+
|
|
34
|
+
```json
|
|
35
|
+
{
|
|
36
|
+
"type": "object",
|
|
37
|
+
"properties": {
|
|
38
|
+
"title": { "type": "string" },
|
|
39
|
+
"explanation": { "type": "string" },
|
|
40
|
+
"common_causes": {
|
|
41
|
+
"type": "array",
|
|
42
|
+
"items": { "type": "string" }
|
|
43
|
+
},
|
|
44
|
+
"fix": { "type": "string" },
|
|
45
|
+
"code_example": { "type": "string" },
|
|
46
|
+
"eli5": { "type": "string" }
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Implementation
|
|
52
|
+
|
|
53
|
+
- Runtime entrypoint: `src/skills/explainError.skill.ts`
|
|
54
|
+
- Exported function: `runExplainErrorSkill(input)`
|
|
55
|
+
|
|
56
|
+
## Example input
|
|
57
|
+
|
|
58
|
+
```json
|
|
59
|
+
{
|
|
60
|
+
"error": "TypeError: Cannot read property 'map' of undefined"
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Example output
|
|
65
|
+
|
|
66
|
+
```json
|
|
67
|
+
{
|
|
68
|
+
"title": "TypeError: Cannot read property 'map' of undefined",
|
|
69
|
+
"explanation": "This happens when .map() is called on a variable that is undefined.",
|
|
70
|
+
"common_causes": [
|
|
71
|
+
"API data not loaded",
|
|
72
|
+
"State not initialized",
|
|
73
|
+
"Incorrect variable reference"
|
|
74
|
+
],
|
|
75
|
+
"fix": "Ensure the array exists before calling map.",
|
|
76
|
+
"code_example": "const items = data?.items ?? [];\\nitems.map(...)",
|
|
77
|
+
"eli5": "Your code expected a box of toys (an array), but the toy box was empty."
|
|
78
|
+
}
|
|
79
|
+
```
|