cc-safe-setup 11.6.0 → 11.8.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/README.md +15 -1
- package/index.mjs +29 -0
- package/package.json +1 -1
- package/.claude/session-snapshot.md +0 -25
- package/cc-safe-setup-export.json +0 -321
- package/docs/README.ja.md +0 -64
- package/docs/ROADMAP.md +0 -83
- package/docs/builder.html +0 -283
- package/docs/by-example.html +0 -234
- package/docs/cheatsheet.html +0 -187
- package/docs/ecosystem.html +0 -223
- package/docs/faq.html +0 -244
- package/docs/hooks-cheatsheet.html +0 -319
- package/docs/hub.html +0 -155
- package/docs/index-legacy.html +0 -685
- package/docs/index.html +0 -271
- package/docs/matrix.html +0 -139
- package/docs/migration-guide.html +0 -198
- package/docs/settings-reference.html +0 -317
- package/docs/troubleshooting.html +0 -189
- package/examples/go/destructive_guard.go +0 -69
- package/examples/python/__pycache__/destructive_guard.cpython-312.pyc +0 -0
- package/examples/python/__pycache__/secret_guard.cpython-312.pyc +0 -0
- package/examples/rust/destructive_guard.rs +0 -72
- package/examples/typescript/destructive-guard.ts +0 -64
package/docs/README.ja.md
DELETED
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
# cc-safe-setup
|
|
2
|
-
|
|
3
|
-
**Claude Codeを安全にするワンコマンドツール。**
|
|
4
|
-
|
|
5
|
-
8個の安全フック + 36個のインストール可能なexample。destructive guard、branch protection、secret leak prevention、syntax check、context monitor、その他。
|
|
6
|
-
|
|
7
|
-
```bash
|
|
8
|
-
npx cc-safe-setup
|
|
9
|
-
```
|
|
10
|
-
|
|
11
|
-
## 何ができるか
|
|
12
|
-
|
|
13
|
-
| コマンド | 機能 |
|
|
14
|
-
|---|---|
|
|
15
|
-
| `npx cc-safe-setup` | 8個の安全フックをインストール |
|
|
16
|
-
| `--create "説明"` | 自然言語でカスタムフック生成 |
|
|
17
|
-
| `--audit` | 安全スコア(0-100) |
|
|
18
|
-
| `--lint` | 設定の静的解析 |
|
|
19
|
-
| `--diff <file>` | 設定を比較 |
|
|
20
|
-
| `--benchmark` | フック実行速度を計測 |
|
|
21
|
-
| `--doctor` | 動かない原因を診断 |
|
|
22
|
-
| `--watch` | ブロックされたコマンドをリアルタイム表示 |
|
|
23
|
-
| `--stats` | ブロック統計レポート |
|
|
24
|
-
| `--share` | 共有URLを生成 |
|
|
25
|
-
| `--export / --import` | チームで設定を共有 |
|
|
26
|
-
| `--verify` | 各フックの動作確認 |
|
|
27
|
-
| `--install-example <name>` | 36個のexampleフック |
|
|
28
|
-
|
|
29
|
-
## インストール
|
|
30
|
-
|
|
31
|
-
```bash
|
|
32
|
-
npx cc-safe-setup
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
Claude Codeを再起動。完了。
|
|
36
|
-
|
|
37
|
-
## 何がブロックされるか
|
|
38
|
-
|
|
39
|
-
| 操作 | Before | After |
|
|
40
|
-
|---|---|---|
|
|
41
|
-
| `rm -rf /` | 実行される | ブロック |
|
|
42
|
-
| `git push --force` | 実行される | ブロック |
|
|
43
|
-
| `git push origin main` | 実行される | ブロック |
|
|
44
|
-
| `git add .env` | 実行される | ブロック |
|
|
45
|
-
| Python構文エラー | 気づかない | 自動検出 |
|
|
46
|
-
| コンテキスト枯渇 | 突然死 | 段階的警告 |
|
|
47
|
-
|
|
48
|
-
## ドキュメント
|
|
49
|
-
|
|
50
|
-
- [settings.jsonリファレンス](../SETTINGS_REFERENCE.md) — 全設定の解説
|
|
51
|
-
- [移行ガイド](../MIGRATION.md) — permissionsからhooksへ
|
|
52
|
-
- [トラブルシューティング](../TROUBLESHOOTING.md) — 動かない時の対処法
|
|
53
|
-
- [チートシート](https://yurukusa.github.io/cc-safe-setup/cheatsheet.html) — 印刷用A4
|
|
54
|
-
- [エコシステム比較](https://yurukusa.github.io/cc-safe-setup/ecosystem.html) — 全hookプロジェクト比較
|
|
55
|
-
- [Web版](https://yurukusa.github.io/cc-safe-setup/) — ブラウザで診断+セットアップ生成
|
|
56
|
-
|
|
57
|
-
## 必要なもの
|
|
58
|
-
|
|
59
|
-
- `jq`: `brew install jq` / `apt install jq`
|
|
60
|
-
- Claude Code 2.1以上
|
|
61
|
-
|
|
62
|
-
## ライセンス
|
|
63
|
-
|
|
64
|
-
MIT
|
package/docs/ROADMAP.md
DELETED
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
# cc-safe-setup Roadmap
|
|
2
|
-
|
|
3
|
-
## Current: v3.8.1
|
|
4
|
-
|
|
5
|
-
- 8 built-in hooks + 38 examples
|
|
6
|
-
- 21 CLI commands
|
|
7
|
-
- 5 web tools (audit, cheatsheet, ecosystem, cookbook, hook builder)
|
|
8
|
-
- 173 tests, CI green
|
|
9
|
-
- 2,500+ daily npm downloads
|
|
10
|
-
|
|
11
|
-
## Next Major: v4.0 (planned)
|
|
12
|
-
|
|
13
|
-
### --dashboard: Real-Time Terminal Dashboard
|
|
14
|
-
|
|
15
|
-
A single screen showing everything about your Claude Code safety:
|
|
16
|
-
|
|
17
|
-
```
|
|
18
|
-
┌─ cc-safe-setup dashboard ─────────────────────────┐
|
|
19
|
-
│ │
|
|
20
|
-
│ Hooks: 26 active (8 built-in + 18 examples) │
|
|
21
|
-
│ Score: 85/100 (Grade A) │
|
|
22
|
-
│ Context: ~60% remaining │
|
|
23
|
-
│ Cost: ~$1.47 (142 tool calls, Opus) │
|
|
24
|
-
│ │
|
|
25
|
-
│ ── Recent Blocks ──────────────────────────────── │
|
|
26
|
-
│ 14:23 rm -rf ~/projects (destructive-guard) │
|
|
27
|
-
│ 14:21 git push --force (branch-guard) │
|
|
28
|
-
│ 14:18 git add .env (secret-guard) │
|
|
29
|
-
│ │
|
|
30
|
-
│ ── Hook Performance ───────────────────────────── │
|
|
31
|
-
│ destructive-guard 15ms ████████ │
|
|
32
|
-
│ branch-guard 7ms ████ │
|
|
33
|
-
│ secret-guard 5ms ███ │
|
|
34
|
-
│ │
|
|
35
|
-
│ ── Today ──────────────────────────────────────── │
|
|
36
|
-
│ Blocks: 12 | Warns: 5 | Approves: 34 │
|
|
37
|
-
│ Top reason: rm on sensitive path (5) │
|
|
38
|
-
└─────────────────────────────────────────────────────┘
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
**Implementation notes:**
|
|
42
|
-
- ANSI escape codes only (no blessed/ink dependencies)
|
|
43
|
-
- Reads blocked-commands.log + context-monitor state + cost-tracker state
|
|
44
|
-
- Refreshes every 2 seconds
|
|
45
|
-
- Ctrl+C to exit
|
|
46
|
-
- Works in any terminal (iTerm, VS Code, Windows Terminal, WSL)
|
|
47
|
-
|
|
48
|
-
### Hook Marketplace (concept)
|
|
49
|
-
|
|
50
|
-
Community-contributed hooks discoverable from CLI:
|
|
51
|
-
|
|
52
|
-
```bash
|
|
53
|
-
npx cc-safe-setup --search "database"
|
|
54
|
-
npx cc-safe-setup --install-remote user/hook-name
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
Would require a registry (GitHub-based, no server). Defer to v5.0.
|
|
58
|
-
|
|
59
|
-
### Hook Composition
|
|
60
|
-
|
|
61
|
-
Chain hooks with conditions:
|
|
62
|
-
|
|
63
|
-
```json
|
|
64
|
-
{
|
|
65
|
-
"hooks": [{
|
|
66
|
-
"if": "branch === 'main'",
|
|
67
|
-
"then": "block-all-writes.sh",
|
|
68
|
-
"else": "allow-all.sh"
|
|
69
|
-
}]
|
|
70
|
-
}
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
Requires Claude Code API changes. Not feasible with current hook system.
|
|
74
|
-
|
|
75
|
-
## Done (Session 39)
|
|
76
|
-
|
|
77
|
-
- --create, --lint, --diff, --share, --benchmark, --doctor, --watch, --stats
|
|
78
|
-
- --export/--import, --audit --json
|
|
79
|
-
- 38 examples including cost-tracker, loop-detector, session-handoff
|
|
80
|
-
- Hook Builder web tool
|
|
81
|
-
- Interactive COOKBOOK
|
|
82
|
-
- 6 documentation files + Japanese README
|
|
83
|
-
- CONTRIBUTING.md for external contributors
|
package/docs/builder.html
DELETED
|
@@ -1,283 +0,0 @@
|
|
|
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>Hook Builder — Claude Code</title>
|
|
7
|
-
<meta name="description" content="Generate Claude Code hooks from plain English. No coding required.">
|
|
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{color:#f0f6fc;font-size:1.4rem;margin-bottom:.3rem}
|
|
13
|
-
.sub{color:#8b949e;font-size:.85rem;margin-bottom:1.2rem}
|
|
14
|
-
a{color:#58a6ff;text-decoration:none}
|
|
15
|
-
input,select{width:100%;padding:.6rem;background:#161b22;border:1px solid #30363d;border-radius:6px;color:#c9d1d9;font-size:.9rem;margin-bottom:.6rem}
|
|
16
|
-
input:focus,select:focus{outline:none;border-color:#58a6ff}
|
|
17
|
-
input::placeholder{color:#484f58}
|
|
18
|
-
label{display:block;color:#8b949e;font-size:.78rem;margin-bottom:.2rem}
|
|
19
|
-
pre{background:#161b22;border:1px solid #30363d;border-radius:6px;padding:.8rem;font-size:.8rem;color:#e6edf3;overflow-x:auto;white-space:pre-wrap;position:relative;margin:.5rem 0}
|
|
20
|
-
.copy-btn{position:absolute;top:.4rem;right:.4rem;background:#21262d;border:1px solid #30363d;color:#8b949e;padding:.2rem .5rem;border-radius:4px;cursor:pointer;font-size:.7rem}
|
|
21
|
-
.copy-btn:hover{color:#f0f6fc;border-color:#58a6ff}
|
|
22
|
-
.btn{background:#238636;color:#fff;border:none;padding:.6rem 1.2rem;border-radius:6px;cursor:pointer;font-size:.9rem;width:100%;margin:.5rem 0}
|
|
23
|
-
.btn:hover{background:#2ea043}
|
|
24
|
-
.templates{display:grid;grid-template-columns:repeat(auto-fill,minmax(180px,1fr));gap:.4rem;margin:.8rem 0}
|
|
25
|
-
.tmpl{background:#161b22;border:1px solid #30363d;border-radius:6px;padding:.5rem;cursor:pointer;font-size:.78rem;color:#8b949e;transition:border-color .15s}
|
|
26
|
-
.tmpl:hover{border-color:#58a6ff;color:#c9d1d9}
|
|
27
|
-
.tmpl-title{font-weight:600;color:#c9d1d9;font-size:.82rem}
|
|
28
|
-
.section{margin:1.2rem 0}
|
|
29
|
-
.footer{text-align:center;color:#484f58;font-size:.7rem;margin-top:2rem}
|
|
30
|
-
.note{background:#161b22;border-left:3px solid #58a6ff;padding:.5rem .8rem;font-size:.78rem;color:#8b949e;margin:.5rem 0}
|
|
31
|
-
.hidden{display:none}
|
|
32
|
-
</style>
|
|
33
|
-
</head>
|
|
34
|
-
<body>
|
|
35
|
-
<div class="c">
|
|
36
|
-
|
|
37
|
-
<h1>Hook Builder</h1>
|
|
38
|
-
<p class="sub">Describe what you want in plain English. Get a working hook.</p>
|
|
39
|
-
|
|
40
|
-
<div class="section">
|
|
41
|
-
<label>What should the hook do?</label>
|
|
42
|
-
<input id="desc" placeholder="e.g., Block npm publish without running tests first" oninput="clearOutput()">
|
|
43
|
-
</div>
|
|
44
|
-
|
|
45
|
-
<div class="section">
|
|
46
|
-
<label>Trigger event</label>
|
|
47
|
-
<select id="trigger">
|
|
48
|
-
<option value="PreToolUse">PreToolUse (before tool runs — block/approve)</option>
|
|
49
|
-
<option value="PostToolUse">PostToolUse (after tool runs — check results)</option>
|
|
50
|
-
<option value="Stop">Stop (when Claude finishes — notify/cleanup)</option>
|
|
51
|
-
</select>
|
|
52
|
-
</div>
|
|
53
|
-
|
|
54
|
-
<div class="section">
|
|
55
|
-
<label>Tool matcher (regex)</label>
|
|
56
|
-
<input id="matcher" placeholder='e.g., Bash, Edit|Write, or empty for all' value="Bash">
|
|
57
|
-
</div>
|
|
58
|
-
|
|
59
|
-
<button class="btn" onclick="generate()">Generate Hook</button>
|
|
60
|
-
|
|
61
|
-
<div id="output" class="hidden">
|
|
62
|
-
<h2 style="color:#f0f6fc;font-size:1rem;margin:1rem 0 .5rem">Generated Hook</h2>
|
|
63
|
-
<pre id="hook-code"><code></code><button class="copy-btn" onclick="copyCode()">Copy</button></pre>
|
|
64
|
-
|
|
65
|
-
<h2 style="color:#f0f6fc;font-size:1rem;margin:1rem 0 .5rem">settings.json entry</h2>
|
|
66
|
-
<pre id="settings-code"><code></code><button class="copy-btn" onclick="copySettings()">Copy Settings</button></pre>
|
|
67
|
-
|
|
68
|
-
<div class="note">
|
|
69
|
-
<strong>Install:</strong> Save the hook to <code>~/.claude/hooks/your-hook.sh</code>, run <code>chmod +x</code>, and add the settings.json entry.
|
|
70
|
-
<br>Or use the CLI: <code id="cli-cmd"></code>
|
|
71
|
-
</div>
|
|
72
|
-
</div>
|
|
73
|
-
|
|
74
|
-
<div class="section">
|
|
75
|
-
<h2 style="color:#8b949e;font-size:.9rem;margin-bottom:.5rem">Templates</h2>
|
|
76
|
-
<div class="templates" id="templates"></div>
|
|
77
|
-
</div>
|
|
78
|
-
|
|
79
|
-
<div class="footer">
|
|
80
|
-
<a href="hooks-cheatsheet.html">Cheat Sheet</a> ·
|
|
81
|
-
<a href="https://yurukusa.github.io/cc-hook-registry/playground.html">Playground</a> ·
|
|
82
|
-
<a href="https://github.com/yurukusa/cc-safe-setup">GitHub</a>
|
|
83
|
-
</div>
|
|
84
|
-
</div>
|
|
85
|
-
|
|
86
|
-
<script>
|
|
87
|
-
const TEMPLATES = [
|
|
88
|
-
{ title: 'Block rm -rf /', desc: 'Block rm -rf on root directory', trigger: 'PreToolUse', matcher: 'Bash', pattern: 'rm\\s+.*-rf\\s+/', action: 'block', msg: 'rm -rf on root directory' },
|
|
89
|
-
{ title: 'Block force push', desc: 'Block git push --force', trigger: 'PreToolUse', matcher: 'Bash', pattern: 'git\\s+push\\s+.*--force', action: 'block', msg: 'Force push' },
|
|
90
|
-
{ title: 'Block .env commit', desc: 'Block git add .env files', trigger: 'PreToolUse', matcher: 'Bash', pattern: 'git\\s+add\\s+.*\\.env', action: 'block', msg: 'Adding .env to git' },
|
|
91
|
-
{ title: 'Block sudo', desc: 'Block all sudo commands', trigger: 'PreToolUse', matcher: 'Bash', pattern: '^\\s*sudo\\b', action: 'block', msg: 'sudo command' },
|
|
92
|
-
{ title: 'Block DB drop', desc: 'Block DROP DATABASE/TABLE', trigger: 'PreToolUse', matcher: 'Bash', pattern: 'DROP\\s+(DATABASE|TABLE)', action: 'block', msg: 'Database drop' },
|
|
93
|
-
{ title: 'Auto-approve tests', desc: 'Auto-approve npm test, pytest, cargo test', trigger: 'PreToolUse', matcher: 'Bash', pattern: '^\\s*(npm\\s+test|pytest|cargo\\s+test)', action: 'approve', msg: 'Safe test command' },
|
|
94
|
-
{ title: 'Warn large output', desc: 'Warn when output exceeds 50KB', trigger: 'PostToolUse', matcher: '', pattern: null, action: 'warn-output', msg: 'Large output consuming context' },
|
|
95
|
-
{ title: 'Session end notify', desc: 'Desktop notification on session end', trigger: 'Stop', matcher: '', pattern: null, action: 'notify', msg: 'Session completed' },
|
|
96
|
-
{ title: 'Block npm -g', desc: 'Block global npm install', trigger: 'PreToolUse', matcher: 'Bash', pattern: 'npm\\s+install\\s+(-g|--global)', action: 'block', msg: 'Global npm install' },
|
|
97
|
-
{ title: 'Block deploy Friday', desc: 'Block deploy commands on Fridays', trigger: 'PreToolUse', matcher: 'Bash', pattern: null, action: 'friday', msg: 'No deploys on Friday' },
|
|
98
|
-
];
|
|
99
|
-
|
|
100
|
-
document.getElementById('templates').innerHTML = TEMPLATES.map((t,i) => `
|
|
101
|
-
<div class="tmpl" onclick="useTemplate(${i})">
|
|
102
|
-
<div class="tmpl-title">${t.title}</div>
|
|
103
|
-
<div>${t.desc}</div>
|
|
104
|
-
</div>
|
|
105
|
-
`).join('');
|
|
106
|
-
|
|
107
|
-
function useTemplate(i) {
|
|
108
|
-
const t = TEMPLATES[i];
|
|
109
|
-
document.getElementById('desc').value = t.desc;
|
|
110
|
-
document.getElementById('trigger').value = t.trigger;
|
|
111
|
-
document.getElementById('matcher').value = t.matcher;
|
|
112
|
-
generate(t);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
function clearOutput() {
|
|
116
|
-
document.getElementById('output').classList.add('hidden');
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
function generate(template) {
|
|
120
|
-
const desc = document.getElementById('desc').value.trim();
|
|
121
|
-
const trigger = document.getElementById('trigger').value;
|
|
122
|
-
const matcher = document.getElementById('matcher').value.trim();
|
|
123
|
-
|
|
124
|
-
if (!desc && !template) return;
|
|
125
|
-
|
|
126
|
-
let hookCode, hookName;
|
|
127
|
-
|
|
128
|
-
if (template) {
|
|
129
|
-
hookName = template.title.toLowerCase().replace(/[^a-z0-9]+/g, '-');
|
|
130
|
-
hookCode = generateFromTemplate(template);
|
|
131
|
-
} else {
|
|
132
|
-
hookName = desc.toLowerCase().replace(/[^a-z0-9]+/g, '-').substring(0, 30);
|
|
133
|
-
hookCode = generateFromDesc(desc, trigger, matcher);
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
const settingsCode = JSON.stringify({
|
|
137
|
-
hooks: {
|
|
138
|
-
[trigger]: [{
|
|
139
|
-
matcher: matcher,
|
|
140
|
-
hooks: [{ type: "command", command: `bash ~/.claude/hooks/${hookName}.sh` }]
|
|
141
|
-
}]
|
|
142
|
-
}
|
|
143
|
-
}, null, 2);
|
|
144
|
-
|
|
145
|
-
document.getElementById('hook-code').querySelector('code').textContent = hookCode;
|
|
146
|
-
document.getElementById('settings-code').querySelector('code').textContent = settingsCode;
|
|
147
|
-
document.getElementById('cli-cmd').textContent = `npx cc-safe-setup --create "${desc}"`;
|
|
148
|
-
document.getElementById('output').classList.remove('hidden');
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
function generateFromTemplate(t) {
|
|
152
|
-
if (t.action === 'block') {
|
|
153
|
-
return `#!/bin/bash
|
|
154
|
-
# ${t.desc}
|
|
155
|
-
# TRIGGER: ${t.trigger} MATCHER: "${t.matcher}"
|
|
156
|
-
COMMAND=$(cat | jq -r '.tool_input.command // empty' 2>/dev/null)
|
|
157
|
-
[ -z "$COMMAND" ] && exit 0
|
|
158
|
-
if echo "$COMMAND" | grep -qE '${t.pattern}'; then
|
|
159
|
-
echo "BLOCKED: ${t.msg}" >&2
|
|
160
|
-
exit 2
|
|
161
|
-
fi
|
|
162
|
-
exit 0`;
|
|
163
|
-
}
|
|
164
|
-
if (t.action === 'approve') {
|
|
165
|
-
return `#!/bin/bash
|
|
166
|
-
# ${t.desc}
|
|
167
|
-
# TRIGGER: ${t.trigger} MATCHER: "${t.matcher}"
|
|
168
|
-
COMMAND=$(cat | jq -r '.tool_input.command // empty' 2>/dev/null)
|
|
169
|
-
[ -z "$COMMAND" ] && exit 0
|
|
170
|
-
if echo "$COMMAND" | grep -qE '${t.pattern}'; then
|
|
171
|
-
echo '{"decision":"approve","reason":"${t.msg}"}'
|
|
172
|
-
fi
|
|
173
|
-
exit 0`;
|
|
174
|
-
}
|
|
175
|
-
if (t.action === 'warn-output') {
|
|
176
|
-
return `#!/bin/bash
|
|
177
|
-
# ${t.desc}
|
|
178
|
-
# TRIGGER: PostToolUse MATCHER: ""
|
|
179
|
-
OUTPUT=$(cat | jq -r '.tool_result // empty' 2>/dev/null)
|
|
180
|
-
LEN=\${#OUTPUT}
|
|
181
|
-
if [ "$LEN" -gt 50000 ]; then
|
|
182
|
-
echo "WARNING: Output is \${LEN} chars — consuming context fast" >&2
|
|
183
|
-
fi
|
|
184
|
-
exit 0`;
|
|
185
|
-
}
|
|
186
|
-
if (t.action === 'notify') {
|
|
187
|
-
return `#!/bin/bash
|
|
188
|
-
# ${t.desc}
|
|
189
|
-
# TRIGGER: Stop MATCHER: ""
|
|
190
|
-
notify-send "Claude Code" "Session completed" 2>/dev/null || \\
|
|
191
|
-
osascript -e 'display notification "Session completed"' 2>/dev/null
|
|
192
|
-
exit 0`;
|
|
193
|
-
}
|
|
194
|
-
if (t.action === 'friday') {
|
|
195
|
-
return `#!/bin/bash
|
|
196
|
-
# ${t.desc}
|
|
197
|
-
# TRIGGER: PreToolUse MATCHER: "Bash"
|
|
198
|
-
COMMAND=$(cat | jq -r '.tool_input.command // empty' 2>/dev/null)
|
|
199
|
-
[ -z "$COMMAND" ] && exit 0
|
|
200
|
-
if [ "$(date +%u)" = "5" ]; then
|
|
201
|
-
if echo "$COMMAND" | grep -qE '(deploy|publish|release)'; then
|
|
202
|
-
echo "BLOCKED: No deploys on Friday" >&2
|
|
203
|
-
exit 2
|
|
204
|
-
fi
|
|
205
|
-
fi
|
|
206
|
-
exit 0`;
|
|
207
|
-
}
|
|
208
|
-
return '#!/bin/bash\n# Custom hook\nexit 0';
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
function generateFromDesc(desc, trigger, matcher) {
|
|
212
|
-
const d = desc.toLowerCase();
|
|
213
|
-
let pattern = '', action = 'block', msg = desc;
|
|
214
|
-
|
|
215
|
-
if (d.includes('block') || d.includes('prevent') || d.includes('stop')) {
|
|
216
|
-
action = 'block';
|
|
217
|
-
if (d.includes('rm') || d.includes('delete')) pattern = 'rm\\s+.*-rf';
|
|
218
|
-
else if (d.includes('push') && d.includes('main')) pattern = 'git\\s+push\\s+.*\\b(main|master)\\b';
|
|
219
|
-
else if (d.includes('force')) pattern = '--force';
|
|
220
|
-
else if (d.includes('sudo')) pattern = '^\\s*sudo\\b';
|
|
221
|
-
else if (d.includes('drop')) pattern = 'DROP\\s+(DATABASE|TABLE)';
|
|
222
|
-
else if (d.includes('publish')) pattern = 'npm\\s+publish';
|
|
223
|
-
else if (d.includes('deploy')) pattern = '(deploy|vercel|netlify|firebase)';
|
|
224
|
-
else pattern = d.split(' ').pop();
|
|
225
|
-
} else if (d.includes('approve') || d.includes('allow') || d.includes('auto')) {
|
|
226
|
-
action = 'approve';
|
|
227
|
-
if (d.includes('test')) pattern = '(npm\\s+test|pytest|cargo\\s+test)';
|
|
228
|
-
else if (d.includes('build')) pattern = '(npm\\s+run\\s+build|cargo\\s+build|go\\s+build)';
|
|
229
|
-
else if (d.includes('git')) pattern = 'git\\s+(status|log|diff)';
|
|
230
|
-
else pattern = d.split(' ').pop();
|
|
231
|
-
} else if (d.includes('warn') || d.includes('alert') || d.includes('notify')) {
|
|
232
|
-
action = 'warn';
|
|
233
|
-
pattern = d.split(' ').pop();
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
if (action === 'block') {
|
|
237
|
-
return `#!/bin/bash
|
|
238
|
-
# ${desc}
|
|
239
|
-
# TRIGGER: ${trigger} MATCHER: "${matcher}"
|
|
240
|
-
COMMAND=$(cat | jq -r '.tool_input.command // empty' 2>/dev/null)
|
|
241
|
-
[ -z "$COMMAND" ] && exit 0
|
|
242
|
-
if echo "$COMMAND" | grep -qiE '${pattern}'; then
|
|
243
|
-
echo "BLOCKED: ${msg}" >&2
|
|
244
|
-
exit 2
|
|
245
|
-
fi
|
|
246
|
-
exit 0`;
|
|
247
|
-
}
|
|
248
|
-
if (action === 'approve') {
|
|
249
|
-
return `#!/bin/bash
|
|
250
|
-
# ${desc}
|
|
251
|
-
# TRIGGER: ${trigger} MATCHER: "${matcher}"
|
|
252
|
-
COMMAND=$(cat | jq -r '.tool_input.command // empty' 2>/dev/null)
|
|
253
|
-
[ -z "$COMMAND" ] && exit 0
|
|
254
|
-
if echo "$COMMAND" | grep -qiE '${pattern}'; then
|
|
255
|
-
echo '{"decision":"approve","reason":"${msg}"}'
|
|
256
|
-
fi
|
|
257
|
-
exit 0`;
|
|
258
|
-
}
|
|
259
|
-
return `#!/bin/bash
|
|
260
|
-
# ${desc}
|
|
261
|
-
# TRIGGER: ${trigger} MATCHER: "${matcher}"
|
|
262
|
-
COMMAND=$(cat | jq -r '.tool_input.command // empty' 2>/dev/null)
|
|
263
|
-
[ -z "$COMMAND" ] && exit 0
|
|
264
|
-
if echo "$COMMAND" | grep -qiE '${pattern}'; then
|
|
265
|
-
echo "WARNING: ${msg}" >&2
|
|
266
|
-
fi
|
|
267
|
-
exit 0`;
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
function copyCode() {
|
|
271
|
-
navigator.clipboard.writeText(document.getElementById('hook-code').querySelector('code').textContent);
|
|
272
|
-
document.getElementById('hook-code').querySelector('.copy-btn').textContent = 'Copied!';
|
|
273
|
-
setTimeout(() => document.getElementById('hook-code').querySelector('.copy-btn').textContent = 'Copy', 1500);
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
function copySettings() {
|
|
277
|
-
navigator.clipboard.writeText(document.getElementById('settings-code').querySelector('code').textContent);
|
|
278
|
-
document.getElementById('settings-code').querySelector('.copy-btn').textContent = 'Copied!';
|
|
279
|
-
setTimeout(() => document.getElementById('settings-code').querySelector('.copy-btn').textContent = 'Copy Settings', 1500);
|
|
280
|
-
}
|
|
281
|
-
</script>
|
|
282
|
-
</body>
|
|
283
|
-
</html>
|
package/docs/by-example.html
DELETED
|
@@ -1,234 +0,0 @@
|
|
|
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 Hooks by Example — Real Incidents, Real Fixes</title>
|
|
7
|
-
<meta name="description" content="Every hook was born from a real incident. See the before/after for each one.">
|
|
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;padding:1.5rem;line-height:1.6}
|
|
11
|
-
.c{max-width:800px;margin:0 auto}
|
|
12
|
-
h1{color:#f0f6fc;font-size:1.5rem;margin-bottom:.3rem}
|
|
13
|
-
.sub{color:#8b949e;font-size:.85rem;margin-bottom:1.2rem}
|
|
14
|
-
a{color:#58a6ff;text-decoration:none}
|
|
15
|
-
.example{background:#161b22;border:1px solid #30363d;border-radius:8px;padding:1rem;margin:.8rem 0}
|
|
16
|
-
.ex-title{font-weight:700;color:#f0f6fc;font-size:.95rem;margin-bottom:.3rem}
|
|
17
|
-
.ex-issue{font-size:.7rem;color:#58a6ff;margin-bottom:.5rem}
|
|
18
|
-
.ex-row{display:grid;grid-template-columns:1fr 1fr;gap:.8rem;margin:.5rem 0}
|
|
19
|
-
.before,.after{border-radius:6px;padding:.6rem;font-size:.78rem}
|
|
20
|
-
.before{background:#da363311;border:1px solid #da363333}
|
|
21
|
-
.after{background:#23863611;border:1px solid #23863633}
|
|
22
|
-
.label{font-size:.65rem;font-weight:bold;text-transform:uppercase;letter-spacing:.05em;margin-bottom:.3rem}
|
|
23
|
-
.label-bad{color:#f85149}
|
|
24
|
-
.label-good{color:#3fb950}
|
|
25
|
-
pre{background:#0d1117;border-radius:4px;padding:.4rem;font-size:.75rem;overflow-x:auto;margin:.3rem 0;color:#e6edf3}
|
|
26
|
-
.install{font-family:monospace;font-size:.75rem;color:#58a6ff;margin-top:.4rem;cursor:pointer}
|
|
27
|
-
.install:hover{text-decoration:underline}
|
|
28
|
-
.footer{text-align:center;color:#484f58;font-size:.7rem;margin-top:2rem;padding-top:1rem;border-top:1px solid #21262d}
|
|
29
|
-
code{background:#21262d;padding:.1rem .25rem;border-radius:3px;font-size:.8rem}
|
|
30
|
-
.count{color:#8b949e;font-size:.85rem;margin-bottom:1rem}
|
|
31
|
-
</style>
|
|
32
|
-
</head>
|
|
33
|
-
<body>
|
|
34
|
-
<div class="c">
|
|
35
|
-
|
|
36
|
-
<h1>Hooks by Example</h1>
|
|
37
|
-
<p class="sub">Every hook was born from a real incident. Here's the before and after.</p>
|
|
38
|
-
<p class="count">15 examples from real GitHub Issues</p>
|
|
39
|
-
|
|
40
|
-
<div class="example">
|
|
41
|
-
<div class="ex-title">destructive-guard</div>
|
|
42
|
-
<div class="ex-issue"><a href="https://github.com/anthropics/claude-code/issues/36339">#36339</a> — User lost entire C:\Users directory</div>
|
|
43
|
-
<div class="ex-row">
|
|
44
|
-
<div class="before"><div class="label label-bad">Without hook</div>
|
|
45
|
-
<pre>Claude: "Let me clean up the temp files"
|
|
46
|
-
$ rm -rf /
|
|
47
|
-
→ Entire filesystem deleted via NTFS junction traversal</pre></div>
|
|
48
|
-
<div class="after"><div class="label label-good">With hook</div>
|
|
49
|
-
<pre>Claude: "Let me clean up the temp files"
|
|
50
|
-
$ rm -rf /
|
|
51
|
-
→ BLOCKED: rm -rf on root directory
|
|
52
|
-
Claude: "I'll target just the temp folder instead"
|
|
53
|
-
$ rm -rf /tmp/old-builds</pre></div>
|
|
54
|
-
</div>
|
|
55
|
-
<div class="install" onclick="navigator.clipboard.writeText('npx cc-safe-setup');this.textContent='Copied!'">npx cc-safe-setup</div>
|
|
56
|
-
</div>
|
|
57
|
-
|
|
58
|
-
<div class="example">
|
|
59
|
-
<div class="ex-title">branch-guard</div>
|
|
60
|
-
<div class="ex-issue"><a href="https://github.com/anthropics/claude-code/issues/36640">#36640</a> — Untested code pushed to main at 3am</div>
|
|
61
|
-
<div class="ex-row">
|
|
62
|
-
<div class="before"><div class="label label-bad">Without hook</div>
|
|
63
|
-
<pre>Claude (autonomous, 3am): "Changes look good"
|
|
64
|
-
$ git push origin main --force
|
|
65
|
-
→ Production branch overwritten with untested code</pre></div>
|
|
66
|
-
<div class="after"><div class="label label-good">With hook</div>
|
|
67
|
-
<pre>Claude (autonomous, 3am): "Changes look good"
|
|
68
|
-
$ git push origin main --force
|
|
69
|
-
→ BLOCKED: Force push to main
|
|
70
|
-
Claude: "I'll create a PR instead"
|
|
71
|
-
$ git push origin feature/auth-fix</pre></div>
|
|
72
|
-
</div>
|
|
73
|
-
<div class="install" onclick="navigator.clipboard.writeText('npx cc-safe-setup');this.textContent='Copied!'">npx cc-safe-setup</div>
|
|
74
|
-
</div>
|
|
75
|
-
|
|
76
|
-
<div class="example">
|
|
77
|
-
<div class="ex-title">secret-guard</div>
|
|
78
|
-
<div class="ex-issue"><a href="https://github.com/anthropics/claude-code/issues/16561">#16561</a> — API keys committed via git add .</div>
|
|
79
|
-
<div class="ex-row">
|
|
80
|
-
<div class="before"><div class="label label-bad">Without hook</div>
|
|
81
|
-
<pre>Claude: "Let me commit all changes"
|
|
82
|
-
$ git add .
|
|
83
|
-
$ git push
|
|
84
|
-
→ .env with API keys pushed to public repo</pre></div>
|
|
85
|
-
<div class="after"><div class="label label-good">With hook</div>
|
|
86
|
-
<pre>Claude: "Let me commit all changes"
|
|
87
|
-
$ git add .
|
|
88
|
-
→ BLOCKED: .env file would be staged
|
|
89
|
-
Claude: "I'll add specific files instead"
|
|
90
|
-
$ git add src/ tests/</pre></div>
|
|
91
|
-
</div>
|
|
92
|
-
<div class="install" onclick="navigator.clipboard.writeText('npx cc-safe-setup');this.textContent='Copied!'">npx cc-safe-setup</div>
|
|
93
|
-
</div>
|
|
94
|
-
|
|
95
|
-
<div class="example">
|
|
96
|
-
<div class="ex-title">uncommitted-work-guard</div>
|
|
97
|
-
<div class="ex-issue"><a href="https://github.com/anthropics/claude-code/issues/37888">#37888</a> — Destroyed work twice in same session</div>
|
|
98
|
-
<div class="ex-row">
|
|
99
|
-
<div class="before"><div class="label label-bad">Without hook</div>
|
|
100
|
-
<pre>Claude: "Let me start fresh"
|
|
101
|
-
$ git checkout -- .
|
|
102
|
-
→ 3 hours of uncommitted edits gone forever
|
|
103
|
-
(Claude does it again 20 minutes later)</pre></div>
|
|
104
|
-
<div class="after"><div class="label label-good">With hook</div>
|
|
105
|
-
<pre>Claude: "Let me start fresh"
|
|
106
|
-
$ git checkout -- .
|
|
107
|
-
→ BLOCKED: 12 uncommitted changes would be lost
|
|
108
|
-
Claude: "I'll commit first"
|
|
109
|
-
$ git stash && git checkout -- .</pre></div>
|
|
110
|
-
</div>
|
|
111
|
-
<div class="install" onclick="navigator.clipboard.writeText('npx cc-safe-setup --install-example uncommitted-work-guard');this.textContent='Copied!'">npx cc-safe-setup --install-example uncommitted-work-guard</div>
|
|
112
|
-
</div>
|
|
113
|
-
|
|
114
|
-
<div class="example">
|
|
115
|
-
<div class="ex-title">test-deletion-guard</div>
|
|
116
|
-
<div class="ex-issue"><a href="https://github.com/anthropics/claude-code/issues/38050">#38050</a> — Claude deletes failing tests instead of fixing code</div>
|
|
117
|
-
<div class="ex-row">
|
|
118
|
-
<div class="before"><div class="label label-bad">Without hook</div>
|
|
119
|
-
<pre>Test: auth.test.js — 5 assertions
|
|
120
|
-
Claude: "Tests are failing, let me fix"
|
|
121
|
-
→ Deletes 3 test assertions
|
|
122
|
-
→ "All tests pass now!" (because there are fewer tests)</pre></div>
|
|
123
|
-
<div class="after"><div class="label label-good">With hook</div>
|
|
124
|
-
<pre>Claude: "Tests are failing, let me fix"
|
|
125
|
-
→ WARNING: Removing 3 test assertions
|
|
126
|
-
→ "Fix the code, not the tests"
|
|
127
|
-
Claude: "You're right, let me fix the auth logic"</pre></div>
|
|
128
|
-
</div>
|
|
129
|
-
<div class="install" onclick="navigator.clipboard.writeText('npx cc-safe-setup --install-example test-deletion-guard');this.textContent='Copied!'">npx cc-safe-setup --install-example test-deletion-guard</div>
|
|
130
|
-
</div>
|
|
131
|
-
|
|
132
|
-
<div class="example">
|
|
133
|
-
<div class="ex-title">token-budget-guard</div>
|
|
134
|
-
<div class="ex-issue"><a href="https://github.com/anthropics/claude-code/issues/38029">#38029</a> — Session consumed $342 without user knowing</div>
|
|
135
|
-
<div class="ex-row">
|
|
136
|
-
<div class="before"><div class="label label-bad">Without hook</div>
|
|
137
|
-
<pre>Session resumes, generates 652K output tokens
|
|
138
|
-
No warning, no limit
|
|
139
|
-
Bill arrives: $342 for one session</pre></div>
|
|
140
|
-
<div class="after"><div class="label label-good">With hook</div>
|
|
141
|
-
<pre>Session runs normally...
|
|
142
|
-
→ WARNING: Estimated cost ~$10, approaching $50 limit
|
|
143
|
-
→ Consider /compact or new session
|
|
144
|
-
At $50: BLOCKED — start a new session</pre></div>
|
|
145
|
-
</div>
|
|
146
|
-
<div class="install" onclick="navigator.clipboard.writeText('npx cc-safe-setup --install-example token-budget-guard');this.textContent='Copied!'">npx cc-safe-setup --install-example token-budget-guard</div>
|
|
147
|
-
</div>
|
|
148
|
-
|
|
149
|
-
<div class="example">
|
|
150
|
-
<div class="ex-title">fact-check-gate</div>
|
|
151
|
-
<div class="ex-issue"><a href="https://github.com/anthropics/claude-code/issues/38057">#38057</a> — False claims in technical docs</div>
|
|
152
|
-
<div class="ex-row">
|
|
153
|
-
<div class="before"><div class="label label-bad">Without hook</div>
|
|
154
|
-
<pre>Claude edits README.md:
|
|
155
|
-
"The `processAuth()` function accepts a JWT token"
|
|
156
|
-
→ processAuth() doesn't exist. Claude never read the source.</pre></div>
|
|
157
|
-
<div class="after"><div class="label label-good">With hook</div>
|
|
158
|
-
<pre>Claude edits README.md referencing `auth.ts`
|
|
159
|
-
→ WARNING: Doc references auth.ts — verify it was read
|
|
160
|
-
Claude: "Let me read the source first"
|
|
161
|
-
$ Read auth.ts → writes accurate documentation</pre></div>
|
|
162
|
-
</div>
|
|
163
|
-
<div class="install" onclick="navigator.clipboard.writeText('npx cc-safe-setup --install-example fact-check-gate');this.textContent='Copied!'">npx cc-safe-setup --install-example fact-check-gate</div>
|
|
164
|
-
</div>
|
|
165
|
-
|
|
166
|
-
<div class="example">
|
|
167
|
-
<div class="ex-title">block-database-wipe</div>
|
|
168
|
-
<div class="ex-issue"><a href="https://github.com/anthropics/claude-code/issues/37405">#37405</a> — Production database wiped</div>
|
|
169
|
-
<div class="ex-row">
|
|
170
|
-
<div class="before"><div class="label label-bad">Without hook</div>
|
|
171
|
-
<pre>Claude: "Let me reset the database schema"
|
|
172
|
-
$ php artisan migrate:fresh
|
|
173
|
-
→ All production data permanently deleted</pre></div>
|
|
174
|
-
<div class="after"><div class="label label-good">With hook</div>
|
|
175
|
-
<pre>Claude: "Let me reset the database schema"
|
|
176
|
-
$ php artisan migrate:fresh
|
|
177
|
-
→ BLOCKED: migrate:fresh wipes all tables
|
|
178
|
-
Claude: "I'll create a migration instead"
|
|
179
|
-
$ php artisan make:migration add_users_table</pre></div>
|
|
180
|
-
</div>
|
|
181
|
-
<div class="install" onclick="navigator.clipboard.writeText('npx cc-safe-setup --install-example block-database-wipe');this.textContent='Copied!'">npx cc-safe-setup --install-example block-database-wipe</div>
|
|
182
|
-
</div>
|
|
183
|
-
|
|
184
|
-
<div class="example">
|
|
185
|
-
<div class="ex-title">error-memory-guard</div>
|
|
186
|
-
<div class="ex-issue">Common pattern — Claude retries the same failing command 10 times</div>
|
|
187
|
-
<div class="ex-row">
|
|
188
|
-
<div class="before"><div class="label label-bad">Without hook</div>
|
|
189
|
-
<pre>$ npm install broken-pkg → ERROR
|
|
190
|
-
$ npm install broken-pkg → ERROR
|
|
191
|
-
$ npm install broken-pkg → ERROR
|
|
192
|
-
(repeats 10 more times)</pre></div>
|
|
193
|
-
<div class="after"><div class="label label-good">With hook</div>
|
|
194
|
-
<pre>$ npm install broken-pkg → ERROR (tracked)
|
|
195
|
-
$ npm install broken-pkg → ERROR (2nd failure)
|
|
196
|
-
$ npm install broken-pkg → BLOCKED: Failed 3 times
|
|
197
|
-
→ Try a different approach
|
|
198
|
-
Claude: "Let me check if there's an alternative package"</pre></div>
|
|
199
|
-
</div>
|
|
200
|
-
<div class="install" onclick="navigator.clipboard.writeText('npx cc-safe-setup --install-example error-memory-guard');this.textContent='Copied!'">npx cc-safe-setup --install-example error-memory-guard</div>
|
|
201
|
-
</div>
|
|
202
|
-
|
|
203
|
-
<div class="example">
|
|
204
|
-
<div class="ex-title">protect-dotfiles</div>
|
|
205
|
-
<div class="ex-issue"><a href="https://github.com/anthropics/claude-code/issues/37478">#37478</a> — .bashrc overwritten</div>
|
|
206
|
-
<div class="ex-row">
|
|
207
|
-
<div class="before"><div class="label label-bad">Without hook</div>
|
|
208
|
-
<pre>Claude: "Let me set up the dev environment"
|
|
209
|
-
→ Overwrites ~/.bashrc with new PATH
|
|
210
|
-
→ All shell aliases, functions, and config lost</pre></div>
|
|
211
|
-
<div class="after"><div class="label label-good">With hook</div>
|
|
212
|
-
<pre>Claude: "Let me set up the dev environment"
|
|
213
|
-
→ BLOCKED: Modifying ~/.bashrc
|
|
214
|
-
Claude: "I'll add to a project-local .envrc instead"</pre></div>
|
|
215
|
-
</div>
|
|
216
|
-
<div class="install" onclick="navigator.clipboard.writeText('npx cc-safe-setup --install-example protect-dotfiles');this.textContent='Copied!'">npx cc-safe-setup --install-example protect-dotfiles</div>
|
|
217
|
-
</div>
|
|
218
|
-
|
|
219
|
-
<div style="text-align:center;margin:1.5rem 0">
|
|
220
|
-
<p style="color:#8b949e;font-size:.85rem">Want all of these? One command:</p>
|
|
221
|
-
<pre style="display:inline-block;font-size:1rem;padding:.6rem 1.2rem;cursor:pointer" onclick="navigator.clipboard.writeText('npx cc-safe-setup --shield');this.textContent='Copied!';setTimeout(()=>this.textContent='npx cc-safe-setup --shield',1500)">npx cc-safe-setup --shield</pre>
|
|
222
|
-
</div>
|
|
223
|
-
|
|
224
|
-
<div class="footer">
|
|
225
|
-
<a href="hooks-cheatsheet.html">Cheat Sheet</a> ·
|
|
226
|
-
<a href="builder.html">Builder</a> ·
|
|
227
|
-
<a href="faq.html">FAQ</a> ·
|
|
228
|
-
<a href="migration-guide.html">Migration</a> ·
|
|
229
|
-
<a href="https://yurukusa.github.io/cc-hook-registry/playground.html">Playground</a> ·
|
|
230
|
-
<a href="https://github.com/yurukusa/cc-safe-setup">GitHub</a>
|
|
231
|
-
</div>
|
|
232
|
-
</div>
|
|
233
|
-
</body>
|
|
234
|
-
</html>
|