proof-of-commitment 1.29.2 → 1.30.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/index.js +91 -26
- 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.30.0
|
|
4
4
|
* Scores npm/PyPI/Cargo/Go packages on behavioral commitment signals.
|
|
5
5
|
* Usage: npx proof-of-commitment [packages...] [options]
|
|
6
6
|
*/
|
|
@@ -596,7 +596,7 @@ function printTable(results, { totalScanned, totalCritical, lockfile } = {}) {
|
|
|
596
596
|
console.log(clr(c.cyan, `\n 🔗 Full report: ${WEB}?packages=${encodeURIComponent(topPkgs)}&${utm}`));
|
|
597
597
|
console.log(clr(c.cyan, ` 🤖 GitHub Action: github.com/piiiico/commit-action — block CRITICAL packages in CI`));
|
|
598
598
|
console.log(clr(c.dim, ` 📋 Add to this project: `) + clr(c.cyan, `poc init`) + clr(c.dim, ` — creates workflow + README badge`));
|
|
599
|
-
console.log(clr(c.dim, ` 🛡️ Protect every install: `) + clr(c.cyan, `poc hook`) + clr(c.dim, ` — Cursor hook, blocks CRITICAL before npm/pip/cargo runs`));
|
|
599
|
+
console.log(clr(c.dim, ` 🛡️ Protect every install: `) + clr(c.cyan, `poc hook`) + clr(c.dim, ` — Cursor/Claude Code/Windsurf hook, blocks CRITICAL before npm/pip/cargo runs`));
|
|
600
600
|
|
|
601
601
|
// Per-package profile URLs — drive traffic to permanent, indexable pages
|
|
602
602
|
const ecoPath = { npm: 'npm', pypi: 'pypi', cargo: 'cargo', golang: 'go' };
|
|
@@ -714,7 +714,7 @@ async function inlineSignup(results, opts = {}) {
|
|
|
714
714
|
// (5) packages today. Server-confirmed repeat-use signal independent of
|
|
715
715
|
// local result shape.
|
|
716
716
|
const engagementSignal = !!opts.engagementSignal;
|
|
717
|
-
// 2026-06-11 v1.
|
|
717
|
+
// 2026-06-11 v1.30.0 proposition shift: gate relaxed to results.length<1.
|
|
718
718
|
// Prior gates (`<3 && !hasFindings && !engagementSignal`) blocked the most
|
|
719
719
|
// common entry point — `npx proof-of-commitment axios` after reading about
|
|
720
720
|
// an attack — when the result was healthy. The watchlist auto-seed shipped
|
|
@@ -726,7 +726,7 @@ async function inlineSignup(results, opts = {}) {
|
|
|
726
726
|
if (results.length < 1) return;
|
|
727
727
|
|
|
728
728
|
// Heading copy: lead with the proposition (auto-watch + alert on attack),
|
|
729
|
-
// not the friction (quota wall). Pre-v1.
|
|
729
|
+
// not the friction (quota wall). Pre-v1.30.0 the engagementSignal heading
|
|
730
730
|
// was wall-approach quota framing (see git log for prior copy) — friction-
|
|
731
731
|
// removal for a user the system has already identified as security-engaged.
|
|
732
732
|
// New framing names what they actually get: watchlist seeded from this
|
|
@@ -846,7 +846,7 @@ async function inlineSignup(results, opts = {}) {
|
|
|
846
846
|
|
|
847
847
|
function printHelp() {
|
|
848
848
|
console.log(`
|
|
849
|
-
${clr(c.bold, 'proof-of-commitment')} v1.
|
|
849
|
+
${clr(c.bold, 'proof-of-commitment')} v1.30.0 — supply chain risk scorer
|
|
850
850
|
|
|
851
851
|
${clr(c.bold, 'Usage:')}
|
|
852
852
|
npx proof-of-commitment Auto-detect manifest in current dir
|
|
@@ -874,11 +874,12 @@ ${clr(c.bold, 'Reports:')}
|
|
|
874
874
|
Saves audit-report.html to cwd + prints Markdown for GitHub issues
|
|
875
875
|
|
|
876
876
|
${clr(c.bold, 'IDE Hooks:')}
|
|
877
|
-
poc hook Install supply chain gate for Cursor + Claude Code
|
|
877
|
+
poc hook Install supply chain gate for Cursor + Claude Code + Windsurf
|
|
878
878
|
poc hook --cursor Install only the Cursor beforeShellExecution hook
|
|
879
879
|
poc hook --claude-code Install only the Claude Code PreToolUse hook
|
|
880
|
-
poc hook --
|
|
881
|
-
poc hook --
|
|
880
|
+
poc hook --windsurf Install only the Windsurf pre_run_command hook
|
|
881
|
+
poc hook --global Install for the current user (~/.cursor + ~/.claude + ~/.codeium/windsurf)
|
|
882
|
+
poc hook --uninstall Remove the hook from all IDEs
|
|
882
883
|
|
|
883
884
|
${clr(c.bold, 'Account:')}
|
|
884
885
|
poc login [key] Save and validate your API key (interactive or direct)
|
|
@@ -1504,18 +1505,20 @@ async function cmdLogout() {
|
|
|
1504
1505
|
}
|
|
1505
1506
|
|
|
1506
1507
|
/**
|
|
1507
|
-
* poc hook [--cursor] [--claude-code] [--global] [--uninstall]
|
|
1508
|
-
* Install a supply chain gate hook for Cursor (beforeShellExecution)
|
|
1509
|
-
* Claude Code (PreToolUse)
|
|
1508
|
+
* poc hook [--cursor] [--claude-code] [--windsurf] [--global] [--uninstall]
|
|
1509
|
+
* Install a supply chain gate hook for Cursor (beforeShellExecution),
|
|
1510
|
+
* Claude Code (PreToolUse), and/or Windsurf (pre_run_command) that scores
|
|
1511
|
+
* packages before install.
|
|
1510
1512
|
*
|
|
1511
1513
|
* Writes a single hook script to ~/.commit/cursor-hook.js (the filename is
|
|
1512
1514
|
* kept for backward compatibility with v1.21.x installs; the same script
|
|
1513
1515
|
* now auto-detects whether stdin is in Cursor or Claude Code format and
|
|
1514
1516
|
* emits the matching response shape).
|
|
1515
1517
|
*
|
|
1516
|
-
* Default installs
|
|
1517
|
-
* --claude-code to install only one.
|
|
1518
|
-
*
|
|
1518
|
+
* Default installs all three (Cursor + Claude Code + Windsurf). Pass
|
|
1519
|
+
* --cursor, --claude-code, or --windsurf to install only one.
|
|
1520
|
+
* --global writes to ~/.cursor, ~/.claude, and ~/.codeium/windsurf;
|
|
1521
|
+
* default writes to ./.cursor, ./.claude, and ./.windsurf.
|
|
1519
1522
|
*/
|
|
1520
1523
|
async function cmdHook(args) {
|
|
1521
1524
|
const os = await import('os');
|
|
@@ -1526,22 +1529,26 @@ async function cmdHook(args) {
|
|
|
1526
1529
|
const uninstall = args.includes('--uninstall') || args.includes('--remove');
|
|
1527
1530
|
const onlyCursor = args.includes('--cursor');
|
|
1528
1531
|
const onlyClaude = args.includes('--claude-code') || args.includes('--claude');
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
const
|
|
1532
|
+
const onlyWindsurf = args.includes('--windsurf');
|
|
1533
|
+
// Default (no client flag) = install all three. Narrow with --cursor, --claude-code, or --windsurf.
|
|
1534
|
+
const hasClientFlag = onlyCursor || onlyClaude || onlyWindsurf;
|
|
1535
|
+
const installCursor = hasClientFlag ? onlyCursor : true;
|
|
1536
|
+
const installClaude = hasClientFlag ? onlyClaude : true;
|
|
1537
|
+
const installWindsurf = hasClientFlag ? onlyWindsurf : true;
|
|
1532
1538
|
|
|
1533
1539
|
// ── Hook script (plain Node.js, no external deps) ─────────────────────
|
|
1534
|
-
// Single script serves
|
|
1535
|
-
// (PreToolUse). It auto-detects which
|
|
1536
|
-
// stdin JSON and emits the matching
|
|
1540
|
+
// Single script serves Cursor (beforeShellExecution), Claude Code
|
|
1541
|
+
// (PreToolUse), AND Windsurf (pre_run_command). It auto-detects which
|
|
1542
|
+
// client called it by inspecting the stdin JSON and emits the matching
|
|
1543
|
+
// response format.
|
|
1537
1544
|
const hookScript = `#!/usr/bin/env node
|
|
1538
1545
|
/**
|
|
1539
|
-
* Commit supply chain hook for Cursor + Claude Code (auto-generated by \`poc hook\`)
|
|
1546
|
+
* Commit supply chain hook for Cursor + Claude Code + Windsurf (auto-generated by \`poc hook\`)
|
|
1540
1547
|
* Intercepts npm/pip/cargo/go install commands and scores packages
|
|
1541
1548
|
* against getcommit.dev before they run.
|
|
1542
1549
|
*
|
|
1543
1550
|
* CRITICAL packages are blocked. HIGH packages trigger confirmation.
|
|
1544
|
-
* Auto-detects Cursor vs Claude Code stdin format and replies in kind.
|
|
1551
|
+
* Auto-detects Cursor vs Claude Code vs Windsurf stdin format and replies in kind.
|
|
1545
1552
|
* Docs: https://getcommit.dev/docs/cursor-hook
|
|
1546
1553
|
*/
|
|
1547
1554
|
const API = process.env.COMMIT_API_URL || 'https://poc-backend.amdal-dev.workers.dev/api/audit';
|
|
@@ -1578,7 +1585,11 @@ function parseInstall(cmd) {
|
|
|
1578
1585
|
// Detect which client called us and how to extract the command.
|
|
1579
1586
|
// Cursor: stdin = { command: 'npm install ...', workingDirectory? }
|
|
1580
1587
|
// Claude Code: stdin = { tool_name: 'Bash', tool_input: { command: '...' }, hook_event_name: 'PreToolUse', ... }
|
|
1588
|
+
// Windsurf: stdin = { agent_action_name: 'pre_run_command', tool_info: { command_line: '...' } }
|
|
1581
1589
|
function detectClient(input) {
|
|
1590
|
+
if (input && input.agent_action_name === 'pre_run_command' && input.tool_info) {
|
|
1591
|
+
return { client: 'windsurf', cmd: input.tool_info.command_line || '' };
|
|
1592
|
+
}
|
|
1582
1593
|
if (input && input.tool_input && typeof input.tool_input.command === 'string') {
|
|
1583
1594
|
return { client: 'claude-code', cmd: input.tool_input.command };
|
|
1584
1595
|
}
|
|
@@ -1590,8 +1601,8 @@ function detectClient(input) {
|
|
|
1590
1601
|
|
|
1591
1602
|
// Emit the appropriate "no decision" / "allow" output for the detected client.
|
|
1592
1603
|
function emitAllow(client) {
|
|
1593
|
-
if (client === 'claude-code') {
|
|
1594
|
-
// No stdout + exit 0 = defer to normal permission flow.
|
|
1604
|
+
if (client === 'claude-code' || client === 'windsurf') {
|
|
1605
|
+
// No stdout + exit 0 = allow / defer to normal permission flow.
|
|
1595
1606
|
return;
|
|
1596
1607
|
}
|
|
1597
1608
|
process.stdout.write(JSON.stringify({ permission: 'allow' }));
|
|
@@ -1599,6 +1610,12 @@ function emitAllow(client) {
|
|
|
1599
1610
|
|
|
1600
1611
|
// Emit deny / ask in the matching format.
|
|
1601
1612
|
function emit(client, decision, userMsg, agentMsg) {
|
|
1613
|
+
if (client === 'windsurf') {
|
|
1614
|
+
// Windsurf uses exit codes: 0 = allow, 2 = block. stderr = message shown in Cascade UI.
|
|
1615
|
+
process.stderr.write(userMsg.replace(/\\\\n/g, '\\n'));
|
|
1616
|
+
process.exit(2);
|
|
1617
|
+
return;
|
|
1618
|
+
}
|
|
1602
1619
|
if (client === 'claude-code') {
|
|
1603
1620
|
process.stdout.write(JSON.stringify({
|
|
1604
1621
|
hookSpecificOutput: {
|
|
@@ -1642,7 +1659,7 @@ async function main() {
|
|
|
1642
1659
|
// Without this, hook would silently allow unscored packages on 429 (false sense of security).
|
|
1643
1660
|
const rateLimited = res.status === 429;
|
|
1644
1661
|
// Per-client attribution so /api/keys/create source counters split traffic cleanly.
|
|
1645
|
-
const refTag = client === 'claude-code' ? 'claude-code-hook-429' : 'cursor-hook-429';
|
|
1662
|
+
const refTag = client === 'claude-code' ? 'claude-code-hook-429' : client === 'windsurf' ? 'windsurf-hook-429' : 'cursor-hook-429';
|
|
1646
1663
|
const rlUrl = rateLimited ? 'https://getcommit.dev/get-started?ref=' + refTag + '&utm_source=cli' : '';
|
|
1647
1664
|
const unscored = rateLimited ? Math.max(0, parsed.pkgs.length - results.length) : 0;
|
|
1648
1665
|
const rlNote = rateLimited
|
|
@@ -1776,6 +1793,46 @@ main();
|
|
|
1776
1793
|
} catch { return false; }
|
|
1777
1794
|
}
|
|
1778
1795
|
|
|
1796
|
+
// ── Windsurf config helpers ──────────────────────────────────────────
|
|
1797
|
+
function windsurfHooksFile(global) {
|
|
1798
|
+
const dir = global ? path.join(os.homedir(), '.codeium', 'windsurf') : path.join(process.cwd(), '.windsurf');
|
|
1799
|
+
return { dir, file: path.join(dir, 'hooks.json') };
|
|
1800
|
+
}
|
|
1801
|
+
|
|
1802
|
+
function installWindsurfHook(global) {
|
|
1803
|
+
const { dir, file } = windsurfHooksFile(global);
|
|
1804
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
1805
|
+
let cfg = { hooks: {} };
|
|
1806
|
+
if (fs.existsSync(file)) {
|
|
1807
|
+
try { cfg = JSON.parse(fs.readFileSync(file, 'utf-8')); } catch {}
|
|
1808
|
+
}
|
|
1809
|
+
if (!cfg.hooks) cfg.hooks = {};
|
|
1810
|
+
if (!Array.isArray(cfg.hooks.pre_run_command)) cfg.hooks.pre_run_command = [];
|
|
1811
|
+
const existing = cfg.hooks.pre_run_command.some(h => h.command?.includes('cursor-hook.js'));
|
|
1812
|
+
if (!existing) {
|
|
1813
|
+
cfg.hooks.pre_run_command.push({
|
|
1814
|
+
command: `node ${hookPath}`,
|
|
1815
|
+
show_output: true
|
|
1816
|
+
});
|
|
1817
|
+
}
|
|
1818
|
+
fs.writeFileSync(file, JSON.stringify(cfg, null, 2) + '\n');
|
|
1819
|
+
return file;
|
|
1820
|
+
}
|
|
1821
|
+
|
|
1822
|
+
function uninstallWindsurfHook(global) {
|
|
1823
|
+
const { file } = windsurfHooksFile(global);
|
|
1824
|
+
if (!fs.existsSync(file)) return false;
|
|
1825
|
+
try {
|
|
1826
|
+
const cfg = JSON.parse(fs.readFileSync(file, 'utf-8'));
|
|
1827
|
+
const hooks = cfg.hooks?.pre_run_command || [];
|
|
1828
|
+
const filtered = hooks.filter(h => !h.command?.includes('cursor-hook.js'));
|
|
1829
|
+
if (filtered.length === hooks.length) return false;
|
|
1830
|
+
cfg.hooks.pre_run_command = filtered;
|
|
1831
|
+
fs.writeFileSync(file, JSON.stringify(cfg, null, 2) + '\n');
|
|
1832
|
+
return true;
|
|
1833
|
+
} catch { return false; }
|
|
1834
|
+
}
|
|
1835
|
+
|
|
1779
1836
|
// ── Uninstall ──────────────────────────────────────────────────────────
|
|
1780
1837
|
if (uninstall) {
|
|
1781
1838
|
let removed = false;
|
|
@@ -1795,9 +1852,16 @@ main();
|
|
|
1795
1852
|
console.log(clr(c.dim, ` Updated: ${claudeSettingsFile(g).file}`));
|
|
1796
1853
|
}
|
|
1797
1854
|
}
|
|
1855
|
+
// Windsurf: same.
|
|
1856
|
+
for (const g of [false, true]) {
|
|
1857
|
+
if (uninstallWindsurfHook(g)) {
|
|
1858
|
+
removed = true;
|
|
1859
|
+
console.log(clr(c.dim, ` Updated: ${windsurfHooksFile(g).file}`));
|
|
1860
|
+
}
|
|
1861
|
+
}
|
|
1798
1862
|
|
|
1799
1863
|
if (removed) {
|
|
1800
|
-
console.log(clr(c.green, '\n ✓ Commit hook uninstalled (Cursor + Claude Code).'));
|
|
1864
|
+
console.log(clr(c.green, '\n ✓ Commit hook uninstalled (Cursor + Claude Code + Windsurf).'));
|
|
1801
1865
|
} else {
|
|
1802
1866
|
console.log(clr(c.dim, '\n No hook found to remove.'));
|
|
1803
1867
|
}
|
|
@@ -1813,6 +1877,7 @@ main();
|
|
|
1813
1877
|
const writtenFiles = [];
|
|
1814
1878
|
if (installCursor) writtenFiles.push({ client: 'Cursor', file: installCursorHook(isGlobal) });
|
|
1815
1879
|
if (installClaude) writtenFiles.push({ client: 'Claude Code', file: installClaudeHook(isGlobal) });
|
|
1880
|
+
if (installWindsurf) writtenFiles.push({ client: 'Windsurf', file: installWindsurfHook(isGlobal) });
|
|
1816
1881
|
|
|
1817
1882
|
// 3. Report
|
|
1818
1883
|
const clientList = writtenFiles.map(w => w.client).join(' + ');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "proof-of-commitment",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.30.0",
|
|
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",
|