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.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "claude-notification-plugin",
3
- "version": "1.0.47",
4
- "description": "Claude Code notifications щт task completion: Telegram, Windows toast, sound, and voice",
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
- Notifications for Claude Code task completion. Sends alerts to Telegram and Windows when Claude finishes working.
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
- - Windows desktop notifications (toast)
7
+ - Desktop notifications (Windows toast, macOS Notification Center, Linux notify-send)
8
8
  - Telegram bot messages with auto-delete
9
- - Sound alert (hidden PowerShell, no popup windows)
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
- "windowsNotification": {
74
+ "desktopNotification": {
75
75
  "enabled": true
76
76
  },
77
77
  "sound": {
78
78
  "enabled": true,
79
- "file": "C:/Windows/Media/notify.wav"
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
- | `CLAUDE_NOTIFY_WINDOWS` | Windows toast notifications|
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
- "CLAUDE_NOTIFY_WINDOWS": 1,
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
- windowsNotification: {
154
+ desktopNotification: {
147
155
  enabled: true,
148
156
  },
149
157
  sound: {
150
158
  enabled: true,
151
- file: 'C:/Windows/Media/notify.wav',
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" } }');
@@ -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
- windowsNotification: {
40
+ desktopNotification: {
25
41
  enabled: true,
26
42
  },
27
43
  sound: {
28
44
  enabled: true,
29
- file: 'C:/Windows/Media/notify.wav',
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.windowsNotification) {
47
- config.windowsNotification = { ...config.windowsNotification, ...user.windowsNotification };
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.CLAUDE_NOTIFY_WINDOWS !== undefined) {
81
- config.windowsNotification.enabled = process.env.CLAUDE_NOTIFY_WINDOWS === '1';
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
- // WINDOWS NOTIFICATION
290
+ // DESKTOP NOTIFICATION
275
291
  // ----------------------
276
292
 
277
- async function sendWindowsNotification (config, message) {
278
- if (!config.windowsNotification.enabled) {
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: true,
323
+ sound: false,
287
324
  wait: false,
288
325
  });
289
- } catch {
290
- // node-notifier not available (e.g. plugin cache without node_modules)
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
- const file = config.sound.file.replace(/'/g, "''");
304
- const psCommand = `(New-Object Media.SoundPlayer '${file}').PlaySync()`;
305
- spawn('powershell', ['-Command', psCommand], {
306
- stdio: 'ignore',
307
- windowsHide: true,
308
- });
309
- } catch {
310
- // silent fail
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
- const text = getVoicePhrase(duration);
451
- const psCommand = [
452
- 'Add-Type -AssemblyName System.Speech;',
453
- '$s = New-Object System.Speech.Synthesis.SpeechSynthesizer;',
454
- `$s.Speak("${text.replace(/"/g, '`"')}");`,
455
- ].join('');
456
- spawn('powershell', ['-Command', psCommand], {
457
- stdio: 'ignore',
458
- windowsHide: true,
459
- });
460
- } catch {
461
- // silent fail
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 sendWindowsNotification(config, message);
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.47",
5
- "description": "Claude Code notifications щт task completion: Telegram, Windows toast, sound, and voice",
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",