@lincoln504/pi-research 0.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.
Files changed (75) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +131 -0
  3. package/config/searxng/default-settings.yml +189 -0
  4. package/config/searxng/limiter.toml +34 -0
  5. package/config/tooling/eslint.config.js +73 -0
  6. package/config/tooling/vitest.config.integration.ts +17 -0
  7. package/config/tooling/vitest.config.ts +40 -0
  8. package/config/tooling/vitest.config.unit.ts +12 -0
  9. package/docs/ARCHITECTURE.md +109 -0
  10. package/docs/assets/README-banner.jpg +0 -0
  11. package/package.json +92 -0
  12. package/scripts/setup.js +174 -0
  13. package/src/config.ts +206 -0
  14. package/src/constants.ts +148 -0
  15. package/src/healthcheck/index.ts +309 -0
  16. package/src/index.ts +71 -0
  17. package/src/infrastructure/searxng-lifecycle.ts +577 -0
  18. package/src/infrastructure/searxng-manager.ts +1173 -0
  19. package/src/infrastructure/state-manager.ts +918 -0
  20. package/src/logger.ts +246 -0
  21. package/src/orchestration/deep-research-orchestrator.ts +848 -0
  22. package/src/orchestration/deep-research-reducer.ts +129 -0
  23. package/src/orchestration/deep-research-types.ts +54 -0
  24. package/src/orchestration/id-utils.ts +63 -0
  25. package/src/orchestration/researcher.ts +104 -0
  26. package/src/orchestration/session-context.ts +43 -0
  27. package/src/prompts/research-tool-usage.md +45 -0
  28. package/src/prompts/researcher.md +166 -0
  29. package/src/prompts/system-coordinator.md +24 -0
  30. package/src/prompts/system-lead-evaluator.md +225 -0
  31. package/src/security/cisa-kev.ts +242 -0
  32. package/src/security/github-advisories.ts +607 -0
  33. package/src/security/index.ts +406 -0
  34. package/src/security/nvd.ts +532 -0
  35. package/src/security/osv.ts +487 -0
  36. package/src/security/types.ts +138 -0
  37. package/src/stackexchange/cache.ts +111 -0
  38. package/src/stackexchange/index.ts +352 -0
  39. package/src/stackexchange/output/compact.ts +64 -0
  40. package/src/stackexchange/output/index.ts +7 -0
  41. package/src/stackexchange/output/json.ts +21 -0
  42. package/src/stackexchange/output/table.ts +123 -0
  43. package/src/stackexchange/queries.ts +168 -0
  44. package/src/stackexchange/rest-client.ts +127 -0
  45. package/src/stackexchange/types.ts +129 -0
  46. package/src/tool.ts +430 -0
  47. package/src/tools/grep.ts +335 -0
  48. package/src/tools/index.ts +77 -0
  49. package/src/tools/scrape.ts +451 -0
  50. package/src/tools/search.ts +188 -0
  51. package/src/tools/security.ts +292 -0
  52. package/src/tools/stackexchange.ts +101 -0
  53. package/src/tui/research-panel.ts +576 -0
  54. package/src/tui/searxng-status.ts +80 -0
  55. package/src/types/extension-context.ts +60 -0
  56. package/src/types/llm.ts +111 -0
  57. package/src/utils/inject-date.ts +29 -0
  58. package/src/utils/input-validation.ts +103 -0
  59. package/src/utils/json-utils.ts +262 -0
  60. package/src/utils/make-resource-loader.ts +43 -0
  61. package/src/utils/research-error.ts +205 -0
  62. package/src/utils/research-export.ts +199 -0
  63. package/src/utils/searxng-config.ts +94 -0
  64. package/src/utils/session-state.ts +295 -0
  65. package/src/utils/shared-links.ts +123 -0
  66. package/src/utils/shutdown-manager.ts +54 -0
  67. package/src/utils/text-utils.ts +83 -0
  68. package/src/utils/tool-usage-tracker.ts +135 -0
  69. package/src/web-research/dependencies.ts +20 -0
  70. package/src/web-research/retry-utils.ts +232 -0
  71. package/src/web-research/scrapers.ts +935 -0
  72. package/src/web-research/search.ts +235 -0
  73. package/src/web-research/types.ts +47 -0
  74. package/src/web-research/utils.ts +158 -0
  75. package/tsconfig.json +40 -0
