kramscan 0.1.0 → 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.
Files changed (105) hide show
  1. package/README.md +392 -87
  2. package/dist/agent/confirmation.d.ts +38 -0
  3. package/dist/agent/confirmation.js +210 -0
  4. package/dist/agent/context.d.ts +81 -0
  5. package/dist/agent/context.js +227 -0
  6. package/dist/agent/index.d.ts +10 -0
  7. package/dist/agent/index.js +32 -0
  8. package/dist/agent/orchestrator.d.ts +63 -0
  9. package/dist/agent/orchestrator.js +370 -0
  10. package/dist/agent/prompts/system.d.ts +6 -0
  11. package/dist/agent/prompts/system.js +116 -0
  12. package/dist/agent/skill-registry.d.ts +78 -0
  13. package/dist/agent/skill-registry.js +202 -0
  14. package/dist/agent/skills/analyze-findings.d.ts +22 -0
  15. package/dist/agent/skills/analyze-findings.js +191 -0
  16. package/dist/agent/skills/generate-report.d.ts +26 -0
  17. package/dist/agent/skills/generate-report.js +436 -0
  18. package/dist/agent/skills/health-check.d.ts +28 -0
  19. package/dist/agent/skills/health-check.js +344 -0
  20. package/dist/agent/skills/index.d.ts +9 -0
  21. package/dist/agent/skills/index.js +17 -0
  22. package/dist/agent/skills/verify-finding.d.ts +17 -0
  23. package/dist/agent/skills/verify-finding.js +91 -0
  24. package/dist/agent/skills/web-scan.d.ts +22 -0
  25. package/dist/agent/skills/web-scan.js +203 -0
  26. package/dist/agent/types.d.ts +141 -0
  27. package/dist/agent/types.js +16 -0
  28. package/dist/cli.d.ts +3 -0
  29. package/dist/cli.js +176 -139
  30. package/dist/commands/agent.d.ts +6 -0
  31. package/dist/commands/agent.js +250 -0
  32. package/dist/commands/ai.d.ts +2 -0
  33. package/dist/commands/ai.js +112 -0
  34. package/dist/commands/analyze.js +104 -55
  35. package/dist/commands/config.js +63 -37
  36. package/dist/commands/doctor.js +22 -17
  37. package/dist/commands/onboard.js +190 -125
  38. package/dist/commands/report.js +69 -77
  39. package/dist/commands/scan.js +261 -81
  40. package/dist/commands/scans.d.ts +2 -0
  41. package/dist/commands/scans.js +51 -0
  42. package/dist/core/ai-client.d.ts +7 -2
  43. package/dist/core/ai-client.js +231 -20
  44. package/dist/core/ai-payloads.d.ts +17 -0
  45. package/dist/core/ai-payloads.js +54 -0
  46. package/dist/core/config-schema.d.ts +197 -0
  47. package/dist/core/config-schema.js +68 -0
  48. package/dist/core/config-schema.test.d.ts +1 -0
  49. package/dist/core/config-schema.test.js +151 -0
  50. package/dist/core/config.d.ts +17 -36
  51. package/dist/core/config.js +261 -20
  52. package/dist/core/errors.d.ts +71 -0
  53. package/dist/core/errors.js +162 -0
  54. package/dist/core/scan-index.d.ts +19 -0
  55. package/dist/core/scan-index.js +52 -0
  56. package/dist/core/scan-storage.d.ts +11 -0
  57. package/dist/core/scan-storage.js +69 -0
  58. package/dist/core/scanner.d.ts +101 -4
  59. package/dist/core/scanner.js +432 -63
  60. package/dist/core/vulnerability-detector.d.ts +18 -2
  61. package/dist/core/vulnerability-detector.js +349 -38
  62. package/dist/core/vulnerability-detector.test.d.ts +1 -0
  63. package/dist/core/vulnerability-detector.test.js +210 -0
  64. package/dist/index.js +3 -0
  65. package/dist/plugins/PluginManager.d.ts +27 -0
  66. package/dist/plugins/PluginManager.js +166 -0
  67. package/dist/plugins/index.d.ts +7 -0
  68. package/dist/plugins/index.js +19 -0
  69. package/dist/plugins/types.d.ts +55 -0
  70. package/dist/plugins/types.js +25 -0
  71. package/dist/plugins/vulnerabilities/CSRFPlugin.d.ts +8 -0
  72. package/dist/plugins/vulnerabilities/CSRFPlugin.js +34 -0
  73. package/dist/plugins/vulnerabilities/SQLInjectionPlugin.d.ts +11 -0
  74. package/dist/plugins/vulnerabilities/SQLInjectionPlugin.js +109 -0
  75. package/dist/plugins/vulnerabilities/SecurityHeadersPlugin.d.ts +11 -0
  76. package/dist/plugins/vulnerabilities/SecurityHeadersPlugin.js +63 -0
  77. package/dist/plugins/vulnerabilities/SensitiveDataPlugin.d.ts +9 -0
  78. package/dist/plugins/vulnerabilities/SensitiveDataPlugin.js +32 -0
  79. package/dist/plugins/vulnerabilities/XSSPlugin.d.ts +15 -0
  80. package/dist/plugins/vulnerabilities/XSSPlugin.js +81 -0
  81. package/dist/reports/PdfGenerator.d.ts +36 -0
  82. package/dist/reports/PdfGenerator.js +379 -0
  83. package/dist/utils/logger.d.ts +33 -1
  84. package/dist/utils/logger.js +127 -8
  85. package/dist/utils/theme.d.ts +55 -0
  86. package/dist/utils/theme.js +195 -0
  87. package/package.json +27 -6
  88. package/dist/core/executor.d.ts +0 -2
  89. package/dist/core/executor.js +0 -74
  90. package/dist/core/logger.d.ts +0 -12
  91. package/dist/core/logger.js +0 -51
  92. package/dist/core/registry.d.ts +0 -3
  93. package/dist/core/registry.js +0 -35
  94. package/dist/core/storage.d.ts +0 -4
  95. package/dist/core/storage.js +0 -39
  96. package/dist/core/types.d.ts +0 -24
  97. package/dist/core/types.js +0 -2
  98. package/dist/skills/base.d.ts +0 -8
  99. package/dist/skills/base.js +0 -6
  100. package/dist/skills/builtin.d.ts +0 -4
  101. package/dist/skills/builtin.js +0 -71
  102. package/dist/skills/loader.d.ts +0 -2
  103. package/dist/skills/loader.js +0 -27
  104. package/dist/skills/types.d.ts +0 -46
  105. package/dist/skills/types.js +0 -2
