devfix-cli 1.0.0 β†’ 1.0.2

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 ADDED
@@ -0,0 +1,108 @@
1
+ # DevFix CLI πŸš€
2
+ AI-powered CLI tool that helps you debug errors, logs, and DevOps issues directly from your terminal.
3
+
4
+ DevFix analyzes your error output (Node, Docker, Kubernetes, Minikube, Git, etc.) and suggests the most likely fix with copy-paste commands.
5
+
6
+ ---
7
+
8
+ ## ✨ Features
9
+
10
+ - πŸ” Login once (saved locally for 7 days)
11
+ - πŸ€– Uses OpenRouter API (supports many models)
12
+ - 🧠 Auto-detects stack (Node, Docker, Kubernetes, Git, Python, React)
13
+ - πŸ“¦ Optional auto context mode (`--context`)
14
+ - 🎬 Animated DevFix logo during analysis (Minikube-style)
15
+ - 🧾 Clean Markdown output in terminal
16
+ - 🧩 Works on macOS / Linux / Windows
17
+
18
+ ---
19
+
20
+ ### 1) Install DevFix globally
21
+ ```bash
22
+ npm install -g devfix
23
+ ```
24
+
25
+ Check installation:
26
+ ```bash
27
+ devfix --version
28
+ ```
29
+
30
+ ---
31
+
32
+ ### 2) Login (required)
33
+
34
+ DevFix needs your OpenRouter API key to work.
35
+
36
+ Run:
37
+ ```bash
38
+ devfix login
39
+ ```
40
+
41
+ It will ask for:
42
+ - Username
43
+ - Email
44
+ - OpenRouter API Key
45
+
46
+ Your login is saved locally for **7 days**, so you don’t need to login again daily.
47
+
48
+ ---
49
+
50
+ ### 3) Start using DevFix
51
+
52
+ Analyze an error directly:
53
+ ```bash
54
+ devfix analyze "npm install failing"
55
+ ```
56
+
57
+ Analyze with automatic context collection (recommended):
58
+ ```bash
59
+ devfix analyze "minikube ingress not working" --context
60
+ ```
61
+
62
+ Analyze a log file:
63
+ ```bash
64
+ devfix analyze --file error.log
65
+ ```
66
+
67
+ ---
68
+
69
+ ### 4) Session commands
70
+
71
+ Check current login:
72
+ ```bash
73
+ devfix whoami
74
+ ```
75
+
76
+ Logout:
77
+ ```bash
78
+ devfix logout
79
+ ```
80
+ ---
81
+ ### 5) Run a command and auto-capture errors for AI fixing
82
+ 1. Context command -> Include project/system context
83
+ ```bash
84
+ devfix run <Command> --context
85
+ ```
86
+ ---
87
+ for example : devfix run kubectl get pods --context
88
+ ---
89
+
90
+ 2. Stack Command -> Force stack type
91
+ ```bash
92
+ devfix run <Command> --stack <StackName>
93
+ ```
94
+ ---
95
+ for example : devfix run kubectl get pods --stack kubernetes
96
+ ---
97
+
98
+ Model Command -> OpenRouter model override
99
+
100
+ ```bash
101
+ devfix run <Command> --model <ModelName>
102
+ ```
103
+ ---
104
+ for example : devfix run kubectl get pods --model openai/gpt-4o-mini
105
+ ---
106
+
107
+
108
+
package/bin/index.js CHANGED
@@ -5,6 +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 { runCommand } from "../src/utils/run.js";
8
9
 
9
10
 
10
11
  const program = new Command();
@@ -38,4 +39,20 @@ program
38
39
  });
39
40
  });
40
41
 
42
+ program
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
+ .option("-m, --model <model>", "OpenRouter model override")
48
+ .argument("<cmd...>", "Command to run")
49
+ .action((cmd, options) => {
50
+ runCommand(cmd, {
51
+ context: options.context,
52
+ stack: options.stack,
53
+ model: options.model,
54
+ });
55
+ });
56
+
57
+
41
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.0"
21
+ "version": "1.0.2"
22
22
  }
