claude-notification-plugin 1.0.4 → 1.0.10

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/README.md CHANGED
@@ -7,9 +7,9 @@ Notifications for Claude Code task completion. Sends alerts to Telegram and Wind
7
7
  - Windows desktop notifications (toast)
8
8
  - Telegram bot messages
9
9
  - Sound alert
10
- - Voice announcement
10
+ - Voice announcement (TTS)
11
11
  - Skips short tasks (< 15s by default)
12
- - Per-project disable
12
+ - Granular per-channel enable/disable (globally and per-project)
13
13
 
14
14
  ## Install
15
15
 
@@ -30,8 +30,13 @@ Config file: `~/.claude/notifier.config.json`
30
30
  ```json
31
31
  {
32
32
  "telegram": {
33
+ "enabled": true,
33
34
  "token": "YOUR_BOT_TOKEN",
34
- "chatId": "YOUR_CHAT_ID"
35
+ "chatId": "YOUR_CHAT_ID",
36
+ "deleteAfterHours": 24
37
+ },
38
+ "windowsNotification": {
39
+ "enabled": true
35
40
  },
36
41
  "sound": {
37
42
  "enabled": true,
@@ -44,11 +49,40 @@ Config file: `~/.claude/notifier.config.json`
44
49
  }
45
50
  ```
46
51
 
52
+ Each channel has an `enabled` flag (`true`/`false`) for global control.
53
+
54
+ `deleteAfterHours` — auto-delete old Telegram messages after the specified number of hours (default: `24`, set `0` to disable).
55
+
47
56
  Environment variables `TELEGRAM_TOKEN` and `TELEGRAM_CHAT_ID` override config file values.
48
57
 
49
- ### Disable per project
58
+ ### Per-channel environment variables
59
+
60
+ These env vars override the global config per channel (`"1"` = on, `"0"` = off):
61
+
62
+ | Variable | Channel |
63
+ |--------------------------|----------------------------|
64
+ | `CLAUDE_NOTIFY_TELEGRAM` | Telegram messages |
65
+ | `CLAUDE_NOTIFY_WINDOWS` | Windows toast notifications|
66
+ | `CLAUDE_NOTIFY_SOUND` | Sound alert |
67
+ | `CLAUDE_NOTIFY_VOICE` | Voice announcement (TTS) |
68
+
69
+ ### Per-project configuration
70
+
71
+ Add to `.claude/settings.local.json` in the project root to control channels per project:
72
+
73
+ ```json
74
+ {
75
+ "env": {
76
+ "DISABLE_CLAUDE_NOTIFIER": 0,
77
+ "CLAUDE_NOTIFY_TELEGRAM": 1,
78
+ "CLAUDE_NOTIFY_WINDOWS": 1,
79
+ "CLAUDE_NOTIFY_SOUND": 1,
80
+ "CLAUDE_NOTIFY_VOICE": 1
81
+ }
82
+ }
83
+ ```
50
84
 
51
- Add to `.claude/settings.local.json` in the project root:
85
+ To disable all notifications for a project:
52
86
 
53
87
  ```json
54
88
  {
@@ -63,7 +97,7 @@ Add to `.claude/settings.local.json` in the project root:
63
97
  1. Open Telegram, find **@BotFather**
64
98
  2. Send `/newbot`, follow prompts, pick a name
65
99
  3. Copy the bot token (format: `123456789:ABCdef...`)
66
- 4. Send any message to your new bot
100
+ 4. **Send any message to your new bot**
67
101
  5. Open `https://api.telegram.org/bot<YOUR_TOKEN>/getUpdates`
68
102
  6. Find `"chat":{"id":123456789}` in the response — that's your Chat ID
69
103
  7. Run `claude-notify-install` and enter the token and chat ID
