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.
- package/.github/workflows/test.yml +12 -0
- package/index.mjs +106 -0
- package/package.json +2 -2
- 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
|
+
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
|
+
"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": "
|
|
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!"
|