cc-safe-setup 8.3.0 → 8.5.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 +3 -1
- package/examples/go/destructive_guard.go +69 -0
- package/examples/typescript/destructive-guard.ts +64 -0
- package/index.mjs +251 -0
- package/package.json +1 -1
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 + 92 examples = **100 hooks**.
|
|
9
|
+
8 built-in + 92 examples = **100 hooks**. 32 CLI commands. 457 tests. [Web Tool](https://yurukusa.github.io/cc-safe-setup/) · [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) · [Playground](https://yurukusa.github.io/cc-hook-registry/playground.html)
|
|
10
10
|
|
|
11
11
|
```bash
|
|
12
12
|
npx cc-safe-setup
|
|
@@ -105,6 +105,8 @@ Safe to run multiple times. Existing settings are preserved. A backup is created
|
|
|
105
105
|
|
|
106
106
|
**Maximum safety:** `npx cc-safe-setup --shield` — one command: fix environment, install hooks, detect stack, configure settings, generate CLAUDE.md.
|
|
107
107
|
|
|
108
|
+
**Team setup:** `npx cc-safe-setup --team` — copy hooks to `.claude/hooks/` with relative paths, commit to repo for team sharing.
|
|
109
|
+
|
|
108
110
|
**Preview first:** `npx cc-safe-setup --dry-run`
|
|
109
111
|
|
|
110
112
|
**Check status:** `npx cc-safe-setup --status` — see which hooks are installed (exit code 1 if missing).
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// destructive_guard.go — Claude Code PreToolUse hook in Go
|
|
2
|
+
//
|
|
3
|
+
// Blocks rm -rf /, git reset --hard, git clean -fd, and similar
|
|
4
|
+
// destructive commands. Exit code 2 = block, 0 = allow.
|
|
5
|
+
//
|
|
6
|
+
// Build: go build -o destructive-guard destructive_guard.go
|
|
7
|
+
// Usage in settings.json:
|
|
8
|
+
// {"type": "command", "command": "/path/to/destructive-guard"}
|
|
9
|
+
package main
|
|
10
|
+
|
|
11
|
+
import (
|
|
12
|
+
"encoding/json"
|
|
13
|
+
"fmt"
|
|
14
|
+
"io"
|
|
15
|
+
"os"
|
|
16
|
+
"regexp"
|
|
17
|
+
"strings"
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
type HookInput struct {
|
|
21
|
+
ToolInput struct {
|
|
22
|
+
Command string `json:"command"`
|
|
23
|
+
} `json:"tool_input"`
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
var dangerousPatterns = []*regexp.Regexp{
|
|
27
|
+
regexp.MustCompile(`\brm\s+.*-rf\s+(/|~/?\s*$|\.\./)`),
|
|
28
|
+
regexp.MustCompile(`\bgit\s+reset\s+--hard`),
|
|
29
|
+
regexp.MustCompile(`\bgit\s+clean\s+-[a-zA-Z]*f`),
|
|
30
|
+
regexp.MustCompile(`\bgit\s+checkout\s+--force`),
|
|
31
|
+
regexp.MustCompile(`\bchmod\s+(-R\s+)?777\s+/`),
|
|
32
|
+
regexp.MustCompile(`\bfind\s+/\s+-delete`),
|
|
33
|
+
regexp.MustCompile(`Remove-Item.*-Recurse.*-Force`),
|
|
34
|
+
regexp.MustCompile(`--no-preserve-root`),
|
|
35
|
+
regexp.MustCompile(`\bsudo\s+mkfs\b`),
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
func main() {
|
|
39
|
+
data, err := io.ReadAll(os.Stdin)
|
|
40
|
+
if err != nil {
|
|
41
|
+
os.Exit(0) // Don't block on read error
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
var input HookInput
|
|
45
|
+
if err := json.Unmarshal(data, &input); err != nil {
|
|
46
|
+
os.Exit(0) // Don't block on parse error
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
cmd := input.ToolInput.Command
|
|
50
|
+
if cmd == "" {
|
|
51
|
+
os.Exit(0)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Skip if command is in an echo/printf context
|
|
55
|
+
lower := strings.ToLower(cmd)
|
|
56
|
+
if strings.HasPrefix(strings.TrimSpace(lower), "echo ") ||
|
|
57
|
+
strings.HasPrefix(strings.TrimSpace(lower), "printf ") {
|
|
58
|
+
os.Exit(0)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
for _, pattern := range dangerousPatterns {
|
|
62
|
+
if pattern.MatchString(cmd) {
|
|
63
|
+
fmt.Fprintf(os.Stderr, "BLOCKED: Dangerous command detected\nCommand: %s\n", cmd)
|
|
64
|
+
os.Exit(2)
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
os.Exit(0)
|
|
69
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
#!/usr/bin/env -S npx tsx
|
|
2
|
+
/**
|
|
3
|
+
* destructive-guard.ts — Claude Code PreToolUse hook in TypeScript
|
|
4
|
+
*
|
|
5
|
+
* Blocks rm -rf /, git reset --hard, git clean -fd, and similar
|
|
6
|
+
* destructive commands. Exit code 2 = block, 0 = allow.
|
|
7
|
+
*
|
|
8
|
+
* Run with: npx tsx destructive-guard.ts
|
|
9
|
+
* Or compile: npx tsc destructive-guard.ts && node destructive-guard.js
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
interface HookInput {
|
|
13
|
+
tool_input: {
|
|
14
|
+
command?: string;
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const DANGEROUS_PATTERNS: RegExp[] = [
|
|
19
|
+
/\brm\s+.*-rf\s+(\/|~\/?\s*$|\.\.\/)/,
|
|
20
|
+
/\bgit\s+reset\s+--hard/,
|
|
21
|
+
/\bgit\s+clean\s+-[a-zA-Z]*f/,
|
|
22
|
+
/\bgit\s+checkout\s+--force/,
|
|
23
|
+
/\bchmod\s+(-R\s+)?777\s+\//,
|
|
24
|
+
/\bfind\s+\/\s+-delete/,
|
|
25
|
+
/Remove-Item.*-Recurse.*-Force/,
|
|
26
|
+
/--no-preserve-root/,
|
|
27
|
+
/\bsudo\s+mkfs\b/,
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
async function main(): Promise<void> {
|
|
31
|
+
let data = '';
|
|
32
|
+
for await (const chunk of process.stdin) {
|
|
33
|
+
data += chunk;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
let input: HookInput;
|
|
37
|
+
try {
|
|
38
|
+
input = JSON.parse(data);
|
|
39
|
+
} catch {
|
|
40
|
+
process.exit(0); // Don't block on parse error
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const cmd = input.tool_input?.command;
|
|
44
|
+
if (!cmd) {
|
|
45
|
+
process.exit(0);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Skip echo/printf context
|
|
49
|
+
const trimmed = cmd.trimStart().toLowerCase();
|
|
50
|
+
if (trimmed.startsWith('echo ') || trimmed.startsWith('printf ')) {
|
|
51
|
+
process.exit(0);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
for (const pattern of DANGEROUS_PATTERNS) {
|
|
55
|
+
if (pattern.test(cmd)) {
|
|
56
|
+
process.stderr.write(`BLOCKED: Dangerous command detected\nCommand: ${cmd}\n`);
|
|
57
|
+
process.exit(2);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
process.exit(0);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
main();
|
package/index.mjs
CHANGED
|
@@ -92,6 +92,9 @@ const REPORT = process.argv.includes('--report');
|
|
|
92
92
|
const QUICKFIX = process.argv.includes('--quickfix');
|
|
93
93
|
const SHIELD = process.argv.includes('--shield');
|
|
94
94
|
const ANALYZE = process.argv.includes('--analyze');
|
|
95
|
+
const TEAM = process.argv.includes('--team');
|
|
96
|
+
const MIGRATE_FROM_IDX = process.argv.findIndex(a => a === '--migrate-from');
|
|
97
|
+
const MIGRATE_FROM = MIGRATE_FROM_IDX !== -1 ? process.argv[MIGRATE_FROM_IDX + 1] : null;
|
|
95
98
|
const PROFILE_IDX = process.argv.findIndex(a => a === '--profile');
|
|
96
99
|
const PROFILE = PROFILE_IDX !== -1 ? process.argv[PROFILE_IDX + 1] : null;
|
|
97
100
|
const COMPARE_IDX = process.argv.findIndex(a => a === '--compare');
|
|
@@ -129,6 +132,8 @@ if (HELP) {
|
|
|
129
132
|
npx cc-safe-setup --doctor Diagnose why hooks aren't working
|
|
130
133
|
npx cc-safe-setup --watch Live dashboard of blocked commands
|
|
131
134
|
npx cc-safe-setup --create "<desc>" Generate a custom hook from description
|
|
135
|
+
npx cc-safe-setup --migrate-from <tool> Migrate from safety-net/hooks-mastery/etc.
|
|
136
|
+
npx cc-safe-setup --team Set up project-level hooks (commit to repo for team)
|
|
132
137
|
npx cc-safe-setup --profile <level> Switch safety profile (strict/standard/minimal)
|
|
133
138
|
npx cc-safe-setup --analyze Analyze what Claude did in your last session
|
|
134
139
|
npx cc-safe-setup --shield Maximum safety in one command (fix + scan + install + CLAUDE.md)
|
|
@@ -838,6 +843,250 @@ async function fullSetup() {
|
|
|
838
843
|
console.log();
|
|
839
844
|
}
|
|
840
845
|
|
|
846
|
+
async function migrateFrom(tool) {
|
|
847
|
+
console.log();
|
|
848
|
+
console.log(c.bold + ' cc-safe-setup --migrate-from ' + (tool || '?') + c.reset);
|
|
849
|
+
console.log();
|
|
850
|
+
|
|
851
|
+
const KNOWN_TOOLS = {
|
|
852
|
+
'safety-net': {
|
|
853
|
+
name: 'Claude Code Safety Net',
|
|
854
|
+
npm: '@anthropic-ai/claude-code-safety-net',
|
|
855
|
+
detect: () => {
|
|
856
|
+
if (!existsSync(SETTINGS_PATH)) return false;
|
|
857
|
+
const s = readFileSync(SETTINGS_PATH, 'utf-8');
|
|
858
|
+
return s.includes('safety-net') || s.includes('claude-code-safety-net');
|
|
859
|
+
},
|
|
860
|
+
mapping: {
|
|
861
|
+
'destructive-commands': 'destructive-guard',
|
|
862
|
+
'secret-files': 'secret-guard',
|
|
863
|
+
'branch-protection': 'branch-guard',
|
|
864
|
+
'git-operations': 'branch-guard',
|
|
865
|
+
},
|
|
866
|
+
desc: 'TypeScript hooks with configurable severity levels'
|
|
867
|
+
},
|
|
868
|
+
'hooks-mastery': {
|
|
869
|
+
name: 'Claude Code Hooks Mastery',
|
|
870
|
+
npm: null,
|
|
871
|
+
detect: () => {
|
|
872
|
+
if (!existsSync(SETTINGS_PATH)) return false;
|
|
873
|
+
const s = readFileSync(SETTINGS_PATH, 'utf-8');
|
|
874
|
+
return s.includes('hooks_mastery') || s.includes('hooks-mastery');
|
|
875
|
+
},
|
|
876
|
+
mapping: {
|
|
877
|
+
'safety': 'destructive-guard',
|
|
878
|
+
'git-safety': 'branch-guard',
|
|
879
|
+
},
|
|
880
|
+
desc: 'Python hooks for all events + LLM integration'
|
|
881
|
+
},
|
|
882
|
+
'manual': {
|
|
883
|
+
name: 'Custom/Manual Hooks',
|
|
884
|
+
npm: null,
|
|
885
|
+
detect: () => {
|
|
886
|
+
if (!existsSync(SETTINGS_PATH)) return false;
|
|
887
|
+
const s = JSON.parse(readFileSync(SETTINGS_PATH, 'utf-8'));
|
|
888
|
+
return Object.keys(s.hooks || {}).length > 0;
|
|
889
|
+
},
|
|
890
|
+
mapping: {},
|
|
891
|
+
desc: 'Hand-written hooks in settings.json'
|
|
892
|
+
}
|
|
893
|
+
};
|
|
894
|
+
|
|
895
|
+
if (!tool) {
|
|
896
|
+
console.log(c.bold + ' Supported migration sources:' + c.reset);
|
|
897
|
+
console.log();
|
|
898
|
+
for (const [id, info] of Object.entries(KNOWN_TOOLS)) {
|
|
899
|
+
const detected = info.detect();
|
|
900
|
+
const icon = detected ? c.green + '●' + c.reset : c.dim + '○' + c.reset;
|
|
901
|
+
console.log(` ${icon} ${c.bold}${id}${c.reset} — ${info.desc}`);
|
|
902
|
+
if (detected) console.log(` ${c.green}Detected in your settings${c.reset}`);
|
|
903
|
+
console.log(` ${c.dim}npx cc-safe-setup --migrate-from ${id}${c.reset}`);
|
|
904
|
+
console.log();
|
|
905
|
+
}
|
|
906
|
+
return;
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
const source = KNOWN_TOOLS[tool];
|
|
910
|
+
if (!source) {
|
|
911
|
+
console.log(c.red + ` Unknown tool: ${tool}` + c.reset);
|
|
912
|
+
console.log(c.dim + ' Supported: ' + Object.keys(KNOWN_TOOLS).join(', ') + c.reset);
|
|
913
|
+
return;
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
console.log(c.dim + ` Migrating from: ${source.name}` + c.reset);
|
|
917
|
+
console.log();
|
|
918
|
+
|
|
919
|
+
// Read current settings
|
|
920
|
+
let settings = {};
|
|
921
|
+
if (existsSync(SETTINGS_PATH)) {
|
|
922
|
+
try { settings = JSON.parse(readFileSync(SETTINGS_PATH, 'utf-8')); } catch {}
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
// Analyze existing hooks
|
|
926
|
+
let existingHooks = [];
|
|
927
|
+
for (const [trigger, groups] of Object.entries(settings.hooks || {})) {
|
|
928
|
+
for (const group of groups) {
|
|
929
|
+
for (const hook of (group.hooks || [])) {
|
|
930
|
+
existingHooks.push({ trigger, matcher: group.matcher, command: hook.command || '' });
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
console.log(` Found ${existingHooks.length} existing hook(s) in settings.json`);
|
|
936
|
+
console.log();
|
|
937
|
+
|
|
938
|
+
// Identify what cc-safe-setup equivalents exist
|
|
939
|
+
const replacements = [];
|
|
940
|
+
for (const h of existingHooks) {
|
|
941
|
+
const cmd = h.command.toLowerCase();
|
|
942
|
+
let replacement = null;
|
|
943
|
+
|
|
944
|
+
// Try source-specific mapping
|
|
945
|
+
for (const [pattern, ccHook] of Object.entries(source.mapping)) {
|
|
946
|
+
if (cmd.includes(pattern)) {
|
|
947
|
+
replacement = ccHook;
|
|
948
|
+
break;
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
// Generic detection
|
|
953
|
+
if (!replacement) {
|
|
954
|
+
if (cmd.includes('rm') || cmd.includes('destruct')) replacement = 'destructive-guard';
|
|
955
|
+
else if (cmd.includes('branch') || cmd.includes('push')) replacement = 'branch-guard';
|
|
956
|
+
else if (cmd.includes('secret') || cmd.includes('env')) replacement = 'secret-guard';
|
|
957
|
+
else if (cmd.includes('syntax') || cmd.includes('lint')) replacement = 'syntax-check';
|
|
958
|
+
else if (cmd.includes('context') || cmd.includes('monitor')) replacement = 'context-monitor';
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
if (replacement) {
|
|
962
|
+
replacements.push({ old: h.command, new: replacement });
|
|
963
|
+
console.log(` ${c.yellow}→${c.reset} ${h.command.substring(0, 50)}`);
|
|
964
|
+
console.log(` ${c.green}→${c.reset} cc-safe-setup: ${replacement}`);
|
|
965
|
+
} else {
|
|
966
|
+
console.log(` ${c.dim}?${c.reset} ${h.command.substring(0, 50)} (no equivalent, keeping)`);
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
console.log();
|
|
971
|
+
console.log(c.bold + ' Migration plan:' + c.reset);
|
|
972
|
+
console.log(` ${c.green}${replacements.length}${c.reset} hooks can be replaced with cc-safe-setup equivalents`);
|
|
973
|
+
console.log(` ${c.dim}${existingHooks.length - replacements.length}${c.reset} hooks will be kept as-is`);
|
|
974
|
+
console.log();
|
|
975
|
+
console.log(c.dim + ' To apply: npx cc-safe-setup --shield' + c.reset);
|
|
976
|
+
console.log(c.dim + ' This installs cc-safe-setup hooks alongside existing ones.' + c.reset);
|
|
977
|
+
console.log(c.dim + ' Remove old hooks manually after verifying the new ones work.' + c.reset);
|
|
978
|
+
console.log();
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
async function team() {
|
|
982
|
+
console.log();
|
|
983
|
+
console.log(c.bold + ' cc-safe-setup --team' + c.reset);
|
|
984
|
+
console.log(c.dim + ' Set up project-level hooks (commit to repo for team sharing)' + c.reset);
|
|
985
|
+
console.log();
|
|
986
|
+
|
|
987
|
+
const cwd = process.cwd();
|
|
988
|
+
const projectHooksDir = join(cwd, '.claude', 'hooks');
|
|
989
|
+
const projectSettings = join(cwd, '.claude', 'settings.local.json');
|
|
990
|
+
|
|
991
|
+
// Create .claude/hooks/ in project
|
|
992
|
+
mkdirSync(projectHooksDir, { recursive: true });
|
|
993
|
+
|
|
994
|
+
// Copy core safety hooks to project
|
|
995
|
+
const coreHooks = ['destructive-guard', 'branch-guard', 'secret-guard', 'syntax-check',
|
|
996
|
+
'context-monitor', 'comment-strip', 'cd-git-allow', 'api-error-alert'];
|
|
997
|
+
|
|
998
|
+
let installed = 0;
|
|
999
|
+
for (const hookId of coreHooks) {
|
|
1000
|
+
const destPath = join(projectHooksDir, `${hookId}.sh`);
|
|
1001
|
+
if (existsSync(destPath)) {
|
|
1002
|
+
console.log(c.dim + ' ✓' + c.reset + ` ${hookId}`);
|
|
1003
|
+
continue;
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
if (SCRIPTS[hookId]) {
|
|
1007
|
+
writeFileSync(destPath, SCRIPTS[hookId]);
|
|
1008
|
+
chmodSync(destPath, 0o755);
|
|
1009
|
+
installed++;
|
|
1010
|
+
console.log(c.green + ' +' + c.reset + ` ${hookId}`);
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
// Detect stack and add relevant hooks
|
|
1015
|
+
const extras = [];
|
|
1016
|
+
if (existsSync(join(cwd, 'package.json'))) extras.push('auto-approve-build');
|
|
1017
|
+
if (existsSync(join(cwd, 'requirements.txt')) || existsSync(join(cwd, 'pyproject.toml'))) extras.push('auto-approve-python');
|
|
1018
|
+
if (existsSync(join(cwd, 'go.mod'))) extras.push('auto-approve-go');
|
|
1019
|
+
if (existsSync(join(cwd, 'Cargo.toml'))) extras.push('auto-approve-cargo');
|
|
1020
|
+
if (existsSync(join(cwd, 'Dockerfile'))) extras.push('auto-approve-docker');
|
|
1021
|
+
|
|
1022
|
+
for (const ex of extras) {
|
|
1023
|
+
const destPath = join(projectHooksDir, `${ex}.sh`);
|
|
1024
|
+
const srcPath = join(__dirname, 'examples', `${ex}.sh`);
|
|
1025
|
+
if (existsSync(destPath)) continue;
|
|
1026
|
+
if (existsSync(srcPath)) {
|
|
1027
|
+
copyFileSync(srcPath, destPath);
|
|
1028
|
+
chmodSync(destPath, 0o755);
|
|
1029
|
+
installed++;
|
|
1030
|
+
console.log(c.green + ' +' + c.reset + ` ${ex} (project-specific)`);
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
// Generate settings.local.json
|
|
1035
|
+
const allHooks = [...coreHooks, ...extras].filter(h => existsSync(join(projectHooksDir, `${h}.sh`)));
|
|
1036
|
+
|
|
1037
|
+
const bashHooks = allHooks.map(h => ({
|
|
1038
|
+
type: 'command',
|
|
1039
|
+
command: `bash .claude/hooks/${h}.sh`
|
|
1040
|
+
}));
|
|
1041
|
+
|
|
1042
|
+
const settings = {
|
|
1043
|
+
hooks: {
|
|
1044
|
+
PreToolUse: [
|
|
1045
|
+
{ matcher: 'Bash', hooks: bashHooks.filter((_, i) => {
|
|
1046
|
+
const name = allHooks[i];
|
|
1047
|
+
return !['syntax-check', 'context-monitor', 'api-error-alert'].includes(name);
|
|
1048
|
+
})},
|
|
1049
|
+
{ matcher: 'Edit|Write', hooks: [
|
|
1050
|
+
{ type: 'command', command: 'bash .claude/hooks/syntax-check.sh' }
|
|
1051
|
+
]}
|
|
1052
|
+
],
|
|
1053
|
+
PostToolUse: [
|
|
1054
|
+
{ matcher: '', hooks: [
|
|
1055
|
+
{ type: 'command', command: 'bash .claude/hooks/context-monitor.sh' }
|
|
1056
|
+
]}
|
|
1057
|
+
],
|
|
1058
|
+
Stop: [
|
|
1059
|
+
{ matcher: '', hooks: [
|
|
1060
|
+
{ type: 'command', command: 'bash .claude/hooks/api-error-alert.sh' }
|
|
1061
|
+
]}
|
|
1062
|
+
]
|
|
1063
|
+
}
|
|
1064
|
+
};
|
|
1065
|
+
|
|
1066
|
+
writeFileSync(projectSettings, JSON.stringify(settings, null, 2));
|
|
1067
|
+
console.log();
|
|
1068
|
+
console.log(c.green + ' ✓' + c.reset + ' Created .claude/settings.local.json');
|
|
1069
|
+
|
|
1070
|
+
// Add .claude/hooks to .gitignore if not there
|
|
1071
|
+
const gitignorePath = join(cwd, '.gitignore');
|
|
1072
|
+
if (existsSync(gitignorePath)) {
|
|
1073
|
+
const gi = readFileSync(gitignorePath, 'utf-8');
|
|
1074
|
+
if (!gi.includes('.claude/')) {
|
|
1075
|
+
// Don't add — hooks should be committed for team sharing
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
console.log();
|
|
1080
|
+
console.log(c.bold + ' Next steps:' + c.reset);
|
|
1081
|
+
console.log(c.dim + ' 1. git add .claude/' + c.reset);
|
|
1082
|
+
console.log(c.dim + ' 2. git commit -m "chore: add Claude Code safety hooks"' + c.reset);
|
|
1083
|
+
console.log(c.dim + ' 3. Team members get hooks automatically on git pull' + c.reset);
|
|
1084
|
+
console.log();
|
|
1085
|
+
console.log(c.dim + ` ${installed} hooks installed, ${allHooks.length} total configured.` + c.reset);
|
|
1086
|
+
console.log(c.dim + ' Hooks use relative paths (.claude/hooks/) — portable across machines.' + c.reset);
|
|
1087
|
+
console.log();
|
|
1088
|
+
}
|
|
1089
|
+
|
|
841
1090
|
async function profile(level) {
|
|
842
1091
|
const { readdirSync } = await import('fs');
|
|
843
1092
|
console.log();
|
|
@@ -3270,6 +3519,8 @@ async function main() {
|
|
|
3270
3519
|
if (FULL) return fullSetup();
|
|
3271
3520
|
if (DOCTOR) return doctor();
|
|
3272
3521
|
if (WATCH) return watch();
|
|
3522
|
+
if (MIGRATE_FROM_IDX !== -1) return migrateFrom(MIGRATE_FROM);
|
|
3523
|
+
if (TEAM) return team();
|
|
3273
3524
|
if (PROFILE_IDX !== -1) return profile(PROFILE);
|
|
3274
3525
|
if (ANALYZE) return analyze();
|
|
3275
3526
|
if (SHIELD) return shield();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cc-safe-setup",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.5.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": {
|