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 CHANGED
@@ -77,19 +77,35 @@ Logout:
77
77
  ```bash
78
78
  devfix logout
79
79
  ```
80
-
81
- scan -> collects context + auto errors:
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 scan
84
+ devfix run <Command> --context
84
85
  ```
86
+ **for example : devfix run kubectl get pods --context**
87
+ ---
88
+
89
+ ---
85
90
 
86
- scan preview -> shows what it found:
91
+ 2. Stack Command -> Force stack type
87
92
  ```bash
88
- devfix scan --preview
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 scan -a
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 { scanCommand } from "../src/commands/scan.js";
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("scan")
44
- .description("Scan your system/project for recent errors and context")
45
- .option("-p, --preview", "Preview collected data only (recommended)")
46
- .option("-a, --analyze", "Send scan results to AI")
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
- .action((options) => {
49
- scanCommand({
50
- preview: options.preview,
51
- analyze: options.analyze,
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
@@ -18,5 +18,5 @@
18
18
  "marked-terminal": "^7.3.0",
19
19
  "ora": "^9.3.0"
20
20
  },
21
- "version": "1.0.1"
21
+ "version": "1.0.3"
22
22
  }
@@ -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
- // If no text + no file, try auto error collection
49
- if ((!input || input.trim().length < 2) && useContext) {
50
- const autoErr = collectAutoError();
51
- if (autoErr) input = autoErr;
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";
@@ -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 safeExec(cmd) {
5
+ function getLatestFile(dir, ext = ".log") {
7
6
  try {
8
- return execSync(cmd, { encoding: "utf-8", stdio: ["ignore", "pipe", "ignore"] }).trim();
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(npmLog)
22
- .filter((f) => f.endsWith(".log"))
10
+ .readdirSync(dir)
11
+ .filter((f) => f.endsWith(ext))
23
12
  .map((f) => ({
24
13
  name: f,
25
- full: path.join(npmLog, f),
26
- time: fs.statSync(path.join(npmLog, f)).mtimeMs,
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
- if (files.length > 0) {
31
- const latest = files[0].full;
32
- const content = fs.readFileSync(latest, "utf-8").slice(-6000);
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
- // 2) Git status (sometimes shows merge conflicts)
38
- const git = safeExec("git status --porcelain=v1");
39
- if (git && git.trim().length > 0) {
40
- return `📌 Auto-collected Git status:\n\n${git}`;
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
- // 3) Kubernetes events (if cluster exists)
44
- const events = safeExec(
45
- "kubectl get events -A --sort-by=.metadata.creationTimestamp | tail -n 25"
46
- );
47
- if (events) {
48
- return `📌 Auto-collected Kubernetes events:\n\n${events}`;
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
  }
@@ -4,8 +4,8 @@ You are DevFix, a CLI debugging assistant.
4
4
 
5
5
  Hard rules:
6
6
  - Be concise.
7
- - Max 5 fix steps.
8
- - Max 6 commands.
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.
@@ -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
+ }
@@ -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
- }