kramscan 0.1.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 (49) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +87 -0
  3. package/bin/kramscan.js +4 -0
  4. package/bin/openscan.js +4 -0
  5. package/dist/cli.d.ts +1 -0
  6. package/dist/cli.js +225 -0
  7. package/dist/commands/analyze.d.ts +2 -0
  8. package/dist/commands/analyze.js +115 -0
  9. package/dist/commands/config.d.ts +2 -0
  10. package/dist/commands/config.js +139 -0
  11. package/dist/commands/doctor.d.ts +2 -0
  12. package/dist/commands/doctor.js +234 -0
  13. package/dist/commands/onboard.d.ts +2 -0
  14. package/dist/commands/onboard.js +146 -0
  15. package/dist/commands/report.d.ts +2 -0
  16. package/dist/commands/report.js +225 -0
  17. package/dist/commands/scan.d.ts +2 -0
  18. package/dist/commands/scan.js +125 -0
  19. package/dist/core/ai-client.d.ts +12 -0
  20. package/dist/core/ai-client.js +89 -0
  21. package/dist/core/config.d.ts +45 -0
  22. package/dist/core/config.js +146 -0
  23. package/dist/core/executor.d.ts +2 -0
  24. package/dist/core/executor.js +74 -0
  25. package/dist/core/logger.d.ts +12 -0
  26. package/dist/core/logger.js +51 -0
  27. package/dist/core/registry.d.ts +3 -0
  28. package/dist/core/registry.js +35 -0
  29. package/dist/core/scanner.d.ts +24 -0
  30. package/dist/core/scanner.js +197 -0
  31. package/dist/core/storage.d.ts +4 -0
  32. package/dist/core/storage.js +39 -0
  33. package/dist/core/types.d.ts +24 -0
  34. package/dist/core/types.js +2 -0
  35. package/dist/core/vulnerability-detector.d.ts +47 -0
  36. package/dist/core/vulnerability-detector.js +150 -0
  37. package/dist/index.d.ts +1 -0
  38. package/dist/index.js +7 -0
  39. package/dist/skills/base.d.ts +8 -0
  40. package/dist/skills/base.js +6 -0
  41. package/dist/skills/builtin.d.ts +4 -0
  42. package/dist/skills/builtin.js +71 -0
  43. package/dist/skills/loader.d.ts +2 -0
  44. package/dist/skills/loader.js +27 -0
  45. package/dist/skills/types.d.ts +46 -0
  46. package/dist/skills/types.js +2 -0
  47. package/dist/utils/logger.d.ts +9 -0
  48. package/dist/utils/logger.js +34 -0
  49. package/package.json +62 -0
