@lincoln504/pi-research 0.1.13 → 1.0.1

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 (248) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +55 -138
  3. package/dist/cli.mjs +23375 -0
  4. package/dist/prompts/research-knowledge-relevance-triage.md +35 -0
  5. package/dist/prompts/research-knowledge-search-extractor.md +41 -0
  6. package/dist/prompts/research-tool-usage.md +92 -0
  7. package/dist/prompts/researcher.md +89 -0
  8. package/dist/prompts/system-coordinator.md +75 -0
  9. package/dist/prompts/system-lead-evaluator.md +118 -0
  10. package/dist/thread-worker.mjs +2611 -0
  11. package/dist/webgpu-probe.mjs +80 -0
  12. package/docs/AGENT-SKILL.md +130 -0
  13. package/docs/ARCHITECTURE.md +306 -80
  14. package/docs/CONFIGURATION.md +249 -0
  15. package/docs/KNOWLEDGE-STORE.md +211 -0
  16. package/docs/PI-EXTENSION.md +91 -0
  17. package/docs/SDK.md +122 -0
  18. package/docs/THIRD-PARTY-NOTICES.md +66 -0
  19. package/package.json +102 -36
  20. package/scripts/build.cjs +140 -0
  21. package/scripts/cleanup.cjs +149 -0
  22. package/scripts/prepare.cjs +28 -0
  23. package/scripts/setup.cjs +134 -0
  24. package/skills/pi-research/README.md +90 -0
  25. package/skills/pi-research/SKILL.md +102 -0
  26. package/skills/pi-research/references/configuration.md +77 -0
  27. package/skills/pi-research/scripts/run.mjs +237 -0
  28. package/src/cleanup/research-cleanup.ts +140 -0
  29. package/src/cli.ts +1195 -0
  30. package/src/config.ts +1032 -155
  31. package/src/constants.ts +115 -84
  32. package/src/core/health-check-service.ts +265 -0
  33. package/src/core/interfaces/health-check-interfaces.ts +50 -0
  34. package/src/core/interfaces/knowledge-interfaces.ts +190 -0
  35. package/src/core/interfaces/observer-interfaces.ts +67 -0
  36. package/src/core/interfaces/orchestration-interfaces.ts +86 -0
  37. package/src/core/interfaces/planning-interfaces.ts +100 -0
  38. package/src/core/interfaces/process-interfaces.ts +37 -0
  39. package/src/core/interfaces/research-plan-types.ts +52 -0
  40. package/src/core/interfaces/scheduler-interfaces.ts +91 -0
  41. package/src/core/interfaces/service-names.ts +39 -0
  42. package/src/core/interfaces/state-manager-interfaces.ts +29 -0
  43. package/src/core/interfaces/steering-interfaces.ts +31 -0
  44. package/src/core/llm/agentic-repair.ts +169 -0
  45. package/src/core/llm/inject-date.ts +52 -0
  46. package/src/core/llm/llm-timeout.ts +64 -0
  47. package/src/core/llm/llm-utils.ts +86 -0
  48. package/src/core/llm/model-registry-factory.ts +287 -0
  49. package/src/core/llm/pi-ai-completion.ts +29 -0
  50. package/src/core/llm/prompts.ts +50 -0
  51. package/src/core/llm/research-model-resolver.ts +91 -0
  52. package/src/core/planning-constants.ts +13 -0
  53. package/src/core/planning-service.ts +649 -0
  54. package/src/core/planning-utils.ts +299 -0
  55. package/src/core/scheduler-factory.ts +48 -0
  56. package/src/core/scheduler-service.ts +123 -0
  57. package/src/core/service-initialization.ts +194 -0
  58. package/src/core/service-interfaces.ts +48 -0
  59. package/src/core/service-registry.ts +659 -0
  60. package/src/healthcheck/index.ts +271 -265
  61. package/src/healthcheck/registry.ts +130 -0
  62. package/src/index.ts +639 -50
  63. package/src/infrastructure/browser/browser-cleanup.ts +309 -0
  64. package/src/infrastructure/browser/browser-client.ts +199 -0
  65. package/src/infrastructure/browser/browser-error-utils.ts +160 -0
  66. package/src/infrastructure/browser/browser-lifecycle.ts +120 -0
  67. package/src/infrastructure/browser/browser-server.ts +174 -0
  68. package/src/infrastructure/browser/browser-task-scheduler.ts +461 -0
  69. package/src/infrastructure/browser/cleanup-utils.ts +155 -0
  70. package/src/infrastructure/browser/client-agent.ts +31 -0
  71. package/src/infrastructure/browser/config.ts +288 -0
  72. package/src/infrastructure/browser/ensure-browser.ts +205 -0
  73. package/src/infrastructure/browser/index.ts +37 -0
  74. package/src/infrastructure/browser/priority-task-queue.ts +226 -0
  75. package/src/infrastructure/browser/scheduler-factory.ts +354 -0
  76. package/src/infrastructure/browser/task-execution-service.ts +209 -0
  77. package/src/infrastructure/browser/thread-worker-browser.ts +282 -0
  78. package/src/infrastructure/browser/thread-worker-lifecycle.ts +323 -0
  79. package/src/infrastructure/browser/thread-worker-messaging.ts +662 -0
  80. package/src/infrastructure/browser/thread-worker-types.ts +25 -0
  81. package/src/infrastructure/browser/thread-worker.mjs +2611 -0
  82. package/src/infrastructure/browser/thread-worker.ts +168 -0
  83. package/src/infrastructure/browser/worker-pool-manager.ts +449 -0
  84. package/src/infrastructure/embedding/embedding-client.ts +205 -0
  85. package/src/infrastructure/embedding/embedding-factory.ts +324 -0
  86. package/src/infrastructure/embedding/embedding-server.ts +535 -0
  87. package/src/infrastructure/file-lock-service.ts +592 -0
  88. package/src/infrastructure/gpu-resource-service.ts +191 -0
  89. package/src/infrastructure/knowledge-store-service.ts +475 -0
  90. package/src/infrastructure/metrics-service.ts +129 -0
  91. package/src/infrastructure/process-lifecycle-service.ts +253 -0
  92. package/src/infrastructure/scheduler-factory-service.ts +58 -0
  93. package/src/infrastructure/service-initialization.ts +292 -0
  94. package/src/infrastructure/state/state-backup-manager.ts +290 -0
  95. package/src/infrastructure/state/state-browser-api.ts +62 -0
  96. package/src/infrastructure/state/state-browser-manager.ts +70 -0
  97. package/src/infrastructure/state/state-manager-service.ts +264 -0
  98. package/src/infrastructure/state/state-manager.ts +547 -0
  99. package/src/infrastructure/state/state-metrics.ts +92 -0
  100. package/src/infrastructure/state/state-migration.ts +103 -0
  101. package/src/infrastructure/state/state-path-configuration.ts +119 -0
  102. package/src/infrastructure/state/state-session-api.ts +94 -0
  103. package/src/infrastructure/state/state-session-manager.ts +127 -0
  104. package/src/infrastructure/state/state-validator.ts +93 -0
  105. package/src/infrastructure/types/state-types.ts +85 -0
  106. package/src/knowledge/chunker.ts +157 -0
  107. package/src/knowledge/embedder-init.ts +413 -0
  108. package/src/knowledge/embedder-utils.ts +296 -0
  109. package/src/knowledge/embedder.ts +592 -0
  110. package/src/knowledge/index.ts +126 -0
  111. package/src/knowledge/lancedb-loader.ts +35 -0
  112. package/src/knowledge/migration.ts +17 -0
  113. package/src/knowledge/model-config.ts +132 -0
  114. package/src/knowledge/onnx-env.ts +48 -0
  115. package/src/knowledge/store-operations.ts +291 -0
  116. package/src/knowledge/store-schema.ts +79 -0
  117. package/src/knowledge/store.ts +1251 -0
  118. package/src/knowledge/transformers-loader.ts +31 -0
  119. package/src/knowledge/webgpu-probe.mjs +80 -0
  120. package/src/knowledge/webgpu-viability.ts +255 -0
  121. package/src/knowledge/writer-queue.ts +252 -0
  122. package/src/logger.ts +316 -122
  123. package/src/observers/research-observer-impl.ts +459 -0
  124. package/src/orchestration/deep-research-orchestrator.ts +524 -773
  125. package/src/orchestration/deep-research-types.ts +47 -35
  126. package/src/orchestration/headless-observer.ts +114 -0
  127. package/src/orchestration/orchestration-types.ts +33 -0
  128. package/src/orchestration/quick-research-orchestrator.ts +463 -0
  129. package/src/orchestration/research-orchestration-service.ts +549 -0
  130. package/src/orchestration/research-session-service.ts +178 -0
  131. package/src/orchestration/research-synthesis-service.ts +348 -0
  132. package/src/orchestration/researcher-executor.ts +394 -0
  133. package/src/orchestration/researcher.ts +115 -42
  134. package/src/orchestration/service-initialization.ts +109 -0
  135. package/src/orchestration/session-context.ts +7 -3
  136. package/src/orchestration/session-state.ts +672 -0
  137. package/src/prompts/research-knowledge-relevance-triage.md +35 -0
  138. package/src/prompts/research-knowledge-search-extractor.md +41 -0
  139. package/src/prompts/research-tool-usage.md +63 -16
  140. package/src/prompts/researcher.md +59 -136
  141. package/src/prompts/system-coordinator.md +64 -13
  142. package/src/prompts/system-lead-evaluator.md +82 -189
  143. package/src/research-config.ts +872 -0
  144. package/src/sdk.ts +847 -0
  145. package/src/security/cisa-kev.ts +72 -9
  146. package/src/security/cvss.ts +92 -0
  147. package/src/security/ecosystem.ts +45 -0
  148. package/src/security/github-advisories.ts +168 -428
  149. package/src/security/github-advisory-types.ts +282 -0
  150. package/src/security/index.ts +63 -177
  151. package/src/security/nvd-client.ts +550 -0
  152. package/src/security/nvd-types.ts +179 -0
  153. package/src/security/osv-client.ts +246 -0
  154. package/src/security/osv-types.ts +285 -0
  155. package/src/security/types.ts +67 -53
  156. package/src/skill-install/skill-installer.ts +519 -0
  157. package/src/stackexchange/index.ts +81 -27
  158. package/src/stackexchange/output/compact.ts +7 -6
  159. package/src/stackexchange/output/table.ts +11 -7
  160. package/src/stackexchange/queries.ts +0 -10
  161. package/src/stackexchange/rest-client.ts +141 -66
  162. package/src/stackexchange/types.ts +0 -17
  163. package/src/tool.ts +15 -423
  164. package/src/tools/grep.ts +32 -310
  165. package/src/tools/health-tool-definition.ts +101 -0
  166. package/src/tools/index.ts +38 -9
  167. package/src/tools/research-knowledge-search.ts +974 -0
  168. package/src/tools/research-knowledge-types.ts +77 -0
  169. package/src/tools/research-tool-definition.ts +494 -0
  170. package/src/tools/scrape.ts +286 -388
  171. package/src/tools/search.ts +98 -153
  172. package/src/tools/security.ts +107 -80
  173. package/src/tools/stackexchange.ts +58 -35
  174. package/src/tools/youtube-transcript.ts +163 -0
  175. package/src/tui/knowledge-search-panel.ts +86 -0
  176. package/src/tui/pulse.ts +86 -0
  177. package/src/tui/research-health.ts +124 -0
  178. package/src/tui/research-panel-color-utils.ts +340 -0
  179. package/src/tui/research-panel-format.ts +54 -0
  180. package/src/tui/research-panel-state.ts +404 -0
  181. package/src/tui/research-panel-wave.ts +280 -0
  182. package/src/tui/research-panel.ts +339 -435
  183. package/src/tui/research-tui-manager.ts +233 -0
  184. package/src/tui/tui-controller.ts +137 -0
  185. package/src/types/extension-context.ts +18 -49
  186. package/src/types/index.ts +61 -0
  187. package/src/types/llm.ts +68 -29
  188. package/src/types/research-panel-types.ts +61 -0
  189. package/src/utils/circuit-breaker.ts +146 -0
  190. package/src/utils/citation-utils.ts +97 -0
  191. package/src/utils/disk-space-checker.ts +55 -0
  192. package/src/utils/error-tracker.ts +265 -0
  193. package/src/utils/host-config.ts +45 -0
  194. package/src/utils/input-validation.ts +8 -10
  195. package/src/utils/json-utils.ts +150 -16
  196. package/src/utils/llm-usage.ts +54 -0
  197. package/src/utils/log-rotation.ts +125 -0
  198. package/src/utils/log-utils.ts +159 -0
  199. package/src/utils/make-resource-loader.ts +3 -1
  200. package/src/utils/metrics-summary.ts +459 -0
  201. package/src/utils/metrics.ts +302 -0
  202. package/src/utils/research-export.ts +28 -12
  203. package/src/utils/safe-unref.ts +31 -0
  204. package/src/utils/shared-links.ts +195 -90
  205. package/src/utils/shutdown-manager.ts +65 -0
  206. package/src/utils/stdio-capture.ts +407 -0
  207. package/src/utils/text-utils.ts +258 -11
  208. package/src/utils/tool-usage-tracker.ts +83 -16
  209. package/src/utils/url-utils.ts +255 -0
  210. package/src/utils/user-agent.ts +28 -0
  211. package/src/web-research/browser-search.ts +179 -0
  212. package/src/web-research/retry-utils.ts +173 -77
  213. package/src/web-research/scraper-types.ts +72 -0
  214. package/src/web-research/scraper-utils.ts +434 -0
  215. package/src/web-research/search.ts +78 -218
  216. package/src/web-research/types.ts +9 -24
  217. package/src/web-research/utils.ts +2 -143
  218. package/src/web-research/web-scraper.ts +477 -0
  219. package/src/youtube/index.ts +154 -0
  220. package/src/youtube/potoken.ts +259 -0
  221. package/src/youtube/transcript-client.ts +390 -0
  222. package/src/youtube/video-id.ts +118 -0
  223. package/config/searxng/default-settings.yml +0 -189
  224. package/config/searxng/limiter.toml +0 -34
  225. package/config/tooling/eslint.config.js +0 -73
  226. package/config/tooling/vitest.config.integration.ts +0 -17
  227. package/config/tooling/vitest.config.ts +0 -40
  228. package/config/tooling/vitest.config.unit.ts +0 -12
  229. package/docs/assets/README-banner.jpg +0 -0
  230. package/scripts/setup.js +0 -212
  231. package/src/infrastructure/searxng-lifecycle.ts +0 -644
  232. package/src/infrastructure/searxng-manager.ts +0 -1183
  233. package/src/infrastructure/state-manager.ts +0 -918
  234. package/src/orchestration/deep-research-reducer.ts +0 -129
  235. package/src/orchestration/id-utils.ts +0 -63
  236. package/src/security/nvd.ts +0 -532
  237. package/src/security/osv.ts +0 -487
  238. package/src/stackexchange/cache.ts +0 -111
  239. package/src/stackexchange/output/index.ts +0 -7
  240. package/src/stackexchange/output/json.ts +0 -21
  241. package/src/tui/searxng-status.ts +0 -80
  242. package/src/utils/inject-date.ts +0 -29
  243. package/src/utils/research-error.ts +0 -205
  244. package/src/utils/searxng-config.ts +0 -94
  245. package/src/utils/session-state.ts +0 -295
  246. package/src/web-research/dependencies.ts +0 -20
  247. package/src/web-research/scrapers.ts +0 -935
  248. package/tsconfig.json +0 -40
