claude-notification-plugin 1.1.6 → 1.1.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
@@ -52,7 +52,7 @@ This removes hooks, config, CLI wrappers, plugin registration, and the npm globa
52
52
 
53
53
  ## Configuration
54
54
 
55
- Config file: `~/.claude/notifier.config.json`
55
+ Config file: `~/.claude/claude-notify.config.json`
56
56
 
57
57
  ```json
58
58
  {
@@ -0,0 +1,33 @@
1
+ import os from 'os';
2
+ import path from 'path';
3
+
4
+ export const HOME = os.homedir();
5
+ export const CLAUDE_DIR = path.join(HOME, '.claude');
6
+ export const PLUGINS_DIR = path.join(CLAUDE_DIR, 'plugins');
7
+
8
+ // File names
9
+ export const CONFIG_FILENAME = 'claude-notify.config.json';
10
+ export const STATE_FILENAME = '.notifier_state.json';
11
+ export const PID_FILENAME = '.listener.pid';
12
+ export const RESOLVER_FILENAME = 'claude-notify-resolve.js';
13
+ export const LISTENER_LOG_FILENAME = '.cc-n-listener.log';
14
+ export const INSTALL_LOG_FILENAME = 'claude-notify-install.log';
15
+
16
+ // Full paths
17
+ export const CONFIG_PATH = path.join(CLAUDE_DIR, CONFIG_FILENAME);
18
+ export const STATE_PATH = path.join(CLAUDE_DIR, STATE_FILENAME);
19
+ export const PID_PATH = path.join(CLAUDE_DIR, PID_FILENAME);
20
+ export const RESOLVER_PATH = path.join(CLAUDE_DIR, RESOLVER_FILENAME);
21
+ export const LISTENER_LOG_PATH = path.join(CLAUDE_DIR, LISTENER_LOG_FILENAME);
22
+ export const INSTALL_LOG_PATH = path.join(CLAUDE_DIR, INSTALL_LOG_FILENAME);
23
+ export const SETTINGS_PATH = path.join(CLAUDE_DIR, 'settings.json');
24
+ export const INSTALLED_PLUGINS_PATH = path.join(PLUGINS_DIR, 'installed_plugins.json');
25
+ export const KNOWN_MARKETPLACES_PATH = path.join(PLUGINS_DIR, 'known_marketplaces.json');
26
+ export const MARKETPLACES_DIR = path.join(PLUGINS_DIR, 'marketplaces');
27
+
28
+ // Plugin identity
29
+ export const HOOK_COMMAND = 'claude-notify';
30
+ export const MARKETPLACE_KEY = 'bazilio-plugins';
31
+ export const PLUGIN_KEY = 'claude-notification-plugin@bazilio-plugins';
32
+ export const MARKETPLACE_REPO = 'https://github.com/Bazilio-san/claude-plugins.git';
33
+ export const MARKETPLACE_GITHUB = 'Bazilio-san/claude-plugins';
package/bin/install.js CHANGED
@@ -1,27 +1,20 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  import fs from 'fs';
4
- import os from 'os';
5
4
  import path from 'path';
6
5
  import readline from 'readline';
7
6
  import { execSync } from 'child_process';
8
7
  import { fileURLToPath } from 'url';
8
+ import {
9
+ HOME, CLAUDE_DIR, PLUGINS_DIR,
10
+ CONFIG_PATH, PID_PATH, RESOLVER_PATH, INSTALL_LOG_PATH,
11
+ SETTINGS_PATH, INSTALLED_PLUGINS_PATH, KNOWN_MARKETPLACES_PATH, MARKETPLACES_DIR,
12
+ HOOK_COMMAND, MARKETPLACE_KEY, PLUGIN_KEY, MARKETPLACE_REPO, MARKETPLACE_GITHUB,
13
+ } from './constants.js';
9
14
 
10
15
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
11
16
  const PACKAGE_ROOT = path.resolve(__dirname, '..');
12
17
 
13
- const home = os.homedir();
14
- const claudeDir = path.join(home, '.claude');
15
- const pluginsDir = path.join(claudeDir, 'plugins');
16
- const configPath = path.join(claudeDir, 'notifier.config.json');
17
- const settingsPath = path.join(claudeDir, 'settings.json');
18
- const installedPluginsPath = path.join(pluginsDir, 'installed_plugins.json');
19
- const knownMarketplacesPath = path.join(pluginsDir, 'known_marketplaces.json');
20
- const marketplacesDir = path.join(pluginsDir, 'marketplaces');
21
- const RESOLVER_PATH = path.join(claudeDir, 'claude-notify-resolve.js');
22
- const pidFile = path.join(claudeDir, '.listener.pid');
23
- const installLogPath = path.join(claudeDir, 'claude-notify-install.log');
24
-
25
18
  // ──────────────────────────────────────
26
19
  // Logging to file
27
20
  // ──────────────────────────────────────
@@ -29,8 +22,8 @@ const installLogPath = path.join(claudeDir, 'claude-notify-install.log');
29
22
  let logStream;
30
23
 
31
24
  function initLog () {
32
- fs.mkdirSync(claudeDir, { recursive: true });
33
- logStream = fs.createWriteStream(installLogPath, { flags: 'w' });
25
+ fs.mkdirSync(CLAUDE_DIR, { recursive: true });
26
+ logStream = fs.createWriteStream(INSTALL_LOG_PATH, { flags: 'w' });
34
27
  const origLog = console.log.bind(console);
35
28
  const origWarn = console.warn.bind(console);
36
29
  const stamp = () => new Date().toISOString();
@@ -107,10 +100,10 @@ function stopListenerIfRunning () {
107
100
  let stopped = false;
108
101
  const pidFromFile = (() => {
109
102
  try {
110
- if (!fs.existsSync(pidFile)) {
103
+ if (!fs.existsSync(PID_PATH)) {
111
104
  return null;
112
105
  }
113
- const pid = parseInt(fs.readFileSync(pidFile, 'utf-8').trim(), 10);
106
+ const pid = parseInt(fs.readFileSync(PID_PATH, 'utf-8').trim(), 10);
114
107
  return Number.isInteger(pid) && pid > 0 ? pid : null;
115
108
  } catch {
116
109
  return null;
@@ -122,20 +115,14 @@ function stopListenerIfRunning () {
122
115
  }
123
116
 
124
117
  try {
125
- if (fs.existsSync(pidFile)) {
126
- fs.unlinkSync(pidFile);
118
+ if (fs.existsSync(PID_PATH)) {
119
+ fs.unlinkSync(PID_PATH);
127
120
  }
128
121
  } catch { /* ignore */ }
129
122
 
130
123
  return stopped;
131
124
  }
132
125
 
133
- const HOOK_COMMAND = 'claude-notify';
134
- const MARKETPLACE_KEY = 'bazilio-plugins';
135
- const PLUGIN_KEY = 'claude-notification-plugin@bazilio-plugins';
136
- const MARKETPLACE_REPO = 'https://github.com/Bazilio-san/claude-plugins.git';
137
- const MARKETPLACE_GITHUB = 'Bazilio-san/claude-plugins';
138
-
139
126
  const CLI_BIN_NAME = 'claude-notify';
140
127
  const CLI_BIN_TARGET = 'bin/cli.js';
141
128
 
@@ -158,7 +145,7 @@ function readCommitSha () {
158
145
  }
159
146
 
160
147
  function copyToCache (version) {
161
- const cacheBase = path.join(pluginsDir, 'cache', MARKETPLACE_KEY, 'claude-notification-plugin');
148
+ const cacheBase = path.join(PLUGINS_DIR, 'cache', MARKETPLACE_KEY, 'claude-notification-plugin');
162
149
  const dest = path.join(cacheBase, version);
163
150
 
164
151
  if (fs.existsSync(dest)) {
@@ -203,12 +190,12 @@ function copyToCache (version) {
203
190
  }
204
191
 
205
192
  function updateInstalledPlugins (version, installPath) {
206
- fs.mkdirSync(pluginsDir, { recursive: true });
193
+ fs.mkdirSync(PLUGINS_DIR, { recursive: true });
207
194
 
208
195
  let data = { version: 2, plugins: {} };
209
- if (fs.existsSync(installedPluginsPath)) {
196
+ if (fs.existsSync(INSTALLED_PLUGINS_PATH)) {
210
197
  try {
211
- data = JSON.parse(fs.readFileSync(installedPluginsPath, 'utf-8'));
198
+ data = JSON.parse(fs.readFileSync(INSTALLED_PLUGINS_PATH, 'utf-8'));
212
199
  } catch {
213
200
  // ignore malformed file
214
201
  }
@@ -226,20 +213,20 @@ function updateInstalledPlugins (version, installPath) {
226
213
  gitCommitSha: readCommitSha(),
227
214
  }];
228
215
 
229
- fs.writeFileSync(installedPluginsPath, JSON.stringify(data, null, 2));
216
+ fs.writeFileSync(INSTALLED_PLUGINS_PATH, JSON.stringify(data, null, 2));
230
217
  }
231
218
 
232
219
  function updateKnownMarketplaces () {
233
220
  let data = {};
234
- if (fs.existsSync(knownMarketplacesPath)) {
221
+ if (fs.existsSync(KNOWN_MARKETPLACES_PATH)) {
235
222
  try {
236
- data = JSON.parse(fs.readFileSync(knownMarketplacesPath, 'utf-8'));
223
+ data = JSON.parse(fs.readFileSync(KNOWN_MARKETPLACES_PATH, 'utf-8'));
237
224
  } catch {
238
225
  // ignore malformed file
239
226
  }
240
227
  }
241
228
 
242
- const installLocation = path.join(marketplacesDir, MARKETPLACE_KEY);
229
+ const installLocation = path.join(MARKETPLACES_DIR, MARKETPLACE_KEY);
243
230
 
244
231
  data[MARKETPLACE_KEY] = {
245
232
  ...data[MARKETPLACE_KEY],
@@ -252,11 +239,11 @@ function updateKnownMarketplaces () {
252
239
  autoUpdate: true,
253
240
  };
254
241
 
255
- fs.writeFileSync(knownMarketplacesPath, JSON.stringify(data, null, 2));
242
+ fs.writeFileSync(KNOWN_MARKETPLACES_PATH, JSON.stringify(data, null, 2));
256
243
  }
257
244
 
258
245
  function cloneOrUpdateMarketplace () {
259
- const dest = path.join(marketplacesDir, MARKETPLACE_KEY);
246
+ const dest = path.join(MARKETPLACES_DIR, MARKETPLACE_KEY);
260
247
 
261
248
  if (fs.existsSync(path.join(dest, '.git'))) {
262
249
  try {
@@ -265,7 +252,7 @@ function cloneOrUpdateMarketplace () {
265
252
  // offline or conflict — not fatal
266
253
  }
267
254
  } else {
268
- fs.mkdirSync(marketplacesDir, { recursive: true });
255
+ fs.mkdirSync(MARKETPLACES_DIR, { recursive: true });
269
256
  try {
270
257
  execSync(`git clone "${MARKETPLACE_REPO}" "${dest}"`, {
271
258
  stdio: 'pipe',
@@ -423,6 +410,9 @@ async function main () {
423
410
 
424
411
  // 0. Stop listener if running (before overwriting files)
425
412
  const listenerWasStopped = stopListenerIfRunning();
413
+ if (listenerWasStopped) {
414
+ console.log(' Listener daemon stopped');
415
+ }
426
416
 
427
417
  // 1. Register plugin in Claude Code
428
418
  const version = getVersion();
@@ -448,9 +438,9 @@ async function main () {
448
438
 
449
439
  // 2. Interactive Telegram setup
450
440
  let existing = {};
451
- if (fs.existsSync(configPath)) {
441
+ if (fs.existsSync(CONFIG_PATH)) {
452
442
  try {
453
- existing = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
443
+ existing = JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf-8'));
454
444
  } catch {
455
445
  // ignore malformed config
456
446
  }
@@ -519,7 +509,7 @@ Send any message to your bot in Telegram, then press Enter.\x1b[0m`);
519
509
  }
