cc-hook-registry 4.1.0 → 5.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/CONTRIBUTING.md +50 -0
- package/README.md +6 -1
- package/docs/index.html +99 -0
- package/index.mjs +124 -0
- package/package.json +1 -1
package/CONTRIBUTING.md
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Contributing to cc-hook-registry
|
|
2
|
+
|
|
3
|
+
Want to add your hook to the registry? Here's how.
|
|
4
|
+
|
|
5
|
+
## Adding a Hook
|
|
6
|
+
|
|
7
|
+
1. Fork this repo
|
|
8
|
+
2. Edit `index.mjs` — add an entry to the `REGISTRY` array:
|
|
9
|
+
|
|
10
|
+
```javascript
|
|
11
|
+
{
|
|
12
|
+
id: 'your-hook-name',
|
|
13
|
+
name: 'Human-Readable Name',
|
|
14
|
+
category: 'safety', // safety, quality, approve, utility, monitoring, ux, framework, security
|
|
15
|
+
source: 'your-github-user/repo',
|
|
16
|
+
trigger: 'PreToolUse', // PreToolUse, PostToolUse, Stop, SessionStart, Notification
|
|
17
|
+
desc: 'One-line description of what it does',
|
|
18
|
+
tags: ['keyword1', 'keyword2'],
|
|
19
|
+
install: 'npx your-package or "git clone + copy"',
|
|
20
|
+
stars: 100, // optional: GitHub stars
|
|
21
|
+
issue: '#12345', // optional: related Claude Code issue
|
|
22
|
+
}
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
3. If your hook is a standalone bash script hosted on GitHub, add a `rawUrl` for direct install support
|
|
26
|
+
4. Submit a PR
|
|
27
|
+
|
|
28
|
+
## Categories
|
|
29
|
+
|
|
30
|
+
| Category | For hooks that... |
|
|
31
|
+
|----------|-------------------|
|
|
32
|
+
| safety | Block dangerous commands |
|
|
33
|
+
| quality | Check code quality, commits, syntax |
|
|
34
|
+
| approve | Auto-approve safe commands |
|
|
35
|
+
| utility | Cleanup, debugging, session management |
|
|
36
|
+
| monitoring | Track context, costs, usage |
|
|
37
|
+
| ux | Notifications, alerts |
|
|
38
|
+
| framework | Multi-hook frameworks |
|
|
39
|
+
| security | Prompt injection, access control |
|
|
40
|
+
|
|
41
|
+
## Requirements
|
|
42
|
+
|
|
43
|
+
- Hook must be publicly available (GitHub, npm, etc.)
|
|
44
|
+
- Hook must have a clear description
|
|
45
|
+
- Hook must work with Claude Code 2.1+
|
|
46
|
+
- No malicious code
|
|
47
|
+
|
|
48
|
+
## Testing
|
|
49
|
+
|
|
50
|
+
Run `bash test.sh` to verify your changes don't break existing functionality.
|
package/README.md
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
# cc-hook-registry
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/cc-hook-registry)
|
|
4
|
+
[](https://github.com/yurukusa/cc-hook-registry/actions/workflows/test.yml)
|
|
5
|
+
|
|
6
|
+
**Hook package manager for Claude Code.** Search, install, update, and manage 48 hooks from 7 projects.
|
|
7
|
+
|
|
8
|
+
**[Browse hooks in your browser](https://yurukusa.github.io/cc-hook-registry/)** — no install needed.
|
|
4
9
|
|
|
5
10
|
```bash
|
|
6
11
|
npx cc-hook-registry search database
|
package/docs/index.html
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>Claude Code Hook Registry</title>
|
|
7
|
+
<meta name="description" content="Search 48 hooks from 7 projects. Find and install Claude Code safety hooks.">
|
|
8
|
+
<style>
|
|
9
|
+
*{box-sizing:border-box;margin:0;padding:0}
|
|
10
|
+
body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;background:#0d1117;color:#c9d1d9;min-height:100vh;padding:1.5rem}
|
|
11
|
+
.c{max-width:800px;margin:0 auto}
|
|
12
|
+
h1{font-size:1.4rem;color:#f0f6fc;margin-bottom:.3rem}
|
|
13
|
+
.sub{color:#8b949e;font-size:.85rem;margin-bottom:1rem}
|
|
14
|
+
input{width:100%;padding:.6rem;background:#161b22;border:1px solid #30363d;border-radius:6px;color:#c9d1d9;font-size:.9rem;margin-bottom:.75rem}
|
|
15
|
+
input::placeholder{color:#484f58}
|
|
16
|
+
.filters{display:flex;gap:.3rem;flex-wrap:wrap;margin-bottom:.75rem}
|
|
17
|
+
.f{padding:.2rem .5rem;border-radius:3px;border:1px solid #30363d;background:transparent;color:#8b949e;cursor:pointer;font-size:.7rem}
|
|
18
|
+
.f.active{background:#238636;border-color:#238636;color:#fff}
|
|
19
|
+
.count{color:#8b949e;font-size:.8rem;margin-bottom:.5rem}
|
|
20
|
+
.hook{background:#161b22;border:1px solid #30363d;border-radius:6px;padding:.75rem;margin:.4rem 0}
|
|
21
|
+
.hook-name{font-weight:600;color:#f0f6fc;font-size:.9rem}
|
|
22
|
+
.hook-meta{font-size:.7rem;color:#484f58;margin-top:.2rem}
|
|
23
|
+
.hook-desc{color:#8b949e;font-size:.8rem;margin-top:.3rem}
|
|
24
|
+
.hook-install{font-family:monospace;font-size:.75rem;color:#58a6ff;margin-top:.3rem}
|
|
25
|
+
a{color:#58a6ff;text-decoration:none}
|
|
26
|
+
.badge{display:inline-block;padding:.1rem .3rem;border-radius:3px;font-size:.6rem;font-weight:bold;margin-left:.3rem}
|
|
27
|
+
.badge-safety{background:#da363322;color:#f85149;border:1px solid #da363344}
|
|
28
|
+
.badge-quality{background:#d2992222;color:#d29922;border:1px solid #d2992244}
|
|
29
|
+
.badge-approve{background:#23863622;color:#3fb950;border:1px solid #23863644}
|
|
30
|
+
.badge-utility{background:#1f6feb22;color:#58a6ff;border:1px solid #1f6feb44}
|
|
31
|
+
.footer{text-align:center;color:#484f58;font-size:.7rem;margin-top:2rem}
|
|
32
|
+
</style>
|
|
33
|
+
</head>
|
|
34
|
+
<body>
|
|
35
|
+
<div class="c">
|
|
36
|
+
<h1>Claude Code Hook Registry</h1>
|
|
37
|
+
<p class="sub">48 hooks from 7 projects. Search, browse, install.</p>
|
|
38
|
+
|
|
39
|
+
<input id="q" placeholder="Search hooks... (database, git, deploy, secret, docker)" oninput="render()">
|
|
40
|
+
|
|
41
|
+
<div class="filters" id="filters"></div>
|
|
42
|
+
<div class="count" id="count"></div>
|
|
43
|
+
<div id="list"></div>
|
|
44
|
+
|
|
45
|
+
<div class="footer">
|
|
46
|
+
<a href="https://github.com/yurukusa/cc-hook-registry">GitHub</a> ·
|
|
47
|
+
<a href="https://www.npmjs.com/package/cc-hook-registry">npm</a> ·
|
|
48
|
+
<code>npx cc-hook-registry search <keyword></code>
|
|
49
|
+
</div>
|
|
50
|
+
</div>
|
|
51
|
+
|
|
52
|
+
<script>
|
|
53
|
+
const H=[
|
|
54
|
+
{id:'destructive-guard',n:'Destructive Guard',c:'safety',d:'Blocks rm -rf, git reset --hard, git clean, PowerShell Remove-Item',i:'npx cc-safe-setup',t:['rm','delete','reset']},
|
|
55
|
+
{id:'branch-guard',n:'Branch Guard',c:'safety',d:'Blocks push to main/master and force-push',i:'npx cc-safe-setup',t:['git','push','force']},
|
|
56
|
+
{id:'secret-guard',n:'Secret Guard',c:'safety',d:'Blocks git add .env and credential files',i:'npx cc-safe-setup',t:['env','secret','key']},
|
|
57
|
+
{id:'block-database-wipe',n:'Database Wipe Guard',c:'safety',d:'Blocks migrate:fresh, DROP DATABASE, prisma reset',i:'npx cc-safe-setup --install-example block-database-wipe',t:['database','migrate','drop']},
|
|
58
|
+
{id:'compound-command-approver',n:'Compound Command Approver',c:'approve',d:'Auto-approve cd && git log compounds',i:'npx cc-safe-setup --install-example compound-command-approver',t:['compound','cd','permission']},
|
|
59
|
+
{id:'case-sensitive-guard',n:'Case-Insensitive FS Guard',c:'safety',d:'Detect exFAT/NTFS case collisions',i:'npx cc-safe-setup --install-example case-sensitive-guard',t:['exfat','ntfs','case']},
|
|
60
|
+
{id:'loop-detector',n:'Loop Detector',c:'safety',d:'Break command repetition (warn 3, block 5)',i:'npx cc-safe-setup --install-example loop-detector',t:['loop','repeat']},
|
|
61
|
+
{id:'cost-tracker',n:'Cost Tracker',c:'utility',d:'Estimate session token cost',i:'npx cc-safe-setup --install-example cost-tracker',t:['cost','token','money']},
|
|
62
|
+
{id:'session-handoff',n:'Session Handoff',c:'utility',d:'Auto-save state for next session',i:'npx cc-safe-setup --install-example session-handoff',t:['session','compact','resume']},
|
|
63
|
+
{id:'deploy-guard',n:'Deploy Guard',c:'safety',d:'Block deploy with uncommitted changes',i:'npx cc-safe-setup --install-example deploy-guard',t:['deploy','vercel','netlify']},
|
|
64
|
+
{id:'protect-dotfiles',n:'Dotfile Protector',c:'safety',d:'Block .bashrc, .aws/, .ssh/ modifications',i:'npx cc-safe-setup --install-example protect-dotfiles',t:['dotfiles','bashrc','ssh']},
|
|
65
|
+
{id:'scope-guard',n:'Scope Guard',c:'safety',d:'Block file operations outside project',i:'npx cc-safe-setup --install-example scope-guard',t:['scope','path','escape']},
|
|
66
|
+
{id:'dependency-audit',n:'Dependency Audit',c:'safety',d:'Warn on unknown package installs',i:'npx cc-safe-setup --install-example dependency-audit',t:['npm','pip','supply-chain']},
|
|
67
|
+
{id:'symlink-guard',n:'Symlink Guard',c:'safety',d:'Detect symlink traversal in rm targets',i:'npx cc-safe-setup --install-example symlink-guard',t:['symlink','junction','ntfs']},
|
|
68
|
+
{id:'env-source-guard',n:'Env Source Guard',c:'safety',d:'Block sourcing .env into shell',i:'npx cc-safe-setup --install-example env-source-guard',t:['env','source','bash']},
|
|
69
|
+
{id:'diff-size-guard',n:'Diff Size Guard',c:'quality',d:'Warn/block large commits (10+ files)',i:'npx cc-safe-setup --install-example diff-size-guard',t:['commit','diff','large']},
|
|
70
|
+
{id:'commit-quality-gate',n:'Commit Quality',c:'quality',d:'Warn on vague commit messages',i:'npx cc-safe-setup --install-example commit-quality-gate',t:['commit','message','vague']},
|
|
71
|
+
{id:'read-before-edit',n:'Read Before Edit',c:'quality',d:'Warn on editing unread files',i:'npx cc-safe-setup --install-example read-before-edit',t:['read','edit','mismatch']},
|
|
72
|
+
{id:'syntax-check',n:'Syntax Check',c:'quality',d:'Python/Shell/JSON/JS syntax after edits',i:'npx cc-safe-setup',t:['syntax','python','json']},
|
|
73
|
+
{id:'context-monitor',n:'Context Monitor',c:'utility',d:'Graduated context warnings (40/25/20/15%)',i:'npx cc-safe-setup',t:['context','memory','compact']},
|
|
74
|
+
{id:'tmp-cleanup',n:'Tmp Cleanup',c:'utility',d:'Clean /tmp/claude-*-cwd files',i:'npx cc-safe-setup --install-example tmp-cleanup',t:['tmp','cleanup','leak']},
|
|
75
|
+
{id:'auto-approve-build',n:'Auto-Approve Build',c:'approve',d:'npm/cargo/go build, test, lint',i:'npx cc-safe-setup --install-example auto-approve-build',t:['build','test','npm']},
|
|
76
|
+
{id:'auto-approve-python',n:'Auto-Approve Python',c:'approve',d:'pytest, mypy, ruff, black',i:'npx cc-safe-setup --install-example auto-approve-python',t:['python','pytest','lint']},
|
|
77
|
+
{id:'auto-approve-docker',n:'Auto-Approve Docker',c:'approve',d:'docker build, compose, ps, logs',i:'npx cc-safe-setup --install-example auto-approve-docker',t:['docker','compose']},
|
|
78
|
+
{id:'safety-net',n:'Safety Net (TS)',c:'safety',d:'TypeScript hooks with configurable severity',i:'npx @anthropic-ai/claude-code-safety-net',t:['typescript','configurable'],s:1185},
|
|
79
|
+
{id:'hooks-mastery',n:'Hooks Mastery (Python)',c:'utility',d:'Python hooks for all events + LLM',i:'git clone',t:['python','framework'],s:3386},
|
|
80
|
+
];
|
|
81
|
+
|
|
82
|
+
const cats=['all','safety','quality','approve','utility'];
|
|
83
|
+
const badgeClass={safety:'badge-safety',quality:'badge-quality',approve:'badge-approve',utility:'badge-utility'};
|
|
84
|
+
|
|
85
|
+
let filter='all';
|
|
86
|
+
document.getElementById('filters').innerHTML=cats.map(c=>`<button class="f${c==='all'?' active':''}" onclick="setFilter('${c}')">${c}</button>`).join('');
|
|
87
|
+
|
|
88
|
+
function setFilter(f){filter=f;document.querySelectorAll('.f').forEach(b=>b.classList.toggle('active',b.textContent===f));render();}
|
|
89
|
+
|
|
90
|
+
function render(){
|
|
91
|
+
const q=(document.getElementById('q').value||'').toLowerCase();
|
|
92
|
+
const filtered=H.filter(h=>(filter==='all'||h.c===filter)&&(!q||h.n.toLowerCase().includes(q)||h.d.toLowerCase().includes(q)||h.t.some(t=>t.includes(q))||h.id.includes(q)));
|
|
93
|
+
document.getElementById('count').textContent=filtered.length+' hooks';
|
|
94
|
+
document.getElementById('list').innerHTML=filtered.map(h=>`<div class="hook"><span class="hook-name">${h.n}</span><span class="badge ${badgeClass[h.c]||''}">${h.c}</span>${h.s?` <span style="color:#484f58;font-size:.65rem">${h.s}★</span>`:''}<div class="hook-desc">${h.d}</div><div class="hook-install">${h.i}</div></div>`).join('');
|
|
95
|
+
}
|
|
96
|
+
render();
|
|
97
|
+
</script>
|
|
98
|
+
</body>
|
|
99
|
+
</html>
|
package/index.mjs
CHANGED
|
@@ -80,6 +80,11 @@ const REGISTRY = [
|
|
|
80
80
|
{ id: 'symlink-guard', name: 'Symlink Traversal Guard', category: 'safety', source: 'cc-safe-setup', trigger: 'PreToolUse', desc: 'Detect symlink/junction traversal in rm targets', tags: ['symlink', 'junction', 'ntfs', 'traversal'], install: 'npx cc-safe-setup --install-example symlink-guard', issue: '#36339' },
|
|
81
81
|
{ id: 'binary-file-guard', name: 'Binary File Guard', category: 'quality', source: 'cc-safe-setup', trigger: 'PreToolUse', desc: 'Warn when Write targets binary file types', tags: ['binary', 'image', 'archive'], install: 'npx cc-safe-setup --install-example binary-file-guard' },
|
|
82
82
|
{ id: 'stale-branch-guard', name: 'Stale Branch Guard', category: 'quality', source: 'cc-safe-setup', trigger: 'PostToolUse', desc: 'Warn when branch is far behind default', tags: ['branch', 'stale', 'behind', 'rebase'], install: 'npx cc-safe-setup --install-example stale-branch-guard' },
|
|
83
|
+
{ id: 'reinject-claudemd', name: 'Re-inject CLAUDE.md', category: 'utility', source: 'cc-safe-setup', trigger: 'SessionStart', desc: 'Remind CLAUDE.md rules after compaction', tags: ['claudemd', 'compact', 'rules', 'memory'], install: 'npx cc-safe-setup --install-example reinject-claudemd', issue: '#6354' },
|
|
84
|
+
{ id: 'no-sudo-guard', name: 'No Sudo Guard', category: 'safety', source: 'cc-safe-setup', trigger: 'PreToolUse', desc: 'Block all sudo commands', tags: ['sudo', 'root', 'privilege'], install: 'npx cc-safe-setup --install-example no-sudo-guard' },
|
|
85
|
+
{ id: 'no-install-global', name: 'No Global Install', category: 'safety', source: 'cc-safe-setup', trigger: 'PreToolUse', desc: 'Block npm -g and system-wide pip', tags: ['npm', 'pip', 'global', 'system'], install: 'npx cc-safe-setup --install-example no-install-global' },
|
|
86
|
+
{ id: 'git-tag-guard', name: 'Git Tag Guard', category: 'quality', source: 'cc-safe-setup', trigger: 'PreToolUse', desc: 'Block pushing all tags at once', tags: ['git', 'tag', 'version', 'release'], install: 'npx cc-safe-setup --install-example git-tag-guard' },
|
|
87
|
+
{ id: 'npm-publish-guard', name: 'NPM Publish Guard', category: 'quality', source: 'cc-safe-setup', trigger: 'PreToolUse', desc: 'Version check before npm publish', tags: ['npm', 'publish', 'version'], install: 'npx cc-safe-setup --install-example npm-publish-guard' },
|
|
83
88
|
|
|
84
89
|
// External projects
|
|
85
90
|
{ id: 'safety-net', name: 'Safety Net (Full Suite)', category: 'safety', source: 'kenryu42/claude-code-safety-net', trigger: 'PreToolUse', desc: 'TypeScript safety hooks with configurable severity levels', tags: ['typescript', 'safety', 'configurable'], install: 'npx @anthropic-ai/claude-code-safety-net', stars: 1185 },
|
|
@@ -113,6 +118,7 @@ if (!command || command === '--help' || command === '-h') {
|
|
|
113
118
|
install <id> Install a hook
|
|
114
119
|
info <id> Show hook details
|
|
115
120
|
recommend Recommend hooks for current project
|
|
121
|
+
init Interactive setup — install recommended hooks
|
|
116
122
|
list List all installed hooks with status
|
|
117
123
|
update [id] Update one or all installed hooks
|
|
118
124
|
uninstall <id> Remove an installed hook
|
|
@@ -371,6 +377,124 @@ else if (command === 'recommend') {
|
|
|
371
377
|
console.log();
|
|
372
378
|
}
|
|
373
379
|
|
|
380
|
+
else if (command === 'init') {
|
|
381
|
+
console.log();
|
|
382
|
+
console.log(c.bold + ' cc-hook-registry init' + c.reset);
|
|
383
|
+
console.log(c.dim + ' Quick setup — installing essential + project-specific hooks' + c.reset);
|
|
384
|
+
console.log();
|
|
385
|
+
|
|
386
|
+
const cwd = process.cwd();
|
|
387
|
+
const toInstall = [];
|
|
388
|
+
|
|
389
|
+
// Essential hooks (always install)
|
|
390
|
+
toInstall.push('destructive-guard', 'branch-guard', 'secret-guard');
|
|
391
|
+
|
|
392
|
+
// Detect project type
|
|
393
|
+
if (existsSync(join(cwd, 'package.json'))) {
|
|
394
|
+
console.log(' ' + c.blue + '⬡' + c.reset + ' Node.js detected');
|
|
395
|
+
toInstall.push('auto-approve-build');
|
|
396
|
+
try {
|
|
397
|
+
const pkg = JSON.parse(readFileSync(join(cwd, 'package.json'), 'utf-8'));
|
|
398
|
+
if (pkg.dependencies?.prisma || pkg.devDependencies?.prisma) {
|
|
399
|
+
console.log(' ' + c.blue + '⬡' + c.reset + ' Prisma detected');
|
|
400
|
+
toInstall.push('block-database-wipe');
|
|
401
|
+
}
|
|
402
|
+
} catch {}
|
|
403
|
+
}
|
|
404
|
+
if (existsSync(join(cwd, 'requirements.txt')) || existsSync(join(cwd, 'pyproject.toml'))) {
|
|
405
|
+
console.log(' ' + c.blue + '⬡' + c.reset + ' Python detected');
|
|
406
|
+
toInstall.push('auto-approve-python');
|
|
407
|
+
}
|
|
408
|
+
if (existsSync(join(cwd, 'Dockerfile'))) {
|
|
409
|
+
console.log(' ' + c.blue + '⬡' + c.reset + ' Docker detected');
|
|
410
|
+
toInstall.push('auto-approve-docker');
|
|
411
|
+
}
|
|
412
|
+
if (existsSync(join(cwd, '.env'))) {
|
|
413
|
+
console.log(' ' + c.blue + '⬡' + c.reset + ' .env file detected');
|
|
414
|
+
toInstall.push('env-source-guard');
|
|
415
|
+
}
|
|
416
|
+
if (existsSync(join(cwd, 'Gemfile')) || existsSync(join(cwd, 'artisan'))) {
|
|
417
|
+
console.log(' ' + c.blue + '⬡' + c.reset + ' Rails/Laravel detected');
|
|
418
|
+
toInstall.push('block-database-wipe');
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// Always useful
|
|
422
|
+
toInstall.push('compound-command-approver', 'loop-detector', 'session-handoff');
|
|
423
|
+
|
|
424
|
+
// Deduplicate
|
|
425
|
+
const unique = [...new Set(toInstall)];
|
|
426
|
+
|
|
427
|
+
// Check what's already installed
|
|
428
|
+
const installed = new Set();
|
|
429
|
+
if (existsSync(HOOKS_DIR)) {
|
|
430
|
+
const { readdirSync } = await import('fs');
|
|
431
|
+
for (const f of readdirSync(HOOKS_DIR)) {
|
|
432
|
+
installed.add(f.replace('.sh', ''));
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
const toActuallyInstall = unique.filter(id => !installed.has(id));
|
|
437
|
+
|
|
438
|
+
console.log();
|
|
439
|
+
if (toActuallyInstall.length === 0) {
|
|
440
|
+
console.log(c.green + ' All recommended hooks already installed!' + c.reset);
|
|
441
|
+
console.log();
|
|
442
|
+
process.exit(0);
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
console.log(c.bold + ' Installing ' + toActuallyInstall.length + ' hooks:' + c.reset);
|
|
446
|
+
|
|
447
|
+
for (const id of toActuallyInstall) {
|
|
448
|
+
const hook = REGISTRY.find(h => h.id === id);
|
|
449
|
+
if (!hook) continue;
|
|
450
|
+
|
|
451
|
+
// Try direct download
|
|
452
|
+
const rawUrl = `https://raw.githubusercontent.com/yurukusa/cc-safe-setup/main/examples/${id}.sh`;
|
|
453
|
+
const hookPath = join(HOOKS_DIR, id + '.sh');
|
|
454
|
+
|
|
455
|
+
try {
|
|
456
|
+
mkdirSync(HOOKS_DIR, { recursive: true });
|
|
457
|
+
const script = execSync(`curl -sL "${rawUrl}"`, { encoding: 'utf-8', timeout: 5000 });
|
|
458
|
+
|
|
459
|
+
if (script.startsWith('#!/bin/bash')) {
|
|
460
|
+
writeFileSync(hookPath, script);
|
|
461
|
+
chmodSync(hookPath, 0o755);
|
|
462
|
+
|
|
463
|
+
// Register in settings
|
|
464
|
+
const trigger = script.includes('PreToolUse') ? 'PreToolUse' :
|
|
465
|
+
script.includes('PostToolUse') ? 'PostToolUse' :
|
|
466
|
+
script.includes('Stop') ? 'Stop' :
|
|
467
|
+
script.includes('SessionStart') ? 'SessionStart' : 'PreToolUse';
|
|
468
|
+
const matcher = script.includes('MATCHER: "Bash"') ? 'Bash' :
|
|
469
|
+
script.includes('MATCHER: "Edit|Write"') ? 'Edit|Write' : '';
|
|
470
|
+
|
|
471
|
+
let settings = {};
|
|
472
|
+
if (existsSync(SETTINGS_PATH)) {
|
|
473
|
+
try { settings = JSON.parse(readFileSync(SETTINGS_PATH, 'utf-8')); } catch {}
|
|
474
|
+
}
|
|
475
|
+
if (!settings.hooks) settings.hooks = {};
|
|
476
|
+
if (!settings.hooks[trigger]) settings.hooks[trigger] = [];
|
|
477
|
+
|
|
478
|
+
const existing = settings.hooks[trigger].flatMap(e => (e.hooks || []).map(h => h.command));
|
|
479
|
+
if (!existing.some(cmd => cmd.includes(id))) {
|
|
480
|
+
settings.hooks[trigger].push({ matcher, hooks: [{ type: 'command', command: hookPath }] });
|
|
481
|
+
mkdirSync(dirname(SETTINGS_PATH), { recursive: true });
|
|
482
|
+
writeFileSync(SETTINGS_PATH, JSON.stringify(settings, null, 2));
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
console.log(' ' + c.green + '✓' + c.reset + ' ' + id);
|
|
486
|
+
}
|
|
487
|
+
} catch {
|
|
488
|
+
console.log(' ' + c.yellow + '✗' + c.reset + ' ' + id + c.dim + ' (download failed)' + c.reset);
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
console.log();
|
|
493
|
+
console.log(c.green + ' Done! Restart Claude Code to activate.' + c.reset);
|
|
494
|
+
console.log(c.dim + ' Run: npx cc-hook-registry list' + c.reset);
|
|
495
|
+
console.log();
|
|
496
|
+
}
|
|
497
|
+
|
|
374
498
|
else if (command === 'list') {
|
|
375
499
|
console.log();
|
|
376
500
|
console.log(c.bold + ' Installed Hooks' + c.reset);
|