klaudio 0.9.1 → 0.9.2

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": "klaudio",
3
- "version": "0.9.1",
3
+ "version": "0.9.2",
4
4
  "description": "Add sound effects to your coding sessions — play sounds when tasks complete, notifications arrive, and more",
5
5
  "type": "module",
6
6
  "bin": {
package/src/installer.js CHANGED
@@ -60,6 +60,12 @@ export async function install({ scope, sounds, tts = false }) {
60
60
  const event = EVENTS[eventId];
61
61
  if (!event) continue;
62
62
 
63
+ // Approval event uses a PreToolUse/PostToolUse timer instead of a direct hook
64
+ if (eventId === "approval") {
65
+ await installApprovalHooks(settings, soundPath, claudeDir);
66
+ continue;
67
+ }
68
+
63
69
  const hookEvent = event.hookEvent;
64
70
  // Enable TTS only for the "stop" event (task complete)
65
71
  const useTts = tts && eventId === "stop";
@@ -101,6 +107,64 @@ export async function install({ scope, sounds, tts = false }) {
101
107
  };
102
108
  }
103
109
 
110
+ /**
111
+ * Install approval notification hooks (PreToolUse/PostToolUse timer).
112
+ * Writes a helper script and hooks that play a sound after 15s if no approval.
113
+ */
114
+ async function installApprovalHooks(settings, soundPath, claudeDir) {
115
+ const normalized = soundPath.replace(/\\/g, "/");
116
+ const scriptPath = join(claudeDir, "approval-notify.sh").replace(/\\/g, "/");
117
+
118
+ // Write the timer script
119
+ const script = `#!/usr/bin/env bash
120
+ # klaudio: approval notification timer
121
+ # Plays a sound if a tool isn't approved within DELAY seconds.
122
+ DELAY=15
123
+ MARKER="/tmp/.claude-approval-pending"
124
+ SOUND="${normalized}"
125
+
126
+ case "$1" in
127
+ start)
128
+ TOKEN="$$-$(date +%s%N)"
129
+ echo "$TOKEN" > "$MARKER"
130
+ (
131
+ sleep "$DELAY"
132
+ if [ -f "$MARKER" ] && [ "$(cat "$MARKER" 2>/dev/null)" = "$TOKEN" ]; then
133
+ rm -f "$MARKER"
134
+ npx klaudio play "$SOUND" 2>/dev/null
135
+ fi
136
+ ) &
137
+ ;;
138
+ cancel)
139
+ rm -f "$MARKER"
140
+ ;;
141
+ esac
142
+ `;
143
+ await writeFile(scriptPath, script, "utf-8");
144
+
145
+ // Add PreToolUse hook
146
+ if (!settings.hooks.PreToolUse) settings.hooks.PreToolUse = [];
147
+ settings.hooks.PreToolUse = settings.hooks.PreToolUse.filter(
148
+ (e) => !e._klaudio && !e._klonk
149
+ );
150
+ settings.hooks.PreToolUse.push({
151
+ _klaudio: true,
152
+ matcher: "",
153
+ hooks: [{ type: "command", command: `bash "${scriptPath}" start` }],
154
+ });
155
+
156
+ // Add PostToolUse hook
157
+ if (!settings.hooks.PostToolUse) settings.hooks.PostToolUse = [];
158
+ settings.hooks.PostToolUse = settings.hooks.PostToolUse.filter(
159
+ (e) => !e._klaudio && !e._klonk
160
+ );
161
+ settings.hooks.PostToolUse.push({
162
+ _klaudio: true,
163
+ matcher: "",
164
+ hooks: [{ type: "command", command: `bash "${scriptPath}" cancel` }],
165
+ });
166
+ }
167
+
104
168
  /**
105
169
  * Install hooks for GitHub Copilot coding agent.
106
170
  * Writes .github/hooks/klaudio.json in the Copilot format.
@@ -168,9 +232,23 @@ export async function getExistingSounds(scope) {
168
232
  if (!settings.hooks) return sounds;
169
233
 
170
234
  for (const [eventId, event] of Object.entries(EVENTS)) {
235
+ // Approval event: read sound from the approval-notify.sh script
236
+ if (eventId === "approval") {
237
+ const scriptPath = join(claudeDir, "approval-notify.sh");
238
+ try {
239
+ const script = await readFile(scriptPath, "utf-8");
240
+ const m = script.match(/SOUND="([^"]+\.(wav|mp3|ogg|flac|aac))"/);
241
+ if (m) {
242
+ sounds[eventId] = m[1].replace(/\//g, join("a", "b").includes("\\") ? "\\" : "/");
243
+ }
244
+ } catch { /* no script */ }
245
+ continue;
246
+ }
247
+
171
248
  const hookEntries = settings.hooks[event.hookEvent];