@@ -1,532 +0,0 @@
1
- /**
2
- * NVD (National Vulnerability Database) API Client
3
- *
4
- * NIST National Vulnerability Database
5
- * API: https://services.nvd.nist.gov/rest/json/cves/2.0
6
- * Free public API, no authentication required
7
- *
8
- * Rate Limits (Free Tier):
9
- * - ~5 requests per 30 seconds
10
- * - 50 requests per rolling 30 seconds
11
- *
12
- * This module includes a rate limiter to respect these limits.
13
- */
14
-
15
- import type { Vulnerability, NVDResult } from './types.ts';
16
- import { logger } from '../logger.ts';
17
- import { createTimeoutSignal, retryWithBackoff } from '../web-research/retry-utils.ts';
18
-
19
- // ============================================================================
20
- // Type Definitions
21
- // ============================================================================
22
-
23
- interface CVSSData {
24
- readonly version?: string;
25
- readonly vectorString?: string;
26
- readonly baseScore?: number;
27
- readonly baseSeverity?: string;
28
- }
29
-
30
- interface CVSSMetricV31 {
31
- readonly cvssData: CVSSData;
32
- }
33
-
34
- interface CVSSMetricV30 {
35
- readonly cvssData: CVSSData;
36
- }
37
-
38
- interface WeaknessDescription {
39
- readonly value?: string;
40
- }
41
-
42
- interface Weakness {
43
- readonly description?: WeaknessDescription[];
44
- }
45
-
46
- interface Reference {
47
- readonly url?: string;
48
- }
49
-
50
- interface CPEMatch {
51
- readonly criteria?: string;
52
- }
53
-
54
- interface Node {
55
- readonly cpeMatch?: CPEMatch[];
56
- }
57
-
58
- interface Configuration {
59
- readonly nodes?: Node[];
60
- }
61
-
62
- interface Description {
63
- readonly value?: string;
64
- }
65
-
66
- interface Metrics {
67
- readonly cvssMetricV31?: CVSSMetricV31[];
68
- readonly cvssMetricV30?: CVSSMetricV30[];
69
- }
70
-
71
- interface CVE {
72
- readonly id?: string;
73
- readonly descriptions?: Description[];
74
- readonly published?: string;
75
- readonly lastModified?: string;
76
- readonly metrics?: Metrics;
77
- readonly weaknesses?: Weakness[];
78
- readonly references?: Reference[];
79
- readonly configurations?: Configuration[];
80
- }
81
-
82
- interface NVDEntry {
83
- readonly cve: CVE;
84
- }
85
-
86
- interface NVDApiResponse {
87
- readonly vulnerabilities?: NVDEntry[];
88
- }
89
-
90
- // ============================================================================
91
- // Type Guards
92
- // ============================================================================
93
-
94
- function isWeaknessDescription(value: unknown): value is WeaknessDescription {
95
- return (
96
- typeof value === 'object' &&
97
- value !== null &&
98
- 'value' in value
99
- );
100
- }
101
-
102
- function isWeakness(value: unknown): value is Weakness {
103
- return (
104
- typeof value === 'object' &&
105
- value !== null &&
106
- 'description' in value
107
- );
108
- }
109
-
110
- function isReference(value: unknown): value is Reference {
111
- return (
112
- typeof value === 'object' &&
113
- value !== null &&
114
- 'url' in value
115
- );
116
- }
117
-
118
- function isCPEMatch(value: unknown): value is CPEMatch {
119
- return (
120
- typeof value === 'object' &&
121
- value !== null &&
122
- 'criteria' in value
123
- );
124
- }
125
-
126
- function isNode(value: unknown): value is Node {
127
- return (
128
- typeof value === 'object' &&
129
- value !== null &&
130
- 'cpeMatch' in value
131
- );
132
- }
133
-
134
- function isConfiguration(value: unknown): value is Configuration {
135
- return (
136
- typeof value === 'object' &&
137
- value !== null &&
138
- 'nodes' in value
139
- );
140
- }
141
-
142
- function isDescription(value: unknown): value is Description {
143
- return (
144
- typeof value === 'object' &&
145
- value !== null &&
146
- 'value' in value
147
- );
148
- }
149
-
150
- function isNVDEntry(value: unknown): value is NVDEntry {
151
- return (
152
- typeof value === 'object' &&
153
- value !== null &&
154
- 'cve' in value
155
- );
156
- }
157
-
158
- function isNVDApiResponse(value: unknown): value is NVDApiResponse {
159
- return (
160
- typeof value === 'object' &&
161
- value !== null &&
162
- 'vulnerabilities' in value
163
- );
164
- }
165
-
166
- // ============================================================================
167
- // Constants
168
- // ============================================================================
169
-
170
- const NVD_BASE_URL = 'https://services.nvd.nist.gov/rest/json/cves/2.0';
171
- const DEFAULT_MAX_RESULTS = 20;
172
- const MAX_RESULTS_PER_PAGE = 2000;
173
-
174
- // ============================================================================
175
- // NVD Rate Limiter
176
- //
177
- // NVD API has rate limits (free tier: ~5 req/30s).
178
- // This rate limiter ensures we stay within limits by:
179
- // 1. Tracking time between requests
180
- // 2. Waiting if necessary to respect the minimum interval
181
- // ============================================================================
182
-
183
- class NVDRateLimiter {
184
- private lastRequest: number = 0;
185
- // 6 seconds between requests to stay within ~5 req/30s limit
186
- private readonly minInterval: number = 6000;
187
-
188
- async acquire(): Promise<void> {
189
- const now = Date.now();
190
- const waitTime = Math.max(0, this.minInterval - (now - this.lastRequest));
191
-
192
- // Update lastRequest IMMEDIATELY to block subsequent parallel callers
193
- this.lastRequest = now + waitTime;
194
-
195
- if (waitTime > 0) {
196
- // Unref the timeout so it doesn't prevent process exit
197
- await new Promise<void>(resolve => {
198
- const timeoutId = setTimeout(resolve, waitTime);
199
- timeoutId.unref();
200
- });
201
- }
202
- }
203
- }
204
-
205
- // Singleton rate limiter instance
206
- const nvdRateLimiter = new NVDRateLimiter();
207
-
208
- // ============================================================================
209
- // Types for query options
210
- // ============================================================================
211
-
212
- type Severity = 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL';
213
-
214
- interface SearchOptions {
215
- readonly severity?: Severity;
216
- readonly maxResults?: number;
217
- readonly includeExploited?: boolean;
218
- readonly cweId?: string;
219
- readonly startDate?: string;
220
- readonly endDate?: string;
221
- }
222
-
223
- interface RetryOptions {
224
- readonly maxRetries: number;
225
- readonly initialDelay: number;
226
- readonly maxDelay: number;
227
- }
228
-
229
- // ============================================================================
230
- // Helper Functions
231
- // ============================================================================
232
-
233
- function isKnownExploited(options: SearchOptions | undefined): boolean {
234
- return options?.includeExploited === true;
235
- }
236
-
237
- function extractCVSSScore(metrics: Metrics | undefined): {
238
- readonly score: number | undefined;
239
- readonly vector: string | undefined;
240
- readonly severity: string;
241
- } {
242
- let cvssScore: number | undefined;
243
- let cvssVector: string | undefined;
244
- let severity: string = 'UNKNOWN';
245
-
246
- if (metrics?.cvssMetricV31 && metrics.cvssMetricV31.length > 0) {
247
- const firstMetric = metrics.cvssMetricV31[0];
248
- const cvssData = firstMetric?.cvssData;
249
- cvssScore = cvssData?.baseScore;
250
- cvssVector = cvssData?.vectorString;
251
- severity = cvssData?.baseSeverity ?? 'UNKNOWN';
252
- } else if (metrics?.cvssMetricV30 && metrics.cvssMetricV30.length > 0) {
253
- const firstMetric = metrics.cvssMetricV30[0];
254
- const cvssData = firstMetric?.cvssData;
255
- cvssScore = cvssData?.baseScore;
256
- cvssVector = cvssData?.vectorString;
257
- severity = cvssData?.baseSeverity ?? 'UNKNOWN';
258
- }
259
-
260
- return { score: cvssScore, vector: cvssVector, severity };
261
- }
262
-
263
- function extractCWEs(cve: CVE): string[] {
264
- const cwes: string[] = [];
265
-
266
- if (cve.weaknesses) {
267
- for (const weakness of cve.weaknesses) {
268
- if (isWeakness(weakness) && weakness.description) {
269
- for (const desc of weakness.description) {
270
- if (isWeaknessDescription(desc) && typeof desc.value === 'string' && desc.value.startsWith('CWE-')) {
271
- cwes.push(desc.value);
272
- }
273
- }
274
- }
275
- }
276
- }
277
-
278
- return cwes;
279
- }
280
-
281
- function extractReferences(cve: CVE): string[] {
282
- const references: string[] = [];
283
-
284
- if (cve.references) {
285
- for (const ref of cve.references) {
286
- if (isReference(ref) && typeof ref.url === 'string') {
287
- references.push(ref.url);
288
- }
289
- }
290
- }
291
-
292
- return references;
293
- }
294
-
295
- function extractAffectedProducts(cve: CVE): string[] {
296
- const affectedProducts: string[] = [];
297
-
298
- if (cve.configurations) {
299
- for (const config of cve.configurations) {
300
- if (isConfiguration(config) && config.nodes) {
301
- for (const node of config.nodes) {
302
- if (isNode(node) && node.cpeMatch) {
303
- for (const match of node.cpeMatch) {
304
- if (isCPEMatch(match) && typeof match.criteria === 'string') {
305
- affectedProducts.push(match.criteria);
306
- }
307
- }
308
- }
309
- }
310
- }
311
- }
312
- }
313
-
314
- return affectedProducts;
315
- }
316
-
317
- function getCVEDescription(cve: CVE): string {
318
- if (cve.descriptions && cve.descriptions.length > 0) {
319
- const firstDesc = cve.descriptions[0];
320
- if (isDescription(firstDesc) && typeof firstDesc.value === 'string' && firstDesc.value.length > 0) {
321
- return firstDesc.value;
322
- }
323
- }
324
- return 'No description available';
325
- }
326
-
327
- function parseNVDEntry(nvdEntry: NVDEntry, options: SearchOptions | undefined): Vulnerability {
328
- const cve = nvdEntry.cve;
329
- const metrics = cve.metrics;
330
-
331
- const { score: cvssScore, vector: cvssVector, severity } = extractCVSSScore(metrics);
332
- const cwes = extractCWEs(cve);
333
- const references = extractReferences(cve);
334
- const affectedProducts = extractAffectedProducts(cve);
335
- const knownExploited = isKnownExploited(options);
336
-
337
- return {
338
- id: cve.id ?? 'UNKNOWN',
339
- source: 'nvd',
340
- severity,
341
- description: getCVEDescription(cve),
342
- published: cve.published,
343
- modified: cve.lastModified,
344
- cvssScore,
345
- cvssVector,
346
- cwes,
347
- references,
348
- affectedProducts,
349
- fixes: [], // NVD doesn't provide fix info directly
350
- knownExploited,
351
- };
352
- }
353
-
354
- function parseNVDResponse(data: unknown, options: SearchOptions | undefined): Vulnerability[] {
355
- if (!isNVDApiResponse(data)) {
356
- return [];
357
- }
358
-
359
- if (Array.isArray(data.vulnerabilities)) {
360
- return data.vulnerabilities
361
- .filter(isNVDEntry)
362
- .map((entry) => parseNVDEntry(entry, options));
363
- }
364
-
365
- return [];
366
- }
367
-
368
- function buildURL(term: string, options: SearchOptions | undefined, maxResults: number): string {
369
- const params = new globalThis.URLSearchParams();
370
-
371
- // Add keyword search (supports multiple keywords with AND logic)
372
- params.append('keywordSearch', term);
373
-
374
- // Add optional filters
375
- if (options?.severity) {
376
- params.append('cvssV3Severity', options.severity);
377
- }
378
- if (options?.includeExploited) {
379
- params.append('hasKev', '');
380
- }
381
- if (options?.cweId) {
382
- params.append('cweId', options.cweId);
383
- }
384
- if (options?.startDate && options.endDate) {
385
- params.append('pubStartDate', options.startDate);
386
- params.append('pubEndDate', options.endDate);
387
- }
388
- params.append('resultsPerPage', maxResults.toString());
389
-
390
- return `${NVD_BASE_URL}?${params.toString()}`;
391
- }
392
-
393
- function createFetchOptions(): RequestInit {
394
- return {
395
- headers: {
396
- 'User-Agent': 'pi-research/2.0',
397
- 'Accept': 'application/json',
398
- },
399
- signal: createTimeoutSignal(30000), // 30s timeout
400
- };
401
- }
402
-
403
- function handleFetchError(error: unknown): never {
404
- if (error instanceof Error) {
405
- if (error.name === 'AbortError' || error.name === 'DOMException') {
406
- throw new Error(`NVD API timeout: ${error.message}`);
407
- }
408
- throw new Error(`NVD API network error: ${error.message}`);
409
- }
410
- throw new Error(`NVD API network error: ${String(error)}`);
411
- }
412
-
413
- function handleResponseStatus(response: Response): void {
414
- if (!response.ok) {
415
- // Handle rate limit specifically with a more helpful message
416
- if (response.status === 429) {
417
- throw new Error('NVD API rate limit exceeded (HTTP 429). Retrying with backoff...');
418
- }
419
- // Server errors (5xx) are also retryable
420
- if (response.status >= 500) {
421
- throw new Error(`NVD server error (HTTP ${response.status}). Retrying with backoff...`);
422
- }
423
- // Client errors (4xx except 429) are not retryable
424
- throw new Error(`HTTP ${response.status}: ${response.statusText}`);
425
- }
426
- }
427
-
428
- function fetchWithRetry(url: string): Promise<Response> {
429
- const retryOptions: RetryOptions = {
430
- maxRetries: 3,
431
- initialDelay: 2000, // Start with 2s delay
432
- maxDelay: 10000, // Max 10s delay
433
- };
434
-
435
- return retryWithBackoff(async () => {
436
- let response: Response;
437
- try {
438
- response = await fetch(url, createFetchOptions());
439
- } catch (error) {
440
- handleFetchError(error);
441
- }
442
- handleResponseStatus(response);
443
- return response;
444
- }, retryOptions);
445
- }
446
-
447
- async function searchSingleTerm(
448
- term: string,
449
- options: SearchOptions | undefined,
450
- maxResults: number,
451
- ): Promise<Vulnerability[]> {
452
- const url = buildURL(term, options, maxResults);
453
-
454
- // Apply rate limiting before making the request
455
- await nvdRateLimiter.acquire();
456
-
457
- // Wrap fetch with retry logic for transient errors (rate limiting, server errors)
458
- const response = await fetchWithRetry(url);
459
- const data = await response.json();
460
-
461
- return parseNVDResponse(data, options);
462
- }
463
-
464
- function deduplicateVulnerabilities(vulnerabilityArrays: Vulnerability[][]): Vulnerability[] {
465
- const uniqueVulns = new Map<string, Vulnerability>();
466
-
467
- // Deduplicate by CVE ID
468
- for (const termResults of vulnerabilityArrays) {
469
- for (const vuln of termResults) {
470
- if (!uniqueVulns.has(vuln.id)) {
471
- uniqueVulns.set(vuln.id, vuln);
472
- }
473
- }
474
- }
475
-
476
- return Array.from(uniqueVulns.values());
477
- }
478
-
479
- /**
480
- * Search NVD for vulnerabilities
481
- *
482
- * @param terms - Search terms (CVE IDs, keywords, CPE names)
483
- * @param options - Optional filters
484
- * @returns Promise<NVDResult> containing vulnerability search results
485
- */
486
- export async function searchNVD(
487
- terms: string[],
488
- options?: SearchOptions,
489
- ): Promise<NVDResult> {
490
- const maxResults = Math.min(options?.maxResults ?? DEFAULT_MAX_RESULTS, MAX_RESULTS_PER_PAGE);
491
- const vulnerabilities: Vulnerability[] = [];
492
- let totalResults = 0;
493
- let error: string | undefined = undefined;
494
-
495
- try {
496
- // Search each term and combine results
497
- const searchPromises = terms.map(
498
- (term: string) => searchSingleTerm(term, options, maxResults),
499
- );
500
-
501
- const allResults = await Promise.all(searchPromises);
502
- const uniqueVulns = deduplicateVulnerabilities(allResults);
503
-
504
- totalResults = uniqueVulns.length;
505
- vulnerabilities.push(...uniqueVulns.slice(0, maxResults));
506
-
507
- } catch (err) {
508
- error = err instanceof Error ? err.message : String(err);
509
- }
510
-
511
- return {
512
- count: totalResults,
513
- vulnerabilities,
514
- error,
515
- };
516
- }
517
-
518
- /**
519
- * Get specific CVE by ID
520
- *
521
- * @param cveId - The CVE ID to fetch (e.g., "CVE-2023-1234")
522
- * @returns Promise<Vulnerability | null> containing the vulnerability or null if not found
523
- */
524
- export async function getCVEById(cveId: string): Promise<Vulnerability | null> {
525
- try {
526
- const results = await searchNVD([cveId], { maxResults: 1 });
527
- return results.vulnerabilities[0] ?? null;
528
- } catch (err) {
529
- logger.error(`[NVD] Error fetching CVE ${cveId}:`, err);
530
- return null;
531
- }
532
- }