@claude-code-hooks/cli 0.1.6 → 0.1.8

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 +238 -0
  2. package/package.json +6 -6
  3. package/src/cli.js +33 -17
package/README.md ADDED
@@ -0,0 +1,238 @@
1
+ # @claude-code-hooks/cli
2
+
3
+ > Part of the [claude-code-hooks](../../README.md) monorepo.
4
+
5
+ Umbrella wizard CLI to set up and manage `@claude-code-hooks` packages for Claude Code (sound, notification, security, secrets).
6
+
7
+ ![CLI install and usage demo](https://github.com/beefiker/claude-code-hooks/raw/main/images/claude-code-hooks-cli.gif)
8
+
9
+ ## Install / run
10
+
11
+ From anywhere:
12
+
13
+ ```bash
14
+ npx @claude-code-hooks/cli@latest
15
+ ```
16
+
17
+ You'll be prompted to choose which packages to configure and where to write settings:
18
+
19
+ - Project (shared): `.claude/settings.json`
20
+ - Project (local): `.claude/settings.local.json`
21
+ - Global: `~/.claude/settings.json`
22
+
23
+ Then you can enable/disable each package (sound, notification, security, secrets) and customize their options.
24
+
25
+ ---
26
+
27
+ ## @claude-code-hooks/sound
28
+
29
+ Cross-platform CLI (macOS, Windows, Linux) that configures **Claude Code Hooks** to play **bundled sounds**.
30
+
31
+ ![claude-sound CLI](https://github.com/beefiker/claude-code-hooks/raw/main/packages/sound/assets/images/how-to-use.gif)
32
+
33
+ - Setup UI: `npx @claude-code-hooks/sound@latest`
34
+ - Hook runner: `npx --yes @claude-code-hooks/sound@latest play --event <Event> --sound <SoundId> --managed-by @claude-code-hooks/sound`
35
+
36
+ ### Install / run
37
+
38
+ ```bash
39
+ npx @claude-code-hooks/sound@latest
40
+ ```
41
+
42
+ You'll be prompted to choose where to write settings, then enable/disable events and choose a sound per event. Selecting a sound plays a quick preview. Choose **Create my own** to generate custom text-to-speech sounds, or **Import from file** to add your own MP3/WAV files.
43
+
44
+ ### Commands
45
+
46
+ ```bash
47
+ claude-sound list-events
48
+ claude-sound list-sounds
49
+ claude-sound play --sound ring1
50
+ claude-sound import <path> # Import MP3/WAV into ~/.claude-sound/sounds/
51
+ ```
52
+
53
+ ### Uninstall / remove hooks
54
+
55
+ Run the setup again and choose **Remove all claude-sound hooks**, then **Apply**. Or manually delete any hook handlers whose command contains:
56
+
57
+ ```
58
+ --managed-by @claude-code-hooks/sound
59
+ ```
60
+
61
+ ### Create my own (text-to-speech)
62
+
63
+ When picking a sound, choose **Create my own** to generate custom sounds from text. Supports English (default) and Korean. See [packages/sound/docs/TTS.md](../sound/docs/TTS.md) for details.
64
+
65
+ ### Import from file
66
+
67
+ Choose **Import from file** and enter a path to an MP3 or WAV file. Or use the CLI:
68
+
69
+ ```bash
70
+ claude-sound import ./my-notification.mp3
71
+ ```
72
+
73
+ Supported formats: MP3, WAV. Max file size: 5MB.
74
+
75
+ ### Platform support
76
+
77
+ | Platform | Audio player | Notes |
78
+ |----------|--------------|-------|
79
+ | **macOS** | `afplay` | Built-in, no setup needed |
80
+ | **Windows** | `ffplay`, `mpv`, `mpg123`, or PowerShell | Install [ffmpeg](https://ffmpeg.org/) or [mpv](https://mpv.io/) for best support. PowerShell plays WAV only. |
81
+ | **Linux** | `ffplay`, `mpv`, `mpg123`, `aplay`, etc. | Install ffmpeg or mpv for MP3 support. |
82
+
83
+ ---
84
+
85
+ ## @claude-code-hooks/notification
86
+
87
+ **Zero-dependency** CLI that sends OS-level notifications from Claude Code hooks.
88
+
89
+ - **macOS**: `osascript` (`display notification`)
90
+ - **Linux**: `notify-send` (libnotify)
91
+ - **Windows**: PowerShell toast via Windows Runtime types (no external modules)
92
+
93
+ Falls back to stdout when no GUI environment is detected (SSH, headless, CI).
94
+
95
+ ### Install / run
96
+
97
+ ```bash
98
+ npx --yes @claude-code-hooks/notification@latest --event Stop
99
+ ```
100
+
101
+ ### CLI options
102
+
103
+ ```
104
+ --title <text> Notification title (default: derived from --event or "Claude Code")
105
+ --message <text> Notification body (default: derived from --event or stdin JSON)
106
+ --event <name> Hook event name (e.g. Stop, Notification, TaskCompleted)
107
+ --dry-run Build the command but don't execute it (prints JSON to stdout)
108
+ ```
109
+
110
+ ### Usage as a Claude Code hook
111
+
112
+ Add to your Claude Code `settings.json`:
113
+
114
+ ```json
115
+ {
116
+ "hooks": {
117
+ "Stop": [
118
+ {
119
+ "matcher": "*",
120
+ "hooks": [
121
+ {
122
+ "type": "command",
123
+ "command": "npx --yes @claude-code-hooks/notification@latest --event Stop",
124
+ "timeout": 8
125
+ }
126
+ ]
127
+ }
128
+ ]
129
+ }
130
+ }
131
+ ```
132
+
133
+ ### Hook event types
134
+
135
+ | Event | Description |
136
+ |-------|-------------|
137
+ | `SessionStart` | Session begins |
138
+ | `Stop` | Claude stops generating |
139
+ | `TaskCompleted` | Task is complete |
140
+ | `Notification` | Claude wants your attention |
141
+ | `PostToolUseFailure` | After a tool fails |
142
+ | ... and more |
143
+
144
+ ### Platform support
145
+
146
+ | Platform | Method | Requirements |
147
+ |----------|--------|--------------|
148
+ | **macOS** | `osascript` | Built-in |
149
+ | **Linux** | `notify-send` | Install `libnotify-bin` (apt) or `libnotify` (pacman/dnf) |
150
+ | **Windows** | PowerShell toast | Built-in PowerShell 5+ |
151
+ | **Other/Headless** | stdout | Prints `[notification] Title: Message` to stdout |
152
+
153
+ ---
154
+
155
+ ## @claude-code-hooks/security
156
+
157
+ Warns (or optionally **blocks**) risky commands/tool invocations. Heuristic and lightweight: scans for suspicious patterns like `rm -rf`, `curl | bash`, writes to `~/.ssh`, etc.
158
+
159
+ ### Install / run
160
+
161
+ ```bash
162
+ npx @claude-code-hooks/security@latest
163
+ ```
164
+
165
+ ### Project config: claude-code-hooks.config.json
166
+
167
+ ```json
168
+ {
169
+ "security": {
170
+ "mode": "warn",
171
+ "enabledEvents": ["PreToolUse", "PermissionRequest"],
172
+ "ignore": { "regex": [] },
173
+ "allow": { "regex": [] }
174
+ }
175
+ }
176
+ ```
177
+
178
+ - `allow.regex`: if any pattern matches, **all risks are suppressed**
179
+ - `ignore.regex`: if any pattern matches, risks are suppressed and a dim note is printed
180
+
181
+ ### Modes
182
+
183
+ - `warn` (default): prints warnings to stderr, exits 0
184
+ - `block`: exits 2 when a risk is detected (**PreToolUse only**; `PermissionRequest` stays advisory)
185
+
186
+ ### Commands
187
+
188
+ ```bash
189
+ claude-security list-events
190
+ claude-security run --event PreToolUse --mode warn
191
+ claude-security doctor
192
+ ```
193
+
194
+ ---
195
+
196
+ ## @claude-code-hooks/secrets
197
+
198
+ Warns when **secret-like tokens** appear in tool inputs — and optionally scans **staged git files** before commits.
199
+
200
+ ### Install / run
201
+
202
+ ```bash
203
+ npx @claude-code-hooks/secrets@latest
204
+ ```
205
+
206
+ ### Project config: claude-code-hooks.config.json
207
+
208
+ ```json
209
+ {
210
+ "secrets": {
211
+ "mode": "warn",
212
+ "enabledEvents": ["PreToolUse", "PermissionRequest"],
213
+ "scanGitCommit": false,
214
+ "ignore": { "regex": [] },
215
+ "allow": { "regex": [] }
216
+ }
217
+ }
218
+ ```
219
+
220
+ - `scanGitCommit`: when `true`, intercepts `git commit` and scans staged files for secret patterns
221
+
222
+ ### Modes
223
+
224
+ - `warn` (default): prints warnings to stderr, exits 0
225
+ - `block`: exits 2 **only for HIGH confidence** findings (private key material)
226
+
227
+ ### What it detects
228
+
229
+ - **HIGH**: `-----BEGIN (RSA|OPENSSH|EC|PGP|DSA|ENCRYPTED) PRIVATE KEY-----`
230
+ - **MED**: OpenAI `sk-...`, GitHub `ghp_...` / `github_pat_...`, AWS `AKIA...`, Slack `xox*`, Google `AIza...`, Stripe `sk_live_...`, npm `npm_...`, PyPI `pypi-...`, DB URLs with credentials, etc.
231
+
232
+ ### Commands
233
+
234
+ ```bash
235
+ claude-secrets list-events
236
+ claude-secrets run --event PreToolUse --mode warn
237
+ claude-secrets doctor
238
+ ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@claude-code-hooks/cli",
3
- "version": "0.1.6",
3
+ "version": "0.1.8",
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,11 +27,11 @@
27
27
  },
28
28
  "dependencies": {
29
29
  "@clack/prompts": "^1.0.0",
30
- "@claude-code-hooks/core": "0.1.2",
31
- "@claude-code-hooks/security": "0.1.4",
32
- "@claude-code-hooks/secrets": "0.1.5",
33
- "@claude-code-hooks/sound": "0.2.8",
34
- "@claude-code-hooks/notification": "0.1.2"
30
+ "@claude-code-hooks/core": "0.1.3",
31
+ "@claude-code-hooks/security": "0.1.7",
32
+ "@claude-code-hooks/secrets": "0.1.8",
33
+ "@claude-code-hooks/sound": "0.2.10",
34
+ "@claude-code-hooks/notification": "0.1.5"
35
35
  },
36
36
  "keywords": [
37
37
  "claude",
package/src/cli.js CHANGED
@@ -24,7 +24,8 @@ import {
24
24
  CONFIG_FILENAME,
25
25
  configFilePath,
26
26
  readProjectConfig,
27
- writeProjectConfig
27
+ writeProjectConfig,
28
+ removeLegacyClaudeSoundHooks
28
29
  } from '@claude-code-hooks/core';
29
30
 
30
31
  import { buildSettingsSnippet } from './snippet.js';
@@ -43,7 +44,7 @@ function dieCancelled(msg = 'Cancelled') {
43
44
 
44
45
  function usage(exitCode = 0) {
45
46
  process.stdout.write(`\
46
- claude-code-hooks\n\nUsage:\n npx @claude-code-hooks/cli@latest\n\nNotes:\n - This wizard can update your Claude Code settings (global) or generate project-only config + snippet.\n`);
47
+ claude-code-hooks\n\nUsage:\n claude-code-hooks\n npx @claude-code-hooks/cli@latest\n\nWhat it does:\n - Update Claude Code settings (global), or generate a project-only config + pasteable snippet.\n`);
47
48
  process.exit(exitCode);
48
49
  }
49
50
 
@@ -66,7 +67,7 @@ async function ensureProjectOnlyConfig(projectDir, selected, perPackageConfig) {
66
67
 
67
68
  async function maybeWriteSnippet(projectDir, snippetObj) {
68
69
  const ok = await confirm({
69
- message: `Write snippet file to ${pc.bold(path.join(projectDir, 'claude-code-hooks.snippet.json'))}?`,
70
+ message: `Write snippet file (${pc.bold('claude-code-hooks.snippet.json')}) to this project?`,
70
71
  initialValue: false
71
72
  });
72
73
  if (isCancel(ok)) return;
@@ -92,20 +93,20 @@ async function main() {
92
93
 
93
94
  while (true) {
94
95
  action = await select({
95
- message: `${pc.dim('Step 1/5')} Action`,
96
+ message: `${pc.dim('Step 1/5')} Choose an action`,
96
97
  options: [
97
- { value: 'setup', label: 'Setup / enable packages' },
98
- { value: 'uninstall', label: 'Uninstall / remove managed hooks' },
98
+ { value: 'setup', label: 'Install / update packages' },
99
+ { value: 'uninstall', label: 'Uninstall (remove managed hooks)' },
99
100
  { value: 'exit', label: 'Exit' }
100
101
  ]
101
102
  });
102
103
  if (isCancel(action) || action === 'exit') dieCancelled('Bye');
103
104
 
104
105
  target = await select({
105
- message: `${pc.dim('Step 2/5')} Target`,
106
+ message: `${pc.dim('Step 2/5')} Choose a target`,
106
107
  options: [
107
108
  { value: 'global', label: `Global (default): ${pc.dim('~/.claude/settings.json')}` },
108
- { value: 'projectOnly', label: `Project-only: write ${pc.bold(CONFIG_FILENAME)} + print snippet` },
109
+ { value: 'projectOnly', label: `Project-only: write ${pc.bold(CONFIG_FILENAME)} + print a snippet` },
109
110
  { value: '__back__', label: 'Back' }
110
111
  ]
111
112
  });
@@ -113,18 +114,18 @@ async function main() {
113
114
  if (target === '__back__') continue;
114
115
 
115
116
  selected = await multiselect({
116
- message: `${pc.dim('Step 3/5')} Packages`,
117
+ message: `${pc.dim('Step 3/5')} Select packages`,
117
118
  options: [
118
119
  { value: 'security', label: '@claude-code-hooks/security', hint: 'Warn/block risky commands' },
119
120
  { value: 'secrets', label: '@claude-code-hooks/secrets', hint: 'Detect secret-like tokens' },
120
- { value: 'sound', label: '@claude-code-hooks/sound', hint: 'Play sounds on events' },
121
- { value: 'notification', label: '@claude-code-hooks/notification', hint: 'OS notifications on events' }
121
+ { value: 'sound', label: '@claude-code-hooks/sound', hint: 'Play sounds for key events' },
122
+ { value: 'notification', label: '@claude-code-hooks/notification', hint: 'OS notifications for key events' }
122
123
  ],
123
124
  required: true
124
125
  });
125
126
  if (isCancel(selected)) dieCancelled();
126
127
 
127
- const proceed = await confirm({ message: 'Continue to configure selected packages?', initialValue: true });
128
+ const proceed = await confirm({ message: 'Configure these packages now?', initialValue: true });
128
129
  if (isCancel(proceed)) dieCancelled();
129
130
  if (!proceed) continue;
130
131
 
@@ -136,8 +137,20 @@ async function main() {
136
137
 
137
138
  // ── Step 4/5: configure ──
138
139
  note(
139
- selected.map((k) => `${pc.bold(k)}: ${pc.dim({ security: 'Warn/block risky commands', secrets: 'Detect secret-like tokens', sound: 'Play sounds on events', notification: 'OS notifications on events' }[k] || '')}`).join('\n'),
140
- `${pc.dim('Step 4/5')} Configure`
140
+ selected
141
+ .map(
142
+ (k) =>
143
+ `${pc.bold(k)}: ${pc.dim(
144
+ {
145
+ security: 'Warn/block risky commands',
146
+ secrets: 'Detect secret-like tokens',
147
+ sound: 'Play sounds for key events',
148
+ notification: 'OS notifications for key events'
149
+ }[k] || ''
150
+ )}`
151
+ )
152
+ .join('\n'),
153
+ `${pc.dim('Step 4/5')} Configure packages`
141
154
  );
142
155
 
143
156
  if (selected.includes('security')) perPackage.security = await planSecuritySetup({ action, projectDir, ui: 'umbrella' });
@@ -179,8 +192,8 @@ async function main() {
179
192
  'Review'
180
193
  );
181
194
 
182
- const ok = await confirm({ message: 'Apply?', initialValue: true });
183
- if (isCancel(ok) || !ok) dieCancelled('No changes written');
195
+ const ok = await confirm({ message: 'Apply changes?', initialValue: true });
196
+ if (isCancel(ok) || !ok) dieCancelled('No changes made');
184
197
 
185
198
  if (target === 'projectOnly') {
186
199
  // Write project config
@@ -209,7 +222,7 @@ async function main() {
209
222
 
210
223
  s.stop('Done');
211
224
 
212
- note(JSON.stringify(snippetObj, null, 2), 'Paste into ~/.claude/settings.json');
225
+ note(JSON.stringify(snippetObj, null, 2), 'Paste into ~/.claude/settings.json (global)');
213
226
  await maybeWriteSnippet(projectDir, snippetObj);
214
227
 
215
228
  outro(`Project config written: ${pc.bold(configFilePath(projectDir))}`);
@@ -235,6 +248,9 @@ async function main() {
235
248
  settings = await plan.applyToSettings(settings);
236
249
  }
237
250
 
251
+ // Remove legacy claude-sound hooks (from old standalone package) to avoid duplicates
252
+ settings = removeLegacyClaudeSoundHooks(settings);
253
+
238
254
  await writeJson(settingsPath, settings);
239
255
 
240
256
  // Update project config only on setup.