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.
Files changed (3) hide show
  1. package/README.md +2 -0
  2. package/index.mjs +102 -1
  3. 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 28 example hooks (5 categories)
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.2.1",
4
- "description": "One command to make Claude Code safe for autonomous operation. 8 built-in hooks + 27 installable examples. Destructive blocker, branch guard, compound command approver, database wipe protection, and more.",
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"