kramscan 0.1.1 → 0.3.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/LICENSE +1 -1
- package/README.md +419 -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 +156 -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/dev.d.ts +2 -0
- package/dist/commands/dev.js +236 -0
- package/dist/commands/doctor.js +20 -15
- package/dist/commands/gate.d.ts +2 -0
- package/dist/commands/gate.js +109 -0
- package/dist/commands/onboard.js +188 -141
- package/dist/commands/report.js +68 -76
- package/dist/commands/scan.js +262 -81
- package/dist/commands/scans.d.ts +2 -0
- package/dist/commands/scans.js +55 -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 +71 -14
- package/dist/core/diff-engine.d.ts +12 -0
- package/dist/core/diff-engine.js +47 -0
- package/dist/core/errors.d.ts +71 -0
- package/dist/core/errors.js +162 -0
- package/dist/core/scan-index.d.ts +20 -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 +342 -248
- package/dist/core/server-probe.d.ts +20 -0
- package/dist/core/server-probe.js +109 -0
- package/dist/core/vulnerability-detector.d.ts +9 -0
- package/dist/core/vulnerability-detector.js +46 -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 +12 -0
- package/dist/plugins/index.js +29 -0
- package/dist/plugins/types.d.ts +55 -0
- package/dist/plugins/types.js +25 -0
- package/dist/plugins/vulnerabilities/CORSAnalyzerPlugin.d.ts +10 -0
- package/dist/plugins/vulnerabilities/CORSAnalyzerPlugin.js +67 -0
- package/dist/plugins/vulnerabilities/CSRFPlugin.d.ts +8 -0
- package/dist/plugins/vulnerabilities/CSRFPlugin.js +34 -0
- package/dist/plugins/vulnerabilities/CookieSecurityPlugin.d.ts +10 -0
- package/dist/plugins/vulnerabilities/CookieSecurityPlugin.js +91 -0
- package/dist/plugins/vulnerabilities/DebugEndpointPlugin.d.ts +15 -0
- package/dist/plugins/vulnerabilities/DebugEndpointPlugin.js +222 -0
- package/dist/plugins/vulnerabilities/DirectoryTraversalPlugin.d.ts +13 -0
- package/dist/plugins/vulnerabilities/DirectoryTraversalPlugin.js +110 -0
- package/dist/plugins/vulnerabilities/OpenRedirectPlugin.d.ts +10 -0
- package/dist/plugins/vulnerabilities/OpenRedirectPlugin.js +69 -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 +404 -0
- package/dist/utils/logger.d.ts +33 -1
- package/dist/utils/logger.js +127 -8
- package/dist/utils/theme.d.ts +56 -0
- package/dist/utils/theme.js +201 -0
- package/package.json +6 -3
package/dist/cli.js
CHANGED
|
@@ -32,12 +32,16 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
32
32
|
return result;
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
35
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
39
|
exports.isVerbose = isVerbose;
|
|
37
40
|
exports.isDebug = isDebug;
|
|
38
41
|
exports.debugLog = debugLog;
|
|
39
42
|
exports.run = run;
|
|
40
43
|
const commander_1 = require("commander");
|
|
44
|
+
const inquirer_1 = __importDefault(require("inquirer"));
|
|
41
45
|
const onboard_1 = require("./commands/onboard");
|
|
42
46
|
const scan_1 = require("./commands/scan");
|
|
43
47
|
const analyze_1 = require("./commands/analyze");
|
|
@@ -45,8 +49,12 @@ const report_1 = require("./commands/report");
|
|
|
45
49
|
const config_1 = require("./commands/config");
|
|
46
50
|
const doctor_1 = require("./commands/doctor");
|
|
47
51
|
const agent_1 = require("./commands/agent");
|
|
52
|
+
const scans_1 = require("./commands/scans");
|
|
53
|
+
const ai_1 = require("./commands/ai");
|
|
54
|
+
const dev_1 = require("./commands/dev");
|
|
55
|
+
const gate_1 = require("./commands/gate");
|
|
48
56
|
const config_2 = require("./core/config");
|
|
49
|
-
const
|
|
57
|
+
const theme_1 = require("./utils/theme");
|
|
50
58
|
let verboseMode = false;
|
|
51
59
|
let debugMode = false;
|
|
52
60
|
function isVerbose() {
|
|
@@ -60,62 +68,12 @@ function debugLog(...args) {
|
|
|
60
68
|
console.log("[DEBUG]", ...args);
|
|
61
69
|
}
|
|
62
70
|
}
|
|
63
|
-
// ─── ANSI Color Helpers ────────────────────────────────────────────
|
|
64
|
-
const c = {
|
|
65
|
-
reset: "\x1b[0m",
|
|
66
|
-
bold: "\x1b[1m",
|
|
67
|
-
dim: "\x1b[2m",
|
|
68
|
-
red: "\x1b[31m",
|
|
69
|
-
green: "\x1b[32m",
|
|
70
|
-
yellow: "\x1b[33m",
|
|
71
|
-
blue: "\x1b[34m",
|
|
72
|
-
magenta: "\x1b[35m",
|
|
73
|
-
cyan: "\x1b[36m",
|
|
74
|
-
white: "\x1b[37m",
|
|
75
|
-
gray: "\x1b[90m",
|
|
76
|
-
bgBlue: "\x1b[44m",
|
|
77
|
-
bgMagenta: "\x1b[45m",
|
|
78
|
-
brightCyan: "\x1b[96m",
|
|
79
|
-
brightMagenta: "\x1b[95m",
|
|
80
|
-
brightBlue: "\x1b[94m",
|
|
81
|
-
brightGreen: "\x1b[92m",
|
|
82
|
-
brightYellow: "\x1b[93m",
|
|
83
|
-
brightWhite: "\x1b[97m",
|
|
84
|
-
};
|
|
85
|
-
// ─── ASCII Art Banner ──────────────────────────────────────────────
|
|
86
|
-
function printBanner() {
|
|
87
|
-
// Sleek ANSI Shadow style — KRAMSCAN
|
|
88
|
-
const lines = [
|
|
89
|
-
`██╗ ██╗██████╗ █████╗ ███╗ ███╗███████╗ ██████╗ █████╗ ███╗ ██╗`,
|
|
90
|
-
`██║ ██╔╝██╔══██╗██╔══██╗████╗ ████║██╔════╝██╔════╝██╔══██╗████╗ ██║`,
|
|
91
|
-
`█████╔╝ ██████╔╝███████║██╔████╔██║███████╗██║ ███████║██╔██╗ ██║`,
|
|
92
|
-
`██╔═██╗ ██╔══██╗██╔══██║██║╚██╔╝██║╚════██║██║ ██╔══██║██║╚██╗██║`,
|
|
93
|
-
`██║ ██╗██║ ██║██║ ██║██║ ╚═╝ ██║███████║╚██████╗██║ ██║██║ ╚████║`,
|
|
94
|
-
`╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝ ╚═════╝╚═╝ ╚═╝╚═╝ ╚═══╝`,
|
|
95
|
-
];
|
|
96
|
-
console.log("");
|
|
97
|
-
lines.forEach((line, i) => {
|
|
98
|
-
const shade = i % 2 === 0 ? c.brightWhite : c.gray;
|
|
99
|
-
console.log(` ${shade}${line}${c.reset}`);
|
|
100
|
-
});
|
|
101
|
-
console.log("");
|
|
102
|
-
}
|
|
103
|
-
// ─── Dashboard Info ────────────────────────────────────────────────
|
|
104
|
-
function printInfo() {
|
|
105
|
-
console.log(` ${c.gray}${c.dim}───────────────────────────────────────────────────────${c.reset}`);
|
|
106
|
-
console.log(` ${c.brightWhite}${c.bold} KramScan${c.reset} ${c.gray}v${CLI_VERSION}${c.reset} ${c.dim}${c.gray}|${c.reset} ${c.cyan}AI-Powered Web Security Scanner${c.reset}`);
|
|
107
|
-
console.log(` ${c.gray}${c.dim}───────────────────────────────────────────────────────${c.reset}`);
|
|
108
|
-
console.log("");
|
|
109
|
-
console.log(` ${c.brightYellow}${c.bold}Tips for getting started:${c.reset}`);
|
|
110
|
-
console.log(` ${c.white}1.${c.reset} ${c.gray}Run${c.reset} ${c.cyan}kramscan onboard${c.reset} ${c.gray}to configure your API keys.${c.reset}`);
|
|
111
|
-
console.log(` ${c.white}2.${c.reset} ${c.gray}Run${c.reset} ${c.cyan}kramscan scan <url>${c.reset} ${c.gray}to scan a target.${c.reset}`);
|
|
112
|
-
console.log(` ${c.white}3.${c.reset} ${c.gray}Run${c.reset} ${c.cyan}kramscan --help${c.reset} ${c.gray}for all commands.${c.reset}`);
|
|
113
|
-
console.log("");
|
|
114
|
-
}
|
|
115
71
|
const menuChoices = [
|
|
116
72
|
{ label: "Agent", value: "agent", description: "AI-powered interactive security assistant", icon: "🤖", status: "active" },
|
|
117
73
|
{ label: "Onboard", value: "onboard", description: "First-time setup wizard", icon: "⚡", status: "active" },
|
|
118
74
|
{ label: "Scan", value: "scan", description: "Scan a target URL for vulnerabilities", icon: "🔍", status: "active" },
|
|
75
|
+
{ label: "Dev", value: "dev", description: "Watch-mode scanning for localhost dev servers", icon: "🛠️", status: "active" },
|
|
76
|
+
{ label: "Gate", value: "gate", description: "CI/CD security quality gate", icon: "🚧", status: "active" },
|
|
119
77
|
{ label: "Analyze", value: "analyze", description: "Deep AI analysis of scan results", icon: "🧠", status: "active" },
|
|
120
78
|
{ label: "Report", value: "report", description: "Generate a professional report", icon: "📄", status: "active" },
|
|
121
79
|
{ label: "Config", value: "config", description: "View or edit your configuration", icon: "⚙️", status: "active" },
|
|
@@ -123,105 +81,124 @@ const menuChoices = [
|
|
|
123
81
|
{ label: "Exit", value: "exit", description: "Quit KramScan", icon: "👋", status: "active" },
|
|
124
82
|
];
|
|
125
83
|
async function showInteractiveMenu() {
|
|
126
|
-
printBanner();
|
|
127
|
-
printInfo();
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
: "";
|
|
146
|
-
if (isSelected) {
|
|
147
|
-
console.log(` ${c.brightCyan}${c.bold}❯ ${choice.icon} ${choice.label}${c.reset}${statusTag} ${c.dim}${c.gray}— ${choice.description}${c.reset}`);
|
|
148
|
-
}
|
|
149
|
-
else {
|
|
150
|
-
console.log(` ${choice.icon} ${c.white}${choice.label}${c.reset}${statusTag} ${c.dim}${c.gray}— ${choice.description}${c.reset}`);
|
|
151
|
-
}
|
|
152
|
-
});
|
|
84
|
+
(0, theme_1.printBanner)();
|
|
85
|
+
(0, theme_1.printInfo)();
|
|
86
|
+
const choices = menuChoices.map((choice) => ({
|
|
87
|
+
name: `${choice.icon} ${choice.label.padEnd(10)} - ${choice.description}${choice.status === "coming_soon" ? " [coming soon]" : ""}`,
|
|
88
|
+
value: choice.value,
|
|
89
|
+
disabled: choice.status === "coming_soon",
|
|
90
|
+
}));
|
|
91
|
+
const { action } = await inquirer_1.default.prompt([
|
|
92
|
+
{
|
|
93
|
+
type: "list",
|
|
94
|
+
name: "action",
|
|
95
|
+
message: theme_1.theme.cyan("What would you like to do?"),
|
|
96
|
+
choices,
|
|
97
|
+
pageSize: 10,
|
|
98
|
+
},
|
|
99
|
+
]);
|
|
100
|
+
if (action === "exit") {
|
|
101
|
+
console.log(theme_1.theme.gray("\n Goodbye! 👋\n"));
|
|
102
|
+
return;
|
|
153
103
|
}
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
// Enter
|
|
177
|
-
if (process.stdin.isTTY) {
|
|
178
|
-
process.stdin.setRawMode(false);
|
|
179
|
-
}
|
|
180
|
-
process.stdin.pause();
|
|
181
|
-
if (inputHandler) {
|
|
182
|
-
process.stdin.removeListener("data", inputHandler);
|
|
183
|
-
}
|
|
184
|
-
rl.close();
|
|
185
|
-
const selected = menuChoices[selectedIndex];
|
|
186
|
-
console.log("");
|
|
187
|
-
if (selected.value === "exit") {
|
|
188
|
-
console.log(` ${c.gray}${c.dim}Goodbye! 👋${c.reset}`);
|
|
189
|
-
console.log("");
|
|
190
|
-
resolve();
|
|
191
|
-
return;
|
|
192
|
-
}
|
|
193
|
-
if (selected.status === "coming_soon") {
|
|
194
|
-
console.log(` ${c.yellow}⚠ ${selected.label}${c.reset} ${c.gray}is coming soon. Stay tuned!${c.reset}`);
|
|
195
|
-
console.log(` ${c.gray}Run ${c.cyan}kramscan --help${c.gray} for available commands.${c.reset}`);
|
|
196
|
-
console.log("");
|
|
197
|
-
resolve();
|
|
198
|
-
return;
|
|
104
|
+
const selected = menuChoices.find((c) => c.value === action);
|
|
105
|
+
if (selected && selected.status === "coming_soon") {
|
|
106
|
+
console.log(theme_1.theme.yellow(`\n [!] ${selected.label} is coming soon. Stay tuned!`));
|
|
107
|
+
console.log(theme_1.theme.gray(` Run ${theme_1.theme.cyan("kramscan --help")} for available commands.\n`));
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
let args = [action];
|
|
111
|
+
// Specific handling for commands that need input
|
|
112
|
+
if (action === "scan") {
|
|
113
|
+
const { url } = await inquirer_1.default.prompt([
|
|
114
|
+
{
|
|
115
|
+
type: "input",
|
|
116
|
+
name: "url",
|
|
117
|
+
message: theme_1.theme.cyan("Enter the URL to scan:"),
|
|
118
|
+
validate: (input) => {
|
|
119
|
+
try {
|
|
120
|
+
new URL(input);
|
|
121
|
+
return true;
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
return "Please enter a valid URL (e.g., https://example.com)";
|
|
125
|
+
}
|
|
199
126
|
}
|
|
200
|
-
// Execute the selected command
|
|
201
|
-
console.log(` ${c.brightGreen}▶${c.reset} ${c.bold}Launching ${selected.label}...${c.reset}`);
|
|
202
|
-
console.log("");
|
|
203
|
-
// Re-run with the command argument
|
|
204
|
-
process.argv.push(selected.value);
|
|
205
|
-
const program = createProgram();
|
|
206
|
-
await program.parseAsync(process.argv);
|
|
207
|
-
resolve();
|
|
208
127
|
}
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
128
|
+
]);
|
|
129
|
+
args.push(url);
|
|
130
|
+
}
|
|
131
|
+
else if (action === "analyze" || action === "report") {
|
|
132
|
+
const { listScans } = await Promise.resolve().then(() => __importStar(require("./core/scan-index")));
|
|
133
|
+
const scans = await listScans(10);
|
|
134
|
+
if (scans.length > 0) {
|
|
135
|
+
const { scanFile } = await inquirer_1.default.prompt([
|
|
136
|
+
{
|
|
137
|
+
type: "list",
|
|
138
|
+
name: "scanFile",
|
|
139
|
+
message: theme_1.theme.cyan(`Select a scan to ${action}:`),
|
|
140
|
+
choices: [
|
|
141
|
+
...scans.map(s => ({
|
|
142
|
+
name: `${s.timestamp} - ${s.hostname} (${s.summary.total} findings)`,
|
|
143
|
+
value: s.jsonPath
|
|
144
|
+
})),
|
|
145
|
+
{ name: "Back to menu", value: "back" }
|
|
146
|
+
]
|
|
217
147
|
}
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
148
|
+
]);
|
|
149
|
+
if (scanFile === "back") {
|
|
150
|
+
return showInteractiveMenu();
|
|
221
151
|
}
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
152
|
+
args.push(scanFile);
|
|
153
|
+
}
|
|
154
|
+
else {
|
|
155
|
+
console.log(theme_1.theme.yellow(`\n [!] No recent scans found. Please run a scan first.\n`));
|
|
156
|
+
await new Promise(r => setTimeout(r, 1500));
|
|
157
|
+
return showInteractiveMenu();
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
console.log(theme_1.theme.green(`\n > Launching ${selected?.label || action}...\n`));
|
|
161
|
+
const program = createProgram();
|
|
162
|
+
try {
|
|
163
|
+
await program.parseAsync(["node", "kramscan", ...args]);
|
|
164
|
+
// After execution, ask if they want to go back to the menu
|
|
165
|
+
const { back } = await inquirer_1.default.prompt([
|
|
166
|
+
{
|
|
167
|
+
type: "confirm",
|
|
168
|
+
name: "back",
|
|
169
|
+
message: theme_1.theme.cyan("Return to main menu?"),
|
|
170
|
+
default: true
|
|
171
|
+
}
|
|
172
|
+
]);
|
|
173
|
+
if (back) {
|
|
174
|
+
return showInteractiveMenu();
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
catch (error) {
|
|
178
|
+
// Error handling is managed by the commands themselves or global handlers
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
async function showDirectCommandInput() {
|
|
182
|
+
(0, theme_1.printBanner)();
|
|
183
|
+
(0, theme_1.printInfo)();
|
|
184
|
+
const { command } = await inquirer_1.default.prompt([
|
|
185
|
+
{
|
|
186
|
+
type: "input",
|
|
187
|
+
name: "command",
|
|
188
|
+
message: theme_1.theme.cyan("Enter a command (e.g., 'scan https://example.com'):"),
|
|
189
|
+
filter: (input) => input.trim(),
|
|
190
|
+
},
|
|
191
|
+
]);
|
|
192
|
+
if (!command) {
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
const tokens = command.match(/(?:[^\s"]+|"[^"]*")+/g)?.map((token) => token.replace(/^"(.*)"$/, "$1")) ?? [];
|
|
196
|
+
const args = tokens[0]?.toLowerCase() === "kramscan" ? tokens.slice(1) : tokens;
|
|
197
|
+
if (args.length > 0) {
|
|
198
|
+
console.log("");
|
|
199
|
+
const program = createProgram();
|
|
200
|
+
await program.parseAsync(["node", "kramscan", ...args]);
|
|
201
|
+
}
|
|
225
202
|
}
|
|
226
203
|
// ─── Program Setup ─────────────────────────────────────────────────
|
|
227
204
|
function createProgram() {
|
|
@@ -229,7 +206,7 @@ function createProgram() {
|
|
|
229
206
|
program
|
|
230
207
|
.name("kramscan")
|
|
231
208
|
.description("KramScan — AI-powered web app security testing")
|
|
232
|
-
.version(CLI_VERSION)
|
|
209
|
+
.version(theme_1.CLI_VERSION)
|
|
233
210
|
.option("-v, --verbose", "Enable verbose output")
|
|
234
211
|
.option("--debug", "Enable debug mode")
|
|
235
212
|
.hook("preAction", (thisCommand) => {
|
|
@@ -245,12 +222,42 @@ function createProgram() {
|
|
|
245
222
|
(0, config_1.registerConfigCommand)(program);
|
|
246
223
|
(0, doctor_1.registerDoctorCommand)(program);
|
|
247
224
|
(0, agent_1.registerAgentCommand)(program);
|
|
225
|
+
(0, scans_1.registerScansCommand)(program);
|
|
226
|
+
(0, ai_1.registerAiCommand)(program);
|
|
227
|
+
(0, dev_1.registerDevCommand)(program);
|
|
228
|
+
(0, gate_1.registerGateCommand)(program);
|
|
229
|
+
// Version subcommand with detailed environment info
|
|
230
|
+
program
|
|
231
|
+
.command("version")
|
|
232
|
+
.description("Show detailed version and environment information")
|
|
233
|
+
.action(async () => {
|
|
234
|
+
const os = await Promise.resolve().then(() => __importStar(require("os")));
|
|
235
|
+
let aiProvider = "not configured";
|
|
236
|
+
try {
|
|
237
|
+
const { getConfig } = await Promise.resolve().then(() => __importStar(require("./core/config")));
|
|
238
|
+
const config = await getConfig();
|
|
239
|
+
if (config.ai.enabled) {
|
|
240
|
+
aiProvider = `${config.ai.provider} (${config.ai.defaultModel})`;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
catch {
|
|
244
|
+
// Config not available
|
|
245
|
+
}
|
|
246
|
+
console.log("");
|
|
247
|
+
console.log(theme_1.theme.brightWhite.bold("KramScan") + " " + theme_1.theme.cyan(`v${theme_1.CLI_VERSION}`));
|
|
248
|
+
console.log(theme_1.theme.gray("─".repeat(40)));
|
|
249
|
+
console.log(theme_1.theme.white(" Node.js: ") + theme_1.theme.cyan(process.version));
|
|
250
|
+
console.log(theme_1.theme.white(" Platform: ") + theme_1.theme.cyan(`${os.platform()} ${os.arch()}`));
|
|
251
|
+
console.log(theme_1.theme.white(" OS: ") + theme_1.theme.cyan(os.release()));
|
|
252
|
+
console.log(theme_1.theme.white(" AI Provider:") + " " + theme_1.theme.cyan(aiProvider));
|
|
253
|
+
console.log("");
|
|
254
|
+
});
|
|
248
255
|
return program;
|
|
249
256
|
}
|
|
250
257
|
// ─── Entry Point ───────────────────────────────────────────────────
|
|
251
258
|
async function run() {
|
|
252
259
|
const args = process.argv.slice(2);
|
|
253
|
-
// If no command is provided, show the interactive
|
|
260
|
+
// If no command is provided, show the interactive menu
|
|
254
261
|
if (args.length === 0) {
|
|
255
262
|
await showInteractiveMenu();
|
|
256
263
|
}
|
package/dist/commands/agent.js
CHANGED
|
@@ -58,6 +58,7 @@ function registerAgentCommand(program) {
|
|
|
58
58
|
skillRegistry.register(new agent_1.AnalyzeFindingsSkill());
|
|
59
59
|
skillRegistry.register(new agent_1.GenerateReportSkill());
|
|
60
60
|
skillRegistry.register(new agent_1.HealthCheckSkill());
|
|
61
|
+
skillRegistry.register(new agent_1.VerifyFindingSkill());
|
|
61
62
|
// Initialize orchestrator
|
|
62
63
|
const orchestrator = new agent_1.AgentOrchestrator(skillRegistry, {
|
|
63
64
|
enableConfirmation: options.confirm !== false,
|
|
@@ -86,67 +87,66 @@ function registerAgentCommand(program) {
|
|
|
86
87
|
});
|
|
87
88
|
}
|
|
88
89
|
async function runInteractiveMode(orchestrator) {
|
|
90
|
+
// Ensure we aren't in raw mode from other interactive flows.
|
|
91
|
+
if (process.stdin.isTTY) {
|
|
92
|
+
try {
|
|
93
|
+
process.stdin.setRawMode(false);
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
// ignore
|
|
97
|
+
}
|
|
98
|
+
}
|
|
89
99
|
const rl = readline.createInterface({
|
|
90
100
|
input: process.stdin,
|
|
91
101
|
output: process.stdout,
|
|
92
|
-
prompt: chalk_1.default.gray("You: "),
|
|
93
102
|
});
|
|
94
|
-
// Print welcome banner
|
|
95
103
|
printWelcomeBanner();
|
|
96
|
-
// Show available skills
|
|
97
104
|
printAvailableSkills(orchestrator);
|
|
105
|
+
// Share this readline interface with confirmations to avoid double-reading stdin.
|
|
106
|
+
orchestrator.setReadlineInterface(rl);
|
|
98
107
|
orchestrator.start();
|
|
99
108
|
console.log(chalk_1.default.gray("Type 'help' for commands or 'exit' to quit.\n"));
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
109
|
+
let closed = false;
|
|
110
|
+
rl.on('SIGINT', () => rl.close());
|
|
111
|
+
rl.on('close', () => { closed = true; });
|
|
112
|
+
const question = (prompt) => new Promise((resolve) => rl.question(prompt, resolve));
|
|
113
|
+
while (!closed) {
|
|
114
|
+
const input = await question(chalk_1.default.gray('You: '));
|
|
115
|
+
if (closed)
|
|
116
|
+
break;
|
|
105
117
|
const trimmed = input.trim();
|
|
106
|
-
if (!trimmed)
|
|
107
|
-
|
|
108
|
-
return;
|
|
109
|
-
}
|
|
110
|
-
// Handle special commands
|
|
118
|
+
if (!trimmed)
|
|
119
|
+
continue;
|
|
111
120
|
const commandResult = await handleSpecialCommand(trimmed, orchestrator, rl);
|
|
112
121
|
if (commandResult.handled) {
|
|
113
|
-
if (commandResult.shouldExit)
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
askQuestion();
|
|
117
|
-
return;
|
|
122
|
+
if (commandResult.shouldExit)
|
|
123
|
+
break;
|
|
124
|
+
continue;
|
|
118
125
|
}
|
|
119
|
-
|
|
120
|
-
console.log("");
|
|
126
|
+
console.log('');
|
|
121
127
|
try {
|
|
122
128
|
const response = await orchestrator.processUserMessage(trimmed);
|
|
123
|
-
|
|
124
|
-
console.log(chalk_1.default.bold.cyan("Agent:"));
|
|
129
|
+
console.log(chalk_1.default.bold.cyan('Agent:'));
|
|
125
130
|
console.log(response.message);
|
|
126
|
-
// Show tool execution summary if applicable
|
|
127
131
|
if (response.toolCalls && response.toolCalls.length > 0) {
|
|
128
|
-
console.log(
|
|
129
|
-
console.log(chalk_1.default.gray(
|
|
132
|
+
console.log('');
|
|
133
|
+
console.log(chalk_1.default.gray('Tools executed:'));
|
|
130
134
|
response.toolCalls.forEach((call) => {
|
|
131
135
|
const result = response.toolCallResults?.find((r) => r.toolCallId === call.id);
|
|
132
|
-
const icon = result?.success ? chalk_1.default.green(
|
|
133
|
-
console.log(chalk_1.default.gray(
|
|
136
|
+
const icon = result?.success ? chalk_1.default.green('OK') : chalk_1.default.red('X');
|
|
137
|
+
console.log(chalk_1.default.gray(' ' + icon + ' ' + call.name));
|
|
134
138
|
});
|
|
135
139
|
}
|
|
136
|
-
console.log(
|
|
140
|
+
console.log('');
|
|
137
141
|
}
|
|
138
142
|
catch (error) {
|
|
139
|
-
console.log(chalk_1.default.red(
|
|
140
|
-
console.log(
|
|
143
|
+
console.log(chalk_1.default.red('Error:'), error instanceof Error ? error.message : String(error));
|
|
144
|
+
console.log('');
|
|
141
145
|
}
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
await orchestrator.shutdown();
|
|
147
|
-
process.exit(0);
|
|
148
|
-
});
|
|
149
|
-
askQuestion();
|
|
146
|
+
}
|
|
147
|
+
console.log(chalk_1.default.gray('\nGoodbye!\n'));
|
|
148
|
+
await orchestrator.shutdown();
|
|
149
|
+
rl.close();
|
|
150
150
|
}
|
|
151
151
|
async function handleSpecialCommand(input, orchestrator, rl) {
|
|
152
152
|
const command = input.toLowerCase();
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.registerAiCommand = registerAiCommand;
|
|
7
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
8
|
+
const axios_1 = __importDefault(require("axios"));
|
|
9
|
+
const openai_1 = __importDefault(require("openai"));
|
|
10
|
+
const config_1 = require("../core/config");
|
|
11
|
+
const ai_client_1 = require("../core/ai-client");
|
|
12
|
+
const logger_1 = require("../utils/logger");
|
|
13
|
+
function getEnvApiKey(provider) {
|
|
14
|
+
const envVars = {
|
|
15
|
+
openai: process.env.OPENAI_API_KEY,
|
|
16
|
+
anthropic: process.env.ANTHROPIC_API_KEY,
|
|
17
|
+
gemini: process.env.GEMINI_API_KEY,
|
|
18
|
+
mistral: process.env.MISTRAL_API_KEY,
|
|
19
|
+
openrouter: process.env.OPENROUTER_API_KEY,
|
|
20
|
+
kimi: process.env.KIMI_API_KEY,
|
|
21
|
+
groq: process.env.GROQ_API_KEY,
|
|
22
|
+
};
|
|
23
|
+
return envVars[provider] || "";
|
|
24
|
+
}
|
|
25
|
+
function registerAiCommand(program) {
|
|
26
|
+
const ai = program.command("ai").description("AI helpers and diagnostics");
|
|
27
|
+
ai
|
|
28
|
+
.command("models")
|
|
29
|
+
.description("List available models for the configured AI provider")
|
|
30
|
+
.option("-n, --limit <number>", "How many models to show", "50")
|
|
31
|
+
.action(async (options) => {
|
|
32
|
+
const config = await (0, config_1.getConfig)();
|
|
33
|
+
if (!config.ai.enabled) {
|
|
34
|
+
logger_1.logger.error("AI analysis is disabled. Run 'kramscan onboard' to enable it.");
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
|
37
|
+
const provider = config.ai.provider;
|
|
38
|
+
const apiKey = config.ai.apiKey || getEnvApiKey(provider);
|
|
39
|
+
if (!apiKey) {
|
|
40
|
+
logger_1.logger.error(`No API key configured for ${provider}.`);
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
const limit = Number.parseInt(options.limit, 10);
|
|
44
|
+
const max = Number.isFinite(limit) ? limit : 50;
|
|
45
|
+
console.log("");
|
|
46
|
+
console.log(chalk_1.default.bold.cyan("Available Models"));
|
|
47
|
+
console.log(chalk_1.default.gray("-".repeat(60)));
|
|
48
|
+
console.log(chalk_1.default.gray("Provider:"), chalk_1.default.cyan(provider));
|
|
49
|
+
console.log(chalk_1.default.gray("Configured default model:"), chalk_1.default.white(config.ai.defaultModel));
|
|
50
|
+
console.log("");
|
|
51
|
+
if (provider === "gemini") {
|
|
52
|
+
const resp = await axios_1.default.get("https://generativelanguage.googleapis.com/v1beta/models", { params: { key: apiKey } });
|
|
53
|
+
const models = resp.data?.models || [];
|
|
54
|
+
const usable = models.filter((m) => (m.supportedGenerationMethods || []).includes("generateContent"));
|
|
55
|
+
const show = usable.slice(0, max);
|
|
56
|
+
for (const m of show) {
|
|
57
|
+
const id = m.name?.startsWith("models/") ? m.name.slice("models/".length) : m.name;
|
|
58
|
+
const dn = m.displayName ? ` (${m.displayName})` : "";
|
|
59
|
+
console.log(chalk_1.default.white(id) + chalk_1.default.gray(dn));
|
|
60
|
+
}
|
|
61
|
+
if (usable.length === 0) {
|
|
62
|
+
logger_1.logger.warn("No models returned with generateContent support.");
|
|
63
|
+
}
|
|
64
|
+
else if (usable.length > show.length) {
|
|
65
|
+
console.log("");
|
|
66
|
+
console.log(chalk_1.default.gray(`... and ${usable.length - show.length} more`));
|
|
67
|
+
}
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
if (provider === "openai" || provider === "openrouter" || provider === "kimi" || provider === "groq") {
|
|
71
|
+
const baseURL = provider === "openrouter"
|
|
72
|
+
? "https://openrouter.ai/api/v1"
|
|
73
|
+
: provider === "kimi"
|
|
74
|
+
? "https://api.moonshot.cn/v1"
|
|
75
|
+
: provider === "groq"
|
|
76
|
+
? "https://api.groq.com/openai/v1"
|
|
77
|
+
: undefined;
|
|
78
|
+
const client = new openai_1.default(baseURL ? { apiKey, baseURL } : { apiKey });
|
|
79
|
+
const resp = await client.models.list();
|
|
80
|
+
const ids = (resp.data || []).map((m) => m.id).slice(0, max);
|
|
81
|
+
ids.forEach((id) => console.log(chalk_1.default.white(id)));
|
|
82
|
+
if ((resp.data || []).length > ids.length) {
|
|
83
|
+
console.log("");
|
|
84
|
+
console.log(chalk_1.default.gray(`... and ${(resp.data || []).length - ids.length} more`));
|
|
85
|
+
}
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
logger_1.logger.warn(`Model listing not implemented for provider: ${provider}`);
|
|
89
|
+
console.log(chalk_1.default.gray("Tip: run 'kramscan doctor' or try a small 'kramscan analyze' to validate the model."));
|
|
90
|
+
});
|
|
91
|
+
ai
|
|
92
|
+
.command("test")
|
|
93
|
+
.description("Test the configured AI provider/model with a small request")
|
|
94
|
+
.action(async () => {
|
|
95
|
+
console.log("");
|
|
96
|
+
console.log(chalk_1.default.bold.cyan("AI Connectivity Test"));
|
|
97
|
+
console.log(chalk_1.default.gray("-".repeat(60)));
|
|
98
|
+
console.log("");
|
|
99
|
+
const spinner = logger_1.logger.spinner("Sending test request...");
|
|
100
|
+
try {
|
|
101
|
+
const client = await (0, ai_client_1.createAIClient)();
|
|
102
|
+
const resp = await client.analyze("Say 'OK' and nothing else.");
|
|
103
|
+
spinner.succeed("AI request succeeded");
|
|
104
|
+
console.log(chalk_1.default.gray("Response:"), chalk_1.default.white(resp.content.trim() || "(empty)"));
|
|
105
|
+
}
|
|
106
|
+
catch (error) {
|
|
107
|
+
spinner.fail("AI request failed");
|
|
108
|
+
logger_1.logger.error(error.message);
|
|
109
|
+
process.exit(1);
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
}
|