cc-hook-registry 3.2.0 → 4.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.
@@ -0,0 +1,12 @@
1
+ name: Tests
2
+ on: [push, pull_request]
3
+ jobs:
4
+ test:
5
+ runs-on: ubuntu-latest
6
+ steps:
7
+ - uses: actions/checkout@v4
8
+ - uses: actions/setup-node@v4
9
+ with:
10
+ node-version: '20'
11
+ - name: CLI tests
12
+ run: bash test.sh
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
+ list List all installed hooks with status
117
+ update [id] Update one or all installed hooks
116
118
  uninstall <id> Remove an installed hook
117
119
  outdated Check installed hooks for updates
118
120
  stats Registry statistics
@@ -369,6 +371,110 @@ else if (command === 'recommend') {
369
371
  console.log();
370
372
  }
371
373
 
374
+ else if (command === 'list') {
375
+ console.log();
376
+ console.log(c.bold + ' Installed Hooks' + c.reset);
377
+ console.log();
378
+
379
+ if (!existsSync(HOOKS_DIR)) {
380
+ console.log(c.dim + ' No hooks directory.' + c.reset);
381
+ process.exit(0);
382
+ }
383
+
384
+ const { readdirSync, statSync } = await import('fs');
385
+ const files = readdirSync(HOOKS_DIR).filter(f => f.endsWith('.sh')).sort();
386
+
387
+ // Count by trigger
388
+ let byTrigger = {};
389
+ if (existsSync(SETTINGS_PATH)) {
390
+ try {
391
+ const s = JSON.parse(readFileSync(SETTINGS_PATH, 'utf-8'));
392
+ for (const [trigger, entries] of Object.entries(s.hooks || {})) {
393
+ for (const e of entries) {
394
+ for (const h of (e.hooks || [])) {
395
+ const name = (h.command || '').split('/').pop();
396
+ if (name) byTrigger[name] = trigger;
397
+ }
398
+ }
399
+ }
400
+ } catch {}
401
+ }
402
+
403
+ for (const file of files) {
404
+ const path = join(HOOKS_DIR, file);
405
+ const name = file.replace('.sh', '');
406
+ const size = statSync(path).size;
407
+ const mtime = statSync(path).mtime;
408
+ const age = Math.floor((Date.now() - mtime.getTime()) / 86400000);
409
+ const inRegistry = REGISTRY.some(h => h.id === name);
410
+ const trigger = byTrigger[file] || '?';
411
+
412
+ const icon = inRegistry ? c.green + '●' + c.reset : c.dim + '○' + c.reset;
413
+ const ageStr = age === 0 ? 'today' : age + 'd ago';
414
+ const triggerStr = c.dim + trigger.padEnd(16) + c.reset;
415
+
416
+ console.log(' ' + icon + ' ' + file.padEnd(30) + triggerStr + (size/1024).toFixed(1) + 'KB ' + c.dim + ageStr + c.reset);
417
+ }
418
+
419
+ console.log();
420
+ const inReg = files.filter(f => REGISTRY.some(h => h.id === f.replace('.sh', ''))).length;
421
+ console.log(' ' + files.length + ' hooks installed (' + c.green + inReg + ' in registry' + c.reset + ', ' + c.dim + (files.length - inReg) + ' custom' + c.reset + ')');
422
+ console.log();
423
+ }
424
+
425
+ else if (command === 'update') {
426
+ const targetId = args[1]; // Optional: update specific hook or all
427
+ console.log();
428
+ console.log(c.bold + ' Updating hooks...' + c.reset);
429
+ console.log();
430
+
431
+ if (!existsSync(HOOKS_DIR)) {
432
+ console.log(c.dim + ' No hooks installed.' + c.reset);
433
+ process.exit(0);
434
+ }
435
+
436
+ const { readdirSync } = await import('fs');
437
+ const installed = readdirSync(HOOKS_DIR).filter(f => f.endsWith('.sh'));
438
+ let updated = 0;
439
+ let skipped = 0;
440
+
441
+ for (const file of installed) {
442
+ const name = file.replace('.sh', '');
443
+ if (targetId && name !== targetId) continue;
444
+
445
+ const hook = REGISTRY.find(h => h.id === name);
446
+ if (!hook || !hook.install.includes('--install-example')) {
447
+ skipped++;
448
+ continue;
449
+ }
450
+
451
+ const rawUrl = `https://raw.githubusercontent.com/yurukusa/cc-safe-setup/main/examples/${name}.sh`;
452
+ try {
453
+ const remote = execSync(`curl -sL "${rawUrl}" 2>/dev/null`, { encoding: 'utf-8', timeout: 5000 });
454
+ const local = readFileSync(join(HOOKS_DIR, file), 'utf-8');
455
+
456
+ if (remote.trim() !== local.trim() && remote.startsWith('#!/bin/bash')) {
457
+ writeFileSync(join(HOOKS_DIR, file), remote);
458
+ chmodSync(join(HOOKS_DIR, file), 0o755);
459
+ console.log(' ' + c.green + '↑' + c.reset + ' Updated: ' + name);
460
+ updated++;
461
+ } else {
462
+ skipped++;
463
+ }
464
+ } catch {
465
+ skipped++;
466
+ }
467
+ }
468
+
469
+ if (updated === 0) {
470
+ console.log(c.green + ' All hooks are up to date.' + c.reset);
471
+ } else {
472
+ console.log();
473
+ console.log(c.green + ' Updated ' + updated + ' hook(s).' + c.reset + ' Restart Claude Code.');
474
+ }
475
+ console.log();
476
+ }
477
+
372
478
  else if (command === 'uninstall') {
373
479
  const id = args[1];
374
480
  if (!id) { console.log(c.red + ' Usage: cc-hook-registry uninstall <id>' + c.reset); process.exit(1); }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cc-hook-registry",
3
- "version": "3.2.0",
3
+ "version": "4.1.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!"