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.
- package/index.js +121 -13
- 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.
|
|
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
|
-
|
|
737
|
-
//
|
|
738
|
-
//
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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",
|