cc-safe-setup 10.4.0 → 10.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 + 104 examples = **118 hooks**. 37 CLI commands. 531 tests. 5 languages. [**Hub**](https://yurukusa.github.io/cc-safe-setup/hub.html) · [Cheat Sheet](https://yurukusa.github.io/cc-safe-setup/hooks-cheatsheet.html) · [Builder](https://yurukusa.github.io/cc-safe-setup/builder.html) · [FAQ](https://yurukusa.github.io/cc-safe-setup/faq.html) · [Examples](https://yurukusa.github.io/cc-safe-setup/by-example.html) · [Matrix](https://yurukusa.github.io/cc-safe-setup/matrix.html) · [Playground](https://yurukusa.github.io/cc-hook-registry/playground.html)
9
+ 8 built-in + 104 examples = **118 hooks**. 38 CLI commands. 531 tests. 5 languages. [**Hub**](https://yurukusa.github.io/cc-safe-setup/hub.html) · [Cheat Sheet](https://yurukusa.github.io/cc-safe-setup/hooks-cheatsheet.html) · [Builder](https://yurukusa.github.io/cc-safe-setup/builder.html) · [FAQ](https://yurukusa.github.io/cc-safe-setup/faq.html) · [Examples](https://yurukusa.github.io/cc-safe-setup/by-example.html) · [Matrix](https://yurukusa.github.io/cc-safe-setup/matrix.html) · [Playground](https://yurukusa.github.io/cc-hook-registry/playground.html)
10
10
 
11
11
  ```bash
12
12
  npx cc-safe-setup
@@ -0,0 +1,14 @@
1
+ #!/bin/bash
2
+ # typescript-strict-guard.sh — Warn when tsconfig.json strict mode is disabled
3
+ # TRIGGER: PostToolUse MATCHER: "Edit"
4
+ INPUT=$(cat)
5
+ FILE=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty' 2>/dev/null)
6
+ [ -z "$FILE" ] && exit 0
7
+ case "$FILE" in */tsconfig.json|tsconfig.json) ;; *) exit 0 ;; esac
8
+ NEW=$(echo "$INPUT" | jq -r '.tool_input.new_string // empty' 2>/dev/null)
9
+ [ -z "$NEW" ] && exit 0
10
+ if echo "$NEW" | grep -q '"strict"' && echo "$NEW" | grep -q 'false'; then
11
+ echo "WARNING: TypeScript strict mode being disabled in tsconfig.json." >&2
12
+ echo "Strict mode catches bugs at compile time. Think twice." >&2
13
+ fi
14
+ exit 0
@@ -0,0 +1,73 @@
1
+ #!/bin/bash
2
+ # ================================================================
3
+ # typosquat-guard.sh — Detect potential typosquatting in npm install
4
+ # ================================================================
5
+ # PURPOSE:
6
+ # Claude sometimes installs packages with slightly misspelled
7
+ # names (e.g., "loadsh" instead of "lodash"). This hook checks
8
+ # common typosquatting patterns in npm/pip install commands.
9
+ #
10
+ # TRIGGER: PreToolUse MATCHER: "Bash"
11
+ # ================================================================
12
+
13
+ COMMAND=$(cat | jq -r '.tool_input.command // empty' 2>/dev/null)
14
+ [ -z "$COMMAND" ] && exit 0
15
+
16
+ # Extract package name from install command
17
+ PKG=""
18
+ if echo "$COMMAND" | grep -qE '^\s*npm\s+install\s+'; then
19
+ PKG=$(echo "$COMMAND" | grep -oE 'npm\s+install\s+(\S+)' | awk '{print $3}' | head -1)
20
+ elif echo "$COMMAND" | grep -qE '^\s*pip\s+install\s+'; then
21
+ PKG=$(echo "$COMMAND" | grep -oE 'pip\s+install\s+(\S+)' | awk '{print $3}' | head -1)
22
+ fi
23
+ [ -z "$PKG" ] && exit 0
24
+
25
+ # Strip scope and version
26
+ PKG=$(echo "$PKG" | sed 's/@[^/]*\///' | sed 's/@.*//')
27
+
28
+ # Known popular packages and their common typos
29
+ declare -A POPULAR=(
30
+ ["lodash"]="loadsh lodassh lodas"
31
+ ["express"]="expresss expres exppress"
32
+ ["react"]="recat raect reatc"
33
+ ["axios"]="axois axio axioss"
34
+ ["moment"]="momnet moemnt momet"
35
+ ["webpack"]="webpak webpackk wepback"
36
+ ["typescript"]="typscript typescrip tyepscript"
37
+ ["eslint"]="esling eslnt elsint"
38
+ ["prettier"]="pretier pretter prettir"
39
+ ["mongoose"]="mongose mongooe mongooes"
40
+ )
41
+
42
+ for legit in "${!POPULAR[@]}"; do
43
+ for typo in ${POPULAR[$legit]}; do
44
+ if [ "$PKG" = "$typo" ]; then
45
+ echo "WARNING: '$PKG' looks like a typo of '$legit'." >&2
46
+ echo "Did you mean: npm install $legit" >&2
47
+ echo "Typosquatting packages can contain malware." >&2
48
+ exit 0
49
+ fi
50
+ done
51
+ done
52
+
53
+ # Check for suspicious single-char differences from popular packages
54
+ LEN=${#PKG}
55
+ if [ "$LEN" -gt 3 ] && [ "$LEN" -lt 20 ]; then
56
+ for legit in "${!POPULAR[@]}"; do
57
+ LEGIT_LEN=${#legit}
58
+ DIFF=$((LEN - LEGIT_LEN))
59
+ [ "$DIFF" -lt -1 ] || [ "$DIFF" -gt 1 ] && continue
60
+ # Simple Levenshtein approximation: count differing chars
61
+ MATCH=0
62
+ for ((i=0; i<LEN && i<LEGIT_LEN; i++)); do
63
+ [ "${PKG:$i:1}" = "${legit:$i:1}" ] && MATCH=$((MATCH+1))
64
+ done
65
+ SIMILARITY=$((MATCH * 100 / (LEGIT_LEN > LEN ? LEGIT_LEN : LEN)))
66
+ if [ "$SIMILARITY" -ge 80 ] && [ "$PKG" != "$legit" ]; then
67
+ echo "NOTE: '$PKG' is similar to '$legit' (${SIMILARITY}% match)." >&2
68
+ echo "Verify this is the correct package name." >&2
69
+ fi
70
+ done
71
+ fi
72
+
73
+ exit 0
package/index.mjs CHANGED
@@ -105,6 +105,7 @@ const PROFILE_IDX = process.argv.findIndex(a => a === '--profile');
105
105
  const PROFILE = PROFILE_IDX !== -1 ? process.argv[PROFILE_IDX + 1] : null;
106
106
  const COMPARE_IDX = process.argv.findIndex(a => a === '--compare');
107
107
  const COMPARE = COMPARE_IDX !== -1 ? { a: process.argv[COMPARE_IDX + 1], b: process.argv[COMPARE_IDX + 2] } : null;
108
+ const REPLAY = process.argv.includes('--replay');
108
109
  const CREATE_IDX = process.argv.findIndex(a => a === '--create');
109
110
  const CREATE_DESC = CREATE_IDX !== -1 ? process.argv.slice(CREATE_IDX + 1).join(' ') : null;
110
111
 
@@ -138,6 +139,7 @@ if (HELP) {
138
139
  npx cc-safe-setup --doctor Diagnose why hooks aren't working
139
140
  npx cc-safe-setup --watch Live dashboard of blocked commands
140
141
  npx cc-safe-setup --create "<desc>" Generate a custom hook from description
142
+ npx cc-safe-setup --replay Replay blocked commands timeline (demo/review)
141
143
  npx cc-safe-setup --guard "<rule>" Instantly enforce a rule (generate + install + activate)
142
144
  npx cc-safe-setup --diff-hooks <path> Compare hooks between two settings files
143
145
  npx cc-safe-setup --from-claudemd Convert CLAUDE.md rules into hooks
@@ -853,6 +855,83 @@ async function fullSetup() {
853
855
  console.log();
854
856
  }
855
857
 
858
+ async function replay() {
859
+ console.log();
860
+ console.log(c.bold + ' cc-safe-setup --replay' + c.reset);
861
+ console.log(c.dim + ' Replay blocked commands timeline' + c.reset);
862
+ console.log();
863
+
864
+ const LOG_PATH = join(HOME, '.claude', 'blocked-commands.log');
865
+ if (!existsSync(LOG_PATH)) {
866
+ console.log(c.dim + ' No blocked commands log found.' + c.reset);
867
+ console.log(c.dim + ' Hooks will create it when they block something.' + c.reset);
868
+ return;
869
+ }
870
+
871
+ const content = readFileSync(LOG_PATH, 'utf-8');
872
+ const lines = content.split('\n').filter(l => l.trim());
873
+
874
+ if (lines.length === 0) {
875
+ console.log(c.dim + ' Log is empty — no commands blocked yet.' + c.reset);
876
+ return;
877
+ }
878
+
879
+ // Parse entries
880
+ const entries = [];
881
+ for (const line of lines) {
882
+ const match = line.match(/^\[([^\]]+)\]\s*BLOCKED:\s*(.+?)\s*\|\s*cmd:\s*(.+)$/);
883
+ if (match) {
884
+ entries.push({ time: match[1], reason: match[2].trim(), cmd: match[3].trim() });
885
+ }
886
+ }
887
+
888
+ // Group by day
889
+ const days = {};
890
+ for (const e of entries) {
891
+ const day = e.time.split('T')[0] || 'unknown';
892
+ if (!days[day]) days[day] = [];
893
+ days[day].push(e);
894
+ }
895
+
896
+ // Show last 7 days
897
+ const sortedDays = Object.keys(days).sort().slice(-7);
898
+
899
+ for (const day of sortedDays) {
900
+ const dayEntries = days[day];
901
+ console.log(c.bold + ` ${day}` + c.reset + c.dim + ` (${dayEntries.length} blocks)` + c.reset);
902
+
903
+ // Category counts
904
+ const cats = {};
905
+ for (const e of dayEntries) {
906
+ const cat = e.reason.split(' ')[0] || 'other';
907
+ cats[cat] = (cats[cat] || 0) + 1;
908
+ }
909
+
910
+ // Top categories as mini bar chart
911
+ const sorted = Object.entries(cats).sort((a, b) => b[1] - a[1]).slice(0, 5);
912
+ for (const [cat, count] of sorted) {
913
+ const bar = '█'.repeat(Math.min(count, 30));
914
+ const color = cat.match(/rm|reset|clean|Remove/i) ? c.red : cat.match(/push|force/i) ? c.red : c.yellow;
915
+ console.log(` ${color}${bar}${c.reset} ${count}× ${cat}`);
916
+ }
917
+
918
+ // Show last 3 entries of the day
919
+ const recent = dayEntries.slice(-3);
920
+ for (const e of recent) {
921
+ const time = (e.time.split('T')[1] || '').replace(/\+.*/, '').substring(0, 8);
922
+ console.log(c.dim + ` ${time} ${e.reason.substring(0, 40)} → ${e.cmd.substring(0, 50)}` + c.reset);
923
+ }
924
+ console.log();
925
+ }
926
+
927
+ // Summary
928
+ console.log(c.bold + ' Summary' + c.reset);
929
+ console.log(` Total blocks: ${entries.length}`);
930
+ console.log(` Days with blocks: ${Object.keys(days).length}`);
931
+ console.log(` Avg per day: ${Math.round(entries.length / Math.max(Object.keys(days).length, 1))}`);
932
+ console.log();
933
+ }
934
+
856
935
  async function guard(description) {
857
936
  if (!description) {
858
937
  console.log();
@@ -3905,6 +3984,7 @@ async function main() {
3905
3984
  if (FULL) return fullSetup();
3906
3985
  if (DOCTOR) return doctor();
3907
3986
  if (WATCH) return watch();
3987
+ if (REPLAY) return replay();
3908
3988
  if (GUARD_IDX !== -1) return guard(GUARD_DESC);
3909
3989
  if (DIFF_HOOKS_IDX !== -1) return diffHooks(DIFF_HOOKS);
3910
3990
  if (FROM_CLAUDEMD) return fromClaudeMd();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cc-safe-setup",
3
- "version": "10.4.0",
3
+ "version": "10.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": {