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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/utils/run.js +115 -110
package/package.json CHANGED
@@ -18,5 +18,5 @@
18
18
  "marked-terminal": "^7.3.0",
19
19
  "ora": "^9.3.0"
20
20
  },
21
- "version": "1.0.6"
21
+ "version": "1.0.7"
22
22
  }
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
- // Markdown renderer
21
- marked.setOptions({
22
- renderer: new TerminalRenderer(),
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
- // ✅ Handle "command not found"
84
+ // ✅ Command not found
79
85
  child.on("error", async (err) => {
80
86
  if (handled) return;
81
87
  handled = true;
82
88
 
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
- }
89
+ const msg =
90
+ err.code === "ENOENT"
91
+ ? `Command not found: ${command}`
92
+ : `Failed to run: ${fullCmd}\n${err.message}`;
100
93
 
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";
94
+ console.log(chalk.red(`\n❌ ${msg}\n`));
105
95
 
106
- const spinner = ora("DevFix AI is analyzing...").start();
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
- try {
109
- const prompt = buildPrompt({
110
- stack,
111
- input: errorText,
112
- context,
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
- const answer = await askAI({
116
- apiKey,
117
- model: usedModel,
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
- spinner.succeed("Analysis complete");
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
- boxen(chalk.bold.green("✅ DevFix Suggested Fix"), {
125
- padding: 1,
126
- borderStyle: "round",
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
- process.exit(1);
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
- // ✅ Handle normal command exit
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 for a fix?",
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 to AI.\n"));
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
- ${fullCmd}
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 answer = await askAI({
218
+ const answerRaw = await askAI({
224
219
  apiKey,
225
220
  model: usedModel,
226
221
  prompt,
227
222
  });
228
223
 
229
- spinner.succeed("Analysis complete");
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.green("✅ DevFix Suggested Fix"), {
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(err?.response?.data || err.message);
249
+ console.log(e?.response?.data || e.message);
245
250
  console.log();
246
251
  }
247
252
  });