@jishankai/solid-cli 1.0.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 +21 -0
- package/README.md +276 -0
- package/config/default.json +79 -0
- package/package.json +60 -0
- package/src/Orchestrator.js +482 -0
- package/src/agents/BaseAgent.js +35 -0
- package/src/agents/BlockchainAgent.js +453 -0
- package/src/agents/DeFiSecurityAgent.js +257 -0
- package/src/agents/NetworkAgent.js +341 -0
- package/src/agents/PermissionAgent.js +192 -0
- package/src/agents/PersistenceAgent.js +361 -0
- package/src/agents/ProcessAgent.js +572 -0
- package/src/agents/ResourceAgent.js +217 -0
- package/src/agents/SystemAgent.js +173 -0
- package/src/config/ConfigManager.js +446 -0
- package/src/index.js +629 -0
- package/src/llm/LLMAnalyzer.js +705 -0
- package/src/logging/Logger.js +352 -0
- package/src/report/ReportManager.js +445 -0
- package/src/report/generators/MarkdownGenerator.js +173 -0
- package/src/report/generators/PDFGenerator.js +616 -0
- package/src/report/templates/report.hbs +465 -0
- package/src/report/utils/formatter.js +426 -0
- package/src/report/utils/sanitizer.js +275 -0
- package/src/utils/commander.js +42 -0
- package/src/utils/signature.js +121 -0
|
@@ -0,0 +1,482 @@
|
|
|
1
|
+
import { ResourceAgent } from './agents/ResourceAgent.js';
|
|
2
|
+
import { SystemAgent } from './agents/SystemAgent.js';
|
|
3
|
+
import { PersistenceAgent } from './agents/PersistenceAgent.js';
|
|
4
|
+
import { ProcessAgent } from './agents/ProcessAgent.js';
|
|
5
|
+
import { NetworkAgent } from './agents/NetworkAgent.js';
|
|
6
|
+
import { PermissionAgent } from './agents/PermissionAgent.js';
|
|
7
|
+
import { BlockchainAgent } from './agents/BlockchainAgent.js';
|
|
8
|
+
import { DeFiSecurityAgent } from './agents/DeFiSecurityAgent.js';
|
|
9
|
+
import { executeShellCommand } from './utils/commander.js';
|
|
10
|
+
import { log } from './logging/Logger.js';
|
|
11
|
+
import ora from 'ora';
|
|
12
|
+
import chalk from 'chalk';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Orchestrator - Coordinates all agents and aggregates results
|
|
16
|
+
*/
|
|
17
|
+
export class Orchestrator {
|
|
18
|
+
constructor(options = {}) {
|
|
19
|
+
this.enableGeoLookup = Boolean(options.enableGeoLookup);
|
|
20
|
+
this.geoLookupLimit = options.geoLookupLimit || 10;
|
|
21
|
+
this.analysisDepth = options.analysisDepth || 'comprehensive'; // 'fast', 'comprehensive', 'deep'
|
|
22
|
+
this.parallelExecution = options.parallelExecution !== false;
|
|
23
|
+
this.maxParallelAgents = options.maxParallelAgents || 3;
|
|
24
|
+
this.agents = {};
|
|
25
|
+
this.results = null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Initialize base agents for Phase 1 analysis
|
|
30
|
+
*/
|
|
31
|
+
initializeBaseAgents() {
|
|
32
|
+
return {
|
|
33
|
+
resource: new ResourceAgent(),
|
|
34
|
+
system: new SystemAgent(),
|
|
35
|
+
persistence: new PersistenceAgent(),
|
|
36
|
+
process: new ProcessAgent(),
|
|
37
|
+
network: new NetworkAgent({
|
|
38
|
+
enableGeoLookup: this.enableGeoLookup,
|
|
39
|
+
geoLookupLimit: this.geoLookupLimit
|
|
40
|
+
}),
|
|
41
|
+
permission: new PermissionAgent()
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Initialize conditional agents for extended analysis
|
|
47
|
+
*/
|
|
48
|
+
initializeConditionalAgents() {
|
|
49
|
+
return {
|
|
50
|
+
blockchain: new BlockchainAgent(),
|
|
51
|
+
defi: new DeFiSecurityAgent()
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Check if blockchain indicators are present in initial results
|
|
57
|
+
*/
|
|
58
|
+
hasBlockchainIndicators(initialResults) {
|
|
59
|
+
return this.extractBlockchainIndicators(initialResults).length > 0;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Extract specific blockchain indicators
|
|
64
|
+
*/
|
|
65
|
+
extractBlockchainIndicators(initialResults) {
|
|
66
|
+
const indicators = [];
|
|
67
|
+
|
|
68
|
+
// Check process findings for blockchain indicators
|
|
69
|
+
const processResult = initialResults.process;
|
|
70
|
+
if (processResult && processResult.findings) {
|
|
71
|
+
const blockchainKeywords = [
|
|
72
|
+
'bitcoin', 'ethereum', 'crypto', 'blockchain', 'wallet', 'mining',
|
|
73
|
+
'metamask', 'phantom', 'coinbase', 'binance', 'uniswap', 'defi'
|
|
74
|
+
];
|
|
75
|
+
|
|
76
|
+
for (const finding of processResult.findings) {
|
|
77
|
+
const searchText = `${finding.command || ''} ${finding.program || ''} ${finding.description || ''}`.toLowerCase();
|
|
78
|
+
const matchedKeywords = blockchainKeywords.filter(keyword => searchText.includes(keyword));
|
|
79
|
+
|
|
80
|
+
if (matchedKeywords.length > 0) {
|
|
81
|
+
indicators.push({
|
|
82
|
+
type: 'process',
|
|
83
|
+
source: finding,
|
|
84
|
+
keywords: matchedKeywords
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Check network connections for blockchain domains
|
|
91
|
+
const networkResult = initialResults.network;
|
|
92
|
+
if (networkResult && networkResult.findings) {
|
|
93
|
+
const blockchainDomains = [
|
|
94
|
+
'etherscan', 'uniswap', 'opensea', 'pancakeswap', 'curve',
|
|
95
|
+
'compound', 'aave', 'sushiswap', '1inch', 'metamask'
|
|
96
|
+
];
|
|
97
|
+
|
|
98
|
+
for (const finding of networkResult.findings) {
|
|
99
|
+
if (finding.remoteAddress) {
|
|
100
|
+
const domain = finding.remoteAddress.toLowerCase();
|
|
101
|
+
const matchedDomains = blockchainDomains.filter(d => domain.includes(d));
|
|
102
|
+
|
|
103
|
+
if (matchedDomains.length > 0) {
|
|
104
|
+
indicators.push({
|
|
105
|
+
type: 'network',
|
|
106
|
+
source: finding,
|
|
107
|
+
domains: matchedDomains
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return indicators;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Check if extended forensics analysis should be triggered
|
|
119
|
+
*/
|
|
120
|
+
needsExtendedForensics(initialResults) {
|
|
121
|
+
const summary = this.generateSummary(initialResults);
|
|
122
|
+
|
|
123
|
+
// Trigger extended analysis if high risk findings or many medium risk findings
|
|
124
|
+
if ((summary?.highRiskFindings || 0) > 0) return true;
|
|
125
|
+
if ((summary?.mediumRiskFindings || 0) >= 5) return true;
|
|
126
|
+
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Run unified adaptive analysis
|
|
132
|
+
*/
|
|
133
|
+
async runAnalysis() {
|
|
134
|
+
console.log(chalk.cyan('\n🚀 Starting Unified Security Analysis...\n'));
|
|
135
|
+
|
|
136
|
+
const results = {};
|
|
137
|
+
const analysisPhases = [];
|
|
138
|
+
|
|
139
|
+
// Phase 1: Base agents always run
|
|
140
|
+
console.log(chalk.blue('Phase 1: Core Security Analysis'));
|
|
141
|
+
const baseAgents = this.initializeBaseAgents();
|
|
142
|
+
let totalAgents = Object.keys(baseAgents).length;
|
|
143
|
+
let completedAgents = 0;
|
|
144
|
+
|
|
145
|
+
if (this.parallelExecution) {
|
|
146
|
+
// Run agents in parallel with limit
|
|
147
|
+
const agentEntries = Object.entries(baseAgents);
|
|
148
|
+
const chunks = [];
|
|
149
|
+
|
|
150
|
+
for (let i = 0; i < agentEntries.length; i += this.maxParallelAgents) {
|
|
151
|
+
chunks.push(agentEntries.slice(i, i + this.maxParallelAgents));
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
for (const chunk of chunks) {
|
|
155
|
+
const chunkPromises = chunk.map(async ([key, agent]) => {
|
|
156
|
+
const spinner = ora(`Running ${agent.name}...`).start();
|
|
157
|
+
const startTime = Date.now();
|
|
158
|
+
|
|
159
|
+
log.agentStart(agent.name, { parallel: true });
|
|
160
|
+
|
|
161
|
+
try {
|
|
162
|
+
const result = await agent.analyze();
|
|
163
|
+
const duration = Date.now() - startTime;
|
|
164
|
+
|
|
165
|
+
results[key] = result;
|
|
166
|
+
log.agentComplete(agent.name, result, duration);
|
|
167
|
+
|
|
168
|
+
const normalizedRisk = (result.overallRisk || 'unknown').toLowerCase();
|
|
169
|
+
const riskColor = this.getRiskColor(normalizedRisk);
|
|
170
|
+
completedAgents += 1;
|
|
171
|
+
spinner.succeed(`${agent.name} completed - ${completedAgents}/${totalAgents} - Risk: ${riskColor(normalizedRisk.toUpperCase())}`);
|
|
172
|
+
return result;
|
|
173
|
+
} catch (error) {
|
|
174
|
+
const duration = Date.now() - startTime;
|
|
175
|
+
|
|
176
|
+
results[key] = {
|
|
177
|
+
agent: agent.name,
|
|
178
|
+
error: error.message,
|
|
179
|
+
overallRisk: 'unknown'
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
log.agentError(agent.name, error, duration);
|
|
183
|
+
completedAgents += 1;
|
|
184
|
+
spinner.fail(`${agent.name} failed - ${completedAgents}/${totalAgents}: ${error.message}`);
|
|
185
|
+
return results[key];
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
await Promise.all(chunkPromises);
|
|
190
|
+
}
|
|
191
|
+
} else {
|
|
192
|
+
// Run sequentially
|
|
193
|
+
for (const [key, agent] of Object.entries(baseAgents)) {
|
|
194
|
+
const spinner = ora(`Running ${agent.name}...`).start();
|
|
195
|
+
const startTime = Date.now();
|
|
196
|
+
|
|
197
|
+
log.agentStart(agent.name, { parallel: false });
|
|
198
|
+
|
|
199
|
+
try {
|
|
200
|
+
const result = await agent.analyze();
|
|
201
|
+
const duration = Date.now() - startTime;
|
|
202
|
+
|
|
203
|
+
results[key] = result;
|
|
204
|
+
log.agentComplete(agent.name, result, duration);
|
|
205
|
+
|
|
206
|
+
const normalizedRisk = (result.overallRisk || 'unknown').toLowerCase();
|
|
207
|
+
const riskColor = this.getRiskColor(normalizedRisk);
|
|
208
|
+
completedAgents += 1;
|
|
209
|
+
spinner.succeed(`${agent.name} completed - ${completedAgents}/${totalAgents} - Risk: ${riskColor(normalizedRisk.toUpperCase())}`);
|
|
210
|
+
} catch (error) {
|
|
211
|
+
const duration = Date.now() - startTime;
|
|
212
|
+
|
|
213
|
+
results[key] = {
|
|
214
|
+
agent: agent.name,
|
|
215
|
+
error: error.message,
|
|
216
|
+
overallRisk: 'unknown'
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
log.agentError(agent.name, error, duration);
|
|
220
|
+
completedAgents += 1;
|
|
221
|
+
spinner.fail(`${agent.name} failed - ${completedAgents}/${totalAgents}: ${error.message}`);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
analysisPhases.push({
|
|
227
|
+
phase: 'Core Security Analysis',
|
|
228
|
+
agents: Object.keys(baseAgents),
|
|
229
|
+
completed: true
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
// Phase 2: Adaptive extension based on findings
|
|
233
|
+
console.log(chalk.blue('\nPhase 2: Adaptive Analysis'));
|
|
234
|
+
|
|
235
|
+
const extendedPhases = [];
|
|
236
|
+
|
|
237
|
+
// Check for blockchain indicators
|
|
238
|
+
if (this.hasBlockchainIndicators(results)) {
|
|
239
|
+
console.log(chalk.yellow('🔗 Blockchain indicators detected - activating blockchain analysis'));
|
|
240
|
+
extendedPhases.push('Blockchain Security Analysis');
|
|
241
|
+
|
|
242
|
+
const indicators = this.extractBlockchainIndicators(results);
|
|
243
|
+
log.blockchainDetection(indicators);
|
|
244
|
+
|
|
245
|
+
const conditionalAgents = this.initializeConditionalAgents();
|
|
246
|
+
totalAgents += Object.keys(conditionalAgents).length;
|
|
247
|
+
|
|
248
|
+
for (const [key, agent] of Object.entries(conditionalAgents)) {
|
|
249
|
+
const spinner = ora(`Running ${agent.name}...`).start();
|
|
250
|
+
const startTime = Date.now();
|
|
251
|
+
|
|
252
|
+
log.agentStart(agent.name, { triggered: 'blockchain_detection' });
|
|
253
|
+
|
|
254
|
+
try {
|
|
255
|
+
const result = await agent.analyze();
|
|
256
|
+
const duration = Date.now() - startTime;
|
|
257
|
+
|
|
258
|
+
results[key] = result;
|
|
259
|
+
log.agentComplete(agent.name, result, duration);
|
|
260
|
+
|
|
261
|
+
const normalizedRisk = (result.overallRisk || 'unknown').toLowerCase();
|
|
262
|
+
const riskColor = this.getRiskColor(normalizedRisk);
|
|
263
|
+
completedAgents += 1;
|
|
264
|
+
spinner.succeed(`${agent.name} completed - ${completedAgents}/${totalAgents} - Risk: ${riskColor(normalizedRisk.toUpperCase())}`);
|
|
265
|
+
} catch (error) {
|
|
266
|
+
const duration = Date.now() - startTime;
|
|
267
|
+
|
|
268
|
+
results[key] = {
|
|
269
|
+
agent: agent.name,
|
|
270
|
+
error: error.message,
|
|
271
|
+
overallRisk: 'unknown'
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
log.agentError(agent.name, error, duration);
|
|
275
|
+
completedAgents += 1;
|
|
276
|
+
spinner.fail(`${agent.name} failed - ${completedAgents}/${totalAgents}: ${error.message}`);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
} else {
|
|
280
|
+
console.log(chalk.gray(' No blockchain indicators detected - skipping blockchain analysis'));
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Check for extended forensics need
|
|
284
|
+
if (this.needsExtendedForensics(results)) {
|
|
285
|
+
console.log(chalk.yellow('🔍 Risk indicators detected - enabling extended analysis'));
|
|
286
|
+
extendedPhases.push('Extended Forensics Analysis');
|
|
287
|
+
|
|
288
|
+
// Future: Add extended forensics logic here
|
|
289
|
+
console.log(chalk.gray(' Extended forensics capabilities will be added in future version'));
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
analysisPhases.push({
|
|
293
|
+
phase: 'Adaptive Analysis',
|
|
294
|
+
extensions: extendedPhases,
|
|
295
|
+
completed: true
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
// Phase 3: Integration and correlation
|
|
299
|
+
console.log(chalk.blue('\nPhase 3: Result Integration'));
|
|
300
|
+
const correlatedResults = this.correlateFindings(results);
|
|
301
|
+
this.updateProgress = () => {};
|
|
302
|
+
console.log(chalk.green('✅ Analysis completed successfully!'));
|
|
303
|
+
|
|
304
|
+
this.results = {
|
|
305
|
+
mode: 'unified',
|
|
306
|
+
analysisDepth: this.analysisDepth,
|
|
307
|
+
timestamp: new Date().toISOString(),
|
|
308
|
+
hostname: await this.getHostname(),
|
|
309
|
+
osVersion: await this.getOSVersion(),
|
|
310
|
+
agents: correlatedResults,
|
|
311
|
+
overallRisk: this.calculateOverallRisk(correlatedResults),
|
|
312
|
+
summary: this.generateSummary(correlatedResults),
|
|
313
|
+
analysisPhases,
|
|
314
|
+
adaptiveAnalysis: {
|
|
315
|
+
blockchainAnalysisEnabled: this.hasBlockchainIndicators(results),
|
|
316
|
+
extendedForensicsEnabled: this.needsExtendedForensics(results),
|
|
317
|
+
totalAgentsRan: Object.keys(correlatedResults).length
|
|
318
|
+
}
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
return this.results;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Correlate findings across agents to identify patterns
|
|
326
|
+
*/
|
|
327
|
+
correlateFindings(results) {
|
|
328
|
+
const correlated = { ...results };
|
|
329
|
+
|
|
330
|
+
// Future: Add sophisticated correlation logic
|
|
331
|
+
// Example: Correlate process findings with network connections
|
|
332
|
+
// Example: Correlate persistence mechanisms with startup items
|
|
333
|
+
|
|
334
|
+
return correlated;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Get system hostname
|
|
339
|
+
*/
|
|
340
|
+
async getHostname() {
|
|
341
|
+
return (await executeShellCommand('hostname')).trim();
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Get macOS version
|
|
346
|
+
*/
|
|
347
|
+
async getOSVersion() {
|
|
348
|
+
return (await executeShellCommand('sw_vers -productVersion')).trim();
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Calculate overall risk across all agents
|
|
353
|
+
*/
|
|
354
|
+
calculateOverallRisk(results) {
|
|
355
|
+
if (!results || typeof results !== 'object') {
|
|
356
|
+
return 'unknown';
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
const risks = Object.values(results)
|
|
360
|
+
.filter(result => result && result.overallRisk)
|
|
361
|
+
.map(r => r.overallRisk);
|
|
362
|
+
|
|
363
|
+
if (risks.length === 0) {
|
|
364
|
+
return 'unknown';
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
if (risks.includes('high')) return 'high';
|
|
368
|
+
if (risks.includes('medium')) return 'medium';
|
|
369
|
+
return 'low';
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* Generate summary of findings
|
|
374
|
+
*/
|
|
375
|
+
generateSummary(results) {
|
|
376
|
+
// Ensure results is a valid object
|
|
377
|
+
if (!results || typeof results !== 'object') {
|
|
378
|
+
return {
|
|
379
|
+
totalFindings: 0,
|
|
380
|
+
highRiskFindings: 0,
|
|
381
|
+
mediumRiskFindings: 0,
|
|
382
|
+
lowRiskFindings: 0,
|
|
383
|
+
agentSummaries: {},
|
|
384
|
+
error: 'Invalid results data'
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
const summary = {
|
|
389
|
+
totalFindings: 0,
|
|
390
|
+
highRiskFindings: 0,
|
|
391
|
+
mediumRiskFindings: 0,
|
|
392
|
+
lowRiskFindings: 0,
|
|
393
|
+
agentSummaries: {}
|
|
394
|
+
};
|
|
395
|
+
|
|
396
|
+
try {
|
|
397
|
+
for (const [key, result] of Object.entries(results)) {
|
|
398
|
+
// Ensure result exists and is valid
|
|
399
|
+
if (!result) {
|
|
400
|
+
summary.agentSummaries[key] = { error: 'Result is null or undefined' };
|
|
401
|
+
continue;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
if (result.error) {
|
|
405
|
+
summary.agentSummaries[key] = { error: result.error };
|
|
406
|
+
continue;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// Ensure findings is an array
|
|
410
|
+
const findings = Array.isArray(result.findings) ? result.findings : [];
|
|
411
|
+
const findingCount = findings.length;
|
|
412
|
+
|
|
413
|
+
summary.totalFindings += findingCount;
|
|
414
|
+
|
|
415
|
+
// Count by risk level
|
|
416
|
+
for (const finding of findings) {
|
|
417
|
+
if (finding && finding.risk === 'high') {
|
|
418
|
+
summary.highRiskFindings++;
|
|
419
|
+
} else if (finding && finding.risk === 'medium') {
|
|
420
|
+
summary.mediumRiskFindings++;
|
|
421
|
+
} else if (finding) {
|
|
422
|
+
summary.lowRiskFindings++;
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
const derivedRisk = this.deriveRiskFromFindings(findings, result.overallRisk);
|
|
427
|
+
|
|
428
|
+
summary.agentSummaries[key] = {
|
|
429
|
+
findings: findingCount,
|
|
430
|
+
risk: derivedRisk
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
} catch (error) {
|
|
434
|
+
console.error(`Error generating summary: ${error.message}`);
|
|
435
|
+
summary.error = error.message;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
return summary;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
/**
|
|
442
|
+
* Derive risk from findings to keep agent badges consistent
|
|
443
|
+
*/
|
|
444
|
+
deriveRiskFromFindings(findings, reportedRisk = 'unknown') {
|
|
445
|
+
if (!Array.isArray(findings) || findings.length === 0) return 'low';
|
|
446
|
+
if (findings.some(f => f && f.risk === 'high')) return 'high';
|
|
447
|
+
if (findings.some(f => f && f.risk === 'medium')) return 'medium';
|
|
448
|
+
return 'low';
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
/**
|
|
452
|
+
* Get color function for risk level
|
|
453
|
+
*/
|
|
454
|
+
getRiskColor(risk) {
|
|
455
|
+
const normalizedRisk = (risk || '').toLowerCase();
|
|
456
|
+
switch (normalizedRisk) {
|
|
457
|
+
case 'high':
|
|
458
|
+
return chalk.red.bold;
|
|
459
|
+
case 'medium':
|
|
460
|
+
return chalk.yellow.bold;
|
|
461
|
+
case 'low':
|
|
462
|
+
return chalk.green;
|
|
463
|
+
default:
|
|
464
|
+
return chalk.gray;
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
updateProgress(spinner, completed, total) {
|
|
469
|
+
const text = `Progress: ${completed}/${total}`;
|
|
470
|
+
if (spinner && spinner.isSpinning) {
|
|
471
|
+
spinner.text = text;
|
|
472
|
+
}
|
|
473
|
+
console.log(chalk.gray(text));
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
/**
|
|
477
|
+
* Get aggregated results
|
|
478
|
+
*/
|
|
479
|
+
getResults() {
|
|
480
|
+
return this.results;
|
|
481
|
+
}
|
|
482
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base Agent class that all specific agents extend
|
|
3
|
+
*/
|
|
4
|
+
export class BaseAgent {
|
|
5
|
+
constructor(name) {
|
|
6
|
+
this.name = name;
|
|
7
|
+
this.results = null;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Execute the agent's analysis
|
|
12
|
+
* Must be implemented by subclasses
|
|
13
|
+
*/
|
|
14
|
+
async analyze() {
|
|
15
|
+
throw new Error(`${this.name}: analyze() must be implemented`);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Get the analysis results
|
|
20
|
+
*/
|
|
21
|
+
getResults() {
|
|
22
|
+
return this.results;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Determine risk level based on findings
|
|
27
|
+
* @param {Array} findings - Array of findings with risk levels
|
|
28
|
+
* @returns {string} - Overall risk level
|
|
29
|
+
*/
|
|
30
|
+
calculateOverallRisk(findings) {
|
|
31
|
+
if (findings.some(f => f.risk === 'high')) return 'high';
|
|
32
|
+
if (findings.some(f => f.risk === 'medium')) return 'medium';
|
|
33
|
+
return 'low';
|
|
34
|
+
}
|
|
35
|
+
}
|