@@ -0,0 +1,76 @@
1
+ import fs from "fs";
2
+ import os from "os";
3
+ import path from "path";
4
+
5
+ function getLatestFile(dir, ext = ".log") {
6
+ try {
7
+ if (!fs.existsSync(dir)) return null;
8
+
9
+ const files = fs
10
+ .readdirSync(dir)
11
+ .filter((f) => f.endsWith(ext))
12
+ .map((f) => ({
13
+ name: f,
14
+ full: path.join(dir, f),
15
+ time: fs.statSync(path.join(dir, f)).mtimeMs,
16
+ }))
17
+ .sort((a, b) => b.time - a.time);
18
+
19
+ return files.length ? files[0].full : null;
20
+ } catch {
21
+ return null;
22
+ }
23
+ }
24
+
25
+ function extractNpmError(logText) {
26
+ const lines = logText.split("\n");
27
+
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
+ }
39
+ }
40
+
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
+ }
73
+ }
74
+
75
+ return null;
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,240 @@
1
+ import chalk from "chalk";
2
+ import boxen from "boxen";
3
+ import inquirer from "inquirer";
4
+ import ora from "ora";
5
+ import { spawn } from "child_process";
6
+
7
+ import { marked } from "marked";
8
+ import TerminalRenderer from "marked-terminal";
9
+
10
+ import { readConfig } from "../utils/config.js";
11
+ import { isSessionValid } from "../utils/session.js";
12
+ import { decrypt } from "../utils/cryptoStore.js";
13
+
14
+ import { collectContext } from "../utils/context.js";
15
+ import { detectStack } from "../utils/detectStack.js";
16
+ import { buildPrompt } from "../utils/prompt.js";
17
+ import { askAI } from "../utils/ai.js";
18
+
19
+ // Markdown renderer
20
+ marked.setOptions({
21
+ renderer: new TerminalRenderer(),
22
+ });
23
+
24
+ export async function runCommand(cmdArgs, options) {
25
+ const config = readConfig();
26
+
27
+ if (!isSessionValid(config)) {
28
+ console.log(chalk.red("\n❌ Not logged in or session expired.\nRun: devfix login\n"));
29
+ process.exit(1);
30
+ }
31
+
32
+ const apiKey = decrypt(config.apiKeyEncrypted);
33
+ if (!apiKey) {
34
+ console.log(chalk.red("\n❌ API key missing. Run: devfix login\n"));
35
+ process.exit(1);
36
+ }
37
+
38
+ if (!cmdArgs || cmdArgs.length === 0) {
39
+ console.log(chalk.red("\n❌ Please provide a command.\nExample: devfix run kubectl get pods\n"));
40
+ process.exit(1);
41
+ }
42
+
43
+ const command = cmdArgs[0];
44
+ const args = cmdArgs.slice(1);
45
+ const fullCmd = `${command} ${args.join(" ")}`.trim();
46
+
47
+ console.log(
48
+ boxen(
49
+ `${chalk.bold.cyan("β–Ά DevFix Run")}\n\n${chalk.white("Command:")} ${chalk.yellow(fullCmd)}\n${
50
+ options.context ? chalk.green("Context: ON") : chalk.red("Context: OFF")
51
+ }`,
52
+ { padding: 1, borderStyle: "round" }
53
+ )
54
+ );
55
+
56
+ // βœ… No shell=true (removes the security warning)
57
+ const child = spawn(command, args, { stdio: ["inherit", "pipe", "pipe"] });
58
+
59
+ let stdout = "";
60
+ let stderr = "";
61
+
62
+ child.stdout.on("data", (d) => {
63
+ const text = d.toString();
64
+ stdout += text;
65
+ process.stdout.write(text);
66
+ });
67
+
68
+ child.stderr.on("data", (d) => {
69
+ const text = d.toString();
70
+ stderr += text;
71
+ process.stderr.write(text);
72
+ });
73
+
74
+ child.on("close", async (code) => {
75
+ if (code === 0) {
76
+ console.log(chalk.green("\nβœ… Command succeeded.\n"));
77
+ return;
78
+ }
79
+
80
+ const errorText = (stderr || stdout || "").trim();
81
+
82
+ console.log(chalk.red(`\n❌ Command failed (exit code: ${code}).\n`));
83
+
84
+ if (!errorText) {
85
+ console.log(chalk.red("No error output captured.\n"));
86
+ return;
87
+ }
88
+
89
+ console.log(
90
+ boxen(chalk.bold.red("⚠️ Captured Error"), {
91
+ padding: 1,
92
+ borderStyle: "round",
93
+ })
94
+ );
95
+
96
+ console.log(chalk.red(errorText.slice(0, 1200)));
97
+ if (errorText.length > 1200) console.log(chalk.gray("\n...trimmed...\n"));
98
+
99
+ const { confirm } = await inquirer.prompt([
100
+ {
101
+ name: "confirm",
102
+ type: "confirm",
103
+ message: "Send this error to DevFix AI for a fix?",
104
+ default: true,
105
+ },
106
+ ]);
107
+
108
+ if (!confirm) {
109
+ console.log(chalk.yellow("\n❌ Not sent to AI.\n"));
110
+ return;
111
+ }
112
+
113
+ const context = options.context ? collectContext() : {};
114
+ const stack = options.stack || detectStack(errorText);
115
+ const usedModel = options.model || "openai/gpt-4o-mini";
116
+
117
+ // πŸ”₯ Send a better bundle (so AI never asks useless questions)
118
+ const errorBundle = `
119
+ Command:
120
+ ${fullCmd}
121
+
122
+ Exit Code:
123
+ ${code}
124
+
125
+ Error Output:
126
+ ${errorText}
127
+ `.trim();
128
+
129
+ console.log(
130
+ boxen(
131
+ `${chalk.bold.white("Stack:")} ${chalk.cyan(stack)}\n${chalk.bold.white("Model:")} ${chalk.magenta(
132
+ usedModel
133
+ )}`,
134
+ { padding: 1, borderStyle: "round" }
135
+ )
136
+ );
137
+
138
+ const spinner = ora("DevFix AI is analyzing...").start();
139
+
140
+ try {
141
+ const prompt = buildPrompt({
142
+ stack,
143
+ input: errorBundle,
144
+ context,
145
+ });
146
+
147
+ const answer = await askAI({
148
+ apiKey,
149
+ model: usedModel,
150
+ prompt,
151
+ });
152
+
153
+ spinner.succeed("Analysis complete");
154
+
155
+ console.log(
156
+ boxen(chalk.bold.green("βœ… DevFix Suggested Fix"), {
157
+ padding: 1,
158
+ borderStyle: "round",
159
+ })
160
+ );
161
+
162
+ // βœ… Pretty output (markdown rendered)
163
+ console.log(marked(answer));
164
+ console.log();
165
+ } catch (err) {
166
+ spinner.fail("AI request failed");
167
+
168
+ console.log(chalk.red("\n❌ Error:\n"));
169
+ console.log(err?.response?.data || err.message);
170
+ console.log();
171
+ }
172
+ });
173
+ child.on("error", async (err) => {
174
+ if (err.code === "ENOENT") {
175
+ console.log(chalk.red(`\n❌ Command not found: ${command}\n`));
176
+ console.log(chalk.gray("Tip: Did you mean `kubectl`?\n"));
177
+
178
+ const { confirm } = await inquirer.prompt([
179
+ {
180
+ name: "confirm",
181
+ type: "confirm",
182
+ message: "Send this error to DevFix AI for a fix?",
183
+ default: true,
184
+ },
185
+ ]);
186
+
187
+ if (!confirm) {
188
+ console.log(chalk.yellow("\n❌ Not sent to AI.\n"));
189
+ process.exit(1);
190
+ }
191
+
192
+ const errorText = `Command not found: ${command}\nTried to run: ${fullCmd}`;
193
+ const context = options.context ? collectContext() : {};
194
+ const stack = options.stack || detectStack(errorText);
195
+ const usedModel = options.model || "openai/gpt-4o-mini";
196
+
197
+ const spinner = ora("DevFix AI is analyzing...").start();
198
+
199
+ try {
200
+ const prompt = buildPrompt({
201
+ stack,
202
+ input: errorText,
203
+ context,
204
+ });
205
+
206
+ const answer = await askAI({
207
+ apiKey,
208
+ model: usedModel,
209
+ prompt,
210
+ });
211
+
212
+ spinner.succeed("Analysis complete");
213
+
214
+ console.log(
215
+ boxen(chalk.bold.green("βœ… DevFix Suggested Fix"), {
216
+ padding: 1,
217
+ borderStyle: "round",
218
+ })
219
+ );
220
+
221
+ console.log(marked(answer));
222
+ console.log();
223
+ } catch (e) {
224
+ spinner.fail("AI request failed");
225
+ console.log(chalk.red("\n❌ Error:\n"));
226
+ console.log(e?.response?.data || e.message);
227
+ console.log();
228
+ }
229
+
230
+ process.exit(1);
231
+ }
232
+
233
+ console.log(chalk.red("\n❌ Failed to run command:\n"));
234
+ console.log(err.message);
235
+ process.exit(1);
236
+ });
237
+
238
+ }
239
+
240
+