claude-notification-plugin 1.0.23 → 1.0.25

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.
@@ -6,7 +6,7 @@ import path from 'path';
6
6
  import process from 'process';
7
7
  import notifier from 'node-notifier';
8
8
  import player from 'play-sound';
9
- import say from 'say';
9
+ import { spawn } from 'child_process';
10
10
 
11
11
  const audio = player({});
12
12
 
@@ -228,13 +228,126 @@ function playSound (config) {
228
228
  }
229
229
  }
230
230
 
231
+ function pluralize (n, forms) {
232
+ // forms: [one, few, many] e.g. ['секунда', 'секунды', 'секунд']
233
+ if (forms.length === 1) {
234
+ return forms[0];
235
+ }
236
+ if (forms.length === 2) {
237
+ return n === 1 ? forms[0] : forms[1];
238
+ }
239
+ // Slavic pluralization (ru, uk, pl, etc.)
240
+ const abs = Math.abs(n);
241
+ const mod10 = abs % 10;
242
+ const mod100 = abs % 100;
243
+ if (mod10 === 1 && mod100 !== 11) {
244
+ return forms[0];
245
+ }
246
+ if (mod10 >= 2 && mod10 <= 4 && (mod100 < 10 || mod100 >= 20)) {
247
+ return forms[1];
248
+ }
249
+ return forms[2];
250
+ }
251
+
252
+ // ----------------------
253
+ // NUMBER TO WORDS
254
+ // ----------------------
255
+
256
+ const numWordsEn = {
257
+ ones: ['', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine',
258
+ 'ten', 'eleven', 'twelve', 'thirteen', 'fourteen', 'fifteen', 'sixteen',
259
+ 'seventeen', 'eighteen', 'nineteen'],
260
+ tens: ['', '', 'twenty', 'thirty', 'forty', 'fifty', 'sixty', 'seventy', 'eighty', 'ninety'],
261
+ };
262
+
263
+ function numberToWordsEn (n) {
264
+ if (n === 0) {
265
+ return 'zero';
266
+ }
267
+ const parts = [];
268
+ if (n >= 1000) {
269
+ parts.push(numWordsEn.ones[Math.floor(n / 1000)] + ' thousand');
270
+ n %= 1000;
271
+ }
272
+ if (n >= 100) {
273
+ parts.push(numWordsEn.ones[Math.floor(n / 100)] + ' hundred');
274
+ n %= 100;
275
+ }
276
+ if (n >= 20) {
277
+ const t = numWordsEn.tens[Math.floor(n / 10)];
278
+ const o = numWordsEn.ones[n % 10];
279
+ parts.push(o ? `${t}-${o}` : t);
280
+ } else if (n > 0) {
281
+ parts.push(numWordsEn.ones[n]);
282
+ }
283
+ return parts.join(' ');
284
+ }
285
+
286
+ // Russian: feminine gender for "секунда" (одна, две vs один, два)
287
+ const numWordsRu = {
288
+ ones: ['', 'одна', 'две', 'три', 'четыре', 'пять', 'шесть', 'семь', 'восемь', 'девять',
289
+ 'десять', 'одиннадцать', 'двенадцать', 'тринадцать', 'четырнадцать', 'пятнадцать',
290
+ 'шестнадцать', 'семнадцать', 'восемнадцать', 'девятнадцать'],
291
+ tens: ['', '', 'двадцать', 'тридцать', 'сорок', 'пятьдесят', 'шестьдесят',
292
+ 'семьдесят', 'восемьдесят', 'девяносто'],
293
+ hundreds: ['', 'сто', 'двести', 'триста', 'четыреста', 'пятьсот', 'шестьсот',
294
+ 'семьсот', 'восемьсот', 'девятьсот'],
295
+ thousands: ['тысяча', 'тысячи', 'тысяч'],
296
+ thousandOnes: ['', 'одна', 'две', 'три', 'четыре', 'пять', 'шесть', 'семь', 'восемь', 'девять'],
297
+ };
298
+
299
+ function numberToWordsRu (n) {
300
+ if (n === 0) {
301
+ return 'ноль';
302
+ }
303
+ const parts = [];
304
+ if (n >= 1000) {
305
+ const th = Math.floor(n / 1000);
306
+ if (th >= 100) {
307
+ parts.push(numWordsRu.hundreds[Math.floor(th / 100)]);
308
+ }
309
+ let thRem = th % 100;
310
+ if (thRem >= 20) {
311
+ parts.push(numWordsRu.tens[Math.floor(thRem / 10)]);
312
+ thRem %= 10;
313
+ }
314
+ if (thRem > 0 && thRem < 20) {
315
+ parts.push(thRem < 10 ? numWordsRu.thousandOnes[thRem] : numWordsRu.ones[thRem]);
316
+ }
317
+ parts.push(pluralize(th, numWordsRu.thousands));
318
+ n %= 1000;
319
+ }
320
+ if (n >= 100) {
321
+ parts.push(numWordsRu.hundreds[Math.floor(n / 100)]);
322
+ n %= 100;
323
+ }
324
+ if (n >= 20) {
325
+ parts.push(numWordsRu.tens[Math.floor(n / 10)]);
326
+ n %= 10;
327
+ }
328
+ if (n > 0) {
329
+ parts.push(numWordsRu.ones[n]);
330
+ }
331
+ return parts.join(' ');
332
+ }
333
+
334
+ function numberToWords (n, lang) {
335
+ if (lang === 'ru') {
336
+ return numberToWordsRu(n);
337
+ }
338
+ if (lang === 'en') {
339
+ return numberToWordsEn(n);
340
+ }
341
+ return String(n);
342
+ }
343
+
231
344
  const voicePhrases = {
232
- en: (d) => `Claude finished coding in ${d} seconds`,
233
- ru: (d) => `Клод завершил работу за ${d} секунд`,
234
- de: (d) => `Claude hat die Arbeit in ${d} Sekunden abgeschlossen`,
235
- fr: (d) => `Claude a termine en ${d} secondes`,
236
- es: (d) => `Claude termino en ${d} segundos`,
237
- pt: (d) => `Claude terminou em ${d} segundos`,
345
+ en: (d) => `Claude finished coding in ${numberToWords(d, 'en')} ${pluralize(d, ['second', 'seconds'])}`,
346
+ ru: (d) => `Клод завершил работу за ${numberToWords(d, 'ru')} ${pluralize(d, ['секунду', 'секунды', 'секунд'])}`,
347
+ de: (d) => `Claude hat die Arbeit in ${d} ${pluralize(d, ['Sekunde', 'Sekunden'])} abgeschlossen`,
348
+ fr: (d) => `Claude a termine en ${d} ${pluralize(d, ['seconde', 'secondes'])}`,
349
+ es: (d) => `Claude termino en ${d} ${pluralize(d, ['segundo', 'segundos'])}`,
350
+ pt: (d) => `Claude terminou em ${d} ${pluralize(d, ['segundo', 'segundos'])}`,
238
351
  ja: (d) => `Claudeは${d}秒でコーディングを完了しました`,
239
352
  ko: (d) => `Claude가 ${d}초 만에 코딩을 완료했습니다`,
240
353
  };
