claude-notification-plugin 1.0.59 → 1.0.63

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,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-notification-plugin",
3
- "version": "1.0.59",
3
+ "version": "1.0.63",
4
4
  "description": "Claude Code task-completion notifications: Telegram, desktop notifications (Windows/macOS/Linux), sound, and voice",
5
5
  "author": {
6
6
  "name": "Viacheslav Makarov",
package/README.md CHANGED
@@ -6,18 +6,19 @@ Cross-platform notifications for Claude Code task completion. Sends alerts to Te
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 (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)
9
+ - Sound alert
10
+ - Voice announcement
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)
14
14
  - Debug mode with full hook event dump
15
+ - [Telegram Listener](LISTENER.md) — your remote control for Claude: send a message in Telegram, and the task starts running on your PC
15
16
 
16
17
  ## Install
17
18
 
18
19
  ### Option A: Claude Code Plugin (recommended)
19
20
 
20
- Add the marketplace and install:
21
+ Auto-updates, seamless integration with the plugin ecosystem.
21
22
 
22
23
  ```shell
23
24
  /plugin marketplace add Bazilio-san/claude-plugins
@@ -26,26 +27,15 @@ Add the marketplace and install:
26
27
  /claude-notification-plugin:setup
27
28
  ```
28
29
 
29
- For a detailed visual walkthrough, see [step-by-step installation guide with screenshots](INSTALL_MARKETPLACE_AND_PLUGIN.md).
30
-
31
- Or load directly for testing:
32
-
33
- ```bash
34
- claude --plugin-dir /path/to/claude-notification-plugin
35
- ```
36
-
37
- Hooks are registered automatically.
38
-
39
- To enable auto-updates, go to `/plugin` → **Marketplaces** tab → select `bazilio-plugins` → **Enable auto-update**.
30
+ Go to `/plugin` **Marketplaces** tab → select `bazilio-plugins` **Enable auto-update**.
40
31
 
41
- To configure Telegram credentials, run:
32
+ For a detailed visual walkthrough, see [step-by-step installation guide with screenshots](INSTALL_MARKETPLACE_AND_PLUGIN.md).
42
33
 
43
- ```shell
44
- /claude-notification-plugin:setup
45
- ```
46
34
 
47
35
  ### Option B: npm global package
48
36
 
37
+ Simple install, works without the plugin system.
38
+
49
39
  ```bash
50
40
  npm install -g claude-notification-plugin
51
41
  claude-notify-install
@@ -89,39 +79,27 @@ Config file: `~/.claude/notifier.config.json`
89
79
  }
90
80
  ```
91
81
 
