claude-notification-plugin 1.0.90 → 1.0.93

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.90",
3
+ "version": "1.0.93",
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
@@ -25,7 +25,7 @@ npm install -g claude-notification-plugin
25
25
  ```
26
26
 
27
27
  The installer prompts for Telegram bot credentials and sets everything up.
28
- Re-run `claude-notify-install` anytime to reconfigure.
28
+ Re-run `claude-notify install` anytime to reconfigure.
29
29
 
30
30
  ## Uninstall
31
31
 
@@ -186,7 +186,7 @@ The `"default"` alias receives messages without a `@project` prefix.
186
186
  ### 2. Start the listener
187
187
 
188
188
  ```bash
189
- claude-notify-listener start
189
+ claude-notify listener start
190
190
  ```
191
191
 
192
192
  ### 3. Send tasks from Telegram
@@ -209,10 +209,10 @@ The bot replies with status and results:
209
209
  ### 4. Manage the daemon
210
210
 
211
211
  ```bash
212
- claude-notify-listener status # Check if running
213
- claude-notify-listener stop # Stop
214
- claude-notify-listener restart # Restart
215
- claude-notify-listener logs # View last 50 log lines
212
+ claude-notify listener status # Check if running
213
+ claude-notify listener stop # Stop
214
+ claude-notify listener restart # Restart
215
+ claude-notify listener logs # View last 50 log lines
216
216
  ```
217
217
 
218
218
  ### 5. Bot commands
@@ -266,10 +266,11 @@ Worktrees are auto-created when you use `@project/branch` syntax (controlled by
266
266
 
267
267
  ## CLI Commands
268
268
 
269
- | Command | Description |
270
- |--------------------------|-------------------------------------------------------------------------------|
271
- | `claude-notify-install` | Re-run to reinstall plugin registration, Telegram config, hooks, CLI wrappers |
272
- | `claude-notify-listener` | Manage the Telegram Listener daemon (start/stop/status/logs/restart) |
269
+ ```
270
+ claude-notify install Reinstall plugin registration, Telegram config, hooks
271
+ claude-notify listener <action> Manage the Telegram Listener daemon
272
+ Actions: start, stop, status, logs, restart
273
+ ```
273
274
 
274
275
  ## License
275
276
 
package/bin/cli.js ADDED
@@ -0,0 +1,37 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { fileURLToPath } from 'url';
4
+ import path from 'path';
5
+
6
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
7
+
8
+ const command = process.argv[2];
9
+
10
+ switch (command) {
11
+ case 'install':
12
+ await import(path.join(__dirname, 'install.js'));
13
+ break;
14
+ case 'uninstall':
15
+ await import(path.join(__dirname, 'uninstall.js'));
16
+ break;
17
+ case 'listener': {
18
+ // Shift argv so listener-cli.js sees subcommand as argv[2]
19
+ process.argv = [process.argv[0], process.argv[1], ...process.argv.slice(3)];
20
+ await import(path.join(__dirname, 'listener-cli.js'));
21
+ break;
22
+ }
23
+ default:
24
+ // Hook mode: Claude Code pipes JSON to stdin with no args
25
+ if (!process.stdin.isTTY) {
26
+ await import(path.join(__dirname, '..', 'notifier', 'notifier.js'));
27
+ } else {
28
+ console.log('Usage: claude-notify <command> [options]');
29
+ console.log('');
30
+ console.log('Commands:');
31
+ console.log(' install Setup plugin registration, Telegram config, hooks');
32
+ console.log(' uninstall Remove plugin, hooks, config, CLI wrappers');
33
+ console.log(' listener <action> Manage the Telegram Listener daemon');
34
+ console.log(' Actions: start, stop, status, logs, restart');
35
+ process.exit(command ? 1 : 0);
36
+ }
37
+ }
package/bin/install.js CHANGED
@@ -20,17 +20,14 @@ const knownMarketplacesPath = path.join(pluginsDir, 'known_marketplaces.json');
20
20
  const marketplacesDir = path.join(pluginsDir, 'marketplaces');
21
21
  const RESOLVER_PATH = path.join(claudeDir, 'claude-notify-resolve.js');
22
22
 
23
- const HOOK_COMMAND = 'claude-notifier';
23
+ const HOOK_COMMAND = 'claude-notify';
24
24
  const MARKETPLACE_KEY = 'bazilio-plugins';
25
25
  const PLUGIN_KEY = 'claude-notification-plugin@bazilio-plugins';
26
26
  const MARKETPLACE_REPO = 'https://github.com/Bazilio-san/claude-plugins.git';
27
27
  const MARKETPLACE_GITHUB = 'Bazilio-san/claude-plugins';
28
28
 
29
- const CLI_BINS = {
30
- 'claude-notify-listener': 'bin/listener-cli.js',
31
- 'claude-notify-install': 'bin/install.js',
32
- 'claude-notify-uninstall': 'bin/uninstall.js',
33
- };
29
+ const CLI_BIN_NAME = 'claude-notify';
30
+ const CLI_BIN_TARGET = 'bin/cli.js';
34
31
 
35
32
  // ──────────────────────────────────────
36
33
  // Plugin registration
@@ -217,34 +214,39 @@ function registerCli () {
217
214
 
218
215
  const isWindows = process.platform === 'win32';
219
216
  const resolverNative = RESOLVER_PATH.replace(/\//g, path.sep);
220
- const created = [];
221
-
222
- for (const [name, relPath] of Object.entries(CLI_BINS)) {
223
- if (isWindows) {
224
- const cmdFile = path.join(binDir, `${name}.cmd`);
225
- const content = `@echo off\r\nnode "${resolverNative}" ${relPath} %*\r\n`;
226
- fs.writeFileSync(cmdFile, content);
227
- created.push(cmdFile);
228
- } else {
229
- const shFile = path.join(binDir, name);
230
- const content = `#!/bin/sh\nexec node "${RESOLVER_PATH}" "${relPath}" "$@"\n`;
231
- fs.writeFileSync(shFile, content, { mode: 0o755 });
232
- created.push(shFile);
233
- }
234
- }
217
+ let wrapperPath;
235
218
 
