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.
Files changed (2) hide show
  1. package/index.js +91 -26
  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.29.2
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.29.2 proposition shift: gate relaxed to results.length<1.
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.29.2 the engagementSignal heading
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.29.2 — supply chain risk scorer
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 (blocks CRITICAL packages)
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 --global Install for the current user (~/.cursor + ~/.claude)
881
- poc hook --uninstall Remove the hook from both Cursor and Claude Code
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) and/or
1509
- * Claude Code (PreToolUse) that scores packages before install.
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 both Cursor + Claude Code configs. Pass --cursor or
1517
- * --claude-code to install only one. --global writes to ~/.cursor and
1518
- * ~/.claude; default writes to ./.cursor and ./.claude.
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
- // Default (no client flag) = install both. --cursor and --claude-code narrow scope.
1530
- const installCursor = !onlyClaude;
1531
- const installClaude = !onlyCursor;
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 BOTH Cursor (beforeShellExecution) and Claude Code
1535
- // (PreToolUse). It auto-detects which client called it by inspecting the
1536
- // stdin JSON and emits the matching response format.
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.29.2",
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",