claude-notification-plugin 1.0.105 → 1.0.107

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.105",
3
+ "version": "1.0.107",
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",
@@ -297,8 +297,8 @@ async function setupListener () {
297
297
  taskTimeoutMinutes: L.taskTimeoutMinutes ?? 30,
298
298
  maxQueuePerWorkDir: L.maxQueuePerWorkDir ?? 10,
299
299
  maxTotalTasks: L.maxTotalTasks ?? 50,
300
- logDir: L.logDir || '',
301
- taskLogDir: L.taskLogDir || '',
300
+ logDir: L.logDir || path.join(home, '.claude'),
301
+ taskLogDir: L.taskLogDir || path.join(home, '.claude'),
302
302
  projectPath: L.projects?.default?.path || '',
303
303
  };
304
304
 
@@ -317,8 +317,8 @@ Press Enter to keep current value shown in [brackets].
317
317
  const taskTimeoutStr = await ask(rl, `Task timeout, minutes [${defaults.taskTimeoutMinutes}]: `) || String(defaults.taskTimeoutMinutes);
318
318
  const maxQueueStr = await ask(rl, `Max queue per work dir [${defaults.maxQueuePerWorkDir}]: `) || String(defaults.maxQueuePerWorkDir);
319
319
  const maxTotalStr = await ask(rl, `Max total tasks [${defaults.maxTotalTasks}]: `) || String(defaults.maxTotalTasks);
320
- const logDir = await ask(rl, `Log dir [${defaults.logDir || '(default)'}]: `) || defaults.logDir;
321
- const taskLogDir = await ask(rl, `Task log dir [${defaults.taskLogDir || '(default)'}]: `) || defaults.taskLogDir;
320
+ const logDir = await ask(rl, `Log dir [${defaults.logDir}]: `) || defaults.logDir;
321
+ const taskLogDir = await ask(rl, `Task log dir [${defaults.taskLogDir}]: `) || defaults.taskLogDir;
322
322
  const projectPath = await ask(rl, `Default project path [${defaults.projectPath || '(none)'}]: `) || defaults.projectPath;
323
323
 
324
324
  rl.close();
package/bin/uninstall.js CHANGED
@@ -1,30 +1,30 @@
1
- #!/usr/bin/env node
2
-
3
- import fs from 'fs';
4
- import os from 'os';
5
- import path from 'path';
6
- import { execSync } from 'child_process';
7
-
8
- const home = os.homedir();
9
- const claudeDir = path.join(home, '.claude');
10
- const configPath = path.join(claudeDir, 'notifier.config.json');
11
- const settingsPath = path.join(claudeDir, 'settings.json');
12
- const statePath = path.join(claudeDir, '.notifier_state.json');
13
- const pidFile = path.join(claudeDir, '.listener.pid');
14
-
15
- const HOOK_COMMAND = 'claude-notify';
16
- const PLUGIN_KEY = 'claude-notification-plugin@bazilio-plugins';
17
- const MARKETPLACE_KEY = 'bazilio-plugins';
18
-
1
+ #!/usr/bin/env node
2
+
3
+ import fs from 'fs';
4
+ import os from 'os';
5
+ import path from 'path';
6
+ import { execSync } from 'child_process';
7
+
8
+ const home = os.homedir();
9
+ const claudeDir = path.join(home, '.claude');
10
+ const configPath = path.join(claudeDir, 'notifier.config.json');
11
+ const settingsPath = path.join(claudeDir, 'settings.json');
12
+ const statePath = path.join(claudeDir, '.notifier_state.json');
13
+ const pidFile = path.join(claudeDir, '.listener.pid');
14
+
15
+ const HOOK_COMMAND = 'claude-notify';
16
+ const PLUGIN_KEY = 'claude-notification-plugin@bazilio-plugins';
17
+ const MARKETPLACE_KEY = 'bazilio-plugins';
18
+
19
19
  function isPluginHookCommand (command) {
20
20
  if (typeof command !== 'string') {
21
21
  return false;
22
22
  }
23
-
24
- const normalized = command.trim().toLowerCase();
25
- if (!normalized) {
26
- return false;
27
- }
23
+
24
+ const normalized = command.trim().toLowerCase();
25
+ if (!normalized) {
26
+ return false;
27
+ }
28
28
 
29
29
  return normalized === HOOK_COMMAND || normalized.startsWith(`${HOOK_COMMAND} `);
30
30
  }
