cc-hook-registry 3.1.0 → 4.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 (3) hide show
  1. package/index.mjs +91 -0
  2. package/package.json +2 -2
  3. package/test.sh +34 -0
package/index.mjs CHANGED
@@ -113,6 +113,8 @@ 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
+ update [id] Update one or all installed hooks
117
+ uninstall <id> Remove an installed hook
116
118
  outdated Check installed hooks for updates
117
119
  stats Registry statistics
118
120
 
@@ -368,6 +370,95 @@ else if (command === 'recommend') {
368
370
  console.log();
369
371
  }
370
372
 
373
+ else if (command === 'update') {
374
+ const targetId = args[1]; // Optional: update specific hook or all
375
+ console.log();
376
+ console.log(c.bold + ' Updating hooks...' + c.reset);
377
+ console.log();
378
+
379
+ if (!existsSync(HOOKS_DIR)) {
380
+ console.log(c.dim + ' No hooks installed.' + c.reset);
381
+ process.exit(0);
382
+ }
383
+
384
+ const { readdirSync } = await import('fs');
385
+ const installed = readdirSync(HOOKS_DIR).filter(f => f.endsWith('.sh'));
386
+ let updated = 0;
387
+ let skipped = 0;
388
+
389
+ for (const file of installed) {
390
+ const name = file.replace('.sh', '');
391
+ if (targetId && name !== targetId) continue;
392
+
393
+ const hook = REGISTRY.find(h => h.id === name);
394
+ if (!hook || !hook.install.includes('--install-example')) {
395
+ skipped++;
396
+ continue;
397
+ }
398
+
399
+ const rawUrl = `https://raw.githubusercontent.com/yurukusa/cc-safe-setup/main/examples/${name}.sh`;
400
+ try {
401
+ const remote = execSync(`curl -sL "${rawUrl}" 2>/dev/null`, { encoding: 'utf-8', timeout: 5000 });
402
+ const local = readFileSync(join(HOOKS_DIR, file), 'utf-8');
403
+
404
+ if (remote.trim() !== local.trim() && remote.startsWith('#!/bin/bash')) {
405
+ writeFileSync(join(HOOKS_DIR, file), remote);
406
+ chmodSync(join(HOOKS_DIR, file), 0o755);
407
+ console.log(' ' + c.green + '↑' + c.reset + ' Updated: ' + name);
408
+ updated++;
409
+ } else {
410
+ skipped++;
411
+ }
412
+ } catch {
413
+ skipped++;
414
+ }
415
+ }
416
+
417
+ if (updated === 0) {
418
+ console.log(c.green + ' All hooks are up to date.' + c.reset);
419
+ } else {
420
+ console.log();
421
+ console.log(c.green + ' Updated ' + updated + ' hook(s).' + c.reset + ' Restart Claude Code.');
422
+ }
423
+ console.log();
424
+ }
425
+
426
+ else if (command === 'uninstall') {
427
+ const id = args[1];
428
+ if (!id) { console.log(c.red + ' Usage: cc-hook-registry uninstall <id>' + c.reset); process.exit(1); }
429
+
430
+ const hookPath = join(HOOKS_DIR, id + '.sh');
431
+ console.log();
432
+
433
+ if (!existsSync(hookPath)) {
434
+ console.log(c.red + ' Hook not installed: ' + id + c.reset);
435
+ process.exit(1);
436
+ }
437
+
438
+ // Remove script
439
+ const { unlinkSync } = await import('fs');
440
+ unlinkSync(hookPath);
441
+ console.log(c.green + ' ✓ Removed: ' + hookPath + c.reset);
442
+
443
+ // Remove from settings.json
444
+ if (existsSync(SETTINGS_PATH)) {
445
+ try {
446
+ const settings = JSON.parse(readFileSync(SETTINGS_PATH, 'utf-8'));
447
+ for (const trigger of Object.keys(settings.hooks || {})) {
448
+ settings.hooks[trigger] = settings.hooks[trigger].filter(entry =>
449
+ !(entry.hooks || []).some(h => (h.command || '').includes(id))
450
+ );
451
+ if (settings.hooks[trigger].length === 0) delete settings.hooks[trigger];
452
+ }
453
+ writeFileSync(SETTINGS_PATH, JSON.stringify(settings, null, 2));
454
+ console.log(c.green + ' ✓ Removed from settings.json' + c.reset);
455
+ } catch {}
456
+ }
457
+
458
+ console.log(c.dim + ' Restart Claude Code to take effect.' + c.reset);
459
+ console.log();
460
+ }
461
+
371
462
  else if (command === 'outdated') {
372
463
  console.log();
373
464
  console.log(c.bold + ' Checking installed hooks for updates...' + c.reset);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cc-hook-registry",
3
- "version": "3.1.0",
3
+ "version": "4.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": {
@@ -15,7 +15,7 @@
15
15
  "cli"
16
16
  ],
17
17
  "scripts": {
18
- "test": "node index.mjs --help"
18
+ "test": "bash test.sh"
19
19
  },
20
20
  "author": "yurukusa",
21
21
  "license": "MIT",
package/test.sh ADDED
@@ -0,0 +1,34 @@
1
+ #!/bin/bash
2
+ # cc-hook-registry tests
3
+ set -euo pipefail
4
+ PASS=0; FAIL=0
5
+ CLI="$(dirname "$0")/index.mjs"
6
+
7
+ test_cmd() {
8
+ local desc="$1" cmd="$2" expect="$3"
9
+ local out
10
+ out=$(eval "$cmd" 2>&1) || true
11
+ if echo "$out" | grep -q "$expect"; then
12
+ echo " PASS: $desc"; PASS=$((PASS+1))
13
+ else
14
+ echo " FAIL: $desc (expected '$expect')"; FAIL=$((FAIL+1))
15
+ fi
16
+ }
17
+
18
+ echo "--- cc-hook-registry tests ---"
19
+
20
+ test_cmd "--help" "node $CLI --help" "Commands"
21
+ test_cmd "search database" "node $CLI search database" "block-database-wipe"
22
+ test_cmd "search git" "node $CLI search git" "result"
23
+ test_cmd "search nonexistent" "node $CLI search zzzznotfound" "No hooks"
24
+ test_cmd "browse" "node $CLI browse" "hooks"
25
+ test_cmd "browse safety" "node $CLI browse safety" "Safety"
26
+ test_cmd "info destructive-guard" "node $CLI info destructive-guard" "Destructive"
27
+ test_cmd "info nonexistent" "node $CLI info notreal" "not found"
28
+ test_cmd "stats" "node $CLI stats" "Total hooks"
29
+ test_cmd "stats count" "node $CLI stats" "48"
30
+
31
+ echo ""
32
+ echo "Results: $PASS/$((PASS+FAIL)) passed"
33
+ [ "$FAIL" -gt 0 ] && exit 1
34
+ echo "All tests passed!"