cc-hook-registry 2.1.0 → 3.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 +95 -0
  2. package/package.json +1 -1
package/index.mjs CHANGED
@@ -112,6 +112,7 @@ 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
115
116
  stats Registry statistics
116
117
 
117
118
  Examples:
@@ -272,6 +273,100 @@ else if (command === 'info') {
272
273
  console.log();
273
274
  }
274
275
 
276
+ else if (command === 'recommend') {
277
+ console.log();
278
+ console.log(c.bold + ' Recommended hooks for this project' + c.reset);
279
+ console.log();
280
+
281
+ const cwd = process.cwd();
282
+ const recommendations = [];
283
+
284
+ // Always recommend safety essentials
285
+ recommendations.push({ id: 'destructive-guard', reason: 'Essential — prevents rm -rf disasters', priority: 1 });
286
+ recommendations.push({ id: 'branch-guard', reason: 'Essential — prevents push to main', priority: 1 });
287
+ recommendations.push({ id: 'secret-guard', reason: 'Essential — prevents .env commits', priority: 1 });
288
+
289
+ // Detect tech stack
290
+ if (existsSync(join(cwd, 'package.json'))) {
291
+ const pkg = JSON.parse(readFileSync(join(cwd, 'package.json'), 'utf-8'));
292
+ recommendations.push({ id: 'auto-approve-build', reason: 'Node.js project detected', priority: 2 });
293
+ if (pkg.dependencies?.prisma || pkg.devDependencies?.prisma) {
294
+ recommendations.push({ id: 'block-database-wipe', reason: 'Prisma detected — protect against migrate reset', priority: 1 });
295
+ }
296
+ if (pkg.scripts?.deploy || pkg.scripts?.['vercel-build']) {
297
+ recommendations.push({ id: 'deploy-guard', reason: 'Deploy script detected', priority: 2 });
298
+ }
299
+ }
300
+
301
+ if (existsSync(join(cwd, 'requirements.txt')) || existsSync(join(cwd, 'pyproject.toml'))) {
302
+ recommendations.push({ id: 'auto-approve-python', reason: 'Python project detected', priority: 2 });
303
+ }
304
+
305
+ if (existsSync(join(cwd, 'Dockerfile')) || existsSync(join(cwd, 'docker-compose.yml'))) {
306
+ recommendations.push({ id: 'auto-approve-docker', reason: 'Docker detected', priority: 2 });
307
+ }
308
+
309
+ if (existsSync(join(cwd, '.env')) || existsSync(join(cwd, '.env.local'))) {
310
+ recommendations.push({ id: 'env-source-guard', reason: '.env file present — prevent sourcing', priority: 1 });
311
+ }
312
+
313
+ if (existsSync(join(cwd, 'Gemfile'))) {
314
+ recommendations.push({ id: 'block-database-wipe', reason: 'Rails detected — protect against db:drop', priority: 1 });
315
+ }
316
+
317
+ if (existsSync(join(cwd, 'artisan'))) {
318
+ recommendations.push({ id: 'block-database-wipe', reason: 'Laravel detected — protect against migrate:fresh', priority: 1 });
319
+ }
320
+
321
+ // Always useful
322
+ recommendations.push({ id: 'compound-command-approver', reason: 'Fixes permission matching for cd && commands', priority: 2 });
323
+ recommendations.push({ id: 'loop-detector', reason: 'Prevents infinite command loops', priority: 3 });
324
+ recommendations.push({ id: 'session-handoff', reason: 'Saves state for next session', priority: 3 });
325
+ recommendations.push({ id: 'cost-tracker', reason: 'Track session costs', priority: 3 });
326
+
327
+ // Deduplicate and sort by priority
328
+ const seen = new Set();
329
+ const unique = recommendations.filter(r => { if (seen.has(r.id)) return false; seen.add(r.id); return true; });
330
+ unique.sort((a, b) => a.priority - b.priority);
331
+
332
+ // Check what's already installed
333
+ let installed = new Set();
334
+ if (existsSync(SETTINGS_PATH)) {
335
+ try {
336
+ const s = JSON.parse(readFileSync(SETTINGS_PATH, 'utf-8'));
337
+ for (const entries of Object.values(s.hooks || {})) {
338
+ for (const e of entries) {
339
+ for (const h of (e.hooks || [])) {
340
+ if (h.command) installed.add(h.command.split('/').pop().replace('.sh', ''));
341
+ }
342
+ }
343
+ }
344
+ } catch {}
345
+ }
346
+
347
+ for (const rec of unique) {
348
+ const hook = REGISTRY.find(h => h.id === rec.id);
349
+ if (!hook) continue;
350
+ const isInstalled = installed.has(rec.id);
351
+ const icon = isInstalled ? c.green + '✓' + c.reset : c.yellow + '○' + c.reset;
352
+ const status = isInstalled ? c.dim + '(installed)' + c.reset : '';
353
+ console.log(' ' + icon + ' ' + c.bold + rec.id + c.reset + ' ' + status);
354
+ console.log(' ' + c.dim + rec.reason + c.reset);
355
+ if (!isInstalled) {
356
+ console.log(' ' + c.dim + 'Install: npx cc-hook-registry install ' + rec.id + c.reset);
357
+ }
358
+ console.log();
359
+ }
360
+
361
+ const notInstalled = unique.filter(r => !installed.has(r.id));
362
+ if (notInstalled.length === 0) {
363
+ console.log(c.green + ' All recommended hooks are installed!' + c.reset);
364
+ } else {
365
+ console.log(c.dim + ' ' + notInstalled.length + ' recommended hook(s) not yet installed.' + c.reset);
366
+ }
367
+ console.log();
368
+ }
369
+
275
370
  else if (command === 'stats') {
276
371
  const categories = {};
277
372
  const sources = {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cc-hook-registry",
3
- "version": "2.1.0",
3
+ "version": "3.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": {