klaudio 0.5.2 → 0.5.3
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/cache.js +21 -4
- package/src/cli.js +3 -3
- package/src/installer.js +73 -4
- package/src/presets.js +2 -0
- package/src/scumm.js +9 -2
package/package.json
CHANGED
package/src/cache.js
CHANGED
|
@@ -14,9 +14,9 @@ const CATEGORY_PATTERNS = {
|
|
|
14
14
|
"forest", "weather", "background_loop", "room_tone",
|
|
15
15
|
],
|
|
16
16
|
music: [
|
|
17
|
-
"music", "mus_", "soundtrack", "bgm", "score", "theme",
|
|
17
|
+
"music", "mus_", "/music/", "soundtrack", "bgm", "score", "theme",
|
|
18
18
|
"menu_music", "mainmenu", "background_music", "level_music",
|
|
19
|
-
"snippet",
|
|
19
|
+
"snippet", "musdisk",
|
|
20
20
|
],
|
|
21
21
|
sfx: [
|
|
22
22
|
"sfx", "effect", "impact", "explosion", "hit", "slash",
|
|
@@ -31,9 +31,9 @@ const CATEGORY_PATTERNS = {
|
|
|
31
31
|
"interface", "hud", "tab", "scroll",
|
|
32
32
|
],
|
|
33
33
|
voice: [
|
|
34
|
-
"voice", "vocal", "vox", "dialogue", "dialog", "speech",
|
|
34
|
+
"voice", "vocal", "vox", "/voice/", "dialogue", "dialog", "speech",
|
|
35
35
|
"narrat", "speak", "talk", "grunt", "shout", "scream",
|
|
36
|
-
"laugh", "cry", "cheer",
|
|
36
|
+
"laugh", "cry", "cheer", "voxdisk",
|
|
37
37
|
],
|
|
38
38
|
creature: [
|
|
39
39
|
"creature", "animal", "monster", "enemy", "npc",
|
|
@@ -42,6 +42,15 @@ const CATEGORY_PATTERNS = {
|
|
|
42
42
|
],
|
|
43
43
|
};
|
|
44
44
|
|
|
45
|
+
/**
|
|
46
|
+
* SCUMM BUN filename prefix patterns.
|
|
47
|
+
* These are checked before generic patterns for files from BUN extraction.
|
|
48
|
+
*/
|
|
49
|
+
const SCUMM_PREFIX_PATTERNS = {
|
|
50
|
+
voice: [/^ad[a-z]/i, /^adgt/i, /^adso/i],
|
|
51
|
+
music: [/^\d{4}-[a-z]_/i],
|
|
52
|
+
};
|
|
53
|
+
|
|
45
54
|
/**
|
|
46
55
|
* Infer a category from a filename, its parent folder path, and optional metadata name.
|
|
47
56
|
*/
|
|
@@ -50,6 +59,14 @@ export function inferCategory(filePath, metadataName) {
|
|
|
50
59
|
const dirPath = dirname(filePath).toLowerCase().replace(/\\/g, "/");
|
|
51
60
|
const combined = `${dirPath}/${name}`;
|
|
52
61
|
|
|
62
|
+
// Check SCUMM prefix patterns first (high-confidence filename-based)
|
|
63
|
+
const baseName = basename(name, extname(name));
|
|
64
|
+
for (const [cat, patterns] of Object.entries(SCUMM_PREFIX_PATTERNS)) {
|
|
65
|
+
for (const re of patterns) {
|
|
66
|
+
if (re.test(baseName)) return cat;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
53
70
|
// Check each category's patterns against the combined path+name
|
|
54
71
|
const scores = {};
|
|
55
72
|
for (const [cat, patterns] of Object.entries(CATEGORY_PATTERNS)) {
|
package/src/cli.js
CHANGED
|
@@ -114,8 +114,8 @@ const NavHint = ({ back = true, extra = "" }) =>
|
|
|
114
114
|
// ── Screen: Scope ───────────────────────────────────────────────
|
|
115
115
|
const ScopeScreen = ({ onNext }) => {
|
|
116
116
|
const items = [
|
|
117
|
-
{ label: "Global
|
|
118
|
-
{ label: "This project (.
|
|
117
|
+
{ label: "Global — Claude Code + VS Code Copilot (all projects)", value: "global" },
|
|
118
|
+
{ label: "This project — Claude Code + Copilot (incl. GitHub agent)", value: "project" },
|
|
119
119
|
];
|
|
120
120
|
return h(Box, { flexDirection: "column" },
|
|
121
121
|
h(Text, { bold: true }, " Where should sounds be installed?"),
|
|
@@ -973,7 +973,7 @@ const ConfirmScreen = ({ scope, sounds, onConfirm, onBack }) => {
|
|
|
973
973
|
return h(Box, { flexDirection: "column" },
|
|
974
974
|
h(Text, { bold: true, marginLeft: 2 }, " Ready to install:"),
|
|
975
975
|
h(Box, { marginTop: 1, flexDirection: "column" },
|
|
976
|
-
h(Text, { marginLeft: 4 }, `Scope: ${scope === "global" ? "Global (
|
|
976
|
+
h(Text, { marginLeft: 4 }, `Scope: ${scope === "global" ? "Global (Claude Code + VS Code Copilot)" : "This project (Claude Code + Copilot + GitHub agent)"}`),
|
|
977
977
|
...soundEntries.map(([eid, path]) =>
|
|
978
978
|
h(Text, { key: eid, marginLeft: 4 },
|
|
979
979
|
`${EVENTS[eid].name} → ${basename(path)}`
|
package/src/installer.js
CHANGED
|
@@ -88,6 +88,9 @@ export async function install({ scope, sounds }) {
|
|
|
88
88
|
// Write settings
|
|
89
89
|
await writeFile(settingsFile, JSON.stringify(settings, null, 2) + "\n", "utf-8");
|
|
90
90
|
|
|
91
|
+
// Also install Copilot coding agent hooks (.github/hooks/klaudio.json)
|
|
92
|
+
await installCopilotHooks(installedSounds, scope);
|
|
93
|
+
|
|
91
94
|
return {
|
|
92
95
|
soundsDir,
|
|
93
96
|
settingsFile,
|
|
@@ -95,6 +98,58 @@ export async function install({ scope, sounds }) {
|
|
|
95
98
|
};
|
|
96
99
|
}
|
|
97
100
|
|
|
101
|
+
/**
|
|
102
|
+
* Install hooks for GitHub Copilot coding agent.
|
|
103
|
+
* Writes .github/hooks/klaudio.json in the Copilot format.
|
|
104
|
+
*/
|
|
105
|
+
async function installCopilotHooks(installedSounds, scope) {
|
|
106
|
+
// Find the repo root (.github lives at repo root)
|
|
107
|
+
const repoRoot = scope === "global" ? null : process.cwd();
|
|
108
|
+
if (!repoRoot) return; // Copilot hooks are project-scoped only
|
|
109
|
+
|
|
110
|
+
const hooksDir = join(repoRoot, ".github", "hooks");
|
|
111
|
+
const hooksFile = join(hooksDir, "klaudio.json");
|
|
112
|
+
|
|
113
|
+
await mkdir(hooksDir, { recursive: true });
|
|
114
|
+
|
|
115
|
+
// Read existing file if present
|
|
116
|
+
let config = { version: 1, hooks: {} };
|
|
117
|
+
try {
|
|
118
|
+
const existing = await readFile(hooksFile, "utf-8");
|
|
119
|
+
config = JSON.parse(existing);
|
|
120
|
+
if (!config.hooks) config.hooks = {};
|
|
121
|
+
} catch { /* start fresh */ }
|
|
122
|
+
|
|
123
|
+
for (const [eventId, soundPath] of Object.entries(installedSounds)) {
|
|
124
|
+
const event = EVENTS[eventId];
|
|
125
|
+
if (!event?.copilotHookEvent) continue;
|
|
126
|
+
|
|
127
|
+
const normalized = soundPath.replace(/\\/g, "/");
|
|
128
|
+
const bashCmd = `afplay "${normalized}" 2>/dev/null & aplay "${normalized}" 2>/dev/null &`;
|
|
129
|
+
const psCmd = `Add-Type -AssemblyName PresentationCore; $p = New-Object System.Windows.Media.MediaPlayer; $p.Open([System.Uri]::new('${normalized.replace(/\//g, "\\")}')); Start-Sleep -Milliseconds 200; $p.Play(); Start-Sleep -Seconds 2`;
|
|
130
|
+
|
|
131
|
+
if (!config.hooks[event.copilotHookEvent]) {
|
|
132
|
+
config.hooks[event.copilotHookEvent] = [];
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Remove existing klaudio entries
|
|
136
|
+
config.hooks[event.copilotHookEvent] = config.hooks[event.copilotHookEvent].filter(
|
|
137
|
+
(entry) => !entry._klaudio
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
config.hooks[event.copilotHookEvent].push({
|
|
141
|
+
_klaudio: true,
|
|
142
|
+
type: "command",
|
|
143
|
+
bash: bashCmd,
|
|
144
|
+
powershell: psCmd,
|
|
145
|
+
timeoutSec: 10,
|
|
146
|
+
comment: `klaudio: ${event.name}`,
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
await writeFile(hooksFile, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
151
|
+
}
|
|
152
|
+
|
|
98
153
|
/**
|
|
99
154
|
* Read existing klaudio sound selections from settings.
|
|
100
155
|
* Returns a map of eventId -> soundFilePath (from the sounds/ dir).
|
|
@@ -154,8 +209,22 @@ export async function uninstall(scope) {
|
|
|
154
209
|
}
|
|
155
210
|
|
|
156
211
|
await writeFile(settingsFile, JSON.stringify(settings, null, 2) + "\n", "utf-8");
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
212
|
+
} catch { /* no existing config */ }
|
|
213
|
+
|
|
214
|
+
// Also clean up Copilot hooks
|
|
215
|
+
await uninstallCopilotHooks(scope);
|
|
216
|
+
|
|
217
|
+
return true;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Remove klaudio entries from .github/hooks/klaudio.json.
|
|
222
|
+
*/
|
|
223
|
+
async function uninstallCopilotHooks(scope) {
|
|
224
|
+
if (scope === "global") return;
|
|
225
|
+
const hooksFile = join(process.cwd(), ".github", "hooks", "klaudio.json");
|
|
226
|
+
try {
|
|
227
|
+
const { unlink } = await import("node:fs/promises");
|
|
228
|
+
await unlink(hooksFile);
|
|
229
|
+
} catch { /* file doesn't exist */ }
|
|
161
230
|
}
|
package/src/presets.js
CHANGED
|
@@ -16,11 +16,13 @@ export const EVENTS = {
|
|
|
16
16
|
name: "Notification",
|
|
17
17
|
description: "Plays when Claude needs your attention",
|
|
18
18
|
hookEvent: "Notification",
|
|
19
|
+
copilotHookEvent: null, // Copilot doesn't have this yet
|
|
19
20
|
},
|
|
20
21
|
stop: {
|
|
21
22
|
name: "Task Complete",
|
|
22
23
|
description: "Plays when Claude finishes a response",
|
|
23
24
|
hookEvent: "Stop",
|
|
25
|
+
copilotHookEvent: "sessionEnd",
|
|
24
26
|
},
|
|
25
27
|
};
|
|
26
28
|
|
package/src/scumm.js
CHANGED
|
@@ -517,11 +517,18 @@ export async function extractBunFile(bunPath, outputDir, onProgress) {
|
|
|
517
517
|
const { sampleRate, bitsPerSample, channels, pcmData } = parseImusResource(fullBuf);
|
|
518
518
|
if (!pcmData || pcmData.length === 0) continue;
|
|
519
519
|
|
|
520
|
-
// Write WAV
|
|
520
|
+
// Write WAV into a subdirectory named after the BUN file
|
|
521
521
|
const wav = createWav(pcmData, sampleRate, channels, bitsPerSample);
|
|
522
|
+
const bunName = basename(bunPath, extname(bunPath)).toLowerCase();
|
|
523
|
+
const subDir = bunName.includes("vox") ? "voice"
|
|
524
|
+
: bunName.includes("mus") ? "music"
|
|
525
|
+
: bunName.includes("sfx") ? "sfx"
|
|
526
|
+
: "other";
|
|
527
|
+
const bunOutputDir = join(outputDir, subDir);
|
|
528
|
+
await mkdir(bunOutputDir, { recursive: true });
|
|
522
529
|
const safeName = entry.filename.replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
523
530
|
const outName = safeName.replace(/\.[^.]+$/, "") + ".wav";
|
|
524
|
-
const outPath = join(
|
|
531
|
+
const outPath = join(bunOutputDir, outName);
|
|
525
532
|
await writeFile(outPath, wav);
|
|
526
533
|
outputs.push(outPath);
|
|
527
534
|
} catch {
|