proof-of-commitment 1.1.0 → 1.3.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 (2) hide show
  1. package/index.js +39 -9
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -140,11 +140,17 @@ ${clr(c.bold, 'Usage:')}
140
140
  npx proof-of-commitment --file pnpm-lock.yaml Audit from pnpm lock file
141
141
  npx proof-of-commitment --file requirements.txt Audit Python packages
142
142
 
143
+ ${clr(c.bold, 'Options:')}
144
+ --json Output results as JSON (exits 1 if any CRITICAL found — useful in CI)
145
+ --pypi Score PyPI packages instead of npm
146
+ --file, -f Read packages from package.json, lock file, or requirements.txt
147
+
143
148
  ${clr(c.bold, 'Examples:')}
144
149
  npx proof-of-commitment axios zod chalk
145
150
  npx proof-of-commitment --pypi litellm langchain requests
146
151
  npx proof-of-commitment --file package.json
147
152
  npx proof-of-commitment --file package-lock.json # scans ALL transitive deps
153
+ npx proof-of-commitment axios chalk --json | jq '.criticalCount'
148
154
 
149
155
  ${clr(c.bold, 'Score meaning:')}
150
156
  🔴 CRITICAL Sole maintainer + >10M downloads/wk (high-value attack target)
@@ -321,12 +327,14 @@ async function main() {
321
327
  let filePath = null;
322
328
  let isLockfile = false;
323
329
  let totalInFile = 0;
330
+ let jsonOutput = false;
324
331
 
325
332
  let i = 0;
326
333
  while (i < args.length) {
327
334
  const a = args[i];
328
335
  if (a === '--pypi') { ecosystem = 'pypi'; i++; }
329
336
  else if (a === '--npm') { ecosystem = 'npm'; i++; }
337
+ else if (a === '--json') { jsonOutput = true; i++; }
330
338
  else if (a === '--file' || a === '-f') {
331
339
  filePath = args[++i];
332
340
  i++;
@@ -345,7 +353,7 @@ async function main() {
345
353
  ecosystem = result.ecosystem;
346
354
  isLockfile = result.lockfile || false;
347
355
  totalInFile = result.totalInFile || packages.length;
348
- console.log(clr(c.dim, `Detected ${totalInFile} packages from ${filePath} (${ecosystem})`));
356
+ if (!jsonOutput) console.log(clr(c.dim, `Detected ${totalInFile} packages from ${filePath} (${ecosystem})`));
349
357
  } catch (err) {
350
358
  console.error(`Error reading ${filePath}: ${err.message}`);
351
359
  process.exit(1);
@@ -363,7 +371,7 @@ async function main() {
363
371
 
364
372
  if (packages.length <= 20) {
365
373
  // Single batch — existing behavior
366
- process.stdout.write(clr(c.dim, `Scoring ${packages.length} ${ecosystem} package${packages.length > 1 ? 's' : ''}...`));
374
+ if (!jsonOutput) process.stdout.write(clr(c.dim, `Scoring ${packages.length} ${ecosystem} package${packages.length > 1 ? 's' : ''}...`));
367
375
 
368
376
  try {
369
377
  const res = await fetch(API, {
@@ -383,12 +391,12 @@ async function main() {
383
391
  }
384
392
 
385
393
  const elapsed = ((Date.now() - t0) / 1000).toFixed(1);
386
- process.stdout.write(clr(c.dim, ` done in ${elapsed}s\n`));
394
+ if (!jsonOutput) process.stdout.write(clr(c.dim, ` done in ${elapsed}s\n`));
387
395
 
388
396
  } else {
389
397
  // Multi-batch for lock files
390
398
  const batches = Math.ceil(packages.length / 20);
391
- process.stdout.write(clr(c.dim, `Scanning ${packages.length} packages (${batches} batches in parallel)...`));
399
+ if (!jsonOutput) process.stdout.write(clr(c.dim, `Scanning ${packages.length} packages (${batches} batches in parallel)...`));
392
400
 
393
401
  let lastPct = 0;
394
402
  try {
@@ -407,24 +415,46 @@ async function main() {
407
415
  }
408
416
 
409
417
  const elapsed = ((Date.now() - t0) / 1000).toFixed(1);
410
- process.stdout.write(clr(c.dim, ` done in ${elapsed}s\n`));
418
+ if (!jsonOutput) process.stdout.write(clr(c.dim, ` done in ${elapsed}s\n`));
419
+
420
+ // JSON output: emit all results with summary
421
+ if (jsonOutput) {
422
+ const criticalCount = allResults.filter(r => r.riskFlags?.includes('CRITICAL')).length;
423
+ console.log(JSON.stringify({
424
+ totalScanned: allResults.length,
425
+ criticalCount,
426
+ results: allResults,
427
+ }, null, 2));
428
+ process.exit(criticalCount > 0 ? 1 : 0);
429
+ }
411
430
 
412
431
  // For lock files: show top 25 highest-risk packages
413
432
  const MAX_DISPLAY = 25;
414
433
  const displayed = allResults.slice(0, MAX_DISPLAY);
415
- const criticalCount = allResults.filter(r => r.riskFlags?.includes('CRITICAL')).length;
416
- const highRiskCount = allResults.filter(r => !r.riskFlags?.includes('CRITICAL') && r.score < 40).length;
417
-
418
434
  const criticalTotal = allResults.filter(r => r.riskFlags?.includes('CRITICAL')).length;
419
435
  printTable(displayed, { totalScanned: allResults.length, totalCritical: criticalTotal, lockfile: true });
420
436
  return;
421
437
  }
422
438
 
423
439
  if (!allResults || allResults.length === 0) {
424
- console.log('No results returned. Check package names and try again.');
440
+ if (jsonOutput) {
441
+ console.log(JSON.stringify({ totalScanned: 0, criticalCount: 0, results: [] }, null, 2));
442
+ } else {
443
+ console.log('No results returned. Check package names and try again.');
444
+ }
425
445
  process.exit(0);
426
446
  }
427
447
 
448
+ if (jsonOutput) {
449
+ const criticalCount = allResults.filter(r => r.riskFlags?.includes('CRITICAL')).length;
450
+ console.log(JSON.stringify({
451
+ totalScanned: allResults.length,
452
+ criticalCount,
453
+ results: allResults,
454
+ }, null, 2));
455
+ process.exit(criticalCount > 0 ? 1 : 0);
456
+ }
457
+
428
458
  printTable(allResults);
429
459
  }
430
460
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "proof-of-commitment",
3
- "version": "1.1.0",
3
+ "version": "1.3.0",
4
4
  "description": "Supply chain risk scorer for npm and PyPI packages — behavioral signals that can't be faked",
5
5
  "type": "module",
6
6
  "bin": {