devfix-cli 1.0.6 → 1.0.7
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/package.json +1 -1
- package/src/utils/run.js +115 -110
package/package.json
CHANGED
package/src/utils/run.js
CHANGED
|
@@ -1,13 +1,9 @@
|
|
|
1
|
-
|
|
2
1
|
import chalk from "chalk";
|
|
3
2
|
import boxen from "boxen";
|
|
4
3
|
import inquirer from "inquirer";
|
|
5
4
|
import ora from "ora";
|
|
6
5
|
import { spawn } from "child_process";
|
|
7
6
|
|
|
8
|
-
import { marked } from "marked";
|
|
9
|
-
import TerminalRenderer from "marked-terminal";
|
|
10
|
-
|
|
11
7
|
import { readConfig } from "../utils/config.js";
|
|
12
8
|
import { isSessionValid } from "../utils/session.js";
|
|
13
9
|
import { decrypt } from "../utils/cryptoStore.js";
|
|
@@ -17,10 +13,28 @@ import { detectStack } from "../utils/detectStack.js";
|
|
|
17
13
|
import { buildPrompt } from "../utils/prompt.js";
|
|
18
14
|
import { askAI } from "../utils/ai.js";
|
|
19
15
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
}
|
|
16
|
+
function headerBox({ fullCmd, contextOn }) {
|
|
17
|
+
return boxen(
|
|
18
|
+
`${chalk.bold.cyan("DevFix Run")}\n` +
|
|
19
|
+
`${chalk.gray("────────────────────────")}\n` +
|
|
20
|
+
`${chalk.white("Command:")} ${chalk.yellow(fullCmd)}\n` +
|
|
21
|
+
`${chalk.white("Context:")} ${contextOn ? chalk.green("ON") : chalk.red("OFF")}`,
|
|
22
|
+
{ padding: 1, borderStyle: "round" }
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function extractFirstCodeBlock(md) {
|
|
27
|
+
const match = md.match(/```(?:bash|sh)?\n([\s\S]*?)```/);
|
|
28
|
+
if (!match) return null;
|
|
29
|
+
return match[1].trim();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function cleanAIText(text) {
|
|
33
|
+
return (text || "")
|
|
34
|
+
.replace(/\n {4,}/g, "\n")
|
|
35
|
+
.replace(/\n\n{3,}/g, "\n\n")
|
|
36
|
+
.trim();
|
|
37
|
+
}
|
|
24
38
|
|
|
25
39
|
export async function runCommand(cmdArgs, options) {
|
|
26
40
|
const config = readConfig();
|
|
@@ -45,24 +59,16 @@ export async function runCommand(cmdArgs, options) {
|
|
|
45
59
|
const args = cmdArgs.slice(1);
|
|
46
60
|
const fullCmd = `${command} ${args.join(" ")}`.trim();
|
|
47
61
|
|
|
48
|
-
// ✅ Prevent duplicate prompts + duplicate AI calls
|
|
49
62
|
let handled = false;
|
|
50
63
|
|
|
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
|
-
);
|
|
64
|
+
console.log("\n" + headerBox({ fullCmd, contextOn: !!options.context }) + "\n");
|
|
59
65
|
|
|
60
|
-
// ✅ No shell=true (removes security warning)
|
|
61
66
|
const child = spawn(command, args, { stdio: ["inherit", "pipe", "pipe"] });
|
|
62
67
|
|
|
63
68
|
let stdout = "";
|
|
64
69
|
let stderr = "";
|
|
65
70
|
|
|
71
|
+
// ✅ Still show live output (like normal terminal)
|
|
66
72
|
child.stdout.on("data", (d) => {
|
|
67
73
|
const text = d.toString();
|
|
68
74
|
stdout += text;
|
|
@@ -75,76 +81,91 @@ export async function runCommand(cmdArgs, options) {
|
|
|
75
81
|
process.stderr.write(text);
|
|
76
82
|
});
|
|
77
83
|
|
|
78
|
-
// ✅
|
|
84
|
+
// ✅ Command not found
|
|
79
85
|
child.on("error", async (err) => {
|
|
80
86
|
if (handled) return;
|
|
81
87
|
handled = true;
|
|
82
88
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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
|
-
}
|
|
89
|
+
const msg =
|
|
90
|
+
err.code === "ENOENT"
|
|
91
|
+
? `Command not found: ${command}`
|
|
92
|
+
: `Failed to run: ${fullCmd}\n${err.message}`;
|
|
100
93
|
|
|
101
|
-
|
|
102
|
-
const context = options.context ? collectContext() : {};
|
|
103
|
-
const stack = options.stack || detectStack(errorText);
|
|
104
|
-
const usedModel = options.model || "openai/gpt-4o-mini";
|
|
94
|
+
console.log(chalk.red(`\n❌ ${msg}\n`));
|
|
105
95
|
|
|
106
|
-
|
|
96
|
+
const { confirm } = await inquirer.prompt([
|
|
97
|
+
{
|
|
98
|
+
name: "confirm",
|
|
99
|
+
type: "confirm",
|
|
100
|
+
message: "Send to DevFix AI?",
|
|
101
|
+
default: true,
|
|
102
|
+
},
|
|
103
|
+
]);
|
|
107
104
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
105
|
+
if (!confirm) {
|
|
106
|
+
console.log(chalk.yellow("\n❌ Not sent.\n"));
|
|
107
|
+
process.exit(1);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const errorBundle = `
|
|
111
|
+
Command: ${fullCmd}
|
|
112
|
+
|
|
113
|
+
Error:
|
|
114
|
+
${msg}
|
|
115
|
+
`.trim();
|
|
114
116
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
prompt,
|
|
119
|
-
});
|
|
117
|
+
const context = options.context ? collectContext() : {};
|
|
118
|
+
const stack = options.stack || detectStack(errorBundle);
|
|
119
|
+
const usedModel = options.model || "openai/gpt-4o-mini";
|
|
120
120
|
|
|
121
|
-
|
|
121
|
+
const spinner = ora("DevFix AI is analyzing...").start();
|
|
122
122
|
|
|
123
|
+
try {
|
|
124
|
+
const prompt = buildPrompt({
|
|
125
|
+
stack,
|
|
126
|
+
input: errorBundle,
|
|
127
|
+
context,
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
const answerRaw = await askAI({
|
|
131
|
+
apiKey,
|
|
132
|
+
model: usedModel,
|
|
133
|
+
prompt,
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
spinner.succeed("Done");
|
|
137
|
+
|
|
138
|
+
const answer = cleanAIText(answerRaw);
|
|
139
|
+
const mainCmd = extractFirstCodeBlock(answer);
|
|
140
|
+
|
|
141
|
+
if (mainCmd) {
|
|
123
142
|
console.log(
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
143
|
+
"\n" +
|
|
144
|
+
boxen(`${chalk.bold.green("Main Fix (copy-paste)")}\n\n${chalk.cyan(mainCmd)}`, {
|
|
145
|
+
padding: 1,
|
|
146
|
+
borderStyle: "round",
|
|
147
|
+
}) +
|
|
148
|
+
"\n"
|
|
128
149
|
);
|
|
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
150
|
}
|
|
138
151
|
|
|
139
|
-
|
|
152
|
+
console.log(
|
|
153
|
+
boxen(`${chalk.bold.white("Explanation")}\n\n${answer}`, {
|
|
154
|
+
padding: 1,
|
|
155
|
+
borderStyle: "round",
|
|
156
|
+
}) + "\n"
|
|
157
|
+
);
|
|
158
|
+
} catch (e) {
|
|
159
|
+
spinner.fail("AI request failed");
|
|
160
|
+
console.log(chalk.red("\n❌ Error:\n"));
|
|
161
|
+
console.log(e?.response?.data || e.message);
|
|
162
|
+
console.log();
|
|
140
163
|
}
|
|
141
164
|
|
|
142
|
-
console.log(chalk.red("\n❌ Failed to run command:\n"));
|
|
143
|
-
console.log(err.message);
|
|
144
165
|
process.exit(1);
|
|
145
166
|
});
|
|
146
167
|
|
|
147
|
-
// ✅
|
|
168
|
+
// ✅ Normal close
|
|
148
169
|
child.on("close", async (code) => {
|
|
149
170
|
if (handled) return;
|
|
150
171
|
handled = true;
|
|
@@ -154,36 +175,22 @@ export async function runCommand(cmdArgs, options) {
|
|
|
154
175
|
return;
|
|
155
176
|
}
|
|
156
177
|
|
|
178
|
+
// ⚠️ We do NOT print captured error again (already shown above)
|
|
157
179
|
const errorText = (stderr || stdout || "").trim();
|
|
158
180
|
|
|
159
181
|
console.log(chalk.red(`\n❌ Command failed (exit code: ${code}).\n`));
|
|
160
182
|
|
|
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
183
|
const { confirm } = await inquirer.prompt([
|
|
177
184
|
{
|
|
178
185
|
name: "confirm",
|
|
179
186
|
type: "confirm",
|
|
180
|
-
message: "Send this error to DevFix AI
|
|
187
|
+
message: "Send this error to DevFix AI?",
|
|
181
188
|
default: true,
|
|
182
189
|
},
|
|
183
190
|
]);
|
|
184
191
|
|
|
185
192
|
if (!confirm) {
|
|
186
|
-
console.log(chalk.yellow("\n❌ Not sent
|
|
193
|
+
console.log(chalk.yellow("\n❌ Not sent.\n"));
|
|
187
194
|
return;
|
|
188
195
|
}
|
|
189
196
|
|
|
@@ -192,25 +199,13 @@ export async function runCommand(cmdArgs, options) {
|
|
|
192
199
|
const usedModel = options.model || "openai/gpt-4o-mini";
|
|
193
200
|
|
|
194
201
|
const errorBundle = `
|
|
195
|
-
Command:
|
|
196
|
-
${
|
|
197
|
-
|
|
198
|
-
Exit Code:
|
|
199
|
-
${code}
|
|
202
|
+
Command: ${fullCmd}
|
|
203
|
+
Exit Code: ${code}
|
|
200
204
|
|
|
201
205
|
Error Output:
|
|
202
206
|
${errorText}
|
|
203
207
|
`.trim();
|
|
204
208
|
|
|
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
209
|
const spinner = ora("DevFix AI is analyzing...").start();
|
|
215
210
|
|
|
216
211
|
try {
|
|
@@ -220,28 +215,38 @@ ${errorText}
|
|
|
220
215
|
context,
|
|
221
216
|
});
|
|
222
217
|
|
|
223
|
-
const
|
|
218
|
+
const answerRaw = await askAI({
|
|
224
219
|
apiKey,
|
|
225
220
|
model: usedModel,
|
|
226
221
|
prompt,
|
|
227
222
|
});
|
|
228
223
|
|
|
229
|
-
spinner.succeed("
|
|
224
|
+
spinner.succeed("Done");
|
|
225
|
+
|
|
226
|
+
const answer = cleanAIText(answerRaw);
|
|
227
|
+
const mainCmd = extractFirstCodeBlock(answer);
|
|
228
|
+
|
|
229
|
+
if (mainCmd) {
|
|
230
|
+
console.log(
|
|
231
|
+
"\n" +
|
|
232
|
+
boxen(`${chalk.bold.green("Main Fix (copy-paste)")}\n\n${chalk.cyan(mainCmd)}`, {
|
|
233
|
+
padding: 1,
|
|
234
|
+
borderStyle: "round",
|
|
235
|
+
}) +
|
|
236
|
+
"\n"
|
|
237
|
+
);
|
|
238
|
+
}
|
|
230
239
|
|
|
231
240
|
console.log(
|
|
232
|
-
boxen(chalk.bold.
|
|
241
|
+
boxen(`${chalk.bold.white("Explanation")}\n\n${answer}`, {
|
|
233
242
|
padding: 1,
|
|
234
243
|
borderStyle: "round",
|
|
235
|
-
})
|
|
244
|
+
}) + "\n"
|
|
236
245
|
);
|
|
237
|
-
|
|
238
|
-
console.log(marked(answer));
|
|
239
|
-
console.log();
|
|
240
|
-
} catch (err) {
|
|
246
|
+
} catch (e) {
|
|
241
247
|
spinner.fail("AI request failed");
|
|
242
|
-
|
|
243
248
|
console.log(chalk.red("\n❌ Error:\n"));
|
|
244
|
-
console.log(
|
|
249
|
+
console.log(e?.response?.data || e.message);
|
|
245
250
|
console.log();
|
|
246
251
|
}
|
|
247
252
|
});
|