warhorn 1.0.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 (32) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +177 -0
  3. package/assets/commands/warhorn/mute.md +14 -0
  4. package/assets/commands/warhorn/setup.md +314 -0
  5. package/assets/commands/warhorn/unmute.md +14 -0
  6. package/assets/scripts/generate_voices.py +512 -0
  7. package/assets/scripts/play-sound.sh +73 -0
  8. package/assets/sounds/Notification/bubble.wav +0 -0
  9. package/assets/sounds/Notification/ping.wav +0 -0
  10. package/assets/sounds/PermissionRequest/alert.wav +0 -0
  11. package/assets/sounds/PermissionRequest/doorbell.wav +0 -0
  12. package/assets/sounds/PostToolUse/.gitkeep +0 -0
  13. package/assets/sounds/PostToolUseFailure/buzz.wav +0 -0
  14. package/assets/sounds/PostToolUseFailure/sad.wav +0 -0
  15. package/assets/sounds/PostToolUseFailure/wonk.wav +0 -0
  16. package/assets/sounds/PreCompact/warning.wav +0 -0
  17. package/assets/sounds/PreToolUse/.gitkeep +0 -0
  18. package/assets/sounds/SessionEnd/shutdown.wav +0 -0
  19. package/assets/sounds/SessionStart/boot.wav +0 -0
  20. package/assets/sounds/SessionStart/ready.wav +0 -0
  21. package/assets/sounds/Stop/bell.wav +0 -0
  22. package/assets/sounds/Stop/chime.wav +0 -0
  23. package/assets/sounds/Stop/success.wav +0 -0
  24. package/assets/sounds/SubagentStart/spawn.wav +0 -0
  25. package/assets/sounds/SubagentStop/return.wav +0 -0
  26. package/assets/sounds/TaskCompleted/victory.wav +0 -0
  27. package/assets/sounds/TeammateIdle/.gitkeep +0 -0
  28. package/assets/sounds/UserPromptSubmit/tap.wav +0 -0
  29. package/assets/sounds/UserPromptSubmit/tick.wav +0 -0
  30. package/bin/install.js +184 -0
  31. package/bin/uninstall.js +148 -0
  32. package/package.json +41 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Alex Levadski
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,177 @@
1
+ <div align="center">
2
+
3
+ # WARHORN
4
+
5
+ **Sound notifications for [Claude Code](https://docs.anthropic.com/en/docs/claude-code)**
6
+
7
+ Never miss the end of a long task, a permission request, or a failed tool call again. Chimes, alerts, and AI voice lines — your choice.
8
+
9
+ <br>
10
+
11
+ ```bash
12
+ npx warhorn
13
+ ```
14
+
15
+ <br>
16
+
17
+ </div>
18
+
19
+ ---
20
+
21
+ ## What is warhorn?
22
+
23
+ Claude Code runs in the terminal — no visual cues, no push notifications, no sound. If you step away or switch tabs during a long task, you have no idea when it's done, when it needs approval, or when something breaks.
24
+
25
+ warhorn fixes that. It hooks into Claude Code's lifecycle events and plays sounds when things happen:
26
+
27
+ - **Instrumental chimes** — bundled WAV files that work immediately, no setup needed
28
+ - **AI voice lines** — short, punchy character voice clips generated via [edge-tts](https://github.com/rany2/edge-tts) (free Microsoft TTS)
29
+ - **Both** — instrumentals and voice lines mixed randomly per hook
30
+ - **Fully customizable** — pick which hooks trigger sounds, choose a voice preset and personality tone, or describe your own
31
+
32
+ ## Getting Started
33
+
34
+ ### 1. Install Claude Code (if you haven't already)
35
+
36
+ ```bash
37
+ # macOS / Linux
38
+ curl -fsSL https://claude.ai/install.sh | bash
39
+
40
+ # Windows (PowerShell)
41
+ irm https://claude.ai/install.ps1 | iex
42
+ ```
43
+
44
+ Verify it's working:
45
+ ```bash
46
+ claude --version
47
+ ```
48
+
49
+ > Need help? See the [official setup guide](https://code.claude.com/docs/en/setup).
50
+
51
+ ### 2. Install warhorn
52
+
53
+ ```bash
54
+ npx warhorn
55
+ ```
56
+
57
+ warhorn installs itself as a Claude Code plugin — sounds, scripts, commands, and hooks all set up automatically.
58
+
59
+ ### 3. Configure
60
+
61
+ Open Claude Code in any project directory and run:
62
+
63
+ ```
64
+ /warhorn:setup
65
+ ```
66
+
67
+ The interactive wizard walks you through picking your hooks, sound type, voice, and personality.
68
+
69
+ ## How It Works
70
+
71
+ **1. Pick your hooks** — Choose which Claude Code events should play sounds (task complete, permission needed, errors, etc.)
72
+
73
+ **2. Pick your sound type** — Instrumental chimes, AI voice lines, or both
74
+
75
+ **3. Pick your voice** — If using voice lines, choose a voice preset and personality tone (sarcastic, grumpy, enthusiastic, dramatic, or describe your own)
76
+
77
+ **4. Generate** — Claude Code writes custom voice lines and generates WAV files via edge-tts. Done.
78
+
79
+ ```
80
+ ~/.claude/warhorn/
81
+ ├── sounds/
82
+ │ ├── Stop/ ← random .wav plays when Claude finishes
83
+ │ │ ├── chime.wav
84
+ │ │ └── voice_1.wav
85
+ │ ├── PostToolUseFailure/ ← plays when a tool fails
86
+ │ ├── PermissionRequest/ ← plays when Claude needs approval
87
+ │ └── ... ← 14 hook folders total
88
+ └── scripts/
89
+ ├── play-sound.sh ← picks random file, plays it
90
+ └── generate_voices.py ← generates WAVs via edge-tts
91
+ ```
92
+
93
+ ## Commands
94
+
95
+ Once installed, you have access to these commands in Claude Code:
96
+
97
+ - `/warhorn:setup` — Interactive setup wizard — pick hooks, sound type, voice character
98
+ - `/warhorn:mute` — Mute all sounds
99
+ - `/warhorn:unmute` — Unmute sounds
100
+
101
+ ## Voice Presets
102
+
103
+ | Preset | Voice |
104
+ |--------|-------|
105
+ | `male_deep` | Australian male, deep and gruff |
106
+ | `male_mid` | British male, mid-pitch (default) |
107
+ | `female_mid` | British female, mid-pitch |
108
+ | `female_high` | American female, bright and high |
109
+
110
+ ### Personality tones
111
+
112
+ | Tone | Vibe | Example |
113
+ |------|------|---------|
114
+ | **sarcastic** | Makes fun of you | "Done. You're welcome." |
115
+ | **grumpy** | Hates everything | "Done. Go away." |
116
+ | **enthusiastic** | Annoyingly excited | "WOOHOO! Done!" |
117
+ | **informational** | Just the facts | "Response complete." |
118
+ | **dramatic** | Over the top epic | "The quest is complete!" |
119
+
120
+ You can also describe a **custom** personality during `/warhorn:setup` and Claude will write voice lines for it.
121
+
122
+ ## Supported Hooks
123
+
124
+ | Hook | Default sound | When it fires |
125
+ |------|:---:|-------------|
126
+ | `Stop` | chime, success, bell | Claude finishes responding |
127
+ | `SessionStart` | boot, ready | Session begins |
128
+ | `SessionEnd` | shutdown | Session ends |
129
+ | `PostToolUseFailure` | buzz, sad, wonk | Tool fails |
130
+ | `PermissionRequest` | alert, doorbell | Claude needs approval |
131
+ | `Notification` | ping, bubble | Claude sends notification |
132
+ | `SubagentStart` | spawn | Subagent spawns |
133
+ | `SubagentStop` | return | Subagent finishes |
134
+ | `TaskCompleted` | victory | Task marked complete |
135
+ | `PreCompact` | warning | Context compaction starting |
136
+ | `UserPromptSubmit` | tick, tap | You submit a prompt |
137
+ | `PreToolUse` | _(empty)_ | Before every tool call |
138
+ | `PostToolUse` | _(empty)_ | After every tool call |
139
+ | `TeammateIdle` | _(empty)_ | Team member idles |
140
+
141
+ ## Add Your Own Sounds
142
+
143
+ Drop any audio file into a hook folder:
144
+
145
+ ```bash
146
+ cp ~/my-sound.wav ~/.claude/warhorn/sounds/Stop/
147
+ ```
148
+
149
+ Supported formats: `.wav` (recommended), `.mp3`, `.aiff`, `.ogg`
150
+
151
+ ## Requirements
152
+
153
+ - **macOS** — Works out of the box (`afplay` is built in)
154
+ - **Linux** — Needs `pulseaudio` or `alsa-utils` for sounds
155
+ - **Voice lines** (optional) — `pip install edge-tts` + internet
156
+
157
+ ## Uninstall
158
+
159
+ ```bash
160
+ npx warhorn uninstall
161
+ ```
162
+
163
+ Cleanly removes sounds, commands, and hooks from `~/.claude/settings.json`.
164
+
165
+ ## License
166
+
167
+ MIT © [Alex Levadski](https://github.com/alevadski)
168
+
169
+ ---
170
+
171
+ <div align="center">
172
+
173
+ **Congrats, your Claude Code just got voice!**
174
+
175
+ [Report Bug](https://github.com/alexlevadski/warhorn/issues) · [Request Feature](https://github.com/alexlevadski/warhorn/issues)
176
+
177
+ </div>
@@ -0,0 +1,14 @@
1
+ ---
2
+ name: warhorn:mute
3
+ description: Mute all warhorn sounds
4
+ allowed-tools:
5
+ - Bash
6
+ ---
7
+
8
+ Mute all warhorn sounds. Run this command:
9
+
10
+ ```bash
11
+ touch "$HOME/.claude/warhorn/.muted"
12
+ ```
13
+
14
+ Confirm: "Sounds muted. Run `/warhorn:unmute` to re-enable."
@@ -0,0 +1,314 @@
1
+ ---
2
+ name: warhorn:setup
3
+ description: Configure warhorn sound notifications
4
+ allowed-tools:
5
+ - AskUserQuestion
6
+ - Bash
7
+ ---
8
+
9
+ <objective>
10
+ Configure warhorn sound notifications. Interview the user about their preferences, then apply the configuration.
11
+ NEVER modify, overwrite, or recreate ANY files in ~/.claude/warhorn/scripts/. Those scripts are pre-installed. Only CALL them.
12
+ </objective>
13
+
14
+ <process>
15
+
16
+ ## Step 1: Greet and Ask About Hooks
17
+
18
+ Display:
19
+ ```
20
+ Let's configure warhorn! I'll ask a few quick questions.
21
+ ```
22
+
23
+ Use the AskUserQuestion tool:
24
+
25
+ ```json
26
+ {
27
+ "questions": [
28
+ {
29
+ "question": "Which hooks should play sounds?",
30
+ "header": "Hooks",
31
+ "multiSelect": true,
32
+ "options": [
33
+ {
34
+ "label": "Core (recommended)",
35
+ "description": "Stop, PermissionRequest, PostToolUseFailure — the essentials"
36
+ },
37
+ {
38
+ "label": "Session",
39
+ "description": "SessionStart, SessionEnd — know when sessions begin/end"
40
+ },
41
+ {
42
+ "label": "Notifications",
43
+ "description": "Notification, TaskCompleted — important alerts"
44
+ },
45
+ {
46
+ "label": "Subagents",
47
+ "description": "SubagentStart, SubagentStop — track agent activity"
48
+ }
49
+ ]
50
+ }
51
+ ]
52
+ }
53
+ ```
54
+
55
+ Wait for response using AskUserQuestion. If they select "Other" and type custom hooks, parse the hook names from their text.
56
+
57
+ Map selections to hook names:
58
+ - Core = Stop, PermissionRequest, PostToolUseFailure
59
+ - Session = SessionStart, SessionEnd
60
+ - Notifications = Notification, TaskCompleted
61
+ - Subagents = SubagentStart, SubagentStop
62
+ - If they mention "all" or "everything" = all 14 hooks
63
+ - If they mention "granular" = PreToolUse, PostToolUse, UserPromptSubmit, TeammateIdle, PreCompact
64
+
65
+ ## Step 2: Ask About Sound Type
66
+
67
+ Use the AskUserQuestion tool:
68
+
69
+ ```json
70
+ {
71
+ "questions": [
72
+ {
73
+ "question": "How should sounds work?",
74
+ "header": "Sound type",
75
+ "multiSelect": false,
76
+ "options": [
77
+ {
78
+ "label": "Instrumental only (recommended)",
79
+ "description": "Bundled chimes and pings. Works right away, no setup needed."
80
+ },
81
+ {
82
+ "label": "AI voice lines",
83
+ "description": "Character voice via edge-tts (needs pip install edge-tts + internet)"
84
+ },
85
+ {
86
+ "label": "Both",
87
+ "description": "Instrumentals + voice lines mixed in each folder"
88
+ }
89
+ ]
90
+ }
91
+ ]
92
+ }
93
+ ```
94
+
95
+ Wait for response using AskUserQuestion.
96
+
97
+ - If "Instrumental only" → skip to Step 5 (Apply).
98
+ - If "AI voice lines" or "Both" → continue to Step 3.
99
+
100
+ ## Step 3: Ask About Voice Character (only if voice or both)
101
+
102
+ Use the AskUserQuestion tool:
103
+
104
+ ```json
105
+ {
106
+ "questions": [
107
+ {
108
+ "question": "Pick a voice:",
109
+ "header": "Voice",
110
+ "multiSelect": false,
111
+ "options": [
112
+ {
113
+ "label": "Male mid-range",
114
+ "description": "British male, mid-pitch. Versatile default."
115
+ },
116
+ {
117
+ "label": "Male deep",
118
+ "description": "Australian male, deep and gruff."
119
+ },
120
+ {
121
+ "label": "Female mid-range",
122
+ "description": "British female, mid-pitch."
123
+ },
124
+ {
125
+ "label": "Female high",
126
+ "description": "American female, bright and high."
127
+ }
128
+ ]
129
+ },
130
+ {
131
+ "question": "Pick a personality tone:",
132
+ "header": "Tone",
133
+ "multiSelect": false,
134
+ "options": [
135
+ {
136
+ "label": "sarcastic",
137
+ "description": "Makes fun of you — \"Done. You're welcome.\""
138
+ },
139
+ {
140
+ "label": "grumpy",
141
+ "description": "Hates everything — \"Done. Go away.\""
142
+ },
143
+ {
144
+ "label": "enthusiastic",
145
+ "description": "Annoyingly excited — \"WOOHOO! Done!\""
146
+ },
147
+ {
148
+ "label": "dramatic",
149
+ "description": "Over the top epic — \"The quest is complete!\""
150
+ }
151
+ ]
152
+ }
153
+ ]
154
+ }
155
+ ```
156
+
157
+ Wait for response using AskUserQuestion. If they select "Other" for tone and type a custom description, save it for voice line generation.
158
+
159
+ Map voice selection to preset name:
160
+ - "Male mid-range" → `male_mid`
161
+ - "Male deep" → `male_deep`
162
+ - "Female mid-range" → `female_mid`
163
+ - "Female high" → `female_high`
164
+
165
+ ## Step 4: Ask How Many Variations (only if voice or both)
166
+
167
+ Use the AskUserQuestion tool:
168
+
169
+ ```json
170
+ {
171
+ "questions": [
172
+ {
173
+ "question": "How many voice line variations per hook?",
174
+ "header": "Variations",
175
+ "multiSelect": false,
176
+ "options": [
177
+ {
178
+ "label": "3 (recommended)",
179
+ "description": "Quick to generate, good variety"
180
+ },
181
+ {
182
+ "label": "5",
183
+ "description": "More variety, takes a bit longer"
184
+ },
185
+ {
186
+ "label": "8",
187
+ "description": "Maximum variety, slower generation"
188
+ }
189
+ ]
190
+ }
191
+ ]
192
+ }
193
+ ```
194
+
195
+ Wait for response using AskUserQuestion. Parse the number from their selection.
196
+
197
+ ## Step 5: Apply Configuration
198
+
199
+ Say: "Applying your configuration..."
200
+
201
+ ### 5a. Update hooks in settings.json
202
+
203
+ ```bash
204
+ python3 -c "
205
+ import json, os
206
+
207
+ settings_path = os.path.expanduser('~/.claude/settings.json')
208
+ script = os.path.expanduser('~/.claude/warhorn/scripts/play-sound.sh')
209
+
210
+ try:
211
+ settings = json.load(open(settings_path))
212
+ except:
213
+ settings = {}
214
+
215
+ if 'hooks' not in settings:
216
+ settings['hooks'] = {}
217
+
218
+ all_hooks = ['SessionStart','UserPromptSubmit','PreToolUse','PermissionRequest','PostToolUse','PostToolUseFailure','Notification','SubagentStart','SubagentStop','Stop','TeammateIdle','TaskCompleted','PreCompact','SessionEnd']
219
+
220
+ enabled = [ENABLED_HOOKS_LIST]
221
+
222
+ for hook in all_hooks:
223
+ entry = {'hooks': [{'type': 'command', 'command': f'{script} {hook}'}]}
224
+ if hook in enabled:
225
+ if hook not in settings['hooks']:
226
+ settings['hooks'][hook] = [entry]
227
+ else:
228
+ has_zz = any(h.get('hooks') and any('warhorn' in (i.get('command','')) for i in h['hooks']) for h in settings['hooks'][hook])
229
+ if not has_zz:
230
+ settings['hooks'][hook].append(entry)
231
+ else:
232
+ if hook in settings['hooks']:
233
+ settings['hooks'][hook] = [h for h in settings['hooks'][hook] if not (h.get('hooks') and any('warhorn' in (i.get('command','')) for i in h['hooks']))]
234
+ if not settings['hooks'][hook]:
235
+ del settings['hooks'][hook]
236
+
237
+ if not settings['hooks']:
238
+ del settings['hooks']
239
+
240
+ with open(settings_path, 'w') as f:
241
+ json.dump(settings, f, indent=2)
242
+ f.write('\n')
243
+
244
+ print(f'Hooks updated: {len(enabled)} enabled, {len(all_hooks) - len(enabled)} disabled')
245
+ "
246
+ ```
247
+
248
+ Replace `[ENABLED_HOOKS_LIST]` with the user's choices, e.g.: `'Stop', 'PermissionRequest', 'PostToolUseFailure'`
249
+
250
+ ### 5b. Generate voice lines (only if voice or both)
251
+
252
+ Install edge-tts:
253
+ ```bash
254
+ pip install edge-tts 2>/dev/null || pip3 install edge-tts 2>/dev/null || pip install --user edge-tts
255
+ ```
256
+
257
+ **YOU must write the voice lines.** Generate a JSON file with short, punchy lines — **maximum 4 words each**. Write at least N+2 lines per enabled hook (where N is the variation count the user chose), so the generator has enough to pick from randomly.
258
+
259
+ The lines must match the chosen tone. Examples for "sarcastic" tone:
260
+ - Stop: "Done. You're welcome.", "Finished. Hold applause."
261
+ - PermissionRequest: "Approve me. Chop chop.", "Permission. Now, boss."
262
+ - PostToolUseFailure: "Broke. Shocking.", "Not my fault."
263
+
264
+ Write the JSON and generate:
265
+
266
+ ```bash
267
+ WARHORN_ROOT="$HOME/.claude/warhorn"
268
+ cat > "$WARHORN_ROOT/custom_lines.json" << 'LINES_EOF'
269
+ {
270
+ "Stop": ["line1", "line2", "line3", ...],
271
+ "PermissionRequest": ["line1", "line2", ...],
272
+ ...
273
+ }
274
+ LINES_EOF
275
+ python3 "$WARHORN_ROOT/scripts/generate_voices.py" \
276
+ --hooks "<comma-separated enabled hooks>" \
277
+ --preset <chosen_preset> \
278
+ --lines-file "$WARHORN_ROOT/custom_lines.json" \
279
+ --count <variation_count>
280
+ ```
281
+
282
+ Use the ALREADY INSTALLED generate_voices.py. Do NOT create a new script.
283
+
284
+ If voice generation fails, tell the user and keep instrumentals.
285
+
286
+ If voice-only (no instrumentals), remove bundled sounds:
287
+ ```bash
288
+ find "$WARHORN_ROOT/sounds" -maxdepth 2 -type f \( -name "*.wav" -o -name "*.mp3" \) ! -name "voice_*" -delete
289
+ ```
290
+
291
+ ### 5c. Unmute and test
292
+
293
+ ```bash
294
+ rm -f "$HOME/.claude/warhorn/.muted"
295
+ "$HOME/.claude/warhorn/scripts/play-sound.sh" Stop
296
+ ```
297
+
298
+ ### 5d. Summary
299
+
300
+ Tell the user what was configured and remind them about `/warhorn:mute` and `/warhorn:unmute`.
301
+
302
+ </process>
303
+
304
+ <critical_rules>
305
+ 1. ONE question at a time — never ask multiple questions in same message
306
+ 2. WAIT for answers — don't proceed until they respond
307
+ 3. Use AskUserQuestion tool — for every question with predefined options
308
+ 4. NEVER modify files in ~/.claude/warhorn/scripts/ — only CALL them
309
+ 5. Always write voice lines yourself as JSON — never use --tone flag, always use --lines-file
310
+ 6. Voice lines must be maximum 4 words each
311
+ 7. If edge-tts fails → suggest pip3 install --break-system-packages edge-tts
312
+ 8. If voice generation fails → keep instrumentals, tell user to retry later
313
+ 9. If any script errors → tell user to reinstall with npx warhorn, do NOT rewrite scripts
314
+ </critical_rules>
@@ -0,0 +1,14 @@
1
+ ---
2
+ name: warhorn:unmute
3
+ description: Unmute all warhorn sounds
4
+ allowed-tools:
5
+ - Bash
6
+ ---
7
+
8
+ Unmute all warhorn sounds. Run this command:
9
+
10
+ ```bash
11
+ rm -f "$HOME/.claude/warhorn/.muted"
12
+ ```
13
+
14
+ Confirm: "Sounds unmuted. The horn sounds!"