cc-hook-registry 2.1.0 → 3.1.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 +29 -1
- package/index.mjs +156 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -19,11 +19,39 @@ npx cc-hook-registry search database
|
|
|
19
19
|
```bash
|
|
20
20
|
npx cc-hook-registry search <keyword> # Find hooks by keyword
|
|
21
21
|
npx cc-hook-registry browse [category] # Browse by category
|
|
22
|
-
npx cc-hook-registry install <id> # Install a hook
|
|
22
|
+
npx cc-hook-registry install <id> # Install a hook (direct download)
|
|
23
|
+
npx cc-hook-registry recommend # Recommend hooks for your project
|
|
23
24
|
npx cc-hook-registry info <id> # Show hook details
|
|
24
25
|
npx cc-hook-registry stats # Registry statistics
|
|
25
26
|
```
|
|
26
27
|
|
|
28
|
+
### recommend
|
|
29
|
+
|
|
30
|
+
Scans your project for `package.json`, `requirements.txt`, `Dockerfile`, `.env`, `Gemfile`, `artisan` and recommends hooks based on your tech stack:
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
npx cc-hook-registry recommend
|
|
34
|
+
|
|
35
|
+
✓ destructive-guard (installed)
|
|
36
|
+
Essential — prevents rm -rf disasters
|
|
37
|
+
|
|
38
|
+
○ auto-approve-build
|
|
39
|
+
Node.js project detected
|
|
40
|
+
Install: npx cc-hook-registry install auto-approve-build
|
|
41
|
+
|
|
42
|
+
○ block-database-wipe
|
|
43
|
+
Prisma detected — protect against migrate reset
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### install (direct download)
|
|
47
|
+
|
|
48
|
+
Hooks are downloaded directly from GitHub — no `cc-safe-setup` dependency needed:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
npx cc-hook-registry install block-database-wipe
|
|
52
|
+
# Downloads script, saves to ~/.claude/hooks/, registers in settings.json
|
|
53
|
+
```
|
|
54
|
+
|
|
27
55
|
## Categories
|
|
28
56
|
|
|
29
57
|
| Category | Hooks | What They Do |
|
package/index.mjs
CHANGED
|
@@ -112,6 +112,8 @@ if (!command || command === '--help' || command === '-h') {
|
|
|
112
112
|
browse [category] Browse by category (safety, quality, approve, utility, monitoring, ux)
|
|
113
113
|
install <id> Install a hook
|
|
114
114
|
info <id> Show hook details
|
|
115
|
+
recommend Recommend hooks for current project
|
|
116
|
+
outdated Check installed hooks for updates
|
|
115
117
|
stats Registry statistics
|
|
116
118
|
|
|
117
119
|
Examples:
|
|
@@ -272,6 +274,160 @@ else if (command === 'info') {
|
|
|
272
274
|
console.log();
|
|
273
275
|
}
|
|
274
276
|
|
|
277
|
+
else if (command === 'recommend') {
|
|
278
|
+
console.log();
|
|
279
|
+
console.log(c.bold + ' Recommended hooks for this project' + c.reset);
|
|
280
|
+
console.log();
|
|
281
|
+
|
|
282
|
+
const cwd = process.cwd();
|
|
283
|
+
const recommendations = [];
|
|
284
|
+
|
|
285
|
+
// Always recommend safety essentials
|
|
286
|
+
recommendations.push({ id: 'destructive-guard', reason: 'Essential — prevents rm -rf disasters', priority: 1 });
|
|
287
|
+
recommendations.push({ id: 'branch-guard', reason: 'Essential — prevents push to main', priority: 1 });
|
|
288
|
+
recommendations.push({ id: 'secret-guard', reason: 'Essential — prevents .env commits', priority: 1 });
|
|
289
|
+
|
|
290
|
+
// Detect tech stack
|
|
291
|
+
if (existsSync(join(cwd, 'package.json'))) {
|
|
292
|
+
const pkg = JSON.parse(readFileSync(join(cwd, 'package.json'), 'utf-8'));
|
|
293
|
+
recommendations.push({ id: 'auto-approve-build', reason: 'Node.js project detected', priority: 2 });
|
|
294
|
+
if (pkg.dependencies?.prisma || pkg.devDependencies?.prisma) {
|
|
295
|
+
recommendations.push({ id: 'block-database-wipe', reason: 'Prisma detected — protect against migrate reset', priority: 1 });
|
|
296
|
+
}
|
|
297
|
+
if (pkg.scripts?.deploy || pkg.scripts?.['vercel-build']) {
|
|
298
|
+
recommendations.push({ id: 'deploy-guard', reason: 'Deploy script detected', priority: 2 });
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
if (existsSync(join(cwd, 'requirements.txt')) || existsSync(join(cwd, 'pyproject.toml'))) {
|
|
303
|
+
recommendations.push({ id: 'auto-approve-python', reason: 'Python project detected', priority: 2 });
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
if (existsSync(join(cwd, 'Dockerfile')) || existsSync(join(cwd, 'docker-compose.yml'))) {
|
|
307
|
+
recommendations.push({ id: 'auto-approve-docker', reason: 'Docker detected', priority: 2 });
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
if (existsSync(join(cwd, '.env')) || existsSync(join(cwd, '.env.local'))) {
|
|
311
|
+
recommendations.push({ id: 'env-source-guard', reason: '.env file present — prevent sourcing', priority: 1 });
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
if (existsSync(join(cwd, 'Gemfile'))) {
|
|
315
|
+
recommendations.push({ id: 'block-database-wipe', reason: 'Rails detected — protect against db:drop', priority: 1 });
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
if (existsSync(join(cwd, 'artisan'))) {
|
|
319
|
+
recommendations.push({ id: 'block-database-wipe', reason: 'Laravel detected — protect against migrate:fresh', priority: 1 });
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// Always useful
|
|
323
|
+
recommendations.push({ id: 'compound-command-approver', reason: 'Fixes permission matching for cd && commands', priority: 2 });
|
|
324
|
+
recommendations.push({ id: 'loop-detector', reason: 'Prevents infinite command loops', priority: 3 });
|
|
325
|
+
recommendations.push({ id: 'session-handoff', reason: 'Saves state for next session', priority: 3 });
|
|
326
|
+
recommendations.push({ id: 'cost-tracker', reason: 'Track session costs', priority: 3 });
|
|
327
|
+
|
|
328
|
+
// Deduplicate and sort by priority
|
|
329
|
+
const seen = new Set();
|
|
330
|
+
const unique = recommendations.filter(r => { if (seen.has(r.id)) return false; seen.add(r.id); return true; });
|
|
331
|
+
unique.sort((a, b) => a.priority - b.priority);
|
|
332
|
+
|
|
333
|
+
// Check what's already installed
|
|
334
|
+
let installed = new Set();
|
|
335
|
+
if (existsSync(SETTINGS_PATH)) {
|
|
336
|
+
try {
|
|
337
|
+
const s = JSON.parse(readFileSync(SETTINGS_PATH, 'utf-8'));
|
|
338
|
+
for (const entries of Object.values(s.hooks || {})) {
|
|
339
|
+
for (const e of entries) {
|
|
340
|
+
for (const h of (e.hooks || [])) {
|
|
341
|
+
if (h.command) installed.add(h.command.split('/').pop().replace('.sh', ''));
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
} catch {}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
for (const rec of unique) {
|
|
349
|
+
const hook = REGISTRY.find(h => h.id === rec.id);
|
|
350
|
+
if (!hook) continue;
|
|
351
|
+
const isInstalled = installed.has(rec.id);
|
|
352
|
+
const icon = isInstalled ? c.green + '✓' + c.reset : c.yellow + '○' + c.reset;
|
|
353
|
+
const status = isInstalled ? c.dim + '(installed)' + c.reset : '';
|
|
354
|
+
console.log(' ' + icon + ' ' + c.bold + rec.id + c.reset + ' ' + status);
|
|
355
|
+
console.log(' ' + c.dim + rec.reason + c.reset);
|
|
356
|
+
if (!isInstalled) {
|
|
357
|
+
console.log(' ' + c.dim + 'Install: npx cc-hook-registry install ' + rec.id + c.reset);
|
|
358
|
+
}
|
|
359
|
+
console.log();
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
const notInstalled = unique.filter(r => !installed.has(r.id));
|
|
363
|
+
if (notInstalled.length === 0) {
|
|
364
|
+
console.log(c.green + ' All recommended hooks are installed!' + c.reset);
|
|
365
|
+
} else {
|
|
366
|
+
console.log(c.dim + ' ' + notInstalled.length + ' recommended hook(s) not yet installed.' + c.reset);
|
|
367
|
+
}
|
|
368
|
+
console.log();
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
else if (command === 'outdated') {
|
|
372
|
+
console.log();
|
|
373
|
+
console.log(c.bold + ' Checking installed hooks for updates...' + c.reset);
|
|
374
|
+
console.log();
|
|
375
|
+
|
|
376
|
+
if (!existsSync(HOOKS_DIR)) {
|
|
377
|
+
console.log(c.dim + ' No hooks installed.' + c.reset);
|
|
378
|
+
process.exit(0);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
const { readdirSync } = await import('fs');
|
|
382
|
+
const installed = readdirSync(HOOKS_DIR).filter(f => f.endsWith('.sh'));
|
|
383
|
+
let outdated = 0;
|
|
384
|
+
let upToDate = 0;
|
|
385
|
+
let unknown = 0;
|
|
386
|
+
|
|
387
|
+
for (const file of installed) {
|
|
388
|
+
const name = file.replace('.sh', '');
|
|
389
|
+
const hook = REGISTRY.find(h => h.id === name);
|
|
390
|
+
const localContent = readFileSync(join(HOOKS_DIR, file), 'utf-8');
|
|
391
|
+
const localLines = localContent.split('\n').length;
|
|
392
|
+
|
|
393
|
+
if (!hook) {
|
|
394
|
+
// Custom hook, not in registry
|
|
395
|
+
console.log(' ' + c.dim + '?' + c.reset + ' ' + file + c.dim + ' (custom, not in registry)' + c.reset);
|
|
396
|
+
unknown++;
|
|
397
|
+
continue;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// Check against GitHub for cc-safe-setup hooks
|
|
401
|
+
if (hook.source === 'cc-safe-setup' && hook.install.includes('--install-example')) {
|
|
402
|
+
try {
|
|
403
|
+
const rawUrl = `https://raw.githubusercontent.com/yurukusa/cc-safe-setup/main/examples/${name}.sh`;
|
|
404
|
+
const remote = execSync(`curl -sL "${rawUrl}" 2>/dev/null`, { encoding: 'utf-8', timeout: 5000 });
|
|
405
|
+
const remoteLines = remote.split('\n').length;
|
|
406
|
+
|
|
407
|
+
if (remote.trim() === localContent.trim()) {
|
|
408
|
+
console.log(' ' + c.green + '✓' + c.reset + ' ' + file + c.dim + ' (up to date)' + c.reset);
|
|
409
|
+
upToDate++;
|
|
410
|
+
} else {
|
|
411
|
+
const diff = remoteLines - localLines;
|
|
412
|
+
console.log(' ' + c.yellow + '↑' + c.reset + ' ' + file + c.yellow + ' (update available: ' + (diff > 0 ? '+' : '') + diff + ' lines)' + c.reset);
|
|
413
|
+
console.log(' ' + c.dim + 'Update: npx cc-hook-registry install ' + name + c.reset);
|
|
414
|
+
outdated++;
|
|
415
|
+
}
|
|
416
|
+
} catch {
|
|
417
|
+
console.log(' ' + c.dim + '?' + c.reset + ' ' + file + c.dim + ' (could not check)' + c.reset);
|
|
418
|
+
unknown++;
|
|
419
|
+
}
|
|
420
|
+
} else {
|
|
421
|
+
console.log(' ' + c.dim + '—' + c.reset + ' ' + file + c.dim + ' (external: ' + hook.source + ')' + c.reset);
|
|
422
|
+
unknown++;
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
console.log();
|
|
427
|
+
console.log(' ' + c.green + upToDate + ' up to date' + c.reset + ' ' + c.yellow + outdated + ' outdated' + c.reset + ' ' + c.dim + unknown + ' unchecked' + c.reset);
|
|
428
|
+
console.log();
|
|
429
|
+
}
|
|
430
|
+
|
|
275
431
|
else if (command === 'stats') {
|
|
276
432
|
const categories = {};
|
|
277
433
|
const sources = {};
|