devfix-cli 1.0.1 → 1.0.3
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 +23 -7
- package/bin/index.js +11 -9
- package/package.json +1 -1
- package/src/commands/analyze.js +4 -13
- package/src/utils/autoError.js +56 -33
- package/src/utils/prompt.js +2 -2
- package/src/utils/run.js +248 -0
- package/src/commands/scan.js +0 -94
package/README.md
CHANGED
|
@@ -77,19 +77,35 @@ Logout:
|
|
|
77
77
|
```bash
|
|
78
78
|
devfix logout
|
|
79
79
|
```
|
|
80
|
-
|
|
81
|
-
|
|
80
|
+
---
|
|
81
|
+
### 5) Run a command and auto-capture errors for AI fixing
|
|
82
|
+
1. Context command -> Include project/system context
|
|
82
83
|
```bash
|
|
83
|
-
devfix
|
|
84
|
+
devfix run <Command> --context
|
|
84
85
|
```
|
|
86
|
+
**for example : devfix run kubectl get pods --context**
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
---
|
|
85
90
|
|
|
86
|
-
|
|
91
|
+
2. Stack Command -> Force stack type
|
|
87
92
|
```bash
|
|
88
|
-
devfix
|
|
93
|
+
devfix run <Command> --stack <StackName>
|
|
89
94
|
```
|
|
95
|
+
**for example : devfix run kubectl get pods --stack kubernetes**
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
3. Model Command -> OpenRouter model override
|
|
90
101
|
|
|
91
|
-
sacn analyze -> Send scan results to AI:
|
|
92
102
|
```bash
|
|
93
|
-
devfix
|
|
103
|
+
devfix run <Command> --model <ModelName>
|
|
94
104
|
```
|
|
105
|
+
**for example : devfix run kubectl get pods --model openai/gpt-4o-mini**
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
|
|
95
111
|
|
package/bin/index.js
CHANGED
|
@@ -5,7 +5,7 @@ import { loginCommand } from "../src/commands/login.js";
|
|
|
5
5
|
import { logoutCommand } from "../src/commands/logout.js";
|
|
6
6
|
import { whoamiCommand } from "../src/commands/whoami.js";
|
|
7
7
|
import { analyzeCommand } from "../src/commands/analyze.js";
|
|
8
|
-
import {
|
|
8
|
+
import { runCommand } from "../src/utils/run.js";
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
const program = new Command();
|
|
@@ -40,17 +40,19 @@ program
|
|
|
40
40
|
});
|
|
41
41
|
|
|
42
42
|
program
|
|
43
|
-
.command("
|
|
44
|
-
.description("
|
|
45
|
-
.option("-
|
|
46
|
-
.option("-
|
|
43
|
+
.command("run")
|
|
44
|
+
.description("Run a command and auto-capture errors for AI fixing")
|
|
45
|
+
.option("-c, --context", "Include project/system context")
|
|
46
|
+
.option("-s, --stack <stack>", "Force stack type")
|
|
47
47
|
.option("-m, --model <model>", "OpenRouter model override")
|
|
48
|
-
.
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
48
|
+
.argument("<cmd...>", "Command to run")
|
|
49
|
+
.action((cmd, options) => {
|
|
50
|
+
runCommand(cmd, {
|
|
51
|
+
context: options.context,
|
|
52
|
+
stack: options.stack,
|
|
52
53
|
model: options.model,
|
|
53
54
|
});
|
|
54
55
|
});
|
|
55
56
|
|
|
57
|
+
|
|
56
58
|
program.parse(process.argv);
|
package/package.json
CHANGED
package/src/commands/analyze.js
CHANGED
|
@@ -10,7 +10,6 @@ import TerminalRenderer from "marked-terminal";
|
|
|
10
10
|
import { readConfig } from "../utils/config.js";
|
|
11
11
|
import { isSessionValid } from "../utils/session.js";
|
|
12
12
|
import { decrypt } from "../utils/cryptoStore.js";
|
|
13
|
-
import { collectAutoError } from "../utils/autoError.js";
|
|
14
13
|
|
|
15
14
|
import { collectContext } from "../utils/context.js";
|
|
16
15
|
import { detectStack } from "../utils/detectStack.js";
|
|
@@ -45,18 +44,10 @@ export async function analyzeCommand({ text, file, stack, model, useContext }) {
|
|
|
45
44
|
input = fs.readFileSync(file, "utf-8");
|
|
46
45
|
}
|
|
47
46
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
if (!input || input.trim().length < 2) {
|
|
55
|
-
console.log(chalk.red("\n❌ Please provide error text or use --file\n"));
|
|
56
|
-
console.log(chalk.gray("Tip: You can also run: devfix analyze --context"));
|
|
57
|
-
process.exit(1);
|
|
58
|
-
}
|
|
59
|
-
|
|
47
|
+
if (!input || input.trim().length < 2) {
|
|
48
|
+
console.log(chalk.red("\n❌ Please provide error text or use --file\n"));
|
|
49
|
+
process.exit(1);
|
|
50
|
+
}
|
|
60
51
|
|
|
61
52
|
const detected = stack || detectStack(input);
|
|
62
53
|
const usedModel = model || "openai/gpt-4o-mini";
|
package/src/utils/autoError.js
CHANGED
|
@@ -1,53 +1,76 @@
|
|
|
1
1
|
import fs from "fs";
|
|
2
2
|
import os from "os";
|
|
3
3
|
import path from "path";
|
|
4
|
-
import { execSync } from "child_process";
|
|
5
4
|
|
|
6
|
-
function
|
|
5
|
+
function getLatestFile(dir, ext = ".log") {
|
|
7
6
|
try {
|
|
8
|
-
|
|
9
|
-
} catch {
|
|
10
|
-
return null;
|
|
11
|
-
}
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export function collectAutoError() {
|
|
15
|
-
const cwd = process.cwd();
|
|
7
|
+
if (!fs.existsSync(dir)) return null;
|
|
16
8
|
|
|
17
|
-
// 1) npm debug log (most common)
|
|
18
|
-
const npmLog = path.join(os.homedir(), ".npm", "_logs");
|
|
19
|
-
if (fs.existsSync(npmLog)) {
|
|
20
9
|
const files = fs
|
|
21
|
-
.readdirSync(
|
|
22
|
-
.filter((f) => f.endsWith(
|
|
10
|
+
.readdirSync(dir)
|
|
11
|
+
.filter((f) => f.endsWith(ext))
|
|
23
12
|
.map((f) => ({
|
|
24
13
|
name: f,
|
|
25
|
-
full: path.join(
|
|
26
|
-
time: fs.statSync(path.join(
|
|
14
|
+
full: path.join(dir, f),
|
|
15
|
+
time: fs.statSync(path.join(dir, f)).mtimeMs,
|
|
27
16
|
}))
|
|
28
17
|
.sort((a, b) => b.time - a.time);
|
|
29
18
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
return `📌 Auto-collected npm debug log:\nFile: ${latest}\n\n${content}`;
|
|
34
|
-
}
|
|
19
|
+
return files.length ? files[0].full : null;
|
|
20
|
+
} catch {
|
|
21
|
+
return null;
|
|
35
22
|
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function extractNpmError(logText) {
|
|
26
|
+
const lines = logText.split("\n");
|
|
36
27
|
|
|
37
|
-
//
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
|
|
28
|
+
// Most npm errors appear in these patterns
|
|
29
|
+
const keywords = ["npm ERR!", "error", "ERR_", "EACCES", "ENOTFOUND", "ECONNRESET"];
|
|
30
|
+
|
|
31
|
+
// Find first error-like line
|
|
32
|
+
let startIndex = -1;
|
|
33
|
+
for (let i = 0; i < lines.length; i++) {
|
|
34
|
+
const lower = lines[i].toLowerCase();
|
|
35
|
+
if (keywords.some((k) => lower.includes(k.toLowerCase()))) {
|
|
36
|
+
startIndex = i;
|
|
37
|
+
break;
|
|
38
|
+
}
|
|
41
39
|
}
|
|
42
40
|
|
|
43
|
-
//
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
41
|
+
// If no errors found, return null
|
|
42
|
+
if (startIndex === -1) return null;
|
|
43
|
+
|
|
44
|
+
// Take a clean block after the error line
|
|
45
|
+
const extracted = lines.slice(startIndex, startIndex + 60).join("\n");
|
|
46
|
+
|
|
47
|
+
// Remove useless "silly" lines
|
|
48
|
+
return extracted
|
|
49
|
+
.split("\n")
|
|
50
|
+
.filter((l) => !l.includes("silly"))
|
|
51
|
+
.slice(0, 60)
|
|
52
|
+
.join("\n")
|
|
53
|
+
.trim();
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function collectAutoError() {
|
|
57
|
+
const npmLogsDir = path.join(os.homedir(), ".npm", "_logs");
|
|
58
|
+
const latestNpmLog = getLatestFile(npmLogsDir, ".log");
|
|
59
|
+
|
|
60
|
+
if (latestNpmLog) {
|
|
61
|
+
const raw = fs.readFileSync(latestNpmLog, "utf-8");
|
|
62
|
+
const extracted = extractNpmError(raw);
|
|
63
|
+
|
|
64
|
+
if (extracted) {
|
|
65
|
+
return {
|
|
66
|
+
source: "npm" | "kubernetes" | "docker" | "git",
|
|
67
|
+
title: "Short readable error title",
|
|
68
|
+
file: "...optional",
|
|
69
|
+
extracted: "error lines",
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
}
|
|
49
73
|
}
|
|
50
74
|
|
|
51
|
-
// Nothing found
|
|
52
75
|
return null;
|
|
53
76
|
}
|
package/src/utils/prompt.js
CHANGED
|
@@ -4,8 +4,8 @@ You are DevFix, a CLI debugging assistant.
|
|
|
4
4
|
|
|
5
5
|
Hard rules:
|
|
6
6
|
- Be concise.
|
|
7
|
-
- Max
|
|
8
|
-
- Max
|
|
7
|
+
- Max 3 fix steps.
|
|
8
|
+
- Max 3 commands.
|
|
9
9
|
- No long paragraphs.
|
|
10
10
|
- If log indicates success (no error), say so and ask for the real error.
|
|
11
11
|
- If ambiguous, ask ONLY 1 question.
|
package/src/utils/run.js
ADDED
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
import boxen from "boxen";
|
|
4
|
+
import inquirer from "inquirer";
|
|
5
|
+
import ora from "ora";
|
|
6
|
+
import { spawn } from "child_process";
|
|
7
|
+
|
|
8
|
+
import { marked } from "marked";
|
|
9
|
+
import TerminalRenderer from "marked-terminal";
|
|
10
|
+
|
|
11
|
+
import { readConfig } from "../utils/config.js";
|
|
12
|
+
import { isSessionValid } from "../utils/session.js";
|
|
13
|
+
import { decrypt } from "../utils/cryptoStore.js";
|
|
14
|
+
|
|
15
|
+
import { collectContext } from "../utils/context.js";
|
|
16
|
+
import { detectStack } from "../utils/detectStack.js";
|
|
17
|
+
import { buildPrompt } from "../utils/prompt.js";
|
|
18
|
+
import { askAI } from "../utils/ai.js";
|
|
19
|
+
|
|
20
|
+
// Markdown renderer
|
|
21
|
+
marked.setOptions({
|
|
22
|
+
renderer: new TerminalRenderer(),
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
export async function runCommand(cmdArgs, options) {
|
|
26
|
+
const config = readConfig();
|
|
27
|
+
|
|
28
|
+
if (!isSessionValid(config)) {
|
|
29
|
+
console.log(chalk.red("\n❌ Not logged in or session expired.\nRun: devfix login\n"));
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const apiKey = decrypt(config.apiKeyEncrypted);
|
|
34
|
+
if (!apiKey) {
|
|
35
|
+
console.log(chalk.red("\n❌ API key missing. Run: devfix login\n"));
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (!cmdArgs || cmdArgs.length === 0) {
|
|
40
|
+
console.log(chalk.red("\n❌ Please provide a command.\nExample: devfix run kubectl get pods\n"));
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const command = cmdArgs[0];
|
|
45
|
+
const args = cmdArgs.slice(1);
|
|
46
|
+
const fullCmd = `${command} ${args.join(" ")}`.trim();
|
|
47
|
+
|
|
48
|
+
// ✅ Prevent duplicate prompts + duplicate AI calls
|
|
49
|
+
let handled = false;
|
|
50
|
+
|
|
51
|
+
console.log(
|
|
52
|
+
boxen(
|
|
53
|
+
`${chalk.bold.cyan("▶ DevFix Run")}\n\n${chalk.white("Command:")} ${chalk.yellow(fullCmd)}\n${
|
|
54
|
+
options.context ? chalk.green("Context: ON") : chalk.red("Context: OFF")
|
|
55
|
+
}`,
|
|
56
|
+
{ padding: 1, borderStyle: "round" }
|
|
57
|
+
)
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
// ✅ No shell=true (removes security warning)
|
|
61
|
+
const child = spawn(command, args, { stdio: ["inherit", "pipe", "pipe"] });
|
|
62
|
+
|
|
63
|
+
let stdout = "";
|
|
64
|
+
let stderr = "";
|
|
65
|
+
|
|
66
|
+
child.stdout.on("data", (d) => {
|
|
67
|
+
const text = d.toString();
|
|
68
|
+
stdout += text;
|
|
69
|
+
process.stdout.write(text);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
child.stderr.on("data", (d) => {
|
|
73
|
+
const text = d.toString();
|
|
74
|
+
stderr += text;
|
|
75
|
+
process.stderr.write(text);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
// ✅ Handle "command not found"
|
|
79
|
+
child.on("error", async (err) => {
|
|
80
|
+
if (handled) return;
|
|
81
|
+
handled = true;
|
|
82
|
+
|
|
83
|
+
if (err.code === "ENOENT") {
|
|
84
|
+
console.log(chalk.red(`\n❌ Command not found: ${command}\n`));
|
|
85
|
+
console.log(chalk.gray("Tip: Did you mean `kubectl`?\n"));
|
|
86
|
+
|
|
87
|
+
const { confirm } = await inquirer.prompt([
|
|
88
|
+
{
|
|
89
|
+
name: "confirm",
|
|
90
|
+
type: "confirm",
|
|
91
|
+
message: "Send this error to DevFix AI for a fix?",
|
|
92
|
+
default: true,
|
|
93
|
+
},
|
|
94
|
+
]);
|
|
95
|
+
|
|
96
|
+
if (!confirm) {
|
|
97
|
+
console.log(chalk.yellow("\n❌ Not sent to AI.\n"));
|
|
98
|
+
process.exit(1);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const errorText = `Command not found: ${command}\nTried to run: ${fullCmd}`;
|
|
102
|
+
const context = options.context ? collectContext() : {};
|
|
103
|
+
const stack = options.stack || detectStack(errorText);
|
|
104
|
+
const usedModel = options.model || "openai/gpt-4o-mini";
|
|
105
|
+
|
|
106
|
+
const spinner = ora("DevFix AI is analyzing...").start();
|
|
107
|
+
|
|
108
|
+
try {
|
|
109
|
+
const prompt = buildPrompt({
|
|
110
|
+
stack,
|
|
111
|
+
input: errorText,
|
|
112
|
+
context,
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
const answer = await askAI({
|
|
116
|
+
apiKey,
|
|
117
|
+
model: usedModel,
|
|
118
|
+
prompt,
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
spinner.succeed("Analysis complete");
|
|
122
|
+
|
|
123
|
+
console.log(
|
|
124
|
+
boxen(chalk.bold.green("✅ DevFix Suggested Fix"), {
|
|
125
|
+
padding: 1,
|
|
126
|
+
borderStyle: "round",
|
|
127
|
+
})
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
console.log(marked(answer));
|
|
131
|
+
console.log();
|
|
132
|
+
} catch (e) {
|
|
133
|
+
spinner.fail("AI request failed");
|
|
134
|
+
console.log(chalk.red("\n❌ Error:\n"));
|
|
135
|
+
console.log(e?.response?.data || e.message);
|
|
136
|
+
console.log();
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
process.exit(1);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
console.log(chalk.red("\n❌ Failed to run command:\n"));
|
|
143
|
+
console.log(err.message);
|
|
144
|
+
process.exit(1);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
// ✅ Handle normal command exit
|
|
148
|
+
child.on("close", async (code) => {
|
|
149
|
+
if (handled) return;
|
|
150
|
+
handled = true;
|
|
151
|
+
|
|
152
|
+
if (code === 0) {
|
|
153
|
+
console.log(chalk.green("\n✅ Command succeeded.\n"));
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const errorText = (stderr || stdout || "").trim();
|
|
158
|
+
|
|
159
|
+
console.log(chalk.red(`\n❌ Command failed (exit code: ${code}).\n`));
|
|
160
|
+
|
|
161
|
+
if (!errorText) {
|
|
162
|
+
console.log(chalk.red("No error output captured.\n"));
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
console.log(
|
|
167
|
+
boxen(chalk.bold.red("⚠️ Captured Error"), {
|
|
168
|
+
padding: 1,
|
|
169
|
+
borderStyle: "round",
|
|
170
|
+
})
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
console.log(chalk.red(errorText.slice(0, 1200)));
|
|
174
|
+
if (errorText.length > 1200) console.log(chalk.gray("\n...trimmed...\n"));
|
|
175
|
+
|
|
176
|
+
const { confirm } = await inquirer.prompt([
|
|
177
|
+
{
|
|
178
|
+
name: "confirm",
|
|
179
|
+
type: "confirm",
|
|
180
|
+
message: "Send this error to DevFix AI for a fix?",
|
|
181
|
+
default: true,
|
|
182
|
+
},
|
|
183
|
+
]);
|
|
184
|
+
|
|
185
|
+
if (!confirm) {
|
|
186
|
+
console.log(chalk.yellow("\n❌ Not sent to AI.\n"));
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const context = options.context ? collectContext() : {};
|
|
191
|
+
const stack = options.stack || detectStack(errorText);
|
|
192
|
+
const usedModel = options.model || "openai/gpt-4o-mini";
|
|
193
|
+
|
|
194
|
+
const errorBundle = `
|
|
195
|
+
Command:
|
|
196
|
+
${fullCmd}
|
|
197
|
+
|
|
198
|
+
Exit Code:
|
|
199
|
+
${code}
|
|
200
|
+
|
|
201
|
+
Error Output:
|
|
202
|
+
${errorText}
|
|
203
|
+
`.trim();
|
|
204
|
+
|
|
205
|
+
console.log(
|
|
206
|
+
boxen(
|
|
207
|
+
`${chalk.bold.white("Stack:")} ${chalk.cyan(stack)}\n${chalk.bold.white("Model:")} ${chalk.magenta(
|
|
208
|
+
usedModel
|
|
209
|
+
)}`,
|
|
210
|
+
{ padding: 1, borderStyle: "round" }
|
|
211
|
+
)
|
|
212
|
+
);
|
|
213
|
+
|
|
214
|
+
const spinner = ora("DevFix AI is analyzing...").start();
|
|
215
|
+
|
|
216
|
+
try {
|
|
217
|
+
const prompt = buildPrompt({
|
|
218
|
+
stack,
|
|
219
|
+
input: errorBundle,
|
|
220
|
+
context,
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
const answer = await askAI({
|
|
224
|
+
apiKey,
|
|
225
|
+
model: usedModel,
|
|
226
|
+
prompt,
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
spinner.succeed("Analysis complete");
|
|
230
|
+
|
|
231
|
+
console.log(
|
|
232
|
+
boxen(chalk.bold.green("✅ DevFix Suggested Fix"), {
|
|
233
|
+
padding: 1,
|
|
234
|
+
borderStyle: "round",
|
|
235
|
+
})
|
|
236
|
+
);
|
|
237
|
+
|
|
238
|
+
console.log(marked(answer));
|
|
239
|
+
console.log();
|
|
240
|
+
} catch (err) {
|
|
241
|
+
spinner.fail("AI request failed");
|
|
242
|
+
|
|
243
|
+
console.log(chalk.red("\n❌ Error:\n"));
|
|
244
|
+
console.log(err?.response?.data || err.message);
|
|
245
|
+
console.log();
|
|
246
|
+
}
|
|
247
|
+
});
|
|
248
|
+
}
|
package/src/commands/scan.js
DELETED
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
import chalk from "chalk";
|
|
2
|
-
import boxen from "boxen";
|
|
3
|
-
|
|
4
|
-
import { readConfig } from "../utils/config.js";
|
|
5
|
-
import { isSessionValid } from "../utils/session.js";
|
|
6
|
-
import { decrypt } from "../utils/cryptoStore.js";
|
|
7
|
-
|
|
8
|
-
import { collectContext } from "../utils/context.js";
|
|
9
|
-
import { collectAutoError } from "../utils/autoError.js";
|
|
10
|
-
import { detectStack } from "../utils/detectStack.js";
|
|
11
|
-
import { buildPrompt } from "../utils/prompt.js";
|
|
12
|
-
import { askAI } from "../utils/ai.js";
|
|
13
|
-
|
|
14
|
-
export async function scanCommand({ preview, analyze, model }) {
|
|
15
|
-
const config = readConfig();
|
|
16
|
-
|
|
17
|
-
if (!isSessionValid(config)) {
|
|
18
|
-
console.log(chalk.red("\n❌ Not logged in or session expired.\nRun: devfix login\n"));
|
|
19
|
-
process.exit(1);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
const apiKey = decrypt(config.apiKeyEncrypted);
|
|
23
|
-
if (!apiKey) {
|
|
24
|
-
console.log(chalk.red("\n❌ API key missing. Run: devfix login\n"));
|
|
25
|
-
process.exit(1);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
const context = collectContext();
|
|
29
|
-
const autoErr = collectAutoError();
|
|
30
|
-
|
|
31
|
-
const detected = detectStack(autoErr || "");
|
|
32
|
-
const usedModel = model || "openai/gpt-4o-mini";
|
|
33
|
-
|
|
34
|
-
console.log(
|
|
35
|
-
boxen(chalk.bold.cyan("🔎 DevFix Scan Results"), {
|
|
36
|
-
padding: 1,
|
|
37
|
-
borderStyle: "round",
|
|
38
|
-
})
|
|
39
|
-
);
|
|
40
|
-
|
|
41
|
-
console.log(chalk.white("\nContext collected:"));
|
|
42
|
-
console.log(chalk.gray(JSON.stringify(context, null, 2)));
|
|
43
|
-
|
|
44
|
-
console.log(chalk.white("\nAuto error detected:"));
|
|
45
|
-
if (autoErr) {
|
|
46
|
-
console.log(chalk.yellow(autoErr.slice(0, 2500)));
|
|
47
|
-
if (autoErr.length > 2500) console.log(chalk.gray("\n...trimmed output...\n"));
|
|
48
|
-
} else {
|
|
49
|
-
console.log(chalk.red("❌ No recent error log found."));
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
console.log(chalk.white("\nDetected stack: ") + chalk.cyan(detected));
|
|
53
|
-
console.log(chalk.white("Model: ") + chalk.magenta(usedModel));
|
|
54
|
-
console.log();
|
|
55
|
-
|
|
56
|
-
// If only preview requested
|
|
57
|
-
if (preview && !analyze) {
|
|
58
|
-
console.log(chalk.green("✅ Preview complete. Nothing was sent to AI.\n"));
|
|
59
|
-
return;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// If analyze requested
|
|
63
|
-
if (analyze) {
|
|
64
|
-
if (!autoErr) {
|
|
65
|
-
console.log(chalk.red("\n❌ No error found to analyze.\n"));
|
|
66
|
-
console.log(chalk.gray("Tip: Run devfix analyze \"your error\" --context\n"));
|
|
67
|
-
process.exit(1);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
console.log(chalk.cyan("🤖 Sending scan data to AI...\n"));
|
|
71
|
-
|
|
72
|
-
const prompt = buildPrompt({
|
|
73
|
-
stack: detected,
|
|
74
|
-
input: autoErr,
|
|
75
|
-
context,
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
const answer = await askAI({
|
|
79
|
-
apiKey,
|
|
80
|
-
model: usedModel,
|
|
81
|
-
prompt,
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
console.log(
|
|
85
|
-
boxen(chalk.bold.green("✅ DevFix Suggested Fix"), {
|
|
86
|
-
padding: 1,
|
|
87
|
-
borderStyle: "round",
|
|
88
|
-
})
|
|
89
|
-
);
|
|
90
|
-
|
|
91
|
-
console.log(answer);
|
|
92
|
-
console.log();
|
|
93
|
-
}
|
|
94
|
-
}
|