package/bin/install.js CHANGED
@@ -112,9 +112,22 @@ async function main () {
112
112
  fs.mkdirSync(claudeDir, { recursive: true });
113
113
 
114
114
  const config = {
115
- telegram: { token, chatId },
116
- sound: { enabled: true, file: 'C:/Windows/Media/notify.wav' },
117
- voice: { enabled: true },
115
+ telegram: {
116
+ enabled: true,
117
+ token,
118
+ chatId,
119
+ deleteAfterHours: 24,
120
+ },
121
+ windowsNotification: {
122
+ enabled: true,
123
+ },
124
+ sound: {
125
+ enabled: true,
126
+ file: 'C:/Windows/Media/notify.wav',
127
+ },
128
+ voice: {
129
+ enabled: true,
130
+ },
118
131
  minSeconds: 15,
119
132
  };
120
133
 
@@ -18,9 +18,22 @@ function loadConfig () {
18
18
  const configPath = path.join(os.homedir(), '.claude', 'notifier.config.json');
19
19
 
20
20
  const config = {
21
- telegram: { token: '', chatId: '' },
22
- sound: { enabled: true, file: 'C:/Windows/Media/notify.wav' },
23
- voice: { enabled: true },
21
+ telegram: {
22
+ enabled: true,
23
+ token: '',
24
+ chatId: '',
25
+ deleteAfterHours: 24,
26
+ },
27
+ windowsNotification: {
28
+ enabled: true,
29
+ },
30
+ sound: {
31
+ enabled: true,
32
+ file: 'C:/Windows/Media/notify.wav',
33
+ },
34
+ voice: {
35
+ enabled: true,
36
+ },
24
37
  minSeconds: 15,
25
38
  };
26
39
 
@@ -31,6 +44,9 @@ function loadConfig () {
31
44
  if (user.telegram) {
32
45
  config.telegram = { ...config.telegram, ...user.telegram };
33
46
  }
47
+ if (user.windowsNotification) {
48
+ config.windowsNotification = { ...config.windowsNotification, ...user.windowsNotification };
49
+ }
34
50
  if (user.sound) {
35
51
  config.sound = { ...config.sound, ...user.sound };
36
52
  }
@@ -52,6 +68,20 @@ function loadConfig () {
52
68
  config.telegram.chatId = process.env.TELEGRAM_CHAT_ID;
53
69
  }
54
70
 
71
+ // Per-channel env overrides (0 = off, 1 = on)
72
+ if (process.env.CLAUDE_NOTIFY_TELEGRAM !== undefined) {
73
+ config.telegram.enabled = process.env.CLAUDE_NOTIFY_TELEGRAM === '1';
74
+ }
75
+ if (process.env.CLAUDE_NOTIFY_WINDOWS !== undefined) {
76
+ config.windowsNotification.enabled = process.env.CLAUDE_NOTIFY_WINDOWS === '1';
77
+ }
78
+ if (process.env.CLAUDE_NOTIFY_SOUND !== undefined) {
79
+ config.sound.enabled = process.env.CLAUDE_NOTIFY_SOUND === '1';
80
+ }
81
+ if (process.env.CLAUDE_NOTIFY_VOICE !== undefined) {
82
+ config.voice.enabled = process.env.CLAUDE_NOTIFY_VOICE === '1';
83
+ }
84
+
55
85
  return config;
56
86
  }
57
87
 
@@ -95,33 +125,72 @@ function saveState (state) {
95
125
  // TELEGRAM
96
126
  // ----------------------
97
127
 
98
- async function sendTelegram (config, text) {
99
- if (!config.telegram.token || !config.telegram.chatId) {
128
+ async function sendTelegram (config, state) {
129
+ if (!config.telegram.enabled || !config.telegram.token || !config.telegram.chatId) {
100
130
  return;
101
131
  }
102
132
 
103
- const url =
104
- `https://api.telegram.org/bot${config.telegram.token}/sendMessage`;
133
+ const baseUrl = `https://api.telegram.org/bot${config.telegram.token}`;
105
134
 
135
+ // Send new message and store its id
106
136
  try {
107
- await fetch(url, {
137
+ const res = await fetch(`${baseUrl}/sendMessage`, {
108
138
  method: 'POST',
109
139
  headers: { 'Content-Type': 'application/json' },
110
140
  body: JSON.stringify({
111
141
  chat_id: config.telegram.chatId,
112
- text,
142
+ text: state._telegramText,
113
143
  }),
114
144
  });
145
+ const data = await res.json();
146
+ if (data.ok && data.result?.message_id) {
147
+ if (!state.sentMessages) {
148
+ state.sentMessages = [];
149
+ }
150
+ state.sentMessages.push({
151
+ id: data.result.message_id,
152
+ ts: Date.now(),
153
+ });
154
+ }
115
155
  } catch {
116
156
  // silent fail
117
157
  }
158
+
159
+ // Delete old messages
160
+ const maxAge = (config.telegram.deleteAfterHours || 24) * 3600_000;
161
+ if (state.sentMessages?.length) {
162
+ const now = Date.now();
163
+ const keep = [];
164
+ for (const msg of state.sentMessages) {
165
+ if (now - msg.ts > maxAge) {
166
+ try {
167
+ await fetch(`${baseUrl}/deleteMessage`, {
168
+ method: 'POST',
169
+ headers: { 'Content-Type': 'application/json' },
170
+ body: JSON.stringify({
171
+ chat_id: config.telegram.chatId,
172
+ message_id: msg.id,
173
+ }),
174
+ });
175
+ } catch {
176
+ // silent fail
177
+ }
178
+ } else {
179
+ keep.push(msg);
180
+ }
181
+ }
182
+ state.sentMessages = keep;
183
+ }
118
184
  }
119
185
 
120
186
  // ----------------------
121
187
  // WINDOWS NOTIFICATION
122
188
  // ----------------------
123
189
 
124
- function sendWindowsNotification (message) {
190
+ function sendWindowsNotification (config, message) {
191
+ if (!config.windowsNotification.enabled) {
192
+ return;
193
+ }
125
194
  notifier.notify({
126
195
  title: 'Claude Code',
127
196
  message,
@@ -145,12 +214,30 @@ function playSound (config) {
145
214
  }
146
215
  }
147
216
 
217
+ const voicePhrases = {
218
+ en: (d) => `Claude finished coding in ${d} seconds`,
219
+ ru: (d) => `Клод завершил работу за ${d} секунд`,
220
+ de: (d) => `Claude hat die Arbeit in ${d} Sekunden abgeschlossen`,
221
+ fr: (d) => `Claude a termine en ${d} secondes`,
222
+ es: (d) => `Claude termino en ${d} segundos`,
223
+ pt: (d) => `Claude terminou em ${d} segundos`,
224
+ ja: (d) => `Claudeは${d}秒でコーディングを完了しました`,
225
+ ko: (d) => `Claude가 ${d}초 만에 코딩을 완료했습니다`,
226
+ };
227
+
228
+ function getVoicePhrase (duration) {
229
+ const locale = Intl.DateTimeFormat().resolvedOptions().locale || 'en';
230
+ const lang = locale.split('-')[0].toLowerCase();
231
+ const fn = voicePhrases[lang] || voicePhrases.en;
232
+ return fn(duration);
233
+ }
234
+
148
235
  function speakResult (config, duration) {
149
236
  if (!config.voice.enabled) {
150
237
  return;
151
238
  }
152
239
  try {
153
- say.speak(`Claude finished coding in ${duration} seconds`);
240
+ say.speak(getVoicePhrase(duration));
154
241
  } catch {
155
242
  // silent fail
156
243
  }
@@ -224,8 +311,12 @@ process.stdin.on('end', async () => {
224
311
  const message =
225
312
  `Claude finished coding\n\nProject: ${project}\nDuration: ${duration}s\nStatus: ${status}`;
226
313
 
227
- await sendTelegram(config, `\u{1F916} ${message}`);
228
- sendWindowsNotification(message);
314
+ state._telegramText = `\u{1F916} ${message}`;
315
+ await sendTelegram(config, state);
316
+ delete state._telegramText;
317
+ saveState(state);
318
+
319
+ sendWindowsNotification(config, message);
229
320
  playSound(config);
230
321
  speakResult(config, duration);
231
322
  });
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",
4
+ "version": "1.0.10",
5
5
  "description": "Telegram and Windows notifications for Claude Code task completion",
6
6
  "type": "module",
7
7
  "engines": {