claude-notification-plugin 1.0.47 → 1.0.50
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/.claude-plugin/plugin.json +2 -2
- package/README.md +10 -8
- package/bin/install.js +22 -2
- package/notifier/notifier.js +101 -33
- package/package.json +6 -3
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-notification-plugin",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"description": "Claude Code
|
|
3
|
+
"version": "1.0.50",
|
|
4
|
+
"description": "Claude Code task-completion notifications: Telegram, desktop notifications (Windows/macOS/Linux), sound, and voice",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Viacheslav Makarov",
|
|
7
7
|
"email": "npmjs@bazilio.ru"
|
package/README.md
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
# claude-notification-plugin
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Cross-platform notifications for Claude Code task completion. Sends alerts to Telegram and desktop (Windows, macOS, Linux) when Claude finishes working.
|
|
4
4
|
|
|
5
5
|
## Features
|
|
6
6
|
|
|
7
|
-
-
|
|
7
|
+
- Desktop notifications (Windows toast, macOS Notification Center, Linux notify-send)
|
|
8
8
|
- Telegram bot messages with auto-delete
|
|
9
|
-
- Sound alert (
|
|
10
|
-
- Voice announcement with number-to-words and pluralization (EN, RU)
|
|
9
|
+
- Sound alert (Windows: PowerShell, macOS: afplay, Linux: paplay/aplay)
|
|
10
|
+
- Voice announcement with number-to-words and pluralization (EN, RU) (Windows: SAPI, macOS: say, Linux: spd-say/espeak)
|
|
11
11
|
- Separate notifications for task completion and waiting-for-input events
|
|
12
12
|
- Skips short tasks (< 15s by default)
|
|
13
13
|
- Granular per-channel enable/disable (globally and per-project)
|
|
@@ -71,12 +71,12 @@ Config file: `~/.claude/notifier.config.json`
|
|
|
71
71
|
"deleteAfterHours": 24,
|
|
72
72
|
"includeLastCcMessageInTelegram": true
|
|
73
73
|
},
|
|
74
|
-
"
|
|
74
|
+
"desktopNotification": {
|
|
75
75
|
"enabled": true
|
|
76
76
|
},
|
|
77
77
|
"sound": {
|
|
78
78
|
"enabled": true,
|
|
79
|
-
"file": "
|
|
79
|
+
"file": ""
|
|
80
80
|
},
|
|
81
81
|
"voice": {
|
|
82
82
|
"enabled": true
|
|
@@ -89,6 +89,8 @@ Config file: `~/.claude/notifier.config.json`
|
|
|
89
89
|
|
|
90
90
|
Each channel has an `enabled` flag (`true`/`false`) for global control.
|
|
91
91
|
|
|
92
|
+
`sound.file` — path to a sound file. Leave empty for platform default (Windows: `C:/Windows/Media/notify.wav`, macOS: `/System/Library/Sounds/Glass.aiff`, Linux: `/usr/share/sounds/freedesktop/stereo/complete.oga`).
|
|
93
|
+
|
|
92
94
|
`deleteAfterHours` — auto-delete old Telegram messages after the specified number of hours (default: `24`, set `0` to disable).
|
|
93
95
|
|
|
94
96
|
`includeLastCcMessageInTelegram` — append Claude's last assistant message to the Telegram notification (default: `true`). Only affects Telegram, not Windows toast notifications. Long messages are truncated to 3500 characters.
|
|
@@ -106,7 +108,7 @@ These env vars override the global config per channel (`"1"` = on, `"0"` = off):
|
|
|
106
108
|
| Variable | Channel |
|
|
107
109
|
|--------------------------|----------------------------|
|
|
108
110
|
| `CLAUDE_NOTIFY_TELEGRAM` | Telegram messages |
|
|
109
|
-
| `
|
|
111
|
+
| `CLAUDE_NOTIFY_DESKTOP` | Desktop notifications |
|
|
110
112
|
| `CLAUDE_NOTIFY_SOUND` | Sound alert |
|
|
111
113
|
| `CLAUDE_NOTIFY_VOICE` | Voice announcement (TTS) |
|
|
112
114
|
| `CLAUDE_NOTIFY_WAITING` | Waiting-for-input events |
|
|
@@ -122,7 +124,7 @@ Add to `.claude/settings.local.json` in the project root to control channels per
|
|
|
122
124
|
"env": {
|
|
123
125
|
"DISABLE_CLAUDE_NOTIFIER": 0,
|
|
124
126
|
"CLAUDE_NOTIFY_TELEGRAM": 1,
|
|
125
|
-
"
|
|
127
|
+
"CLAUDE_NOTIFY_DESKTOP": 1,
|
|
126
128
|
"CLAUDE_NOTIFY_SOUND": 1,
|
|
127
129
|
"CLAUDE_NOTIFY_VOICE": 1,
|
|
128
130
|
"CLAUDE_NOTIFY_WAITING": 1,
|
package/bin/install.js
CHANGED
|
@@ -136,6 +136,14 @@ async function main () {
|
|
|
136
136
|
// Create / update config
|
|
137
137
|
fs.mkdirSync(claudeDir, { recursive: true });
|
|
138
138
|
|
|
139
|
+
const platform = process.platform;
|
|
140
|
+
let defaultSoundFile;
|
|
141
|
+
switch (platform) {
|
|
142
|
+
case 'darwin': defaultSoundFile = '/System/Library/Sounds/Glass.aiff'; break;
|
|
143
|
+
case 'linux': defaultSoundFile = '/usr/share/sounds/freedesktop/stereo/complete.oga'; break;
|
|
144
|
+
default: defaultSoundFile = 'C:/Windows/Media/notify.wav';
|
|
145
|
+
}
|
|
146
|
+
|
|
139
147
|
const defaults = {
|
|
140
148
|
telegram: {
|
|
141
149
|
enabled: true,
|
|
@@ -143,12 +151,12 @@ async function main () {
|
|
|
143
151
|
chatId: '',
|
|
144
152
|
deleteAfterHours: 24,
|
|
145
153
|
},
|
|
146
|
-
|
|
154
|
+
desktopNotification: {
|
|
147
155
|
enabled: true,
|
|
148
156
|
},
|
|
149
157
|
sound: {
|
|
150
158
|
enabled: true,
|
|
151
|
-
file:
|
|
159
|
+
file: defaultSoundFile,
|
|
152
160
|
},
|
|
153
161
|
voice: {
|
|
154
162
|
enabled: true,
|
|
@@ -213,6 +221,18 @@ async function main () {
|
|
|
213
221
|
console.log('Telegram: not configured (edit config later)');
|
|
214
222
|
}
|
|
215
223
|
|
|
224
|
+
if (platform === 'darwin') {
|
|
225
|
+
console.log('');
|
|
226
|
+
console.log('Tip: install terminal-notifier for better macOS notifications:');
|
|
227
|
+
console.log(' brew install terminal-notifier');
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if (platform === 'linux') {
|
|
231
|
+
console.log('');
|
|
232
|
+
console.log('Tip: for voice announcements, install espeak:');
|
|
233
|
+
console.log(' sudo apt install espeak');
|
|
234
|
+
}
|
|
235
|
+
|
|
216
236
|
console.log('');
|
|
217
237
|
console.log('To disable per project, add to .claude/settings.local.json:');
|
|
218
238
|
console.log(' { "env": { "DISABLE_CLAUDE_NOTIFIER": "1" } }');
|
package/notifier/notifier.js
CHANGED
|
@@ -10,6 +10,22 @@ import { spawn } from 'child_process';
|
|
|
10
10
|
// CONFIG
|
|
11
11
|
// ----------------------
|
|
12
12
|
|
|
13
|
+
const PLATFORM = process.platform; // 'win32' | 'darwin' | 'linux'
|
|
14
|
+
|
|
15
|
+
function getDefaultSoundFile () {
|
|
16
|
+
switch (PLATFORM) {
|
|
17
|
+
case 'darwin': return '/System/Library/Sounds/Glass.aiff';
|
|
18
|
+
case 'linux': return '/usr/share/sounds/freedesktop/stereo/complete.oga';
|
|
19
|
+
default: return 'C:/Windows/Media/notify.wav';
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function debugLog (config, ...args) {
|
|
24
|
+
if (config.debug) {
|
|
25
|
+
console.error('[claude-notifier]', ...args);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
13
29
|
function loadConfig () {
|
|
14
30
|
const configPath = path.join(os.homedir(), '.claude', 'notifier.config.json');
|
|
15
31
|
|
|
@@ -21,12 +37,12 @@ function loadConfig () {
|
|
|
21
37
|
deleteAfterHours: 24,
|
|
22
38
|
includeLastCcMessageInTelegram: true,
|
|
23
39
|
},
|
|
24
|
-
|
|
40
|
+
desktopNotification: {
|
|
25
41
|
enabled: true,
|
|
26
42
|
},
|
|
27
43
|
sound: {
|
|
28
44
|
enabled: true,
|
|
29
|
-
file:
|
|
45
|
+
file: getDefaultSoundFile(),
|
|
30
46
|
},
|
|
31
47
|
voice: {
|
|
32
48
|
enabled: true,
|
|
@@ -43,8 +59,8 @@ function loadConfig () {
|
|
|
43
59
|
if (user.telegram) {
|
|
44
60
|
config.telegram = { ...config.telegram, ...user.telegram };
|
|
45
61
|
}
|
|
46
|
-
if (user.
|
|
47
|
-
config.
|
|
62
|
+
if (user.desktopNotification) {
|
|
63
|
+
config.desktopNotification = { ...config.desktopNotification, ...user.desktopNotification };
|
|
48
64
|
}
|
|
49
65
|
if (user.sound) {
|
|
50
66
|
config.sound = { ...config.sound, ...user.sound };
|
|
@@ -77,8 +93,8 @@ function loadConfig () {
|
|
|
77
93
|
if (process.env.CLAUDE_NOTIFY_TELEGRAM !== undefined) {
|
|
78
94
|
config.telegram.enabled = process.env.CLAUDE_NOTIFY_TELEGRAM === '1';
|
|
79
95
|
}
|
|
80
|
-
if (process.env.
|
|
81
|
-
config.
|
|
96
|
+
if (process.env.CLAUDE_NOTIFY_DESKTOP !== undefined) {
|
|
97
|
+
config.desktopNotification.enabled = process.env.CLAUDE_NOTIFY_DESKTOP === '1';
|
|
82
98
|
}
|
|
83
99
|
if (process.env.CLAUDE_NOTIFY_SOUND !== undefined) {
|
|
84
100
|
config.sound.enabled = process.env.CLAUDE_NOTIFY_SOUND === '1';
|
|
@@ -271,11 +287,32 @@ async function sendTelegram (config, state) {
|
|
|
271
287
|
}
|
|
272
288
|
|
|
273
289
|
// ----------------------
|
|
274
|
-
//
|
|
290
|
+
// DESKTOP NOTIFICATION
|
|
275
291
|
// ----------------------
|
|
276
292
|
|
|
277
|
-
|
|
278
|
-
|
|
293
|
+
function sendNativeFallback (config, message) {
|
|
294
|
+
try {
|
|
295
|
+
switch (PLATFORM) {
|
|
296
|
+
case 'darwin': {
|
|
297
|
+
const escaped = message.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
|
|
298
|
+
spawn('osascript', ['-e', `display notification "${escaped}" with title "Claude Code"`], {
|
|
299
|
+
stdio: 'ignore',
|
|
300
|
+
});
|
|
301
|
+
break;
|
|
302
|
+
}
|
|
303
|
+
case 'linux':
|
|
304
|
+
spawn('notify-send', ['Claude Code', message], {
|
|
305
|
+
stdio: 'ignore',
|
|
306
|
+
});
|
|
307
|
+
break;
|
|
308
|
+
}
|
|
309
|
+
} catch (err) {
|
|
310
|
+
debugLog(config, 'native notification fallback failed:', err.message);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
async function sendDesktopNotification (config, message) {
|
|
315
|
+
if (!config.desktopNotification.enabled) {
|
|
279
316
|
return;
|
|
280
317
|
}
|
|
281
318
|
try {
|
|
@@ -283,11 +320,12 @@ async function sendWindowsNotification (config, message) {
|
|
|
283
320
|
notifier.notify({
|
|
284
321
|
title: 'Claude Code',
|
|
285
322
|
message,
|
|
286
|
-
sound:
|
|
323
|
+
sound: false,
|
|
287
324
|
wait: false,
|
|
288
325
|
});
|
|
289
|
-
} catch {
|
|
290
|
-
|
|
326
|
+
} catch (err) {
|
|
327
|
+
debugLog(config, 'node-notifier failed, trying native fallback:', err.message);
|
|
328
|
+
sendNativeFallback(config, message);
|
|
291
329
|
}
|
|
292
330
|
}
|
|
293
331
|
|
|
@@ -299,15 +337,30 @@ function playSound (config) {
|
|
|
299
337
|
if (!config.sound.enabled) {
|
|
300
338
|
return;
|
|
301
339
|
}
|
|
340
|
+
const file = config.sound.file;
|
|
302
341
|
try {
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
342
|
+
switch (PLATFORM) {
|
|
343
|
+
case 'win32': {
|
|
344
|
+
const psCommand = `(New-Object Media.SoundPlayer '${file.replace(/'/g, "''")}').PlaySync()`;
|
|
345
|
+
spawn('powershell', ['-Command', psCommand], {
|
|
346
|
+
stdio: 'ignore',
|
|
347
|
+
windowsHide: true,
|
|
348
|
+
});
|
|
349
|
+
break;
|
|
350
|
+
}
|
|
351
|
+
case 'darwin':
|
|
352
|
+
spawn('afplay', [file], { stdio: 'ignore' });
|
|
353
|
+
break;
|
|
354
|
+
case 'linux': {
|
|
355
|
+
const child = spawn('paplay', [file], { stdio: 'ignore' });
|
|
356
|
+
child.on('error', () => {
|
|
357
|
+
spawn('aplay', [file], { stdio: 'ignore' });
|
|
358
|
+
});
|
|
359
|
+
break;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
} catch (err) {
|
|
363
|
+
debugLog(config, 'playSound failed:', err.message);
|
|
311
364
|
}
|
|
312
365
|
}
|
|
313
366
|
|
|
@@ -446,19 +499,34 @@ function speakResult (config, duration) {
|
|
|
446
499
|
if (!config.voice.enabled) {
|
|
447
500
|
return;
|
|
448
501
|
}
|
|
502
|
+
const text = getVoicePhrase(duration);
|
|
449
503
|
try {
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
504
|
+
switch (PLATFORM) {
|
|
505
|
+
case 'win32': {
|
|
506
|
+
const psCommand = [
|
|
507
|
+
'Add-Type -AssemblyName System.Speech;',
|
|
508
|
+
'$s = New-Object System.Speech.Synthesis.SpeechSynthesizer;',
|
|
509
|
+
`$s.Speak("${text.replace(/"/g, '`"')}");`,
|
|
510
|
+
].join('');
|
|
511
|
+
spawn('powershell', ['-Command', psCommand], {
|
|
512
|
+
stdio: 'ignore',
|
|
513
|
+
windowsHide: true,
|
|
514
|
+
});
|
|
515
|
+
break;
|
|
516
|
+
}
|
|
517
|
+
case 'darwin':
|
|
518
|
+
spawn('say', [text], { stdio: 'ignore' });
|
|
519
|
+
break;
|
|
520
|
+
case 'linux': {
|
|
521
|
+
const child = spawn('spd-say', [text], { stdio: 'ignore' });
|
|
522
|
+
child.on('error', () => {
|
|
523
|
+
spawn('espeak', [text], { stdio: 'ignore' });
|
|
524
|
+
});
|
|
525
|
+
break;
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
} catch (err) {
|
|
529
|
+
debugLog(config, 'speakResult failed:', err.message);
|
|
462
530
|
}
|
|
463
531
|
}
|
|
464
532
|
|
|
@@ -558,7 +626,7 @@ process.stdin.on('end', async () => {
|
|
|
558
626
|
delete state._telegramText;
|
|
559
627
|
saveState(state);
|
|
560
628
|
|
|
561
|
-
await
|
|
629
|
+
await sendDesktopNotification(config, message);
|
|
562
630
|
playSound(config);
|
|
563
631
|
speakResult(config, duration);
|
|
564
632
|
});
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-notification-plugin",
|
|
3
3
|
"productName": "claude-notification-plugin",
|
|
4
|
-
"version": "1.0.
|
|
5
|
-
"description": "Claude Code
|
|
4
|
+
"version": "1.0.50",
|
|
5
|
+
"description": "Claude Code task-completion notifications: Telegram, desktop notifications (Windows/macOS/Linux), sound, and voice",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"engines": {
|
|
8
8
|
"node": ">=18.0.0"
|
|
@@ -30,7 +30,10 @@
|
|
|
30
30
|
"claude-code",
|
|
31
31
|
"notifications",
|
|
32
32
|
"telegram",
|
|
33
|
-
"hooks"
|
|
33
|
+
"hooks",
|
|
34
|
+
"macos",
|
|
35
|
+
"linux",
|
|
36
|
+
"cross-platform"
|
|
34
37
|
],
|
|
35
38
|
"author": {
|
|
36
39
|
"name": "Viacheslav Makarov",
|