236
- if (created.length > 0) {
237
- console.log(' CLI commands registered:');
238
- for (const f of created) {
239
- console.log(` ${f}`);
240
- }
219
+ if (isWindows) {
220
+ wrapperPath = path.join(binDir, `${CLI_BIN_NAME}.cmd`);
221
+ const content = `@echo off\r\nnode "${resolverNative}" ${CLI_BIN_TARGET} %*\r\n`;
222
+ fs.writeFileSync(wrapperPath, content);
223
+ } else {
224
+ wrapperPath = path.join(binDir, CLI_BIN_NAME);
225
+ const content = `#!/bin/sh\nexec node "${RESOLVER_PATH}" "${CLI_BIN_TARGET}" "$@"\n`;
226
+ fs.writeFileSync(wrapperPath, content, { mode: 0o755 });
241
227
  }
228
+
229
+ console.log(` CLI wrapper registered: ${wrapperPath}`);
242
230
  }
243
231
 
244
232
  // ──────────────────────────────────────
245
233
  // Helpers
246
234
  // ──────────────────────────────────────
247
235
 
236
+ function openTtyInput () {
237
+ // If stdin is already a TTY (e.g. local `npm install`), use it directly
238
+ if (process.stdin.isTTY) {
239
+ return process.stdin;
240
+ }
241
+ // Otherwise try to open the TTY device directly (works even when npm pipes stdin)
242
+ try {
243
+ const ttyPath = process.platform === 'win32' ? '\\\\.\\CON' : '/dev/tty';
244
+ return fs.createReadStream(ttyPath, { encoding: 'utf-8' });
245
+ } catch {
246
+ return null;
247
+ }
248
+ }
249
+
248
250
  function ask (rl, question) {
249
251
  return new Promise((resolve) => {
250
252
  rl.question(question, (answer) => resolve(answer.trim()));
@@ -322,16 +324,6 @@ async function main () {
322
324
  console.log(' Plugin registered.');
323
325
 
324
326
  // 2. Interactive Telegram setup
325
- const rl = readline.createInterface({
326
- input: process.stdin,
327
- output: process.stdout,
328
- });
329
-
330
- console.log('');
331
- console.log('Claude Notification Plugin - Setup');
332
- console.log('==================================');
333
- console.log('');
334
-
335
327
  let existing = {};
336
328
  if (fs.existsSync(configPath)) {
337
329
  try {
@@ -347,41 +339,65 @@ async function main () {
347
339
  let token = existingToken;
348
340
  let chatId = existingChatId;
349
341
 
350
- if (existingToken) {
351
- const masked = existingToken.slice(0, 6) + '...' + existingToken.slice(-4);
352
- console.log(`Telegram token found: ${masked}`);
353
- const reuse = await ask(rl, 'Keep existing token? (Y/n): ');
354
- if (reuse.toLowerCase() === 'n') {
355
- token = await ask(rl, 'New Bot Token: ');
356
- chatId = '';
357
- }
358
- } else {
359
- const useTelegram = await ask(rl, 'Configure Telegram? (y/N): ');
360
- if (useTelegram.toLowerCase() === 'y') {
361
- token = await ask(rl, 'Bot Token: ');
362
- }
363
- }
342
+ const ttyInput = openTtyInput();
343
+ if (ttyInput) {
344
+ const rl = readline.createInterface({
345
+ input: ttyInput,
346
+ output: process.stdout,
347
+ });
364
348
 
365
- if (token && !chatId) {
366
349
  console.log('');
367
- console.log('Send any message to your bot in Telegram, then press Enter.');
368
- await ask(rl, '');
350
+ console.log('Claude Notification Plugin - Setup');
351
+ console.log('==================================');
352
+ console.log('');
369
353
 
370
- console.log('Fetching Chat ID...');
371
- chatId = await fetchChatId(token);
354
+ if (existingToken) {
355
+ const masked = existingToken.slice(0, 6) + '...' + existingToken.slice(-4);
356
+ console.log(`Telegram token found: ${masked}`);
357
+ const reuse = await ask(rl, 'Keep existing token? (Y/n): ');
358
+ if (reuse.toLowerCase() === 'n') {
359
+ token = await ask(rl, 'New Bot Token: ');
360
+ chatId = '';
361
+ }
362
+ } else {
363
+ const useTelegram = await ask(rl, 'Configure Telegram? (y/N): ');
364
+ if (useTelegram.toLowerCase() === 'y') {
365
+ token = await ask(rl, 'Bot Token: ');
366
+ }
367
+ }
368
+
369
+ if (token && !chatId) {
370
+ console.log('');
371
+ console.log('Send any message to your bot in Telegram, then press Enter.');
372
+ await ask(rl, '');
373
+
374
+ console.log('Fetching Chat ID...');
375
+ chatId = await fetchChatId(token);
376
+
377
+ if (chatId) {
378
+ console.log('Chat ID detected: ' + chatId);
379
+ } else {
380
+ console.log('Could not detect Chat ID automatically.');
381
+ chatId = await ask(rl, 'Enter Chat ID manually: ');
382
+ }
383
+ } else if (token && chatId) {
384
+ console.log(`Chat ID: ${chatId}`);
385
+ }
372
386
 
373
- if (chatId) {
374
- console.log('Chat ID detected: ' + chatId);
387
+ rl.close();
388
+ if (ttyInput !== process.stdin) {
389
+ ttyInput.destroy();
390
+ }
391
+ } else {
392
+ console.log('');
393
+ console.log('Non-interactive install (stdin is not a terminal).');
394
+ if (token && chatId) {
395
+ console.log('Telegram: using existing config.');
375
396
  } else {
376
- console.log('Could not detect Chat ID automatically.');
377
- chatId = await ask(rl, 'Enter Chat ID manually: ');
397
+ console.log('Telegram: skipped. Run "claude-notify install" to configure.');
378
398
  }
379
- } else if (token && chatId) {
380
- console.log(`Chat ID: ${chatId}`);
381
399
  }
382
400
 
383
- rl.close();
384
-
385
401
  // 3. Write config
386
402
  fs.mkdirSync(claudeDir, { recursive: true });
387
403
 
@@ -44,7 +44,7 @@ switch (command) {
44
44
  setTimeout(() => startDaemon(), 1000);
45
45
  break;
46
46
  default:
47
- console.log('Usage: claude-notify-listener <start|stop|status|logs|restart>');
47
+ console.log('Usage: claude-notify listener <start|stop|status|logs|restart>');
48
48
  console.log('');
49
49
  console.log('Commands:');
50
50
  console.log(' start Start the listener daemon');
@@ -75,7 +75,7 @@ function startDaemon () {
75
75
  // Validate config
76
76
  if (!fs.existsSync(CONFIG_FILE)) {
77
77
  console.error(`Config not found: ${CONFIG_FILE}`);
78
- console.error('Run claude-notify-install first, or create the config manually.');
78
+ console.error('Run claude-notify install first, or create the config manually.');
79
79
  process.exit(1);
80
80
  }
81
81
 
package/bin/uninstall.js CHANGED
@@ -11,7 +11,7 @@ const configPath = path.join(claudeDir, 'notifier.config.json');
11
11
  const settingsPath = path.join(claudeDir, 'settings.json');
12
12
  const statePath = path.join(claudeDir, '.notifier_state.json');
13
13
 
14
- const HOOK_COMMAND = 'claude-notifier';
14
+ const HOOK_COMMAND = 'claude-notify';
15
15
 
16
16
  // Remove hooks from settings.json
17
17
  if (fs.existsSync(settingsPath)) {
@@ -49,7 +49,8 @@ for (const file of [configPath, statePath, resolverPath]) {
49
49
  }
50
50
 
51
51
  // Remove CLI wrapper scripts
52
- const CLI_BINS = ['claude-notify-listener', 'claude-notify-install', 'claude-notify-uninstall'];
52
+ // Current name + legacy names from previous versions
53
+ const WRAPPER_NAMES = ['claude-notify', 'claude-notify-listener', 'claude-notify-install', 'claude-notify-uninstall'];
53
54
  let cliBinsRemoved = false;
54
55
  const ext = process.platform === 'win32' ? '.cmd' : '';
55
56
 
@@ -69,7 +70,7 @@ try {
69
70
  wrapperDirs.add(path.join(home, '.local', 'bin'));
70
71
 
71
72
  for (const dir of wrapperDirs) {
72
- for (const name of CLI_BINS) {
73
+ for (const name of WRAPPER_NAMES) {
73
74
  const filePath = path.join(dir, `${name}${ext}`);
74
75
  if (fs.existsSync(filePath)) {
75
76
  fs.unlinkSync(filePath);
@@ -87,7 +87,7 @@ The `offset` parameter in the request ensures each message is processed exactly
87
87
 
88
88
  ## Detached process: why the listener lives without a terminal
89
89
 
90
- When you run `claude-notify-listener start`, the following happens:
90
+ When you run `claude-notify listener start`, the following happens:
91
91
 
92
92
  ```
93
93
  Your terminal
@@ -117,7 +117,7 @@ Your terminal
117
117
  `child.unref()` — allows `listener-cli.js` to exit without waiting for the child.
118
118
 
119
119
  Result: the listener runs as a background OS process. It is not tied to a terminal or to Claude Code — only to the operating system. It will only stop when:
120
- - The `claude-notify-listener stop` command is issued (or `/stop` in Telegram)
120
+ - The `claude-notify listener stop` command is issued (or `/stop` in Telegram)
121
121
  - The computer is shut down or restarted
122
122
  - It crashes due to an error
123
123
 
@@ -856,7 +856,7 @@ exec(`claude -p "${userText}"`)
856
856
  ### Listener won't start
857
857
 
858
858
  ```bash
859
- claude-notify-listener status
859
+ claude-notify listener status
860
860
  # → Status: not running
861
861
  ```
862
862
 
@@ -865,11 +865,11 @@ Check:
865
865
  1. Does the config exist? `cat ~/.claude/notifier.config.json`
866
866
  2. Are `telegram.token` and `telegram.chatId` present?
867
867
  3. Is there a `listener.projects` section?
868
- 4. Logs: `claude-notify-listener logs`
868
+ 4. Logs: `claude-notify listener logs`
869
869
 
870
870
  ### Bot doesn't respond
871
871
 
872
- 1. Is the listener running? `claude-notify-listener status`
872
+ 1. Is the listener running? `claude-notify listener status`
873
873
  2. Is the chatId correct? Messages from other chats are ignored (check the log: `WARN Ignored message from chat ...`)
874
874
  3. Is the bot added to the chat? Write `/help` to the bot — if there's no response, check the token
875
875
 
@@ -882,7 +882,7 @@ Check:
882
882
  Or restart the listener:
883
883
 
884
884
  ```bash
885
- claude-notify-listener restart
885
+ claude-notify listener restart
886
886
  ```
887
887
 
888
888
  The watchdog will automatically clear stale tasks on the next startup.
@@ -925,7 +925,7 @@ Suppose you have two projects: an API server and a web application.
925
925
  ```
926
926
  === 10:00 — Startup ===
927
927
 
928
- You (terminal): claude-notify-listener start
928
+ You (terminal): claude-notify listener start
929
929
  → Listener started (PID: 12345)
930
930
 
931
931
  === 10:01 — First task ===
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.90",
4
+ "version": "1.0.93",
5
5
  "description": "Claude Code task-completion notifications: Telegram, desktop notifications (Windows/macOS/Linux), sound, and voice",
6
6
  "type": "module",
7
7
  "engines": {
@@ -17,10 +17,7 @@
17
17
  "LICENSE"
18
18
  ],
19
19
  "bin": {
20
- "claude-notify-install": "bin/install.js",
21
- "claude-notify-uninstall": "bin/uninstall.js",
22
- "claude-notifier": "notifier/notifier.js",
23
- "claude-notify-listener": "bin/listener-cli.js"
20
+ "claude-notify": "bin/cli.js"
24
21
  },
25
22
  "scripts": {
26
23
  "preuninstall": "node bin/uninstall.js",