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