@@ -62,6 +62,7 @@ function killProcessTree (pid) {
62
62
  stdio: 'ignore',
63
63
  windowsHide: true,
64
64
  });
65
+ console.log(`\x1b[33mListener process ${pid} killed\x1b[0m`);
65
66
  return true;
66
67
  }
67
68
 
@@ -73,6 +74,7 @@ function killProcessTree (pid) {
73
74
  if (isProcessAlive(pid)) {
74
75
  process.kill(pid, 'SIGKILL');
75
76
  }
77
+ console.log(`\x1b[33mListener process ${pid} killed\x1b[0m`);
76
78
  return true;
77
79
  } catch {
78
80
  return false;
@@ -176,174 +178,200 @@ function stopListenerIfRunning () {
176
178
 
177
179
  // Stop listener daemon if running
178
180
  const listenerStopped = stopListenerIfRunning();
179
-
180
- // Remove hooks from settings.json
181
- let hooksRemoved = false;
182
- let hooksRemoveError = '';
183
- if (fs.existsSync(settingsPath)) {
184
- try {
185
- const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf-8'));
186
- let hadPluginHooks = false;
187
-
188
- if (settings.hooks) {
189
- for (const event of Object.keys(settings.hooks)) {
190
- const eventHooks = Array.isArray(settings.hooks[event]) ? settings.hooks[event] : [];
191
- const hadInEvent = eventHooks.some((matcher) =>
192
- matcher.hooks?.some((h) => isPluginHookCommand(h.command)),
193
- );
194
- hadPluginHooks = hadPluginHooks || hadInEvent;
195
-
196
- settings.hooks[event] = settings.hooks[event].filter((matcher) =>
197
- !matcher.hooks?.some((h) => isPluginHookCommand(h.command)),
198
- );
199
-
200
- if (settings.hooks[event].length === 0) {
201
- delete settings.hooks[event];
202
- }
203
- }
204
-
205
- if (Object.keys(settings.hooks).length === 0) {
206
- delete settings.hooks;
207
- }
208
- }
209
-
210
- // Remove plugin from enabledPlugins
211
- if (settings.enabledPlugins?.[PLUGIN_KEY]) {
212
- delete settings.enabledPlugins[PLUGIN_KEY];
213
- if (Object.keys(settings.enabledPlugins).length === 0) {
214
- delete settings.enabledPlugins;
215
- }
216
- }
217
-
218
- // Remove marketplace from extraKnownMarketplaces
219
- if (settings.extraKnownMarketplaces?.[MARKETPLACE_KEY]) {
220
- delete settings.extraKnownMarketplaces[MARKETPLACE_KEY];
221
- if (Object.keys(settings.extraKnownMarketplaces).length === 0) {
222
- delete settings.extraKnownMarketplaces;
223
- }
224
- }
225
-
226
- fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
227
-
228
- // Verify hooks were actually removed
229
- const verify = JSON.parse(fs.readFileSync(settingsPath, 'utf-8'));
230
- const remainingPluginHooks = verify.hooks
231
- ? Object.values(verify.hooks).some((matchers) =>
232
- Array.isArray(matchers) &&
233
- matchers.some((m) => m.hooks?.some((h) => isPluginHookCommand(h.command))),
234
- )
235
- : false;
236
- hooksRemoved = hadPluginHooks && !remainingPluginHooks;
237
- if (hadPluginHooks && remainingPluginHooks) {
238
- hooksRemoveError = 'Hooks still present in settings.json after removal attempt';
239
- }
240
- } catch (err) {
241
- hooksRemoveError = `Failed to update settings.json: ${err.message}`;
242
- }
243
- }
244
-
245
- // Remove config, state, resolver, and listener files
246
- const resolverPath = path.join(claudeDir, 'claude-notify-resolve.js');
247
- const listenerLogFile = path.join(claudeDir, '.cc-n-listener.log');
248
- for (const file of [configPath, statePath, resolverPath, pidFile, listenerLogFile]) {
249
- if (fs.existsSync(file)) {
250
- fs.unlinkSync(file);
251
- }
252
- }
253
-
254
- // Remove CLI wrapper script
255
- const WRAPPER_NAMES = ['claude-notify'];
256
- let cliBinsRemoved = false;
257
- const ext = process.platform === 'win32' ? '.cmd' : '';
258
-
259
- // Collect directories to check for wrapper scripts
260
- const wrapperDirs = new Set();
261
-
262
- // Directory next to claude binary
263
- try {
264
- const cmd = process.platform === 'win32' ? 'where claude' : 'which claude';
265
- const claudeBin = execSync(cmd, { encoding: 'utf-8', windowsHide: true }).trim().split('\n')[0].trim();
266
- wrapperDirs.add(path.dirname(claudeBin));
267
- } catch {
268
- // claude not in PATH
269
- }
270
-
271
- // ~/.local/bin (common on Linux/macOS, also used on Windows)
272
- wrapperDirs.add(path.join(home, '.local', 'bin'));
273
-
274
- for (const dir of wrapperDirs) {
275
- for (const name of WRAPPER_NAMES) {
276
- const filePath = path.join(dir, `${name}${ext}`);
277
- if (fs.existsSync(filePath)) {
278
- fs.unlinkSync(filePath);
279
- cliBinsRemoved = true;
280
- }
281
- }
282
- }
283
-
284
- // Remove from installed_plugins.json
285
- const installedPluginsPath = path.join(claudeDir, 'plugins', 'installed_plugins.json');
286
- let pluginEntryRemoved = false;
287
- if (fs.existsSync(installedPluginsPath)) {
288
- try {
289
- const data = JSON.parse(fs.readFileSync(installedPluginsPath, 'utf-8'));
290
- if (data.plugins?.[PLUGIN_KEY]) {
291
- delete data.plugins[PLUGIN_KEY];
292
- fs.writeFileSync(installedPluginsPath, JSON.stringify(data, null, 2));
293
- pluginEntryRemoved = true;
294
- }
295
- } catch {
296
- // ignore
297
- }
298
- }
299
-
300
- // Remove plugin cache
301
- const pluginCacheDir = path.join(claudeDir, 'plugins', 'cache', 'bazilio-plugins', 'claude-notification-plugin');
302
- let cacheRemoved = false;
303
- if (fs.existsSync(pluginCacheDir)) {
304
- fs.rmSync(pluginCacheDir, { recursive: true, force: true });
305
- cacheRemoved = !fs.existsSync(pluginCacheDir);
306
- }
307
-
308
- const hooksStatus = hooksRemoved
309
- ? 'Hooks removed from settings.json'
310
- : hooksRemoveError
311
- ? `Warning: ${hooksRemoveError}`
312
- : 'No hooks found in settings.json';
313
-
314
- const extras = [
315
- listenerStopped ? 'Listener daemon stopped.' : '',
316
- pluginEntryRemoved || cacheRemoved ? 'Plugin registration cleaned.' : '',
317
- cliBinsRemoved ? 'CLI wrapper scripts removed.' : '',
318
- ].filter(Boolean).join('\n');
319
-
320
- const cacheStillExists = fs.existsSync(pluginCacheDir);
321
- if (cacheStillExists) {
322
- console.log(`
323
- Warning: Could not fully remove plugin cache directory:
324
- ${pluginCacheDir}
325
- Please remove it manually.
326
- ${hooksStatus}
327
- Config files deleted.${extras ? `\n${extras}` : ''}
328
- `);
329
- } else {
330
- console.log(`
331
- Claude Notification Plugin uninstalled.
332
- ${hooksStatus}
333
- Config files deleted.${extras ? `\n${extras}` : ''}
334
- `);
335
- }
336
-
337
- // If run manually (not via npm lifecycle), remove the global npm package too
338
- if (!process.env.npm_lifecycle_event) {
339
- try {
340
- console.log('Removing npm global package...');
341
- execSync('npm uninstall -g claude-notification-plugin', {
342
- stdio: 'inherit',
343
- windowsHide: true,
344
- });
345
- console.log('');
346
- } catch {
347
- console.log('Could not remove npm package automatically.\nRun manually: npm uninstall -g claude-notification-plugin\n');
348
- }
349
- }
181
+
182
+ // Remove hooks from settings.json
183
+ let hooksRemoved = false;
184
+ let hooksRemoveError = '';
185
+ if (fs.existsSync(settingsPath)) {
186
+ try {
187
+ const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf-8'));
188
+ let hadPluginHooks = false;
189
+
190
+ if (settings.hooks) {
191
+ for (const event of Object.keys(settings.hooks)) {
192
+ const eventHooks = Array.isArray(settings.hooks[event]) ? settings.hooks[event] : [];
193
+ const hadInEvent = eventHooks.some((matcher) =>
194
+ matcher.hooks?.some((h) => isPluginHookCommand(h.command)),
195
+ );
196
+ hadPluginHooks = hadPluginHooks || hadInEvent;
197
+
198
+ settings.hooks[event] = settings.hooks[event].filter((matcher) =>
199
+ !matcher.hooks?.some((h) => isPluginHookCommand(h.command)),
200
+ );
201
+
202
+ if (settings.hooks[event].length === 0) {
203
+ delete settings.hooks[event];
204
+ }
205
+ }
206
+
207
+ if (Object.keys(settings.hooks).length === 0) {
208
+ delete settings.hooks;
209
+ }
210
+ }
211
+
212
+ // Remove plugin from enabledPlugins
213
+ if (settings.enabledPlugins?.[PLUGIN_KEY]) {
214
+ delete settings.enabledPlugins[PLUGIN_KEY];
215
+ if (Object.keys(settings.enabledPlugins).length === 0) {
216
+ delete settings.enabledPlugins;
217
+ }
218
+ }
219
+
220
+ // Remove marketplace from extraKnownMarketplaces
221
+ if (settings.extraKnownMarketplaces?.[MARKETPLACE_KEY]) {
222
+ delete settings.extraKnownMarketplaces[MARKETPLACE_KEY];
223
+ if (Object.keys(settings.extraKnownMarketplaces).length === 0) {
224
+ delete settings.extraKnownMarketplaces;
225
+ }
226
+ }
227
+
228
+ fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
229
+
230
+ // Verify hooks were actually removed
231
+ const verify = JSON.parse(fs.readFileSync(settingsPath, 'utf-8'));
232
+ const remainingPluginHooks = verify.hooks
233
+ ? Object.values(verify.hooks).some((matchers) =>
234
+ Array.isArray(matchers) &&
235
+ matchers.some((m) => m.hooks?.some((h) => isPluginHookCommand(h.command))),
236
+ )
237
+ : false;
238
+ hooksRemoved = hadPluginHooks && !remainingPluginHooks;
239
+ if (hadPluginHooks && remainingPluginHooks) {
240
+ hooksRemoveError = 'Hooks still present in settings.json after removal attempt';
241
+ }
242
+ } catch (err) {
243
+ hooksRemoveError = `Failed to update settings.json: ${err.message}`;
244
+ }
245
+ }
246
+
247
+ // Remove config, state, resolver, and listener files
248
+ const resolverPath = path.join(claudeDir, 'claude-notify-resolve.js');
249
+ const listenerLogFile = path.join(claudeDir, '.cc-n-listener.log');
250
+ for (const file of [configPath, statePath, resolverPath, pidFile, listenerLogFile]) {
251
+ if (fs.existsSync(file)) {
252
+ fs.unlinkSync(file);
253
+ }
254
+ }
255
+
256
+ // Remove CLI wrapper script
257
+ const WRAPPER_NAMES = ['claude-notify'];
258
+ let cliBinsRemoved = false;
259
+ const ext = process.platform === 'win32' ? '.cmd' : '';
260
+
261
+ // Collect directories to check for wrapper scripts
262
+ const wrapperDirs = new Set();
263
+
264
+ // Directory next to claude binary
265
+ try {
266
+ const cmd = process.platform === 'win32' ? 'where claude' : 'which claude';
267
+ const claudeBin = execSync(cmd, { encoding: 'utf-8', windowsHide: true }).trim().split('\n')[0].trim();
268
+ wrapperDirs.add(path.dirname(claudeBin));
269
+ } catch {
270
+ // claude not in PATH
271
+ }
272
+
273
+ // ~/.local/bin (common on Linux/macOS, also used on Windows)
274
+ wrapperDirs.add(path.join(home, '.local', 'bin'));
275
+
276
+ for (const dir of wrapperDirs) {
277
+ for (const name of WRAPPER_NAMES) {
278
+ const filePath = path.join(dir, `${name}${ext}`);
279
+ if (fs.existsSync(filePath)) {
280
+ fs.unlinkSync(filePath);
281
+ cliBinsRemoved = true;
282
+ }
283
+ }
284
+ }
285
+
286
+ // Remove from installed_plugins.json
287
+ const installedPluginsPath = path.join(claudeDir, 'plugins', 'installed_plugins.json');
288
+ let pluginEntryRemoved = false;
289
+ if (fs.existsSync(installedPluginsPath)) {
290
+ try {
291
+ const data = JSON.parse(fs.readFileSync(installedPluginsPath, 'utf-8'));
292
+ if (data.plugins?.[PLUGIN_KEY]) {
293
+ delete data.plugins[PLUGIN_KEY];
294
+ fs.writeFileSync(installedPluginsPath, JSON.stringify(data, null, 2));
295
+ pluginEntryRemoved = true;
296
+ }
297
+ } catch {
298
+ // ignore
299
+ }
300
+ }
301
+
302
+ // Remove marketplace from known_marketplaces.json if no plugins reference it
303
+ const knownMarketplacesPath = path.join(claudeDir, 'plugins', 'known_marketplaces.json');
304
+ try {
305
+ if (fs.existsSync(knownMarketplacesPath)) {
306
+ // Check if any remaining plugins reference this marketplace
307
+ let hasMarketplacePlugins = false;
308
+ if (fs.existsSync(installedPluginsPath)) {
309
+ const data = JSON.parse(fs.readFileSync(installedPluginsPath, 'utf-8'));
310
+ if (data.plugins) {
311
+ hasMarketplacePlugins = Object.keys(data.plugins)
312
+ .some((key) => key.endsWith(`@${MARKETPLACE_KEY}`));
313
+ }
314
+ }
315
+
316
+ if (!hasMarketplacePlugins) {
317
+ const marketplaces = JSON.parse(fs.readFileSync(knownMarketplacesPath, 'utf-8'));
318
+ if (marketplaces[MARKETPLACE_KEY]) {
319
+ delete marketplaces[MARKETPLACE_KEY];
320
+ fs.writeFileSync(knownMarketplacesPath, JSON.stringify(marketplaces, null, 2));
321
+ }
322
+ }
323
+ }
324
+ } catch {
325
+ // ignore
326
+ }
327
+
328
+ // Remove plugin cache
329
+ const pluginCacheDir = path.join(claudeDir, 'plugins', 'cache', 'bazilio-plugins', 'claude-notification-plugin');
330
+ let cacheRemoved = false;
331
+ if (fs.existsSync(pluginCacheDir)) {
332
+ fs.rmSync(pluginCacheDir, { recursive: true, force: true });
333
+ cacheRemoved = !fs.existsSync(pluginCacheDir);
334
+ }
335
+
336
+ const hooksStatus = hooksRemoved
337
+ ? 'Hooks removed from settings.json'
338
+ : hooksRemoveError
339
+ ? `Warning: ${hooksRemoveError}`
340
+ : 'No hooks found in settings.json';
341
+
342
+ const extras = [
343
+ listenerStopped ? 'Listener daemon stopped.' : '',
344
+ pluginEntryRemoved || cacheRemoved ? 'Plugin registration cleaned.' : '',
345
+ cliBinsRemoved ? 'CLI wrapper scripts removed.' : '',
346
+ ].filter(Boolean).join('\n');
347
+
348
+ const cacheStillExists = fs.existsSync(pluginCacheDir);
349
+ if (cacheStillExists) {
350
+ console.log(`
351
+ Warning: Could not fully remove plugin cache directory:
352
+ ${pluginCacheDir}
353
+ Please remove it manually.
354
+ ${hooksStatus}
355
+ Config files deleted.${extras ? `\n${extras}` : ''}
356
+ `);
357
+ } else {
358
+ console.log(`
359
+ Claude Notification Plugin uninstalled.
360
+ ${hooksStatus}
361
+ Config files deleted.${extras ? `\n${extras}` : ''}
362
+ `);
363
+ }
364
+
365
+ // If run manually (not via npm lifecycle), remove the global npm package too
366
+ if (!process.env.npm_lifecycle_event) {
367
+ try {
368
+ console.log('Removing npm global package...');
369
+ execSync('npm uninstall -g claude-notification-plugin', {
370
+ stdio: 'inherit',
371
+ windowsHide: true,
372
+ });
373
+ console.log('');
374
+ } catch {
375
+ console.log('Could not remove npm package automatically.\nRun manually: npm uninstall -g claude-notification-plugin\n');
376
+ }
377
+ }
package/commit-sha CHANGED
@@ -1 +1 @@
1
- a9b07bbf2e32a077e4f17b6ccad9b2e4c6979563
1
+ c5e25e27c544bccfb19bcac820c751c4279d3a2e
@@ -524,24 +524,26 @@ async function handleStop () {
524
524
  }
525
525
 
526
526
  function handleHelp () {
527
- return '<b>📖 Commands:</b>\n'
528
- + '\n/status — status of all projects'
529
- + '\n/status @project project status'
530
- + '\n/queueall queues'
531
- + '\n/cancel [@project[/branch]] cancel task'
532
- + '\n/drop @project N remove task from queue'
533
- + '\n/clear @project[/branch]clear queue'
534
- + '\n/projectslist projects'
535
- + '\n/worktrees @project project worktrees'
536
- + '\n/worktree @project branch create worktree'
537
- + '\n/rmworktree @project branch — remove worktree'
538
- + '\n/historytask history'
539
- + '\n/stopstop listener'
540
- + '\n/helpthis help'
541
- + '\n\n<b>Tasks:</b>'
542
- + '\n<code>@project task</code> — main worktree'
543
- + '\n<code>@project/branch task</code> — worktree'
544
- + '\n<code>task</code> — default project';
527
+ return `<b>📖 Commands:</b>
528
+
529
+ /status — status of all projects
530
+ /status @project project status
531
+ /queueall queues
532
+ /cancel [@project[/branch]]cancel task
533
+ /drop @project N remove task from queue
534
+ /clear @project[/branch]clear queue
535
+ /projectslist projects
536
+ /worktrees @project — project worktrees
537
+ /worktree @project branch — create worktree
538
+ /rmworktree @project branch remove worktree
539
+ /historytask history
540
+ /stopstop listener
541
+ /help — this help
542
+
543
+ <b>Tasks:</b>
544
+ <code>@project task</code> — main worktree
545
+ <code>@project/branch task</code> — worktree
546
+ <code>task</code> — default project`;
545
547
  }
546
548
 
547
549
  // ----------------------
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.105",
4
+ "version": "1.0.107",
5
5
  "description": "Claude Code task-completion notifications: Telegram, desktop notifications (Windows/macOS/Linux), sound, and voice",
6
6
  "type": "module",
7
7
  "engines": {