proof-of-commitment 1.28.0 → 1.29.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 (2) hide show
  1. package/index.js +121 -13
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * proof-of-commitment CLI v1.28.0
3
+ * proof-of-commitment CLI v1.29.0
4
4
  * Scores npm/PyPI/Cargo/Go packages on behavioral commitment signals.
5
5
  * Usage: npx proof-of-commitment [packages...] [options]
6
6
  */
@@ -649,6 +649,59 @@ function printTable(results, { totalScanned, totalCritical, lockfile } = {}) {
649
649
  * intent. Copy adapts to context: degradation alerts (CRITICAL) vs baseline
650
650
  * lock-in (healthy). Quick lookups (<3 packages) still skip the prompt.
651
651
  */
652
+ /**
653
+ * Build the top-3-by-risk-priority watch seeds for /api/keys/create body.watch.
654
+ *
655
+ * Mirrors the web-side buildWatchSeeds at
656
+ * commit-landing-v2/src/pages/audit.astro:1299 so the two signup paths
657
+ * (web audit form vs CLI inline-prompt) feed the backend with the same
658
+ * shape — backend then writes the user's default project's
659
+ * monitored_packages BEFORE the welcome email so step 1 names actual
660
+ * packages instead of hardcoded `poc watch express / lodash` examples.
661
+ *
662
+ * Priority: compromised > CRITICAL > HIGH > others (any flags) > clean.
663
+ * The free-tier cap (3) is enforced both here and again on the backend
664
+ * (PACKAGE_LIMITS.free) — defense in depth across client-server drift.
665
+ * Validates ecosystem against the backend ECOSYSTEMS set
666
+ * (npm/pypi/cargo/golang); unknown ecosystems fall back to npm because
667
+ * the backend rejects unknowns and we want to surface SOMETHING rather
668
+ * than nothing. Filters out names that fail npm's 214-char max and
669
+ * dedupes by (name, ecosystem).
670
+ *
671
+ * Closes the second proposition-gap layer (CLI-side mirror of the
672
+ * 2026-06-11 audit-page watchlist auto-seed at abe53f1/df8a8be).
673
+ */
674
+ function buildCliWatchSeeds(results) {
675
+ if (!Array.isArray(results) || results.length === 0) return [];
676
+ const VALID_ECOS = new Set(['npm', 'pypi', 'cargo', 'golang']);
677
+ function priority(r) {
678
+ if (r.compromised) return 0;
679
+ if (Array.isArray(r.riskFlags) && r.riskFlags.some(f => typeof f === 'string' && f.startsWith('CRITICAL'))) return 1;
680
+ if (Array.isArray(r.riskFlags) && r.riskFlags.some(f => typeof f === 'string' && f.startsWith('HIGH'))) return 2;
681
+ if (Array.isArray(r.riskFlags) && r.riskFlags.length > 0) return 3;
682
+ return 4;
683
+ }
684
+ const filtered = results.filter(r => typeof r.name === 'string' && r.name.length > 0 && r.name.length < 215);
685
+ const sorted = filtered.slice().sort((a, b) => {
686
+ const pa = priority(a);
687
+ const pb = priority(b);
688
+ if (pa !== pb) return pa - pb;
689
+ return filtered.indexOf(a) - filtered.indexOf(b);
690
+ });
691
+ const seeds = [];
692
+ const seen = new Set();
693
+ for (const r of sorted) {
694
+ const rawEco = typeof r.ecosystem === 'string' ? r.ecosystem.toLowerCase() : 'npm';
695
+ const eco = VALID_ECOS.has(rawEco) ? rawEco : 'npm';
696
+ const key = `${r.name}|${eco}`;
697
+ if (seen.has(key)) continue;
698
+ seen.add(key);
699
+ seeds.push({ name: r.name, ecosystem: eco });
700
+ if (seeds.length >= 3) break; // free-tier cap (backend re-validates)
701
+ }
702
+ return seeds;
703
+ }
704
+
652
705
  async function inlineSignup(results, opts = {}) {
653
706
  // Only prompt in interactive TTY when no key saved
654
707
  if (!process.stdin.isTTY || !process.stdout.isTTY) return;
@@ -719,10 +772,22 @@ async function inlineSignup(results, opts = {}) {
719
772
  // 'cli-soft-cta' in this same commit; older worker versions drop
720
773
  // unknown sources to 'web' (safe degradation, no error).
721
774
  const source = engagementSignal && !hasFindings ? 'cli-soft-cta' : 'cli';
775
+ // Proposition shift (2026-06-11, second layer): same defect the audit-page
776
+ // welcome email had until df8a8be — pre-fix, every CLI signup got hardcoded
777
+ // "poc watch express / lodash" in their welcome email regardless of what
778
+ // they actually scanned. Web side now seeds top-3-by-risk-priority on POST
779
+ // and the backend writes them to the user's default project before sending
780
+ // the welcome email. CLI must mirror or signups via `npx proof-of-commitment
781
+ // <some-package>` still get an email orthogonal to their intent. Backend
782
+ // accepts body.watch = [{name, ecosystem}], caps at PACKAGE_LIMITS.free=3,
783
+ // echoes seededPackages back as data.watched_packages. Priority order
784
+ // (compromised > CRITICAL > HIGH > others) matches the web-side
785
+ // buildWatchSeeds at commit-landing-v2/src/pages/audit.astro:1299.
786
+ const watch = buildCliWatchSeeds(results);
722
787
  const res = await fetch('https://poc-backend.amdal-dev.workers.dev/api/keys/create', {
723
788
  method: 'POST',
724
789
  headers: { 'Content-Type': 'application/json' },
725
- body: JSON.stringify({ email, source }),
790
+ body: JSON.stringify({ email, source, watch }),
726
791
  });
727
792
 
728
793
  const data = await res.json();
@@ -733,16 +798,39 @@ async function inlineSignup(results, opts = {}) {
733
798
  console.log(clr(c.green, ' ✓ Saved to ~/.commit/config'));
734
799
  console.log(clr(c.dim, ` Backup sent to ${email}`));
735
800
  console.log();
736
- console.log(clr(c.bold, ' Next steps:'));
737
- // Surface a concrete watch target. CRITICAL first (highest urgency);
738
- // otherwise pick the lowest-score package as the most-likely-to-degrade.
739
- const watchTarget = critPkgs[0]?.name
740
- || results.slice().sort((a, b) => (a.score || 100) - (b.score || 100))[0]?.name;
741
- if (watchTarget) {
742
- console.log(clr(c.dim, ' • ') + clr(c.cyan, `poc watch ${watchTarget}`) + clr(c.dim, ' — monitor this package (free: 3 packages, weekly)'));
801
+ // Render the backend echo (data.watched_packages) the user sees
802
+ // "Now watching: foo, bar, baz" before the first weekly digest fires
803
+ // (~7d). Mirrors the audit-page renderInlineForm success state at
804
+ // commit-landing-v2/src/pages/audit.astro:1971 so on-context-switch the
805
+ // user does not see contradictory "you have nothing watched" messaging
806
+ // in `poc list`. Trust the server echo, not our pre-submit array (the
807
+ // server caps + dedups). Older backend versions that predate body.watch
808
+ // simply omit watched_packages — we fall through to the legacy
809
+ // single-target hint, no regression.
810
+ const watched = Array.isArray(data.watched_packages) ? data.watched_packages : [];
811
+ if (watched.length > 0) {
812
+ const names = watched.map(w => w.name).join(', ');
813
+ const noun = watched.length === 1 ? 'package' : 'packages';
814
+ console.log(clr(c.green, ` ✓ Now watching ${watched.length} ${noun}: ${names}`));
815
+ console.log(clr(c.dim, ' Mondays we email you if any score drops a tier or a watched package gets attacked.\n'));
816
+ console.log(clr(c.bold, ' Next steps:'));
817
+ console.log(clr(c.dim, ' • ') + clr(c.cyan, 'poc list') + clr(c.dim, ' — confirm your watchlist'));
818
+ console.log(clr(c.dim, ' • ') + clr(c.cyan, 'poc init') + clr(c.dim, ' — add CI gate to this project'));
819
+ console.log(clr(c.dim, ' • ') + clr(c.cyan, 'poc status') + clr(c.dim, ' — check your account'));
820
+ } else {
821
+ console.log(clr(c.bold, ' Next steps:'));
822
+ // Legacy fallback: backend did not seed (old worker, empty seeds, or
823
+ // seed failure swallowed). Surface a concrete watch target. CRITICAL
824
+ // first (highest urgency); otherwise pick the lowest-score package as
825
+ // the most-likely-to-degrade.
826
+ const watchTarget = critPkgs[0]?.name
827
+ || results.slice().sort((a, b) => (a.score || 100) - (b.score || 100))[0]?.name;
828
+ if (watchTarget) {
829
+ console.log(clr(c.dim, ' • ') + clr(c.cyan, `poc watch ${watchTarget}`) + clr(c.dim, ' — monitor this package (free: 3 packages, weekly)'));
830
+ }
831
+ console.log(clr(c.dim, ' • ') + clr(c.cyan, 'poc init') + clr(c.dim, ' — add CI gate to this project'));
832
+ console.log(clr(c.dim, ' • ') + clr(c.cyan, 'poc status') + clr(c.dim, ' — check your account'));
743
833
  }
744
- console.log(clr(c.dim, ' • ') + clr(c.cyan, 'poc init') + clr(c.dim, ' — add CI gate to this project'));
745
- console.log(clr(c.dim, ' • ') + clr(c.cyan, 'poc status') + clr(c.dim, ' — check your account'));
746
834
  } else if (data.message) {
747
835
  console.log(clr(c.green, ` ✓ ${data.message}`));
748
836
  } else {
@@ -757,10 +845,11 @@ async function inlineSignup(results, opts = {}) {
757
845
 
758
846
  function printHelp() {
759
847
  console.log(`
760
- ${clr(c.bold, 'proof-of-commitment')} v1.28.0 — supply chain risk scorer
848
+ ${clr(c.bold, 'proof-of-commitment')} v1.29.1 — supply chain risk scorer
761
849
 
762
850
  ${clr(c.bold, 'Usage:')}
763
851
  npx proof-of-commitment Auto-detect manifest in current dir
852
+ npx proof-of-commitment audit Same — verb-first alias (also: scan, check)
764
853
  npx proof-of-commitment [packages...] Score npm packages
765
854
  npx proof-of-commitment --pypi [pkgs...] Score PyPI packages
766
855
  npx proof-of-commitment --cargo [crates...] Score Rust crates
@@ -2377,7 +2466,26 @@ async function main() {
2377
2466
  }
2378
2467
 
2379
2468
  // Subcommands
2380
- const subcmd = args[0];
2469
+ let subcmd = args[0];
2470
+
2471
+ // Transparent aliases: every other package manager (`npm audit`, `yarn audit`,
2472
+ // `pnpm audit`, `cargo audit`, `pip-audit`) puts the verb first. Users —
2473
+ // including readers of our own blog post at npm-trust-q2-2026 line 559 — type
2474
+ // `npx proof-of-commitment audit` and expect it to scan cwd's manifest.
2475
+ //
2476
+ // Without this branch the CLI parses `audit` as a POSITIONAL PACKAGE NAME,
2477
+ // which is a 13.9y-old npmjs.com/package/audit utility — silently scoring
2478
+ // the wrong package while burning the caller's daily quota. Caught during
2479
+ // 2026-06-11 buyer-journey dogfood (full transcript in reflection).
2480
+ //
2481
+ // We shift the verb off and fall through to the main parser so all flags
2482
+ // (--file, --pypi, --cargo, --golang, --json, --sarif, --fail-on) continue
2483
+ // to work positionally: `proof-of-commitment audit lodash --json` still
2484
+ // means "scan lodash, JSON output".
2485
+ if (subcmd === 'audit' || subcmd === 'scan' || subcmd === 'check') {
2486
+ args.shift();
2487
+ subcmd = args[0];
2488
+ }
2381
2489
 
2382
2490
  if (subcmd === 'init') {
2383
2491
  await cmdInit();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "proof-of-commitment",
3
- "version": "1.28.0",
3
+ "version": "1.29.1",
4
4
  "mcpName": "io.github.piiiico/proof-of-commitment",
5
5
  "description": "Supply chain security risk scorer for npm, PyPI, Cargo, and Go packages — behavioral signals that can't be faked",
6
6
  "type": "module",