cc-hook-registry 4.1.0 → 5.0.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.mjs +119 -0
  2. package/package.json +1 -1
package/index.mjs CHANGED
@@ -113,6 +113,7 @@ if (!command || command === '--help' || command === '-h') {
113
113
  install <id> Install a hook
114
114
  info <id> Show hook details
115
115
  recommend Recommend hooks for current project
116
+ init Interactive setup — install recommended hooks
116
117
  list List all installed hooks with status
117
118
  update [id] Update one or all installed hooks
118
119
  uninstall <id> Remove an installed hook
@@ -371,6 +372,124 @@ else if (command === 'recommend') {
371
372
  console.log();
372
373
  }
373
374
 
375
+ else if (command === 'init') {
376
+ console.log();
377
+ console.log(c.bold + ' cc-hook-registry init' + c.reset);
378
+ console.log(c.dim + ' Quick setup — installing essential + project-specific hooks' + c.reset);
379
+ console.log();
380
+
381
+ const cwd = process.cwd();
382
+ const toInstall = [];
383
+
384
+ // Essential hooks (always install)
385
+ toInstall.push('destructive-guard', 'branch-guard', 'secret-guard');
386
+
387
+ // Detect project type
388
+ if (existsSync(join(cwd, 'package.json'))) {
389
+ console.log(' ' + c.blue + '⬡' + c.reset + ' Node.js detected');
390
+ toInstall.push('auto-approve-build');
391
+ try {
392
+ const pkg = JSON.parse(readFileSync(join(cwd, 'package.json'), 'utf-8'));
393
+ if (pkg.dependencies?.prisma || pkg.devDependencies?.prisma) {
394
+ console.log(' ' + c.blue + '⬡' + c.reset + ' Prisma detected');
395
+ toInstall.push('block-database-wipe');
396
+ }
397
+ } catch {}
398
+ }
399
+ if (existsSync(join(cwd, 'requirements.txt')) || existsSync(join(cwd, 'pyproject.toml'))) {
400
+ console.log(' ' + c.blue + '⬡' + c.reset + ' Python detected');
401
+ toInstall.push('auto-approve-python');
402
+ }
403
+ if (existsSync(join(cwd, 'Dockerfile'))) {
404
+ console.log(' ' + c.blue + '⬡' + c.reset + ' Docker detected');
405
+ toInstall.push('auto-approve-docker');
406
+ }
407
+ if (existsSync(join(cwd, '.env'))) {
408
+ console.log(' ' + c.blue + '⬡' + c.reset + ' .env file detected');
409
+ toInstall.push('env-source-guard');
410
+ }
411
+ if (existsSync(join(cwd, 'Gemfile')) || existsSync(join(cwd, 'artisan'))) {
412
+ console.log(' ' + c.blue + '⬡' + c.reset + ' Rails/Laravel detected');
413
+ toInstall.push('block-database-wipe');
414
+ }
415
+
416
+ // Always useful
417
+ toInstall.push('compound-command-approver', 'loop-detector', 'session-handoff');
418
+
419
+ // Deduplicate
420
+ const unique = [...new Set(toInstall)];
421
+
422
+ // Check what's already installed
423
+ const installed = new Set();
424
+ if (existsSync(HOOKS_DIR)) {
425
+ const { readdirSync } = await import('fs');
426
+ for (const f of readdirSync(HOOKS_DIR)) {
427
+ installed.add(f.replace('.sh', ''));
428
+ }
429
+ }
430
+
431
+ const toActuallyInstall = unique.filter(id => !installed.has(id));
432
+
433
+ console.log();
434
+ if (toActuallyInstall.length === 0) {
435
+ console.log(c.green + ' All recommended hooks already installed!' + c.reset);
436
+ console.log();
437
+ process.exit(0);
438
+ }
439
+
440
+ console.log(c.bold + ' Installing ' + toActuallyInstall.length + ' hooks:' + c.reset);
441
+
442
+ for (const id of toActuallyInstall) {
443
+ const hook = REGISTRY.find(h => h.id === id);
444
+ if (!hook) continue;
445
+
446
+ // Try direct download
447
+ const rawUrl = `https://raw.githubusercontent.com/yurukusa/cc-safe-setup/main/examples/${id}.sh`;
448
+ const hookPath = join(HOOKS_DIR, id + '.sh');
449
+
450
+ try {
451
+ mkdirSync(HOOKS_DIR, { recursive: true });
452
+ const script = execSync(`curl -sL "${rawUrl}"`, { encoding: 'utf-8', timeout: 5000 });
453
+
454
+ if (script.startsWith('#!/bin/bash')) {
455
+ writeFileSync(hookPath, script);
456
+ chmodSync(hookPath, 0o755);
457
+
458
+ // Register in settings
459
+ const trigger = script.includes('PreToolUse') ? 'PreToolUse' :
460
+ script.includes('PostToolUse') ? 'PostToolUse' :
461
+ script.includes('Stop') ? 'Stop' :
462
+ script.includes('SessionStart') ? 'SessionStart' : 'PreToolUse';
463
+ const matcher = script.includes('MATCHER: "Bash"') ? 'Bash' :
464
+ script.includes('MATCHER: "Edit|Write"') ? 'Edit|Write' : '';
465
+
466
+ let settings = {};
467
+ if (existsSync(SETTINGS_PATH)) {
468
+ try { settings = JSON.parse(readFileSync(SETTINGS_PATH, 'utf-8')); } catch {}
469
+ }
470
+ if (!settings.hooks) settings.hooks = {};
471
+ if (!settings.hooks[trigger]) settings.hooks[trigger] = [];
472
+
473
+ const existing = settings.hooks[trigger].flatMap(e => (e.hooks || []).map(h => h.command));
474
+ if (!existing.some(cmd => cmd.includes(id))) {
475
+ settings.hooks[trigger].push({ matcher, hooks: [{ type: 'command', command: hookPath }] });
476
+ mkdirSync(dirname(SETTINGS_PATH), { recursive: true });
477
+ writeFileSync(SETTINGS_PATH, JSON.stringify(settings, null, 2));
478
+ }
479
+
480
+ console.log(' ' + c.green + '✓' + c.reset + ' ' + id);
481
+ }
482
+ } catch {
483
+ console.log(' ' + c.yellow + '✗' + c.reset + ' ' + id + c.dim + ' (download failed)' + c.reset);
484
+ }
485
+ }
486
+
487
+ console.log();
488
+ console.log(c.green + ' Done! Restart Claude Code to activate.' + c.reset);
489
+ console.log(c.dim + ' Run: npx cc-hook-registry list' + c.reset);
490
+ console.log();
491
+ }
492
+
374
493
  else if (command === 'list') {
375
494
  console.log();
376
495
  console.log(c.bold + ' Installed Hooks' + c.reset);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cc-hook-registry",
3
- "version": "4.1.0",
3
+ "version": "5.0.0",
4
4
  "description": "Search, browse, and install Claude Code hooks from the community. GitHub-based registry, no server needed.",
5
5
  "main": "index.mjs",
6
6
  "bin": {