cc-safe-setup 8.7.0 → 8.8.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 +89 -0
  2. package/package.json +1 -1
package/index.mjs CHANGED
@@ -95,6 +95,7 @@ const ANALYZE = process.argv.includes('--analyze');
95
95
  const TEAM = process.argv.includes('--team');
96
96
  const MIGRATE_FROM_IDX = process.argv.findIndex(a => a === '--migrate-from');
97
97
  const MIGRATE_FROM = MIGRATE_FROM_IDX !== -1 ? process.argv[MIGRATE_FROM_IDX + 1] : null;
98
+ const HEALTH = process.argv.includes('--health');
98
99
  const PROFILE_IDX = process.argv.findIndex(a => a === '--profile');
99
100
  const PROFILE = PROFILE_IDX !== -1 ? process.argv[PROFILE_IDX + 1] : null;
100
101
  const COMPARE_IDX = process.argv.findIndex(a => a === '--compare');
@@ -132,6 +133,7 @@ if (HELP) {
132
133
  npx cc-safe-setup --doctor Diagnose why hooks aren't working
133
134
  npx cc-safe-setup --watch Live dashboard of blocked commands
134
135
  npx cc-safe-setup --create "<desc>" Generate a custom hook from description
136
+ npx cc-safe-setup --health Hook health dashboard (size, permissions, age)
135
137
  npx cc-safe-setup --migrate-from <tool> Migrate from safety-net/hooks-mastery/etc.
136
138
  npx cc-safe-setup --team Set up project-level hooks (commit to repo for team)
137
139
  npx cc-safe-setup --profile <level> Switch safety profile (strict/standard/minimal)
@@ -843,6 +845,92 @@ async function fullSetup() {
843
845
  console.log();
844
846
  }
845
847
 
848
+ async function health() {
849
+ const { readdirSync, statSync } = await import('fs');
850
+ console.log();
851
+ console.log(c.bold + ' Hook Health Dashboard' + c.reset);
852
+ console.log();
853
+
854
+ if (!existsSync(HOOKS_DIR)) {
855
+ console.log(c.red + ' No hooks directory found.' + c.reset);
856
+ console.log(c.dim + ' Run: npx cc-safe-setup --shield' + c.reset);
857
+ return;
858
+ }
859
+
860
+ const hooks = readdirSync(HOOKS_DIR).filter(f => f.endsWith('.sh') || f.endsWith('.py'));
861
+ if (hooks.length === 0) {
862
+ console.log(c.yellow + ' No hooks installed.' + c.reset);
863
+ return;
864
+ }
865
+
866
+ // Table header
867
+ const pad = (s, n) => s.substring(0, n).padEnd(n);
868
+ console.log(c.dim + ' ' + pad('Hook', 30) + pad('Size', 8) + pad('Perms', 7) + pad('Age', 12) + 'Shebang' + c.reset);
869
+ console.log(c.dim + ' ' + '─'.repeat(70) + c.reset);
870
+
871
+ let healthy = 0, issues = 0;
872
+ const now = Date.now();
873
+
874
+ for (const h of hooks.sort()) {
875
+ const fullPath = join(HOOKS_DIR, h);
876
+ const st = statSync(fullPath);
877
+ const content = readFileSync(fullPath, 'utf-8');
878
+ const firstLine = content.split('\n')[0];
879
+
880
+ // Size
881
+ const size = st.size < 1024 ? st.size + 'B' : Math.round(st.size / 1024) + 'KB';
882
+
883
+ // Permissions
884
+ const isExec = !!(st.mode & 0o111);
885
+ const perms = isExec ? c.green + '✓ exec' + c.reset : c.red + '✗ exec' + c.reset;
886
+
887
+ // Age
888
+ const ageDays = Math.floor((now - st.mtimeMs) / 86400000);
889
+ const age = ageDays === 0 ? 'today' : ageDays === 1 ? '1 day' : ageDays + ' days';
890
+
891
+ // Shebang
892
+ const hasShebang = firstLine.startsWith('#!');
893
+ const shebang = hasShebang ? c.green + '✓' + c.reset : c.red + '✗' + c.reset;
894
+
895
+ // Status icon
896
+ const ok = isExec && hasShebang;
897
+ const icon = ok ? c.green + '●' + c.reset : c.red + '●' + c.reset;
898
+ if (ok) healthy++; else issues++;
899
+
900
+ console.log(' ' + icon + ' ' + pad(h, 29) + pad(size, 8) + pad(isExec ? '✓ exec' : '✗ exec', 7) + pad(age, 12) + (hasShebang ? '✓' : '✗'));
901
+ }
902
+
903
+ console.log(c.dim + ' ' + '─'.repeat(70) + c.reset);
904
+ console.log(` ${c.green}${healthy} healthy${c.reset} · ${issues > 0 ? c.red + issues + ' issues' + c.reset : c.dim + '0 issues' + c.reset} · ${hooks.length} total`);
905
+
906
+ if (issues > 0) {
907
+ console.log();
908
+ console.log(c.yellow + ' Fix issues: npx cc-safe-setup --quickfix' + c.reset);
909
+ }
910
+
911
+ // Settings.json hook count
912
+ console.log();
913
+ if (existsSync(SETTINGS_PATH)) {
914
+ try {
915
+ const s = JSON.parse(readFileSync(SETTINGS_PATH, 'utf-8'));
916
+ let configured = 0;
917
+ for (const groups of Object.values(s.hooks || {})) {
918
+ for (const g of groups) {
919
+ configured += (g.hooks || []).length;
920
+ }
921
+ }
922
+ console.log(c.dim + ` ${configured} hooks configured in settings.json` + c.reset);
923
+ if (configured < hooks.length) {
924
+ console.log(c.yellow + ` ${hooks.length - configured} hooks installed but not configured.` + c.reset);
925
+ console.log(c.dim + ' Run --shield to auto-configure all hooks.' + c.reset);
926
+ }
927
+ } catch {
928
+ console.log(c.red + ' settings.json has syntax errors.' + c.reset);
929
+ }
930
+ }
931
+ console.log();
932
+ }
933
+
846
934
  async function migrateFrom(tool) {
847
935
  console.log();
848
936
  console.log(c.bold + ' cc-safe-setup --migrate-from ' + (tool || '?') + c.reset);
@@ -3519,6 +3607,7 @@ async function main() {
3519
3607
  if (FULL) return fullSetup();
3520
3608
  if (DOCTOR) return doctor();
3521
3609
  if (WATCH) return watch();
3610
+ if (HEALTH) return health();
3522
3611
  if (MIGRATE_FROM_IDX !== -1) return migrateFrom(MIGRATE_FROM);
3523
3612
  if (TEAM) return team();
3524
3613
  if (PROFILE_IDX !== -1) return profile(PROFILE);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cc-safe-setup",
3
- "version": "8.7.0",
3
+ "version": "8.8.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": {