@@ -1,124 +1,304 @@
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.registerScanCommand = registerScanCommand;
7
- const chalk_1 = __importDefault(require("chalk"));
40
+ const commander_1 = require("commander");
8
41
  const scanner_1 = require("../core/scanner");
42
+ const scan_index_1 = require("../core/scan-index");
43
+ const scan_storage_1 = require("../core/scan-storage");
44
+ const config_1 = require("../core/config");
45
+ const PdfGenerator_1 = require("../reports/PdfGenerator");
46
+ const theme_1 = require("../utils/theme");
9
47
  const logger_1 = require("../utils/logger");
10
48
  const promises_1 = __importDefault(require("fs/promises"));
11
49
  const path_1 = __importDefault(require("path"));
12
- const os_1 = __importDefault(require("os"));
13
50
  function registerScanCommand(program) {
14
51
  program
15
52
  .command("scan <url>")
16
53
  .description("Scan a target URL for vulnerabilities")
17
- .option("-d, --depth <number>", "Crawl depth", "2")
18
- .option("-t, --timeout <ms>", "Request timeout", "30000")
54
+ .option("--profile <name>", "Scan profile: quick|balanced|deep", "balanced")
55
+ .option("-d, --depth <number>", "Crawl depth (overrides profile)")
56
+ .option("-t, --timeout <ms>", "Request timeout (overrides profile)")
57
+ .option("--max-pages <number>", "Maximum pages to crawl (overrides profile)")
58
+ .option("--max-links-per-page <number>", "Maximum links to follow per page (overrides profile)")
59
+ .option("--include <regex...>", "Only include URLs matching these regex patterns")
60
+ .option("--exclude <regex...>", "Exclude URLs matching these regex patterns")
61
+ .option("--no-pdf", "Disable automatic PDF report generation")
62
+ .option("--json", "Output scan results as JSON to stdout (CI/CD mode)")
19
63
  .option("-o, --output <file>", "Save results to file")
20
64
  .option("--headless", "Run in headless mode", true)
65
+ .option("--no-plugins", "Disable plugin-based scanning (use legacy mode)")
21
66
  .action(async (url, options) => {
22
- console.log("");
23
- console.log(chalk_1.default.bold.cyan("🔍 Starting Security Scan"));
24
- console.log(chalk_1.default.gray("".repeat(50)));
25
- console.log("");
67
+ const jsonMode = options.json === true;
68
+ if (!jsonMode) {
69
+ console.log("");
70
+ console.log(theme_1.theme.brand.bold("🔍 Starting Security Scan"));
71
+ console.log(theme_1.theme.gray("─".repeat(50)));
72
+ console.log("");
73
+ }
26
74
  // Validate URL
27
75
  try {
28
76
  new URL(url);
29
77
  }
30
78
  catch (error) {
31
- logger_1.logger.error(`Invalid URL: ${url}`);
79
+ if (jsonMode) {
80
+ console.log(JSON.stringify({ error: `Invalid URL: ${url}` }));
81
+ }
82
+ else {
83
+ logger_1.logger.error(`Invalid URL: ${url}`);
84
+ }
32
85
  process.exit(1);
33
86
  }
34
- const spinner = logger_1.logger.spinner("Initializing scanner...");
87
+ const spinner = jsonMode ? null : logger_1.logger.spinner("Initializing scanner...");
35
88
  try {
36
- const scanner = new scanner_1.Scanner();
37
- spinner.text = `Scanning ${url}...`;
38
- const result = await scanner.scan(url, {
39
- depth: parseInt(options.depth),
40
- timeout: parseInt(options.timeout),
41
- headless: options.headless,
89
+ const profile = String(options.profile || "balanced").toLowerCase();
90
+ const defaults = config_1.scanProfiles[profile] || config_1.scanProfiles.balanced;
91
+ const parsedDepth = Number.parseInt(options.depth ?? String(defaults.depth), 10);
92
+ const parsedTimeout = Number.parseInt(options.timeout ?? String(defaults.timeout), 10);
93
+ const parsedMaxPages = Number.parseInt(options.maxPages ?? String(defaults.maxPages), 10);
94
+ const parsedMaxLinksPerPage = Number.parseInt(options.maxLinksPerPage ?? String(defaults.maxLinksPerPage), 10);
95
+ if (!Number.isFinite(parsedDepth) || parsedDepth < 1 || parsedDepth > 5) {
96
+ throw new Error("Depth must be a number between 1 and 5.");
97
+ }
98
+ if (!Number.isFinite(parsedTimeout) || parsedTimeout < 1000) {
99
+ throw new Error("Timeout must be a positive number (milliseconds).");
100
+ }
101
+ if (!Number.isFinite(parsedMaxPages) || parsedMaxPages < 1) {
102
+ throw new Error("max-pages must be a positive number.");
103
+ }
104
+ if (!Number.isFinite(parsedMaxLinksPerPage) || parsedMaxLinksPerPage < 1) {
105
+ throw new Error("max-links-per-page must be a positive number.");
106
+ }
107
+ // Display scan estimate
108
+ if (!jsonMode) {
109
+ const estimateMap = {
110
+ quick: "~15–30s",
111
+ balanced: "~30–90s",
112
+ deep: "~2–5min",
113
+ };
114
+ const estimate = estimateMap[profile] || estimateMap.balanced;
115
+ console.log(theme_1.theme.gray(` ⏱ Estimated duration: ${estimate} (${profile} profile)`));
116
+ console.log("");
117
+ }
118
+ const scanner = new scanner_1.Scanner(options.plugins !== false);
119
+ // Set up event listeners for progress feedback
120
+ let currentStage = "initializing";
121
+ let vulnerabilitiesFound = 0;
122
+ scanner.on("scan:start", () => {
123
+ if (spinner)
124
+ spinner.text = `Starting scan of ${url}...`;
125
+ currentStage = "scanning";
126
+ });
127
+ scanner.on("crawl:page", (data) => {
128
+ if (spinner)
129
+ spinner.text = `Crawling: ${data.url} (${data.crawledCount}/${data.maxPages})`;
130
+ currentStage = "crawling";
131
+ });
132
+ scanner.on("form:test", (data) => {
133
+ if (spinner)
134
+ spinner.text = `Testing forms on ${data.url} (${data.formCount} forms)...`;
135
+ currentStage = "testing forms";
42
136
  });
43
- spinner.succeed("Scan complete!");
137
+ scanner.on("vuln:found", (data) => {
138
+ vulnerabilitiesFound++;
139
+ if (spinner) {
140
+ spinner.stopAndPersist({
141
+ symbol: theme_1.theme.warning("⚠️"),
142
+ text: `Found ${data.vulnerability.severity} vulnerability: ${data.vulnerability.title}`
143
+ });
144
+ spinner.start(`Continuing scan (${vulnerabilitiesFound} vulns found)...`);
145
+ }
146
+ });
147
+ scanner.on("scan:complete", () => {
148
+ if (spinner)
149
+ spinner.text = "Finalizing scan results...";
150
+ });
151
+ scanner.on("crawl:error", (data) => {
152
+ if (!jsonMode)
153
+ logger_1.logger.warn(`Failed to crawl ${data.url}: ${data.error.message}`);
154
+ });
155
+ const scanOptions = {
156
+ depth: parsedDepth,
157
+ timeout: parsedTimeout,
158
+ headless: options.headless,
159
+ maxPages: parsedMaxPages,
160
+ maxLinksPerPage: parsedMaxLinksPerPage,
161
+ include: options.include,
162
+ exclude: options.exclude,
163
+ profile,
164
+ };
165
+ const result = await scanner.scan(url, scanOptions);
166
+ if (spinner)
167
+ spinner.succeed("Scan complete!");
44
168
  // Save results
45
- const scanDir = path_1.default.join(os_1.default.homedir(), ".kramscan", "scans");
46
- await promises_1.default.mkdir(scanDir, { recursive: true });
169
+ const scanDir = await (0, scan_storage_1.ensureScansDirectory)();
47
170
  const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
48
171
  const filename = options.output || `scan-${timestamp}.json`;
49
172
  const filepath = path_1.default.isAbsolute(filename)
50
173
  ? filename
51
174
  : path_1.default.join(scanDir, filename);
52
- await promises_1.default.writeFile(filepath, JSON.stringify(result, null, 2));
53
- // Display summary
54
- console.log("");
55
- console.log(chalk_1.default.bold("📊 Scan Summary"));
56
- console.log(chalk_1.default.gray("─".repeat(50)));
57
- console.log("");
58
- console.log(chalk_1.default.white("Target:"), chalk_1.default.cyan(result.target));
59
- console.log(chalk_1.default.white("Duration:"), chalk_1.default.cyan(`${(result.duration / 1000).toFixed(2)}s`));
60
- console.log(chalk_1.default.white("URLs Crawled:"), chalk_1.default.cyan(result.metadata.crawledUrls));
61
- console.log(chalk_1.default.white("Forms Tested:"), chalk_1.default.cyan(result.metadata.testedForms));
62
- console.log(chalk_1.default.white("Requests Made:"), chalk_1.default.cyan(result.metadata.requestsMade));
63
- console.log("");
64
- // Vulnerability summary
65
- console.log(chalk_1.default.bold("🛡️ Vulnerabilities Found"));
66
- console.log(chalk_1.default.gray("─".repeat(50)));
67
- console.log("");
68
- const { summary } = result;
69
- if (summary.total === 0) {
70
- console.log(chalk_1.default.green("✓ No vulnerabilities found!"));
175
+ // Include error data in JSON
176
+ const scanErrors = scanner.getScanErrors();
177
+ const pluginErrors = scanner.getPluginErrors();
178
+ const resultWithErrors = {
179
+ ...result,
180
+ errors: {
181
+ scan: scanErrors,
182
+ plugins: Object.fromEntries(pluginErrors),
183
+ },
184
+ };
185
+ await promises_1.default.writeFile(filepath, JSON.stringify(resultWithErrors, null, 2));
186
+ // In JSON mode, output the result to stdout and exit
187
+ if (jsonMode) {
188
+ console.log(JSON.stringify(resultWithErrors, null, 2));
189
+ return;
71
190
  }
72
- else {
73
- if (summary.critical > 0)
74
- console.log(chalk_1.default.red(` ${summary.critical} Critical`), chalk_1.default.gray("- Immediate action required"));
75
- if (summary.high > 0)
76
- console.log(chalk_1.default.red(` ${summary.high} High`), chalk_1.default.gray("- Should be fixed soon"));
77
- if (summary.medium > 0)
78
- console.log(chalk_1.default.yellow(` ${summary.medium} Medium`), chalk_1.default.gray("- Fix when possible"));
79
- if (summary.low > 0)
80
- console.log(chalk_1.default.blue(` ${summary.low} Low`), chalk_1.default.gray("- Minor issues"));
81
- if (summary.info > 0)
82
- console.log(chalk_1.default.gray(` ${summary.info} Info`), chalk_1.default.gray("- Informational"));
191
+ let pdfPath = null;
192
+ if (options.pdf !== false) {
193
+ const pdfSpinner = logger_1.logger.spinner("Generating PDF report...");
194
+ try {
195
+ const pdfData = {
196
+ scanResult: result,
197
+ scanErrors,
198
+ pluginErrors,
199
+ };
200
+ pdfPath = await PdfGenerator_1.pdfGenerator.generate(pdfData);
201
+ pdfSpinner.succeed("PDF report generated!");
202
+ }
203
+ catch (error) {
204
+ pdfSpinner.fail("PDF report generation failed");
205
+ logger_1.logger.warn(`Could not generate PDF automatically: ${error.message}`);
206
+ }
83
207
  }
84
- console.log("");
85
- console.log(chalk_1.default.gray("Results saved to:"), chalk_1.default.white(filepath));
86
- console.log("");
87
- // Show top vulnerabilities
88
- if (result.vulnerabilities.length > 0) {
89
- console.log(chalk_1.default.bold("🔴 Top Findings"));
90
- console.log(chalk_1.default.gray("─".repeat(50)));
208
+ try {
209
+ const target = new URL(result.target);
210
+ await (0, scan_index_1.addScanToIndex)({
211
+ target: result.target,
212
+ hostname: target.hostname || "unknown",
213
+ timestamp: result.timestamp,
214
+ jsonPath: filepath,
215
+ pdfPath: pdfPath || undefined,
216
+ summary: result.summary,
217
+ });
218
+ }
219
+ catch (error) {
220
+ logger_1.logger.debug(`Failed to update scan index: ${error.message}`);
221
+ }
222
+ // Display summary using theme
223
+ (0, theme_1.displayScanSummary)({
224
+ target: result.target,
225
+ duration: result.duration,
226
+ metadata: result.metadata,
227
+ summary: result.summary,
228
+ vulnerabilities: result.vulnerabilities,
229
+ filepath,
230
+ pdfPath,
231
+ });
232
+ // Display any scan errors
233
+ const scanErrorsList = scanner.getScanErrors();
234
+ const pluginErrorsMap = scanner.getPluginErrors();
235
+ if (scanErrorsList.length > 0 || pluginErrorsMap.size > 0) {
236
+ console.log(theme_1.theme.warning("⚠️ Some URLs/plugins encountered errors:"));
237
+ if (scanErrorsList.length > 0) {
238
+ console.log(theme_1.theme.yellow(" Crawl Errors:"));
239
+ for (const error of scanErrorsList.slice(0, 5)) {
240
+ console.log(theme_1.theme.gray(` - ${error.url}: ${error.error}`));
241
+ }
242
+ if (scanErrorsList.length > 5) {
243
+ console.log(theme_1.theme.gray(` ... and ${scanErrorsList.length - 5} more`));
244
+ }
245
+ }
246
+ if (pluginErrorsMap.size > 0) {
247
+ console.log(theme_1.theme.yellow(" Plugin Errors:"));
248
+ for (const [pluginName, errors] of pluginErrorsMap) {
249
+ console.log(theme_1.theme.gray(` ${pluginName}:`));
250
+ for (const error of errors.slice(0, 3)) {
251
+ console.log(theme_1.theme.gray(` - ${error.url}: ${error.error}`));
252
+ }
253
+ if (errors.length > 3) {
254
+ console.log(theme_1.theme.gray(` ... and ${errors.length - 3} more`));
255
+ }
256
+ }
257
+ const totalPluginErrors = Array.from(pluginErrorsMap.values()).reduce((sum, errs) => sum + errs.length, 0);
258
+ if (totalPluginErrors > 10) {
259
+ console.log(theme_1.theme.gray(` Total plugin errors: ${totalPluginErrors}`));
260
+ }
261
+ }
91
262
  console.log("");
92
- const topVulns = result.vulnerabilities
93
- .sort((a, b) => {
94
- const severityOrder = { critical: 0, high: 1, medium: 2, low: 3, info: 4 };
95
- return severityOrder[a.severity] - severityOrder[b.severity];
96
- })
97
- .slice(0, 5);
98
- for (const vuln of topVulns) {
99
- const severityColor = vuln.severity === "critical" || vuln.severity === "high"
100
- ? chalk_1.default.red
101
- : vuln.severity === "medium"
102
- ? chalk_1.default.yellow
103
- : chalk_1.default.blue;
104
- console.log(severityColor(`[${vuln.severity.toUpperCase()}]`), chalk_1.default.bold(vuln.title));
105
- console.log(chalk_1.default.gray(` ${vuln.url}`));
106
- console.log(chalk_1.default.white(` ${vuln.description}`));
107
- console.log("");
263
+ }
264
+ // Add "What's Next" interactive prompt
265
+ if (!jsonMode) {
266
+ const inquirer = (await Promise.resolve().then(() => __importStar(require("inquirer")))).default;
267
+ const { nextAction } = await inquirer.prompt([
268
+ {
269
+ type: "list",
270
+ name: "nextAction",
271
+ message: theme_1.theme.cyan("Scan complete! What would you like to do next?"),
272
+ choices: [
273
+ { name: "🧠 Analyze findings with AI", value: "analyze" },
274
+ { name: "📄 Generate a professional report", value: "report" },
275
+ { name: "👋 Exit to main menu", value: "exit" }
276
+ ]
277
+ }
278
+ ]);
279
+ if (nextAction === "analyze") {
280
+ const { registerAnalyzeCommand } = await Promise.resolve().then(() => __importStar(require("./analyze")));
281
+ const analyzeProgram = new commander_1.Command();
282
+ registerAnalyzeCommand(analyzeProgram);
283
+ await analyzeProgram.parseAsync(["node", "kramscan", "analyze", filepath]);
108
284
  }
109
- if (result.vulnerabilities.length > 5) {
110
- console.log(chalk_1.default.gray(` ... and ${result.vulnerabilities.length - 5} more`));
111
- console.log("");
285
+ else if (nextAction === "report") {
286
+ const { registerReportCommand } = await Promise.resolve().then(() => __importStar(require("./report")));
287
+ const reportProgram = new commander_1.Command();
288
+ registerReportCommand(reportProgram);
289
+ await reportProgram.parseAsync(["node", "kramscan", "report", filepath]);
112
290
  }
113
291
  }
114
- console.log(chalk_1.default.cyan("💡 Next steps:"));
115
- console.log(chalk_1.default.white(` 1. Run ${chalk_1.default.cyan(`kramscan analyze ${filepath}`)} for AI-powered insights`));
116
- console.log(chalk_1.default.white(` 2. Run ${chalk_1.default.cyan(`kramscan report ${filepath}`)} to generate a report`));
117
- console.log("");
118
292
  }
119
293
  catch (error) {
120
- spinner.fail("Scan failed");
121
- logger_1.logger.error(error.message);
294
+ if (spinner)
295
+ spinner.fail("Scan failed");
296
+ if (jsonMode) {
297
+ console.log(JSON.stringify({ error: error.message }));
298
+ }
299
+ else {
300
+ logger_1.logger.error(error.message);
301
+ }
122
302
  process.exit(1);
123
303
  }
124
304
  });
@@ -0,0 +1,2 @@
1
+ import { Command } from "commander";
2
+ export declare function registerScansCommand(program: Command): void;
@@ -0,0 +1,51 @@
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.registerScansCommand = registerScansCommand;
7
+ const chalk_1 = __importDefault(require("chalk"));
8
+ const scan_index_1 = require("../core/scan-index");
9
+ const logger_1 = require("../utils/logger");
10
+ function registerScansCommand(program) {
11
+ const scans = program.command("scans").description("Manage saved scans");
12
+ scans
13
+ .command("list")
14
+ .description("List recent scans")
15
+ .option("-n, --limit <number>", "How many scans to show", "20")
16
+ .action(async (options) => {
17
+ const limit = Number.parseInt(options.limit, 10);
18
+ const entries = await (0, scan_index_1.listScans)(Number.isFinite(limit) ? limit : 20);
19
+ if (entries.length === 0) {
20
+ logger_1.logger.warn("No scans found in index yet. Run 'kramscan scan <url>' first.");
21
+ return;
22
+ }
23
+ console.log("");
24
+ console.log(chalk_1.default.bold.cyan("Recent Scans"));
25
+ console.log(chalk_1.default.gray("-".repeat(60)));
26
+ for (const entry of entries) {
27
+ console.log(chalk_1.default.white(entry.timestamp), chalk_1.default.gray("-"), chalk_1.default.cyan(entry.hostname));
28
+ console.log(chalk_1.default.gray(" JSON:"), chalk_1.default.white(entry.jsonPath));
29
+ if (entry.pdfPath) {
30
+ console.log(chalk_1.default.gray(" PDF :"), chalk_1.default.white(entry.pdfPath));
31
+ }
32
+ console.log(chalk_1.default.gray(" Findings:"), chalk_1.default.white(`${entry.summary.total} total (${entry.summary.critical}C ${entry.summary.high}H ${entry.summary.medium}M ${entry.summary.low}L ${entry.summary.info}I)`));
33
+ console.log("");
34
+ }
35
+ });
36
+ scans
37
+ .command("latest")
38
+ .description("Show the latest scan paths")
39
+ .action(async () => {
40
+ const latest = await (0, scan_index_1.getLatestScan)();
41
+ if (!latest) {
42
+ logger_1.logger.warn("No scans found in index yet. Run 'kramscan scan <url>' first.");
43
+ return;
44
+ }
45
+ console.log(chalk_1.default.bold("Latest scan:"));
46
+ console.log(chalk_1.default.gray("Target:"), chalk_1.default.cyan(latest.target));
47
+ console.log(chalk_1.default.gray("Time :"), chalk_1.default.white(latest.timestamp));
48
+ console.log(chalk_1.default.gray("JSON :"), chalk_1.default.white(latest.jsonPath));
49
+ console.log(chalk_1.default.gray("PDF :"), chalk_1.default.white(latest.pdfPath || "N/A"));
50
+ });
51
+ }
@@ -1,3 +1,4 @@
1
+ import { ScanResult } from "./vulnerability-detector";
1
2
  export interface AIResponse {
2
3
  content: string;
3
4
  usage?: {
@@ -7,6 +8,10 @@ export interface AIResponse {
7
8
  };
8
9
  }
9
10
  export interface AIClient {
10
- analyze(prompt: string): Promise<AIResponse>;
11
+ analyze(prompt: string, systemPrompt?: string): Promise<AIResponse>;
11
12
  }
12
- export declare function createAIClient(): AIClient;
13
+ export declare function createAIClient(): Promise<AIClient>;
14
+ /**
15
+ * Generates a high-level executive summary of the scan results using AI.
16
+ */
17
+ export declare function generateExecutiveSummary(client: AIClient, result: ScanResult): Promise<string>;