devfix-cli 1.0.0 β 1.0.1
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 +95 -0
- package/bin/index.js +15 -0
- package/package.json +1 -1
- package/src/commands/analyze.js +13 -4
- package/src/commands/scan.js +94 -0
- package/src/utils/autoError.js +53 -0
package/README.md
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
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
|
+
scan -> collects context + auto errors:
|
|
82
|
+
```bash
|
|
83
|
+
devfix scan
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
scan preview -> shows what it found:
|
|
87
|
+
```bash
|
|
88
|
+
devfix scan --preview
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
sacn analyze -> Send scan results to AI:
|
|
92
|
+
```bash
|
|
93
|
+
devfix scan -a
|
|
94
|
+
```
|
|
95
|
+
|
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 { scanCommand } from "../src/commands/scan.js";
|
|
8
9
|
|
|
9
10
|
|
|
10
11
|
const program = new Command();
|
|
@@ -38,4 +39,18 @@ program
|
|
|
38
39
|
});
|
|
39
40
|
});
|
|
40
41
|
|
|
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")
|
|
47
|
+
.option("-m, --model <model>", "OpenRouter model override")
|
|
48
|
+
.action((options) => {
|
|
49
|
+
scanCommand({
|
|
50
|
+
preview: options.preview,
|
|
51
|
+
analyze: options.analyze,
|
|
52
|
+
model: options.model,
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
|
|
41
56
|
program.parse(process.argv);
|
package/package.json
CHANGED
package/src/commands/analyze.js
CHANGED
|
@@ -10,6 +10,7 @@ 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";
|
|
13
14
|
|
|
14
15
|
import { collectContext } from "../utils/context.js";
|
|
15
16
|
import { detectStack } from "../utils/detectStack.js";
|
|
@@ -44,10 +45,18 @@ export async function analyzeCommand({ text, file, stack, model, useContext }) {
|
|
|
44
45
|
input = fs.readFileSync(file, "utf-8");
|
|
45
46
|
}
|
|
46
47
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
+
|
|
51
60
|
|
|
52
61
|
const detected = stack || detectStack(input);
|
|
53
62
|
const usedModel = model || "openai/gpt-4o-mini";
|
|
@@ -0,0 +1,94 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import os from "os";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import { execSync } from "child_process";
|
|
5
|
+
|
|
6
|
+
function safeExec(cmd) {
|
|
7
|
+
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();
|
|
16
|
+
|
|
17
|
+
// 1) npm debug log (most common)
|
|
18
|
+
const npmLog = path.join(os.homedir(), ".npm", "_logs");
|
|
19
|
+
if (fs.existsSync(npmLog)) {
|
|
20
|
+
const files = fs
|
|
21
|
+
.readdirSync(npmLog)
|
|
22
|
+
.filter((f) => f.endsWith(".log"))
|
|
23
|
+
.map((f) => ({
|
|
24
|
+
name: f,
|
|
25
|
+
full: path.join(npmLog, f),
|
|
26
|
+
time: fs.statSync(path.join(npmLog, f)).mtimeMs,
|
|
27
|
+
}))
|
|
28
|
+
.sort((a, b) => b.time - a.time);
|
|
29
|
+
|
|
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
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
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}`;
|
|
41
|
+
}
|
|
42
|
+
|
|
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}`;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Nothing found
|
|
52
|
+
return null;
|
|
53
|
+
}
|