@@ -0,0 +1,125 @@
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.registerScanCommand = registerScanCommand;
7
+ const chalk_1 = __importDefault(require("chalk"));
8
+ const scanner_1 = require("../core/scanner");
9
+ const logger_1 = require("../utils/logger");
10
+ const promises_1 = __importDefault(require("fs/promises"));
11
+ const path_1 = __importDefault(require("path"));
12
+ const os_1 = __importDefault(require("os"));
13
+ function registerScanCommand(program) {
14
+ program
15
+ .command("scan <url>")
16
+ .description("Scan a target URL for vulnerabilities")
17
+ .option("-d, --depth <number>", "Crawl depth", "2")
18
+ .option("-t, --timeout <ms>", "Request timeout", "30000")
19
+ .option("-o, --output <file>", "Save results to file")
20
+ .option("--headless", "Run in headless mode", true)
21
+ .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("");
26
+ // Validate URL
27
+ try {
28
+ new URL(url);
29
+ }
30
+ catch (error) {
31
+ logger_1.logger.error(`Invalid URL: ${url}`);
32
+ process.exit(1);
33
+ }
34
+ const spinner = logger_1.logger.spinner("Initializing scanner...");
35
+ 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,
42
+ });
43
+ spinner.succeed("Scan complete!");
44
+ // Save results
45
+ const scanDir = path_1.default.join(os_1.default.homedir(), ".kramscan", "scans");
46
+ await promises_1.default.mkdir(scanDir, { recursive: true });
47
+ const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
48
+ const filename = options.output || `scan-${timestamp}.json`;
49
+ const filepath = path_1.default.isAbsolute(filename)
50
+ ? filename
51
+ : 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!"));
71
+ }
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"));
83
+ }
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)));
91
+ 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("");
108
+ }
109
+ if (result.vulnerabilities.length > 5) {
110
+ console.log(chalk_1.default.gray(` ... and ${result.vulnerabilities.length - 5} more`));
111
+ console.log("");
112
+ }
113
+ }
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
+ }
119
+ catch (error) {
120
+ spinner.fail("Scan failed");
121
+ logger_1.logger.error(error.message);
122
+ process.exit(1);
123
+ }
124
+ });
125
+ }
@@ -0,0 +1,12 @@
1
+ export interface AIResponse {
2
+ content: string;
3
+ usage?: {
4
+ promptTokens: number;
5
+ completionTokens: number;
6
+ totalTokens: number;
7
+ };
8
+ }
9
+ export interface AIClient {
10
+ analyze(prompt: string): Promise<AIResponse>;
11
+ }
12
+ export declare function createAIClient(): AIClient;
@@ -0,0 +1,89 @@
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.createAIClient = createAIClient;
7
+ const openai_1 = __importDefault(require("openai"));
8
+ const sdk_1 = __importDefault(require("@anthropic-ai/sdk"));
9
+ const config_1 = require("./config");
10
+ class OpenAIClient {
11
+ client;
12
+ model;
13
+ constructor(apiKey, model) {
14
+ this.client = new openai_1.default({ apiKey });
15
+ this.model = model;
16
+ }
17
+ async analyze(prompt) {
18
+ const response = await this.client.chat.completions.create({
19
+ model: this.model,
20
+ messages: [
21
+ {
22
+ role: "system",
23
+ content: "You are a security expert analyzing web application vulnerabilities. Provide detailed, actionable insights.",
24
+ },
25
+ { role: "user", content: prompt },
26
+ ],
27
+ temperature: 0.3,
28
+ });
29
+ const content = response.choices[0]?.message?.content || "";
30
+ return {
31
+ content,
32
+ usage: {
33
+ promptTokens: response.usage?.prompt_tokens || 0,
34
+ completionTokens: response.usage?.completion_tokens || 0,
35
+ totalTokens: response.usage?.total_tokens || 0,
36
+ },
37
+ };
38
+ }
39
+ }
40
+ class AnthropicClient {
41
+ client;
42
+ model;
43
+ constructor(apiKey, model) {
44
+ this.client = new sdk_1.default({ apiKey });
45
+ this.model = model;
46
+ }
47
+ async analyze(prompt) {
48
+ const response = await this.client.messages.create({
49
+ model: this.model,
50
+ max_tokens: 4096,
51
+ messages: [
52
+ {
53
+ role: "user",
54
+ content: prompt,
55
+ },
56
+ ],
57
+ system: "You are a security expert analyzing web application vulnerabilities. Provide detailed, actionable insights.",
58
+ });
59
+ const content = response.content[0]?.type === "text" ? response.content[0].text : "";
60
+ return {
61
+ content,
62
+ usage: {
63
+ promptTokens: response.usage.input_tokens,
64
+ completionTokens: response.usage.output_tokens,
65
+ totalTokens: response.usage.input_tokens + response.usage.output_tokens,
66
+ },
67
+ };
68
+ }
69
+ }
70
+ function createAIClient() {
71
+ const config = (0, config_1.getConfig)();
72
+ if (!config.ai.enabled) {
73
+ throw new Error("AI analysis is not enabled. Run 'kramscan onboard' first.");
74
+ }
75
+ const provider = config.ai.provider;
76
+ const apiKey = config.ai.apiKey;
77
+ const model = config.ai.defaultModel;
78
+ if (!apiKey) {
79
+ throw new Error(`No API key configured for ${provider}. Run 'kramscan onboard' to set it up.`);
80
+ }
81
+ switch (provider) {
82
+ case "openai":
83
+ return new OpenAIClient(apiKey, model);
84
+ case "anthropic":
85
+ return new AnthropicClient(apiKey, model);
86
+ default:
87
+ throw new Error(`Unsupported AI provider: ${provider}`);
88
+ }
89
+ }
@@ -0,0 +1,45 @@
1
+ export type AiProviderName = "openai" | "anthropic";
2
+ export type ReportFormat = "word" | "txt" | "json";
3
+ export interface Config {
4
+ ai: {
5
+ provider: AiProviderName;
6
+ apiKey: string;
7
+ defaultModel: string;
8
+ enabled: boolean;
9
+ };
10
+ scan: {
11
+ defaultTimeout: number;
12
+ maxThreads: number;
13
+ userAgent: string;
14
+ followRedirects: boolean;
15
+ verifySSL: boolean;
16
+ rateLimitPerSecond: number;
17
+ strictScope: boolean;
18
+ };
19
+ report: {
20
+ defaultFormat: ReportFormat;
21
+ companyName: string;
22
+ includeScreenshots: boolean;
23
+ severityThreshold: "info" | "low" | "medium" | "high" | "critical";
24
+ };
25
+ skills: Record<string, {
26
+ enabled: boolean;
27
+ timeout?: number;
28
+ }>;
29
+ proxy?: string;
30
+ }
31
+ declare class ConfigStore {
32
+ private configPath;
33
+ private data;
34
+ constructor(projectName: string, defaultConfig: Config);
35
+ get store(): Config;
36
+ get(key: string): unknown;
37
+ set(key: string, value: unknown): void;
38
+ private save;
39
+ }
40
+ export declare function getConfigStore(): ConfigStore;
41
+ export declare function getConfig(): Config;
42
+ export declare function getConfigValue(key: string): unknown;
43
+ export declare function setConfigValue(key: string, value: unknown): void;
44
+ export declare function setConfig(config: Config): void;
45
+ export {};
@@ -0,0 +1,146 @@
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
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.getConfigStore = getConfigStore;
37
+ exports.getConfig = getConfig;
38
+ exports.getConfigValue = getConfigValue;
39
+ exports.setConfigValue = setConfigValue;
40
+ exports.setConfig = setConfig;
41
+ const fs = __importStar(require("fs"));
42
+ const path = __importStar(require("path"));
43
+ const os = __importStar(require("os"));
44
+ const defaults = {
45
+ ai: {
46
+ provider: "openai",
47
+ apiKey: "",
48
+ defaultModel: "gpt-4",
49
+ enabled: false,
50
+ },
51
+ scan: {
52
+ defaultTimeout: 60,
53
+ maxThreads: 5,
54
+ userAgent: "KramScan/0.1.0",
55
+ followRedirects: true,
56
+ verifySSL: true,
57
+ rateLimitPerSecond: 5,
58
+ strictScope: true
59
+ },
60
+ report: {
61
+ defaultFormat: "word",
62
+ companyName: "Your Company",
63
+ includeScreenshots: false,
64
+ severityThreshold: "low"
65
+ },
66
+ skills: {
67
+ sqli: { enabled: true, timeout: 120 },
68
+ xss: { enabled: true, timeout: 90 },
69
+ headers: { enabled: true },
70
+ csrf: { enabled: true },
71
+ idor: { enabled: true },
72
+ jwt: { enabled: true }
73
+ }
74
+ };
75
+ // Simple JSON-file config store (replaces ESM-only 'conf' package)
76
+ class ConfigStore {
77
+ configPath;
78
+ data;
79
+ constructor(projectName, defaultConfig) {
80
+ const configDir = path.join(os.homedir(), `.${projectName}`);
81
+ if (!fs.existsSync(configDir)) {
82
+ fs.mkdirSync(configDir, { recursive: true });
83
+ }
84
+ this.configPath = path.join(configDir, "config.json");
85
+ if (fs.existsSync(this.configPath)) {
86
+ try {
87
+ const raw = fs.readFileSync(this.configPath, "utf-8");
88
+ this.data = { ...defaultConfig, ...JSON.parse(raw) };
89
+ }
90
+ catch {
91
+ this.data = { ...defaultConfig };
92
+ }
93
+ }
94
+ else {
95
+ this.data = { ...defaultConfig };
96
+ }
97
+ }
98
+ get store() {
99
+ return this.data;
100
+ }
101
+ get(key) {
102
+ const keys = key.split(".");
103
+ let current = this.data;
104
+ for (const k of keys) {
105
+ if (current && typeof current === "object" && k in current) {
106
+ current = current[k];
107
+ }
108
+ else {
109
+ return undefined;
110
+ }
111
+ }
112
+ return current;
113
+ }
114
+ set(key, value) {
115
+ const keys = key.split(".");
116
+ let current = this.data;
117
+ for (let i = 0; i < keys.length - 1; i++) {
118
+ if (!(keys[i] in current) || typeof current[keys[i]] !== "object") {
119
+ current[keys[i]] = {};
120
+ }
121
+ current = current[keys[i]];
122
+ }
123
+ current[keys[keys.length - 1]] = value;
124
+ this.save();
125
+ }
126
+ save() {
127
+ fs.writeFileSync(this.configPath, JSON.stringify(this.data, null, 2), "utf-8");
128
+ }
129
+ }
130
+ const store = new ConfigStore("kramscan", defaults);
131
+ function getConfigStore() {
132
+ return store;
133
+ }
134
+ function getConfig() {
135
+ return store.store;
136
+ }
137
+ function getConfigValue(key) {
138
+ return store.get(key);
139
+ }
140
+ function setConfigValue(key, value) {
141
+ store.set(key, value);
142
+ }
143
+ function setConfig(config) {
144
+ Object.assign(store.store, config);
145
+ store.save();
146
+ }
@@ -0,0 +1,2 @@
1
+ import { ScanOptions, ScanResult } from "./types";
2
+ export declare function executeScan(targetUrl: string, options: ScanOptions): Promise<ScanResult>;
@@ -0,0 +1,74 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.executeScan = executeScan;
4
+ const scanner_1 = require("./scanner");
5
+ const builtin_1 = require("../skills/builtin");
6
+ const registry_1 = require("./registry");
7
+ const logger_1 = require("./logger");
8
+ async function executeScan(targetUrl, options) {
9
+ const logger = (0, logger_1.createLogger)();
10
+ const startedAt = new Date().toISOString();
11
+ const skills = (0, registry_1.listSkills)();
12
+ const selectedSkills = skills
13
+ .map((skill) => skill.id)
14
+ .filter((skillId) => {
15
+ if (options.skills && options.skills.length > 0) {
16
+ return options.skills.includes(skillId);
17
+ }
18
+ return true;
19
+ })
20
+ .filter((skillId) => {
21
+ if (options.excludeSkills && options.excludeSkills.length > 0) {
22
+ return !options.excludeSkills.includes(skillId);
23
+ }
24
+ return true;
25
+ });
26
+ const scanner = await (0, scanner_1.createScannerClient)({
27
+ userAgent: "OpenScan/0.1.0",
28
+ timeoutSeconds: options.timeoutSeconds,
29
+ proxy: options.proxy,
30
+ verifySSL: true,
31
+ followRedirects: true,
32
+ enableBrowser: options.deep
33
+ });
34
+ const findings = [];
35
+ for (const skillId of selectedSkills) {
36
+ const runner = builtin_1.builtinSkillRunners[skillId];
37
+ if (!runner) {
38
+ logger.warn(`No runner available for skill ${skillId}.`);
39
+ continue;
40
+ }
41
+ try {
42
+ const result = await runner({
43
+ targetUrl,
44
+ timeoutSeconds: options.timeoutSeconds,
45
+ logger,
46
+ http: {
47
+ get: async (url) => {
48
+ const response = await scanner.http.get(url);
49
+ return {
50
+ data: response.data,
51
+ headers: response.headers
52
+ };
53
+ }
54
+ }
55
+ });
56
+ findings.push(...result.findings);
57
+ }
58
+ catch (error) {
59
+ logger.error(`Skill ${skillId} failed: ${error.message}`);
60
+ }
61
+ }
62
+ await scanner.close();
63
+ const finishedAt = new Date().toISOString();
64
+ const result = {
65
+ id: `scan-${Date.now()}`,
66
+ startedAt,
67
+ finishedAt,
68
+ target: { url: targetUrl },
69
+ options,
70
+ skillsRun: selectedSkills,
71
+ findings
72
+ };
73
+ return result;
74
+ }
@@ -0,0 +1,12 @@
1
+ export interface Logger {
2
+ info(message: string): void;
3
+ warn(message: string): void;
4
+ error(message: string): void;
5
+ success(message: string): void;
6
+ spinner(message: string): {
7
+ stop: () => void;
8
+ succeed: (msg?: string) => void;
9
+ fail: (msg?: string) => void;
10
+ };
11
+ }
12
+ export declare function createLogger(): Logger;
@@ -0,0 +1,51 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createLogger = createLogger;
4
+ // ANSI color helpers (replaces ESM-only chalk)
5
+ const c = {
6
+ reset: "\x1b[0m",
7
+ cyan: "\x1b[36m",
8
+ yellow: "\x1b[33m",
9
+ red: "\x1b[31m",
10
+ green: "\x1b[32m",
11
+ gray: "\x1b[90m",
12
+ bold: "\x1b[1m",
13
+ };
14
+ function createLogger() {
15
+ return {
16
+ info(message) {
17
+ console.log(`${c.cyan}ℹ ${message}${c.reset}`);
18
+ },
19
+ warn(message) {
20
+ console.log(`${c.yellow}⚠ ${message}${c.reset}`);
21
+ },
22
+ error(message) {
23
+ console.error(`${c.red}✖ ${message}${c.reset}`);
24
+ },
25
+ success(message) {
26
+ console.log(`${c.green}✔ ${message}${c.reset}`);
27
+ },
28
+ spinner(message) {
29
+ const frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
30
+ let i = 0;
31
+ const id = setInterval(() => {
32
+ process.stdout.write(`\r${c.cyan}${frames[i % frames.length]}${c.reset} ${message}`);
33
+ i++;
34
+ }, 80);
35
+ return {
36
+ stop() {
37
+ clearInterval(id);
38
+ process.stdout.write("\r\x1b[K"); // Clear line
39
+ },
40
+ succeed(msg) {
41
+ clearInterval(id);
42
+ console.log(`\r${c.green}✔${c.reset} ${msg || message}`);
43
+ },
44
+ fail(msg) {
45
+ clearInterval(id);
46
+ console.log(`\r${c.red}✖${c.reset} ${msg || message}`);
47
+ },
48
+ };
49
+ },
50
+ };
51
+ }
@@ -0,0 +1,3 @@
1
+ import { SkillMetadata } from "../skills/types";
2
+ export declare function listSkills(): SkillMetadata[];
3
+ export declare function getSkillMetadata(skillId: string): SkillMetadata | undefined;
@@ -0,0 +1,35 @@
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.listSkills = listSkills;
7
+ exports.getSkillMetadata = getSkillMetadata;
8
+ const fs_1 = __importDefault(require("fs"));
9
+ const path_1 = __importDefault(require("path"));
10
+ const loader_1 = require("../skills/loader");
11
+ function getSkillsRoot() {
12
+ return path_1.default.resolve(process.cwd(), "skills");
13
+ }
14
+ function listSkills() {
15
+ const skillsRoot = getSkillsRoot();
16
+ if (!fs_1.default.existsSync(skillsRoot)) {
17
+ return [];
18
+ }
19
+ const entries = fs_1.default.readdirSync(skillsRoot, { withFileTypes: true });
20
+ const skills = [];
21
+ for (const entry of entries) {
22
+ if (!entry.isDirectory()) {
23
+ continue;
24
+ }
25
+ const skillDir = path_1.default.join(skillsRoot, entry.name);
26
+ const meta = (0, loader_1.loadSkillMetadata)(skillDir);
27
+ if (meta) {
28
+ skills.push(meta);
29
+ }
30
+ }
31
+ return skills;
32
+ }
33
+ function getSkillMetadata(skillId) {
34
+ return listSkills().find((skill) => skill.id === skillId);
35
+ }
@@ -0,0 +1,24 @@
1
+ import { ScanResult } from "./vulnerability-detector";
2
+ export interface ScanOptions {
3
+ depth?: number;
4
+ timeout?: number;
5
+ headless?: boolean;
6
+ }
7
+ export declare class Scanner {
8
+ private browser;
9
+ private detector;
10
+ private visitedUrls;
11
+ private crawledUrls;
12
+ private testedForms;
13
+ private requestsMade;
14
+ constructor();
15
+ initialize(options?: ScanOptions): Promise<void>;
16
+ scan(targetUrl: string, options?: ScanOptions): Promise<ScanResult>;
17
+ private crawl;
18
+ private testForms;
19
+ private testXSS;
20
+ private testSQLi;
21
+ private buildTestUrl;
22
+ private extractLinks;
23
+ close(): Promise<void>;
24
+ }