cc-safe-setup 3.2.1 → 3.4.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 +2 -0
- package/index.mjs +102 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -216,6 +216,8 @@ Or browse all available examples in [`examples/`](examples/):
|
|
|
216
216
|
- **case-sensitive-guard.sh** — Detect case-insensitive filesystems (exFAT, NTFS, HFS+) and block rm/mkdir that would collide due to case folding ([#37875](https://github.com/anthropics/claude-code/issues/37875))
|
|
217
217
|
- **compound-command-approver.sh** — Auto-approve safe compound commands (`cd && git log`, `cd && npm test`) that the permission system can't match ([#30519](https://github.com/anthropics/claude-code/issues/30519) [#16561](https://github.com/anthropics/claude-code/issues/16561))
|
|
218
218
|
- **tmp-cleanup.sh** — Clean up accumulated `/tmp/claude-*-cwd` files on session end ([#8856](https://github.com/anthropics/claude-code/issues/8856))
|
|
219
|
+
- **session-checkpoint.sh** — Save session state to mission file before context compaction ([#37866](https://github.com/anthropics/claude-code/issues/37866))
|
|
220
|
+
- **verify-before-commit.sh** — Block git commit when lint/test commands haven't been run ([#37818](https://github.com/anthropics/claude-code/issues/37818))
|
|
219
221
|
|
|
220
222
|
## Safety Checklist
|
|
221
223
|
|
package/index.mjs
CHANGED
|
@@ -80,6 +80,8 @@ const IMPORT_FILE = IMPORT_IDX !== -1 ? process.argv[IMPORT_IDX + 1] : null;
|
|
|
80
80
|
const STATS = process.argv.includes('--stats');
|
|
81
81
|
const JSON_OUTPUT = process.argv.includes('--json');
|
|
82
82
|
const LINT = process.argv.includes('--lint');
|
|
83
|
+
const DIFF_IDX = process.argv.findIndex(a => a === '--diff');
|
|
84
|
+
const DIFF_FILE = DIFF_IDX !== -1 ? process.argv[DIFF_IDX + 1] : null;
|
|
83
85
|
const CREATE_IDX = process.argv.findIndex(a => a === '--create');
|
|
84
86
|
const CREATE_DESC = CREATE_IDX !== -1 ? process.argv.slice(CREATE_IDX + 1).join(' ') : null;
|
|
85
87
|
|
|
@@ -93,7 +95,7 @@ if (HELP) {
|
|
|
93
95
|
npx cc-safe-setup --verify Test each hook with sample inputs
|
|
94
96
|
npx cc-safe-setup --dry-run Preview without installing
|
|
95
97
|
npx cc-safe-setup --uninstall Remove all installed hooks
|
|
96
|
-
npx cc-safe-setup --examples List
|
|
98
|
+
npx cc-safe-setup --examples List 30 example hooks (5 categories)
|
|
97
99
|
npx cc-safe-setup --install-example <name> Install a specific example
|
|
98
100
|
npx cc-safe-setup --full Complete setup: hooks + scan + audit + badge
|
|
99
101
|
npx cc-safe-setup --audit Safety score (0-100) with fixes
|
|
@@ -101,6 +103,7 @@ if (HELP) {
|
|
|
101
103
|
npx cc-safe-setup --audit --json Machine-readable output for CI/CD
|
|
102
104
|
npx cc-safe-setup --scan Detect tech stack, recommend hooks
|
|
103
105
|
npx cc-safe-setup --learn Learn from your block history
|
|
106
|
+
npx cc-safe-setup --diff <file> Compare your settings with another file
|
|
104
107
|
npx cc-safe-setup --lint Static analysis of hook configuration
|
|
105
108
|
npx cc-safe-setup --doctor Diagnose why hooks aren't working
|
|
106
109
|
npx cc-safe-setup --watch Live dashboard of blocked commands
|
|
@@ -769,6 +772,103 @@ async function fullSetup() {
|
|
|
769
772
|
console.log();
|
|
770
773
|
}
|
|
771
774
|
|
|
775
|
+
function diff(otherFile) {
|
|
776
|
+
console.log();
|
|
777
|
+
console.log(c.bold + ' cc-safe-setup --diff' + c.reset);
|
|
778
|
+
console.log();
|
|
779
|
+
|
|
780
|
+
if (!existsSync(otherFile)) {
|
|
781
|
+
console.log(c.red + ' File not found: ' + otherFile + c.reset);
|
|
782
|
+
process.exit(1);
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
if (!existsSync(SETTINGS_PATH)) {
|
|
786
|
+
console.log(c.red + ' No local settings.json found.' + c.reset);
|
|
787
|
+
process.exit(1);
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
let local, other;
|
|
791
|
+
try { local = JSON.parse(readFileSync(SETTINGS_PATH, 'utf-8')); } catch { console.log(c.red + ' Cannot parse local settings.json' + c.reset); process.exit(1); }
|
|
792
|
+
try { other = JSON.parse(readFileSync(otherFile, 'utf-8')); } catch { console.log(c.red + ' Cannot parse ' + otherFile + c.reset); process.exit(1); }
|
|
793
|
+
|
|
794
|
+
const getHookCommands = (settings, trigger) => {
|
|
795
|
+
return (settings.hooks?.[trigger] || []).flatMap(e => (e.hooks || []).map(h => {
|
|
796
|
+
const cmd = h.command || '';
|
|
797
|
+
return cmd.split('/').pop().replace(/\.sh$|\.js$|\.py$/, '');
|
|
798
|
+
}));
|
|
799
|
+
};
|
|
800
|
+
|
|
801
|
+
const getAllow = (settings) => settings.permissions?.allow || [];
|
|
802
|
+
const getDeny = (settings) => settings.permissions?.deny || [];
|
|
803
|
+
|
|
804
|
+
console.log(c.dim + ' Local: ' + SETTINGS_PATH + c.reset);
|
|
805
|
+
console.log(c.dim + ' Other: ' + otherFile + c.reset);
|
|
806
|
+
console.log();
|
|
807
|
+
|
|
808
|
+
// Compare hooks
|
|
809
|
+
const triggers = [...new Set([...Object.keys(local.hooks || {}), ...Object.keys(other.hooks || {})])];
|
|
810
|
+
|
|
811
|
+
let diffs = 0;
|
|
812
|
+
for (const trigger of triggers) {
|
|
813
|
+
const localHooks = new Set(getHookCommands(local, trigger));
|
|
814
|
+
const otherHooks = new Set(getHookCommands(other, trigger));
|
|
815
|
+
|
|
816
|
+
const onlyLocal = [...localHooks].filter(h => !otherHooks.has(h));
|
|
817
|
+
const onlyOther = [...otherHooks].filter(h => !localHooks.has(h));
|
|
818
|
+
const both = [...localHooks].filter(h => otherHooks.has(h));
|
|
819
|
+
|
|
820
|
+
if (onlyLocal.length > 0 || onlyOther.length > 0) {
|
|
821
|
+
console.log(c.bold + ' ' + trigger + c.reset);
|
|
822
|
+
for (const h of both) console.log(c.dim + ' = ' + h + c.reset);
|
|
823
|
+
for (const h of onlyLocal) { console.log(c.green + ' + ' + h + ' (local only)' + c.reset); diffs++; }
|
|
824
|
+
for (const h of onlyOther) { console.log(c.red + ' - ' + h + ' (other only)' + c.reset); diffs++; }
|
|
825
|
+
console.log();
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
// Compare permissions
|
|
830
|
+
const localAllow = getAllow(local);
|
|
831
|
+
const otherAllow = getAllow(other);
|
|
832
|
+
const onlyLocalAllow = localAllow.filter(a => !otherAllow.includes(a));
|
|
833
|
+
const onlyOtherAllow = otherAllow.filter(a => !localAllow.includes(a));
|
|
834
|
+
|
|
835
|
+
if (onlyLocalAllow.length > 0 || onlyOtherAllow.length > 0) {
|
|
836
|
+
console.log(c.bold + ' permissions.allow' + c.reset);
|
|
837
|
+
for (const a of onlyLocalAllow) { console.log(c.green + ' + ' + a + ' (local only)' + c.reset); diffs++; }
|
|
838
|
+
for (const a of onlyOtherAllow) { console.log(c.red + ' - ' + a + ' (other only)' + c.reset); diffs++; }
|
|
839
|
+
console.log();
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
// Compare deny
|
|
843
|
+
const localDeny = getDeny(local);
|
|
844
|
+
const otherDeny = getDeny(other);
|
|
845
|
+
const onlyLocalDeny = localDeny.filter(a => !otherDeny.includes(a));
|
|
846
|
+
const onlyOtherDeny = otherDeny.filter(a => !localDeny.includes(a));
|
|
847
|
+
|
|
848
|
+
if (onlyLocalDeny.length > 0 || onlyOtherDeny.length > 0) {
|
|
849
|
+
console.log(c.bold + ' permissions.deny' + c.reset);
|
|
850
|
+
for (const d of onlyLocalDeny) { console.log(c.green + ' + ' + d + ' (local only)' + c.reset); diffs++; }
|
|
851
|
+
for (const d of onlyOtherDeny) { console.log(c.red + ' - ' + d + ' (other only)' + c.reset); diffs++; }
|
|
852
|
+
console.log();
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
// Compare mode
|
|
856
|
+
if ((local.defaultMode || 'default') !== (other.defaultMode || 'default')) {
|
|
857
|
+
console.log(c.bold + ' defaultMode' + c.reset);
|
|
858
|
+
console.log(c.green + ' local: ' + (local.defaultMode || 'default') + c.reset);
|
|
859
|
+
console.log(c.red + ' other: ' + (other.defaultMode || 'default') + c.reset);
|
|
860
|
+
console.log();
|
|
861
|
+
diffs++;
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
if (diffs === 0) {
|
|
865
|
+
console.log(c.green + ' No differences found.' + c.reset);
|
|
866
|
+
} else {
|
|
867
|
+
console.log(c.dim + ' ' + diffs + ' difference(s) found.' + c.reset);
|
|
868
|
+
}
|
|
869
|
+
console.log();
|
|
870
|
+
}
|
|
871
|
+
|
|
772
872
|
async function lint() {
|
|
773
873
|
console.log();
|
|
774
874
|
console.log(c.bold + ' cc-safe-setup --lint' + c.reset);
|
|
@@ -1832,6 +1932,7 @@ async function main() {
|
|
|
1832
1932
|
if (FULL) return fullSetup();
|
|
1833
1933
|
if (DOCTOR) return doctor();
|
|
1834
1934
|
if (WATCH) return watch();
|
|
1935
|
+
if (DIFF_FILE) return diff(DIFF_FILE);
|
|
1835
1936
|
if (LINT) return lint();
|
|
1836
1937
|
if (CREATE_DESC) return createHook(CREATE_DESC);
|
|
1837
1938
|
if (STATS) return stats();
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cc-safe-setup",
|
|
3
|
-
"version": "3.
|
|
4
|
-
"description": "One command to make Claude Code safe for autonomous operation. 8 built-in hooks +
|
|
3
|
+
"version": "3.4.0",
|
|
4
|
+
"description": "One command to make Claude Code safe for autonomous operation. 8 built-in hooks + 30 installable examples. Destructive blocker, branch guard, compound command approver, database wipe protection, tmp cleanup, and more.",
|
|
5
5
|
"main": "index.mjs",
|
|
6
6
|
"bin": {
|
|
7
7
|
"cc-safe-setup": "index.mjs"
|