cc-safe-setup 8.2.0 → 8.3.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.
Files changed (2) hide show
  1. package/index.mjs +117 -0
  2. package/package.json +1 -1
package/index.mjs CHANGED
@@ -92,6 +92,8 @@ 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 PROFILE_IDX = process.argv.findIndex(a => a === '--profile');
96
+ const PROFILE = PROFILE_IDX !== -1 ? process.argv[PROFILE_IDX + 1] : null;
95
97
  const COMPARE_IDX = process.argv.findIndex(a => a === '--compare');
96
98
  const COMPARE = COMPARE_IDX !== -1 ? { a: process.argv[COMPARE_IDX + 1], b: process.argv[COMPARE_IDX + 2] } : null;
97
99
  const CREATE_IDX = process.argv.findIndex(a => a === '--create');
@@ -127,6 +129,7 @@ if (HELP) {
127
129
  npx cc-safe-setup --doctor Diagnose why hooks aren't working
128
130
  npx cc-safe-setup --watch Live dashboard of blocked commands
129
131
  npx cc-safe-setup --create "<desc>" Generate a custom hook from description
132
+ npx cc-safe-setup --profile <level> Switch safety profile (strict/standard/minimal)
130
133
  npx cc-safe-setup --analyze Analyze what Claude did in your last session
131
134
  npx cc-safe-setup --shield Maximum safety in one command (fix + scan + install + CLAUDE.md)
132
135
  npx cc-safe-setup --quickfix Auto-detect and fix common Claude Code problems
@@ -835,6 +838,119 @@ async function fullSetup() {
835
838
  console.log();
836
839
  }
837
840
 
841
+ async function profile(level) {
842
+ const { readdirSync } = await import('fs');
843
+ console.log();
844
+
845
+ const PROFILES = {
846
+ strict: {
847
+ desc: 'Maximum safety — blocks everything dangerous, requires verification',
848
+ hooks: ['destructive-guard', 'branch-guard', 'secret-guard', 'syntax-check',
849
+ 'context-monitor', 'comment-strip', 'cd-git-allow', 'api-error-alert',
850
+ 'scope-guard', 'no-sudo-guard', 'protect-claudemd', 'env-source-guard',
851
+ 'no-install-global', 'deploy-guard', 'protect-dotfiles', 'symlink-guard',
852
+ 'strict-allowlist', 'uncommitted-work-guard', 'test-deletion-guard',
853
+ 'overwrite-guard', 'error-memory-guard', 'hardcoded-secret-detector',
854
+ 'conflict-marker-guard', 'token-budget-guard', 'fact-check-gate',
855
+ 'block-database-wipe', 'no-eval', 'file-size-limit', 'large-read-guard',
856
+ 'loop-detector', 'verify-before-done', 'diff-size-guard', 'commit-scope-guard']
857
+ },
858
+ standard: {
859
+ desc: 'Balanced — blocks dangerous commands, auto-approves safe ones',
860
+ hooks: ['destructive-guard', 'branch-guard', 'secret-guard', 'syntax-check',
861
+ 'context-monitor', 'comment-strip', 'cd-git-allow', 'api-error-alert',
862
+ 'scope-guard', 'no-sudo-guard', 'protect-claudemd',
863
+ 'auto-approve-build', 'auto-approve-python', 'auto-approve-docker',
864
+ 'loop-detector', 'deploy-guard', 'block-database-wipe',
865
+ 'compound-command-approver', 'session-handoff', 'cost-tracker']
866
+ },
867
+ minimal: {
868
+ desc: 'Essential only — just the 8 core safety hooks',
869
+ hooks: ['destructive-guard', 'branch-guard', 'secret-guard', 'syntax-check',
870
+ 'context-monitor', 'comment-strip', 'cd-git-allow', 'api-error-alert']
871
+ },
872
+ };
873
+
874
+ if (!level || !PROFILES[level]) {
875
+ console.log(c.bold + ' Safety Profiles' + c.reset);
876
+ console.log();
877
+ for (const [name, prof] of Object.entries(PROFILES)) {
878
+ console.log(` ${c.bold}${name}${c.reset} (${prof.hooks.length} hooks)`);
879
+ console.log(` ${c.dim}${prof.desc}${c.reset}`);
880
+ console.log(` ${c.dim}npx cc-safe-setup --profile ${name}${c.reset}`);
881
+ console.log();
882
+ }
883
+ return;
884
+ }
885
+
886
+ const prof = PROFILES[level];
887
+ console.log(c.bold + ` Applying "${level}" profile` + c.reset);
888
+ console.log(c.dim + ` ${prof.desc}` + c.reset);
889
+ console.log();
890
+
891
+ mkdirSync(HOOKS_DIR, { recursive: true });
892
+ let installed = 0;
893
+
894
+ for (const hookId of prof.hooks) {
895
+ const hookPath = join(HOOKS_DIR, `${hookId}.sh`);
896
+ if (existsSync(hookPath)) {
897
+ console.log(c.dim + ' ✓' + c.reset + ` ${hookId}`);
898
+ continue;
899
+ }
900
+
901
+ // Try built-in first
902
+ if (SCRIPTS[hookId]) {
903
+ writeFileSync(hookPath, SCRIPTS[hookId]);
904
+ chmodSync(hookPath, 0o755);
905
+ installed++;
906
+ console.log(c.green + ' +' + c.reset + ` ${hookId}`);
907
+ continue;
908
+ }
909
+
910
+ // Try examples
911
+ const exPath = join(__dirname, 'examples', `${hookId}.sh`);
912
+ if (existsSync(exPath)) {
913
+ copyFileSync(exPath, hookPath);
914
+ chmodSync(hookPath, 0o755);
915
+ installed++;
916
+ console.log(c.green + ' +' + c.reset + ` ${hookId}`);
917
+ continue;
918
+ }
919
+
920
+ console.log(c.yellow + ' ?' + c.reset + ` ${hookId} (not found)`);
921
+ }
922
+
923
+ // Update settings.json
924
+ let settings = {};
925
+ if (existsSync(SETTINGS_PATH)) {
926
+ try { settings = JSON.parse(readFileSync(SETTINGS_PATH, 'utf-8')); } catch {}
927
+ }
928
+ if (!settings.hooks) settings.hooks = {};
929
+
930
+ // Register all hooks in settings
931
+ const hookFiles = prof.hooks.filter(h => existsSync(join(HOOKS_DIR, `${h}.sh`)));
932
+ const bashHooks = hookFiles.map(h => ({ type: 'command', command: `bash ${join(HOOKS_DIR, h + '.sh')}` }));
933
+
934
+ // Simplified: put all under PreToolUse Bash for now
935
+ if (!settings.hooks.PreToolUse) settings.hooks.PreToolUse = [];
936
+ const existing = settings.hooks.PreToolUse.find(e => e.matcher === 'Bash');
937
+ if (existing) {
938
+ const existingCmds = new Set(existing.hooks.map(h => h.command));
939
+ for (const h of bashHooks) {
940
+ if (!existingCmds.has(h.command)) existing.hooks.push(h);
941
+ }
942
+ } else {
943
+ settings.hooks.PreToolUse.push({ matcher: 'Bash', hooks: bashHooks });
944
+ }
945
+
946
+ writeFileSync(SETTINGS_PATH, JSON.stringify(settings, null, 2));
947
+
948
+ console.log();
949
+ console.log(c.green + ` ✓ "${level}" profile applied (${installed} new hooks installed)` + c.reset);
950
+ console.log(c.dim + ` ${prof.hooks.length} hooks total in profile` + c.reset);
951
+ console.log();
952
+ }
953
+
838
954
  async function analyze() {
839
955
  const { execSync } = await import('child_process');
840
956
  const { readdirSync, statSync } = await import('fs');
@@ -3154,6 +3270,7 @@ async function main() {
3154
3270
  if (FULL) return fullSetup();
3155
3271
  if (DOCTOR) return doctor();
3156
3272
  if (WATCH) return watch();
3273
+ if (PROFILE_IDX !== -1) return profile(PROFILE);
3157
3274
  if (ANALYZE) return analyze();
3158
3275
  if (SHIELD) return shield();
3159
3276
  if (QUICKFIX) return quickfix();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cc-safe-setup",
3
- "version": "8.2.0",
3
+ "version": "8.3.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": {