claude-code-sounds 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.
package/README.md ADDED
@@ -0,0 +1,273 @@
1
+ <div align="center">
2
+
3
+ <img src="images/peon.png" width="280" alt="WC3 Orc Peon" />
4
+
5
+ # claude-code-sounds
6
+
7
+ **Sound themes for [Claude Code](https://docs.anthropic.com/en/docs/claude-code) lifecycle hooks.**
8
+
9
+ Plays sound effects when sessions start, prompts are submitted, responses finish, errors occur, and more.
10
+
11
+ Ships with a **WC3 Orc Peon** theme. Bring your own sounds or create new themes.
12
+
13
+ *"Something need doing?"*
14
+
15
+ </div>
16
+
17
+ ## Quick Start
18
+
19
+ ```bash
20
+ npx claude-code-sounds
21
+ ```
22
+
23
+ That's it. Requires macOS (uses `afplay`) and Node.js 16+.
24
+
25
+ <details>
26
+ <summary>Alternative: install from source</summary>
27
+
28
+ ```bash
29
+ git clone https://github.com/ryparker/claude-code-sounds.git
30
+ cd claude-code-sounds
31
+ ./install.sh
32
+ ```
33
+
34
+ The bash installer requires `jq` (`brew install jq`).
35
+
36
+ </details>
37
+
38
+ ## Usage
39
+
40
+ ```bash
41
+ npx claude-code-sounds # Install default theme (wc3-peon)
42
+ npx claude-code-sounds <theme> # Install a specific theme
43
+ npx claude-code-sounds --list # List available themes
44
+ npx claude-code-sounds --uninstall # Remove all sounds and hooks
45
+ ```
46
+
47
+ ## WC3 Orc Peon Theme
48
+
49
+ 55 sounds from Warcraft 3 Orc units mapped across 11 Claude Code lifecycle events.
50
+
51
+ > After installing, preview all sounds with `./preview.sh` or a specific category with `./preview.sh start`
52
+
53
+ <details open>
54
+ <summary><b>start</b> — Session starting, being summoned (5 sounds)</summary>
55
+
56
+ | Sound | Quote | Unit |
57
+ |---|---|---|
58
+ | `ready-to-work.wav` | *"Ready to work!"* | Peon |
59
+ | `something-need-doing.wav` | *"Something need doing?"* | Peon |
60
+ | `more-work.mp3` | *"More work?"* | Peon (WC2) |
61
+ | `how-can-i-help.wav` | *"How can I help?"* | Shaman |
62
+ | `someone-call-for-the-doctor.wav` | *"Someone call for the doctor?"* | Witch Doctor |
63
+
64
+ </details>
65
+
66
+ <details>
67
+ <summary><b>prompt</b> — User submitted a prompt, acknowledging order (7 sounds)</summary>
68
+
69
+ | Sound | Quote | Unit |
70
+ |---|---|---|
71
+ | `yes.wav` | *"Yes"* | Peon |
72
+ | `dabu.wav` | *"Dabu"* | Grunt |
73
+ | `zug-zug.wav` | *"Zug zug"* | Grunt |
74
+ | `right-away.wav` | *"Right away"* | Shaman |
75
+ | `immediately.wav` | *"Immediately!"* | Tauren |
76
+ | `anything-you-want.wav` | *"Anything you want"* | Headhunter |
77
+ | `more-work.mp3` | *"More work?"* | Peon (WC2) |
78
+
79
+ </details>
80
+
81
+ <details>
82
+ <summary><b>stop</b> — Claude finished responding (8 sounds)</summary>
83
+
84
+ | Sound | Quote | Unit |
85
+ |---|---|---|
86
+ | `zug-zug.wav` | *"Zug zug"* | Peon |
87
+ | `ok.wav` | *"OK"* | Peon |
88
+ | `i-can-do-that.wav` | *"I can do that"* | Peon |
89
+ | `be-happy-to.wav` | *"Be happy to"* | Peon |
90
+ | `understood.wav` | *"Understood"* | Shaman |
91
+ | `of-course.wav` | *"Of course"* | Far Seer |
92
+ | `it-is-certain.wav` | *"It is certain"* | Far Seer |
93
+ | `whatever-you-say.wav` | *"Whatever you say"* | Grom Hellscream |
94
+
95
+ </details>
96
+
97
+ <details>
98
+ <summary><b>permission</b> — Permission prompt, waiting for approval (5 sounds)</summary>
99
+
100
+ | Sound | Quote | Unit |
101
+ |---|---|---|
102
+ | `hmmm.wav` | *"Hmmm?"* | Peon |
103
+ | `what-you-want.wav` | *"What you want?"* | Peon |
104
+ | `what-you-want-me-to-do.wav` | *"What you want me to do?"* | Headhunter |
105
+ | `who-you-want-me-kill.wav` | *"Who you want me kill?"* | Headhunter |
106
+ | `you-seek-me-help.wav` | *"You seek me help?"* | Witch Doctor |
107
+
108
+ </details>
109
+
110
+ <details>
111
+ <summary><b>subagent</b> — Spawning a subagent (6 sounds)</summary>
112
+
113
+ | Sound | Quote | Unit |
114
+ |---|---|---|
115
+ | `work-work.wav` | *"Work, work"* | Peon |
116
+ | `zug-zug.wav` | *"Zug zug"* | Peon |
117
+ | `ill-try.wav` | *"I'll try"* | Peon |
118
+ | `why-not.wav` | *"Why not?"* | Peon |
119
+ | `for-the-horde.wav` | *"For the Horde!"* | Grunt |
120
+ | `taste-the-fury.wav` | *"Taste the fury of the Warsong!"* | Grom Hellscream |
121
+
122
+ </details>
123
+
124
+ <details>
125
+ <summary><b>idle</b> — Waiting for user input (7 sounds)</summary>
126
+
127
+ | Sound | Quote | Unit |
128
+ |---|---|---|
129
+ | `me-busy-leave-me-alone.wav` | *"Me busy, leave me alone!"* | Peon |
130
+ | `no-time-for-play.wav` | *"No time for play!"* | Peon |
131
+ | `me-not-that-kind-of-orc.wav` | *"Me not that kind of orc!"* | Peon |
132
+ | `why-you-poking-me.wav` | *"Why are you poking me again?"* | Grunt |
133
+ | `not-easy-being-green.wav` | *"It's not easy being green"* | Grunt |
134
+ | `i-can-wait-no-longer.wav` | *"I can wait no longer!"* | Grom Hellscream |
135
+ | `outlook-not-so-good.wav` | *"Outlook... not so good"* | Far Seer |
136
+
137
+ </details>
138
+
139
+ <details>
140
+ <summary><b>error</b> — Tool call failed (4 sounds)</summary>
141
+
142
+ | Sound | Quote | Unit |
143
+ |---|---|---|
144
+ | `peon-death.wav` | *(death sound)* | Peon |
145
+ | `grunt-death.wav` | *(death sound)* | Grunt |
146
+ | `headhunter-death.wav` | *(death sound)* | Headhunter |
147
+ | `reply-hazy-try-again.wav` | *"Reply hazy, try again"* | Far Seer |
148
+
149
+ </details>
150
+
151
+ <details>
152
+ <summary><b>end</b> — Session ending (3 sounds)</summary>
153
+
154
+ | Sound | Quote | Unit |
155
+ |---|---|---|
156
+ | `well-done.wav` | *"Well done!"* | Tauren |
157
+ | `finally.wav` | *"Finally!"* | Grom Hellscream |
158
+ | `okie-dokie.wav` | *"Okie dokie"* | Peon |
159
+
160
+ </details>
161
+
162
+ <details>
163
+ <summary><b>task-completed</b> — Task marked done (2 sounds)</summary>
164
+
165
+ | Sound | Quote | Unit |
166
+ |---|---|---|
167
+ | `well-done.wav` | *"Well done!"* | Tauren |
168
+ | `finally.wav` | *"Finally!"* | Grom Hellscream |
169
+
170
+ </details>
171
+
172
+ <details>
173
+ <summary><b>compact</b> — Context compaction, memory fading (4 sounds)</summary>
174
+
175
+ | Sound | Quote | Unit |
176
+ |---|---|---|
177
+ | `concentrate-and-ask-again.wav` | *"Concentrate... and ask again"* | Far Seer |
178
+ | `reply-hazy-try-again.wav` | *"Reply hazy, try again"* | Far Seer |
179
+ | `i-can-wait-no-longer.wav` | *"I can wait no longer!"* | Grom Hellscream |
180
+ | `death.wav` | *(death sound)* | Peon |
181
+
182
+ </details>
183
+
184
+ <details>
185
+ <summary><b>teammate-idle</b> — Teammate went idle (4 sounds)</summary>
186
+
187
+ | Sound | Quote | Unit |
188
+ |---|---|---|
189
+ | `what.wav` | *"What?!"* | Peon |
190
+ | `me-busy-leave-me-alone.wav` | *"Me busy, leave me alone!"* | Peon |
191
+ | `no-time-for-play.wav` | *"No time for play!"* | Peon |
192
+ | `i-can-wait-no-longer.wav` | *"I can wait no longer!"* | Grom Hellscream |
193
+
194
+ </details>
195
+
196
+ ## Hook Events
197
+
198
+ | Event | Hook | When |
199
+ |---|---|---|
200
+ | `start` | `SessionStart` | Session begins |
201
+ | `end` | `SessionEnd` | Session ends |
202
+ | `prompt` | `UserPromptSubmit` | You submit a prompt |
203
+ | `stop` | `Stop` | Claude finishes responding |
204
+ | `permission` | `Notification` | Permission prompt appears |
205
+ | `idle` | `Notification` | Waiting for your input |
206
+ | `subagent` | `SubagentStart` | Subagent spawned |
207
+ | `error` | `PostToolUseFailure` | Tool call failed |
208
+ | `task-completed` | `TaskCompleted` | Task marked done |
209
+ | `compact` | `PreCompact` | Context compaction |
210
+ | `teammate-idle` | `TeammateIdle` | Teammate went idle |
211
+
212
+ ## Creating a Theme
213
+
214
+ Themes live in `themes/<name>/` with two files:
215
+
216
+ ### `theme.json`
217
+
218
+ Defines metadata and maps source files to hook categories:
219
+
220
+ ```json
221
+ {
222
+ "name": "My Theme",
223
+ "description": "A short description",
224
+ "sounds": {
225
+ "start": {
226
+ "description": "Session starting",
227
+ "files": [
228
+ { "src": "path/to/file.wav", "name": "descriptive-name.wav" }
229
+ ]
230
+ }
231
+ }
232
+ }
233
+ ```
234
+
235
+ ### `download.sh`
236
+
237
+ Downloads the sound files. Receives two arguments:
238
+ - `$1` — target sounds directory (`~/.claude/sounds`)
239
+ - `$2` — temp directory for downloads
240
+
241
+ The script should download and extract files so they're accessible at `$2/Orc/<src path>` (matching the `src` values in `theme.json`).
242
+
243
+ ## How It Works
244
+
245
+ A single script (`~/.claude/hooks/play-sound.sh`) handles all events. It takes a category name as an argument, picks a random `.wav` or `.mp3` from `~/.claude/sounds/<category>/`, and plays it with `afplay`.
246
+
247
+ Hooks are configured in `~/.claude/settings.json` — each Claude Code lifecycle event calls the script with the appropriate category.
248
+
249
+ ## Customizing
250
+
251
+ Drop any `.wav` or `.mp3` into the sound directories to add your own clips:
252
+
253
+ ```
254
+ ~/.claude/sounds/
255
+ ├── start/ # add files here for session start
256
+ ├── stop/ # add files here for response complete
257
+ ├── error/ # add files here for failures
258
+ └── ...
259
+ ```
260
+
261
+ The script picks randomly from whatever files are in each directory.
262
+
263
+ ## Uninstalling
264
+
265
+ ```bash
266
+ npx claude-code-sounds --uninstall
267
+ ```
268
+
269
+ This removes all sound files, the hook script, and the hooks config from `settings.json`.
270
+
271
+ ## Disclaimer
272
+
273
+ Sound files are downloaded from third-party sources at install time and are not included in this repository. All game audio is property of Blizzard Entertainment.
package/bin/cli.js ADDED
@@ -0,0 +1,262 @@
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require("fs");
4
+ const path = require("path");
5
+ const os = require("os");
6
+ const { execSync } = require("child_process");
7
+
8
+ // ─── Paths ───────────────────────────────────────────────────────────────────
9
+
10
+ const PKG_DIR = path.resolve(__dirname, "..");
11
+ const CLAUDE_DIR = path.join(os.homedir(), ".claude");
12
+ const SOUNDS_DIR = path.join(CLAUDE_DIR, "sounds");
13
+ const HOOKS_DIR = path.join(CLAUDE_DIR, "hooks");
14
+ const SETTINGS_PATH = path.join(CLAUDE_DIR, "settings.json");
15
+ const THEMES_DIR = path.join(PKG_DIR, "themes");
16
+
17
+ // ─── Helpers ─────────────────────────────────────────────────────────────────
18
+
19
+ function print(msg = "") {
20
+ console.log(msg);
21
+ }
22
+
23
+ function die(msg) {
24
+ console.error(`Error: ${msg}`);
25
+ process.exit(1);
26
+ }
27
+
28
+ function mkdirp(dir) {
29
+ fs.mkdirSync(dir, { recursive: true });
30
+ }
31
+
32
+ function exec(cmd, opts = {}) {
33
+ return execSync(cmd, { encoding: "utf-8", stdio: "pipe", ...opts });
34
+ }
35
+
36
+ function listThemes() {
37
+ const themes = [];
38
+ for (const name of fs.readdirSync(THEMES_DIR)) {
39
+ const themeJson = path.join(THEMES_DIR, name, "theme.json");
40
+ if (!fs.existsSync(themeJson)) continue;
41
+ const meta = JSON.parse(fs.readFileSync(themeJson, "utf-8"));
42
+ themes.push({ name, description: meta.description || "", display: meta.name || name });
43
+ }
44
+ return themes;
45
+ }
46
+
47
+ function readSettings() {
48
+ if (fs.existsSync(SETTINGS_PATH)) {
49
+ return JSON.parse(fs.readFileSync(SETTINGS_PATH, "utf-8"));
50
+ }
51
+ return {};
52
+ }
53
+
54
+ function writeSettings(settings) {
55
+ mkdirp(CLAUDE_DIR);
56
+ fs.writeFileSync(SETTINGS_PATH, JSON.stringify(settings, null, 2) + "\n");
57
+ }
58
+
59
+ // ─── Hooks Config ────────────────────────────────────────────────────────────
60
+
61
+ const HOOKS_CONFIG = {
62
+ SessionStart: [{ matcher: "startup", hooks: [{ type: "command", command: '/bin/bash "$HOME/.claude/hooks/play-sound.sh" start', timeout: 5 }] }],
63
+ SessionEnd: [{ hooks: [{ type: "command", command: '/bin/bash "$HOME/.claude/hooks/play-sound.sh" end', timeout: 5 }] }],
64
+ Notification: [
65
+ { matcher: "permission_prompt", hooks: [{ type: "command", command: '/bin/bash "$HOME/.claude/hooks/play-sound.sh" permission', timeout: 5 }] },
66
+ { matcher: "idle_prompt", hooks: [{ type: "command", command: '/bin/bash "$HOME/.claude/hooks/play-sound.sh" idle', timeout: 5 }] },
67
+ ],
68
+ Stop: [{ hooks: [{ type: "command", command: '/bin/bash "$HOME/.claude/hooks/play-sound.sh" stop', timeout: 5 }] }],
69
+ SubagentStart: [{ hooks: [{ type: "command", command: '/bin/bash "$HOME/.claude/hooks/play-sound.sh" subagent', timeout: 5 }] }],
70
+ PostToolUseFailure: [{ hooks: [{ type: "command", command: '/bin/bash "$HOME/.claude/hooks/play-sound.sh" error', timeout: 5 }] }],
71
+ UserPromptSubmit: [{ hooks: [{ type: "command", command: '/bin/bash "$HOME/.claude/hooks/play-sound.sh" prompt', timeout: 5 }] }],
72
+ TaskCompleted: [{ hooks: [{ type: "command", command: '/bin/bash "$HOME/.claude/hooks/play-sound.sh" task-completed', timeout: 5 }] }],
73
+ PreCompact: [{ hooks: [{ type: "command", command: '/bin/bash "$HOME/.claude/hooks/play-sound.sh" compact', timeout: 5 }] }],
74
+ TeammateIdle: [{ hooks: [{ type: "command", command: '/bin/bash "$HOME/.claude/hooks/play-sound.sh" teammate-idle', timeout: 5 }] }],
75
+ };
76
+
77
+ // ─── Commands ────────────────────────────────────────────────────────────────
78
+
79
+ function showHelp() {
80
+ print("");
81
+ print(" claude-code-sounds");
82
+ print(" ──────────────────────────────");
83
+ print("");
84
+ print(" Usage:");
85
+ print(" npx claude-code-sounds Install default theme (wc3-peon)");
86
+ print(" npx claude-code-sounds <theme> Install a specific theme");
87
+ print(" npx claude-code-sounds --list List available themes");
88
+ print(" npx claude-code-sounds --uninstall Remove all sounds and hooks");
89
+ print(" npx claude-code-sounds --help Show this help");
90
+ print("");
91
+ }
92
+
93
+ function showList() {
94
+ print("");
95
+ print(" Available themes:");
96
+ print("");
97
+ for (const t of listThemes()) {
98
+ print(` ${t.name} — ${t.description}`);
99
+ }
100
+ print("");
101
+ }
102
+
103
+ function uninstall() {
104
+ print("");
105
+ print(" Uninstalling claude-code-sounds...");
106
+
107
+ if (fs.existsSync(SOUNDS_DIR)) {
108
+ fs.rmSync(SOUNDS_DIR, { recursive: true });
109
+ print(" Removed ~/.claude/sounds/");
110
+ }
111
+
112
+ const hookScript = path.join(HOOKS_DIR, "play-sound.sh");
113
+ if (fs.existsSync(hookScript)) {
114
+ fs.unlinkSync(hookScript);
115
+ print(" Removed ~/.claude/hooks/play-sound.sh");
116
+ }
117
+
118
+ if (fs.existsSync(SETTINGS_PATH)) {
119
+ const settings = readSettings();
120
+ delete settings.hooks;
121
+ writeSettings(settings);
122
+ print(" Removed hooks from settings.json");
123
+ }
124
+
125
+ print("");
126
+ print(" Done. All sounds removed.");
127
+ print("");
128
+ }
129
+
130
+ function install(themeName) {
131
+ const themeDir = path.join(THEMES_DIR, themeName);
132
+ const themeJsonPath = path.join(themeDir, "theme.json");
133
+
134
+ if (!fs.existsSync(themeJsonPath)) {
135
+ die(`Theme '${themeName}' not found.\n\nAvailable themes:\n${listThemes().map((t) => ` ${t.name} — ${t.description}`).join("\n")}`);
136
+ }
137
+
138
+ const theme = JSON.parse(fs.readFileSync(themeJsonPath, "utf-8"));
139
+ const categories = Object.keys(theme.sounds);
140
+
141
+ // Preflight
142
+ try {
143
+ exec("which afplay");
144
+ } catch {
145
+ die("afplay not found. This tool requires macOS.");
146
+ }
147
+
148
+ print("");
149
+ print(" claude-code-sounds");
150
+ print(" ──────────────────────────────");
151
+ print(` Theme: ${theme.name}`);
152
+ print("");
153
+
154
+ // 1. Create directories
155
+ print(" [1/4] Creating directories...");
156
+ for (const cat of categories) {
157
+ mkdirp(path.join(SOUNDS_DIR, cat));
158
+ }
159
+ mkdirp(HOOKS_DIR);
160
+
161
+ // 2. Download sounds
162
+ print(" [2/4] Downloading sounds...");
163
+ const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "claude-sounds-"));
164
+
165
+ try {
166
+ const downloadScript = path.join(themeDir, "download.sh");
167
+ if (fs.existsSync(downloadScript)) {
168
+ exec(`bash "${downloadScript}" "${SOUNDS_DIR}" "${tmpDir}"`, { stdio: "inherit" });
169
+ }
170
+
171
+ // 3. Sort sounds
172
+ print(" [3/4] Sorting sounds...");
173
+
174
+ // Clear existing sounds
175
+ for (const cat of categories) {
176
+ const catDir = path.join(SOUNDS_DIR, cat);
177
+ for (const f of fs.readdirSync(catDir)) {
178
+ if (f.endsWith(".wav") || f.endsWith(".mp3")) {
179
+ fs.unlinkSync(path.join(catDir, f));
180
+ }
181
+ }
182
+ }
183
+
184
+ // Copy files based on theme.json
185
+ const srcBase = path.join(tmpDir, "Orc");
186
+ for (const [category, config] of Object.entries(theme.sounds)) {
187
+ for (const file of config.files) {
188
+ let srcFile;
189
+ if (file.src.startsWith("@soundfxcenter/")) {
190
+ srcFile = path.join(srcBase, path.basename(file.src));
191
+ } else {
192
+ srcFile = path.join(srcBase, file.src);
193
+ }
194
+
195
+ const destFile = path.join(SOUNDS_DIR, category, file.name);
196
+ if (fs.existsSync(srcFile)) {
197
+ fs.copyFileSync(srcFile, destFile);
198
+ } else {
199
+ print(` Warning: ${file.src} not found, skipping`);
200
+ }
201
+ }
202
+ }
203
+
204
+ // 4. Install hooks
205
+ print(" [4/4] Installing hooks...");
206
+
207
+ // Copy play-sound.sh
208
+ const hookSrc = path.join(PKG_DIR, "hooks", "play-sound.sh");
209
+ const hookDest = path.join(HOOKS_DIR, "play-sound.sh");
210
+ fs.copyFileSync(hookSrc, hookDest);
211
+ fs.chmodSync(hookDest, 0o755);
212
+
213
+ // Merge hooks into settings.json
214
+ const settings = readSettings();
215
+ settings.hooks = HOOKS_CONFIG;
216
+ writeSettings(settings);
217
+
218
+ // Summary
219
+ print("");
220
+ print(" Installed! Here's what you'll hear:");
221
+ print(" ─────────────────────────────────────");
222
+
223
+ let total = 0;
224
+ for (const [cat, config] of Object.entries(theme.sounds)) {
225
+ const count = config.files.length;
226
+ total += count;
227
+ print(` ${cat} (${count}) — ${config.description}`);
228
+ }
229
+
230
+ print("");
231
+ print(` ${total} sound files across ${categories.length} events.`);
232
+ print(" Start a new Claude Code session to hear it!");
233
+ print("");
234
+ print(" Zug zug.");
235
+ print("");
236
+ } finally {
237
+ // Cleanup
238
+ fs.rmSync(tmpDir, { recursive: true, force: true });
239
+ }
240
+ }
241
+
242
+ // ─── Main ────────────────────────────────────────────────────────────────────
243
+
244
+ const args = process.argv.slice(2);
245
+ const arg = args[0] || "wc3-peon";
246
+
247
+ switch (arg) {
248
+ case "--help":
249
+ case "-h":
250
+ showHelp();
251
+ break;
252
+ case "--list":
253
+ case "-l":
254
+ showList();
255
+ break;
256
+ case "--uninstall":
257
+ case "--remove":
258
+ uninstall();
259
+ break;
260
+ default:
261
+ install(arg);
262
+ }
@@ -0,0 +1,23 @@
1
+ #!/bin/bash
2
+ SOUNDS_DIR="$HOME/.claude/sounds"
3
+ CATEGORY="${1:-}"
4
+
5
+ # Drain stdin so the hook system doesn't get a broken pipe
6
+ cat > /dev/null 2>&1
7
+
8
+ [[ -z "$CATEGORY" ]] && exit 0
9
+ DIR="$SOUNDS_DIR/$CATEGORY"
10
+ [[ ! -d "$DIR" ]] && exit 0
11
+
12
+ # Collect all .wav and .mp3 files
13
+ FILES=()
14
+ for f in "$DIR"/*.wav "$DIR"/*.mp3; do
15
+ [[ -f "$f" ]] && FILES+=("$f")
16
+ done
17
+ [[ ${#FILES[@]} -eq 0 ]] && exit 0
18
+
19
+ # Pick a random file and play it in the background
20
+ RANDOM_FILE="${FILES[$RANDOM % ${#FILES[@]}]}"
21
+ afplay "$RANDOM_FILE" &
22
+
23
+ exit 0
Binary file
Binary file
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "claude-code-sounds",
3
+ "version": "1.0.0",
4
+ "description": "Sound themes for Claude Code lifecycle hooks",
5
+ "bin": {
6
+ "claude-code-sounds": "bin/cli.js"
7
+ },
8
+ "keywords": [
9
+ "claude",
10
+ "claude-code",
11
+ "sounds",
12
+ "hooks",
13
+ "warcraft",
14
+ "peon",
15
+ "soundboard"
16
+ ],
17
+ "author": "ryparker",
18
+ "license": "MIT",
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "git+https://github.com/ryparker/claude-code-sounds.git"
22
+ },
23
+ "files": [
24
+ "bin/",
25
+ "hooks/",
26
+ "themes/",
27
+ "images/"
28
+ ],
29
+ "engines": {
30
+ "node": ">=16"
31
+ }
32
+ }
@@ -0,0 +1,30 @@
1
+ #!/bin/bash
2
+ #
3
+ # Downloads sound files for the wc3-peon theme.
4
+ # Called by install.sh with $1 = target sounds directory, $2 = temp directory.
5
+ #
6
+ set -e
7
+
8
+ SOUNDS_DIR="$1"
9
+ TMP_DIR="$2"
10
+
11
+ echo " Downloading WC3 Orc sounds..."
12
+ ZIP="$TMP_DIR/orc-sounds.zip"
13
+ curl -sL -o "$ZIP" "https://sounds.spriters-resource.com/media/assets/422/425494.zip?updated=1755544622"
14
+
15
+ if ! file "$ZIP" | grep -q "Zip"; then
16
+ echo " Error: Failed to download WC3 sound pack."
17
+ return 1
18
+ fi
19
+
20
+ unzip -qo "$ZIP" \
21
+ "Orc/Peon/*" "Orc/Grunt/*" "Orc/HeadHunter/*" "Orc/Shaman/*" \
22
+ "Orc/Hellscream/*" "Orc/HeroFarseer/*" "Orc/Tauren/*" "Orc/WitchDoctor/*" \
23
+ -d "$TMP_DIR"
24
+
25
+ # Download supplemental clips not in the zip
26
+ echo " Downloading supplemental clips..."
27
+ curl -sL -o "$TMP_DIR/Orc/more-work.mp3" \
28
+ "https://soundfxcenter.com/video-games/warcraft-2/8d82b5_Warcraft_2_Peasant_More_Work_Sound_Effect.mp3"
29
+
30
+ echo " Download complete."
@@ -0,0 +1,117 @@
1
+ {
2
+ "name": "WC3 Orc Peon",
3
+ "description": "Warcraft 3 Orc unit soundbites — Peons, Grunts, Shamans, and more",
4
+ "author": "ryparker",
5
+ "sounds": {
6
+ "start": {
7
+ "description": "Session starting — being summoned",
8
+ "files": [
9
+ { "src": "Peon/PeonReady1.wav", "name": "ready-to-work.wav" },
10
+ { "src": "Peon/PeonWhat4.wav", "name": "something-need-doing.wav" },
11
+ { "src": "Shaman/ShamanWhat3.wav", "name": "how-can-i-help.wav" },
12
+ { "src": "WitchDoctor/WitchDoctorReady1.wav", "name": "someone-call-for-the-doctor.wav" },
13
+ { "src": "@soundfxcenter/more-work.mp3", "name": "more-work.mp3" }
14
+ ]
15
+ },
16
+ "end": {
17
+ "description": "Session over — farewell",
18
+ "files": [
19
+ { "src": "Tauren/TaurenYes3.wav", "name": "well-done.wav" },
20
+ { "src": "Hellscream/GromYes3.wav", "name": "finally.wav" },
21
+ { "src": "Peon/PeonYes4.wav", "name": "okie-dokie.wav" }
22
+ ]
23
+ },
24
+ "permission": {
25
+ "description": "Permission prompt — what do you want to approve?",
26
+ "files": [
27
+ { "src": "Peon/PeonWhat2.wav", "name": "hmmm.wav" },
28
+ { "src": "Peon/PeonWhat3.wav", "name": "what-you-want.wav" },
29
+ { "src": "HeadHunter/HeadHunterWhat3.wav", "name": "what-you-want-me-to-do.wav" },
30
+ { "src": "HeadHunter/HeadHunterWhat1.wav", "name": "who-you-want-me-kill.wav" },
31
+ { "src": "WitchDoctor/WitchDoctorWhat3.wav", "name": "you-seek-me-help.wav" }
32
+ ]
33
+ },
34
+ "stop": {
35
+ "description": "Done responding — acknowledgment",
36
+ "files": [
37
+ { "src": "Peon/PeonYesAttack2.wav", "name": "zug-zug.wav" },
38
+ { "src": "Peon/PeonYesAttack1.wav", "name": "ok.wav" },
39
+ { "src": "Peon/PeonYes1.wav", "name": "i-can-do-that.wav" },
40
+ { "src": "Peon/PeonYes2.wav", "name": "be-happy-to.wav" },
41
+ { "src": "Shaman/ShamanYes2.wav", "name": "understood.wav" },
42
+ { "src": "HeroFarseer/HeroFarseerYes2.wav", "name": "of-course.wav" },
43
+ { "src": "HeroFarseer/HeroFarseerYes4.wav", "name": "it-is-certain.wav" },
44
+ { "src": "Hellscream/GromYes2.wav", "name": "whatever-you-say.wav" }
45
+ ]
46
+ },
47
+ "subagent": {
48
+ "description": "Spawning subagent — dispatching",
49
+ "files": [
50
+ { "src": "Peon/PeonYes3.wav", "name": "work-work.wav" },
51
+ { "src": "Grunt/GruntWarcry1.wav", "name": "for-the-horde.wav" },
52
+ { "src": "Hellscream/GromWarcry1.wav", "name": "taste-the-fury.wav" },
53
+ { "src": "Peon/PeonYesAttack3.wav", "name": "ill-try.wav" },
54
+ { "src": "Peon/PeonWarcry1.wav", "name": "why-not.wav" },
55
+ { "src": "Peon/PeonYesAttack2.wav", "name": "zug-zug.wav" }
56
+ ]
57
+ },
58
+ "idle": {
59
+ "description": "Waiting for input — bored and annoyed",
60
+ "files": [
61
+ { "src": "Peon/PeonAngry2.wav", "name": "me-busy-leave-me-alone.wav" },
62
+ { "src": "Peon/PeonAngry3.wav", "name": "no-time-for-play.wav" },
63
+ { "src": "Peon/PeonAngry4.wav", "name": "me-not-that-kind-of-orc.wav" },
64
+ { "src": "Grunt/GruntAngry1.wav", "name": "why-you-poking-me.wav" },
65
+ { "src": "Grunt/GruntAngry7.wav", "name": "not-easy-being-green.wav" },
66
+ { "src": "Hellscream/GromWhat3.wav", "name": "i-can-wait-no-longer.wav" },
67
+ { "src": "HeroFarseer/HeroFarseerAngry4.wav", "name": "outlook-not-so-good.wav" }
68
+ ]
69
+ },
70
+ "error": {
71
+ "description": "Tool failure — something broke",
72
+ "files": [
73
+ { "src": "Peon/PeonDeath.wav", "name": "peon-death.wav" },
74
+ { "src": "Grunt/GruntDeath.wav", "name": "grunt-death.wav" },
75
+ { "src": "HeadHunter/HeadHunterDeath.wav", "name": "headhunter-death.wav" },
76
+ { "src": "HeroFarseer/HeroFarseerAngry5.wav", "name": "reply-hazy-try-again.wav" }
77
+ ]
78
+ },
79
+ "prompt": {
80
+ "description": "User submitted prompt — acknowledging order",
81
+ "files": [
82
+ { "src": "Peon/PeonWhat1.wav", "name": "yes.wav" },
83
+ { "src": "Grunt/GruntYes1.wav", "name": "dabu.wav" },
84
+ { "src": "Grunt/GruntYes4.wav", "name": "zug-zug.wav" },
85
+ { "src": "Shaman/ShamanYes4.wav", "name": "right-away.wav" },
86
+ { "src": "Tauren/TaurenYes2.wav", "name": "immediately.wav" },
87
+ { "src": "HeadHunter/HeadHunterYes2.wav", "name": "anything-you-want.wav" },
88
+ { "src": "@soundfxcenter/more-work.mp3", "name": "more-work.mp3" }
89
+ ]
90
+ },
91
+ "task-completed": {
92
+ "description": "Task finished — victory",
93
+ "files": [
94
+ { "src": "Tauren/TaurenYes3.wav", "name": "well-done.wav" },
95
+ { "src": "Hellscream/GromYes3.wav", "name": "finally.wav" }
96
+ ]
97
+ },
98
+ "compact": {
99
+ "description": "Context compaction — memory fading",
100
+ "files": [
101
+ { "src": "HeroFarseer/HeroFarseerAngry3.wav", "name": "concentrate-and-ask-again.wav" },
102
+ { "src": "HeroFarseer/HeroFarseerAngry5.wav", "name": "reply-hazy-try-again.wav" },
103
+ { "src": "Peon/PeonDeath.wav", "name": "death.wav" },
104
+ { "src": "Hellscream/GromWhat3.wav", "name": "i-can-wait-no-longer.wav" }
105
+ ]
106
+ },
107
+ "teammate-idle": {
108
+ "description": "Teammate went idle — impatient",
109
+ "files": [
110
+ { "src": "Peon/PeonAngry1.wav", "name": "what.wav" },
111
+ { "src": "Peon/PeonAngry2.wav", "name": "me-busy-leave-me-alone.wav" },
112
+ { "src": "Peon/PeonAngry3.wav", "name": "no-time-for-play.wav" },
113
+ { "src": "Hellscream/GromWhat3.wav", "name": "i-can-wait-no-longer.wav" }
114
+ ]
115
+ }
116
+ }
117
+ }