proof-of-commitment 1.23.0 → 1.25.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/README.md +2 -2
- package/index.js +101 -20
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
An MCP server and web tool that scores npm packages, PyPI packages, Rust crates, Go modules, and GitHub repos on **behavioral commitment** — signals that are harder to fake than stars, READMEs, or download counts.
|
|
8
8
|
|
|
9
|
-
## The supply chain problem
|
|
9
|
+
## The supply chain security problem
|
|
10
10
|
|
|
11
11
|
26 of the 91 npm packages with >10M weekly downloads have a **single npm publisher**. Together they account for over 3 billion downloads per week. `npm audit` doesn't surface this. Stars don't either.
|
|
12
12
|
|
|
@@ -93,7 +93,7 @@ Add to Claude Desktop, Cursor, Windsurf, or any MCP-compatible AI tool. Then ask
|
|
|
93
93
|
|
|
94
94
|
## GitHub Action
|
|
95
95
|
|
|
96
|
-
Add supply chain auditing to any CI pipeline in 30 seconds — auto-detects packages from `package.json` or `requirements.txt`, **posts results as a PR comment**, writes to GitHub Step Summary, and optionally fails on CRITICAL packages.
|
|
96
|
+
Add supply chain security auditing to any CI pipeline in 30 seconds — auto-detects packages from `package.json` or `requirements.txt`, **posts results as a PR comment**, writes to GitHub Step Summary, and optionally fails on CRITICAL packages.
|
|
97
97
|
|
|
98
98
|
Use the dedicated action at [piiiico/commit-action](https://github.com/piiiico/commit-action):
|
|
99
99
|
|
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.24.0
|
|
4
4
|
* Scores npm/PyPI/Cargo/Go packages on behavioral commitment signals.
|
|
5
5
|
* Usage: npx proof-of-commitment [packages...] [options]
|
|
6
6
|
*/
|
|
@@ -101,6 +101,14 @@ async function handle429(res) {
|
|
|
101
101
|
const retryAfter = Number.isFinite(data.retry_after_seconds)
|
|
102
102
|
? data.retry_after_seconds
|
|
103
103
|
: null;
|
|
104
|
+
// Backend signals "you've blown past the free wall, Developer $15/mo is the
|
|
105
|
+
// right fix" via overshoot=true / tier_suggestion="developer" (added
|
|
106
|
+
// backend-side 2026-06-04). When set, backend routes instantKeyUrl to
|
|
107
|
+
// /pricing — so the CLI must NOT promise "Free API key in 30 seconds" or
|
|
108
|
+
// prompt for email (a 200/day key won't help someone scanning 260+/day).
|
|
109
|
+
// Mismatched CTA text + destination kills trust and conversion. This branch
|
|
110
|
+
// aligns label + URL + skips the inline email prompt. (Dogfood, 2026-06-06.)
|
|
111
|
+
const overshoot = data.overshoot === true || data.tier_suggestion === 'developer';
|
|
104
112
|
|
|
105
113
|
// Forward-compat: if backend ever returns partial scoring on 429,
|
|
106
114
|
// print what we have BEFORE the rescue message. Falls back to JSON
|
|
@@ -130,6 +138,28 @@ async function handle429(res) {
|
|
|
130
138
|
}
|
|
131
139
|
console.error('');
|
|
132
140
|
|
|
141
|
+
// Overshoot path: free key is the wrong tool. Surface a URL aligned with
|
|
142
|
+
// the backend's Developer recommendation, skip the email prompt, exit.
|
|
143
|
+
// Without this branch, the CLI would say "Free API key in 30 seconds (no
|
|
144
|
+
// card)" while the URL goes to /pricing — bait-and-switch that erodes
|
|
145
|
+
// trust at the highest-intent moment we get with a user.
|
|
146
|
+
if (overshoot) {
|
|
147
|
+
console.error(
|
|
148
|
+
clr(
|
|
149
|
+
c.cyan + c.bold,
|
|
150
|
+
` → Compare plans (Developer $15/mo · 1,000/day · batch API): ${instantKeyUrl}`
|
|
151
|
+
)
|
|
152
|
+
);
|
|
153
|
+
if (retryAfter && retryAfter > 0) {
|
|
154
|
+
const hours = Math.floor(retryAfter / 3600);
|
|
155
|
+
const mins = Math.floor((retryAfter % 3600) / 60);
|
|
156
|
+
const resetIn = hours > 0 ? `${hours}h ${mins}m` : `${mins}m`;
|
|
157
|
+
console.error(clr(c.dim, ` or wait — free-tier resets in ${resetIn} (00:00 UTC).`));
|
|
158
|
+
}
|
|
159
|
+
console.error('');
|
|
160
|
+
process.exit(1);
|
|
161
|
+
}
|
|
162
|
+
|
|
133
163
|
// TTY: inline signup collapses the 6-step browser flow (visit URL → enter
|
|
134
164
|
// email → copy key → switch back to terminal → export key → re-run) to a
|
|
135
165
|
// single terminal prompt. Non-TTY (CI/piped) falls through to the URL.
|
|
@@ -383,10 +413,11 @@ function printTable(results, { totalScanned, totalCritical, lockfile } = {}) {
|
|
|
383
413
|
console.log(clr(c.dim, `\n 📊 Monitor ${effectiveCritical === 1 ? 'this package' : 'these packages'}: `) +
|
|
384
414
|
clr(c.cyan, `poc watch ${results.find(r => hasCritical(r.riskFlags))?.name || results[0]?.name}`));
|
|
385
415
|
} else if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
386
|
-
// Non-TTY (CI, piped): show
|
|
416
|
+
// Non-TTY (CI, piped): show one-step watch command since interactive prompt won't work
|
|
417
|
+
const watchPkg = results.find(r => hasCritical(r.riskFlags))?.name || results[0]?.name;
|
|
387
418
|
console.log(clr(c.dim, `\n 📊 Monitor ${effectiveCritical === 1 ? 'this' : 'these ' + effectiveCritical} CRITICAL ${effectiveCritical === 1 ? 'package' : 'packages'} — get alerted when scores change.`));
|
|
388
|
-
console.log(clr(c.dim, '
|
|
389
|
-
console.log(clr(c.dim, '
|
|
419
|
+
console.log(clr(c.dim, ' One step: ') + clr(c.cyan, `poc watch ${watchPkg} --email you@company.com`));
|
|
420
|
+
console.log(clr(c.dim, ' Free: 3 packages, weekly digest. Developer $15/mo: 15 packages, daily scans.'));
|
|
390
421
|
}
|
|
391
422
|
// else: TTY mode — inlineSignup() will prompt interactively after printTable
|
|
392
423
|
} else if (!hasKey && (!process.stdin.isTTY || !process.stdout.isTTY)) {
|
|
@@ -396,8 +427,8 @@ function printTable(results, { totalScanned, totalCritical, lockfile } = {}) {
|
|
|
396
427
|
// text only in CI/piped output where interactive prompts can't fire.
|
|
397
428
|
// ref=audit-baseline distinguishes this funnel from audit-cli-429
|
|
398
429
|
// (rate-limit rescue) and from the static utm_source=cli help-line.
|
|
399
|
-
console.log(clr(c.dim, '\n 📊
|
|
400
|
-
console.log(clr(c.dim, ' ') + clr(c.cyan, '
|
|
430
|
+
console.log(clr(c.dim, '\n 📊 Get alerted if any package degrades:'));
|
|
431
|
+
console.log(clr(c.dim, ' ') + clr(c.cyan, `poc watch ${results[0]?.name || '<package>'} --email you@company.com`) + clr(c.dim, ' (free: 3 packages, weekly digest)'));
|
|
401
432
|
}
|
|
402
433
|
console.log();
|
|
403
434
|
}
|
|
@@ -475,15 +506,15 @@ async function inlineSignup(results) {
|
|
|
475
506
|
console.log(clr(c.dim, ` Backup sent to ${email}`));
|
|
476
507
|
console.log();
|
|
477
508
|
console.log(clr(c.bold, ' Next steps:'));
|
|
478
|
-
console.log(clr(c.dim, ' • ') + clr(c.cyan, 'poc status') + clr(c.dim, ' — check your account'));
|
|
479
509
|
// Surface a concrete watch target. CRITICAL first (highest urgency);
|
|
480
510
|
// otherwise pick the lowest-score package as the most-likely-to-degrade.
|
|
481
511
|
const watchTarget = critPkgs[0]?.name
|
|
482
512
|
|| results.slice().sort((a, b) => (a.score || 100) - (b.score || 100))[0]?.name;
|
|
483
513
|
if (watchTarget) {
|
|
484
|
-
console.log(clr(c.dim, ' • ') + clr(c.cyan, `poc watch ${watchTarget}`) + clr(c.dim, ' —
|
|
514
|
+
console.log(clr(c.dim, ' • ') + clr(c.cyan, `poc watch ${watchTarget}`) + clr(c.dim, ' — monitor this package (free: 3 packages, weekly)'));
|
|
485
515
|
}
|
|
486
516
|
console.log(clr(c.dim, ' • ') + clr(c.cyan, 'poc init') + clr(c.dim, ' — add CI gate to this project'));
|
|
517
|
+
console.log(clr(c.dim, ' • ') + clr(c.cyan, 'poc status') + clr(c.dim, ' — check your account'));
|
|
487
518
|
} else if (data.message) {
|
|
488
519
|
console.log(clr(c.green, ` ✓ ${data.message}`));
|
|
489
520
|
} else {
|
|
@@ -498,7 +529,7 @@ async function inlineSignup(results) {
|
|
|
498
529
|
|
|
499
530
|
function printHelp() {
|
|
500
531
|
console.log(`
|
|
501
|
-
${clr(c.bold, 'proof-of-commitment')} v1.
|
|
532
|
+
${clr(c.bold, 'proof-of-commitment')} v1.24.0 — supply chain risk scorer
|
|
502
533
|
|
|
503
534
|
${clr(c.bold, 'Usage:')}
|
|
504
535
|
npx proof-of-commitment Auto-detect manifest in current dir
|
|
@@ -536,9 +567,9 @@ ${clr(c.bold, 'Account:')}
|
|
|
536
567
|
poc status Show current tier, usage, and limits
|
|
537
568
|
poc logout Remove saved API key
|
|
538
569
|
|
|
539
|
-
${clr(c.bold, 'Monitoring (Developer $15/mo
|
|
540
|
-
poc watch <package> [--ecosystem npm|pypi|cargo|golang]
|
|
541
|
-
Add a package to
|
|
570
|
+
${clr(c.bold, 'Monitoring (free: 3 packages weekly · Developer $15/mo: 15 daily):')}
|
|
571
|
+
poc watch <package> [--email you@co.com] [--ecosystem npm|pypi|cargo|golang]
|
|
572
|
+
Add a package to monitoring. --email creates a free key in one step.
|
|
542
573
|
poc watchlist List monitored packages with current scores + risk
|
|
543
574
|
poc unwatch <pkg> Remove a package from monitoring
|
|
544
575
|
|
|
@@ -1525,15 +1556,53 @@ async function printUpgradeRequired(res, campaign = 'watchlist-402') {
|
|
|
1525
1556
|
/**
|
|
1526
1557
|
* poc watch <package> [--ecosystem npm|pypi|cargo|golang]
|
|
1527
1558
|
*/
|
|
1528
|
-
async function cmdWatch(pkg, ecosystem) {
|
|
1529
|
-
|
|
1559
|
+
async function cmdWatch(pkg, ecosystem, emailArg) {
|
|
1560
|
+
let key = await readApiKey();
|
|
1561
|
+
|
|
1562
|
+
// --email flag: create a free key inline if none exists, collapsing the
|
|
1563
|
+
// visit-site → enter-email → copy-key → poc-login → poc-watch flow to one step.
|
|
1564
|
+
if (!key && emailArg) {
|
|
1565
|
+
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(emailArg)) {
|
|
1566
|
+
console.error(clr(c.red, 'Invalid email. Usage: poc watch <pkg> --email you@co.com'));
|
|
1567
|
+
process.exit(1);
|
|
1568
|
+
}
|
|
1569
|
+
process.stdout.write(clr(c.dim, ' Creating free API key...'));
|
|
1570
|
+
try {
|
|
1571
|
+
const createRes = await fetch('https://poc-backend.amdal-dev.workers.dev/api/keys/create', {
|
|
1572
|
+
method: 'POST',
|
|
1573
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1574
|
+
body: JSON.stringify({ email: emailArg, source: 'cli-watch' }),
|
|
1575
|
+
});
|
|
1576
|
+
const keyData = await createRes.json();
|
|
1577
|
+
if (keyData.key) {
|
|
1578
|
+
await writeApiKey(keyData.key);
|
|
1579
|
+
key = keyData.key;
|
|
1580
|
+
console.log(clr(c.green, ' ✓'));
|
|
1581
|
+
console.log(clr(c.dim, ` Key saved to ~/.commit/config. Backup sent to ${emailArg}.`));
|
|
1582
|
+
} else {
|
|
1583
|
+
const errMsg = keyData.error === 'rate_limit_exceeded'
|
|
1584
|
+
? 'Too many keys from this IP today.'
|
|
1585
|
+
: (keyData.message || 'Could not create key.');
|
|
1586
|
+
console.error(clr(c.red, ` ${errMsg}`));
|
|
1587
|
+
process.exit(1);
|
|
1588
|
+
}
|
|
1589
|
+
} catch (err) {
|
|
1590
|
+
console.error(clr(c.red, ` Error: ${err.message}`));
|
|
1591
|
+
process.exit(1);
|
|
1592
|
+
}
|
|
1593
|
+
}
|
|
1594
|
+
|
|
1530
1595
|
if (!key) {
|
|
1531
|
-
console.error(clr(c.red, 'No API key found.
|
|
1532
|
-
console.error(
|
|
1596
|
+
console.error(clr(c.red, 'No API key found.'));
|
|
1597
|
+
console.error('');
|
|
1598
|
+
console.error(clr(c.bold, ' One-step setup — creates key + starts monitoring:'));
|
|
1599
|
+
console.error(clr(c.cyan, ` poc watch ${pkg} --email you@company.com`));
|
|
1600
|
+
console.error('');
|
|
1601
|
+
console.error(clr(c.dim, ' Or set COMMIT_API_KEY / add api_key=<key> to ~/.commit/config'));
|
|
1533
1602
|
process.exit(1);
|
|
1534
1603
|
}
|
|
1535
1604
|
|
|
1536
|
-
process.stdout.write(clr(c.dim, `Adding ${pkg} (${ecosystem}) to watchlist...`));
|
|
1605
|
+
process.stdout.write(clr(c.dim, ` Adding ${pkg} (${ecosystem}) to watchlist...`));
|
|
1537
1606
|
const res = await fetch(WATCHLIST_API, {
|
|
1538
1607
|
method: 'POST',
|
|
1539
1608
|
headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${key}` },
|
|
@@ -1541,6 +1610,16 @@ async function cmdWatch(pkg, ecosystem) {
|
|
|
1541
1610
|
});
|
|
1542
1611
|
|
|
1543
1612
|
if (res.status === 402) { process.stdout.write('\n'); await printUpgradeRequired(res, 'watch-cmd'); process.exit(1); }
|
|
1613
|
+
if (res.status === 422) {
|
|
1614
|
+
const errData = await res.json().catch(() => ({}));
|
|
1615
|
+
process.stdout.write('\n');
|
|
1616
|
+
console.log(clr(c.yellow, ` ⚠ ${errData.message || 'Package limit reached.'}`));
|
|
1617
|
+
if (errData.upgrade) {
|
|
1618
|
+
console.log(clr(c.dim, ` ${errData.upgrade.message || `Upgrade to ${errData.upgrade.plan} for more:`}`));
|
|
1619
|
+
console.log(clr(c.cyan, ` ${errData.upgrade.url}`));
|
|
1620
|
+
}
|
|
1621
|
+
process.exit(1);
|
|
1622
|
+
}
|
|
1544
1623
|
|
|
1545
1624
|
const data = await res.json();
|
|
1546
1625
|
if (!res.ok) {
|
|
@@ -1552,7 +1631,7 @@ async function cmdWatch(pkg, ecosystem) {
|
|
|
1552
1631
|
process.stdout.write('\n');
|
|
1553
1632
|
if (isNew) {
|
|
1554
1633
|
console.log(clr(c.green, ` ✓ Now watching ${pkg}`));
|
|
1555
|
-
console.log(clr(c.dim, '
|
|
1634
|
+
console.log(clr(c.dim, ' Weekly digest (Mondays). Upgrade to Developer ($15/mo) for daily scans + Slack alerts.'));
|
|
1556
1635
|
} else {
|
|
1557
1636
|
console.log(clr(c.dim, ` ↩ ${pkg} is already in your watchlist`));
|
|
1558
1637
|
}
|
|
@@ -2147,15 +2226,17 @@ async function main() {
|
|
|
2147
2226
|
|
|
2148
2227
|
if (subcmd === 'watch') {
|
|
2149
2228
|
const pkg = args[1];
|
|
2150
|
-
if (!pkg) { console.error('Usage: poc watch <package> [--ecosystem npm|pypi|cargo|golang]'); process.exit(1); }
|
|
2229
|
+
if (!pkg) { console.error('Usage: poc watch <package> [--email you@co.com] [--ecosystem npm|pypi|cargo|golang]'); process.exit(1); }
|
|
2151
2230
|
let ecosystem = 'npm';
|
|
2231
|
+
let email = null;
|
|
2152
2232
|
for (let i = 2; i < args.length; i++) {
|
|
2153
2233
|
if (args[i] === '--ecosystem' || args[i] === '-e') ecosystem = args[++i] || 'npm';
|
|
2234
|
+
else if (args[i] === '--email') email = args[++i] || null;
|
|
2154
2235
|
else if (args[i] === '--pypi') ecosystem = 'pypi';
|
|
2155
2236
|
else if (args[i] === '--cargo') ecosystem = 'cargo';
|
|
2156
2237
|
else if (args[i] === '--golang' || args[i] === '--go') ecosystem = 'golang';
|
|
2157
2238
|
}
|
|
2158
|
-
await cmdWatch(pkg, ecosystem);
|
|
2239
|
+
await cmdWatch(pkg, ecosystem, email);
|
|
2159
2240
|
process.exit(0);
|
|
2160
2241
|
}
|
|
2161
2242
|
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "proof-of-commitment",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.25.0",
|
|
4
4
|
"mcpName": "io.github.piiiico/proof-of-commitment",
|
|
5
|
-
"description": "Supply chain risk scorer for npm, PyPI, Cargo, and Go packages — behavioral signals that can't be faked",
|
|
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",
|
|
7
7
|
"bin": {
|
|
8
8
|
"proof-of-commitment": "./index.js",
|
|
@@ -16,7 +16,9 @@
|
|
|
16
16
|
],
|
|
17
17
|
"keywords": [
|
|
18
18
|
"supply-chain",
|
|
19
|
+
"supply-chain-security",
|
|
19
20
|
"security",
|
|
21
|
+
"scanner",
|
|
20
22
|
"npm",
|
|
21
23
|
"pypi",
|
|
22
24
|
"cargo",
|