92
- Each channel has an `enabled` flag (`true`/`false`) for global control.
93
-
94
- `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`).
95
-
96
- `deleteAfterHours` auto-delete old Telegram messages after the specified number of hours (default: `24`, set `0` to disable).
97
-
98
- `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.
99
-
100
- `notifyOnWaiting` send notifications when Claude is waiting for user input, e.g. permission prompts (default: `false`, set `true` to enable).
101
-
102
- `webhookUrl` — URL to send a POST request with full notification data (JSON). If empty, no request is sent. On `Stop`/`Notification` events the payload includes `title`, `project`, `branch`, `duration`, `trigger`, `voicePhrase`, and `hookEvent` (the raw hook input). Useful for integrating with custom dashboards, logging services, or automation pipelines.
103
-
104
- `sendUserPromptToWebhook` — also send user prompts to the webhook URL (default: `false`). When enabled, each `UserPromptSubmit` event sends a POST with `title`, `project`, `trigger`, `prompt` (user's message text), and `hookEvent`. Requires `webhookUrl` to be set.
105
-
106
- `debug` include extra info in notifications: voice phrase text, full hook event JSON (formatted as code block in Telegram). Default: `false`.
107
-
108
- Environment variables `CLAUDE_NOTIFY_TELEGRAM_TOKEN` and `CLAUDE_NOTIFY_TELEGRAM_CHAT_ID` override config file values.
109
-
110
- ### Per-channel environment variables
111
-
112
- These env vars override the global config per channel (`"1"` = on, `"0"` = off):
113
-
114
- | Variable | Channel |
115
- |-------------------------------------------------------|-----------------------------------------|
116
- | `CLAUDE_NOTIFY_TELEGRAM` | Telegram messages |
117
- | `CLAUDE_NOTIFY_DESKTOP` | Desktop notifications |
118
- | `CLAUDE_NOTIFY_SOUND` | Sound alert |
119
- | `CLAUDE_NOTIFY_VOICE` | Voice announcement (TTS) |
120
- | `CLAUDE_NOTIFY_WAITING` | Waiting-for-input events |
121
- | `CLAUDE_NOTIFY_DEBUG` | Debug mode |
122
- | `CLAUDE_NOTIFY_INCLUDE_LAST_CC_MESSAGE_IN_TELEGRAM` | Include last Claude message in Telegram |
123
- | `CLAUDE_NOTIFY_WEBHOOK_URL` | Webhook URL for POST requests |
124
- | `CLAUDE_NOTIFY_SEND_USER_PROMPT_TO_WEBHOOK` | Send user prompts to webhook |
82
+ Each channel has an `enabled` flag (`true`/`false`) for global control. Environment variables override config values (`"1"` = on, `"0"` = off, or a string for URLs).
83
+
84
+ | Config option | Env var override | Default | Description |
85
+ |---|---|---|---|
86
+ | `telegram.enabled` | `CLAUDE_NOTIFY_TELEGRAM` | `true` | Telegram messages |
87
+ | `telegram.token` | `CLAUDE_NOTIFY_TELEGRAM_TOKEN` | — | Bot token |
88
+ | `telegram.chatId` | `CLAUDE_NOTIFY_TELEGRAM_CHAT_ID` | | Chat ID |
89
+ | `telegram.deleteAfterHours` | — | `24` | Auto-delete old messages (hours, `0` to disable) |
90
+ | `telegram.includeLastCcMessageInTelegram` | `CLAUDE_NOTIFY_INCLUDE_LAST_CC_MESSAGE_IN_TELEGRAM` | `true` | Append Claude's last message to Telegram notification (truncated to 3500 chars) |
91
+ | `desktopNotification.enabled` | `CLAUDE_NOTIFY_DESKTOP` | `true` | Desktop notifications (toast / Notification Center / notify-send) |
92
+ | `sound.enabled` | `CLAUDE_NOTIFY_SOUND` | `true` | Sound alert |
93
+ | `sound.file` | — | platform default | Path to a custom sound file |
94
+ | `voice.enabled` | `CLAUDE_NOTIFY_VOICE` | `true` | Voice announcement (TTS) |
95
+ | `notifyOnWaiting` | `CLAUDE_NOTIFY_WAITING` | `false` | Notify when Claude is waiting for input (e.g. permission prompts) |
96
+ | `webhookUrl` | `CLAUDE_NOTIFY_WEBHOOK_URL` | `""` | POST notification data (JSON) to this URL. Payload: `title`, `project`, `branch`, `duration`, `trigger`, `voicePhrase`, `hookEvent` |
97
+ | `sendUserPromptToWebhook` | `CLAUDE_NOTIFY_SEND_USER_PROMPT_TO_WEBHOOK` | `false` | Also send user prompts to the webhook. Payload: `title`, `project`, `trigger`, `prompt`, `hookEvent` |
98
+ | `minSeconds` | — | `15` | Skip notifications for tasks shorter than this (seconds) |
99
+ | `debug` | `CLAUDE_NOTIFY_DEBUG` | `false` | Include voice phrase text and full hook event JSON in notifications |
100
+ | | `CLAUDE_NOTIFY_DISABLE` | `0` | Disable all notifications (`1` to disable) |
101
+
102
+ Default sound files: Windows `C:/Windows/Media/notify.wav`, macOS `/System/Library/Sounds/Glass.aiff`, Linux `/usr/share/sounds/freedesktop/stereo/complete.oga`.
125
103
 
126
104
  ### Per-project configuration
127
105
 
@@ -164,7 +142,6 @@ Notifications include project name, git branch (when available), duration, and t
164
142
  Project: my-project
165
143
  Branch: feature-auth
166
144
  Duration: 45s
167
- Trigger: Stop
168
145
  ```
169
146
 
170
147
  When Claude is waiting for input (and `notifyOnWaiting` is enabled):
@@ -175,7 +152,6 @@ When Claude is waiting for input (and `notifyOnWaiting` is enabled):
175
152
  Project: my-project
176
153
  Branch: feature-auth
177
154
  Duration: 30s
178
- Trigger: Notification
179
155
  ```
180
156
 
181
157
  ## Telegram Setup
@@ -190,19 +166,31 @@ Trigger: Notification
190
166
 
191
167
  Alternative for Chat ID: add **@userinfobot** to a chat and it will reply with the ID.
192
168
 
169
+ ## Telegram Listener (Telegram → Claude Code)
170
+
171
+ Your remote control for Claude: send a message in Telegram, and the task starts running on your PC. See **[LISTENER.md](LISTENER.md)** for the full guide.
172
+
193
173
  ## Uninstall
194
174
 
195
175
  ### Plugin install
196
176
 
197
177
  Uninstall via the plugin manager or remove `--plugin-dir` flag.
198
178
 
199
- ### npm install
179
+ ### npm
200
180
 
201
181
  ```bash