172
249
  if (!hookEntries) continue;
173
- const entry = hookEntries.find((e) => e._klaudio || e._klonk);
250
+ const entry = hookEntries.find((e) => e._klaudio || e._klonk
251
+ || e.hooks?.[0]?.command?.includes("klaudio"));
174
252
  if (!entry?.hooks?.[0]?.command) continue;
175
253
 
176
254
  // Extract file path from the play command
package/src/presets.js CHANGED
@@ -14,7 +14,7 @@ const SOUNDS_DIR = isCompiledBinary
14
14
  export const EVENTS = {
15
15
  notification: {
16
16
  name: "Notification",
17
- description: "Plays when Claude needs your attention",
17
+ description: "Plays when Claude sends a notification (e.g. long task finished in background)",
18
18
  hookEvent: "Notification",
19
19
  copilotHookEvent: null, // Copilot doesn't have this yet
20
20
  },
@@ -24,6 +24,12 @@ export const EVENTS = {
24
24
  hookEvent: "Stop",
25
25
  copilotHookEvent: "sessionEnd",
26
26
  },
27
+ approval: {
28
+ name: "Waiting for Approval",
29
+ description: "Plays when Claude waits for you to approve an action",
30
+ hookEvent: null, // Uses PreToolUse/PostToolUse timer, not a direct hook
31
+ copilotHookEvent: null,
32
+ },
27
33
  };
28
34
 
29
35
  /**
@@ -37,6 +43,7 @@ export const PRESETS = {
37
43
  sounds: {
38
44
  stop: join(SOUNDS_DIR, "retro-8bit", "stop.wav"),
39
45
  notification: join(SOUNDS_DIR, "retro-8bit", "notification.wav"),
46
+ approval: join(SOUNDS_DIR, "retro-8bit", "notification.wav"),
40
47
  },
41
48
  },
42
49
  "minimal-zen": {
@@ -46,6 +53,7 @@ export const PRESETS = {
46
53
  sounds: {
47
54
  stop: join(SOUNDS_DIR, "minimal-zen", "stop.wav"),
48
55
  notification: join(SOUNDS_DIR, "minimal-zen", "notification.wav"),
56
+ approval: join(SOUNDS_DIR, "minimal-zen", "notification.wav"),
49
57
  },
50
58
  },
51
59
  "sci-fi-terminal": {
@@ -55,6 +63,7 @@ export const PRESETS = {
55
63
  sounds: {
56
64
  stop: join(SOUNDS_DIR, "sci-fi-terminal", "stop.wav"),
57
65
  notification: join(SOUNDS_DIR, "sci-fi-terminal", "notification.wav"),
66
+ approval: join(SOUNDS_DIR, "sci-fi-terminal", "notification.wav"),
58
67
  },
59
68
  },
60
69
  "victory-fanfare": {
@@ -64,6 +73,7 @@ export const PRESETS = {
64
73
  sounds: {
65
74
  stop: join(SOUNDS_DIR, "victory-fanfare", "stop.wav"),
66
75
  notification: join(SOUNDS_DIR, "victory-fanfare", "notification.wav"),
76
+ approval: join(SOUNDS_DIR, "victory-fanfare", "notification.wav"),
67
77
  },
68
78
  },
69
79
  };