cc-safe-setup 3.8.0 → 4.0.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 +2 -0
- package/docs/ROADMAP.md +83 -0
- package/examples/cost-tracker.sh +56 -0
- package/index.mjs +73 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -224,6 +224,8 @@ Or browse all available examples in [`examples/`](examples/):
|
|
|
224
224
|
- **session-handoff.sh** — Auto-save git state and session info to `~/.claude/session-handoff.md` on session end
|
|
225
225
|
- **diff-size-guard.sh** — Warn/block when committing too many files at once (default: warn at 10, block at 50)
|
|
226
226
|
- **dependency-audit.sh** — Warn when installing packages not in manifest (npm/pip/cargo supply chain awareness)
|
|
227
|
+
- **cost-tracker.sh** — Estimate session token cost and warn at thresholds ($1, $5)
|
|
228
|
+
- **read-before-edit.sh** — Warn when editing files not recently read (prevents old_string mismatches)
|
|
227
229
|
|
|
228
230
|
## Safety Checklist
|
|
229
231
|
|
package/docs/ROADMAP.md
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
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
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# ================================================================
|
|
3
|
+
# cost-tracker.sh — Estimate session token cost
|
|
4
|
+
# ================================================================
|
|
5
|
+
# PURPOSE:
|
|
6
|
+
# Claude Code doesn't show token costs. This hook tracks
|
|
7
|
+
# tool calls and estimates cumulative cost, warning at thresholds.
|
|
8
|
+
#
|
|
9
|
+
# TRIGGER: PostToolUse
|
|
10
|
+
# MATCHER: ""
|
|
11
|
+
#
|
|
12
|
+
# HOW IT WORKS:
|
|
13
|
+
# Counts tool calls as a proxy for token usage.
|
|
14
|
+
# Average tool call ≈ 2K tokens input + 1K output.
|
|
15
|
+
# Opus: $15/M input, $75/M output
|
|
16
|
+
# Sonnet: $3/M input, $15/M output
|
|
17
|
+
#
|
|
18
|
+
# CONFIGURATION:
|
|
19
|
+
# CC_COST_MODEL=opus (default) or sonnet
|
|
20
|
+
# CC_COST_WARN=1.00 warn at $1 (default)
|
|
21
|
+
# CC_COST_BLOCK=5.00 warn at $5 (default, doesn't block)
|
|
22
|
+
# ================================================================
|
|
23
|
+
|
|
24
|
+
COUNTER_FILE="/tmp/cc-cost-tracker-calls"
|
|
25
|
+
LAST_WARN="/tmp/cc-cost-tracker-warned"
|
|
26
|
+
|
|
27
|
+
COUNT=$(cat "$COUNTER_FILE" 2>/dev/null || echo 0)
|
|
28
|
+
COUNT=$((COUNT + 1))
|
|
29
|
+
echo "$COUNT" > "$COUNTER_FILE"
|
|
30
|
+
|
|
31
|
+
MODEL="${CC_COST_MODEL:-opus}"
|
|
32
|
+
WARN="${CC_COST_WARN:-1.00}"
|
|
33
|
+
BLOCK="${CC_COST_BLOCK:-5.00}"
|
|
34
|
+
|
|
35
|
+
# Estimate: ~2K input + ~1K output tokens per tool call
|
|
36
|
+
if [ "$MODEL" = "opus" ]; then
|
|
37
|
+
# Opus: $15/M in, $75/M out → ~$0.105 per tool call
|
|
38
|
+
COST=$(echo "scale=2; $COUNT * 0.105" | bc 2>/dev/null || echo "0")
|
|
39
|
+
else
|
|
40
|
+
# Sonnet: $3/M in, $15/M out → ~$0.021 per tool call
|
|
41
|
+
COST=$(echo "scale=2; $COUNT * 0.021" | bc 2>/dev/null || echo "0")
|
|
42
|
+
fi
|
|
43
|
+
|
|
44
|
+
# Graduated warnings (with cooldown)
|
|
45
|
+
WARNED=$(cat "$LAST_WARN" 2>/dev/null || echo "0")
|
|
46
|
+
|
|
47
|
+
if [ "$(echo "$COST >= $BLOCK" | bc 2>/dev/null)" = "1" ] && [ "$WARNED" != "block" ]; then
|
|
48
|
+
echo "COST: ~\$${COST} estimated ($COUNT tool calls, $MODEL)" >&2
|
|
49
|
+
echo "Consider finishing current task and compacting." >&2
|
|
50
|
+
echo "block" > "$LAST_WARN"
|
|
51
|
+
elif [ "$(echo "$COST >= $WARN" | bc 2>/dev/null)" = "1" ] && [ "$WARNED" = "0" ]; then
|
|
52
|
+
echo "COST: ~\$${COST} estimated ($COUNT tool calls, $MODEL)" >&2
|
|
53
|
+
echo "warn" > "$LAST_WARN"
|
|
54
|
+
fi
|
|
55
|
+
|
|
56
|
+
exit 0
|
package/index.mjs
CHANGED
|
@@ -84,6 +84,7 @@ const DIFF_IDX = process.argv.findIndex(a => a === '--diff');
|
|
|
84
84
|
const DIFF_FILE = DIFF_IDX !== -1 ? process.argv[DIFF_IDX + 1] : null;
|
|
85
85
|
const SHARE = process.argv.includes('--share');
|
|
86
86
|
const BENCHMARK = process.argv.includes('--benchmark');
|
|
87
|
+
const DASHBOARD = process.argv.includes('--dashboard');
|
|
87
88
|
const CREATE_IDX = process.argv.findIndex(a => a === '--create');
|
|
88
89
|
const CREATE_DESC = CREATE_IDX !== -1 ? process.argv.slice(CREATE_IDX + 1).join(' ') : null;
|
|
89
90
|
|
|
@@ -105,6 +106,7 @@ if (HELP) {
|
|
|
105
106
|
npx cc-safe-setup --audit --json Machine-readable output for CI/CD
|
|
106
107
|
npx cc-safe-setup --scan Detect tech stack, recommend hooks
|
|
107
108
|
npx cc-safe-setup --learn Learn from your block history
|
|
109
|
+
npx cc-safe-setup --dashboard Real-time status dashboard
|
|
108
110
|
npx cc-safe-setup --benchmark Measure hook execution time
|
|
109
111
|
npx cc-safe-setup --share Generate shareable URL for your setup
|
|
110
112
|
npx cc-safe-setup --diff <file> Compare your settings with another file
|
|
@@ -356,6 +358,8 @@ function examples() {
|
|
|
356
358
|
'commit-quality-gate.sh': 'Warn on vague or too-long commit messages',
|
|
357
359
|
'diff-size-guard.sh': 'Warn/block on large diffs (10+ files warn, 50+ block)',
|
|
358
360
|
'dependency-audit.sh': 'Warn on new package installs not in manifest',
|
|
361
|
+
'cost-tracker.sh': 'Estimate session token cost ($1 warn, $5 alert)',
|
|
362
|
+
'read-before-edit.sh': 'Warn when editing files not recently read',
|
|
359
363
|
},
|
|
360
364
|
};
|
|
361
365
|
|
|
@@ -786,6 +790,74 @@ async function fullSetup() {
|
|
|
786
790
|
console.log();
|
|
787
791
|
}
|
|
788
792
|
|
|
793
|
+
async function dashboard() {
|
|
794
|
+
const { createReadStream, watchFile } = await import('fs');
|
|
795
|
+
const { createInterface: createRL } = await import('readline');
|
|
796
|
+
|
|
797
|
+
const BLOCK_LOG = join(HOME, '.claude', 'blocked-commands.log');
|
|
798
|
+
const COST_FILE = '/tmp/cc-cost-tracker-calls';
|
|
799
|
+
const CONTEXT_FILE = '/tmp/cc-context-pct';
|
|
800
|
+
|
|
801
|
+
const clear = () => process.stdout.write('\x1b[2J\x1b[H');
|
|
802
|
+
|
|
803
|
+
// Count hooks
|
|
804
|
+
let hookCount = 0;
|
|
805
|
+
let exampleCount = 0;
|
|
806
|
+
if (existsSync(SETTINGS_PATH)) {
|
|
807
|
+
try {
|
|
808
|
+
const s = JSON.parse(readFileSync(SETTINGS_PATH, 'utf-8'));
|
|
809
|
+
for (const entries of Object.values(s.hooks || {})) {
|
|
810
|
+
hookCount += entries.reduce((n, e) => n + (e.hooks || []).length, 0);
|
|
811
|
+
}
|
|
812
|
+
} catch {}
|
|
813
|
+
}
|
|
814
|
+
exampleCount = existsSync(join(HOOKS_DIR)) ?
|
|
815
|
+
(await import('fs')).readdirSync(HOOKS_DIR).filter(f => f.endsWith('.sh')).length : 0;
|
|
816
|
+
|
|
817
|
+
function render() {
|
|
818
|
+
clear();
|
|
819
|
+
|
|
820
|
+
// Header
|
|
821
|
+
console.log(c.bold + ' cc-safe-setup --dashboard' + c.reset + ' ' + c.dim + new Date().toLocaleTimeString() + c.reset);
|
|
822
|
+
console.log(' ' + '─'.repeat(50));
|
|
823
|
+
|
|
824
|
+
// Status row
|
|
825
|
+
const context = existsSync(CONTEXT_FILE) ? readFileSync(CONTEXT_FILE, 'utf-8').trim() + '%' : '?';
|
|
826
|
+
const calls = existsSync(COST_FILE) ? readFileSync(COST_FILE, 'utf-8').trim() : '0';
|
|
827
|
+
const cost = (parseInt(calls) * 0.105).toFixed(2);
|
|
828
|
+
|
|
829
|
+
console.log(' Hooks: ' + c.green + hookCount + c.reset + ' registered | Scripts: ' + exampleCount);
|
|
830
|
+
console.log(' Context: ' + c.yellow + context + c.reset + ' | Cost: ~$' + cost + ' (' + calls + ' calls)');
|
|
831
|
+
console.log(' ' + '─'.repeat(50));
|
|
832
|
+
|
|
833
|
+
// Recent blocks
|
|
834
|
+
console.log(c.bold + ' Recent Blocks' + c.reset);
|
|
835
|
+
if (existsSync(BLOCK_LOG)) {
|
|
836
|
+
const lines = readFileSync(BLOCK_LOG, 'utf-8').split('\n').filter(l => l.trim());
|
|
837
|
+
const recent = lines.slice(-5);
|
|
838
|
+
for (const line of recent) {
|
|
839
|
+
const m = line.match(/^\[([^\]]+)\]\s*BLOCKED:\s*(.+?)\s*\|/);
|
|
840
|
+
if (m) {
|
|
841
|
+
const time = m[1].replace(/T/, ' ').replace(/\+.*/, '').slice(11, 16);
|
|
842
|
+
console.log(' ' + c.dim + time + c.reset + ' ' + c.red + m[2].trim() + c.reset);
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
if (recent.length === 0) console.log(c.dim + ' (none)' + c.reset);
|
|
846
|
+
} else {
|
|
847
|
+
console.log(c.dim + ' (no log yet)' + c.reset);
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
console.log(' ' + '─'.repeat(50));
|
|
851
|
+
console.log(c.dim + ' Refreshing every 3s. Ctrl+C to exit.' + c.reset);
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
render();
|
|
855
|
+
setInterval(render, 3000);
|
|
856
|
+
|
|
857
|
+
// Keep alive
|
|
858
|
+
await new Promise(() => {});
|
|
859
|
+
}
|
|
860
|
+
|
|
789
861
|
async function benchmark() {
|
|
790
862
|
const { spawnSync } = await import('child_process');
|
|
791
863
|
|
|
@@ -2073,6 +2145,7 @@ async function main() {
|
|
|
2073
2145
|
if (FULL) return fullSetup();
|
|
2074
2146
|
if (DOCTOR) return doctor();
|
|
2075
2147
|
if (WATCH) return watch();
|
|
2148
|
+
if (DASHBOARD) return dashboard();
|
|
2076
2149
|
if (BENCHMARK) return benchmark();
|
|
2077
2150
|
if (SHARE) return share();
|
|
2078
2151
|
if (DIFF_FILE) return diff(DIFF_FILE);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cc-safe-setup",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.0",
|
|
4
4
|
"description": "One command to make Claude Code safe for autonomous operation. 8 built-in + 36 examples. 21 commands: create, audit, lint, diff, share, benchmark, watch, learn. 2,500+ daily npm downloads.",
|
|
5
5
|
"main": "index.mjs",
|
|
6
6
|
"bin": {
|