cc-safe-setup 10.5.0 → 10.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/README.md +1 -1
- package/index.mjs +85 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
**One command to make Claude Code safe for autonomous operation.** [日本語](docs/README.ja.md)
|
|
8
8
|
|
|
9
|
-
8 built-in + 104 examples = **118 hooks**.
|
|
9
|
+
8 built-in + 104 examples = **118 hooks**. 38 CLI commands. 531 tests. 5 languages. [**Hub**](https://yurukusa.github.io/cc-safe-setup/hub.html) · [Cheat Sheet](https://yurukusa.github.io/cc-safe-setup/hooks-cheatsheet.html) · [Builder](https://yurukusa.github.io/cc-safe-setup/builder.html) · [FAQ](https://yurukusa.github.io/cc-safe-setup/faq.html) · [Examples](https://yurukusa.github.io/cc-safe-setup/by-example.html) · [Matrix](https://yurukusa.github.io/cc-safe-setup/matrix.html) · [Playground](https://yurukusa.github.io/cc-hook-registry/playground.html)
|
|
10
10
|
|
|
11
11
|
```bash
|
|
12
12
|
npx cc-safe-setup
|
package/index.mjs
CHANGED
|
@@ -105,6 +105,7 @@ const PROFILE_IDX = process.argv.findIndex(a => a === '--profile');
|
|
|
105
105
|
const PROFILE = PROFILE_IDX !== -1 ? process.argv[PROFILE_IDX + 1] : null;
|
|
106
106
|
const COMPARE_IDX = process.argv.findIndex(a => a === '--compare');
|
|
107
107
|
const COMPARE = COMPARE_IDX !== -1 ? { a: process.argv[COMPARE_IDX + 1], b: process.argv[COMPARE_IDX + 2] } : null;
|
|
108
|
+
const REPLAY = process.argv.includes('--replay');
|
|
108
109
|
const CREATE_IDX = process.argv.findIndex(a => a === '--create');
|
|
109
110
|
const CREATE_DESC = CREATE_IDX !== -1 ? process.argv.slice(CREATE_IDX + 1).join(' ') : null;
|
|
110
111
|
|
|
@@ -138,6 +139,7 @@ if (HELP) {
|
|
|
138
139
|
npx cc-safe-setup --doctor Diagnose why hooks aren't working
|
|
139
140
|
npx cc-safe-setup --watch Live dashboard of blocked commands
|
|
140
141
|
npx cc-safe-setup --create "<desc>" Generate a custom hook from description
|
|
142
|
+
npx cc-safe-setup --replay Replay blocked commands timeline (demo/review)
|
|
141
143
|
npx cc-safe-setup --guard "<rule>" Instantly enforce a rule (generate + install + activate)
|
|
142
144
|
npx cc-safe-setup --diff-hooks <path> Compare hooks between two settings files
|
|
143
145
|
npx cc-safe-setup --from-claudemd Convert CLAUDE.md rules into hooks
|
|
@@ -412,12 +414,14 @@ function examples() {
|
|
|
412
414
|
};
|
|
413
415
|
|
|
414
416
|
// Optional category filter: --examples safety, --examples ux, etc.
|
|
415
|
-
const
|
|
416
|
-
const
|
|
417
|
+
const exIdx = Math.max(process.argv.indexOf('--examples'), process.argv.indexOf('-e'));
|
|
418
|
+
const nextArg = exIdx !== -1 ? (process.argv[exIdx + 1] || '') : '';
|
|
419
|
+
const filter = nextArg.startsWith('-') ? '' : nextArg.toLowerCase();
|
|
417
420
|
|
|
418
421
|
console.log();
|
|
419
422
|
console.log(c.bold + ' cc-safe-setup --examples' + c.reset + (filter ? ' ' + filter : ''));
|
|
420
|
-
|
|
423
|
+
const totalExamples = Object.values(CATEGORIES).reduce((sum, cat) => sum + Object.keys(cat).length, 0);
|
|
424
|
+
console.log(c.dim + ` ${totalExamples} hooks beyond the 8 built-in ones` + c.reset);
|
|
421
425
|
if (filter) console.log(c.dim + ' Filter: ' + filter + c.reset);
|
|
422
426
|
console.log();
|
|
423
427
|
|
|
@@ -853,6 +857,83 @@ async function fullSetup() {
|
|
|
853
857
|
console.log();
|
|
854
858
|
}
|
|
855
859
|
|
|
860
|
+
async function replay() {
|
|
861
|
+
console.log();
|
|
862
|
+
console.log(c.bold + ' cc-safe-setup --replay' + c.reset);
|
|
863
|
+
console.log(c.dim + ' Replay blocked commands timeline' + c.reset);
|
|
864
|
+
console.log();
|
|
865
|
+
|
|
866
|
+
const LOG_PATH = join(HOME, '.claude', 'blocked-commands.log');
|
|
867
|
+
if (!existsSync(LOG_PATH)) {
|
|
868
|
+
console.log(c.dim + ' No blocked commands log found.' + c.reset);
|
|
869
|
+
console.log(c.dim + ' Hooks will create it when they block something.' + c.reset);
|
|
870
|
+
return;
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
const content = readFileSync(LOG_PATH, 'utf-8');
|
|
874
|
+
const lines = content.split('\n').filter(l => l.trim());
|
|
875
|
+
|
|
876
|
+
if (lines.length === 0) {
|
|
877
|
+
console.log(c.dim + ' Log is empty — no commands blocked yet.' + c.reset);
|
|
878
|
+
return;
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
// Parse entries
|
|
882
|
+
const entries = [];
|
|
883
|
+
for (const line of lines) {
|
|
884
|
+
const match = line.match(/^\[([^\]]+)\]\s*BLOCKED:\s*(.+?)\s*\|\s*cmd:\s*(.+)$/);
|
|
885
|
+
if (match) {
|
|
886
|
+
entries.push({ time: match[1], reason: match[2].trim(), cmd: match[3].trim() });
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
// Group by day
|
|
891
|
+
const days = {};
|
|
892
|
+
for (const e of entries) {
|
|
893
|
+
const day = e.time.split('T')[0] || 'unknown';
|
|
894
|
+
if (!days[day]) days[day] = [];
|
|
895
|
+
days[day].push(e);
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
// Show last 7 days
|
|
899
|
+
const sortedDays = Object.keys(days).sort().slice(-7);
|
|
900
|
+
|
|
901
|
+
for (const day of sortedDays) {
|
|
902
|
+
const dayEntries = days[day];
|
|
903
|
+
console.log(c.bold + ` ${day}` + c.reset + c.dim + ` (${dayEntries.length} blocks)` + c.reset);
|
|
904
|
+
|
|
905
|
+
// Category counts
|
|
906
|
+
const cats = {};
|
|
907
|
+
for (const e of dayEntries) {
|
|
908
|
+
const cat = e.reason.split(' ')[0] || 'other';
|
|
909
|
+
cats[cat] = (cats[cat] || 0) + 1;
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
// Top categories as mini bar chart
|
|
913
|
+
const sorted = Object.entries(cats).sort((a, b) => b[1] - a[1]).slice(0, 5);
|
|
914
|
+
for (const [cat, count] of sorted) {
|
|
915
|
+
const bar = '█'.repeat(Math.min(count, 30));
|
|
916
|
+
const color = cat.match(/rm|reset|clean|Remove/i) ? c.red : cat.match(/push|force/i) ? c.red : c.yellow;
|
|
917
|
+
console.log(` ${color}${bar}${c.reset} ${count}× ${cat}`);
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
// Show last 3 entries of the day
|
|
921
|
+
const recent = dayEntries.slice(-3);
|
|
922
|
+
for (const e of recent) {
|
|
923
|
+
const time = (e.time.split('T')[1] || '').replace(/\+.*/, '').substring(0, 8);
|
|
924
|
+
console.log(c.dim + ` ${time} ${e.reason.substring(0, 40)} → ${e.cmd.substring(0, 50)}` + c.reset);
|
|
925
|
+
}
|
|
926
|
+
console.log();
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
// Summary
|
|
930
|
+
console.log(c.bold + ' Summary' + c.reset);
|
|
931
|
+
console.log(` Total blocks: ${entries.length}`);
|
|
932
|
+
console.log(` Days with blocks: ${Object.keys(days).length}`);
|
|
933
|
+
console.log(` Avg per day: ${Math.round(entries.length / Math.max(Object.keys(days).length, 1))}`);
|
|
934
|
+
console.log();
|
|
935
|
+
}
|
|
936
|
+
|
|
856
937
|
async function guard(description) {
|
|
857
938
|
if (!description) {
|
|
858
939
|
console.log();
|
|
@@ -3905,6 +3986,7 @@ async function main() {
|
|
|
3905
3986
|
if (FULL) return fullSetup();
|
|
3906
3987
|
if (DOCTOR) return doctor();
|
|
3907
3988
|
if (WATCH) return watch();
|
|
3989
|
+
if (REPLAY) return replay();
|
|
3908
3990
|
if (GUARD_IDX !== -1) return guard(GUARD_DESC);
|
|
3909
3991
|
if (DIFF_HOOKS_IDX !== -1) return diffHooks(DIFF_HOOKS);
|
|
3910
3992
|
if (FROM_CLAUDEMD) return fromClaudeMd();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cc-safe-setup",
|
|
3
|
-
"version": "10.
|
|
3
|
+
"version": "10.7.0",
|
|
4
4
|
"description": "One command to make Claude Code safe. 59 hooks (8 built-in + 51 examples). 26 CLI commands: dashboard, create, audit, lint, diff, migrate, compare, generate-ci. 284 tests.",
|
|
5
5
|
"main": "index.mjs",
|
|
6
6
|
"bin": {
|