@claude-code-hooks/cli 0.1.1 → 0.1.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@claude-code-hooks/cli",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "Wizard CLI to set up and manage @claude-code-hooks packages for Claude Code.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://github.com/beefiker/claude-code-hooks/tree/main/packages/cli",
@@ -27,10 +27,10 @@
27
27
  },
28
28
  "dependencies": {
29
29
  "@clack/prompts": "^1.0.0",
30
- "@claude-code-hooks/core": "0.1.1",
31
- "@claude-code-hooks/security": "0.1.2",
32
- "@claude-code-hooks/secrets": "0.1.2",
33
- "@claude-code-hooks/sound": "0.2.6"
30
+ "@claude-code-hooks/core": "0.1.2",
31
+ "@claude-code-hooks/security": "0.1.4",
32
+ "@claude-code-hooks/secrets": "0.1.4",
33
+ "@claude-code-hooks/sound": "0.2.8"
34
34
  },
35
35
  "keywords": [
36
36
  "claude",
package/src/cli.js CHANGED
@@ -64,13 +64,13 @@ async function ensureProjectOnlyConfig(projectDir, selected, perPackageConfig) {
64
64
 
65
65
  async function maybeWriteSnippet(projectDir, snippetObj) {
66
66
  const ok = await confirm({
67
- message: `Write snippet file to ${pc.bold(path.join(projectDir, 'claude-hooks.snippet.json'))}?`,
67
+ message: `Write snippet file to ${pc.bold(path.join(projectDir, 'claude-code-hooks.snippet.json'))}?`,
68
68
  initialValue: false
69
69
  });
70
70
  if (isCancel(ok)) return;
71
71
  if (!ok) return;
72
72
 
73
- const filePath = path.join(projectDir, 'claude-hooks.snippet.json');
73
+ const filePath = path.join(projectDir, 'claude-code-hooks.snippet.json');
74
74
  await fs.writeFile(filePath, JSON.stringify(snippetObj, null, 2) + '\n');
75
75
  note(filePath, 'Wrote snippet');
76
76
  }
@@ -83,61 +83,93 @@ async function main() {
83
83
 
84
84
  intro('claude-code-hooks');
85
85
 
86
- const action = await select({
87
- message: 'What do you want to do?',
88
- options: [
89
- { value: 'setup', label: 'Setup / enable packages' },
90
- { value: 'uninstall', label: 'Uninstall / remove managed hooks' },
91
- { value: 'exit', label: 'Exit' }
92
- ]
93
- });
94
- if (isCancel(action) || action === 'exit') dieCancelled('Bye');
95
-
96
- const target = await select({
97
- message: 'Where do you want to apply changes?',
98
- options: [
99
- { value: 'global', label: `Global (default): ${pc.dim('~/.claude/settings.json')}` },
100
- { value: 'projectOnly', label: `Project-only: write ${pc.bold(CONFIG_FILENAME)} + print snippet` }
101
- ]
102
- });
103
- if (isCancel(target)) dieCancelled();
104
-
105
- const selected = await multiselect({
106
- message: action === 'setup' ? 'Which packages do you want to enable?' : 'Which packages do you want to uninstall?',
107
- options: [
108
- { value: 'security', label: '@claude-code-hooks/security' },
109
- { value: 'secrets', label: '@claude-code-hooks/secrets' },
110
- { value: 'sound', label: '@claude-code-hooks/sound' }
111
- ],
112
- required: true
113
- });
114
- if (isCancel(selected)) dieCancelled();
86
+ // ── Step 1–3: action, target, packages (with simple back navigation) ──
87
+ let action;
88
+ let target;
89
+ let selected;
90
+
91
+ while (true) {
92
+ action = await select({
93
+ message: `${pc.dim('Step 1/5')} Action`,
94
+ options: [
95
+ { value: 'setup', label: 'Setup / enable packages' },
96
+ { value: 'uninstall', label: 'Uninstall / remove managed hooks' },
97
+ { value: 'exit', label: 'Exit' }
98
+ ]
99
+ });
100
+ if (isCancel(action) || action === 'exit') dieCancelled('Bye');
101
+
102
+ target = await select({
103
+ message: `${pc.dim('Step 2/5')} Target`,
104
+ options: [
105
+ { value: 'global', label: `Global (default): ${pc.dim('~/.claude/settings.json')}` },
106
+ { value: 'projectOnly', label: `Project-only: write ${pc.bold(CONFIG_FILENAME)} + print snippet` },
107
+ { value: '__back__', label: 'Back' }
108
+ ]
109
+ });
110
+ if (isCancel(target)) dieCancelled();
111
+ if (target === '__back__') continue;
112
+
113
+ selected = await multiselect({
114
+ message: `${pc.dim('Step 3/5')} Packages`,
115
+ options: [
116
+ { value: 'security', label: '@claude-code-hooks/security', hint: 'Warn/block risky commands' },
117
+ { value: 'secrets', label: '@claude-code-hooks/secrets', hint: 'Detect secret-like tokens' },
118
+ { value: 'sound', label: '@claude-code-hooks/sound', hint: 'Play sounds on events' }
119
+ ],
120
+ required: true
121
+ });
122
+ if (isCancel(selected)) dieCancelled();
123
+
124
+ const proceed = await confirm({ message: 'Continue to configure selected packages?', initialValue: true });
125
+ if (isCancel(proceed)) dieCancelled();
126
+ if (!proceed) continue;
127
+
128
+ break;
129
+ }
115
130
 
116
131
  // Build per-package plan/config
117
- const perPackage = {
118
- security: null,
119
- secrets: null,
120
- sound: null
121
- };
132
+ const perPackage = { security: null, secrets: null, sound: null };
133
+
134
+ // ── Step 4/5: configure ──
135
+ note(
136
+ selected.map((k) => `${pc.bold(k)}: ${pc.dim({ security: 'Warn/block risky commands', secrets: 'Detect secret-like tokens', sound: 'Play sounds on events' }[k] || '')}`).join('\n'),
137
+ `${pc.dim('Step 4/5')} Configure`
138
+ );
122
139
 
123
- if (selected.includes('security')) perPackage.security = await planSecuritySetup({ action, projectDir });
124
- if (selected.includes('secrets')) perPackage.secrets = await planSecretsSetup({ action, projectDir });
125
- if (selected.includes('sound')) perPackage.sound = await planSoundSetup({ action, projectDir });
140
+ if (selected.includes('security')) perPackage.security = await planSecuritySetup({ action, projectDir, ui: 'umbrella' });
141
+ if (selected.includes('secrets')) perPackage.secrets = await planSecretsSetup({ action, projectDir, ui: 'umbrella' });
142
+ if (selected.includes('sound')) perPackage.sound = await planSoundSetup({ action, projectDir, ui: 'umbrella' });
126
143
 
127
- // Review summary
144
+ // ── Step 5/5: review ──
128
145
  const files = [];
129
146
  if (target === 'global') files.push(configPathForScope('global', projectDir));
130
147
  if (target === 'projectOnly') {
131
148
  files.push(path.join(projectDir, CONFIG_FILENAME));
132
- files.push(path.join(projectDir, 'claude-hooks.snippet.json (optional)'));
149
+ files.push(path.join(projectDir, 'claude-code-hooks.snippet.json (optional)'));
150
+ }
151
+
152
+ function summarizePlan(key, plan) {
153
+ if (!plan) return `${key}: (skipped)`;
154
+ if (action === 'uninstall') return `${key}: remove managed hooks`;
155
+
156
+ const events = plan.snippetHooks ? Object.keys(plan.snippetHooks) : [];
157
+ const list = events.slice(0, 5);
158
+ const tail = events.length > 5 ? ` +${events.length - 5} more` : '';
159
+ return `${key}: ${events.length} event(s)${events.length ? ` (${list.join(', ')}${tail})` : ''}`;
133
160
  }
134
161
 
135
162
  note(
136
163
  [
164
+ `${pc.dim('Step 5/5')} Review`,
165
+ '',
137
166
  `Action: ${pc.bold(action)}`,
138
167
  `Target: ${pc.bold(target === 'global' ? 'global settings' : 'project-only')}`,
139
- `Packages: ${pc.bold(selected.join(', '))}`,
140
- `Files:`,
168
+ '',
169
+ `${pc.bold('Packages')}`,
170
+ ...selected.map((k) => ` - ${summarizePlan(k, perPackage[k])}`),
171
+ '',
172
+ `${pc.bold('Files')}`,
141
173
  ...files.map((f) => ` - ${f}`)
142
174
  ].join('\n'),
143
175
  'Review'
@@ -151,7 +183,7 @@ async function main() {
151
183
  const s = spinner();
152
184
  s.start('Writing project config...');
153
185
 
154
- // perPackage.*.projectConfigSection is shaped for claude-hooks.config.json sections.
186
+ // perPackage.*.projectConfigSection is shaped for claude-code-hooks.config.json sections.
155
187
  const projectCfg = await ensureProjectOnlyConfig(projectDir, selected, {
156
188
  security: perPackage.security?.projectConfigSection,
157
189
  secrets: perPackage.secrets?.projectConfigSection,
package/src/snippet.js CHANGED
@@ -19,7 +19,7 @@ export function buildSettingsSnippet({ projectDir, selected, packagePlans }) {
19
19
  }
20
20
 
21
21
  // Include a comment-like pointer to project config path (JSON doesn't support comments, so we use a metadata key).
22
- const cfgPath = path.join(projectDir, 'claude-hooks.config.json');
22
+ const cfgPath = path.join(projectDir, 'claude-code-hooks.config.json');
23
23
 
24
24
  return {
25
25
  "__generated_by": "@claude-code-hooks/cli",