klaudio 0.8.1 → 0.8.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.
Files changed (3) hide show
  1. package/package.json +1 -1
  2. package/src/player.js +18 -3
  3. package/src/tts.js +33 -15
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "klaudio",
3
- "version": "0.8.1",
3
+ "version": "0.8.3",
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/player.js CHANGED
@@ -392,13 +392,28 @@ export async function handlePlayCommand(args) {
392
392
 
393
393
  // TTS: speak first 1-2 sentences of last_assistant_message
394
394
  if (tts && hookData.last_assistant_message) {
395
- const msg = hookData.last_assistant_message;
396
- // Extract first sentence only
395
+ // Strip markdown syntax and extract first sentence
396
+ const msg = hookData.last_assistant_message
397
+ .replace(/```[\s\S]*?```/g, "") // remove code blocks
398
+ .replace(/`([^`]+)`/g, "$1") // inline code -> text
399
+ .replace(/\*\*([^*]+)\*\*/g, "$1") // **bold** -> text
400
+ .replace(/\*([^*]+)\*/g, "$1") // *italic* -> text
401
+ .replace(/__([^_]+)__/g, "$1") // __bold__ -> text
402
+ .replace(/_([^_]+)_/g, "$1") // _italic_ -> text
403
+ .replace(/#{1,6}\s+/g, "") // headings
404
+ .replace(/\[([^\]]+)\]\([^)]+\)/g, "$1") // [links](url) -> text
405
+ .replace(/^\s*[-*+]\s+/gm, "") // list bullets
406
+ .replace(/^\s*\d+\.\s+/gm, "") // numbered lists
407
+ .replace(/\n+/g, " ") // newlines -> spaces
408
+ .trim();
397
409
  const sentences = msg.match(/[^.!?]*[.!?]/g);
398
410
  const summary = sentences ? sentences[0].trim() : msg.slice(0, 100);
411
+ // Prefix with project folder name if available
412
+ const project = hookData.cwd ? hookData.cwd.replace(/\\/g, "/").split("/").pop() : null;
413
+ const spoken = project ? `${project}: ${summary}` : summary;
399
414
  await soundPromise;
400
415
  const { speak } = await import("./tts.js");
401
- await speak(summary);
416
+ await speak(spoken);
402
417
  } else {
403
418
  await soundPromise;
404
419
  }
package/src/tts.js CHANGED
@@ -179,13 +179,27 @@ export async function ensureVoiceModel(onProgress) {
179
179
  }
180
180
 
181
181
  /**
182
- * Speak text using Piper TTS.
182
+ * Speak text using macOS `say` command (built-in, good quality).
183
+ */
184
+ function speakMacOS(text) {
185
+ return new Promise((resolve) => {
186
+ execFile("say", ["-v", "Daniel", text], { timeout: 15000 }, () => resolve());
187
+ });
188
+ }
189
+
190
+ /**
191
+ * Speak text using Piper TTS, with macOS `say` fallback.
183
192
  * Auto-downloads piper and voice model on first use.
184
193
  * Returns a promise that resolves when speech is done.
185
194
  */
186
195
  export async function speak(text, onProgress) {
187
196
  if (!text) return;
188
197
 
198
+ // macOS: use built-in `say` — better compatibility, no dylib issues
199
+ if (platform() === "darwin") {
200
+ return speakMacOS(text);
201
+ }
202
+
189
203
  let piperBin, modelPath;
190
204
  try {
191
205
  [piperBin, modelPath] = await Promise.all([
@@ -201,20 +215,24 @@ export async function speak(text, onProgress) {
201
215
  const hash = createHash("md5").update(text).digest("hex").slice(0, 8);
202
216
  const outPath = join(tmpdir(), `klaudio-tts-${hash}.wav`);
203
217
 
204
- await new Promise((resolve, reject) => {
205
- const child = execFile(piperBin, [
206
- "--model", modelPath,
207
- "--output_file", outPath,
208
- ], { windowsHide: true, timeout: 15000 }, (err) => {
209
- if (err) reject(err);
210
- else resolve();
218
+ try {
219
+ await new Promise((resolve, reject) => {
220
+ const child = execFile(piperBin, [
221
+ "--model", modelPath,
222
+ "--output_file", outPath,
223
+ ], { windowsHide: true, timeout: 15000 }, (err) => {
224
+ if (err) reject(err);
225
+ else resolve();
226
+ });
227
+ // Feed text via stdin
228
+ child.stdin.write(text);
229
+ child.stdin.end();
211
230
  });
212
- // Feed text via stdin
213
- child.stdin.write(text);
214
- child.stdin.end();
215
- });
216
231
 
217
- // Play the generated wav
218
- const { playSoundWithCancel } = await import("./player.js");
219
- await playSoundWithCancel(outPath, { maxSeconds: 0 }).promise.catch(() => {});
232
+ // Play the generated wav
233
+ const { playSoundWithCancel } = await import("./player.js");
234
+ await playSoundWithCancel(outPath, { maxSeconds: 0 }).promise.catch(() => {});
235
+ } catch {
236
+ // Piper failed (dylib error, etc.) — skip silently
237
+ }
220
238
  }