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.
- package/index.mjs +114 -0
- 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.
|
|
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": {
|