cc-safe-setup 3.3.0 → 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 (2) hide show
  1. package/index.mjs +101 -0
  2. package/package.json +1 -1
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
 
@@ -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,6 +1,6 @@
1
1
  {
2
2
  "name": "cc-safe-setup",
3
- "version": "3.3.0",
3
+ "version": "3.4.0",
4
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": {