cc-safe-setup 12.0.0 → 12.1.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 -1
- package/index.mjs +79 -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 + 104 examples = **118 hooks**.
|
|
9
|
+
8 built-in + 104 examples = **118 hooks**. 42 CLI commands. 561 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
|
package/index.mjs
CHANGED
|
@@ -111,6 +111,8 @@ const SAVE_PROFILE = SAVE_PROFILE_IDX !== -1 ? process.argv[SAVE_PROFILE_IDX + 1
|
|
|
111
111
|
const CREATE_IDX = process.argv.findIndex(a => a === '--create');
|
|
112
112
|
const CREATE_DESC = CREATE_IDX !== -1 ? process.argv.slice(CREATE_IDX + 1).join(' ') : null;
|
|
113
113
|
const SUGGEST = process.argv.includes('--suggest');
|
|
114
|
+
const TEST_HOOK_IDX = process.argv.findIndex(a => a === '--test-hook');
|
|
115
|
+
const TEST_HOOK = TEST_HOOK_IDX !== -1 ? process.argv[TEST_HOOK_IDX + 1] : null;
|
|
114
116
|
const WHY_IDX = process.argv.findIndex(a => a === '--why');
|
|
115
117
|
const WHY_HOOK = WHY_IDX !== -1 ? process.argv[WHY_IDX + 1] : null;
|
|
116
118
|
|
|
@@ -144,6 +146,7 @@ if (HELP) {
|
|
|
144
146
|
npx cc-safe-setup --doctor Diagnose why hooks aren't working
|
|
145
147
|
npx cc-safe-setup --watch Live dashboard of blocked commands
|
|
146
148
|
npx cc-safe-setup --create "<desc>" Generate a custom hook from description
|
|
149
|
+
npx cc-safe-setup --test-hook <name> Test a specific hook with sample inputs
|
|
147
150
|
npx cc-safe-setup --save-profile <name> Save current hooks as a named profile
|
|
148
151
|
npx cc-safe-setup --suggest Analyze project and predict risks → suggest hooks
|
|
149
152
|
npx cc-safe-setup --why <hook> Why this hook exists (real incident + issue link)
|
|
@@ -931,6 +934,81 @@ async function fullSetup() {
|
|
|
931
934
|
console.log();
|
|
932
935
|
}
|
|
933
936
|
|
|
937
|
+
async function testHook(hookName) {
|
|
938
|
+
const { execSync } = await import('child_process');
|
|
939
|
+
console.log();
|
|
940
|
+
|
|
941
|
+
if (!hookName) {
|
|
942
|
+
console.log(c.bold + ' cc-safe-setup --test-hook <name>' + c.reset);
|
|
943
|
+
console.log(c.dim + ' Test any hook with sample inputs.' + c.reset);
|
|
944
|
+
console.log();
|
|
945
|
+
console.log(' Example: npx cc-safe-setup --test-hook destructive-guard');
|
|
946
|
+
return;
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
const name = hookName.replace('.sh', '');
|
|
950
|
+
// Find the hook
|
|
951
|
+
let hookPath = join(HOOKS_DIR, `${name}.sh`);
|
|
952
|
+
if (!existsSync(hookPath)) hookPath = join(__dirname, 'examples', `${name}.sh`);
|
|
953
|
+
if (!existsSync(hookPath)) {
|
|
954
|
+
console.log(c.red + ` Hook "${name}" not found.` + c.reset);
|
|
955
|
+
return;
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
console.log(c.bold + ` Testing: ${name}` + c.reset);
|
|
959
|
+
console.log(c.dim + ` File: ${hookPath}` + c.reset);
|
|
960
|
+
console.log();
|
|
961
|
+
|
|
962
|
+
// Sample inputs per hook type
|
|
963
|
+
const SAMPLES = {
|
|
964
|
+
'should-block': [
|
|
965
|
+
{ desc: 'dangerous rm', input: '{"tool_input":{"command":"rm -rf /"}}' },
|
|
966
|
+
{ desc: 'git reset hard', input: '{"tool_input":{"command":"git reset --hard"}}' },
|
|
967
|
+
{ desc: 'force push', input: '{"tool_input":{"command":"git push origin main --force"}}' },
|
|
968
|
+
{ desc: 'git add .env', input: '{"tool_input":{"command":"git add .env"}}' },
|
|
969
|
+
{ desc: 'sudo command', input: '{"tool_input":{"command":"sudo rm -rf /home"}}' },
|
|
970
|
+
{ desc: 'drop database', input: '{"tool_input":{"command":"DROP DATABASE production"}}' },
|
|
971
|
+
],
|
|
972
|
+
'should-allow': [
|
|
973
|
+
{ desc: 'safe ls', input: '{"tool_input":{"command":"ls -la"}}' },
|
|
974
|
+
{ desc: 'git status', input: '{"tool_input":{"command":"git status"}}' },
|
|
975
|
+
{ desc: 'npm test', input: '{"tool_input":{"command":"npm test"}}' },
|
|
976
|
+
{ desc: 'cat file', input: '{"tool_input":{"command":"cat README.md"}}' },
|
|
977
|
+
{ desc: 'empty input', input: '{}' },
|
|
978
|
+
],
|
|
979
|
+
};
|
|
980
|
+
|
|
981
|
+
let pass = 0, total = 0;
|
|
982
|
+
|
|
983
|
+
for (const [category, samples] of Object.entries(SAMPLES)) {
|
|
984
|
+
console.log(c.dim + ` ${category}:` + c.reset);
|
|
985
|
+
for (const sample of samples) {
|
|
986
|
+
total++;
|
|
987
|
+
try {
|
|
988
|
+
execSync(`echo '${sample.input}' | bash "${hookPath}"`, { stdio: 'pipe', timeout: 5000 });
|
|
989
|
+
// Exit 0 = allowed
|
|
990
|
+
const icon = category === 'should-allow' ? c.green + '✓' : c.yellow + '·';
|
|
991
|
+
console.log(` ${icon}${c.reset} ${sample.desc} → allowed (exit 0)`);
|
|
992
|
+
if (category === 'should-allow') pass++;
|
|
993
|
+
} catch (e) {
|
|
994
|
+
const code = e.status;
|
|
995
|
+
if (code === 2) {
|
|
996
|
+
// Blocked
|
|
997
|
+
const icon = category === 'should-block' ? c.green + '✓' : c.red + '✗';
|
|
998
|
+
console.log(` ${icon}${c.reset} ${sample.desc} → ${c.red}BLOCKED${c.reset} (exit 2)`);
|
|
999
|
+
if (category === 'should-block') pass++;
|
|
1000
|
+
} else {
|
|
1001
|
+
console.log(` ${c.yellow}?${c.reset} ${sample.desc} → exit ${code}`);
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
console.log();
|
|
1008
|
+
console.log(` ${pass}/${total} samples matched expected behavior`);
|
|
1009
|
+
console.log();
|
|
1010
|
+
}
|
|
1011
|
+
|
|
934
1012
|
async function saveProfile(name) {
|
|
935
1013
|
const { readdirSync } = await import('fs');
|
|
936
1014
|
const profilesDir = join(HOME, '.claude', 'profiles');
|
|
@@ -4340,6 +4418,7 @@ async function main() {
|
|
|
4340
4418
|
if (FULL) return fullSetup();
|
|
4341
4419
|
if (DOCTOR) return doctor();
|
|
4342
4420
|
if (WATCH) return watch();
|
|
4421
|
+
if (TEST_HOOK_IDX !== -1) return testHook(TEST_HOOK);
|
|
4343
4422
|
if (SAVE_PROFILE_IDX !== -1) return saveProfile(SAVE_PROFILE);
|
|
4344
4423
|
if (SUGGEST) return suggest();
|
|
4345
4424
|
if (WHY_IDX !== -1) return why(WHY_HOOK);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cc-safe-setup",
|
|
3
|
-
"version": "12.
|
|
3
|
+
"version": "12.1.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": {
|