cc-safe-setup 5.3.0 → 5.4.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 +1 -0
- package/audit-web/index.html +228 -642
- package/docs/index-legacy.html +685 -0
- package/docs/index.html +228 -642
- package/examples/reinject-claudemd.sh +44 -0
- package/index.mjs +9 -0
- package/package.json +1 -1
- package/docs/app.html +0 -271
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# ================================================================
|
|
3
|
+
# reinject-claudemd.sh — Re-inject CLAUDE.md content after compact
|
|
4
|
+
# ================================================================
|
|
5
|
+
# PURPOSE:
|
|
6
|
+
# After /compact, Claude Code often "forgets" CLAUDE.md rules.
|
|
7
|
+
# This SessionStart hook reads CLAUDE.md and outputs its content
|
|
8
|
+
# as a reminder, ensuring rules persist across sessions.
|
|
9
|
+
#
|
|
10
|
+
# GitHub #6354 (27r) — "Claude forgets everything in CLAUDE.md
|
|
11
|
+
# after compaction"
|
|
12
|
+
#
|
|
13
|
+
# TRIGGER: SessionStart
|
|
14
|
+
# MATCHER: ""
|
|
15
|
+
#
|
|
16
|
+
# HOW IT WORKS:
|
|
17
|
+
# On session start, reads CLAUDE.md from the current project
|
|
18
|
+
# and outputs key rules as a reminder message.
|
|
19
|
+
# ================================================================
|
|
20
|
+
|
|
21
|
+
# Look for CLAUDE.md in current directory and parents
|
|
22
|
+
CLAUDE_MD=""
|
|
23
|
+
DIR="$(pwd)"
|
|
24
|
+
while [ "$DIR" != "/" ]; do
|
|
25
|
+
if [ -f "$DIR/CLAUDE.md" ]; then
|
|
26
|
+
CLAUDE_MD="$DIR/CLAUDE.md"
|
|
27
|
+
break
|
|
28
|
+
fi
|
|
29
|
+
DIR=$(dirname "$DIR")
|
|
30
|
+
done
|
|
31
|
+
|
|
32
|
+
if [ -z "$CLAUDE_MD" ]; then
|
|
33
|
+
exit 0
|
|
34
|
+
fi
|
|
35
|
+
|
|
36
|
+
# Read CLAUDE.md and extract key rules (lines starting with - or *)
|
|
37
|
+
RULES=$(grep -E '^\s*[-*]' "$CLAUDE_MD" 2>/dev/null | head -20)
|
|
38
|
+
|
|
39
|
+
if [ -n "$RULES" ]; then
|
|
40
|
+
echo "REMINDER: CLAUDE.md rules (from $CLAUDE_MD):" >&2
|
|
41
|
+
echo "$RULES" >&2
|
|
42
|
+
fi
|
|
43
|
+
|
|
44
|
+
exit 0
|
package/index.mjs
CHANGED
|
@@ -135,6 +135,8 @@ if (HELP) {
|
|
|
135
135
|
api-error-alert Notifies when session stops due to API errors
|
|
136
136
|
|
|
137
137
|
More: https://github.com/yurukusa/cc-safe-setup
|
|
138
|
+
Find hooks: npx cc-hook-registry search <keyword>
|
|
139
|
+
Test hooks: npx cc-hook-test <hook.sh>
|
|
138
140
|
`);
|
|
139
141
|
process.exit(0);
|
|
140
142
|
}
|
|
@@ -363,6 +365,9 @@ function examples() {
|
|
|
363
365
|
'commit-quality-gate.sh': 'Warn on vague or too-long commit messages',
|
|
364
366
|
'diff-size-guard.sh': 'Warn/block on large diffs (10+ files warn, 50+ block)',
|
|
365
367
|
'dependency-audit.sh': 'Warn on new package installs not in manifest',
|
|
368
|
+
'binary-file-guard.sh': 'Warn when Write targets binary file types',
|
|
369
|
+
'stale-branch-guard.sh': 'Warn when branch is far behind default',
|
|
370
|
+
'symlink-guard.sh': 'Detect symlink/junction traversal in rm targets',
|
|
366
371
|
'cost-tracker.sh': 'Estimate session token cost ($1 warn, $5 alert)',
|
|
367
372
|
'read-before-edit.sh': 'Warn when editing files not recently read',
|
|
368
373
|
},
|
|
@@ -919,6 +924,10 @@ function issues() {
|
|
|
919
924
|
{ hook: 'diff-size-guard', issues: ['Unreviable mega-commits'] },
|
|
920
925
|
{ hook: 'dependency-audit', issues: ['Supply chain risk from unknown packages'] },
|
|
921
926
|
{ hook: 'read-before-edit', issues: ['old_string mismatch from editing unread files'] },
|
|
927
|
+
{ hook: 'symlink-guard', issues: ['#36339 NTFS junction traversal (93r)', '#764 Symlink resolution failure (63r)'] },
|
|
928
|
+
{ hook: 'binary-file-guard', issues: ['Binary file corruption from Write tool'] },
|
|
929
|
+
{ hook: 'stale-branch-guard', issues: ['Merge conflicts from stale branches'] },
|
|
930
|
+
{ hook: 'reinject-claudemd', issues: ['#6354 CLAUDE.md lost after compaction (27r)'] },
|
|
922
931
|
];
|
|
923
932
|
|
|
924
933
|
console.log();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cc-safe-setup",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.4.0",
|
|
4
4
|
"description": "One command to make Claude Code safe for autonomous operation. 8 built-in + 39 examples. 23 commands including dashboard, issues, create, audit, lint, diff. 260 tests. 2,500+ daily npm downloads.",
|
|
5
5
|
"main": "index.mjs",
|
|
6
6
|
"bin": {
|
package/docs/app.html
DELETED
|
@@ -1,271 +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>cc-safe-setup — Claude Code Safety Suite</title>
|
|
7
|
-
<meta name="description" content="Audit, build, browse, and learn Claude Code hooks. All in one page, 100% client-side.">
|
|
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}
|
|
11
|
-
.nav{display:flex;background:#161b22;border-bottom:1px solid #30363d;overflow-x:auto}
|
|
12
|
-
.nav a{padding:.75rem 1.2rem;color:#8b949e;text-decoration:none;font-size:.85rem;white-space:nowrap;border-bottom:2px solid transparent;cursor:pointer}
|
|
13
|
-
.nav a.active{color:#f0f6fc;border-bottom-color:#f78166}
|
|
14
|
-
.nav a:hover{color:#c9d1d9}
|
|
15
|
-
.page{display:none;max-width:800px;margin:0 auto;padding:1.5rem}
|
|
16
|
-
.page.active{display:block}
|
|
17
|
-
h1{font-size:1.3rem;color:#f0f6fc;margin-bottom:.5rem}
|
|
18
|
-
h2{font-size:1.1rem;color:#f0f6fc;margin:1.5rem 0 .5rem}
|
|
19
|
-
h3{font-size:.95rem;color:#c9d1d9;margin:1rem 0 .3rem}
|
|
20
|
-
p,.sub{color:#8b949e;font-size:.85rem;margin-bottom:.75rem}
|
|
21
|
-
a{color:#58a6ff;text-decoration:none}
|
|
22
|
-
textarea,input,select{width:100%;padding:.5rem;background:#161b22;border:1px solid #30363d;border-radius:4px;color:#c9d1d9;font-family:monospace;font-size:.8rem;margin:.25rem 0}
|
|
23
|
-
textarea{height:150px;resize:vertical}
|
|
24
|
-
button,.btn{background:#238636;color:#fff;border:none;padding:.5rem 1rem;border-radius:4px;cursor:pointer;font-size:.85rem;margin:.25rem .25rem .25rem 0}
|
|
25
|
-
button:hover,.btn:hover{background:#2ea043}
|
|
26
|
-
.btn-sm{padding:.25rem .5rem;font-size:.75rem}
|
|
27
|
-
.btn-secondary{background:#30363d}
|
|
28
|
-
pre{background:#161b22;border:1px solid #30363d;border-radius:4px;padding:.6rem;font-size:.75rem;overflow-x:auto;position:relative;margin:.3rem 0}
|
|
29
|
-
.copy{position:absolute;top:.3rem;right:.3rem;background:#30363d;color:#c9d1d9;border:none;padding:.15rem .4rem;border-radius:3px;font-size:.65rem;cursor:pointer}
|
|
30
|
-
.score{font-size:1.8rem;font-weight:bold;margin:.5rem 0}
|
|
31
|
-
.score.good{color:#3fb950} .score.mid{color:#d29922} .score.bad{color:#f85149}
|
|
32
|
-
.risk{background:#161b22;border:1px solid #30363d;border-radius:4px;padding:.75rem;margin:.3rem 0;font-size:.8rem}
|
|
33
|
-
.good-item{color:#3fb950;margin:.2rem 0;font-size:.8rem}
|
|
34
|
-
table{width:100%;border-collapse:collapse;font-size:.8rem;margin:.5rem 0}
|
|
35
|
-
th,td{text-align:left;padding:.35rem .5rem;border-bottom:1px solid #21262d}
|
|
36
|
-
th{font-weight:600;color:#f0f6fc}
|
|
37
|
-
.recipe{background:#161b22;border:1px solid #30363d;border-radius:4px;margin:.3rem 0;overflow:hidden}
|
|
38
|
-
.recipe summary{padding:.5rem .75rem;cursor:pointer;font-weight:600;color:#f0f6fc;font-size:.85rem}
|
|
39
|
-
.recipe-body{padding:0 .75rem .5rem}
|
|
40
|
-
.filter{padding:.2rem .5rem;border-radius:3px;border:1px solid #30363d;background:transparent;color:#8b949e;cursor:pointer;font-size:.7rem;margin:.15rem}
|
|
41
|
-
.filter.active{background:#238636;border-color:#238636;color:#fff}
|
|
42
|
-
.badge{display:inline-block;padding:.1rem .3rem;border-radius:3px;font-size:.65rem;font-weight:bold}
|
|
43
|
-
.badge-bash{background:#1f6feb22;color:#58a6ff;border:1px solid #1f6feb44}
|
|
44
|
-
.badge-js{background:#f0e68c22;color:#f0e68c;border:1px solid #f0e68c44}
|
|
45
|
-
.badge-py{background:#3fb95022;color:#3fb950;border:1px solid #3fb95044}
|
|
46
|
-
.badge-ts{background:#388bfd22;color:#388bfd;border:1px solid #388bfd44}
|
|
47
|
-
.check{color:#3fb950} .cross{color:#484f58}
|
|
48
|
-
.footer{text-align:center;color:#484f58;font-size:.7rem;padding:2rem 1rem}
|
|
49
|
-
@media print{.nav{display:none} .page{display:block!important}}
|
|
50
|
-
</style>
|
|
51
|
-
</head>
|
|
52
|
-
<body>
|
|
53
|
-
|
|
54
|
-
<nav class="nav">
|
|
55
|
-
<a onclick="go('audit')" id="nav-audit" class="active">Audit</a>
|
|
56
|
-
<a onclick="go('builder')" id="nav-builder">Hook Builder</a>
|
|
57
|
-
<a onclick="go('cookbook')" id="nav-cookbook">Cookbook</a>
|
|
58
|
-
<a onclick="go('ecosystem')" id="nav-ecosystem">Ecosystem</a>
|
|
59
|
-
<a onclick="go('cheatsheet')" id="nav-cheatsheet">Cheat Sheet</a>
|
|
60
|
-
</nav>
|
|
61
|
-
|
|
62
|
-
<!-- PAGE: AUDIT -->
|
|
63
|
-
<div class="page active" id="page-audit">
|
|
64
|
-
<h1>Safety Audit</h1>
|
|
65
|
-
<p>Paste your <code>~/.claude/settings.json</code>. Nothing leaves your browser.</p>
|
|
66
|
-
<textarea id="settings" placeholder='{"permissions":{"allow":["Bash(git:*)"]},"hooks":{"PreToolUse":[...]}}'></textarea>
|
|
67
|
-
<button onclick="runAudit()">Run Audit</button>
|
|
68
|
-
<button class="btn-secondary" onclick="generateFresh()">Generate Fresh Setup</button>
|
|
69
|
-
<div id="audit-results"></div>
|
|
70
|
-
</div>
|
|
71
|
-
|
|
72
|
-
<!-- PAGE: HOOK BUILDER -->
|
|
73
|
-
<div class="page" id="page-builder">
|
|
74
|
-
<h1>Hook Builder</h1>
|
|
75
|
-
<p>Build a custom hook without writing code.</p>
|
|
76
|
-
<div style="display:flex;gap:.75rem;flex-wrap:wrap">
|
|
77
|
-
<div style="flex:1;min-width:180px">
|
|
78
|
-
<label style="font-size:.75rem;color:#8b949e">Action</label>
|
|
79
|
-
<select id="hb-action"><option value="block">Block</option><option value="warn">Warn</option><option value="approve">Auto-approve</option></select>
|
|
80
|
-
</div>
|
|
81
|
-
<div style="flex:2;min-width:250px">
|
|
82
|
-
<label style="font-size:.75rem;color:#8b949e">Pattern (regex)</label>
|
|
83
|
-
<input id="hb-pattern" placeholder="e.g. rm\s+-rf, git push --force">
|
|
84
|
-
</div>
|
|
85
|
-
</div>
|
|
86
|
-
<label style="font-size:.75rem;color:#8b949e">Message</label>
|
|
87
|
-
<input id="hb-message" placeholder="e.g. Run tests before pushing">
|
|
88
|
-
<button onclick="buildHook()">Generate</button>
|
|
89
|
-
<div id="hb-result"></div>
|
|
90
|
-
</div>
|
|
91
|
-
|
|
92
|
-
<!-- PAGE: COOKBOOK -->
|
|
93
|
-
<div class="page" id="page-cookbook">
|
|
94
|
-
<h1>Hooks Cookbook</h1>
|
|
95
|
-
<p>Copy-paste recipes from real GitHub Issues.</p>
|
|
96
|
-
<input id="cb-search" placeholder="Search... (database, git, deploy)" oninput="filterRecipes()">
|
|
97
|
-
<div style="margin:.3rem 0" id="cb-filters"></div>
|
|
98
|
-
<div id="cb-count" style="color:#8b949e;font-size:.8rem"></div>
|
|
99
|
-
<div id="cb-list"></div>
|
|
100
|
-
</div>
|
|
101
|
-
|
|
102
|
-
<!-- PAGE: ECOSYSTEM -->
|
|
103
|
-
<div class="page" id="page-ecosystem">
|
|
104
|
-
<h1>Ecosystem Comparison</h1>
|
|
105
|
-
<p>All major Claude Code hook projects compared.</p>
|
|
106
|
-
<table>
|
|
107
|
-
<tr><th>Project</th><th>Lang</th><th>Hooks</th><th>Install</th></tr>
|
|
108
|
-
<tr><td><a href="https://github.com/kenryu42/claude-code-safety-net">safety-net</a></td><td><span class="badge badge-ts">TS</span></td><td>5</td><td>npx</td></tr>
|
|
109
|
-
<tr><td><a href="https://github.com/yurukusa/cc-safe-setup">cc-safe-setup</a></td><td><span class="badge badge-bash">Bash</span></td><td>8+39</td><td>npx</td></tr>
|
|
110
|
-
<tr><td><a href="https://github.com/karanb192/claude-code-hooks">karanb192</a></td><td><span class="badge badge-js">JS</span></td><td>5+</td><td>copy</td></tr>
|
|
111
|
-
<tr><td><a href="https://github.com/disler/claude-code-hooks-mastery">mastery</a></td><td><span class="badge badge-py">Python</span></td><td>12</td><td>copy</td></tr>
|
|
112
|
-
<tr><td><a href="https://github.com/lasso-security/claude-hooks">lasso</a></td><td><span class="badge badge-py">Python</span></td><td>1</td><td>install.sh</td></tr>
|
|
113
|
-
</table>
|
|
114
|
-
<h2>Feature Matrix</h2>
|
|
115
|
-
<table>
|
|
116
|
-
<tr><th>Feature</th><th>safety-net</th><th>cc-safe-setup</th><th>karanb192</th><th>mastery</th></tr>
|
|
117
|
-
<tr><td>rm -rf blocker</td><td class="check">✓</td><td class="check">✓</td><td class="check">✓</td><td class="check">✓</td></tr>
|
|
118
|
-
<tr><td>Branch guard</td><td class="check">✓</td><td class="check">✓</td><td class="cross">-</td><td class="cross">-</td></tr>
|
|
119
|
-
<tr><td>Secret guard</td><td class="cross">-</td><td class="check">✓</td><td class="check">✓</td><td class="cross">-</td></tr>
|
|
120
|
-
<tr><td>Syntax check</td><td class="cross">-</td><td class="check">✓</td><td class="cross">-</td><td class="cross">-</td></tr>
|
|
121
|
-
<tr><td>Context monitor</td><td class="cross">-</td><td class="check">✓</td><td class="cross">-</td><td class="cross">-</td></tr>
|
|
122
|
-
<tr><td>Hook generator</td><td class="cross">-</td><td class="check">✓</td><td class="cross">-</td><td class="cross">-</td></tr>
|
|
123
|
-
<tr><td>Dashboard</td><td class="cross">-</td><td class="check">✓</td><td class="cross">-</td><td class="cross">-</td></tr>
|
|
124
|
-
<tr><td>GitHub Action</td><td class="cross">-</td><td class="check">✓</td><td class="cross">-</td><td class="cross">-</td></tr>
|
|
125
|
-
</table>
|
|
126
|
-
</div>
|
|
127
|
-
|
|
128
|
-
<!-- PAGE: CHEAT SHEET -->
|
|
129
|
-
<div class="page" id="page-cheatsheet">
|
|
130
|
-
<h1>Hooks Cheat Sheet</h1>
|
|
131
|
-
<p>Print this page (Ctrl+P) for a quick reference.</p>
|
|
132
|
-
|
|
133
|
-
<h3>Lifecycle</h3>
|
|
134
|
-
<pre>Prompt → PreToolUse → Tool → PostToolUse → Stop</pre>
|
|
135
|
-
|
|
136
|
-
<h3>Exit Codes</h3>
|
|
137
|
-
<table><tr><th>Code</th><th>Meaning</th></tr>
|
|
138
|
-
<tr><td><code>0</code></td><td>Allow</td></tr>
|
|
139
|
-
<tr><td><code>2</code></td><td><strong>Block</strong></td></tr></table>
|
|
140
|
-
|
|
141
|
-
<h3>Minimal Block Hook</h3>
|
|
142
|
-
<pre>#!/bin/bash
|
|
143
|
-
CMD=$(cat | jq -r '.tool_input.command // empty')
|
|
144
|
-
[ -z "$CMD" ] && exit 0
|
|
145
|
-
echo "$CMD" | grep -qE 'PATTERN' && echo "BLOCKED" >&2 && exit 2
|
|
146
|
-
exit 0</pre>
|
|
147
|
-
|
|
148
|
-
<h3>Auto-Approve Hook</h3>
|
|
149
|
-
<pre>#!/bin/bash
|
|
150
|
-
CMD=$(cat | jq -r '.tool_input.command // empty')
|
|
151
|
-
[ -z "$CMD" ] && exit 0
|
|
152
|
-
echo "$CMD" | grep -qE '^git\s+(status|log|diff)' && \
|
|
153
|
-
jq -n '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"allow"}}'
|
|
154
|
-
exit 0</pre>
|
|
155
|
-
|
|
156
|
-
<h3>Quick Commands</h3>
|
|
157
|
-
<table>
|
|
158
|
-
<tr><td><code>npx cc-safe-setup</code></td><td>Install 8 hooks</td></tr>
|
|
159
|
-
<tr><td><code>--create "desc"</code></td><td>Generate hook</td></tr>
|
|
160
|
-
<tr><td><code>--audit</code></td><td>Score 0-100</td></tr>
|
|
161
|
-
<tr><td><code>--dashboard</code></td><td>Live status</td></tr>
|
|
162
|
-
<tr><td><code>--doctor</code></td><td>Diagnose</td></tr>
|
|
163
|
-
<tr><td><code>--benchmark</code></td><td>Speed test</td></tr>
|
|
164
|
-
</table>
|
|
165
|
-
</div>
|
|
166
|
-
|
|
167
|
-
<div class="footer">
|
|
168
|
-
cc-safe-setup · 100% client-side · <a href="https://github.com/yurukusa/cc-safe-setup">GitHub</a> · <a href="https://www.npmjs.com/package/cc-safe-setup">npm</a>
|
|
169
|
-
</div>
|
|
170
|
-
|
|
171
|
-
<script>
|
|
172
|
-
// Navigation
|
|
173
|
-
function go(page) {
|
|
174
|
-
document.querySelectorAll('.page').forEach(p => p.classList.remove('active'));
|
|
175
|
-
document.querySelectorAll('.nav a').forEach(a => a.classList.remove('active'));
|
|
176
|
-
document.getElementById('page-' + page).classList.add('active');
|
|
177
|
-
document.getElementById('nav-' + page).classList.add('active');
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
// AUDIT
|
|
181
|
-
function runAudit() {
|
|
182
|
-
const raw = document.getElementById('settings').value.trim();
|
|
183
|
-
const el = document.getElementById('audit-results');
|
|
184
|
-
if (!raw) { generateFresh(); return; }
|
|
185
|
-
let s; try { s = JSON.parse(raw); } catch { el.innerHTML='<p style="color:#f85149">Invalid JSON</p>'; return; }
|
|
186
|
-
const {risks,good,score} = analyze(s);
|
|
187
|
-
renderAudit(risks, good, score, el);
|
|
188
|
-
}
|
|
189
|
-
function generateFresh() {
|
|
190
|
-
const {risks,good,score} = analyze({});
|
|
191
|
-
renderAudit(risks, good, score, document.getElementById('audit-results'));
|
|
192
|
-
}
|
|
193
|
-
function analyze(s) {
|
|
194
|
-
const risks=[], good=[];
|
|
195
|
-
const pre=s.hooks?.PreToolUse||[], post=s.hooks?.PostToolUse||[];
|
|
196
|
-
const all=JSON.stringify(pre).toLowerCase();
|
|
197
|
-
if(!pre.length) risks.push({s:'CRITICAL',i:'No PreToolUse hooks',f:'npx cc-safe-setup'});
|
|
198
|
-
else { good.push('PreToolUse ('+pre.length+')');
|
|
199
|
-
if(!all.match(/destructive|guard|rm.*rf/)) risks.push({s:'HIGH',i:'No destructive guard',f:'npx cc-safe-setup'});
|
|
200
|
-
else good.push('Destructive protection');
|
|
201
|
-
if(!all.match(/branch|push|main/)) risks.push({s:'HIGH',i:'No branch guard',f:'npx cc-safe-setup'});
|
|
202
|
-
else good.push('Branch protection');
|
|
203
|
-
if(!all.match(/secret|env|credential/)) risks.push({s:'HIGH',i:'No secret guard',f:'npx cc-safe-setup'});
|
|
204
|
-
else good.push('Secret protection');
|
|
205
|
-
}
|
|
206
|
-
if(!post.length) risks.push({s:'MEDIUM',i:'No PostToolUse hooks',f:'npx cc-safe-setup'});
|
|
207
|
-
else good.push('PostToolUse ('+post.length+')');
|
|
208
|
-
const score=Math.max(0,100-risks.reduce((n,r)=>n+(r.s==='CRITICAL'?30:r.s==='HIGH'?20:10),0));
|
|
209
|
-
return {risks,good,score};
|
|
210
|
-
}
|
|
211
|
-
function renderAudit(risks,good,score,el) {
|
|
212
|
-
const cls=score>=80?'good':score>=50?'mid':'bad';
|
|
213
|
-
let h='<div class="score '+cls+'">'+score+'/100</div>';
|
|
214
|
-
if(good.length) { h+='<h3 style="color:#3fb950">Working</h3>'; good.forEach(g=>h+='<div class="good-item">✓ '+g+'</div>'); }
|
|
215
|
-
if(risks.length) { h+='<h3>Risks ('+risks.length+')</h3>'; risks.forEach(r=>h+='<div class="risk"><strong>['+r.s+']</strong> '+r.i+'<br><code>'+r.f+'</code></div>'); }
|
|
216
|
-
if(!risks.length) h+='<p style="color:#3fb950">No risks detected.</p>';
|
|
217
|
-
el.innerHTML=h;
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
// HOOK BUILDER
|
|
221
|
-
function buildHook() {
|
|
222
|
-
const action=document.getElementById('hb-action').value;
|
|
223
|
-
const pattern=document.getElementById('hb-pattern').value.trim();
|
|
224
|
-
const message=document.getElementById('hb-message').value.trim()||'Blocked';
|
|
225
|
-
const el=document.getElementById('hb-result');
|
|
226
|
-
if(!pattern){el.innerHTML='<p style="color:#f85149">Enter a pattern</p>';return;}
|
|
227
|
-
let s='#!/bin/bash\nCMD=$(cat | jq -r \'.tool_input.command // empty\' 2>/dev/null)\n[ -z "$CMD" ] && exit 0\n';
|
|
228
|
-
if(action==='block') s+='echo "$CMD" | grep -qE \''+pattern+'\' && echo "BLOCKED: '+message+'" >&2 && exit 2\n';
|
|
229
|
-
else if(action==='warn') s+='echo "$CMD" | grep -qE \''+pattern+'\' && echo "WARNING: '+message+'" >&2\n';
|
|
230
|
-
else s+='echo "$CMD" | grep -qE \''+pattern+'\' && jq -n \'{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"allow"}}\'\n';
|
|
231
|
-
s+='exit 0';
|
|
232
|
-
el.innerHTML='<h3>Script</h3><pre>'+esc(s)+'</pre><p style="font-size:.75rem;color:#8b949e">Save as ~/.claude/hooks/custom.sh, chmod +x, add to settings.json</p>';
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
// COOKBOOK
|
|
236
|
-
const RECIPES=[
|
|
237
|
-
{cat:'block',t:'Block rm -rf',d:'Destructive commands (#36339)',code:'CMD=$(cat|jq -r \'.tool_input.command // empty\')\n[ -z "$CMD" ] && exit 0\necho "$CMD"|grep -qE \'rm\\s+(-[rf]+\\s+)*/\' && echo "BLOCKED" >&2 && exit 2\nexit 0',trigger:'PreToolUse'},
|
|
238
|
-
{cat:'block',t:'Block force push',d:'Branch protection',code:'CMD=$(cat|jq -r \'.tool_input.command // empty\')\n[ -z "$CMD" ] && exit 0\necho "$CMD"|grep -qE \'git\\s+push.*--force\' && echo "BLOCKED" >&2 && exit 2\nexit 0',trigger:'PreToolUse'},
|
|
239
|
-
{cat:'block',t:'Block .env staging',d:'Secret leak prevention (#6527)',code:'CMD=$(cat|jq -r \'.tool_input.command // empty\')\n[ -z "$CMD" ] && exit 0\necho "$CMD"|grep -qiE \'git\\s+add.*\\.env\' && echo "BLOCKED" >&2 && exit 2\nexit 0',trigger:'PreToolUse'},
|
|
240
|
-
{cat:'block',t:'Block database wipe',d:'migrate:fresh, DROP DATABASE (#37405)',code:'CMD=$(cat|jq -r \'.tool_input.command // empty\')\n[ -z "$CMD" ] && exit 0\necho "$CMD"|grep -qiE \'migrate:fresh|DROP\\s+DATABASE|prisma\\s+migrate\\s+reset\' && echo "BLOCKED" >&2 && exit 2\nexit 0',trigger:'PreToolUse'},
|
|
241
|
-
{cat:'approve',t:'Auto-approve git read',d:'git status/log/diff with -C flag (#36900)',code:'CMD=$(cat|jq -r \'.tool_input.command // empty\')\n[ -z "$CMD" ] && exit 0\necho "$CMD"|grep -qE \'^\\s*git\\s+(-C\\s+\\S+\\s+)?(status|log|diff|branch|show)\' && jq -n \'{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"allow"}}\'\nexit 0',trigger:'PreToolUse'},
|
|
242
|
-
{cat:'approve',t:'Compound commands',d:'cd && git log auto-approve (#30519)',code:'# Full: npx cc-safe-setup --install-example compound-command-approver',trigger:'PreToolUse'},
|
|
243
|
-
{cat:'detect',t:'Loop detector',d:'Break command repetition (5+ times)',code:'CMD=$(cat|jq -r \'.tool_input.command // empty\')\n[ -z "$CMD" ] && exit 0\nSTATE=/tmp/cc-loop\necho "$CMD">>$STATE\ntail -n 10 $STATE>${STATE}.tmp&&mv ${STATE}.tmp $STATE\nCOUNT=$(grep -cF "$CMD" $STATE||echo 0)\n[ "$COUNT" -ge 5 ]&&echo "BLOCKED: repeated $COUNT times" >&2&&exit 2\nexit 0',trigger:'PreToolUse'},
|
|
244
|
-
{cat:'utility',t:'Session handoff',d:'Save state for next session',code:'# npx cc-safe-setup --install-example session-handoff',trigger:'Stop'},
|
|
245
|
-
{cat:'utility',t:'Cost tracker',d:'Estimate session token cost',code:'# npx cc-safe-setup --install-example cost-tracker',trigger:'PostToolUse'},
|
|
246
|
-
{cat:'utility',t:'tmp cleanup',d:'/tmp/claude-*-cwd files (#8856)',code:'find /tmp -maxdepth 1 -name \'claude-*-cwd\' -type f -mmin +60 -delete 2>/dev/null\nexit 0',trigger:'Stop'},
|
|
247
|
-
];
|
|
248
|
-
|
|
249
|
-
function initCookbook() {
|
|
250
|
-
const cats=['all','block','approve','detect','utility'];
|
|
251
|
-
document.getElementById('cb-filters').innerHTML=cats.map(c=>'<button class="filter'+(c==='all'?' active':'')+'" onclick="setCbFilter(\''+c+'\')">'+c+'</button>').join('');
|
|
252
|
-
filterRecipes();
|
|
253
|
-
}
|
|
254
|
-
let cbFilter='all';
|
|
255
|
-
function setCbFilter(f){cbFilter=f;document.querySelectorAll('#cb-filters .filter').forEach(b=>b.classList.toggle('active',b.textContent===f));filterRecipes();}
|
|
256
|
-
function filterRecipes(){
|
|
257
|
-
const q=(document.getElementById('cb-search')?.value||'').toLowerCase();
|
|
258
|
-
const filtered=RECIPES.filter(r=>(cbFilter==='all'||r.cat===cbFilter)&&(!q||r.t.toLowerCase().includes(q)||r.d.toLowerCase().includes(q)||(r.code||'').toLowerCase().includes(q)));
|
|
259
|
-
document.getElementById('cb-count').textContent=filtered.length+' recipe(s)';
|
|
260
|
-
document.getElementById('cb-list').innerHTML=filtered.map(r=>'<details class="recipe"><summary>'+esc(r.t)+' <span style="color:#484f58;font-size:.7rem">['+r.trigger+']</span></summary><div class="recipe-body"><p style="color:#8b949e;font-size:.8rem">'+esc(r.d)+'</p>'+(r.code?'<pre>'+esc(r.code)+'</pre>':'')+'</div></details>').join('');
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
function esc(s){return(s||'').replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');}
|
|
264
|
-
|
|
265
|
-
// URL param
|
|
266
|
-
(function(){const p=new URLSearchParams(location.search);const c=p.get('config');if(c){try{document.getElementById('settings').value=atob(c);runAudit();}catch{}}})();
|
|
267
|
-
|
|
268
|
-
initCookbook();
|
|
269
|
-
</script>
|
|
270
|
-
</body>
|
|
271
|
-
</html>
|