@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.
- package/README.md +238 -0
- package/package.json +6 -6
- 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
|
+

|
|
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
|
+

|
|
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.
|
|
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.
|
|
31
|
-
"@claude-code-hooks/security": "0.1.
|
|
32
|
-
"@claude-code-hooks/secrets": "0.1.
|
|
33
|
-
"@claude-code-hooks/sound": "0.2.
|
|
34
|
-
"@claude-code-hooks/notification": "0.1.
|
|
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\
|
|
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
|
|
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')}
|
|
96
|
+
message: `${pc.dim('Step 1/5')} Choose an action`,
|
|
96
97
|
options: [
|
|
97
|
-
{ value: 'setup', label: '
|
|
98
|
-
{ value: 'uninstall', label: 'Uninstall
|
|
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')}
|
|
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')}
|
|
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
|
|
121
|
-
{ value: 'notification', label: '@claude-code-hooks/notification', hint: 'OS notifications
|
|
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: '
|
|
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
|
|
140
|
-
|
|
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
|
|
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.
|