cc-safe-setup 2.6.0 → 2.7.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 +114 -0
  2. package/package.json +1 -1
package/index.mjs CHANGED
@@ -73,6 +73,7 @@ const LEARN = process.argv.includes('--learn');
73
73
  const SCAN = process.argv.includes('--scan');
74
74
  const FULL = process.argv.includes('--full');
75
75
  const DOCTOR = process.argv.includes('--doctor');
76
+ const WATCH = process.argv.includes('--watch');
76
77
 
77
78
  if (HELP) {
78
79
  console.log(`
@@ -92,6 +93,7 @@ if (HELP) {
92
93
  npx cc-safe-setup --scan Detect tech stack, recommend hooks
93
94
  npx cc-safe-setup --learn Learn from your block history
94
95
  npx cc-safe-setup --doctor Diagnose why hooks aren't working
96
+ npx cc-safe-setup --watch Live dashboard of blocked commands
95
97
  npx cc-safe-setup --help Show this help
96
98
 
97
99
  Hooks installed:
@@ -740,6 +742,117 @@ async function fullSetup() {
740
742
  console.log();
741
743
  }
742
744
 
745
+ async function watch() {
746
+ const { spawn } = await import('child_process');
747
+ const { createReadStream, watchFile } = await import('fs');
748
+ const { createInterface: createRL } = await import('readline');
749
+
750
+ const LOG_PATH = join(HOME, '.claude', 'blocked-commands.log');
751
+ const ERROR_LOG = join(HOME, '.claude', 'session-errors.log');
752
+
753
+ console.log();
754
+ console.log(c.bold + ' cc-safe-setup --watch' + c.reset);
755
+ console.log(c.dim + ' Live safety dashboard — watching blocked commands' + c.reset);
756
+ console.log(c.dim + ' Log: ' + LOG_PATH + c.reset);
757
+ console.log();
758
+
759
+ let blockCount = 0;
760
+ let lastPrint = 0;
761
+
762
+ function formatLine(line) {
763
+ // Format: [2026-03-24T01:30:00+09:00] BLOCKED: reason | cmd: actual command
764
+ const match = line.match(/^\[([^\]]+)\]\s*BLOCKED:\s*(.+?)\s*\|\s*cmd:\s*(.+)$/);
765
+ if (!match) return c.dim + ' ' + line + c.reset;
766
+
767
+ const [, ts, reason, cmd] = match;
768
+ const time = ts.replace(/T/, ' ').replace(/\+.*/, '');
769
+ blockCount++;
770
+
771
+ let severity = c.yellow;
772
+ if (reason.match(/rm|reset|clean|Remove-Item|drop/i)) severity = c.red;
773
+ if (reason.match(/push|force/i)) severity = c.red;
774
+ if (reason.match(/env|secret|credential/i)) severity = c.red;
775
+
776
+ return severity + ' BLOCKED' + c.reset + ' ' + c.dim + time + c.reset + '\n' +
777
+ ' ' + c.bold + reason.trim() + c.reset + '\n' +
778
+ ' ' + c.dim + cmd.trim().slice(0, 120) + c.reset;
779
+ }
780
+
781
+ function printStats() {
782
+ const now = Date.now();
783
+ if (now - lastPrint < 30000) return;
784
+ lastPrint = now;
785
+ console.log(c.dim + ' --- ' + blockCount + ' blocks total | ' + new Date().toLocaleTimeString() + ' ---' + c.reset);
786
+ }
787
+
788
+ // Print existing log entries
789
+ if (existsSync(LOG_PATH)) {
790
+ const rl = createRL({ input: createReadStream(LOG_PATH) });
791
+ for await (const line of rl) {
792
+ if (line.trim()) console.log(formatLine(line));
793
+ }
794
+ if (blockCount > 0) {
795
+ console.log();
796
+ console.log(c.dim + ' === History: ' + blockCount + ' blocks ===' + c.reset);
797
+ console.log(c.dim + ' Watching for new blocks... (Ctrl+C to stop)' + c.reset);
798
+ console.log();
799
+ }
800
+ } else {
801
+ console.log(c.dim + ' No blocked-commands.log yet. Hooks will create it on first block.' + c.reset);
802
+ console.log(c.dim + ' Watching... (Ctrl+C to stop)' + c.reset);
803
+ console.log();
804
+ }
805
+
806
+ // Watch for new entries using tail -f
807
+ let tailProcess;
808
+ try {
809
+ // Ensure log file exists for tail
810
+ if (!existsSync(LOG_PATH)) {
811
+ const { mkdirSync: mkDir, writeFileSync: writeFile } = await import('fs');
812
+ mkDir(dirname(LOG_PATH), { recursive: true });
813
+ writeFile(LOG_PATH, '', 'utf-8');
814
+ }
815
+
816
+ tailProcess = spawn('tail', ['-f', '-n', '0', LOG_PATH], { stdio: ['ignore', 'pipe', 'ignore'] });
817
+
818
+ const tailRL = createRL({ input: tailProcess.stdout });
819
+ for await (const line of tailRL) {
820
+ if (line.trim()) {
821
+ console.log(formatLine(line));
822
+ printStats();
823
+ }
824
+ }
825
+ } catch (e) {
826
+ // tail not available — fall back to polling
827
+ let lastSize = 0;
828
+ try {
829
+ const { statSync } = await import('fs');
830
+ lastSize = statSync(LOG_PATH).size;
831
+ } catch {}
832
+
833
+ console.log(c.dim + ' (tail not available, using polling)' + c.reset);
834
+
835
+ setInterval(async () => {
836
+ try {
837
+ const { statSync, readFileSync: readFile } = await import('fs');
838
+ const stat = statSync(LOG_PATH);
839
+ if (stat.size > lastSize) {
840
+ const content = readFile(LOG_PATH, 'utf-8');
841
+ const lines = content.split('\n').slice(-10);
842
+ for (const line of lines) {
843
+ if (line.trim()) console.log(formatLine(line));
844
+ }
845
+ lastSize = stat.size;
846
+ printStats();
847
+ }
848
+ } catch {}
849
+ }, 2000);
850
+
851
+ // Keep process alive
852
+ await new Promise(() => {});
853
+ }
854
+ }
855
+
743
856
  async function doctor() {
744
857
  const { execSync, spawnSync } = await import('child_process');
745
858
  const { statSync, readdirSync } = await import('fs');
@@ -1059,6 +1172,7 @@ async function main() {
1059
1172
  if (SCAN) return scan();
1060
1173
  if (FULL) return fullSetup();
1061
1174
  if (DOCTOR) return doctor();
1175
+ if (WATCH) return watch();
1062
1176
 
1063
1177
  console.log();
1064
1178
  console.log(c.bold + ' cc-safe-setup' + c.reset);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cc-safe-setup",
3
- "version": "2.6.0",
3
+ "version": "2.7.0",
4
4
  "description": "One command to make Claude Code safe for autonomous operation. 8 built-in hooks + 26 installable examples. Destructive blocker, branch guard, database wipe protection, case-insensitive FS guard, and more.",
5
5
  "main": "index.mjs",
6
6
  "bin": {