kramscan 0.1.1 → 0.2.0
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 +392 -236
- package/dist/agent/confirmation.d.ts +5 -1
- package/dist/agent/confirmation.js +29 -9
- package/dist/agent/context.js +2 -3
- package/dist/agent/orchestrator.d.ts +2 -0
- package/dist/agent/orchestrator.js +50 -8
- package/dist/agent/prompts/system.d.ts +1 -1
- package/dist/agent/prompts/system.js +5 -7
- package/dist/agent/skills/health-check.js +22 -2
- package/dist/agent/skills/index.d.ts +1 -0
- package/dist/agent/skills/index.js +3 -1
- package/dist/agent/skills/verify-finding.d.ts +17 -0
- package/dist/agent/skills/verify-finding.js +91 -0
- package/dist/agent/skills/web-scan.js +46 -0
- package/dist/cli.js +150 -149
- package/dist/commands/agent.js +38 -38
- package/dist/commands/ai.d.ts +2 -0
- package/dist/commands/ai.js +112 -0
- package/dist/commands/analyze.js +103 -54
- package/dist/commands/config.js +55 -29
- package/dist/commands/doctor.js +20 -15
- package/dist/commands/onboard.js +188 -141
- package/dist/commands/report.js +68 -76
- package/dist/commands/scan.js +261 -81
- package/dist/commands/scans.d.ts +2 -0
- package/dist/commands/scans.js +51 -0
- package/dist/core/ai-client.d.ts +6 -1
- package/dist/core/ai-client.js +80 -12
- package/dist/core/ai-payloads.d.ts +17 -0
- package/dist/core/ai-payloads.js +54 -0
- package/dist/core/config-schema.d.ts +197 -0
- package/dist/core/config-schema.js +68 -0
- package/dist/core/config-schema.test.d.ts +1 -0
- package/dist/core/config-schema.test.js +151 -0
- package/dist/core/config.d.ts +8 -31
- package/dist/core/config.js +68 -11
- package/dist/core/errors.d.ts +71 -0
- package/dist/core/errors.js +162 -0
- package/dist/core/scan-index.d.ts +19 -0
- package/dist/core/scan-index.js +52 -0
- package/dist/core/scan-storage.d.ts +11 -0
- package/dist/core/scan-storage.js +69 -0
- package/dist/core/scanner.d.ts +95 -13
- package/dist/core/scanner.js +336 -248
- package/dist/core/vulnerability-detector.d.ts +3 -0
- package/dist/core/vulnerability-detector.js +25 -15
- package/dist/core/vulnerability-detector.test.d.ts +1 -0
- package/dist/core/vulnerability-detector.test.js +210 -0
- package/dist/index.js +3 -0
- package/dist/plugins/PluginManager.d.ts +27 -0
- package/dist/plugins/PluginManager.js +166 -0
- package/dist/plugins/index.d.ts +7 -0
- package/dist/plugins/index.js +19 -0
- package/dist/plugins/types.d.ts +55 -0
- package/dist/plugins/types.js +25 -0
- package/dist/plugins/vulnerabilities/CSRFPlugin.d.ts +8 -0
- package/dist/plugins/vulnerabilities/CSRFPlugin.js +34 -0
- package/dist/plugins/vulnerabilities/SQLInjectionPlugin.d.ts +11 -0
- package/dist/plugins/vulnerabilities/SQLInjectionPlugin.js +109 -0
- package/dist/plugins/vulnerabilities/SecurityHeadersPlugin.d.ts +11 -0
- package/dist/plugins/vulnerabilities/SecurityHeadersPlugin.js +63 -0
- package/dist/plugins/vulnerabilities/SensitiveDataPlugin.d.ts +9 -0
- package/dist/plugins/vulnerabilities/SensitiveDataPlugin.js +32 -0
- package/dist/plugins/vulnerabilities/XSSPlugin.d.ts +15 -0
- package/dist/plugins/vulnerabilities/XSSPlugin.js +81 -0
- package/dist/reports/PdfGenerator.d.ts +36 -0
- package/dist/reports/PdfGenerator.js +379 -0
- package/dist/utils/logger.d.ts +33 -1
- package/dist/utils/logger.js +127 -8
- package/dist/utils/theme.d.ts +55 -0
- package/dist/utils/theme.js +195 -0
- package/package.json +1 -1
package/dist/commands/analyze.js
CHANGED
|
@@ -1,47 +1,66 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
2
35
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
36
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
37
|
};
|
|
5
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
39
|
exports.registerAnalyzeCommand = registerAnalyzeCommand;
|
|
40
|
+
const commander_1 = require("commander");
|
|
7
41
|
const chalk_1 = __importDefault(require("chalk"));
|
|
8
42
|
const ai_client_1 = require("../core/ai-client");
|
|
43
|
+
const scan_storage_1 = require("../core/scan-storage");
|
|
9
44
|
const logger_1 = require("../utils/logger");
|
|
45
|
+
const theme_1 = require("../utils/theme");
|
|
10
46
|
const promises_1 = __importDefault(require("fs/promises"));
|
|
11
|
-
const path_1 = __importDefault(require("path"));
|
|
12
|
-
const os_1 = __importDefault(require("os"));
|
|
13
47
|
function registerAnalyzeCommand(program) {
|
|
14
48
|
program
|
|
15
49
|
.command("analyze [scan-file]")
|
|
16
50
|
.description("AI-powered analysis of scan results")
|
|
17
51
|
.option("-m, --model <name>", "Override default AI model")
|
|
18
52
|
.option("-v, --verbose", "Show detailed analysis")
|
|
19
|
-
.action(async (scanFile
|
|
53
|
+
.action(async (scanFile) => {
|
|
20
54
|
console.log("");
|
|
21
|
-
console.log(chalk_1.default.bold.cyan("
|
|
22
|
-
console.log(chalk_1.default.gray("
|
|
55
|
+
console.log(chalk_1.default.bold.cyan("AI Security Analysis"));
|
|
56
|
+
console.log(chalk_1.default.gray("-".repeat(50)));
|
|
23
57
|
console.log("");
|
|
58
|
+
let spinner = null;
|
|
24
59
|
try {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
if (
|
|
28
|
-
|
|
29
|
-
? scanFile
|
|
30
|
-
: path_1.default.join(process.cwd(), scanFile);
|
|
31
|
-
}
|
|
32
|
-
else {
|
|
33
|
-
// Find latest scan
|
|
34
|
-
const scanDir = path_1.default.join(os_1.default.homedir(), ".kramscan", "scans");
|
|
35
|
-
const files = await promises_1.default.readdir(scanDir);
|
|
36
|
-
const scanFiles = files.filter((f) => f.endsWith(".json"));
|
|
37
|
-
if (scanFiles.length === 0) {
|
|
38
|
-
logger_1.logger.error("No scan results found. Run 'kramscan scan <url>' first.");
|
|
39
|
-
process.exit(1);
|
|
40
|
-
}
|
|
41
|
-
// Get most recent
|
|
42
|
-
scanFiles.sort().reverse();
|
|
43
|
-
filepath = path_1.default.join(scanDir, scanFiles[0]);
|
|
44
|
-
logger_1.logger.info(`Using latest scan: ${scanFiles[0]}`);
|
|
60
|
+
const resolved = await (0, scan_storage_1.resolveScanFile)(scanFile);
|
|
61
|
+
const filepath = resolved.filepath;
|
|
62
|
+
if (resolved.isLatest) {
|
|
63
|
+
logger_1.logger.info(`Using latest scan: ${resolved.filename}`);
|
|
45
64
|
}
|
|
46
65
|
const content = await promises_1.default.readFile(filepath, "utf-8");
|
|
47
66
|
const scanResult = JSON.parse(content);
|
|
@@ -49,26 +68,53 @@ function registerAnalyzeCommand(program) {
|
|
|
49
68
|
logger_1.logger.success("No vulnerabilities to analyze!");
|
|
50
69
|
return;
|
|
51
70
|
}
|
|
52
|
-
|
|
53
|
-
|
|
71
|
+
spinner = logger_1.logger.spinner("Analyzing vulnerabilities with AI...");
|
|
72
|
+
const { getConfig } = await Promise.resolve().then(() => __importStar(require("../core/config")));
|
|
73
|
+
const config = await getConfig();
|
|
74
|
+
if (!config.ai.enabled) {
|
|
75
|
+
spinner.stop();
|
|
76
|
+
const { default: inquirer } = await Promise.resolve().then(() => __importStar(require("inquirer")));
|
|
77
|
+
const { setup } = await inquirer.prompt([
|
|
78
|
+
{
|
|
79
|
+
type: "confirm",
|
|
80
|
+
name: "setup",
|
|
81
|
+
message: theme_1.theme.yellow("AI analysis is not enabled. Would you like to set it up now?"),
|
|
82
|
+
default: true,
|
|
83
|
+
},
|
|
84
|
+
]);
|
|
85
|
+
if (setup) {
|
|
86
|
+
const { registerOnboardCommand } = await Promise.resolve().then(() => __importStar(require("./onboard")));
|
|
87
|
+
const onboardProgram = new commander_1.Command();
|
|
88
|
+
registerOnboardCommand(onboardProgram);
|
|
89
|
+
await onboardProgram.parseAsync(["node", "kramscan", "onboard"]);
|
|
90
|
+
// Re-check after onboarding
|
|
91
|
+
const updatedConfig = await getConfig();
|
|
92
|
+
if (!updatedConfig.ai.enabled) {
|
|
93
|
+
logger_1.logger.error("AI analysis still not enabled. Exiting.");
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
spinner.start("Analyzing vulnerabilities with AI...");
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
logger_1.logger.warn("AI analysis skipped.");
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
54
103
|
const aiClient = await (0, ai_client_1.createAIClient)();
|
|
55
|
-
// Build analysis prompt
|
|
56
104
|
const prompt = buildAnalysisPrompt(scanResult);
|
|
57
|
-
// Get AI analysis
|
|
58
105
|
const response = await aiClient.analyze(prompt);
|
|
59
106
|
spinner.succeed("Analysis complete!");
|
|
60
107
|
console.log("");
|
|
61
|
-
console.log(chalk_1.default.bold("
|
|
62
|
-
console.log(chalk_1.default.gray("
|
|
108
|
+
console.log(chalk_1.default.bold("AI Analysis"));
|
|
109
|
+
console.log(chalk_1.default.gray("-".repeat(50)));
|
|
63
110
|
console.log("");
|
|
64
111
|
console.log(response.content);
|
|
65
112
|
console.log("");
|
|
66
113
|
if (response.usage) {
|
|
67
|
-
console.log(chalk_1.default.gray("
|
|
114
|
+
console.log(chalk_1.default.gray("-".repeat(50)));
|
|
68
115
|
console.log(chalk_1.default.gray(`Tokens used: ${response.usage.totalTokens} (${response.usage.promptTokens} prompt + ${response.usage.completionTokens} completion)`));
|
|
69
116
|
console.log("");
|
|
70
117
|
}
|
|
71
|
-
// Save enhanced results
|
|
72
118
|
const enhancedResult = {
|
|
73
119
|
...scanResult,
|
|
74
120
|
aiAnalysis: {
|
|
@@ -82,6 +128,9 @@ function registerAnalyzeCommand(program) {
|
|
|
82
128
|
console.log("");
|
|
83
129
|
}
|
|
84
130
|
catch (error) {
|
|
131
|
+
if (spinner) {
|
|
132
|
+
spinner.fail("Analysis failed");
|
|
133
|
+
}
|
|
85
134
|
logger_1.logger.error(error.message);
|
|
86
135
|
process.exit(1);
|
|
87
136
|
}
|
|
@@ -89,27 +138,27 @@ function registerAnalyzeCommand(program) {
|
|
|
89
138
|
}
|
|
90
139
|
function buildAnalysisPrompt(scanResult) {
|
|
91
140
|
const vulnList = scanResult.vulnerabilities
|
|
92
|
-
.map((v, i) => `${i + 1}. [${v.severity.toUpperCase()}] ${v.title}
|
|
93
|
-
URL: ${v.url}
|
|
94
|
-
Description: ${v.description}
|
|
95
|
-
|
|
96
|
-
|
|
141
|
+
.map((v, i) => `${i + 1}. [${v.severity.toUpperCase()}] ${v.title}\n` +
|
|
142
|
+
` URL: ${v.url}\n` +
|
|
143
|
+
` Description: ${v.description}\n` +
|
|
144
|
+
`${v.evidence ? ` Evidence: ${v.evidence}\n` : ""}` +
|
|
145
|
+
`${v.cwe ? ` CWE: ${v.cwe}` : ""}`)
|
|
97
146
|
.join("\n\n");
|
|
98
|
-
return `You are a security expert analyzing web application vulnerabilities.
|
|
99
|
-
|
|
100
|
-
Target: ${scanResult.target}
|
|
101
|
-
Scan Date: ${scanResult.timestamp}
|
|
102
|
-
Total Vulnerabilities: ${scanResult.summary.total}
|
|
103
|
-
|
|
104
|
-
Vulnerabilities Found:
|
|
105
|
-
${vulnList}
|
|
106
|
-
|
|
107
|
-
Please provide:
|
|
108
|
-
1.
|
|
109
|
-
2.
|
|
110
|
-
3.
|
|
111
|
-
4.
|
|
112
|
-
5.
|
|
113
|
-
|
|
147
|
+
return `You are a security expert analyzing web application vulnerabilities.
|
|
148
|
+
|
|
149
|
+
Target: ${scanResult.target}
|
|
150
|
+
Scan Date: ${scanResult.timestamp}
|
|
151
|
+
Total Vulnerabilities: ${scanResult.summary.total}
|
|
152
|
+
|
|
153
|
+
Vulnerabilities Found:
|
|
154
|
+
${vulnList}
|
|
155
|
+
|
|
156
|
+
Please provide:
|
|
157
|
+
1. Executive Summary: Brief overview of the security posture
|
|
158
|
+
2. Risk Assessment: Overall risk level and business impact
|
|
159
|
+
3. Priority Recommendations: Top 3-5 vulnerabilities to fix first, with specific remediation steps
|
|
160
|
+
4. Attack Scenarios: How an attacker could chain these vulnerabilities
|
|
161
|
+
5. Remediation Roadmap: Step-by-step plan to address all findings
|
|
162
|
+
|
|
114
163
|
Format your response in clear markdown with headers and bullet points.`;
|
|
115
164
|
}
|
package/dist/commands/config.js
CHANGED
|
@@ -22,21 +22,26 @@ function registerConfigCommand(program) {
|
|
|
22
22
|
logger_1.logger.error(`Configuration key '${key}' not found`);
|
|
23
23
|
process.exit(1);
|
|
24
24
|
}
|
|
25
|
-
|
|
25
|
+
const displayValue = key === "ai.apiKey" && typeof value === "string" && value
|
|
26
|
+
? "***configured***"
|
|
27
|
+
: value;
|
|
28
|
+
console.log(chalk_1.default.cyan(key), "=", chalk_1.default.white(JSON.stringify(displayValue, null, 2)));
|
|
26
29
|
});
|
|
27
30
|
configCmd
|
|
28
31
|
.command("set <key> <value>")
|
|
29
32
|
.description("Set a configuration value")
|
|
30
33
|
.action(async (key, value) => {
|
|
31
34
|
const config = await (0, config_1.getConfig)();
|
|
32
|
-
// Parse value
|
|
33
35
|
let parsedValue = value;
|
|
34
|
-
if (value === "true")
|
|
36
|
+
if (value === "true") {
|
|
35
37
|
parsedValue = true;
|
|
36
|
-
|
|
38
|
+
}
|
|
39
|
+
else if (value === "false") {
|
|
37
40
|
parsedValue = false;
|
|
38
|
-
|
|
41
|
+
}
|
|
42
|
+
else if (!Number.isNaN(Number(value))) {
|
|
39
43
|
parsedValue = Number(value);
|
|
44
|
+
}
|
|
40
45
|
setNestedValue(config, key, parsedValue);
|
|
41
46
|
await (0, config_1.setConfig)(config);
|
|
42
47
|
logger_1.logger.success(`Set ${chalk_1.default.cyan(key)} = ${chalk_1.default.white(JSON.stringify(parsedValue))}`);
|
|
@@ -46,11 +51,18 @@ function registerConfigCommand(program) {
|
|
|
46
51
|
.description("List all configuration")
|
|
47
52
|
.action(async () => {
|
|
48
53
|
const config = await (0, config_1.getConfig)();
|
|
54
|
+
const sanitizedConfig = {
|
|
55
|
+
...config,
|
|
56
|
+
ai: {
|
|
57
|
+
...config.ai,
|
|
58
|
+
apiKey: config.ai.apiKey ? "***configured***" : "",
|
|
59
|
+
},
|
|
60
|
+
};
|
|
49
61
|
console.log("");
|
|
50
|
-
console.log(chalk_1.default.bold.cyan("
|
|
51
|
-
console.log(chalk_1.default.gray("
|
|
62
|
+
console.log(chalk_1.default.bold.cyan("Current Configuration"));
|
|
63
|
+
console.log(chalk_1.default.gray("-".repeat(50)));
|
|
52
64
|
console.log("");
|
|
53
|
-
console.log(JSON.stringify(
|
|
65
|
+
console.log(JSON.stringify(sanitizedConfig, null, 2));
|
|
54
66
|
console.log("");
|
|
55
67
|
});
|
|
56
68
|
configCmd
|
|
@@ -59,8 +71,8 @@ function registerConfigCommand(program) {
|
|
|
59
71
|
.action(async () => {
|
|
60
72
|
const config = await (0, config_1.getConfig)();
|
|
61
73
|
console.log("");
|
|
62
|
-
console.log(chalk_1.default.bold.cyan("
|
|
63
|
-
console.log(chalk_1.default.gray("
|
|
74
|
+
console.log(chalk_1.default.bold.cyan("Configuration Editor"));
|
|
75
|
+
console.log(chalk_1.default.gray("-".repeat(50)));
|
|
64
76
|
console.log("");
|
|
65
77
|
const answers = await inquirer_1.default.prompt([
|
|
66
78
|
{
|
|
@@ -73,23 +85,23 @@ function registerConfigCommand(program) {
|
|
|
73
85
|
type: "list",
|
|
74
86
|
name: "aiProvider",
|
|
75
87
|
message: "AI provider:",
|
|
76
|
-
choices: ["openai", "anthropic"],
|
|
88
|
+
choices: ["openai", "anthropic", "gemini", "openrouter", "mistral", "kimi", "groq"],
|
|
77
89
|
default: config.ai.provider,
|
|
78
|
-
when: (
|
|
90
|
+
when: (ans) => ans.aiEnabled,
|
|
79
91
|
},
|
|
80
92
|
{
|
|
81
93
|
type: "input",
|
|
82
94
|
name: "apiKey",
|
|
83
|
-
message: "API key:",
|
|
84
|
-
default:
|
|
85
|
-
when: (
|
|
95
|
+
message: "API key (leave blank to keep current):",
|
|
96
|
+
default: "",
|
|
97
|
+
when: (ans) => ans.aiEnabled,
|
|
86
98
|
},
|
|
87
99
|
{
|
|
88
100
|
type: "input",
|
|
89
101
|
name: "model",
|
|
90
102
|
message: "Default AI model:",
|
|
91
103
|
default: config.ai.defaultModel,
|
|
92
|
-
when: (
|
|
104
|
+
when: (ans) => ans.aiEnabled,
|
|
93
105
|
},
|
|
94
106
|
{
|
|
95
107
|
type: "list",
|
|
@@ -103,36 +115,50 @@ function registerConfigCommand(program) {
|
|
|
103
115
|
name: "rateLimit",
|
|
104
116
|
message: "Requests per second rate limit:",
|
|
105
117
|
default: config.scan.rateLimitPerSecond,
|
|
118
|
+
validate: (value) => Number.isFinite(value) && value > 0
|
|
119
|
+
? true
|
|
120
|
+
: "Rate limit must be a positive number",
|
|
106
121
|
},
|
|
107
122
|
]);
|
|
108
|
-
|
|
109
|
-
if (answers.aiEnabled !== undefined)
|
|
123
|
+
if (answers.aiEnabled !== undefined) {
|
|
110
124
|
config.ai.enabled = answers.aiEnabled;
|
|
111
|
-
|
|
125
|
+
}
|
|
126
|
+
if (answers.aiProvider) {
|
|
112
127
|
config.ai.provider = answers.aiProvider;
|
|
113
|
-
|
|
128
|
+
}
|
|
129
|
+
if (answers.apiKey) {
|
|
114
130
|
config.ai.apiKey = answers.apiKey;
|
|
115
|
-
|
|
131
|
+
}
|
|
132
|
+
if (answers.model) {
|
|
116
133
|
config.ai.defaultModel = answers.model;
|
|
117
|
-
|
|
134
|
+
}
|
|
135
|
+
if (answers.reportFormat) {
|
|
118
136
|
config.report.defaultFormat = answers.reportFormat;
|
|
119
|
-
|
|
137
|
+
}
|
|
138
|
+
if (Number.isFinite(answers.rateLimit) && answers.rateLimit > 0) {
|
|
120
139
|
config.scan.rateLimitPerSecond = answers.rateLimit;
|
|
121
|
-
|
|
140
|
+
}
|
|
141
|
+
await (0, config_1.setConfig)(config);
|
|
122
142
|
console.log("");
|
|
123
143
|
logger_1.logger.success("Configuration updated successfully!");
|
|
124
144
|
console.log("");
|
|
125
145
|
});
|
|
126
146
|
}
|
|
127
|
-
function getNestedValue(obj,
|
|
128
|
-
return
|
|
147
|
+
function getNestedValue(obj, keyPath) {
|
|
148
|
+
return keyPath
|
|
149
|
+
.split(".")
|
|
150
|
+
.reduce((current, key) => current?.[key], obj);
|
|
129
151
|
}
|
|
130
|
-
function setNestedValue(obj,
|
|
131
|
-
const keys =
|
|
152
|
+
function setNestedValue(obj, keyPath, value) {
|
|
153
|
+
const keys = keyPath.split(".");
|
|
132
154
|
const lastKey = keys.pop();
|
|
155
|
+
if (!lastKey) {
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
133
158
|
const target = keys.reduce((current, key) => {
|
|
134
|
-
if (!current[key])
|
|
159
|
+
if (!current[key] || typeof current[key] !== "object") {
|
|
135
160
|
current[key] = {};
|
|
161
|
+
}
|
|
136
162
|
return current[key];
|
|
137
163
|
}, obj);
|
|
138
164
|
target[lastKey] = value;
|
package/dist/commands/doctor.js
CHANGED
|
@@ -40,11 +40,9 @@ exports.registerDoctorCommand = registerDoctorCommand;
|
|
|
40
40
|
const chalk_1 = __importDefault(require("chalk"));
|
|
41
41
|
const config_1 = require("../core/config");
|
|
42
42
|
const logger_1 = require("../utils/logger");
|
|
43
|
-
const
|
|
44
|
-
const util_1 = require("util");
|
|
43
|
+
const dns = __importStar(require("dns/promises"));
|
|
45
44
|
const promises_1 = __importDefault(require("fs/promises"));
|
|
46
45
|
const os_1 = __importDefault(require("os"));
|
|
47
|
-
const execAsync = (0, util_1.promisify)(child_process_1.exec);
|
|
48
46
|
function registerDoctorCommand(program) {
|
|
49
47
|
program
|
|
50
48
|
.command("doctor")
|
|
@@ -123,7 +121,7 @@ async function checkNodeVersion() {
|
|
|
123
121
|
}
|
|
124
122
|
async function checkPuppeteer() {
|
|
125
123
|
try {
|
|
126
|
-
|
|
124
|
+
await Promise.resolve().then(() => __importStar(require("puppeteer")));
|
|
127
125
|
return {
|
|
128
126
|
name: "Puppeteer",
|
|
129
127
|
status: "pass",
|
|
@@ -158,6 +156,15 @@ async function checkConfig() {
|
|
|
158
156
|
async function checkAPIKeys() {
|
|
159
157
|
try {
|
|
160
158
|
const config = await (0, config_1.getConfig)();
|
|
159
|
+
const envFallback = {
|
|
160
|
+
openai: process.env.OPENAI_API_KEY,
|
|
161
|
+
anthropic: process.env.ANTHROPIC_API_KEY,
|
|
162
|
+
gemini: process.env.GEMINI_API_KEY,
|
|
163
|
+
openrouter: process.env.OPENROUTER_API_KEY,
|
|
164
|
+
mistral: process.env.MISTRAL_API_KEY,
|
|
165
|
+
kimi: process.env.KIMI_API_KEY,
|
|
166
|
+
groq: process.env.GROQ_API_KEY,
|
|
167
|
+
};
|
|
161
168
|
if (!config.ai.enabled) {
|
|
162
169
|
return {
|
|
163
170
|
name: "AI Configuration",
|
|
@@ -165,7 +172,8 @@ async function checkAPIKeys() {
|
|
|
165
172
|
message: "AI analysis is disabled. Run 'kramscan onboard' to enable it.",
|
|
166
173
|
};
|
|
167
174
|
}
|
|
168
|
-
|
|
175
|
+
const apiKey = config.ai.apiKey || envFallback[config.ai.provider] || "";
|
|
176
|
+
if (!apiKey) {
|
|
169
177
|
return {
|
|
170
178
|
name: "AI Configuration",
|
|
171
179
|
status: "warn",
|
|
@@ -208,16 +216,13 @@ async function checkDiskSpace() {
|
|
|
208
216
|
}
|
|
209
217
|
async function checkNetwork() {
|
|
210
218
|
try {
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
resolve(true);
|
|
219
|
-
});
|
|
220
|
-
});
|
|
219
|
+
const timeoutMs = 3000;
|
|
220
|
+
await Promise.race([
|
|
221
|
+
dns.lookup("example.com"),
|
|
222
|
+
new Promise((_, reject) => {
|
|
223
|
+
setTimeout(() => reject(new Error("DNS lookup timeout")), timeoutMs);
|
|
224
|
+
}),
|
|
225
|
+
]);
|
|
221
226
|
return {
|
|
222
227
|
name: "Network Connectivity",
|
|
223
228
|
status: "pass",
|