@@ -251,7 +364,16 @@ function speakResult (config, duration) {
251
364
  return;
252
365
  }
253
366
  try {
254
- say.speak(getVoicePhrase(duration));
367
+ const text = getVoicePhrase(duration);
368
+ const psCommand = [
369
+ 'Add-Type -AssemblyName System.Speech;',
370
+ '$s = New-Object System.Speech.Synthesis.SpeechSynthesizer;',
371
+ `$s.Speak("${text.replace(/"/g, '`"')}");`,
372
+ ].join('');
373
+ spawn('powershell', ['-Command', psCommand], {
374
+ stdio: 'ignore',
375
+ windowsHide: true,
376
+ });
255
377
  } catch {
256
378
  // silent fail
257
379
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "claude-notification-plugin",
3
3
  "productName": "claude-notification-plugin",
4
- "version": "1.0.23",
4
+ "version": "1.0.25",
5
5
  "description": "Telegram and Windows notifications for Claude Code task completion",
6
6
  "type": "module",
7
7
  "engines": {
@@ -44,8 +44,7 @@
44
44
  },
45
45
  "dependencies": {
46
46
  "node-notifier": "^10.0.1",
47
- "play-sound": "^1.1.6",
48
- "say": "^0.16.0"
47
+ "play-sound": "^1.1.6"
49
48
  },
50
49
  "devDependencies": {
51
50
  "eslint-plugin-import": "^2.31.0"