cc-safe-setup 11.5.0 → 11.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.
Files changed (3) hide show
  1. package/README.md +1 -1
  2. package/index.mjs +80 -0
  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**. 40 CLI commands. 561 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)
9
+ 8 built-in + 104 examples = **118 hooks**. 41 CLI commands. 561 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
@@ -106,6 +106,8 @@ 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
108
  const REPLAY = process.argv.includes('--replay');
109
+ const SAVE_PROFILE_IDX = process.argv.findIndex(a => a === '--save-profile');
110
+ const SAVE_PROFILE = SAVE_PROFILE_IDX !== -1 ? process.argv[SAVE_PROFILE_IDX + 1] : null;
109
111
  const CREATE_IDX = process.argv.findIndex(a => a === '--create');
110
112
  const CREATE_DESC = CREATE_IDX !== -1 ? process.argv.slice(CREATE_IDX + 1).join(' ') : null;
111
113
  const SUGGEST = process.argv.includes('--suggest');
@@ -142,6 +144,7 @@ if (HELP) {
142
144
  npx cc-safe-setup --doctor Diagnose why hooks aren't working
143
145
  npx cc-safe-setup --watch Live dashboard of blocked commands
144
146
  npx cc-safe-setup --create "<desc>" Generate a custom hook from description
147
+ npx cc-safe-setup --save-profile <name> Save current hooks as a named profile
145
148
  npx cc-safe-setup --suggest Analyze project and predict risks → suggest hooks
146
149
  npx cc-safe-setup --why <hook> Why this hook exists (real incident + issue link)
147
150
  npx cc-safe-setup --replay Replay blocked commands timeline (demo/review)
@@ -928,6 +931,53 @@ async function fullSetup() {
928
931
  console.log();
929
932
  }
930
933
 
934
+ async function saveProfile(name) {
935
+ const { readdirSync } = await import('fs');
936
+ const profilesDir = join(HOME, '.claude', 'profiles');
937
+ mkdirSync(profilesDir, { recursive: true });
938
+
939
+ if (!name) {
940
+ // List saved profiles
941
+ console.log();
942
+ console.log(c.bold + ' Saved Profiles' + c.reset);
943
+ console.log();
944
+ const files = existsSync(profilesDir) ? readdirSync(profilesDir).filter(f => f.endsWith('.json')) : [];
945
+ if (files.length === 0) {
946
+ console.log(c.dim + ' No saved profiles yet.' + c.reset);
947
+ console.log(c.dim + ' Save: npx cc-safe-setup --save-profile my-setup' + c.reset);
948
+ } else {
949
+ for (const f of files) {
950
+ const pName = f.replace('.json', '');
951
+ const data = JSON.parse(readFileSync(join(profilesDir, f), 'utf-8'));
952
+ console.log(` ${c.bold}${pName}${c.reset} (${data.hooks?.length || 0} hooks, saved ${data.savedAt?.split('T')[0] || '?'})`);
953
+ console.log(c.dim + ` Load: npx cc-safe-setup --profile ${pName}` + c.reset);
954
+ }
955
+ }
956
+ console.log();
957
+ return;
958
+ }
959
+
960
+ // Save current hook state
961
+ const hookDir = join(HOME, '.claude', 'hooks');
962
+ const hooks = existsSync(hookDir) ? readdirSync(hookDir).filter(f => f.endsWith('.sh') || f.endsWith('.py')) : [];
963
+
964
+ const profile = {
965
+ name,
966
+ savedAt: new Date().toISOString(),
967
+ hooks: hooks.map(h => h.replace(/\.(sh|py)$/, '')),
968
+ settings: existsSync(SETTINGS_PATH) ? JSON.parse(readFileSync(SETTINGS_PATH, 'utf-8')) : {},
969
+ };
970
+
971
+ const profilePath = join(profilesDir, `${name}.json`);
972
+ writeFileSync(profilePath, JSON.stringify(profile, null, 2));
973
+
974
+ console.log();
975
+ console.log(c.green + ` ✓ Profile "${name}" saved (${hooks.length} hooks)` + c.reset);
976
+ console.log(c.dim + ` File: ${profilePath}` + c.reset);
977
+ console.log(c.dim + ` Load: npx cc-safe-setup --profile ${name}` + c.reset);
978
+ console.log();
979
+ }
980
+
931
981
  async function suggest() {
932
982
  const { execSync } = await import('child_process');
933
983
  const { readdirSync } = await import('fs');
@@ -1831,6 +1881,16 @@ async function profile(level) {
1831
1881
  },
1832
1882
  };
1833
1883
 
1884
+ // Check for saved custom profiles
1885
+ const profilesDir = join(HOME, '.claude', 'profiles');
1886
+ if (level && !PROFILES[level]) {
1887
+ const customPath = join(profilesDir, `${level}.json`);
1888
+ if (existsSync(customPath)) {
1889
+ const custom = JSON.parse(readFileSync(customPath, 'utf-8'));
1890
+ PROFILES[level] = { desc: `Custom profile (saved ${custom.savedAt?.split('T')[0] || '?'})`, hooks: custom.hooks || [] };
1891
+ }
1892
+ }
1893
+
1834
1894
  if (!level || !PROFILES[level]) {
1835
1895
  console.log(c.bold + ' Safety Profiles' + c.reset);
1836
1896
  console.log();
@@ -1840,6 +1900,25 @@ async function profile(level) {
1840
1900
  console.log(` ${c.dim}npx cc-safe-setup --profile ${name}${c.reset}`);
1841
1901
  console.log();
1842
1902
  }
1903
+
1904
+ // Show saved profiles too
1905
+ if (existsSync(profilesDir)) {
1906
+ const saved = readdirSync(profilesDir).filter(f => f.endsWith('.json'));
1907
+ if (saved.length > 0) {
1908
+ console.log(c.bold + ' Saved Profiles' + c.reset);
1909
+ console.log();
1910
+ for (const f of saved) {
1911
+ const sName = f.replace('.json', '');
1912
+ try {
1913
+ const data = JSON.parse(readFileSync(join(profilesDir, f), 'utf-8'));
1914
+ console.log(` ${c.bold}${sName}${c.reset} (${data.hooks?.length || 0} hooks)`);
1915
+ console.log(` ${c.dim}Saved ${data.savedAt?.split('T')[0] || '?'}${c.reset}`);
1916
+ console.log(` ${c.dim}npx cc-safe-setup --profile ${sName}${c.reset}`);
1917
+ console.log();
1918
+ } catch {}
1919
+ }
1920
+ }
1921
+ }
1843
1922
  return;
1844
1923
  }
1845
1924
 
@@ -4261,6 +4340,7 @@ async function main() {
4261
4340
  if (FULL) return fullSetup();
4262
4341
  if (DOCTOR) return doctor();
4263
4342
  if (WATCH) return watch();
4343
+ if (SAVE_PROFILE_IDX !== -1) return saveProfile(SAVE_PROFILE);
4264
4344
  if (SUGGEST) return suggest();
4265
4345
  if (WHY_IDX !== -1) return why(WHY_HOOK);
4266
4346
  if (REPLAY) return replay();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cc-safe-setup",
3
- "version": "11.5.0",
3
+ "version": "11.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": {