vettcode-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/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,525 @@
1
+ "use strict";
2
+ /**
3
+ * Verification Layer
4
+ * Validates AI findings to prevent hallucinations and false positives
5
+ * Cross-references with static analysis and code context
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.verifyFindings = verifyFindings;
9
+ exports.deduplicateFindings = deduplicateFindings;
10
+ exports.calculateReportConfidence = calculateReportConfidence;
11
+ /**
12
+ * Cross-verify AI findings with static analysis results
13
+ */
14
+ function verifyFindings(aiFindings, staticFindings, codeFiles) {
15
+ const verified = [];
16
+ const falsePositives = [];
17
+ for (const aiFinding of aiFindings) {
18
+ const verification = verifyIndividualFinding(aiFinding, staticFindings, codeFiles);
19
+ if (verification.verificationStatus === "false-positive") {
20
+ falsePositives.push(aiFinding);
21
+ }
22
+ else {
23
+ verified.push(verification);
24
+ }
25
+ }
26
+ // Sort by confidence and severity
27
+ verified.sort((a, b) => {
28
+ const confidenceScore = { high: 3, medium: 2, low: 1 };
29
+ const severityScore = { critical: 5, high: 4, medium: 3, low: 2, info: 1 };
30
+ const aScore = confidenceScore[a.confidence] + severityScore[a.severity];
31
+ const bScore = confidenceScore[b.confidence] + severityScore[b.severity];
32
+ return bScore - aScore;
33
+ });
34
+ const summary = {
35
+ totalFindings: aiFindings.length,
36
+ confirmed: verified.filter(f => f.verificationStatus === "confirmed").length,
37
+ likely: verified.filter(f => f.verificationStatus === "likely").length,
38
+ uncertain: verified.filter(f => f.verificationStatus === "uncertain").length,
39
+ falsePositives: falsePositives.length,
40
+ };
41
+ return { verified, falsePositives, summary };
42
+ }
43
+ function verifyIndividualFinding(aiFinding, staticFindings, codeFiles) {
44
+ const sources = ["ai-analysis"];
45
+ let confidence = "medium";
46
+ let verificationStatus = "likely";
47
+ let verificationNotes = "";
48
+ // 1. Check if static analysis also found this issue
49
+ const matchingStatic = staticFindings.find(sf => sf.file === aiFinding.file &&
50
+ Math.abs(sf.line - aiFinding.line) <= 3 && // Within 3 lines
51
+ sf.category === aiFinding.category);
52
+ if (matchingStatic) {
53
+ sources.push("static-analysis");
54
+ confidence = "high";
55
+ verificationStatus = "confirmed";
56
+ verificationNotes = "Confirmed by static analysis";
57
+ }
58
+ // 2. Verify the evidence actually exists in the code
59
+ const file = codeFiles.find(f => f.path === aiFinding.file);
60
+ if (file) {
61
+ const lines = file.content.split("\n");
62
+ const targetLine = lines[aiFinding.line - 1];
63
+ if (!targetLine) {
64
+ verificationStatus = "false-positive";
65
+ verificationNotes = "Line number does not exist in file";
66
+ confidence = "low";
67
+ }
68
+ else {
69
+ // Check if evidence matches actual code
70
+ // Ensure evidence is a string before calling trim()
71
+ const evidenceStr = typeof aiFinding.evidence === 'string' ? aiFinding.evidence : String(aiFinding.evidence || '');
72
+ const evidenceNormalized = evidenceStr.trim().replace(/\s+/g, " ");
73
+ const lineNormalized = targetLine.trim().replace(/\s+/g, " ");
74
+ if (evidenceNormalized && (lineNormalized.includes(evidenceNormalized) || evidenceNormalized.includes(lineNormalized))) {
75
+ sources.push("pattern-match");
76
+ if (verificationStatus === "likely") {
77
+ verificationStatus = "confirmed";
78
+ verificationNotes = "Evidence matches code exactly";
79
+ confidence = "high";
80
+ }
81
+ }
82
+ else {
83
+ // Evidence doesn't match - might be hallucination
84
+ if (verificationStatus !== "confirmed") {
85
+ verificationStatus = "uncertain";
86
+ verificationNotes = "Evidence does not match actual code at specified line";
87
+ confidence = "low";
88
+ }
89
+ }
90
+ }
91
+ }
92
+ else {
93
+ verificationStatus = "false-positive";
94
+ verificationNotes = "File not found in codebase";
95
+ confidence = "low";
96
+ }
97
+ // 3. Severity-specific validation
98
+ if (aiFinding.severity === "critical" || aiFinding.severity === "high") {
99
+ // High-severity findings need stronger evidence
100
+ if (sources.length < 2 && verificationStatus !== "confirmed") {
101
+ confidence = confidence === "high" ? "medium" : "low";
102
+ verificationNotes += "; High-severity finding needs stronger evidence";
103
+ }
104
+ }
105
+ // 4. Category-specific validation
106
+ const categoryValidation = validateByCategory(aiFinding, file?.content);
107
+ if (categoryValidation.isFalsePositive) {
108
+ verificationStatus = "false-positive";
109
+ verificationNotes = categoryValidation.reason || "Marked as false positive";
110
+ confidence = "low";
111
+ }
112
+ else if (categoryValidation.adjustConfidence) {
113
+ confidence = categoryValidation.adjustConfidence;
114
+ verificationNotes += categoryValidation.reason ? `; ${categoryValidation.reason}` : "";
115
+ }
116
+ return {
117
+ ...aiFinding,
118
+ confidence,
119
+ verificationStatus,
120
+ verificationNotes,
121
+ sources,
122
+ source: sources.includes("static-analysis") ? "verified" : "ai", // Tag AI-discovered findings
123
+ };
124
+ }
125
+ function validateByCategory(finding, fileContent) {
126
+ if (!fileContent)
127
+ return { isFalsePositive: false };
128
+ const lines = fileContent.split("\n");
129
+ const contextStart = Math.max(0, finding.line - 5);
130
+ const contextEnd = Math.min(lines.length, finding.line + 5);
131
+ const context = lines.slice(contextStart, contextEnd).join("\n");
132
+ const fullContext = lines.slice(Math.max(0, finding.line - 20), Math.min(lines.length, finding.line + 20)).join("\n");
133
+ switch (finding.category) {
134
+ case "security":
135
+ // SQL Injection validation
136
+ if (finding.title.toLowerCase().includes("sql injection")) {
137
+ // Check if parameterized queries are used
138
+ if (/\$\d+|\?|:[\w]+/.test(context)) {
139
+ return {
140
+ isFalsePositive: true,
141
+ reason: "Uses parameterized queries, not vulnerable to SQL injection",
142
+ };
143
+ }
144
+ // Check for ORM usage (Prisma, TypeORM, Sequelize, Mongoose, etc.)
145
+ if (/prisma\.|typeorm\.|sequelize\.|mongoose\./i.test(context)) {
146
+ return {
147
+ isFalsePositive: true,
148
+ reason: "Uses ORM with built-in SQL injection protection",
149
+ };
150
+ }
151
+ // Check for query builders (Knex, etc.)
152
+ if (/knex\(|\.where\(|\.select\(|\.insert\(/i.test(context)) {
153
+ return {
154
+ isFalsePositive: true,
155
+ reason: "Uses query builder with parameterization",
156
+ };
157
+ }
158
+ }
159
+ // XSS validation
160
+ if (finding.title.toLowerCase().includes("xss") || finding.title.toLowerCase().includes("cross-site scripting")) {
161
+ // Check for sanitization
162
+ if (/DOMPurify|sanitize|escape|encodeURIComponent|textContent/i.test(fullContext)) {
163
+ return {
164
+ isFalsePositive: true,
165
+ reason: "Content is sanitized before rendering",
166
+ };
167
+ }
168
+ // React/Next.js automatically escapes by default
169
+ if (/\{.*\}|dangerouslySetInnerHTML/.test(context)) {
170
+ if (!/dangerouslySetInnerHTML/.test(context)) {
171
+ return {
172
+ isFalsePositive: true,
173
+ reason: "React/Next.js automatically escapes JSX expressions",
174
+ };
175
+ }
176
+ }
177
+ }
178
+ // Hardcoded secrets validation
179
+ if (finding.title.toLowerCase().includes("hardcoded") || finding.title.toLowerCase().includes("secret") || finding.title.toLowerCase().includes("api key")) {
180
+ // Check if it's actually an env variable or placeholder
181
+ if (/process\.env|import\.meta\.env|YOUR_|XXX|PLACEHOLDER|EXAMPLE|TEST_|DEMO_/i.test(context)) {
182
+ return {
183
+ isFalsePositive: true,
184
+ reason: "Uses environment variable or is a placeholder/example",
185
+ };
186
+ }
187
+ // Check for common test/example patterns
188
+ if (/sk_test_|pk_test_|test-|demo-|example-/i.test(finding.evidence)) {
189
+ return {
190
+ isFalsePositive: true,
191
+ reason: "Test or example key, not production secret",
192
+ };
193
+ }
194
+ }
195
+ // Authentication bypass validation
196
+ if (finding.title.toLowerCase().includes("authentication") && finding.title.toLowerCase().includes("bypass")) {
197
+ // Check if there's actual auth middleware or JWT validation
198
+ if (/authenticateToken|isAuthenticated|requireAuth|protect|verifyToken|jwt\.verify|jsonwebtoken|authHeader|Authorization|Bearer/i.test(fullContext)) {
199
+ return {
200
+ isFalsePositive: true,
201
+ reason: "Authentication middleware or token validation is present",
202
+ };
203
+ }
204
+ }
205
+ // Missing authentication check validation
206
+ if (finding.title.toLowerCase().includes("missing") && finding.title.toLowerCase().includes("authentication")) {
207
+ // Check if auth is actually implemented
208
+ if (/req\.headers\.get\(['"]authorization['"]\)|authHeader|Bearer|jwt\.verify|verifyToken|requireAuth|isAuthenticated/i.test(fullContext)) {
209
+ return {
210
+ isFalsePositive: true,
211
+ reason: "Authentication check is present in the code",
212
+ };
213
+ }
214
+ // Check if it's a public endpoint (login, register, health, etc.)
215
+ if (/\/login|\/register|\/signup|\/health|\/ping|\/public|\/webhook/i.test(fileContent)) {
216
+ return {
217
+ isFalsePositive: true,
218
+ reason: "Public endpoint, authentication not required",
219
+ };
220
+ }
221
+ }
222
+ // Rate limiting validation
223
+ if (finding.title.toLowerCase().includes("rate limit")) {
224
+ // Check for rate limiting implementation
225
+ if (/rateLimit|limiter|throttle|maxRequests|keyLock|requestCount|resetAt/i.test(fullContext)) {
226
+ return {
227
+ isFalsePositive: true,
228
+ reason: "Rate limiting is implemented",
229
+ };
230
+ }
231
+ }
232
+ // API key exposure validation
233
+ if (finding.title.toLowerCase().includes("api key") && finding.title.toLowerCase().includes("exposed")) {
234
+ // Check if it's using environment variables
235
+ if (/process\.env|import\.meta\.env|Deno\.env/i.test(context)) {
236
+ return {
237
+ isFalsePositive: true,
238
+ reason: "API keys are loaded from environment variables, not exposed",
239
+ };
240
+ }
241
+ }
242
+ // JSON injection validation
243
+ if (finding.title.toLowerCase().includes("json injection")) {
244
+ // Check for input validation or sanitization
245
+ if (/JSON\.parse\([^)]*\).*try|try.*JSON\.parse|\.replace\(|sanitize|validate|DOMPurify/i.test(fullContext)) {
246
+ return {
247
+ isFalsePositive: true,
248
+ reason: "JSON parsing is wrapped in error handling or input is sanitized",
249
+ };
250
+ }
251
+ // Check for malicious pattern detection
252
+ if (/__proto__|constructor.*prototype|<script|javascript:|on\w+=/i.test(fullContext)) {
253
+ return {
254
+ isFalsePositive: false,
255
+ adjustConfidence: "high",
256
+ reason: "Code checks for malicious patterns, but verify completeness",
257
+ };
258
+ }
259
+ }
260
+ // Race condition validation
261
+ if (finding.title.toLowerCase().includes("race condition")) {
262
+ // Check for mutex, locks, or queue implementation
263
+ if (/mutex|lock|queue|await\s+queue|semaphore|Promise\.all\(.*map/i.test(fullContext)) {
264
+ return {
265
+ isFalsePositive: true,
266
+ reason: "Synchronization mechanism (mutex/lock/queue) is implemented",
267
+ };
268
+ }
269
+ }
270
+ break;
271
+ case "production":
272
+ // Unhandled promise validation
273
+ if (finding.title.toLowerCase().includes("unhandled")) {
274
+ // Check for try-catch or .catch() or finally
275
+ if (/try\s*\{|\.catch\(|\.finally\(|\.then\([^,)]+,/i.test(fullContext)) {
276
+ return {
277
+ isFalsePositive: true,
278
+ reason: "Error handling is present (try-catch or .catch() or promise rejection handler)",
279
+ };
280
+ }
281
+ // Check if it's wrapped in another async function with error handling
282
+ if (/async\s+function/.test(fullContext) && /try\s*\{/.test(fullContext)) {
283
+ return {
284
+ isFalsePositive: true,
285
+ reason: "Async function with try-catch error handling",
286
+ };
287
+ }
288
+ }
289
+ // Missing validation validation
290
+ if (finding.title.toLowerCase().includes("missing") && finding.title.toLowerCase().includes("validation")) {
291
+ // Check for validation libraries (Zod, Joi, Yup, etc.)
292
+ if (/\.parse\(|\.validate\(|\.schema\(|z\.|Joi\.|yup\./i.test(fullContext)) {
293
+ return {
294
+ isFalsePositive: true,
295
+ reason: "Validation is implemented using validation library",
296
+ };
297
+ }
298
+ // Check for manual validation
299
+ if (/if\s*\([^)]*(?:!|typeof|instanceof|Array\.isArray)\s*[^)]*\)\s*\{?\s*(?:throw|return)/i.test(fullContext)) {
300
+ return {
301
+ isFalsePositive: false,
302
+ adjustConfidence: "medium",
303
+ reason: "Manual validation is present, review for completeness",
304
+ };
305
+ }
306
+ }
307
+ break;
308
+ case "database":
309
+ // N+1 query validation
310
+ if (finding.title.toLowerCase().includes("n+1") || finding.title.toLowerCase().includes("n-plus-1")) {
311
+ // Check if it's actually a problem (small arrays are fine)
312
+ if (/\.slice\(0,\s*[1-9]\)|\.take\([1-9]\)|\.limit\([1-9]\)/i.test(context)) {
313
+ return {
314
+ isFalsePositive: true,
315
+ reason: "Limited to small number of items, not a performance issue",
316
+ };
317
+ }
318
+ // Check for eager loading (Prisma include, TypeORM relations, etc.)
319
+ if (/include\s*:|relations\s*:|populate\(/i.test(fullContext)) {
320
+ return {
321
+ isFalsePositive: true,
322
+ reason: "Uses eager loading to prevent N+1 queries",
323
+ };
324
+ }
325
+ }
326
+ // Missing index validation
327
+ if (finding.title.toLowerCase().includes("index") || finding.title.toLowerCase().includes("slow query")) {
328
+ // Check if it's a read-only or small table
329
+ if (/\.findMany\(\)\s*\.length\s*<\s*\d+|LIMIT\s+\d+/i.test(context)) {
330
+ return {
331
+ isFalsePositive: false,
332
+ adjustConfidence: "low",
333
+ reason: "Query appears to be limited, may not need index",
334
+ };
335
+ }
336
+ }
337
+ break;
338
+ case "code-quality":
339
+ // AI placeholder code validation
340
+ if (finding.title.toLowerCase().includes("placeholder") || finding.title.toLowerCase().includes("ai-generated")) {
341
+ // Check if the code has actual implementation
342
+ if (/const\s+\w+\s*=\s*await|return\s+\w+|throw\s+new\s+Error|if\s*\(|for\s*\(|while\s*\(/i.test(context)) {
343
+ return {
344
+ isFalsePositive: true,
345
+ reason: "Code has actual implementation, not a placeholder",
346
+ };
347
+ }
348
+ // Check for TODO or FIXME comments which indicate it's genuinely incomplete
349
+ if (!/TODO|FIXME|PLACEHOLDER|FIX THIS|IMPLEMENT THIS/i.test(fullContext)) {
350
+ return {
351
+ isFalsePositive: true,
352
+ reason: "No placeholder markers found, appears to be complete code",
353
+ };
354
+ }
355
+ }
356
+ // Magic number validation
357
+ if (finding.title.toLowerCase().includes("magic number")) {
358
+ // HTTP status codes are acceptable
359
+ if (/\b(?:200|201|204|301|302|304|400|401|403|404|409|422|500|502|503)\b/.test(finding.evidence)) {
360
+ return {
361
+ isFalsePositive: true,
362
+ reason: "HTTP status code, not a magic number",
363
+ };
364
+ }
365
+ // Common constants (0, 1, -1, 100, 1000) are often acceptable
366
+ if (/\b(?:0|1|-1|100|1000)\b/.test(finding.evidence) && !/\*\s*(?:0|1|-1|100|1000)|(?:0|1|-1|100|1000)\s*\*/.test(context)) {
367
+ return {
368
+ isFalsePositive: false,
369
+ adjustConfidence: "low",
370
+ reason: "Common constant, review if it should be extracted",
371
+ };
372
+ }
373
+ }
374
+ // Console.log validation
375
+ if (finding.title.toLowerCase().includes("console") && finding.title.toLowerCase().includes("log")) {
376
+ // Check if it's debug logging wrapped in condition
377
+ if (/if\s*\([^)]*(?:debug|dev|development)[^)]*\)/i.test(fullContext)) {
378
+ return {
379
+ isFalsePositive: false,
380
+ adjustConfidence: "low",
381
+ reason: "Conditional debug logging, may be intentional",
382
+ };
383
+ }
384
+ // Check if it's in a development file
385
+ if (finding.file.includes("dev") || finding.file.includes("test") || finding.file.includes("debug")) {
386
+ return {
387
+ isFalsePositive: true,
388
+ reason: "Development/test file, console.log is acceptable",
389
+ };
390
+ }
391
+ }
392
+ // TODO comments validation
393
+ if (finding.title.toLowerCase().includes("todo")) {
394
+ // TODOs in test files are less critical
395
+ if (finding.file.includes("test") || finding.file.includes("spec") || finding.file.includes("__tests__")) {
396
+ return {
397
+ isFalsePositive: false,
398
+ adjustConfidence: "low",
399
+ reason: "TODO in test file, lower priority",
400
+ };
401
+ }
402
+ }
403
+ break;
404
+ case "react":
405
+ // Missing key prop validation
406
+ if (finding.title.toLowerCase().includes("key")) {
407
+ // Check if key is actually present nearby
408
+ if (/key\s*=/i.test(context)) {
409
+ return {
410
+ isFalsePositive: true,
411
+ reason: "Key prop is present",
412
+ };
413
+ }
414
+ // Check if it's mapping a static array
415
+ if (/\[(["'][^"']+["'],?\s*){1,5}\]\.map/i.test(fullContext)) {
416
+ return {
417
+ isFalsePositive: false,
418
+ adjustConfidence: "low",
419
+ reason: "Static array with few items, key may not be critical",
420
+ };
421
+ }
422
+ }
423
+ // useEffect dependency validation
424
+ if (finding.title.toLowerCase().includes("useeffect") && finding.title.toLowerCase().includes("dependency")) {
425
+ // Check if dependency is explicitly omitted with eslint-disable
426
+ if (/eslint-disable|eslint-disable-next-line/.test(context)) {
427
+ return {
428
+ isFalsePositive: false,
429
+ adjustConfidence: "medium",
430
+ reason: "Dependency intentionally omitted with ESLint disable",
431
+ };
432
+ }
433
+ }
434
+ break;
435
+ case "performance":
436
+ // Large bundle size validation
437
+ if (finding.title.toLowerCase().includes("bundle") || finding.title.toLowerCase().includes("import")) {
438
+ // Check for dynamic imports
439
+ if (/import\(|React\.lazy|next\/dynamic/i.test(fullContext)) {
440
+ return {
441
+ isFalsePositive: true,
442
+ reason: "Uses dynamic imports for code splitting",
443
+ };
444
+ }
445
+ }
446
+ break;
447
+ case "typing":
448
+ // Missing type validation
449
+ if (finding.title.toLowerCase().includes("type") && (finding.title.toLowerCase().includes("missing") || finding.title.toLowerCase().includes("any"))) {
450
+ // Check if it's JavaScript file (types not required)
451
+ if (finding.file.endsWith(".js") || finding.file.endsWith(".jsx")) {
452
+ return {
453
+ isFalsePositive: true,
454
+ reason: "JavaScript file, types not required",
455
+ };
456
+ }
457
+ // Check if any is intentional with comment
458
+ if (/\/\/\s*@ts-ignore|\/\*.*any.*\*\/|eslint-disable/i.test(context)) {
459
+ return {
460
+ isFalsePositive: false,
461
+ adjustConfidence: "medium",
462
+ reason: "Type explicitly ignored or documented",
463
+ };
464
+ }
465
+ }
466
+ break;
467
+ }
468
+ return { isFalsePositive: false };
469
+ }
470
+ /**
471
+ * Merge duplicate findings (same issue reported multiple times)
472
+ */
473
+ function deduplicateFindings(findings) {
474
+ const seen = new Map();
475
+ for (const finding of findings) {
476
+ // Create a key based on file, line, and category
477
+ const key = `${finding.file}:${finding.line}:${finding.category}`;
478
+ const existing = seen.get(key);
479
+ if (existing) {
480
+ // Keep the one with higher confidence
481
+ if (finding.confidence === "high" && existing.confidence !== "high") {
482
+ seen.set(key, finding);
483
+ }
484
+ else if (finding.verificationStatus === "confirmed" && existing.verificationStatus !== "confirmed") {
485
+ seen.set(key, finding);
486
+ }
487
+ // Merge sources
488
+ existing.sources = [...new Set([...existing.sources, ...finding.sources])];
489
+ }
490
+ else {
491
+ seen.set(key, finding);
492
+ }
493
+ }
494
+ return Array.from(seen.values());
495
+ }
496
+ /**
497
+ * Calculate overall confidence score for the entire report
498
+ */
499
+ function calculateReportConfidence(findings) {
500
+ if (findings.length === 0) {
501
+ return {
502
+ score: 100,
503
+ grade: "A+",
504
+ explanation: "No findings to verify",
505
+ };
506
+ }
507
+ const confirmedCount = findings.filter(f => f.verificationStatus === "confirmed").length;
508
+ const likelyCount = findings.filter(f => f.verificationStatus === "likely").length;
509
+ const uncertainCount = findings.filter(f => f.verificationStatus === "uncertain").length;
510
+ // Weighted confidence score
511
+ const score = Math.round(((confirmedCount * 1.0 + likelyCount * 0.7 + uncertainCount * 0.3) / findings.length) * 100);
512
+ let grade = "F";
513
+ if (score >= 90)
514
+ grade = "A+";
515
+ else if (score >= 80)
516
+ grade = "A";
517
+ else if (score >= 70)
518
+ grade = "B";
519
+ else if (score >= 60)
520
+ grade = "C";
521
+ else if (score >= 50)
522
+ grade = "D";
523
+ const explanation = `${confirmedCount} confirmed, ${likelyCount} likely, ${uncertainCount} uncertain out of ${findings.length} total findings`;
524
+ return { score, grade, explanation };
525
+ }
package/package.json ADDED
@@ -0,0 +1,61 @@
1
+ {
2
+ "name": "vettcode-cli",
3
+ "version": "1.0.0",
4
+ "description": "CLI version of VettCode - AI-powered codebase security and quality scanner",
5
+ "main": "dist/cli.js",
6
+ "bin": {
7
+ "vettcode": "./dist/cli.js"
8
+ },
9
+ "scripts": {
10
+ "build": "tsc",
11
+ "dev": "tsc && node dist/cli.js",
12
+ "start": "node dist/cli.js",
13
+ "lint": "eslint src --ext .ts",
14
+ "prepublishOnly": "npm run build"
15
+ },
16
+ "keywords": [
17
+ "security",
18
+ "code-scanner",
19
+ "vulnerability",
20
+ "static-analysis",
21
+ "cli",
22
+ "ast",
23
+ "ai",
24
+ "code-review",
25
+ "sast",
26
+ "security-audit"
27
+ ],
28
+ "author": "",
29
+ "license": "MIT",
30
+ "repository": {
31
+ "type": "git",
32
+ "url": "https://github.com/mixifys33/vettcode-cli.git"
33
+ },
34
+ "homepage": "https://github.com/mixifys33/vettcode-cli#readme",
35
+ "bugs": {
36
+ "url": "https://github.com/mixifys33/vettcode-cli/issues"
37
+ },
38
+ "files": [
39
+ "dist",
40
+ "README.md",
41
+ ".env.example",
42
+ "LICENSE"
43
+ ],
44
+ "engines": {
45
+ "node": ">=16.0.0"
46
+ },
47
+ "dependencies": {
48
+ "@babel/parser": "^7.26.3",
49
+ "@babel/traverse": "^7.26.5",
50
+ "chalk": "^4.1.2",
51
+ "commander": "^11.1.0",
52
+ "ora": "^5.4.1",
53
+ "cli-table3": "^0.6.3",
54
+ "dotenv": "^16.4.0"
55
+ },
56
+ "devDependencies": {
57
+ "@types/babel__traverse": "^7.20.6",
58
+ "@types/node": "^22.10.0",
59
+ "typescript": "^5.7.0"
60
+ }
61
+ }