@@ -0,0 +1,487 @@
1
+ /**
2
+ * OSV (Open Source Vulnerabilities) API Client
3
+ *
4
+ * Open Source Vulnerabilities
5
+ * API: https://api.osv.dev/v1/
6
+ * Free public API, no authentication required
7
+ */
8
+
9
+ import type { Vulnerability, OSVResult } from './types.ts';
10
+ import { createTimeoutSignal, retryWithBackoff, isTransientError } from '../web-research/retry-utils.ts';
11
+ import { logger } from '../logger.ts';
12
+ import { OSV_TIMEOUT_MS, DEFAULT_MAX_RETRIES, DEFAULT_INITIAL_DELAY_MS, DEFAULT_MAX_DELAY_MS } from '../constants.ts';
13
+
14
+ const OSV_BASE_URL = 'https://api.osv.dev/v1';
15
+ const DEFAULT_MAX_RESULTS = 20;
16
+
17
+ // ==================== OSV API Type Definitions ====================
18
+
19
+ /**
20
+ * OSV API vulnerability response (from GET /vulns/{id})
21
+ */
22
+ interface OsvVulnerability {
23
+ id: string;
24
+ summary?: string;
25
+ details?: string;
26
+ published?: string;
27
+ modified?: string;
28
+ withdrawn?: string;
29
+ aliases?: string[];
30
+ related?: string[];
31
+ affected?: OsvAffected[];
32
+ severity?: OsvSeverity[];
33
+ references?: OsvReference[];
34
+ database_specific?: OsvDatabaseSpecific;
35
+ }
36
+
37
+ /**
38
+ * OSV severity entry
39
+ */
40
+ interface OsvSeverity {
41
+ type: string;
42
+ score: string;
43
+ }
44
+
45
+ /**
46
+ * OSV database-specific metadata
47
+ */
48
+ interface OsvDatabaseSpecific {
49
+ severity?: string;
50
+ cwe?: Array<{ id: string } | string>;
51
+ [key: string]: unknown;
52
+ }
53
+
54
+ /**
55
+ * OSV affected package entry
56
+ */
57
+ interface OsvAffected {
58
+ package?: OsvPackage;
59
+ ranges?: OsvRange[];
60
+ versions?: string[];
61
+ database_specific?: Record<string, unknown>;
62
+ severity?: OsvSeverity[];
63
+ }
64
+
65
+ /**
66
+ * OSV package information
67
+ */
68
+ interface OsvPackage {
69
+ name?: string;
70
+ ecosystem?: string;
71
+ purl?: string;
72
+ ecosystem_specific?: {
73
+ name?: string;
74
+ [key: string]: unknown;
75
+ };
76
+ [key: string]: unknown;
77
+ }
78
+
79
+ /**
80
+ * OSV version range information
81
+ */
82
+ interface OsvRange {
83
+ type: string;
84
+ repo?: string;
85
+ events: OsvRangeEvent[];
86
+ database_specific?: Record<string, unknown>;
87
+ }
88
+
89
+ /**
90
+ * OSV range event (introduced, fixed, last_affected, limits)
91
+ */
92
+ interface OsvRangeEvent {
93
+ introduced?: string;
94
+ fixed?: string;
95
+ last_affected?: string;
96
+ limit?: string;
97
+ }
98
+
99
+ /**
100
+ * OSV reference entry
101
+ */
102
+ interface OsvReference {
103
+ type?: string;
104
+ url?: string;
105
+ }
106
+
107
+ /**
108
+ * OSV query request body
109
+ */
110
+ interface OsvQueryRequest {
111
+ package: {
112
+ name: string;
113
+ ecosystem: string;
114
+ purl?: string;
115
+ };
116
+ version?: string;
117
+ commit?: string;
118
+ }
119
+
120
+ /**
121
+ * OSV query response (from POST /query)
122
+ */
123
+ interface OsvQueryResponse {
124
+ vulns?: OsvVulnerability[];
125
+ [key: string]: unknown;
126
+ }
127
+
128
+ /**
129
+ * Type guard to check if value is an OsvVulnerability
130
+ */
131
+ function isOsvVulnerability(value: unknown): value is OsvVulnerability {
132
+ if (value === null || typeof value !== 'object') {
133
+ return false;
134
+ }
135
+ const obj = value as Record<string, unknown>;
136
+ return typeof obj['id'] === 'string';
137
+ }
138
+
139
+ /**
140
+ * Type guard to check if value is an OsvQueryResponse
141
+ */
142
+ function isOsvQueryResponse(value: unknown): value is OsvQueryResponse {
143
+ if (value === null || typeof value !== 'object') {
144
+ return false;
145
+ }
146
+ const obj = value as Record<string, unknown>;
147
+ return Array.isArray(obj['vulns']);
148
+ }
149
+
150
+ // ==================== API Functions ====================
151
+
152
+ /**
153
+ * Fetch with retry for resilience
154
+ */
155
+ async function fetchWithRetry(
156
+ url: string,
157
+ options: RequestInit,
158
+ ): Promise<Response> {
159
+ return retryWithBackoff(
160
+ async () => {
161
+ const response = await fetch(url, options);
162
+ if (!response.ok) {
163
+ const error = new Error(`HTTP ${response.status}: ${response.statusText}`) as Error & { status?: number };
164
+ error.status = response.status;
165
+ throw error;
166
+ }
167
+ return response;
168
+ },
169
+ {
170
+ maxRetries: DEFAULT_MAX_RETRIES,
171
+ initialDelay: DEFAULT_INITIAL_DELAY_MS,
172
+ maxDelay: DEFAULT_MAX_DELAY_MS,
173
+ label: `OSV API: ${url}`,
174
+ isTransientError: (error) => {
175
+ // Check if error has status property
176
+ const status = (error as Error & { status?: number })?.status;
177
+ if (typeof status === 'number') {
178
+ // Retry on 429 (rate limit), 403 (forbidden), 5xx (server errors)
179
+ return status === 429 || status === 403 || status >= 500;
180
+ }
181
+ // For other errors, use default transient error check
182
+ return isTransientError(error);
183
+ },
184
+ },
185
+ );
186
+ }
187
+
188
+ /**
189
+ * Search OSV for vulnerabilities
190
+ *
191
+ * @param terms - Package names, CVE IDs, or keywords
192
+ * @param options - Optional filters
193
+ * @returns OSV search result with vulnerabilities and optional error
194
+ */
195
+ export async function searchOSV(
196
+ terms: string[],
197
+ options?: {
198
+ ecosystem?: string; // npm, PyPI, Maven, Go, Rust, etc.
199
+ severity?: string; // LOW, MODERATE, HIGH, CRITICAL
200
+ maxResults?: number;
201
+ includeAffected?: boolean; // Include affected version ranges
202
+ },
203
+ ): Promise<OSVResult> {
204
+ const maxResults: number = options?.maxResults ?? DEFAULT_MAX_RESULTS;
205
+ const vulnerabilities: Vulnerability[] = [];
206
+ let error: string | undefined = undefined;
207
+
208
+ try {
209
+ const allVulns: Vulnerability[] = [];
210
+ let skippedNoEcosystem = 0;
211
+
212
+ for (const term of terms) {
213
+ let response: Response;
214
+
215
+ // OSV API: GET /vulns/{id} for CVE/OSV/GHSA IDs, POST /query for package names
216
+ const termUpper: string = term.toUpperCase();
217
+ if (termUpper.startsWith('CVE-') || termUpper.startsWith('GHSA-') || termUpper.startsWith('OSV-')) {
218
+ // GHSA IDs: OSV requires uppercase prefix + lowercase hex portions.
219
+ // Normalize regardless of how the caller typed it.
220
+ const normalizedId: string = termUpper.startsWith('GHSA-')
221
+ ? `GHSA-${term.slice(term.indexOf('-') + 1).toLowerCase()}`
222
+ : termUpper;
223
+ const url: string = `${OSV_BASE_URL}/vulns/${encodeURIComponent(normalizedId)}`;
224
+ response = await fetchWithRetry(url, {
225
+ headers: { 'User-Agent': 'pi-research/2.0', 'Accept': 'application/json' },
226
+ signal: createTimeoutSignal(OSV_TIMEOUT_MS),
227
+ });
228
+ } else {
229
+ // POST /query requires package.name + package.ecosystem.
230
+ // Sending name without ecosystem always returns HTTP 400 "Invalid query."
231
+ if (options?.ecosystem === undefined || options.ecosystem === '') {
232
+ skippedNoEcosystem++;
233
+ continue;
234
+ }
235
+ const body: OsvQueryRequest = { package: { name: term, ecosystem: options.ecosystem } };
236
+ response = await fetchWithRetry(`${OSV_BASE_URL}/query`, {
237
+ method: 'POST',
238
+ headers: {
239
+ 'User-Agent': 'pi-research/2.0',
240
+ 'Accept': 'application/json',
241
+ 'Content-Type': 'application/json',
242
+ },
243
+ body: JSON.stringify(body),
244
+ signal: createTimeoutSignal(OSV_TIMEOUT_MS),
245
+ });
246
+ }
247
+
248
+ if (!response.ok) {
249
+ logger.warn(`OSV query failed for "${term}": ${response.status}`);
250
+ continue;
251
+ }
252
+
253
+ const data: unknown = await response.json();
254
+
255
+ // Single vuln response (GET /vulns/{id}) vs batch response (POST /query)
256
+ let items: OsvVulnerability[];
257
+
258
+ if (isOsvVulnerability(data)) {
259
+ items = [data];
260
+ } else if (isOsvQueryResponse(data)) {
261
+ items = data.vulns ?? [];
262
+ } else {
263
+ logger.warn(`OSV returned unexpected format for "${term}"`);
264
+ continue;
265
+ }
266
+
267
+ for (const item of items) {
268
+ const vuln: Vulnerability = mapOsvItemToVulnerability(item);
269
+
270
+ // Filter by severity if provided
271
+ if (options?.severity !== undefined && options.severity !== '') {
272
+ const severity: string = options.severity.toUpperCase();
273
+ if (vuln.severity !== severity) {
274
+ continue;
275
+ }
276
+ }
277
+
278
+ allVulns.push(vuln);
279
+ }
280
+ }
281
+
282
+ // Deduplicate by OSV ID
283
+ const uniqueVulns = new Map<string, Vulnerability>();
284
+ for (const vuln of allVulns) {
285
+ if (!uniqueVulns.has(vuln.id)) {
286
+ uniqueVulns.set(vuln.id, vuln);
287
+ }
288
+ }
289
+
290
+ vulnerabilities.push(...Array.from(uniqueVulns.values()));
291
+
292
+ if (skippedNoEcosystem > 0) {
293
+ error = `${skippedNoEcosystem} term(s) require the ecosystem parameter for OSV package search (e.g., ecosystem: "npm"). CVE/GHSA/OSV IDs work without it.`;
294
+ }
295
+
296
+ } catch (err: unknown) {
297
+ error = err instanceof Error ? err.message : String(err);
298
+ }
299
+
300
+ return {
301
+ count: vulnerabilities.length,
302
+ vulnerabilities: vulnerabilities.slice(0, maxResults),
303
+ error,
304
+ };
305
+ }
306
+
307
+ /**
308
+ * Get vulnerability by OSV ID
309
+ *
310
+ * @param osvId - OSV vulnerability ID (e.g., CVE-2023-1234, GHSA-abc1-23de-fg45)
311
+ * @returns Vulnerability object if found, null otherwise
312
+ */
313
+ export async function getOSVById(osvId: string): Promise<Vulnerability | null> {
314
+ try {
315
+ const url: string = `${OSV_BASE_URL}/vulns/${osvId}`;
316
+ const response: Response = await fetchWithRetry(url, {
317
+ headers: {
318
+ 'User-Agent': 'pi-research/2.0',
319
+ 'Accept': 'application/json',
320
+ },
321
+ signal: createTimeoutSignal(OSV_TIMEOUT_MS),
322
+ });
323
+
324
+ const data: unknown = await response.json();
325
+
326
+ if (!isOsvVulnerability(data)) {
327
+ logger.error(`OSV ${osvId} returned unexpected format`);
328
+ return null;
329
+ }
330
+
331
+ return mapOsvItemToVulnerability(data);
332
+ } catch (err: unknown) {
333
+ logger.error(`Error fetching OSV ${osvId}:`, err);
334
+ return null;
335
+ }
336
+ }
337
+
338
+ // ==================== Helper Functions ====================
339
+
340
+ /**
341
+ * Map OSV item to Vulnerability interface
342
+ *
343
+ * @param item - OSV vulnerability object
344
+ * @returns Standardized Vulnerability object
345
+ */
346
+ function mapOsvItemToVulnerability(item: OsvVulnerability): Vulnerability {
347
+ const osvId: string = item.id;
348
+ const summary: string = item.summary ?? item.details ?? '';
349
+ const details: string = item.details ?? '';
350
+ const published: string | undefined = item.published;
351
+ const modified: string = item.modified ?? '';
352
+
353
+ // OSV severity mapping
354
+ // item.severity is an array of {type, score} objects; string severity is in database_specific
355
+ let severityStr: string | undefined;
356
+ if (item.database_specific !== undefined &&
357
+ typeof item.database_specific.severity === 'string') {
358
+ severityStr = item.database_specific.severity;
359
+ }
360
+ const severity: string = mapOsvSeverity(severityStr);
361
+
362
+ // Extract aliases (including CVE IDs)
363
+ const aliases: string[] = item.aliases ?? [];
364
+ const references: string[] = [...aliases];
365
+
366
+ if (item.references !== undefined) {
367
+ for (const ref of item.references) {
368
+ if (ref.url !== undefined && ref.url !== '') {
369
+ references.push(ref.url);
370
+ }
371
+ }
372
+ }
373
+
374
+ // Extract affected packages and versions
375
+ const affectedProducts: string[] = [];
376
+ const fixes: string[] = [];
377
+
378
+ if (item.affected !== undefined) {
379
+ for (const affectedEntry of item.affected) {
380
+ const pkg: OsvPackage | undefined = affectedEntry.package;
381
+
382
+ if (pkg !== undefined) {
383
+ const pkgName: string = pkg.name ??
384
+ (pkg.ecosystem_specific !== undefined && typeof pkg.ecosystem_specific.name === 'string'
385
+ ? pkg.ecosystem_specific.name
386
+ : '');
387
+
388
+ if (pkgName !== '') {
389
+ affectedProducts.push(pkgName);
390
+ }
391
+
392
+ // Version ranges
393
+ // OSV range format: { type, events: [{introduced: "x"}, {fixed: "y"}] }
394
+ if (affectedEntry.ranges !== undefined) {
395
+ for (const range of affectedEntry.ranges) {
396
+ const events: OsvRangeEvent[] = range.events;
397
+
398
+ const introducedEvent: OsvRangeEvent | undefined = events.find(
399
+ (e: OsvRangeEvent) => e.introduced !== undefined,
400
+ );
401
+ const fixedEvent: OsvRangeEvent | undefined = events.find(
402
+ (e: OsvRangeEvent) => e.fixed !== undefined,
403
+ );
404
+ const lastAffectedEvent: OsvRangeEvent | undefined = events.find(
405
+ (e: OsvRangeEvent) => e.last_affected !== undefined,
406
+ );
407
+
408
+ const versionInfo: string[] = [];
409
+
410
+ if (introducedEvent?.introduced !== undefined) {
411
+ versionInfo.push(`introduced: ${introducedEvent.introduced}`);
412
+ }
413
+ if (fixedEvent?.fixed !== undefined) {
414
+ versionInfo.push(`fixed: ${fixedEvent.fixed}`);
415
+ }
416
+ if (lastAffectedEvent?.last_affected !== undefined) {
417
+ versionInfo.push(`last affected: ${lastAffectedEvent.last_affected}`);
418
+ }
419
+
420
+ if (versionInfo.length > 0 && pkgName !== '') {
421
+ fixes.push(`${pkgName}: ${versionInfo.join(', ')}`);
422
+ }
423
+ }
424
+ }
425
+ }
426
+ }
427
+ }
428
+
429
+ // Extract CWEs if available
430
+ const cwes: string[] = [];
431
+ if (item.database_specific !== undefined) {
432
+ if (item.database_specific.cwe !== undefined && Array.isArray(item.database_specific.cwe)) {
433
+ for (const cwe of item.database_specific.cwe) {
434
+ if (typeof cwe === 'string') {
435
+ cwes.push(cwe);
436
+ } else if (typeof cwe === 'object' && typeof (cwe as { id?: string }).id === 'string') {
437
+ cwes.push((cwe as { id: string }).id);
438
+ }
439
+ }
440
+ }
441
+ }
442
+
443
+ return {
444
+ id: osvId,
445
+ source: 'osv',
446
+ severity,
447
+ description: summary !== '' ? summary : details,
448
+ published,
449
+ modified,
450
+ cvssScore: undefined, // OSV doesn't provide CVSS score
451
+ cvssVector: undefined,
452
+ cwes,
453
+ references,
454
+ affectedProducts,
455
+ fixes,
456
+ knownExploited: false,
457
+ };
458
+ }
459
+
460
+ /**
461
+ * Map OSV severity to standard values
462
+ *
463
+ * @param severity - OSV severity string (e.g., "CRITICAL", "HIGH", "MODERATE", "LOW")
464
+ * @returns Standardized severity value ("CRITICAL", "HIGH", "MEDIUM", "LOW", "UNKNOWN")
465
+ */
466
+ function mapOsvSeverity(severity?: string): string {
467
+ if (severity === undefined || severity === '') {
468
+ return 'UNKNOWN';
469
+ }
470
+
471
+ const upper: string = severity.toUpperCase();
472
+
473
+ if (upper === 'CRITICAL') {
474
+ return 'CRITICAL';
475
+ }
476
+ if (upper === 'HIGH') {
477
+ return 'HIGH';
478
+ }
479
+ if (upper === 'MEDIUM' || upper === 'MODERATE') {
480
+ return 'MEDIUM';
481
+ }
482
+ if (upper === 'LOW') {
483
+ return 'LOW';
484
+ }
485
+
486
+ return 'UNKNOWN';
487
+ }
@@ -0,0 +1,138 @@
1
+ /**
2
+ * Security Module Types
3
+ *
4
+ * Type definitions for security database integrations
5
+ */
6
+
7
+ export interface Vulnerability {
8
+ readonly id: string;
9
+ readonly source: string;
10
+ readonly severity: string;
11
+ readonly description: string;
12
+ readonly published?: string;
13
+ readonly modified?: string;
14
+ readonly cvssScore?: number;
15
+ readonly cvssVector?: string;
16
+ readonly cwes: readonly string[];
17
+ readonly references: readonly string[];
18
+ readonly affectedProducts: readonly string[];
19
+ readonly fixes: readonly string[];
20
+ readonly knownExploited?: boolean;
21
+ readonly vendor?: string;
22
+ readonly product?: string;
23
+ readonly dueDate?: string;
24
+ readonly requiredAction?: string;
25
+ }
26
+
27
+ export interface Advisory {
28
+ readonly id: string;
29
+ readonly source: string;
30
+ readonly severity: string;
31
+ readonly summary: string;
32
+ readonly description?: string;
33
+ readonly published: string;
34
+ readonly modified: string;
35
+ readonly cveId?: string;
36
+ readonly references: readonly string[];
37
+ readonly affectedPackages: readonly string[];
38
+ }
39
+
40
+ export interface NVDResult {
41
+ readonly count: number;
42
+ readonly vulnerabilities: readonly Vulnerability[];
43
+ readonly error?: string;
44
+ }
45
+
46
+ export interface CisaKevResult {
47
+ readonly count: number;
48
+ readonly vulnerabilities: readonly Vulnerability[];
49
+ readonly error?: string;
50
+ }
51
+
52
+ export interface GitHubResult {
53
+ readonly count: number;
54
+ readonly advisories: readonly Advisory[];
55
+ readonly error?: string;
56
+ }
57
+
58
+ export interface OSVResult {
59
+ readonly count: number;
60
+ readonly vulnerabilities: readonly Vulnerability[];
61
+ readonly error?: string;
62
+ }
63
+
64
+ export interface SecurityDatabaseResults {
65
+ readonly nvd?: NVDResult;
66
+ readonly cisa_kev?: CisaKevResult;
67
+ readonly github?: GitHubResult;
68
+ readonly osv?: OSVResult;
69
+ }
70
+
71
+ export interface SecuritySearchParams {
72
+ readonly terms: readonly string[];
73
+ readonly databases: readonly ('nvd' | 'cisa_kev' | 'github' | 'osv')[];
74
+ readonly severity?: string;
75
+ readonly ecosystem?: string;
76
+ readonly maxResults?: number;
77
+ readonly includeExploited?: boolean;
78
+ readonly githubRepo?: string;
79
+ }
80
+
81
+ export interface SecuritySearchResult {
82
+ readonly results: SecurityDatabaseResults;
83
+ readonly totalDatabases: number;
84
+ readonly totalVulnerabilities: number;
85
+ readonly duration: number;
86
+ }
87
+
88
+ // ============================================================================
89
+ // Security API Client Interfaces (for dependency injection)
90
+ // ============================================================================
91
+
92
+ export interface INVDClient {
93
+ search(terms: readonly string[], options?: NVDSearchOptions): Promise<NVDResult>;
94
+ getById(cveId: string): Promise<Vulnerability | null>;
95
+ }
96
+
97
+ export interface NVDSearchOptions {
98
+ readonly severity?: 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL';
99
+ readonly maxResults?: number;
100
+ readonly includeExploited?: boolean;
101
+ readonly cweId?: string;
102
+ readonly startDate?: string;
103
+ readonly endDate?: string;
104
+ }
105
+
106
+ export interface ICisaKevClient {
107
+ search(terms: readonly string[], options?: CisaKevSearchOptions): Promise<CisaKevResult>;
108
+ }
109
+
110
+ export interface CisaKevSearchOptions {
111
+ readonly vendor?: string;
112
+ readonly product?: string;
113
+ readonly maxResults?: number;
114
+ }
115
+
116
+ export interface IGitHubAdvisoriesClient {
117
+ search(terms: readonly string[], options?: GitHubSearchOptions): Promise<GitHubResult>;
118
+ getById(id: string): Promise<Advisory | null>;
119
+ }
120
+
121
+ export interface GitHubSearchOptions {
122
+ readonly ecosystem?: string;
123
+ readonly severity?: string;
124
+ readonly maxResults?: number;
125
+ readonly repo?: string;
126
+ }
127
+
128
+ export interface IOSVClient {
129
+ search(terms: readonly string[], options?: OSVSearchOptions): Promise<OSVResult>;
130
+ getById(osvId: string): Promise<Vulnerability | null>;
131
+ }
132
+
133
+ export interface OSVSearchOptions {
134
+ readonly ecosystem?: string;
135
+ readonly severity?: string;
136
+ readonly maxResults?: number;
137
+ readonly includeAffected?: boolean;
138
+ }