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 +1 -1
- package/src/installer.js +79 -1
- package/src/presets.js +11 -1
package/package.json
CHANGED
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
|
|
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
|
};
|