proof-of-commitment 1.21.0 → 1.22.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 +224 -76
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* proof-of-commitment CLI v1.21.
|
|
3
|
+
* proof-of-commitment CLI v1.21.1
|
|
4
4
|
* Scores npm/PyPI/Cargo/Go packages on behavioral commitment signals.
|
|
5
5
|
* Usage: npx proof-of-commitment [packages...] [options]
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
const API = 'https://poc-backend.amdal-dev.workers.dev/api/audit';
|
|
8
|
+
const API = process.env.COMMIT_API_URL || 'https://poc-backend.amdal-dev.workers.dev/api/audit';
|
|
9
9
|
const KEYS_API = 'https://poc-backend.amdal-dev.workers.dev/api/keys';
|
|
10
10
|
const WATCHLIST_API = 'https://poc-backend.amdal-dev.workers.dev/api/watchlist';
|
|
11
11
|
const WEB = 'https://getcommit.dev/audit';
|
|
@@ -479,7 +479,7 @@ async function inlineSignup(results) {
|
|
|
479
479
|
|
|
480
480
|
function printHelp() {
|
|
481
481
|
console.log(`
|
|
482
|
-
${clr(c.bold, 'proof-of-commitment')} v1.21.
|
|
482
|
+
${clr(c.bold, 'proof-of-commitment')} v1.21.1 — supply chain risk scorer
|
|
483
483
|
|
|
484
484
|
${clr(c.bold, 'Usage:')}
|
|
485
485
|
npx proof-of-commitment Auto-detect manifest in current dir
|
|
@@ -506,9 +506,11 @@ ${clr(c.bold, 'Reports:')}
|
|
|
506
506
|
Saves audit-report.html to cwd + prints Markdown for GitHub issues
|
|
507
507
|
|
|
508
508
|
${clr(c.bold, 'IDE Hooks:')}
|
|
509
|
-
poc hook Install Cursor
|
|
510
|
-
poc hook --
|
|
511
|
-
poc hook --
|
|
509
|
+
poc hook Install supply chain gate for Cursor + Claude Code (blocks CRITICAL packages)
|
|
510
|
+
poc hook --cursor Install only the Cursor beforeShellExecution hook
|
|
511
|
+
poc hook --claude-code Install only the Claude Code PreToolUse hook
|
|
512
|
+
poc hook --global Install for the current user (~/.cursor + ~/.claude)
|
|
513
|
+
poc hook --uninstall Remove the hook from both Cursor and Claude Code
|
|
512
514
|
|
|
513
515
|
${clr(c.bold, 'Account:')}
|
|
514
516
|
poc login [key] Save and validate your API key (interactive or direct)
|
|
@@ -1132,10 +1134,18 @@ async function cmdLogout() {
|
|
|
1132
1134
|
}
|
|
1133
1135
|
|
|
1134
1136
|
/**
|
|
1135
|
-
* poc hook [--cursor] [--global] [--uninstall]
|
|
1136
|
-
* Install a
|
|
1137
|
-
*
|
|
1138
|
-
*
|
|
1137
|
+
* poc hook [--cursor] [--claude-code] [--global] [--uninstall]
|
|
1138
|
+
* Install a supply chain gate hook for Cursor (beforeShellExecution) and/or
|
|
1139
|
+
* Claude Code (PreToolUse) that scores packages before install.
|
|
1140
|
+
*
|
|
1141
|
+
* Writes a single hook script to ~/.commit/cursor-hook.js (the filename is
|
|
1142
|
+
* kept for backward compatibility with v1.21.x installs; the same script
|
|
1143
|
+
* now auto-detects whether stdin is in Cursor or Claude Code format and
|
|
1144
|
+
* emits the matching response shape).
|
|
1145
|
+
*
|
|
1146
|
+
* Default installs both Cursor + Claude Code configs. Pass --cursor or
|
|
1147
|
+
* --claude-code to install only one. --global writes to ~/.cursor and
|
|
1148
|
+
* ~/.claude; default writes to ./.cursor and ./.claude.
|
|
1139
1149
|
*/
|
|
1140
1150
|
async function cmdHook(args) {
|
|
1141
1151
|
const os = await import('os');
|
|
@@ -1144,18 +1154,27 @@ async function cmdHook(args) {
|
|
|
1144
1154
|
|
|
1145
1155
|
const isGlobal = args.includes('--global') || args.includes('-g');
|
|
1146
1156
|
const uninstall = args.includes('--uninstall') || args.includes('--remove');
|
|
1157
|
+
const onlyCursor = args.includes('--cursor');
|
|
1158
|
+
const onlyClaude = args.includes('--claude-code') || args.includes('--claude');
|
|
1159
|
+
// Default (no client flag) = install both. --cursor and --claude-code narrow scope.
|
|
1160
|
+
const installCursor = !onlyClaude;
|
|
1161
|
+
const installClaude = !onlyCursor;
|
|
1147
1162
|
|
|
1148
1163
|
// ── Hook script (plain Node.js, no external deps) ─────────────────────
|
|
1164
|
+
// Single script serves BOTH Cursor (beforeShellExecution) and Claude Code
|
|
1165
|
+
// (PreToolUse). It auto-detects which client called it by inspecting the
|
|
1166
|
+
// stdin JSON and emits the matching response format.
|
|
1149
1167
|
const hookScript = `#!/usr/bin/env node
|
|
1150
1168
|
/**
|
|
1151
|
-
* Commit supply chain hook for Cursor (auto-generated by \`poc hook\`)
|
|
1169
|
+
* Commit supply chain hook for Cursor + Claude Code (auto-generated by \`poc hook\`)
|
|
1152
1170
|
* Intercepts npm/pip/cargo/go install commands and scores packages
|
|
1153
1171
|
* against getcommit.dev before they run.
|
|
1154
1172
|
*
|
|
1155
1173
|
* CRITICAL packages are blocked. HIGH packages trigger confirmation.
|
|
1174
|
+
* Auto-detects Cursor vs Claude Code stdin format and replies in kind.
|
|
1156
1175
|
* Docs: https://getcommit.dev/docs/cursor-hook
|
|
1157
1176
|
*/
|
|
1158
|
-
const API = 'https://poc-backend.amdal-dev.workers.dev/api/audit';
|
|
1177
|
+
const API = process.env.COMMIT_API_URL || 'https://poc-backend.amdal-dev.workers.dev/api/audit';
|
|
1159
1178
|
const fs = require('fs');
|
|
1160
1179
|
const path = require('path');
|
|
1161
1180
|
|
|
@@ -1169,7 +1188,7 @@ function readKey() {
|
|
|
1169
1188
|
}
|
|
1170
1189
|
|
|
1171
1190
|
function parseInstall(cmd) {
|
|
1172
|
-
const t = cmd.trim();
|
|
1191
|
+
const t = (cmd || '').trim();
|
|
1173
1192
|
let m;
|
|
1174
1193
|
// npm / pnpm / yarn
|
|
1175
1194
|
m = t.match(/^(?:npm\\s+(?:i|install|add)|pnpm\\s+(?:i|install|add)|yarn\\s+add)\\s+(.+)/);
|
|
@@ -1186,11 +1205,51 @@ function parseInstall(cmd) {
|
|
|
1186
1205
|
return null;
|
|
1187
1206
|
}
|
|
1188
1207
|
|
|
1208
|
+
// Detect which client called us and how to extract the command.
|
|
1209
|
+
// Cursor: stdin = { command: 'npm install ...', workingDirectory? }
|
|
1210
|
+
// Claude Code: stdin = { tool_name: 'Bash', tool_input: { command: '...' }, hook_event_name: 'PreToolUse', ... }
|
|
1211
|
+
function detectClient(input) {
|
|
1212
|
+
if (input && input.tool_input && typeof input.tool_input.command === 'string') {
|
|
1213
|
+
return { client: 'claude-code', cmd: input.tool_input.command };
|
|
1214
|
+
}
|
|
1215
|
+
if (input && typeof input.command === 'string') {
|
|
1216
|
+
return { client: 'cursor', cmd: input.command };
|
|
1217
|
+
}
|
|
1218
|
+
return { client: 'cursor', cmd: '' };
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1221
|
+
// Emit the appropriate "no decision" / "allow" output for the detected client.
|
|
1222
|
+
function emitAllow(client) {
|
|
1223
|
+
if (client === 'claude-code') {
|
|
1224
|
+
// No stdout + exit 0 = defer to normal permission flow.
|
|
1225
|
+
return;
|
|
1226
|
+
}
|
|
1227
|
+
process.stdout.write(JSON.stringify({ permission: 'allow' }));
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1230
|
+
// Emit deny / ask in the matching format.
|
|
1231
|
+
function emit(client, decision, userMsg, agentMsg) {
|
|
1232
|
+
if (client === 'claude-code') {
|
|
1233
|
+
process.stdout.write(JSON.stringify({
|
|
1234
|
+
hookSpecificOutput: {
|
|
1235
|
+
hookEventName: 'PreToolUse',
|
|
1236
|
+
permissionDecision: decision,
|
|
1237
|
+
permissionDecisionReason: userMsg,
|
|
1238
|
+
},
|
|
1239
|
+
}));
|
|
1240
|
+
return;
|
|
1241
|
+
}
|
|
1242
|
+
const body = { permission: decision, user_message: userMsg };
|
|
1243
|
+
if (agentMsg) body.agent_message = agentMsg;
|
|
1244
|
+
process.stdout.write(JSON.stringify(body));
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1189
1247
|
async function main() {
|
|
1190
1248
|
let input;
|
|
1191
|
-
try { input = JSON.parse(fs.readFileSync('/dev/stdin', 'utf-8')); } catch {
|
|
1192
|
-
const
|
|
1193
|
-
|
|
1249
|
+
try { input = JSON.parse(fs.readFileSync('/dev/stdin', 'utf-8')); } catch { emitAllow('cursor'); return; }
|
|
1250
|
+
const { client, cmd } = detectClient(input);
|
|
1251
|
+
const parsed = parseInstall(cmd);
|
|
1252
|
+
if (!parsed || parsed.pkgs.length === 0) { emitAllow(client); return; }
|
|
1194
1253
|
|
|
1195
1254
|
const headers = { 'Content-Type': 'application/json', 'Accept': 'application/json' };
|
|
1196
1255
|
const key = readKey();
|
|
@@ -1201,7 +1260,7 @@ async function main() {
|
|
|
1201
1260
|
const timer = setTimeout(() => ctrl.abort(), 4000);
|
|
1202
1261
|
const res = await fetch(API, { method: 'POST', headers, body: JSON.stringify({ packages: parsed.pkgs, ecosystem: parsed.eco }), signal: ctrl.signal });
|
|
1203
1262
|
clearTimeout(timer);
|
|
1204
|
-
if (!res.ok && res.status !== 429) {
|
|
1263
|
+
if (!res.ok && res.status !== 429) { emitAllow(client); return; }
|
|
1205
1264
|
const data = await res.json();
|
|
1206
1265
|
const results = data.results || data.packages_already_scored || [];
|
|
1207
1266
|
|
|
@@ -1209,25 +1268,46 @@ async function main() {
|
|
|
1209
1268
|
const high = results.filter(r => (r.riskFlags || []).some(f => f.startsWith('HIGH')));
|
|
1210
1269
|
const url = 'https://getcommit.dev/audit?packages=' + parsed.pkgs.join(',') + '&ecosystem=' + parsed.eco;
|
|
1211
1270
|
|
|
1271
|
+
// Detect rate-limit hit and surface signup CTA + unscored-package warning.
|
|
1272
|
+
// Without this, hook would silently allow unscored packages on 429 (false sense of security).
|
|
1273
|
+
const rateLimited = res.status === 429;
|
|
1274
|
+
// Per-client attribution so /api/keys/create source counters split traffic cleanly.
|
|
1275
|
+
const refTag = client === 'claude-code' ? 'claude-code-hook-429' : 'cursor-hook-429';
|
|
1276
|
+
const rlUrl = rateLimited ? 'https://getcommit.dev/get-started?ref=' + refTag + '&utm_source=cli' : '';
|
|
1277
|
+
const unscored = rateLimited ? Math.max(0, parsed.pkgs.length - results.length) : 0;
|
|
1278
|
+
const rlNote = rateLimited
|
|
1279
|
+
? '\\n\\n\\u26A0 Commit free limit reached'
|
|
1280
|
+
+ (unscored > 0 ? ' \\u2014 ' + unscored + ' of ' + parsed.pkgs.length + ' package(s) NOT audited' : '')
|
|
1281
|
+
+ '\\n Free key (200/day, no card): ' + rlUrl
|
|
1282
|
+
: '';
|
|
1283
|
+
|
|
1212
1284
|
if (critical.length > 0) {
|
|
1213
1285
|
const lines = critical.map(r => ' \\u{1F534} ' + r.name + ' (score ' + (r.score||'?') + ') \\u2014 ' + (r.riskFlags||[]).slice(0,1).join(', '));
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
}));
|
|
1286
|
+
emit(client, 'deny',
|
|
1287
|
+
'\\u{1F534} Commit blocked: ' + critical.map(r=>r.name).join(', ') + ' flagged CRITICAL\\n\\n' + lines.join('\\n') + '\\n\\n\\u2192 ' + url + rlNote,
|
|
1288
|
+
'Package install blocked by Commit. CRITICAL = sole publisher + high downloads (attack surface of axios/node-ipc incidents). ' + critical.map(r=>r.name).join(', ') + '. Report: ' + url
|
|
1289
|
+
);
|
|
1219
1290
|
return;
|
|
1220
1291
|
}
|
|
1221
1292
|
if (high.length > 0) {
|
|
1222
1293
|
const lines = high.map(r => ' \\u{1F7E1} ' + r.name + ' (score ' + (r.score||'?') + ') \\u2014 ' + (r.riskFlags||[]).slice(0,1).join(', '));
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1294
|
+
emit(client, 'ask',
|
|
1295
|
+
'\\u{1F7E1} Commit: ' + high.map(r=>r.name).join(', ') + ' scored HIGH risk\\n\\n' + lines.join('\\n') + '\\n\\nProceed? \\u2192 ' + url + rlNote
|
|
1296
|
+
);
|
|
1297
|
+
return;
|
|
1298
|
+
}
|
|
1299
|
+
// Rate-limited with no critical/high in the scored partial: still alert user.
|
|
1300
|
+
// If unscored packages remain, this is a security signal (could be CRITICAL we missed).
|
|
1301
|
+
// If all packages scored clean, this is a conversion signal (drive them to sign up).
|
|
1302
|
+
if (rateLimited) {
|
|
1303
|
+
const head = unscored > 0
|
|
1304
|
+
? '\\u26A0 Commit free limit reached \\u2014 ' + unscored + ' of ' + parsed.pkgs.length + ' package(s) NOT audited'
|
|
1305
|
+
: '\\u2713 ' + parsed.pkgs.join(', ') + ' look clean (free-tier audit)';
|
|
1306
|
+
emit(client, 'ask', head + '\\n\\nFree API key (200/day, no card, 30s):\\n ' + rlUrl + '\\n\\nProceed anyway?');
|
|
1227
1307
|
return;
|
|
1228
1308
|
}
|
|
1229
|
-
|
|
1230
|
-
} catch {
|
|
1309
|
+
emitAllow(client);
|
|
1310
|
+
} catch { emitAllow(client); }
|
|
1231
1311
|
}
|
|
1232
1312
|
main();
|
|
1233
1313
|
`;
|
|
@@ -1235,35 +1315,119 @@ main();
|
|
|
1235
1315
|
const commitDir = path.join(os.homedir(), '.commit');
|
|
1236
1316
|
const hookPath = path.join(commitDir, 'cursor-hook.js');
|
|
1237
1317
|
|
|
1318
|
+
// ── Cursor config helpers ─────────────────────────────────────────────
|
|
1319
|
+
function cursorHooksFile(global) {
|
|
1320
|
+
const dir = global ? path.join(os.homedir(), '.cursor') : path.join(process.cwd(), '.cursor');
|
|
1321
|
+
return { dir, file: path.join(dir, 'hooks.json') };
|
|
1322
|
+
}
|
|
1323
|
+
|
|
1324
|
+
function installCursorHook(global) {
|
|
1325
|
+
const { dir, file } = cursorHooksFile(global);
|
|
1326
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
1327
|
+
let cfg = { version: 1, hooks: {} };
|
|
1328
|
+
if (fs.existsSync(file)) {
|
|
1329
|
+
try { cfg = JSON.parse(fs.readFileSync(file, 'utf-8')); } catch {}
|
|
1330
|
+
}
|
|
1331
|
+
if (!cfg.hooks) cfg.hooks = {};
|
|
1332
|
+
if (!cfg.hooks.beforeShellExecution) cfg.hooks.beforeShellExecution = [];
|
|
1333
|
+
const existing = cfg.hooks.beforeShellExecution.some(h => h.command?.includes('cursor-hook.js'));
|
|
1334
|
+
if (!existing) cfg.hooks.beforeShellExecution.push({ command: `node ${hookPath}` });
|
|
1335
|
+
fs.writeFileSync(file, JSON.stringify(cfg, null, 2) + '\n');
|
|
1336
|
+
return file;
|
|
1337
|
+
}
|
|
1338
|
+
|
|
1339
|
+
function uninstallCursorHook(global) {
|
|
1340
|
+
const { file } = cursorHooksFile(global);
|
|
1341
|
+
if (!fs.existsSync(file)) return false;
|
|
1342
|
+
try {
|
|
1343
|
+
const cfg = JSON.parse(fs.readFileSync(file, 'utf-8'));
|
|
1344
|
+
const hooks = cfg.hooks?.beforeShellExecution || [];
|
|
1345
|
+
const filtered = hooks.filter(h => !h.command?.includes('cursor-hook.js'));
|
|
1346
|
+
if (filtered.length === hooks.length) return false;
|
|
1347
|
+
cfg.hooks.beforeShellExecution = filtered;
|
|
1348
|
+
fs.writeFileSync(file, JSON.stringify(cfg, null, 2) + '\n');
|
|
1349
|
+
return true;
|
|
1350
|
+
} catch { return false; }
|
|
1351
|
+
}
|
|
1352
|
+
|
|
1353
|
+
// ── Claude Code config helpers ────────────────────────────────────────
|
|
1354
|
+
function claudeSettingsFile(global) {
|
|
1355
|
+
const dir = global ? path.join(os.homedir(), '.claude') : path.join(process.cwd(), '.claude');
|
|
1356
|
+
return { dir, file: path.join(dir, 'settings.json') };
|
|
1357
|
+
}
|
|
1358
|
+
|
|
1359
|
+
function installClaudeHook(global) {
|
|
1360
|
+
const { dir, file } = claudeSettingsFile(global);
|
|
1361
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
1362
|
+
let cfg = {};
|
|
1363
|
+
if (fs.existsSync(file)) {
|
|
1364
|
+
try { cfg = JSON.parse(fs.readFileSync(file, 'utf-8')); } catch {}
|
|
1365
|
+
}
|
|
1366
|
+
if (!cfg.hooks) cfg.hooks = {};
|
|
1367
|
+
if (!Array.isArray(cfg.hooks.PreToolUse)) cfg.hooks.PreToolUse = [];
|
|
1368
|
+
|
|
1369
|
+
// Find or create the Bash matcher entry.
|
|
1370
|
+
let bashEntry = cfg.hooks.PreToolUse.find(e => e.matcher === 'Bash');
|
|
1371
|
+
if (!bashEntry) {
|
|
1372
|
+
bashEntry = { matcher: 'Bash', hooks: [] };
|
|
1373
|
+
cfg.hooks.PreToolUse.push(bashEntry);
|
|
1374
|
+
}
|
|
1375
|
+
if (!Array.isArray(bashEntry.hooks)) bashEntry.hooks = [];
|
|
1376
|
+
const existing = bashEntry.hooks.some(h => h.command?.includes('cursor-hook.js'));
|
|
1377
|
+
if (!existing) {
|
|
1378
|
+
bashEntry.hooks.push({
|
|
1379
|
+
type: 'command',
|
|
1380
|
+
command: `node ${hookPath}`,
|
|
1381
|
+
timeout: 10,
|
|
1382
|
+
});
|
|
1383
|
+
}
|
|
1384
|
+
fs.writeFileSync(file, JSON.stringify(cfg, null, 2) + '\n');
|
|
1385
|
+
return file;
|
|
1386
|
+
}
|
|
1387
|
+
|
|
1388
|
+
function uninstallClaudeHook(global) {
|
|
1389
|
+
const { file } = claudeSettingsFile(global);
|
|
1390
|
+
if (!fs.existsSync(file)) return false;
|
|
1391
|
+
try {
|
|
1392
|
+
const cfg = JSON.parse(fs.readFileSync(file, 'utf-8'));
|
|
1393
|
+
const pre = cfg.hooks?.PreToolUse || [];
|
|
1394
|
+
let changed = false;
|
|
1395
|
+
for (const entry of pre) {
|
|
1396
|
+
if (!Array.isArray(entry.hooks)) continue;
|
|
1397
|
+
const before = entry.hooks.length;
|
|
1398
|
+
entry.hooks = entry.hooks.filter(h => !h.command?.includes('cursor-hook.js'));
|
|
1399
|
+
if (entry.hooks.length !== before) changed = true;
|
|
1400
|
+
}
|
|
1401
|
+
// Drop empty Bash entries so we don't leave a dangling matcher.
|
|
1402
|
+
cfg.hooks.PreToolUse = pre.filter(e => !(e.matcher === 'Bash' && (!e.hooks || e.hooks.length === 0)));
|
|
1403
|
+
if (!changed) return false;
|
|
1404
|
+
fs.writeFileSync(file, JSON.stringify(cfg, null, 2) + '\n');
|
|
1405
|
+
return true;
|
|
1406
|
+
} catch { return false; }
|
|
1407
|
+
}
|
|
1408
|
+
|
|
1238
1409
|
// ── Uninstall ──────────────────────────────────────────────────────────
|
|
1239
1410
|
if (uninstall) {
|
|
1240
1411
|
let removed = false;
|
|
1241
|
-
if (fs.existsSync(hookPath)) {
|
|
1242
|
-
|
|
1243
|
-
|
|
1412
|
+
if (fs.existsSync(hookPath)) { fs.unlinkSync(hookPath); removed = true; }
|
|
1413
|
+
|
|
1414
|
+
// Cursor: clean both project and global, regardless of flags — be thorough.
|
|
1415
|
+
for (const g of [false, true]) {
|
|
1416
|
+
if (uninstallCursorHook(g)) {
|
|
1417
|
+
removed = true;
|
|
1418
|
+
console.log(clr(c.dim, ` Updated: ${cursorHooksFile(g).file}`));
|
|
1419
|
+
}
|
|
1244
1420
|
}
|
|
1245
|
-
//
|
|
1246
|
-
for (const
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
const hooksFile = path.join(hooksDir, 'hooks.json');
|
|
1251
|
-
if (fs.existsSync(hooksFile)) {
|
|
1252
|
-
try {
|
|
1253
|
-
const cfg = JSON.parse(fs.readFileSync(hooksFile, 'utf-8'));
|
|
1254
|
-
const hooks = cfg.hooks?.beforeShellExecution || [];
|
|
1255
|
-
const filtered = hooks.filter(h => !h.command?.includes('cursor-hook.js'));
|
|
1256
|
-
if (filtered.length !== hooks.length) {
|
|
1257
|
-
cfg.hooks.beforeShellExecution = filtered;
|
|
1258
|
-
fs.writeFileSync(hooksFile, JSON.stringify(cfg, null, 2) + '\n');
|
|
1259
|
-
removed = true;
|
|
1260
|
-
console.log(clr(c.dim, ` Updated: ${hooksFile}`));
|
|
1261
|
-
}
|
|
1262
|
-
} catch {}
|
|
1421
|
+
// Claude Code: same.
|
|
1422
|
+
for (const g of [false, true]) {
|
|
1423
|
+
if (uninstallClaudeHook(g)) {
|
|
1424
|
+
removed = true;
|
|
1425
|
+
console.log(clr(c.dim, ` Updated: ${claudeSettingsFile(g).file}`));
|
|
1263
1426
|
}
|
|
1264
1427
|
}
|
|
1428
|
+
|
|
1265
1429
|
if (removed) {
|
|
1266
|
-
console.log(clr(c.green, '\n ✓
|
|
1430
|
+
console.log(clr(c.green, '\n ✓ Commit hook uninstalled (Cursor + Claude Code).'));
|
|
1267
1431
|
} else {
|
|
1268
1432
|
console.log(clr(c.dim, '\n No hook found to remove.'));
|
|
1269
1433
|
}
|
|
@@ -1276,49 +1440,33 @@ main();
|
|
|
1276
1440
|
if (!fs.existsSync(commitDir)) fs.mkdirSync(commitDir, { recursive: true });
|
|
1277
1441
|
fs.writeFileSync(hookPath, hookScript, { mode: 0o755 });
|
|
1278
1442
|
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
: path.join(process.cwd(), '.cursor');
|
|
1283
|
-
if (!fs.existsSync(hooksDir)) fs.mkdirSync(hooksDir, { recursive: true });
|
|
1284
|
-
|
|
1285
|
-
const hooksFile = path.join(hooksDir, 'hooks.json');
|
|
1286
|
-
let cfg = { version: 1, hooks: {} };
|
|
1287
|
-
if (fs.existsSync(hooksFile)) {
|
|
1288
|
-
try { cfg = JSON.parse(fs.readFileSync(hooksFile, 'utf-8')); } catch {}
|
|
1289
|
-
}
|
|
1290
|
-
if (!cfg.hooks) cfg.hooks = {};
|
|
1291
|
-
if (!cfg.hooks.beforeShellExecution) cfg.hooks.beforeShellExecution = [];
|
|
1292
|
-
|
|
1293
|
-
// Avoid duplicates
|
|
1294
|
-
const existing = cfg.hooks.beforeShellExecution.some(h => h.command?.includes('cursor-hook.js'));
|
|
1295
|
-
if (!existing) {
|
|
1296
|
-
cfg.hooks.beforeShellExecution.push({
|
|
1297
|
-
command: `node ${hookPath}`,
|
|
1298
|
-
});
|
|
1299
|
-
}
|
|
1300
|
-
fs.writeFileSync(hooksFile, JSON.stringify(cfg, null, 2) + '\n');
|
|
1443
|
+
const writtenFiles = [];
|
|
1444
|
+
if (installCursor) writtenFiles.push({ client: 'Cursor', file: installCursorHook(isGlobal) });
|
|
1445
|
+
if (installClaude) writtenFiles.push({ client: 'Claude Code', file: installClaudeHook(isGlobal) });
|
|
1301
1446
|
|
|
1302
1447
|
// 3. Report
|
|
1303
|
-
|
|
1448
|
+
const clientList = writtenFiles.map(w => w.client).join(' + ');
|
|
1449
|
+
console.log(clr(c.green, `\n ✓ Commit supply chain hook installed (${clientList})`));
|
|
1304
1450
|
console.log();
|
|
1305
1451
|
console.log(clr(c.bold, ' What happens now:'));
|
|
1306
1452
|
console.log(clr(c.dim, ' Every ') + clr(c.cyan, 'npm install') + clr(c.dim, ', ') +
|
|
1307
1453
|
clr(c.cyan, 'pip install') + clr(c.dim, ', ') + clr(c.cyan, 'cargo add') + clr(c.dim, ', and ') +
|
|
1308
|
-
clr(c.cyan, 'go get') + clr(c.dim,
|
|
1454
|
+
clr(c.cyan, 'go get') + clr(c.dim, ` in ${clientList}`));
|
|
1309
1455
|
console.log(clr(c.dim, ' is scored against Commit before it runs.'));
|
|
1310
1456
|
console.log(clr(c.dim, ' CRITICAL packages are blocked. HIGH packages ask for confirmation.'));
|
|
1311
1457
|
console.log();
|
|
1312
1458
|
console.log(clr(c.bold, ' Files:'));
|
|
1313
1459
|
console.log(clr(c.dim, ` Hook script: ${hookPath}`));
|
|
1314
|
-
|
|
1460
|
+
for (const w of writtenFiles) {
|
|
1461
|
+
console.log(clr(c.dim, ` ${w.client.padEnd(11)} ${w.file}`));
|
|
1462
|
+
}
|
|
1315
1463
|
console.log();
|
|
1316
1464
|
|
|
1317
1465
|
const key = await readApiKey();
|
|
1318
1466
|
if (!key) {
|
|
1319
1467
|
console.log(clr(c.yellow, ' ⚠ No API key found.') + clr(c.dim, ' Anonymous limit: 15 audits/day.'));
|
|
1320
1468
|
console.log(clr(c.dim, ' Get a free key (200/day): ') + clr(c.cyan, 'poc login'));
|
|
1321
|
-
console.log(clr(c.dim, ' Or: ') + clr(c.cyan, 'https://getcommit.dev/get-started?ref=
|
|
1469
|
+
console.log(clr(c.dim, ' Or: ') + clr(c.cyan, 'https://getcommit.dev/get-started?ref=poc-hook&utm_source=cli'));
|
|
1322
1470
|
console.log();
|
|
1323
1471
|
}
|
|
1324
1472
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "proof-of-commitment",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.22.0",
|
|
4
4
|
"mcpName": "io.github.piiiico/proof-of-commitment",
|
|
5
5
|
"description": "Supply chain risk scorer for npm, PyPI, Cargo, and Go packages — behavioral signals that can't be faked",
|
|
6
6
|
"type": "module",
|