202
182
  claude-notify-uninstall
203
183
  npm uninstall -g claude-notification-plugin
204
184
  ```
205
185
 
186
+ ## Testing (load without install)
187
+
188
+ ```bash
189
+ claude --plugin-dir /path/to/claude-notification-plugin
190
+ ```
191
+
192
+ Hooks are registered automatically.
193
+
206
194
  ## License
207
195
 
208
196
  MIT
@@ -0,0 +1,255 @@
1
+ #!/usr/bin/env node
2
+
3
+ import fs from 'fs';
4
+ import os from 'os';
5
+ import path from 'path';
6
+ import { spawn, execSync } from 'child_process';
7
+ import { fileURLToPath } from 'url';
8
+
9
+ const __filename = fileURLToPath(import.meta.url);
10
+ const __dirname = path.dirname(__filename);
11
+
12
+ const PID_FILE = path.join(os.homedir(), '.claude', '.listener.pid');
13
+ const LOG_FILE = path.join(os.homedir(), '.claude', '.listener.log');
14
+ const CONFIG_FILE = path.join(os.homedir(), '.claude', 'notifier.config.json');
15
+ const LISTENER_SCRIPT = path.join(__dirname, '..', 'listener', 'listener.js');
16
+
17
+ const command = process.argv[2];
18
+
19
+ switch (command) {
20
+ case 'start':
21
+ startDaemon();
22
+ break;
23
+ case 'stop':
24
+ stopDaemon();
25
+ break;
26
+ case 'status':
27
+ showStatus();
28
+ break;
29
+ case 'logs':
30
+ showLogs();
31
+ break;
32
+ case 'restart':
33
+ stopDaemon();
34
+ setTimeout(() => startDaemon(), 1000);
35
+ break;
36
+ default:
37
+ console.log('Usage: claude-notify-listener <start|stop|status|logs|restart>');
38
+ console.log('');
39
+ console.log('Commands:');
40
+ console.log(' start Start the listener daemon');
41
+ console.log(' stop Stop the listener daemon');
42
+ console.log(' status Show daemon status');
43
+ console.log(' logs Show recent log entries');
44
+ console.log(' restart Restart the daemon');
45
+ process.exit(command ? 1 : 0);
46
+ }
47
+
48
+ function startDaemon () {
49
+ // Check if already running
50
+ const existingPid = readPid();
51
+ if (existingPid && isProcessAlive(existingPid)) {
52
+ console.log(`Listener is already running (PID: ${existingPid})`);
53
+ process.exit(1);
54
+ }
55
+
56
+ // Clean stale PID file
57
+ if (existingPid) {
58
+ try {
59
+ fs.unlinkSync(PID_FILE);
60
+ } catch {
61
+ // ignore
62
+ }
63
+ }
64
+
65
+ // Validate config
66
+ if (!fs.existsSync(CONFIG_FILE)) {
67
+ console.error(`Config not found: ${CONFIG_FILE}`);
68
+ console.error('Run claude-notify-install first, or create the config manually.');
69
+ process.exit(1);
70
+ }
71
+
72
+ let config;
73
+ try {
74
+ config = JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf-8'));
75
+ } catch (err) {
76
+ console.error(`Invalid config: ${err.message}`);
77
+ process.exit(1);
78
+ }
79
+
80
+ const token = process.env.CLAUDE_NOTIFY_TELEGRAM_TOKEN || config.telegramToken || config.telegram?.token;
81
+ const chatId = process.env.CLAUDE_NOTIFY_TELEGRAM_CHAT_ID || config.telegramChatId || config.telegram?.chatId;
82
+
83
+ if (!token || !chatId) {
84
+ console.error('Missing telegramToken or telegramChatId in config.');
85
+ console.error('These are required for the listener to receive messages.');
86
+ process.exit(1);
87
+ }
88
+
89
+ if (!config.listener?.projects || Object.keys(config.listener.projects).length === 0) {
90
+ console.error('No projects defined in config.listener.projects');
91
+ console.error('');
92
+ console.error('Add projects to your config:');
93
+ console.error(JSON.stringify({
94
+ listener: {
95
+ projects: {
96
+ default: { path: '/path/to/your/project' },
97
+ },
98
+ },
99
+ }, null, 2));
100
+ process.exit(1);
101
+ }
102
+
103
+ // Ensure log directory exists
104
+ fs.mkdirSync(path.dirname(LOG_FILE), { recursive: true });
105
+
106
+ // Open log file for child stdio
107
+ const logFd = fs.openSync(LOG_FILE, 'a');
108
+
109
+ // Spawn detached child
110
+ const child = spawn(process.execPath, [LISTENER_SCRIPT], {
111
+ detached: true,
112
+ stdio: ['ignore', logFd, logFd],
113
+ env: { ...process.env },
114
+ windowsHide: true,
115
+ });
116
+
117
+ child.unref();
118
+ fs.closeSync(logFd);
119
+
120
+ // Write PID
121
+ fs.mkdirSync(path.dirname(PID_FILE), { recursive: true });
122
+ fs.writeFileSync(PID_FILE, String(child.pid));
123
+
124
+ console.log(`Listener started (PID: ${child.pid})`);
125
+ console.log(`Log: ${LOG_FILE}`);
126
+ console.log(`Projects: ${Object.keys(config.listener.projects).join(', ')}`);
127
+ }
128
+
129
+ function stopDaemon () {
130
+ const pid = readPid();
131
+ if (!pid) {
132
+ console.log('Listener is not running (no PID file)');
133
+ return;
134
+ }
135
+
136
+ if (!isProcessAlive(pid)) {
137
+ console.log(`Listener is not running (stale PID: ${pid})`);
138
+ cleanPid();
139
+ return;
140
+ }
141
+
142
+ console.log(`Stopping listener (PID: ${pid})...`);
143
+
144
+ try {
145
+ if (process.platform === 'win32') {
146
+ execSync(`taskkill /PID ${pid} /T /F`, {
147
+ stdio: 'ignore',
148
+ windowsHide: true,
149
+ });
150
+ } else {
151
+ process.kill(pid, 'SIGTERM');
152
+ // Wait for graceful shutdown
153
+ let tries = 10;
154
+ while (tries-- > 0 && isProcessAlive(pid)) {
155
+ execSync('sleep 0.5', { stdio: 'ignore' });
156
+ }
157
+ if (isProcessAlive(pid)) {
158
+ process.kill(pid, 'SIGKILL');
159
+ }
160
+ }
161
+ } catch {
162
+ // Process may already be dead
163
+ }
164
+
165
+ cleanPid();
166
+ console.log('Listener stopped');
167
+ }
168
+
169
+ function showStatus () {
170
+ const pid = readPid();
171
+ if (!pid) {
172
+ console.log('Status: not running');
173
+ return;
174
+ }
175
+
176
+ if (!isProcessAlive(pid)) {
177
+ console.log(`Status: not running (stale PID: ${pid})`);
178
+ cleanPid();
179
+ return;
180
+ }
181
+
182
+ console.log(`Status: running (PID: ${pid})`);
183
+ console.log(`Log: ${LOG_FILE}`);
184
+
185
+ // Show last few log lines
186
+ try {
187
+ if (fs.existsSync(LOG_FILE)) {
188
+ const content = fs.readFileSync(LOG_FILE, 'utf-8');
189
+ const lines = content.trim().split('\n');
190
+ const last = lines.slice(-5);
191
+ console.log('\nRecent log:');
192
+ for (const line of last) {
193
+ console.log(` ${line}`);
194
+ }
195
+ }
196
+ } catch {
197
+ // ignore
198
+ }
199
+ }
200
+
201
+ function showLogs () {
202
+ try {
203
+ if (!fs.existsSync(LOG_FILE)) {
204
+ console.log('No log file found');
205
+ return;
206
+ }
207
+ const content = fs.readFileSync(LOG_FILE, 'utf-8');
208
+ const lines = content.trim().split('\n');
209
+ const last = lines.slice(-50);
210
+ for (const line of last) {
211
+ console.log(line);
212
+ }
213
+ } catch (err) {
214
+ console.error(`Failed to read log: ${err.message}`);
215
+ }
216
+ }
217
+
218
+ function readPid () {
219
+ try {
220
+ if (fs.existsSync(PID_FILE)) {
221
+ const pid = parseInt(fs.readFileSync(PID_FILE, 'utf-8').trim(), 10);
222
+ return isNaN(pid) ? null : pid;
223
+ }
224
+ } catch {
225
+ // ignore
226
+ }
227
+ return null;
228
+ }
229
+
230
+ function cleanPid () {
231
+ try {
232
+ if (fs.existsSync(PID_FILE)) {
233
+ fs.unlinkSync(PID_FILE);
234
+ }
235
+ } catch {
236
+ // ignore
237
+ }
238
+ }
239
+
240
+ function isProcessAlive (pid) {
241
+ try {
242
+ if (process.platform === 'win32') {
243
+ const result = execSync(`tasklist /FI "PID eq ${pid}" /NH`, {
244
+ encoding: 'utf-8',
245
+ windowsHide: true,
246
+ stdio: ['pipe', 'pipe', 'ignore'],
247
+ });
248
+ return result.includes(String(pid));
249
+ }
250
+ process.kill(pid, 0);
251
+ return true;
252
+ } catch {
253
+ return false;
254
+ }
255
+ }
@@ -0,0 +1,100 @@
1
+ ---
2
+ description: Manage the Telegram Listener daemon (start, stop, status, logs, restart)
3
+ ---
4
+
5
+ # Listener Management
6
+
7
+ Manage the Telegram Listener daemon that receives tasks from Telegram and executes them via `claude -p`.
8
+
9
+ The user invokes this command as `/claude-notification-plugin:listener <action>`.
10
+
11
+ ## Actions
12
+
13
+ Determine the action from the user's input. If no action is provided, show the help text below and ask which action they want.
14
+
15
+ ### start
16
+
17
+ Start the listener daemon.
18
+
19
+ 1. Determine the plugin root path — use the directory where this command file lives: `${CLAUDE_PLUGIN_ROOT}`. The listener CLI script is at `${CLAUDE_PLUGIN_ROOT}/bin/listener-cli.js`.
20
+ 2. Run in a shell:
21
+ ```bash
22
+ node "${CLAUDE_PLUGIN_ROOT}/bin/listener-cli.js" start
23
+ ```
24
+ 3. Show the output to the user. If it says "Listener started", confirm success. If there is an error (missing config, missing projects, already running), explain what to do.
25
+
26
+ ### stop
27
+
28
+ Stop the listener daemon.
29
+
30
+ 1. Run:
31
+ ```bash
32
+ node "${CLAUDE_PLUGIN_ROOT}/bin/listener-cli.js" stop
33
+ ```
34
+ 2. Show the output.
35
+
36
+ ### status
37
+
38
+ Show the listener status.
39
+
40
+ 1. Run:
41
+ ```bash
42
+ node "${CLAUDE_PLUGIN_ROOT}/bin/listener-cli.js" status
43
+ ```
44
+ 2. Show the output.
45
+
46
+ ### logs
47
+
48
+ Show recent log entries.
49
+
50
+ 1. Run:
51
+ ```bash
52
+ node "${CLAUDE_PLUGIN_ROOT}/bin/listener-cli.js" logs
53
+ ```
54
+ 2. Show the output.
55
+
56
+ ### restart
57
+
58
+ Restart the listener daemon.
59
+
60
+ 1. Run:
61
+ ```bash
62
+ node "${CLAUDE_PLUGIN_ROOT}/bin/listener-cli.js" restart
63
+ ```
64
+ 2. Show the output.
65
+
66
+ ## Help text
67
+
68
+ If the user doesn't specify an action, show:
69
+
70
+ ```
71
+ Telegram Listener — receives tasks from Telegram and executes via claude -p.
72
+
73
+ Usage:
74
+ /claude-notification-plugin:listener start — Start the daemon
75
+ /claude-notification-plugin:listener stop — Stop the daemon
76
+ /claude-notification-plugin:listener status — Show status
77
+ /claude-notification-plugin:listener logs — Show recent logs
78
+ /claude-notification-plugin:listener restart — Restart the daemon
79
+
80
+ Prerequisites:
81
+ 1. Telegram bot configured (token + chatId in ~/.claude/notifier.config.json)
82
+ 2. "listener.projects" section in config with at least one project
83
+
84
+ See LISTENER.md for detailed documentation.
85
+ ```
86
+
87
+ ## Important
88
+
89
+ - The `${CLAUDE_PLUGIN_ROOT}` variable resolves to the plugin installation directory. Always use it to build the path to `bin/listener-cli.js`.
90
+ - If the config doesn't have a `listener.projects` section, tell the user to add one and show a minimal example:
91
+ ```json
92
+ {
93
+ "listener": {
94
+ "projects": {
95
+ "default": { "path": "/path/to/your/project" }
96
+ }
97
+ }
98
+ }
99
+ ```
100
+ - If Telegram credentials are missing, suggest running `/claude-notification-plugin:setup` first.