klaudio 0.2.0-c2cee62 → 0.4.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 +8 -6
- package/package.json +2 -2
- package/src/cache.js +1 -1
- package/src/cli.js +10 -5
- package/src/extractor.js +1 -1
- package/src/installer.js +7 -7
- package/src/player.js +1 -1
package/README.md
CHANGED
|
@@ -11,8 +11,8 @@ npx klaudio
|
|
|
11
11
|
The interactive installer walks you through:
|
|
12
12
|
|
|
13
13
|
1. **Choose scope** — install globally (`~/.claude`) or per-project (`.claude/`)
|
|
14
|
-
2. **Pick a source** — use a built-in preset, scan your
|
|
15
|
-
3. **Preview & assign** — listen to sounds and assign them to events
|
|
14
|
+
2. **Pick a source** — use a built-in preset, scan your Steam & Epic Games library for sounds, or provide custom files
|
|
15
|
+
3. **Preview & assign** — listen to sounds and assign them to events (tab to switch between events)
|
|
16
16
|
4. **Install** — writes Claude Code hooks to your `settings.json`
|
|
17
17
|
|
|
18
18
|
## Sound Sources
|
|
@@ -23,13 +23,14 @@ Ready-made sound packs (Retro 8-bit, Minimal Zen, Sci-Fi Terminal, Victory Fanfa
|
|
|
23
23
|
|
|
24
24
|
### Game Sound Scanner
|
|
25
25
|
|
|
26
|
-
Scans your Steam and Epic Games libraries for audio files:
|
|
26
|
+
Scans your local Steam and Epic Games libraries for audio files:
|
|
27
27
|
|
|
28
28
|
- Finds loose audio files (`.wav`, `.mp3`, `.ogg`, `.flac`, `.aac`)
|
|
29
29
|
- Extracts packed audio (Wwise `.wem`, FMOD `.bank`, `.fsb`) using [vgmstream](https://vgmstream.org/) (downloaded automatically)
|
|
30
|
+
- Extracts Unity game audio from `.resource` files (PCM decoded directly, Vorbis converted via vgmstream)
|
|
30
31
|
- Parses Wwise metadata (`SoundbanksInfo.json`) for descriptive filenames
|
|
31
32
|
- Categorizes sounds (voice, ambient, music, SFX, UI, creature) for easy browsing
|
|
32
|
-
- Caches extracted sounds in `~/.
|
|
33
|
+
- Caches extracted sounds in `~/.klaudio/cache/` for instant reuse
|
|
33
34
|
|
|
34
35
|
### Custom Files
|
|
35
36
|
|
|
@@ -38,18 +39,19 @@ Point to your own `.wav`/`.mp3` files.
|
|
|
38
39
|
## Features
|
|
39
40
|
|
|
40
41
|
- **Auto-preview** — sounds play automatically as you browse the list (toggle with `p`)
|
|
42
|
+
- **Multi-game selection** — pick sounds from different games, tab between events
|
|
41
43
|
- **Category filtering** — drill into voice, ambient, SFX, etc. when a game has enough variety
|
|
42
44
|
- **Type-to-filter** — start typing to narrow down long lists
|
|
43
45
|
- **10-second clamp** — long sounds are processed with ffmpeg: silence stripped, fade out baked in
|
|
44
46
|
- **Background scanning** — game list updates live as directories are scanned
|
|
45
|
-
- **
|
|
47
|
+
- **Pre-loads existing config** — re-running the installer shows your current sound selections
|
|
46
48
|
|
|
47
49
|
## Events
|
|
48
50
|
|
|
49
51
|
| Event | Triggers when |
|
|
50
52
|
|---|---|
|
|
51
|
-
| Task Complete | Claude finishes a response |
|
|
52
53
|
| Notification | Claude needs your attention |
|
|
54
|
+
| Task Complete | Claude finishes a response |
|
|
53
55
|
|
|
54
56
|
## Uninstall
|
|
55
57
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "klaudio",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
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": {
|
|
@@ -41,4 +41,4 @@
|
|
|
41
41
|
"type": "git",
|
|
42
42
|
"url": "https://github.com/needle-tools/klaudio.git"
|
|
43
43
|
}
|
|
44
|
-
}
|
|
44
|
+
}
|
package/src/cache.js
CHANGED
|
@@ -2,7 +2,7 @@ import { readdir, mkdir, readFile, writeFile, stat, copyFile } from "node:fs/pro
|
|
|
2
2
|
import { join, extname, basename, dirname } from "node:path";
|
|
3
3
|
import { homedir } from "node:os";
|
|
4
4
|
|
|
5
|
-
const CACHE_DIR = join(homedir(), ".
|
|
5
|
+
const CACHE_DIR = join(homedir(), ".klaudio", "cache");
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Category keywords to match against folder names, filenames, and Wwise event names.
|
package/src/cli.js
CHANGED
|
@@ -599,11 +599,16 @@ const GameSoundsScreen = ({ game, sounds, onSelectSound, onDone, onBack }) => {
|
|
|
599
599
|
...eventIds.map((eid, i) => {
|
|
600
600
|
const assigned = sounds[eid];
|
|
601
601
|
const isCurrent = i === currentEvent;
|
|
602
|
+
const truncName = assigned ? basename(assigned).slice(0, 20) : null;
|
|
603
|
+
const prefix = isCurrent ? "▸" : assigned ? "✓" : "·";
|
|
604
|
+
const label = truncName
|
|
605
|
+
? `${prefix} ${EVENTS[eid].name}: ${truncName}`
|
|
606
|
+
: `${prefix} ${EVENTS[eid].name}`;
|
|
602
607
|
return h(Text, {
|
|
603
608
|
key: eid,
|
|
604
609
|
bold: isCurrent,
|
|
605
610
|
color: isCurrent ? ACCENT : assigned ? "green" : "gray",
|
|
606
|
-
},
|
|
611
|
+
}, label);
|
|
607
612
|
}),
|
|
608
613
|
h(Text, {
|
|
609
614
|
key: "_done",
|
|
@@ -798,7 +803,7 @@ const ExtractingScreen = ({ game, onDone, onBack }) => {
|
|
|
798
803
|
return;
|
|
799
804
|
}
|
|
800
805
|
|
|
801
|
-
const outputDir = join(tmpdir(), "
|
|
806
|
+
const outputDir = join(tmpdir(), "klaudio-extract", game.name.replace(/[^a-zA-Z0-9]/g, "_"));
|
|
802
807
|
const allOutputs = [];
|
|
803
808
|
|
|
804
809
|
// Unity .resource files — extract FSB5 banks directly (no vgmstream needed for PCM16)
|
|
@@ -990,7 +995,7 @@ const DoneScreen = ({ result }) => {
|
|
|
990
995
|
),
|
|
991
996
|
),
|
|
992
997
|
h(Box, { marginTop: 1 },
|
|
993
|
-
h(Text, { dimColor: true }, " To remove: npx
|
|
998
|
+
h(Text, { dimColor: true }, " To remove: npx klaudio --uninstall"),
|
|
994
999
|
),
|
|
995
1000
|
);
|
|
996
1001
|
};
|
|
@@ -1032,13 +1037,13 @@ const UninstallApp = () => {
|
|
|
1032
1037
|
if (phase === "done") {
|
|
1033
1038
|
return h(Box, { flexDirection: "column" },
|
|
1034
1039
|
h(Header, null),
|
|
1035
|
-
h(Text, { color: "green", marginLeft: 2 }, " ✓
|
|
1040
|
+
h(Text, { color: "green", marginLeft: 2 }, " ✓ Klaudio hooks removed."),
|
|
1036
1041
|
);
|
|
1037
1042
|
}
|
|
1038
1043
|
|
|
1039
1044
|
return h(Box, { flexDirection: "column" },
|
|
1040
1045
|
h(Header, null),
|
|
1041
|
-
h(Text, { color: "yellow", marginLeft: 2 }, " No
|
|
1046
|
+
h(Text, { color: "yellow", marginLeft: 2 }, " No Klaudio configuration found."),
|
|
1042
1047
|
);
|
|
1043
1048
|
};
|
|
1044
1049
|
|
package/src/extractor.js
CHANGED
|
@@ -5,7 +5,7 @@ import { join, extname, basename, resolve as resolvePath } from "node:path";
|
|
|
5
5
|
import { platform, homedir, tmpdir } from "node:os";
|
|
6
6
|
import { pipeline } from "node:stream/promises";
|
|
7
7
|
|
|
8
|
-
const TOOLS_DIR = join(homedir(), ".
|
|
8
|
+
const TOOLS_DIR = join(homedir(), ".klaudio", "tools");
|
|
9
9
|
|
|
10
10
|
// Packed audio formats that vgmstream-cli can convert to WAV
|
|
11
11
|
const PACKED_EXTENSIONS = new Set([".wem", ".bnk", ".bank", ".fsb", ".pck"]);
|
package/src/installer.js
CHANGED
|
@@ -62,19 +62,19 @@ export async function install({ scope, sounds }) {
|
|
|
62
62
|
const hookEvent = event.hookEvent;
|
|
63
63
|
const playCommand = getHookPlayCommand(soundPath);
|
|
64
64
|
|
|
65
|
-
// Check if there's already a
|
|
65
|
+
// Check if there's already a klaudio hook for this event
|
|
66
66
|
if (!settings.hooks[hookEvent]) {
|
|
67
67
|
settings.hooks[hookEvent] = [];
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
-
// Remove any existing
|
|
70
|
+
// Remove any existing klaudio entries
|
|
71
71
|
settings.hooks[hookEvent] = settings.hooks[hookEvent].filter(
|
|
72
|
-
(entry) => !entry.
|
|
72
|
+
(entry) => !entry._klaudio
|
|
73
73
|
);
|
|
74
74
|
|
|
75
75
|
// Add our hook
|
|
76
76
|
settings.hooks[hookEvent].push({
|
|
77
|
-
|
|
77
|
+
_klaudio: true,
|
|
78
78
|
matcher: "",
|
|
79
79
|
hooks: [
|
|
80
80
|
{
|
|
@@ -112,7 +112,7 @@ export async function getExistingSounds(scope) {
|
|
|
112
112
|
for (const [eventId, event] of Object.entries(EVENTS)) {
|
|
113
113
|
const hookEntries = settings.hooks[event.hookEvent];
|
|
114
114
|
if (!hookEntries) continue;
|
|
115
|
-
const entry = hookEntries.find((e) => e.
|
|
115
|
+
const entry = hookEntries.find((e) => e._klaudio);
|
|
116
116
|
if (!entry?.hooks?.[0]?.command) continue;
|
|
117
117
|
|
|
118
118
|
// Extract file path from the play command
|
|
@@ -129,7 +129,7 @@ export async function getExistingSounds(scope) {
|
|
|
129
129
|
}
|
|
130
130
|
|
|
131
131
|
/**
|
|
132
|
-
* Uninstall
|
|
132
|
+
* Uninstall klaudio hooks from settings.
|
|
133
133
|
*/
|
|
134
134
|
export async function uninstall(scope) {
|
|
135
135
|
const claudeDir = getTargetDir(scope);
|
|
@@ -142,7 +142,7 @@ export async function uninstall(scope) {
|
|
|
142
142
|
if (settings.hooks) {
|
|
143
143
|
for (const [event, entries] of Object.entries(settings.hooks)) {
|
|
144
144
|
settings.hooks[event] = entries.filter(
|
|
145
|
-
(entry) => !entry.
|
|
145
|
+
(entry) => !entry._klaudio
|
|
146
146
|
);
|
|
147
147
|
if (settings.hooks[event].length === 0) {
|
|
148
148
|
delete settings.hooks[event];
|
package/src/player.js
CHANGED
|
@@ -301,7 +301,7 @@ export async function processSound(filePath) {
|
|
|
301
301
|
|
|
302
302
|
// Build a deterministic output path based on input file hash
|
|
303
303
|
const hash = createHash("md5").update(absPath).digest("hex").slice(0, 12);
|
|
304
|
-
const outDir = join(tmpdir(), "
|
|
304
|
+
const outDir = join(tmpdir(), "klaudio-processed");
|
|
305
305
|
const outName = `${basename(absPath, extname(absPath))}_${hash}.wav`;
|
|
306
306
|
const outPath = join(outDir, outName);
|
|
307
307
|
|