cc-safe-setup 7.5.0 → 7.6.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 CHANGED
@@ -6,7 +6,7 @@
6
6
 
7
7
  **One command to make Claude Code safe for autonomous operation.** [日本語](docs/README.ja.md)
8
8
 
9
- 8 built-in + 68 examples = **76 hooks**. 28 CLI commands. 394 tests. [Web Tool](https://yurukusa.github.io/cc-safe-setup/) · [Cheat Sheet](https://yurukusa.github.io/cc-safe-setup/cheatsheet.html) · [Troubleshooting](TROUBLESHOOTING.md)
9
+ 8 built-in + 71 examples = **79 hooks**. 28 CLI commands. 405 tests. [Web Tool](https://yurukusa.github.io/cc-safe-setup/) · [Hooks Cheat Sheet](https://yurukusa.github.io/cc-safe-setup/hooks-cheatsheet.html) · [Playground](https://yurukusa.github.io/cc-hook-registry/playground.html) · [Troubleshooting](TROUBLESHOOTING.md)
10
10
 
11
11
  ```bash
12
12
  npx cc-safe-setup
@@ -87,7 +87,7 @@ Each hook exists because a real incident happened without it.
87
87
  | `--scan [--apply]` | Tech stack detection |
88
88
  | `--export / --import` | Team config sharing |
89
89
  | `--verify` | Test each hook |
90
- | `--install-example <name>` | Install from 68 examples |
90
+ | `--install-example <name>` | Install from 71 examples |
91
91
  | `--examples [filter]` | Browse examples by keyword |
92
92
  | `--full` | All-in-one setup |
93
93
  | `--status` | Check installed hooks |
@@ -0,0 +1,319 @@
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 Cheat Sheet — Copy-Paste Patterns</title>
7
+ <meta name="description" content="30+ copy-paste hook patterns for Claude Code. Block dangerous commands, auto-approve safe ones, monitor costs. One page, zero fluff.">
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.5}
11
+ .c{max-width:900px;margin:0 auto}
12
+ h1{color:#f0f6fc;font-size:1.5rem;margin-bottom:.3rem}
13
+ h2{color:#f0f6fc;font-size:1.1rem;margin:1.5rem 0 .5rem;padding-bottom:.3rem;border-bottom:1px solid #21262d}
14
+ h3{color:#c9d1d9;font-size:.9rem;margin:.8rem 0 .3rem}
15
+ .sub{color:#8b949e;font-size:.85rem;margin-bottom:1.5rem}
16
+ a{color:#58a6ff;text-decoration:none}
17
+ code{background:#161b22;padding:.15rem .3rem;border-radius:3px;font-size:.85rem;color:#e6edf3}
18
+ pre{background:#161b22;border:1px solid #30363d;border-radius:6px;padding:.8rem;overflow-x:auto;font-size:.8rem;color:#e6edf3;margin:.4rem 0 .8rem;position:relative}
19
+ .copy{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}
20
+ .copy:hover{color:#f0f6fc;border-color:#58a6ff}
21
+ .pattern{background:#161b22;border:1px solid #30363d;border-radius:6px;padding:.8rem;margin:.5rem 0}
22
+ .pattern-title{font-weight:600;color:#f0f6fc;font-size:.85rem}
23
+ .pattern-desc{color:#8b949e;font-size:.78rem;margin:.2rem 0}
24
+ .badge{display:inline-block;padding:.1rem .3rem;border-radius:3px;font-size:.6rem;font-weight:bold;margin-left:.3rem}
25
+ .b-block{background:#da363322;color:#f85149;border:1px solid #da363344}
26
+ .b-warn{background:#d2992222;color:#d29922;border:1px solid #d2992244}
27
+ .b-approve{background:#23863622;color:#3fb950;border:1px solid #23863644}
28
+ .b-monitor{background:#1f6feb22;color:#58a6ff;border:1px solid #1f6feb44}
29
+ .toc{display:grid;grid-template-columns:repeat(auto-fill,minmax(180px,1fr));gap:.3rem;margin:.8rem 0}
30
+ .toc a{display:block;background:#161b22;border:1px solid #30363d;border-radius:4px;padding:.3rem .5rem;font-size:.75rem}
31
+ .toc a:hover{border-color:#58a6ff}
32
+ .footer{text-align:center;color:#484f58;font-size:.7rem;margin-top:2rem;padding-top:1rem;border-top:1px solid #21262d}
33
+ .quick{background:#238636;color:#fff;padding:.5rem 1rem;border-radius:6px;font-family:monospace;font-size:.85rem;cursor:pointer;border:none;display:block;width:100%;text-align:center;margin:.5rem 0}
34
+ .quick:hover{background:#2ea043}
35
+ table{width:100%;border-collapse:collapse;margin:.5rem 0;font-size:.8rem}
36
+ th,td{padding:.3rem .5rem;border:1px solid #21262d;text-align:left}
37
+ th{background:#161b22;color:#f0f6fc}
38
+ .note{background:#161b22;border-left:3px solid #58a6ff;padding:.5rem .8rem;margin:.5rem 0;font-size:.78rem;color:#8b949e}
39
+ </style>
40
+ </head>
41
+ <body>
42
+ <div class="c">
43
+
44
+ <h1>Claude Code Hooks Cheat Sheet</h1>
45
+ <p class="sub">Copy-paste patterns. Zero fluff. <a href="https://docs.anthropic.com/en/docs/claude-code/hooks">Official docs</a></p>
46
+
47
+ <button class="quick" onclick="navigator.clipboard.writeText('npx cc-safe-setup');this.textContent='Copied!';setTimeout(()=>this.textContent='npx cc-safe-setup — install 8 safety hooks instantly',1500)">npx cc-safe-setup — install 8 safety hooks instantly</button>
48
+
49
+ <div class="toc">
50
+ <a href="#basics">Basics</a>
51
+ <a href="#block">Block Patterns</a>
52
+ <a href="#approve">Auto-Approve</a>
53
+ <a href="#monitor">Monitor</a>
54
+ <a href="#settings">settings.json</a>
55
+ <a href="#debug">Debug</a>
56
+ <a href="#recipes">Quick Recipes</a>
57
+ <a href="#exit-codes">Exit Codes</a>
58
+ </div>
59
+
60
+ <h2 id="basics">How Hooks Work</h2>
61
+
62
+ <table>
63
+ <tr><th>Event</th><th>When</th><th>Use for</th></tr>
64
+ <tr><td><code>PreToolUse</code></td><td>Before any tool runs</td><td>Block/approve commands</td></tr>
65
+ <tr><td><code>PostToolUse</code></td><td>After tool completes</td><td>Check results, monitor</td></tr>
66
+ <tr><td><code>Stop</code></td><td>Claude finishes responding</td><td>Notifications, cleanup</td></tr>
67
+ <tr><td><code>SubagentStop</code></td><td>Subagent finishes</td><td>Monitor sub-agents</td></tr>
68
+ </table>
69
+
70
+ <table>
71
+ <tr><th>Exit Code</th><th>Effect</th></tr>
72
+ <tr><td><code>0</code></td><td>Allow (no action)</td></tr>
73
+ <tr><td><code>2</code></td><td><strong>BLOCK</strong> — model cannot bypass</td></tr>
74
+ <tr><td>stdout JSON</td><td>Override decision (approve/block with reason)</td></tr>
75
+ </table>
76
+
77
+ <div class="note">
78
+ <strong>Key insight:</strong> CLAUDE.md rules degrade as context fills up. Hooks run every single time, enforced at the process level.
79
+ </div>
80
+
81
+ <h2 id="block">Block Patterns</h2>
82
+
83
+ <h3>Block a specific command <span class="badge b-block">BLOCK</span></h3>
84
+ <pre><code>#!/bin/bash
85
+ COMMAND=$(cat | jq -r '.tool_input.command // empty' 2>/dev/null)
86
+ [ -z "$COMMAND" ] && exit 0
87
+ if echo "$COMMAND" | grep -qE 'rm\s+.*-rf\s+/'; then
88
+ echo "BLOCKED: rm -rf on root" >&2
89
+ exit 2
90
+ fi
91
+ exit 0</code><button class="copy" onclick="copyBlock(this)">Copy</button></pre>
92
+
93
+ <h3>Block file edits to specific paths <span class="badge b-block">BLOCK</span></h3>
94
+ <pre><code>#!/bin/bash
95
+ FILE=$(cat | jq -r '.tool_input.file_path // empty' 2>/dev/null)
96
+ [ -z "$FILE" ] && exit 0
97
+ case "$FILE" in
98
+ */.env*|*/.ssh/*|*/.aws/*|*/credentials*)
99
+ echo "BLOCKED: Protected file" >&2; exit 2 ;;
100
+ esac
101
+ exit 0</code><button class="copy" onclick="copyBlock(this)">Copy</button></pre>
102
+
103
+ <h3>Block git push to main <span class="badge b-block">BLOCK</span></h3>
104
+ <pre><code>#!/bin/bash
105
+ COMMAND=$(cat | jq -r '.tool_input.command // empty' 2>/dev/null)
106
+ [ -z "$COMMAND" ] && exit 0
107
+ if echo "$COMMAND" | grep -qE 'git\s+push\s+.*\b(main|master)\b'; then
108
+ echo "BLOCKED: Direct push to main" >&2; exit 2
109
+ fi
110
+ if echo "$COMMAND" | grep -qE 'git\s+push\s+.*--force'; then
111
+ echo "BLOCKED: Force push" >&2; exit 2
112
+ fi
113
+ exit 0</code><button class="copy" onclick="copyBlock(this)">Copy</button></pre>
114
+
115
+ <h3>Block destructive git when dirty <span class="badge b-block">BLOCK</span></h3>
116
+ <pre><code>#!/bin/bash
117
+ COMMAND=$(cat | jq -r '.tool_input.command // empty' 2>/dev/null)
118
+ [ -z "$COMMAND" ] && exit 0
119
+ echo "$COMMAND" | grep -qE 'git\s+(checkout\s+--|reset\s+--hard|clean\s+-f)' || exit 0
120
+ DIRTY=$(git status --porcelain 2>/dev/null)
121
+ if [ -n "$DIRTY" ]; then
122
+ echo "BLOCKED: Uncommitted changes would be lost" >&2; exit 2
123
+ fi
124
+ exit 0</code><button class="copy" onclick="copyBlock(this)">Copy</button></pre>
125
+
126
+ <h3>Block commits with conflict markers <span class="badge b-block">BLOCK</span></h3>
127
+ <pre><code>#!/bin/bash
128
+ COMMAND=$(cat | jq -r '.tool_input.command // empty' 2>/dev/null)
129
+ echo "$COMMAND" | grep -qE 'git\s+commit' || exit 0
130
+ CONFLICTS=$(git diff --cached --name-only 2>/dev/null | while read f; do
131
+ [ -f "$f" ] && grep -lE '^(<{7}|={7}|>{7})' "$f" 2>/dev/null
132
+ done)
133
+ if [ -n "$CONFLICTS" ]; then
134
+ echo "BLOCKED: Conflict markers in staged files" >&2; exit 2
135
+ fi
136
+ exit 0</code><button class="copy" onclick="copyBlock(this)">Copy</button></pre>
137
+
138
+ <h2 id="approve">Auto-Approve Patterns</h2>
139
+
140
+ <h3>Auto-approve safe commands <span class="badge b-approve">APPROVE</span></h3>
141
+ <pre><code>#!/bin/bash
142
+ COMMAND=$(cat | jq -r '.tool_input.command // empty' 2>/dev/null)
143
+ [ -z "$COMMAND" ] && exit 0
144
+ if echo "$COMMAND" | grep -qE '^\s*(npm\s+test|cargo\s+test|go\s+test|pytest|make\s+test)'; then
145
+ echo '{"decision":"approve","reason":"Safe test command"}'
146
+ exit 0
147
+ fi
148
+ exit 0</code><button class="copy" onclick="copyBlock(this)">Copy</button></pre>
149
+
150
+ <h3>Auto-approve read-only git <span class="badge b-approve">APPROVE</span></h3>
151
+ <pre><code>#!/bin/bash
152
+ COMMAND=$(cat | jq -r '.tool_input.command // empty' 2>/dev/null)
153
+ [ -z "$COMMAND" ] && exit 0
154
+ if echo "$COMMAND" | grep -qE '^\s*git\s+(status|log|diff|show|branch|remote|tag\s+-l)'; then
155
+ echo '{"decision":"approve","reason":"Read-only git"}'
156
+ fi
157
+ exit 0</code><button class="copy" onclick="copyBlock(this)">Copy</button></pre>
158
+
159
+ <h2 id="monitor">Monitor Patterns</h2>
160
+
161
+ <h3>Log all tool calls <span class="badge b-monitor">MONITOR</span></h3>
162
+ <pre><code>#!/bin/bash
163
+ INPUT=$(cat)
164
+ TOOL=$(echo "$INPUT" | jq -r '.tool_name // "unknown"' 2>/dev/null)
165
+ CMD=$(echo "$INPUT" | jq -r '.tool_input.command // .tool_input.file_path // ""' 2>/dev/null)
166
+ echo "[$(date -Iseconds)] $TOOL: $CMD" >> ~/.claude/tool-calls.log
167
+ exit 0</code><button class="copy" onclick="copyBlock(this)">Copy</button></pre>
168
+
169
+ <h3>Warn on large output <span class="badge b-warn">WARN</span></h3>
170
+ <pre><code>#!/bin/bash
171
+ OUTPUT=$(cat | jq -r '.tool_result // empty' 2>/dev/null)
172
+ LEN=${#OUTPUT}
173
+ if [ "$LEN" -gt 50000 ]; then
174
+ echo "WARNING: Output is ${LEN} chars — consuming context fast" >&2
175
+ fi
176
+ exit 0</code><button class="copy" onclick="copyBlock(this)">Copy</button></pre>
177
+
178
+ <h3>Desktop notification on session end <span class="badge b-monitor">MONITOR</span></h3>
179
+ <pre><code>#!/bin/bash
180
+ # TRIGGER: Stop MATCHER: ""
181
+ MSG=$(cat | jq -r '.stop_reason // "completed"' 2>/dev/null)
182
+ notify-send "Claude Code" "Session $MSG" 2>/dev/null || # Linux
183
+ osascript -e "display notification \"$MSG\"" 2>/dev/null # macOS
184
+ exit 0</code><button class="copy" onclick="copyBlock(this)">Copy</button></pre>
185
+
186
+ <h2 id="settings">settings.json Reference</h2>
187
+
188
+ <h3>Minimal setup (one hook)</h3>
189
+ <pre><code>{
190
+ "hooks": {
191
+ "PreToolUse": [{
192
+ "matcher": "Bash",
193
+ "hooks": [{
194
+ "type": "command",
195
+ "command": "bash ~/.claude/hooks/my-guard.sh"
196
+ }]
197
+ }]
198
+ }
199
+ }</code><button class="copy" onclick="copyBlock(this)">Copy</button></pre>
200
+
201
+ <h3>Multiple hooks on different triggers</h3>
202
+ <pre><code>{
203
+ "hooks": {
204
+ "PreToolUse": [
205
+ {
206
+ "matcher": "Bash",
207
+ "hooks": [
208
+ {"type":"command","command":"bash ~/.claude/hooks/destructive-guard.sh"},
209
+ {"type":"command","command":"bash ~/.claude/hooks/branch-guard.sh"}
210
+ ]
211
+ },
212
+ {
213
+ "matcher": "Edit|Write",
214
+ "hooks": [
215
+ {"type":"command","command":"bash ~/.claude/hooks/scope-guard.sh"}
216
+ ]
217
+ }
218
+ ],
219
+ "PostToolUse": [{
220
+ "matcher": "",
221
+ "hooks": [{"type":"command","command":"bash ~/.claude/hooks/monitor.sh"}]
222
+ }],
223
+ "Stop": [{
224
+ "matcher": "",
225
+ "hooks": [{"type":"command","command":"bash ~/.claude/hooks/notify.sh"}]
226
+ }]
227
+ }
228
+ }</code><button class="copy" onclick="copyBlock(this)">Copy</button></pre>
229
+
230
+ <div class="note">
231
+ <strong>Matcher:</strong> Empty string <code>""</code> matches all tools. <code>"Bash"</code> matches Bash only. <code>"Edit|Write"</code> matches either. Matcher is a regex against the tool name.
232
+ </div>
233
+
234
+ <h2 id="debug">Debug Hooks</h2>
235
+
236
+ <h3>Test a hook manually</h3>
237
+ <pre><code># Test PreToolUse hook with sample input
238
+ echo '{"tool_name":"Bash","tool_input":{"command":"rm -rf /"}}' | bash ~/.claude/hooks/my-guard.sh
239
+ echo "Exit code: $?"</code><button class="copy" onclick="copyBlock(this)">Copy</button></pre>
240
+
241
+ <h3>Check hook is executable</h3>
242
+ <pre><code>ls -la ~/.claude/hooks/
243
+ # Fix: chmod +x ~/.claude/hooks/*.sh</code><button class="copy" onclick="copyBlock(this)">Copy</button></pre>
244
+
245
+ <h3>Validate settings.json</h3>
246
+ <pre><code>python3 -c "import json; json.load(open('$HOME/.claude/settings.json')); print('Valid')"</code><button class="copy" onclick="copyBlock(this)">Copy</button></pre>
247
+
248
+ <h3>Quick health check</h3>
249
+ <pre><code>npx cc-safe-setup --quickfix # Auto-detect and fix problems
250
+ npx cc-safe-setup --doctor # Detailed diagnosis
251
+ npx cc-safe-setup --verify # Test each hook</code><button class="copy" onclick="copyBlock(this)">Copy</button></pre>
252
+
253
+ <h2 id="recipes">Quick Recipes</h2>
254
+
255
+ <div class="pattern">
256
+ <div class="pattern-title">I want to block all sudo commands</div>
257
+ <div class="pattern-desc"><code>npx cc-safe-setup --install-example no-sudo-guard</code></div>
258
+ </div>
259
+
260
+ <div class="pattern">
261
+ <div class="pattern-title">I want to block database drops</div>
262
+ <div class="pattern-desc"><code>npx cc-safe-setup --install-example block-database-wipe</code></div>
263
+ </div>
264
+
265
+ <div class="pattern">
266
+ <div class="pattern-title">I want to auto-approve Python tools</div>
267
+ <div class="pattern-desc"><code>npx cc-safe-setup --install-example auto-approve-python</code></div>
268
+ </div>
269
+
270
+ <div class="pattern">
271
+ <div class="pattern-title">I want to prevent deploys on Fridays</div>
272
+ <div class="pattern-desc"><code>npx cc-safe-setup --install-example no-deploy-friday</code></div>
273
+ </div>
274
+
275
+ <div class="pattern">
276
+ <div class="pattern-title">I want to track session cost</div>
277
+ <div class="pattern-desc"><code>npx cc-safe-setup --install-example token-budget-guard</code></div>
278
+ </div>
279
+
280
+ <div class="pattern">
281
+ <div class="pattern-title">I want to generate a custom hook</div>
282
+ <div class="pattern-desc"><code>npx cc-safe-setup --create "block npm publish without tests"</code></div>
283
+ </div>
284
+
285
+ <div class="pattern">
286
+ <div class="pattern-title">I want to detect what my stack needs</div>
287
+ <div class="pattern-desc"><code>npx cc-safe-setup --scan</code></div>
288
+ </div>
289
+
290
+ <h2 id="exit-codes">Exit Code Reference</h2>
291
+ <table>
292
+ <tr><th>Code</th><th>Meaning</th><th>When to use</th></tr>
293
+ <tr><td><code>0</code></td><td>Allow / no opinion</td><td>Default — hook has nothing to say</td></tr>
294
+ <tr><td><code>2</code></td><td>BLOCK</td><td>Dangerous command detected</td></tr>
295
+ <tr><td><code>0</code> + stdout JSON</td><td>Override decision</td><td>Auto-approve with reason</td></tr>
296
+ <tr><td><code>1</code></td><td>Hook error (ignored)</td><td>Don't use — hook failures are silent</td></tr>
297
+ </table>
298
+
299
+ <div class="note">
300
+ <strong>stdout JSON format:</strong> <code>{"decision":"approve","reason":"Safe command"}</code> or <code>{"decision":"block","reason":"Too dangerous"}</code>
301
+ </div>
302
+
303
+ <div class="footer">
304
+ <a href="https://github.com/yurukusa/cc-safe-setup">cc-safe-setup</a> — 71 hooks, 28 commands, 405 tests ·
305
+ <a href="https://yurukusa.github.io/cc-hook-registry/playground.html">Playground</a> ·
306
+ <a href="https://yurukusa.github.io/cc-hook-registry/">Registry</a>
307
+ </div>
308
+ </div>
309
+
310
+ <script>
311
+ function copyBlock(btn) {
312
+ const code = btn.parentElement.querySelector('code').textContent;
313
+ navigator.clipboard.writeText(code);
314
+ btn.textContent = 'Copied!';
315
+ setTimeout(() => btn.textContent = 'Copy', 1500);
316
+ }
317
+ </script>
318
+ </body>
319
+ </html>
@@ -0,0 +1,88 @@
1
+ #!/bin/bash
2
+ # ================================================================
3
+ # strict-allowlist.sh — Only allow explicitly permitted commands
4
+ # ================================================================
5
+ # PURPOSE:
6
+ # Instead of blocking known-bad commands (denylist), this hook
7
+ # only allows known-good commands (allowlist). Every command not
8
+ # on the list requires explicit approval.
9
+ #
10
+ # TRIGGER: PreToolUse MATCHER: "Bash"
11
+ #
12
+ # CONFIG:
13
+ # CC_ALLOWLIST_FILE=~/.claude/allowlist.txt
14
+ # One pattern per line, regex supported.
15
+ # Empty file = block everything.
16
+ #
17
+ # Born from: https://github.com/anthropics/claude-code/issues/37471
18
+ # "Immutable session manifest with allowlist-only enforcement"
19
+ # ================================================================
20
+
21
+ COMMAND=$(cat | jq -r '.tool_input.command // empty' 2>/dev/null)
22
+ [ -z "$COMMAND" ] && exit 0
23
+
24
+ ALLOWLIST="${CC_ALLOWLIST_FILE:-$HOME/.claude/allowlist.txt}"
25
+
26
+ # If no allowlist file exists, create a default one
27
+ if [ ! -f "$ALLOWLIST" ]; then
28
+ mkdir -p "$(dirname "$ALLOWLIST")"
29
+ cat > "$ALLOWLIST" <<'DEFAULT'
30
+ # Claude Code Strict Allowlist
31
+ # One regex pattern per line. Commands matching any pattern are allowed.
32
+ # Lines starting with # are comments.
33
+ # Empty = block all Bash commands.
34
+
35
+ # Read-only operations
36
+ ^ls\b
37
+ ^cat\b
38
+ ^head\b
39
+ ^tail\b
40
+ ^wc\b
41
+ ^grep\b
42
+ ^find\b
43
+ ^which\b
44
+ ^echo\b
45
+ ^pwd$
46
+ ^date$
47
+
48
+ # Git read
49
+ ^git\s+(status|log|diff|show|branch|remote|tag\s+-l)
50
+ ^git\s+add\b
51
+ ^git\s+commit\b
52
+
53
+ # Build/test
54
+ ^npm\s+(test|run\s+(build|lint|check|format))
55
+ ^pytest\b
56
+ ^cargo\s+(build|test|check|clippy)
57
+ ^go\s+(build|test|vet|fmt)
58
+ ^make\s*(build|test|lint|check|all)?$
59
+
60
+ # Package info
61
+ ^npm\s+(ls|list|info|view|outdated)
62
+ ^pip\s+(list|show|freeze)
63
+ DEFAULT
64
+ echo "NOTE: Created default allowlist at $ALLOWLIST" >&2
65
+ echo "Edit it to customize permitted commands." >&2
66
+ fi
67
+
68
+ # Check command against allowlist
69
+ ALLOWED=0
70
+ while IFS= read -r pattern; do
71
+ # Skip comments and empty lines
72
+ [[ "$pattern" =~ ^[[:space:]]*# ]] && continue
73
+ [[ -z "$pattern" ]] && continue
74
+
75
+ if echo "$COMMAND" | grep -qE "$pattern"; then
76
+ ALLOWED=1
77
+ break
78
+ fi
79
+ done < "$ALLOWLIST"
80
+
81
+ if [ "$ALLOWED" -eq 0 ]; then
82
+ echo "BLOCKED: Command not in allowlist." >&2
83
+ echo "Command: $COMMAND" >&2
84
+ echo "Add a matching pattern to $ALLOWLIST to permit." >&2
85
+ exit 2
86
+ fi
87
+
88
+ exit 0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cc-safe-setup",
3
- "version": "7.5.0",
3
+ "version": "7.6.0",
4
4
  "description": "One command to make Claude Code safe. 59 hooks (8 built-in + 51 examples). 26 CLI commands: dashboard, create, audit, lint, diff, migrate, compare, generate-ci. 284 tests.",
5
5
  "main": "index.mjs",
6
6
  "bin": {