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.
- package/notifier/notifier.js +130 -8
- package/package.json +2 -3
package/notifier/notifier.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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.
|
|
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"
|