klaudio 0.13.1 → 0.13.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/bin/cli.js +21 -0
- package/package.json +1 -1
- package/src/cli.js +3 -2
- package/src/installer.js +62 -69
- package/src/player.js +1 -4
package/bin/cli.js
CHANGED
|
@@ -52,6 +52,27 @@ if (["help", "--help", "-h"].includes(process.argv[2])) {
|
|
|
52
52
|
process.exit(0);
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
+
// Auto-update: check npm for a newer version and install it before showing UI
|
|
56
|
+
async function autoUpdate() {
|
|
57
|
+
try {
|
|
58
|
+
const { createRequire } = await import("node:module");
|
|
59
|
+
const pkg = createRequire(import.meta.url)("../package.json");
|
|
60
|
+
const res = await fetch(`https://registry.npmjs.org/${pkg.name}/latest`, {
|
|
61
|
+
signal: AbortSignal.timeout(3000),
|
|
62
|
+
});
|
|
63
|
+
if (!res.ok) return;
|
|
64
|
+
const { version: latest } = await res.json();
|
|
65
|
+
if (latest === pkg.version) return;
|
|
66
|
+
console.log(`\n Updating klaudio ${pkg.version} → ${latest}...\n`);
|
|
67
|
+
const { spawnSync } = await import("node:child_process");
|
|
68
|
+
spawnSync("npm", ["install", "-g", "klaudio@latest"], { stdio: "inherit" });
|
|
69
|
+
// Re-exec so the UI runs under the new version
|
|
70
|
+
const result = spawnSync("npx", ["--yes", "klaudio"], { stdio: "inherit" });
|
|
71
|
+
process.exit(result.status ?? 0);
|
|
72
|
+
} catch { /* ignore network/registry errors */ }
|
|
73
|
+
}
|
|
74
|
+
await autoUpdate();
|
|
75
|
+
|
|
55
76
|
// Default: interactive installer UI
|
|
56
77
|
const { run } = await import("../src/cli.js");
|
|
57
78
|
|
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -294,7 +294,7 @@ const PresetScreen = ({ existingSounds, outdatedReasons, onNext, onReapply, onBa
|
|
|
294
294
|
|
|
295
295
|
// ── Screen: Preview ─────────────────────────────────────────────
|
|
296
296
|
const PreviewScreen = ({ presetId, sounds, onAccept, onBack, onUpdateSound }) => {
|
|
297
|
-
const preset = PRESETS[presetId];
|
|
297
|
+
const preset = PRESETS[presetId] ?? PRESETS[Object.keys(PRESETS)[0]];
|
|
298
298
|
const eventIds = Object.keys(EVENTS);
|
|
299
299
|
const [currentEvent, setCurrentEvent] = useState(0);
|
|
300
300
|
const [playing, setPlaying] = useState(false);
|
|
@@ -1808,7 +1808,8 @@ const InstallApp = () => {
|
|
|
1808
1808
|
onConfirm: () => setScreen(SCREEN.INSTALLING),
|
|
1809
1809
|
onBack: () => {
|
|
1810
1810
|
if (selectedGame) setScreen(SCREEN.GAME_SOUNDS);
|
|
1811
|
-
else setScreen(SCREEN.PREVIEW);
|
|
1811
|
+
else if (presetId) setScreen(SCREEN.PREVIEW);
|
|
1812
|
+
else setScreen(SCREEN.PRESET);
|
|
1812
1813
|
},
|
|
1813
1814
|
});
|
|
1814
1815
|
|
package/src/installer.js
CHANGED
|
@@ -1,13 +1,9 @@
|
|
|
1
1
|
import { readFile, writeFile, mkdir, copyFile } from "node:fs/promises";
|
|
2
2
|
import { join, basename, extname } from "node:path";
|
|
3
3
|
import { homedir } from "node:os";
|
|
4
|
-
import { createRequire } from "node:module";
|
|
5
4
|
import { getHookPlayCommand, processSound } from "./player.js";
|
|
6
5
|
import { EVENTS } from "./presets.js";
|
|
7
6
|
|
|
8
|
-
const _require = createRequire(import.meta.url);
|
|
9
|
-
const KLAUDIO_VERSION = _require("../package.json").version;
|
|
10
|
-
|
|
11
7
|
/**
|
|
12
8
|
* Get the target directory based on install scope.
|
|
13
9
|
*/
|
|
@@ -39,7 +35,10 @@ export async function install({ scope, sounds, tts = false, voice, speed } = {})
|
|
|
39
35
|
const installedSounds = {};
|
|
40
36
|
for (const [eventId, sourcePath] of Object.entries(sounds)) {
|
|
41
37
|
const processedPath = await processSound(sourcePath);
|
|
42
|
-
|
|
38
|
+
// Strip any accumulated eventId prefixes from previous installs
|
|
39
|
+
let srcName = basename(sourcePath, extname(sourcePath));
|
|
40
|
+
const prefixRe = new RegExp(`^(${Object.keys(EVENTS).join("|")})-`, "i");
|
|
41
|
+
while (prefixRe.test(srcName)) srcName = srcName.replace(prefixRe, "");
|
|
43
42
|
const outExt = extname(processedPath) || ".wav";
|
|
44
43
|
const fileName = `${eventId}-${srcName}${outExt}`;
|
|
45
44
|
const destPath = join(soundsDir, fileName);
|
|
@@ -88,8 +87,6 @@ export async function install({ scope, sounds, tts = false, voice, speed } = {})
|
|
|
88
87
|
|
|
89
88
|
// Add our hook
|
|
90
89
|
settings.hooks[hookEvent].push({
|
|
91
|
-
_klaudio: true,
|
|
92
|
-
_klaudioVersion: KLAUDIO_VERSION,
|
|
93
90
|
matcher: "",
|
|
94
91
|
hooks: [
|
|
95
92
|
{
|
|
@@ -119,39 +116,47 @@ export async function install({ scope, sounds, tts = false, voice, speed } = {})
|
|
|
119
116
|
*/
|
|
120
117
|
async function installApprovalHooks(settings, soundPath, claudeDir) {
|
|
121
118
|
const normalized = soundPath.replace(/\\/g, "/");
|
|
122
|
-
const scriptPath = join(claudeDir, "approval-notify.
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
119
|
+
const scriptPath = join(claudeDir, "klaudio-approval-notify.js").replace(/\\/g, "/");
|
|
120
|
+
|
|
121
|
+
// Write the timer script (CJS so it works outside any module context)
|
|
122
|
+
const script = `#!/usr/bin/env node
|
|
123
|
+
'use strict';
|
|
124
|
+
const { writeFileSync, readFileSync, unlinkSync } = require('node:fs');
|
|
125
|
+
const { tmpdir } = require('node:os');
|
|
126
|
+
const { join } = require('node:path');
|
|
127
|
+
const { spawn, spawnSync } = require('node:child_process');
|
|
128
|
+
|
|
129
|
+
const DELAY_MS = 120000;
|
|
130
|
+
const MARKER = join(tmpdir(), '.claude-approval-pending');
|
|
131
|
+
const SOUND = "${normalized}";
|
|
132
|
+
|
|
133
|
+
const action = process.argv[2];
|
|
134
|
+
|
|
135
|
+
if (action === 'start') {
|
|
136
|
+
const token = process.pid + '-' + Date.now();
|
|
137
|
+
writeFileSync(MARKER, token + '\\n' + process.cwd() + '\\n', 'utf-8');
|
|
138
|
+
const child = spawn(process.execPath, [__filename, 'timer', token], {
|
|
139
|
+
detached: true,
|
|
140
|
+
stdio: 'ignore',
|
|
141
|
+
});
|
|
142
|
+
child.unref();
|
|
143
|
+
} else if (action === 'cancel') {
|
|
144
|
+
try { unlinkSync(MARKER); } catch (_) {}
|
|
145
|
+
} else if (action === 'timer') {
|
|
146
|
+
const token = process.argv[3];
|
|
147
|
+
setTimeout(function () {
|
|
148
|
+
try {
|
|
149
|
+
const content = readFileSync(MARKER, 'utf-8');
|
|
150
|
+
const lines = content.trim().split('\\n');
|
|
151
|
+
if (lines[0] !== token) return;
|
|
152
|
+
unlinkSync(MARKER);
|
|
153
|
+
const project = (lines[1] || '').split(/[\\/\\\\]/).pop() || 'project';
|
|
154
|
+
spawnSync('npx', ['--yes', 'klaudio', 'play', SOUND], { stdio: 'ignore' });
|
|
155
|
+
spawnSync('npx', ['--yes', 'klaudio', 'notify', project, 'Waiting for your approval'], { stdio: 'ignore' });
|
|
156
|
+
spawnSync('npx', ['--yes', 'klaudio', 'say', project + ' needs your attention'], { stdio: 'ignore' });
|
|
157
|
+
} catch (_) {}
|
|
158
|
+
}, DELAY_MS);
|
|
159
|
+
}
|
|
155
160
|
`;
|
|
156
161
|
await writeFile(scriptPath, script, "utf-8");
|
|
157
162
|
|
|
@@ -161,9 +166,8 @@ esac
|
|
|
161
166
|
(e) => !e._klaudio && !e._klonk
|
|
162
167
|
);
|
|
163
168
|
settings.hooks.PreToolUse.push({
|
|
164
|
-
_klaudio: true,
|
|
165
169
|
matcher: "",
|
|
166
|
-
hooks: [{ type: "command", command: `
|
|
170
|
+
hooks: [{ type: "command", command: `node "${scriptPath}" start` }],
|
|
167
171
|
});
|
|
168
172
|
|
|
169
173
|
// Add PostToolUse hook
|
|
@@ -172,9 +176,8 @@ esac
|
|
|
172
176
|
(e) => !e._klaudio && !e._klonk
|
|
173
177
|
);
|
|
174
178
|
settings.hooks.PostToolUse.push({
|
|
175
|
-
_klaudio: true,
|
|
176
179
|
matcher: "",
|
|
177
|
-
hooks: [{ type: "command", command: `
|
|
180
|
+
hooks: [{ type: "command", command: `node "${scriptPath}" cancel` }],
|
|
178
181
|
});
|
|
179
182
|
}
|
|
180
183
|
|
|
@@ -218,7 +221,6 @@ async function installCopilotHooks(installedSounds, scope) {
|
|
|
218
221
|
);
|
|
219
222
|
|
|
220
223
|
config.hooks[event.copilotHookEvent].push({
|
|
221
|
-
_klaudio: true,
|
|
222
224
|
type: "command",
|
|
223
225
|
bash: bashCmd,
|
|
224
226
|
powershell: psCmd,
|
|
@@ -245,12 +247,12 @@ export async function getExistingSounds(scope) {
|
|
|
245
247
|
if (!settings.hooks) return sounds;
|
|
246
248
|
|
|
247
249
|
for (const [eventId, event] of Object.entries(EVENTS)) {
|
|
248
|
-
// Approval event: read sound from the approval-notify.
|
|
250
|
+
// Approval event: read sound from the klaudio-approval-notify.js script
|
|
249
251
|
if (eventId === "approval") {
|
|
250
|
-
const scriptPath = join(claudeDir, "approval-notify.
|
|
252
|
+
const scriptPath = join(claudeDir, "klaudio-approval-notify.js");
|
|
251
253
|
try {
|
|
252
254
|
const script = await readFile(scriptPath, "utf-8");
|
|
253
|
-
const m = script.match(/SOUND
|
|
255
|
+
const m = script.match(/SOUND\s*=\s*"([^"]+\.(wav|mp3|ogg|flac|aac))"/);
|
|
254
256
|
if (m) {
|
|
255
257
|
sounds[eventId] = m[1].replace(/\//g, join("a", "b").includes("\\") ? "\\" : "/");
|
|
256
258
|
}
|
|
@@ -294,18 +296,6 @@ export async function checkHooksOutdated(scope) {
|
|
|
294
296
|
|
|
295
297
|
const isOurs = (e) => e._klaudio || e._klonk || e.hooks?.[0]?.command?.includes("klaudio") || e.hooks?.[0]?.command?.includes(".claude/sounds/");
|
|
296
298
|
|
|
297
|
-
// Check if any klaudio hook was installed with an older version
|
|
298
|
-
const allKlaudioHooks = Object.values(settings.hooks).flat().filter(isOurs);
|
|
299
|
-
if (allKlaudioHooks.length > 0) {
|
|
300
|
-
const hasVersionMismatch = allKlaudioHooks.some((e) => e._klaudioVersion !== KLAUDIO_VERSION);
|
|
301
|
-
if (hasVersionMismatch) {
|
|
302
|
-
const installedVer = allKlaudioHooks.find((e) => e._klaudioVersion)?._klaudioVersion;
|
|
303
|
-
reasons.push(installedVer
|
|
304
|
-
? `Hooks from v${installedVer} → v${KLAUDIO_VERSION}`
|
|
305
|
-
: `Hooks need update to v${KLAUDIO_VERSION}`);
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
|
|
309
299
|
// Check Stop hook for --notify flag
|
|
310
300
|
const stopEntries = settings.hooks.Stop || [];
|
|
311
301
|
const stopHook = stopEntries.find(isOurs);
|
|
@@ -320,22 +310,25 @@ export async function checkHooksOutdated(scope) {
|
|
|
320
310
|
reasons.push("System notifications on background task");
|
|
321
311
|
}
|
|
322
312
|
|
|
323
|
-
// Check approval script
|
|
324
|
-
const
|
|
313
|
+
// Check approval script exists as the new Node.js version
|
|
314
|
+
const oldScriptPath = join(claudeDir, "approval-notify.sh");
|
|
315
|
+
const newScriptPath = join(claudeDir, "klaudio-approval-notify.js");
|
|
325
316
|
try {
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
317
|
+
await readFile(oldScriptPath, "utf-8");
|
|
318
|
+
// Old bash script still present — needs upgrade
|
|
319
|
+
reasons.push("Approval timer upgraded to cross-platform Node.js");
|
|
320
|
+
} catch { /* old script gone, good */ }
|
|
321
|
+
try {
|
|
322
|
+
const script = await readFile(newScriptPath, "utf-8");
|
|
323
|
+
const delayMatch = script.match(/DELAY_MS\s*=\s*(\d+)/);
|
|
324
|
+
if (delayMatch && parseInt(delayMatch[1]) < 120000) {
|
|
332
325
|
reasons.push("Approval timer too short (now 120s)");
|
|
333
326
|
}
|
|
334
327
|
} catch { /* no script */ }
|
|
335
328
|
|
|
336
329
|
// Check for duplicate hooks (old bug)
|
|
337
330
|
for (const [event, entries] of Object.entries(settings.hooks)) {
|
|
338
|
-
const klaudioEntries = entries.filter(
|
|
331
|
+
const klaudioEntries = entries.filter(isOurs);
|
|
339
332
|
if (klaudioEntries.length > 1) {
|
|
340
333
|
reasons.push(`Duplicate ${event} hooks`);
|
|
341
334
|
}
|
package/src/player.js
CHANGED
|
@@ -499,8 +499,5 @@ export function getHookPlayCommand(soundFilePath, { tts = false, voice, speed, n
|
|
|
499
499
|
const voiceFlag = tts && voice ? ` --voice ${voice}` : "";
|
|
500
500
|
const speedFlag = tts && speed && speed !== 1.0 ? ` --speed ${speed}` : "";
|
|
501
501
|
const notifyFlag = notify ? " --notify" : "";
|
|
502
|
-
|
|
503
|
-
// When klaudio is updated and re-installed, hooks are rewritten with the new path.
|
|
504
|
-
const cliPath = new URL("../bin/cli.js", import.meta.url).pathname;
|
|
505
|
-
return `node "${cliPath}" play "${normalized}"${ttsFlag}${voiceFlag}${speedFlag}${notifyFlag}`;
|
|
502
|
+
return `npx --yes klaudio play "${normalized}"${ttsFlag}${voiceFlag}${speedFlag}${notifyFlag}`;
|
|
506
503
|
}
|