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.
@@ -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.0",
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,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');}
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>