520
510
 
521
511
  // 3. Write config
522
- fs.mkdirSync(claudeDir, { recursive: true });
512
+ fs.mkdirSync(CLAUDE_DIR, { recursive: true });
523
513
 
524
514
  const platform = process.platform;
525
515
  let defaultSoundFile;
@@ -553,7 +543,7 @@ Send any message to your bot in Telegram, then press Enter.\x1b[0m`);
553
543
  debug: false,
554
544
  listener: {
555
545
  projects: {},
556
- worktreeBaseDir: path.join(home, '.claude', 'worktrees'),
546
+ worktreeBaseDir: path.join(HOME, '.claude', 'worktrees'),
557
547
  autoCreateWorktree: true,
558
548
  taskTimeoutMinutes: 30,
559
549
  maxQueuePerWorkDir: 10,
@@ -577,14 +567,15 @@ Send any message to your bot in Telegram, then press Enter.\x1b[0m`);
577
567
  config.telegram.chatId = chatId;
578
568
  }
579
569
 
580
- fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
570
+ fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2));
571
+ console.log(` Config saved: ${CONFIG_PATH}`);
581
572
 
582
573
  // 4. Register hooks
583
574
  let settings = {};
584
575
 
585
- if (fs.existsSync(settingsPath)) {
576
+ if (fs.existsSync(SETTINGS_PATH)) {
586
577
  try {
587
- settings = JSON.parse(fs.readFileSync(settingsPath, 'utf-8'));
578
+ settings = JSON.parse(fs.readFileSync(SETTINGS_PATH, 'utf-8'));
588
579
  } catch {
589
580
  settings = {};
590
581
  }
@@ -593,6 +584,7 @@ Send any message to your bot in Telegram, then press Enter.\x1b[0m`);
593
584
  // Register plugin as enabled
594
585
  settings.enabledPlugins = settings.enabledPlugins || {};
595
586
  settings.enabledPlugins[PLUGIN_KEY] = true;
587
+ console.log(' Registered plugin in enabledPlugins');
596
588
 
597
589
  // When the plugin is enabled, Claude Code loads hooks from hooks/hooks.json automatically.
598
590
  // Remove any duplicate hooks from settings.json to avoid double notifications.
@@ -600,6 +592,7 @@ Send any message to your bot in Telegram, then press Enter.\x1b[0m`);
600
592
  removeHook(settings, 'UserPromptSubmit');
601
593
  removeHook(settings, 'Stop');
602
594
  removeHook(settings, 'Notification');
595
+ console.log(' Cleaned duplicate hooks from settings.json');
603
596
 
604
597
  // Register marketplace
605
598
  settings.extraKnownMarketplaces = settings.extraKnownMarketplaces || {};
@@ -609,8 +602,10 @@ Send any message to your bot in Telegram, then press Enter.\x1b[0m`);
609
602
  repo: MARKETPLACE_GITHUB,
610
603
  },
611
604
  };
605
+ console.log(' Registered marketplace in extraKnownMarketplaces');
612
606
 
613
- fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
607
+ fs.writeFileSync(SETTINGS_PATH, JSON.stringify(settings, null, 2));
608
+ console.log(` Settings saved: ${SETTINGS_PATH}`);
614
609
 
615
610
  // 5. Summary
616
611
  const telegramStatus = config.telegram.token && config.telegram.chatId
@@ -626,20 +621,18 @@ Send any message to your bot in Telegram, then press Enter.\x1b[0m`);
626
621
  sudo apt install espeak`;
627
622
  }
628
623
 
629
- const listenerLine = listenerWasStopped ? '\nListener was stopped (restart manually if needed).' : '';
630
-
631
624
  console.log(`
632
625
  Installed!
633
- ${listenerLine}
626
+
634
627
  Plugin hooks (via hooks/hooks.json):
635
628
  - UserPromptSubmit (start timer)
636
629
  - Stop (task finished)
637
630
  - Notification (waiting for input)
638
631
 
639
- Config: ${configPath}
632
+ Config: ${CONFIG_PATH}
640
633
  ${telegramStatus}${platformTip}
641
634
 
642
- Log: ${installLogPath}
635
+ Log: ${INSTALL_LOG_PATH}
643
636
 
644
637
  To uninstall: claude-notify uninstall
645
638
 
@@ -1,26 +1,24 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  import fs from 'fs';
4
- import os from 'os';
5
4
  import path from 'path';
6
5
  import readline from 'readline';
7
6
  import { spawn, execSync } from 'child_process';
8
7
  import { fileURLToPath } from 'url';
8
+ import {
9
+ HOME, CLAUDE_DIR, CONFIG_PATH, PID_PATH, LISTENER_LOG_FILENAME,
10
+ } from './constants.js';
9
11
 
10
12
  const __filename = fileURLToPath(import.meta.url);
11
13
  const __dirname = path.dirname(__filename);
12
14
 
13
- const DEFAULT_LOG_DIR = path.join(os.homedir(), '.claude');
14
- const PID_FILE = path.join(os.homedir(), '.claude', '.listener.pid');
15
- const CONFIG_FILE = path.join(os.homedir(), '.claude', 'notifier.config.json');
16
-
17
15
  function getLogFile () {
18
16
  try {
19
- const cfg = JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf-8'));
20
- const logDir = cfg.listener?.logDir || DEFAULT_LOG_DIR;
21
- return path.join(logDir, '.cc-n-listener.log');
17
+ const cfg = JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf-8'));
18
+ const logDir = cfg.listener?.logDir || CLAUDE_DIR;
19
+ return path.join(logDir, LISTENER_LOG_FILENAME);
22
20
  } catch {
23
- return path.join(DEFAULT_LOG_DIR, '.cc-n-listener.log');
21
+ return path.join(CLAUDE_DIR, LISTENER_LOG_FILENAME);
24
22
  }
25
23
  }
26
24
  const LISTENER_SCRIPT = path.join(__dirname, '..', 'listener', 'listener.js');
@@ -71,22 +69,22 @@ function startDaemon () {
71
69
  // Clean stale PID file
72
70
  if (existingPid) {
73
71
  try {
74
- fs.unlinkSync(PID_FILE);
72
+ fs.unlinkSync(PID_PATH);
75
73
  } catch {
76
74
  // ignore
77
75
  }
78
76
  }
79
77
 
80
78
  // Validate config
81
- if (!fs.existsSync(CONFIG_FILE)) {
82
- console.error(`Config not found: ${CONFIG_FILE}`);
79
+ if (!fs.existsSync(CONFIG_PATH)) {
80
+ console.error(`Config not found: ${CONFIG_PATH}`);
83
81
  console.error('Run claude-notify install first, or create the config manually.');
84
82
  process.exit(1);
85
83
  }
86
84
 
87
85
  let config;
88
86
  try {
89
- config = JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf-8'));
87
+ config = JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf-8'));
90
88
  } catch (err) {
91
89
  console.error(`Invalid config: ${err.message}`);
92
90
  process.exit(1);
@@ -134,8 +132,8 @@ function startDaemon () {
134
132
  fs.closeSync(logFd);
135
133
 
136
134
  // Write PID
137
- fs.mkdirSync(path.dirname(PID_FILE), { recursive: true });
138
- fs.writeFileSync(PID_FILE, String(child.pid));
135
+ fs.mkdirSync(path.dirname(PID_PATH), { recursive: true });
136
+ fs.writeFileSync(PID_PATH, String(child.pid));
139
137
 
140
138
  console.log(`Listener started (PID: ${child.pid})
141
139
  Log: ${logFile}
@@ -235,8 +233,8 @@ function showLogs () {
235
233
 
236
234
  function readPid () {
237
235
  try {
238
- if (fs.existsSync(PID_FILE)) {
239
- const pid = parseInt(fs.readFileSync(PID_FILE, 'utf-8').trim(), 10);
236
+ if (fs.existsSync(PID_PATH)) {
237
+ const pid = parseInt(fs.readFileSync(PID_PATH, 'utf-8').trim(), 10);
240
238
  return isNaN(pid) ? null : pid;
241
239
  }
242
240
  } catch {
@@ -247,8 +245,8 @@ function readPid () {
247
245
 
248
246
  function cleanPid () {
249
247
  try {
250
- if (fs.existsSync(PID_FILE)) {
251
- fs.unlinkSync(PID_FILE);
248
+ if (fs.existsSync(PID_PATH)) {
249
+ fs.unlinkSync(PID_PATH);
252
250
  }
253
251
  } catch {
254
252
  // ignore
@@ -365,9 +363,9 @@ async function validateProjectPath (rl, inputPath) {
365
363
 
366
364
  async function setupListener () {
367
365
  let config = {};
368
- if (fs.existsSync(CONFIG_FILE)) {
366
+ if (fs.existsSync(CONFIG_PATH)) {
369
367
  try {
370
- config = JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf-8'));
368
+ config = JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf-8'));
371
369
  } catch {
372
370
  // ignore
373
371
  }
@@ -375,10 +373,8 @@ async function setupListener () {
375
373
 
376
374
  config.listener = config.listener || {};
377
375
  const L = config.listener;
378
- const home = os.homedir();
379
-
380
376
  const defaults = {
381
- worktreeBaseDir: L.worktreeBaseDir || path.join(home, '.claude', 'worktrees'),
377
+ worktreeBaseDir: L.worktreeBaseDir || path.join(HOME, '.claude', 'worktrees'),
382
378
  taskTimeoutMinutes: L.taskTimeoutMinutes ?? 30,
383
379
  maxQueuePerWorkDir: L.maxQueuePerWorkDir ?? 10,
384
380
  maxTotalTasks: L.maxTotalTasks ?? 50,
@@ -497,10 +493,10 @@ Press Enter to keep current value shown in [brackets].
497
493
 
498
494
  rl.close();
499
495
 
500
- fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
496
+ fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2));
501
497
 
502
498
  const projectCount = Object.keys(L.projects).length;
503
- console.log(`\nListener config saved to ${CONFIG_FILE}`);
499
+ console.log(`\nListener config saved to ${CONFIG_PATH}`);
504
500
  if (hasValidProject || projectCount > 0) {
505
501
  console.log('Run "claude-notify listener start" to apply.');
506
502
  } else {
package/bin/uninstall.js CHANGED
@@ -1,20 +1,14 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  import fs from 'fs';
4
- import os from 'os';
5
4
  import path from 'path';
6
5
  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';
6
+ import {
7
+ HOME, CLAUDE_DIR,
8
+ CONFIG_PATH, STATE_PATH, PID_PATH, RESOLVER_PATH, LISTENER_LOG_PATH,
9
+ SETTINGS_PATH, INSTALLED_PLUGINS_PATH, KNOWN_MARKETPLACES_PATH,
10
+ HOOK_COMMAND, PLUGIN_KEY, MARKETPLACE_KEY,
11
+ } from './constants.js';
18
12
 
19
13
  function isPluginHookCommand (command) {
20
14
  if (typeof command !== 'string') {
@@ -155,7 +149,7 @@ function stopListenerIfRunning () {
155
149
  let stopped = false;
156
150
  const processed = new Set();
157
151
 
158
- const pidFromFile = readPid(pidFile);
152
+ const pidFromFile = readPid(PID_PATH);
159
153
  if (pidFromFile) {
160
154
  if (killProcessTree(pidFromFile)) {
161
155
  stopped = true;
@@ -172,19 +166,19 @@ function stopListenerIfRunning () {
172
166
  }
173
167
  }
174
168
 
175
- removeFileIfExists(pidFile);
169
+ removeFileIfExists(PID_PATH);
176
170
  return stopped;
177
171
  }
178
172
 
173
+ console.log('\nUninstalling Claude Notification Plugin...\n');
174
+
179
175
  // Stop listener daemon if running
180
- const listenerStopped = stopListenerIfRunning();
176
+ stopListenerIfRunning();
181
177
 
182
178
  // Remove hooks from settings.json
183
- let hooksRemoved = false;
184
- let hooksRemoveError = '';
185
- if (fs.existsSync(settingsPath)) {
179
+ if (fs.existsSync(SETTINGS_PATH)) {
186
180
  try {
187
- const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf-8'));
181
+ const settings = JSON.parse(fs.readFileSync(SETTINGS_PATH, 'utf-8'));
188
182
  let hadPluginHooks = false;
189
183
 
190
184
  if (settings.hooks) {
@@ -215,6 +209,7 @@ if (fs.existsSync(settingsPath)) {
215
209
  if (Object.keys(settings.enabledPlugins).length === 0) {
216
210
  delete settings.enabledPlugins;
217
211
  }
212
+ console.log('Removed plugin from enabledPlugins in settings.json');
218
213
  }
219
214
 
220
215
  // Remove marketplace from extraKnownMarketplaces
@@ -223,39 +218,39 @@ if (fs.existsSync(settingsPath)) {
223
218
  if (Object.keys(settings.extraKnownMarketplaces).length === 0) {
224
219
  delete settings.extraKnownMarketplaces;
225
220
  }
221
+ console.log('Removed marketplace from extraKnownMarketplaces in settings.json');
226
222
  }
227
223
 
228
- fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
224
+ fs.writeFileSync(SETTINGS_PATH, JSON.stringify(settings, null, 2));
229
225
 
230
226
  // Verify hooks were actually removed
231
- const verify = JSON.parse(fs.readFileSync(settingsPath, 'utf-8'));
227
+ const verify = JSON.parse(fs.readFileSync(SETTINGS_PATH, 'utf-8'));
232
228
  const remainingPluginHooks = verify.hooks
233
229
  ? Object.values(verify.hooks).some((matchers) =>
234
230
  Array.isArray(matchers) &&
235
231
  matchers.some((m) => m.hooks?.some((h) => isPluginHookCommand(h.command))),
236
232
  )
237
233
  : false;
238
- hooksRemoved = hadPluginHooks && !remainingPluginHooks;
239
- if (hadPluginHooks && remainingPluginHooks) {
240
- hooksRemoveError = 'Hooks still present in settings.json after removal attempt';
234
+ if (hadPluginHooks && !remainingPluginHooks) {
235
+ console.log('Removed hooks from settings.json');
236
+ } else if (hadPluginHooks && remainingPluginHooks) {
237
+ console.warn('Warning: hooks still present in settings.json after removal attempt');
241
238
  }
242
239
  } catch (err) {
243
- hooksRemoveError = `Failed to update settings.json: ${err.message}`;
240
+ console.error(`Error: failed to update settings.json: ${err.message}`);
244
241
  }
245
242
  }
246
243
 
247
244
  // 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]) {
245
+ for (const file of [CONFIG_PATH, STATE_PATH, RESOLVER_PATH, PID_PATH, LISTENER_LOG_PATH]) {
251
246
  if (fs.existsSync(file)) {
252
247
  fs.unlinkSync(file);
248
+ console.log(`Removed ${path.basename(file)}`);
253
249
  }
254
250
  }
255
251
 
256
252
  // Remove CLI wrapper script
257
253
  const WRAPPER_NAMES = ['claude-notify'];
258
- let cliBinsRemoved = false;
259
254
  const ext = process.platform === 'win32' ? '.cmd' : '';
260
255
 
261
256
  // Collect directories to check for wrapper scripts
@@ -271,28 +266,26 @@ try {
271
266
  }
272
267
 
273
268
  // ~/.local/bin (common on Linux/macOS, also used on Windows)
274
- wrapperDirs.add(path.join(home, '.local', 'bin'));
269
+ wrapperDirs.add(path.join(HOME, '.local', 'bin'));
275
270
 
276
271
  for (const dir of wrapperDirs) {
277
272
  for (const name of WRAPPER_NAMES) {
278
273
  const filePath = path.join(dir, `${name}${ext}`);
279
274
  if (fs.existsSync(filePath)) {
280
275
  fs.unlinkSync(filePath);
281
- cliBinsRemoved = true;
276
+ console.log(`Removed CLI wrapper: ${filePath}`);
282
277
  }
283
278
  }
284
279
  }
285
280
 
286
281
  // Remove from installed_plugins.json
287
- const installedPluginsPath = path.join(claudeDir, 'plugins', 'installed_plugins.json');
288
- let pluginEntryRemoved = false;
289
- if (fs.existsSync(installedPluginsPath)) {
282
+ if (fs.existsSync(INSTALLED_PLUGINS_PATH)) {
290
283
  try {
291
- const data = JSON.parse(fs.readFileSync(installedPluginsPath, 'utf-8'));
284
+ const data = JSON.parse(fs.readFileSync(INSTALLED_PLUGINS_PATH, 'utf-8'));
292
285
  if (data.plugins?.[PLUGIN_KEY]) {
293
286
  delete data.plugins[PLUGIN_KEY];
294
- fs.writeFileSync(installedPluginsPath, JSON.stringify(data, null, 2));
295
- pluginEntryRemoved = true;
287
+ fs.writeFileSync(INSTALLED_PLUGINS_PATH, JSON.stringify(data, null, 2));
288
+ console.log('Removed plugin from installed_plugins.json');
296
289
  }
297
290
  } catch {
298
291
  // ignore
@@ -300,13 +293,12 @@ if (fs.existsSync(installedPluginsPath)) {
300
293
  }
301
294
 
302
295
  // Remove marketplace from known_marketplaces.json if no plugins reference it
303
- const knownMarketplacesPath = path.join(claudeDir, 'plugins', 'known_marketplaces.json');
304
296
  try {
305
- if (fs.existsSync(knownMarketplacesPath)) {
297
+ if (fs.existsSync(KNOWN_MARKETPLACES_PATH)) {
306
298
  // Check if any remaining plugins reference this marketplace
307
299
  let hasMarketplacePlugins = false;
308
- if (fs.existsSync(installedPluginsPath)) {
309
- const data = JSON.parse(fs.readFileSync(installedPluginsPath, 'utf-8'));
300
+ if (fs.existsSync(INSTALLED_PLUGINS_PATH)) {
301
+ const data = JSON.parse(fs.readFileSync(INSTALLED_PLUGINS_PATH, 'utf-8'));
310
302
  if (data.plugins) {
311
303
  hasMarketplacePlugins = Object.keys(data.plugins)
312
304
  .some((key) => key.endsWith(`@${MARKETPLACE_KEY}`));
@@ -314,13 +306,13 @@ try {
314
306
  }
315
307
 
316
308
  if (!hasMarketplacePlugins) {
317
- const marketplaces = JSON.parse(fs.readFileSync(knownMarketplacesPath, 'utf-8'));
309
+ const marketplaces = JSON.parse(fs.readFileSync(KNOWN_MARKETPLACES_PATH, 'utf-8'));
318
310
  if (marketplaces[MARKETPLACE_KEY]) {
319
311
  delete marketplaces[MARKETPLACE_KEY];
320
- fs.writeFileSync(knownMarketplacesPath, JSON.stringify(marketplaces, null, 2));
312
+ fs.writeFileSync(KNOWN_MARKETPLACES_PATH, JSON.stringify(marketplaces, null, 2));
321
313
  console.log(`Removed marketplace "${MARKETPLACE_KEY}" from known_marketplaces.json`);
322
314
  }
323
- const marketplaceDir = path.join(claudeDir, 'plugins', 'marketplaces', MARKETPLACE_KEY);
315
+ const marketplaceDir = path.join(CLAUDE_DIR, 'plugins', 'marketplaces', MARKETPLACE_KEY);
324
316
  if (fs.existsSync(marketplaceDir)) {
325
317
  fs.rmSync(marketplaceDir, { recursive: true, force: true });
326
318
  console.log(`Removed marketplace directory: ${marketplaceDir}`);
@@ -332,41 +324,17 @@ try {
332
324
  }
333
325
 
334
326
  // Remove plugin cache
335
- const pluginCacheDir = path.join(claudeDir, 'plugins', 'cache', 'bazilio-plugins', 'claude-notification-plugin');
336
- let cacheRemoved = false;
327
+ const pluginCacheDir = path.join(CLAUDE_DIR, 'plugins', 'cache', 'bazilio-plugins', 'claude-notification-plugin');
337
328
  if (fs.existsSync(pluginCacheDir)) {
338
329
  fs.rmSync(pluginCacheDir, { recursive: true, force: true });
339
- cacheRemoved = !fs.existsSync(pluginCacheDir);
330
+ if (fs.existsSync(pluginCacheDir)) {
331
+ console.warn(`Warning: could not fully remove plugin cache directory:\n ${pluginCacheDir}\nPlease remove it manually.`);
332
+ } else {
333
+ console.log('Removed plugin cache');
334
+ }
340
335
  }
341
336
 
342
- const hooksStatus = hooksRemoved
343
- ? 'Hooks removed from settings.json'
344
- : hooksRemoveError
345
- ? `Warning: ${hooksRemoveError}`
346
- : 'No hooks found in settings.json';
347
-
348
- const extras = [
349
- listenerStopped ? 'Listener daemon stopped.' : '',
350
- pluginEntryRemoved || cacheRemoved ? 'Plugin registration cleaned.' : '',
351
- cliBinsRemoved ? 'CLI wrapper scripts removed.' : '',
352
- ].filter(Boolean).join('\n');
353
-
354
- const cacheStillExists = fs.existsSync(pluginCacheDir);
355
- if (cacheStillExists) {
356
- console.log(`
357
- Warning: Could not fully remove plugin cache directory:
358
- ${pluginCacheDir}
359
- Please remove it manually.
360
- ${hooksStatus}
361
- Config files deleted.${extras ? `\n${extras}` : ''}
362
- `);
363
- } else {
364
- console.log(`
365
- Claude Notification Plugin uninstalled.
366
- ${hooksStatus}
367
- Config files deleted.${extras ? `\n${extras}` : ''}
368
- `);
369
- }
337
+ console.log('\nDone.\n');
370
338
 
371
339
  // If run manually (not via npm lifecycle), remove the global npm package too
372
340
  if (!process.env.npm_lifecycle_event) {
package/commit-sha CHANGED
@@ -1 +1 @@
1
- ce8065e53638e41f0ae971030f26f0b556dc287b
1
+ 87470f0ddd7c4de3cdcbe91886dc3285b942c595
@@ -296,7 +296,7 @@ Re-run `claude-notify listener setup` anytime to reconfigure.
296
296
 
297
297
  ### Manual configuration
298
298
 
299
- Full example of `~/.claude/notifier.config.json` with the listener section:
299
+ Full example of `~/.claude/claude-notify.config.json` with the listener section:
300
300
 
301
301
  ```json
302
302
  {
@@ -882,7 +882,7 @@ claude-notify listener status
882
882
 
883
883
  Check:
884
884
 
885
- 1. Does the config exist? `cat ~/.claude/notifier.config.json`
885
+ 1. Does the config exist? `cat ~/.claude/claude-notify.config.json`
886
886
  2. Are `telegram.token` and `telegram.chatId` present?
887
887
  3. Is there a `listener.projects` section?
888
888
  4. Logs: `claude-notify listener logs`
@@ -2,7 +2,6 @@
2
2
  // noinspection UnnecessaryLocalVariableJS
3
3
 
4
4
  import fs from 'fs';
5
- import os from 'os';
6
5
  import path from 'path';
7
6
  import process from 'process';
8
7
  import { createLogger } from './logger.js';
@@ -12,13 +11,13 @@ import { WorkQueue } from './work-queue.js';
12
11
  import { TaskRunner } from './task-runner.js';
13
12
  import { WorktreeManager } from './worktree-manager.js';
14
13
  import { parseMessage, parseTarget } from './message-parser.js';
14
+ import { CLAUDE_DIR, CONFIG_PATH, LISTENER_LOG_FILENAME } from '../bin/constants.js';
15
15
 
16
16
  // ----------------------
17
17
  // CONFIG
18
18
  // ----------------------
19
19
 
20
- const CONFIG_PATH = path.join(os.homedir(), '.claude', 'notifier.config.json');
21
- const DEFAULT_LOG_DIR = path.join(os.homedir(), '.claude');
20
+ const DEFAULT_LOG_DIR = CLAUDE_DIR;
22
21
 
23
22
  function loadConfig () {
24
23
  try {
@@ -37,7 +36,7 @@ function loadConfig () {
37
36
  const config = loadConfig();
38
37
  const listenerLogDir = config.listener?.logDir || DEFAULT_LOG_DIR;
39
38
  fs.mkdirSync(listenerLogDir, { recursive: true });
40
- const logger = createLogger(path.join(listenerLogDir, '.cc-n-listener.log'));
39
+ const logger = createLogger(path.join(listenerLogDir, LISTENER_LOG_FILENAME));
41
40
 
42
41
  // Validate required fields
43
42
  const token = process.env.CLAUDE_NOTIFY_TELEGRAM_TOKEN || config.telegramToken || config.telegram?.token;
@@ -1,10 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  import fs from 'fs';
4
- import os from 'os';
5
4
  import path from 'path';
6
5
  import process from 'process';
7
6
  import { execSync, spawn } from 'child_process';
7
+ import { CONFIG_PATH, STATE_PATH } from '../bin/constants.js';
8
8
 
9
9
  // ----------------------
10
10
  // CONFIG
@@ -40,7 +40,7 @@ function getBranch (cwd) {
40
40
  }
41
41
 
42
42
  function loadConfig () {
43
- const configPath = path.join(os.homedir(), '.claude', 'notifier.config.json');
43
+ const configPath = CONFIG_PATH;
44
44
 
45
45
  const config = {
46
46
  telegram: {
@@ -158,27 +158,19 @@ function isNotifierDisabled () {
158
158
  return true;
159
159
  }
160
160
  // Skip notifications for listener-spawned tasks unless explicitly enabled
161
- if (process.env.CLAUDE_NOTIFY_FROM_LISTENER === '1'
162
- && process.env.CLAUDE_NOTIFY_AFTER_LISTENER !== '1') {
163
- return true;
164
- }
165
- return false;
161
+ return process.env.CLAUDE_NOTIFY_FROM_LISTENER === '1'
162
+ && process.env.CLAUDE_NOTIFY_AFTER_LISTENER !== '1';
163
+
166
164
  }
167
165
 
168
166
  // ----------------------
169
167
  // STATE FILE
170
168
  // ----------------------
171
169
 
172
- const STATE_FILE = path.join(
173
- os.homedir(),
174
- '.claude',
175
- '.notifier_state.json',
176
- );
177
-
178
170
  function loadState () {
179
- if (fs.existsSync(STATE_FILE)) {
171
+ if (fs.existsSync(STATE_PATH)) {
180
172
  try {
181
- const raw = JSON.parse(fs.readFileSync(STATE_FILE, 'utf-8'));
173
+ const raw = JSON.parse(fs.readFileSync(STATE_PATH, 'utf-8'));
182
174
  // Migrate flat state (pre-session format) to new format
183
175
  if (!raw.sessions && raw.start !== undefined) {
184
176
  return { sessions: {}, sentMessages: raw.sentMessages || [] };
@@ -198,9 +190,9 @@ function loadState () {
198
190
  }
199
191
 
200
192
  function saveState (state) {
201
- const dir = path.dirname(STATE_FILE);
193
+ const dir = path.dirname(STATE_PATH);
202
194
  fs.mkdirSync(dir, { recursive: true });
203
- fs.writeFileSync(STATE_FILE, JSON.stringify(state));
195
+ fs.writeFileSync(STATE_PATH, JSON.stringify(state));
204
196
  }
205
197
 
206
198
  function cleanStaleSessions (state) {
@@ -259,7 +251,7 @@ function markdownToTelegramHtml (md) {
259
251
  result = result.replace(/~~(.+?)~~/g, '<s>$1</s>');
260
252
 
261
253
  // Links: [text](url)
262
- result = result.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2">$1</a>');
254
+ result = result.replace(/\[([^\]]+)]\(([^)]+)\)/g, '<a href="$2">$1</a>');
263
255
 
264
256
  // Restore code blocks and inline codes
265
257
  result = result.replace(/\x00CB(\d+)\x00/g, (_, i) => codeBlocks[i]);
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.1.6",
4
+ "version": "1.1.10",
5
5
  "description": "Claude Code task-completion notifications: Telegram, desktop notifications (Windows/macOS/Linux), sound, and voice",
6
6
  "type": "module",
7
7
  "engines": {
@@ -53,6 +53,7 @@
53
53
  "node-notifier": "^10.0.1"
54
54
  },
55
55
  "devDependencies": {
56
- "eslint-plugin-import": "^2.31.0"
56
+ "eslint-plugin-import": "^2.31.0",
57
+ "eslint-plugin-unused-imports": "^4.4.1"
57
58
  }
58
59
  }