claude-notification-plugin 1.1.9 → 1.1.12

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
@@ -48,11 +48,12 @@ claude-notify install
48
48
  claude-notify uninstall
49
49
  ```
50
50
 
51
- This removes hooks, config, CLI wrappers, plugin registration, and the npm global package.
51
+ This removes hooks, CLI wrappers, plugin registration, and the npm global package.
52
+ Your config file (`~/.claude/claude-notify.config.json`) is preserved so settings survive reinstalls.
52
53
 
53
54
  ## Configuration
54
55
 
55
- Config file: `~/.claude/notifier.config.json`
56
+ Config file: `~/.claude/claude-notify.config.json`
56
57
 
57
58
  ```json
58
59
  {
@@ -293,7 +294,7 @@ Alternative: add **@userinfobot** to a chat and it will reply with the ID.
293
294
 
294
295
  ```
295
296
  claude-notify install Reinstall plugin registration, Telegram config, hooks
296
- claude-notify uninstall Remove plugin, hooks, config, CLI wrappers
297
+ claude-notify uninstall Remove plugin, hooks, CLI wrappers (config preserved)
297
298
  claude-notify listener <action> Manage the Telegram Listener daemon
298
299
  Actions: start, stop, status, setup, logs, restart
299
300
  ```
@@ -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',
@@ -451,9 +438,9 @@ async function main () {
451
438
 
452
439
  // 2. Interactive Telegram setup
453
440
  let existing = {};
454
- if (fs.existsSync(configPath)) {
441
+ if (fs.existsSync(CONFIG_PATH)) {
455
442
  try {
456
- existing = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
443
+ existing = JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf-8'));
457
444
  } catch {
458
445
  // ignore malformed config
459
446
  }
@@ -522,7 +509,7 @@ Send any message to your bot in Telegram, then press Enter.\x1b[0m`);
522
509
  }
523
510
 
524
511
  // 3. Write config
525
- fs.mkdirSync(claudeDir, { recursive: true });
512
+ fs.mkdirSync(CLAUDE_DIR, { recursive: true });
526
513
 
527
514
  const platform = process.platform;
528
515
  let defaultSoundFile;
@@ -556,7 +543,7 @@ Send any message to your bot in Telegram, then press Enter.\x1b[0m`);
556
543
  debug: false,
557
544
  listener: {
558
545
  projects: {},
559
- worktreeBaseDir: path.join(home, '.claude', 'worktrees'),
546
+ worktreeBaseDir: path.join(HOME, '.claude', 'worktrees'),
560
547
  autoCreateWorktree: true,
561
548
  taskTimeoutMinutes: 30,
562
549
  maxQueuePerWorkDir: 10,
@@ -580,15 +567,15 @@ Send any message to your bot in Telegram, then press Enter.\x1b[0m`);
580
567
  config.telegram.chatId = chatId;
581
568
  }
582
569
 
583
- fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
584
- console.log(` Config saved: ${configPath}`);
570
+ fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2));
571
+ console.log(` Config saved: ${CONFIG_PATH}`);
585
572
 
586
573
  // 4. Register hooks
587
574
  let settings = {};
588
575
 
589
- if (fs.existsSync(settingsPath)) {
576
+ if (fs.existsSync(SETTINGS_PATH)) {
590
577
  try {
591
- settings = JSON.parse(fs.readFileSync(settingsPath, 'utf-8'));
578
+ settings = JSON.parse(fs.readFileSync(SETTINGS_PATH, 'utf-8'));
592
579
  } catch {
593
580
  settings = {};
594
581
  }
@@ -617,8 +604,8 @@ Send any message to your bot in Telegram, then press Enter.\x1b[0m`);
617
604
  };
618
605
  console.log(' Registered marketplace in extraKnownMarketplaces');
619
606
 
620
- fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
621
- console.log(` Settings saved: ${settingsPath}`);
607
+ fs.writeFileSync(SETTINGS_PATH, JSON.stringify(settings, null, 2));
608
+ console.log(` Settings saved: ${SETTINGS_PATH}`);
622
609
 
623
610
  // 5. Summary
624
611
  const telegramStatus = config.telegram.token && config.telegram.chatId
@@ -642,10 +629,10 @@ Plugin hooks (via hooks/hooks.json):
642
629
  - Stop (task finished)
643
630
  - Notification (waiting for input)
644
631
 
645
- Config: ${configPath}
632
+ Config: ${CONFIG_PATH}
646
633
  ${telegramStatus}${platformTip}
647
634
 
648
- Log: ${installLogPath}
635
+ Log: ${INSTALL_LOG_PATH}
649
636
 
650
637
  To uninstall: claude-notify uninstall
651
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
+ 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,7 +166,7 @@ function stopListenerIfRunning () {
172
166
  }
173
167
  }
174
168
 
175
- removeFileIfExists(pidFile);
169
+ removeFileIfExists(PID_PATH);
176
170
  return stopped;
177
171
  }
178
172
 
@@ -182,9 +176,9 @@ console.log('\nUninstalling Claude Notification Plugin...\n');
182
176
  stopListenerIfRunning();
183
177
 
184
178
  // Remove hooks from settings.json
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) {
@@ -227,10 +221,10 @@ if (fs.existsSync(settingsPath)) {
227
221
  console.log('Removed marketplace from extraKnownMarketplaces in settings.json');
228
222
  }
229
223
 
230
- fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
224
+ fs.writeFileSync(SETTINGS_PATH, JSON.stringify(settings, null, 2));
231
225
 
232
226
  // Verify hooks were actually removed
233
- const verify = JSON.parse(fs.readFileSync(settingsPath, 'utf-8'));
227
+ const verify = JSON.parse(fs.readFileSync(SETTINGS_PATH, 'utf-8'));
234
228
  const remainingPluginHooks = verify.hooks
235
229
  ? Object.values(verify.hooks).some((matchers) =>
236
230
  Array.isArray(matchers) &&
@@ -247,10 +241,8 @@ if (fs.existsSync(settingsPath)) {
247
241
  }
248
242
  }
249
243
 
250
- // Remove config, state, resolver, and listener files
251
- const resolverPath = path.join(claudeDir, 'claude-notify-resolve.js');
252
- const listenerLogFile = path.join(claudeDir, '.cc-n-listener.log');
253
- for (const file of [configPath, statePath, resolverPath, pidFile, listenerLogFile]) {
244
+ // Remove state, resolver, and listener files (config is preserved for reinstall)
245
+ for (const file of [STATE_PATH, RESOLVER_PATH, PID_PATH, LISTENER_LOG_PATH]) {
254
246
  if (fs.existsSync(file)) {
255
247
  fs.unlinkSync(file);
256
248
  console.log(`Removed ${path.basename(file)}`);
@@ -274,7 +266,7 @@ try {
274
266
  }
275
267
 
276
268
  // ~/.local/bin (common on Linux/macOS, also used on Windows)
277
- wrapperDirs.add(path.join(home, '.local', 'bin'));
269
+ wrapperDirs.add(path.join(HOME, '.local', 'bin'));
278
270
 
279
271
  for (const dir of wrapperDirs) {
280
272
  for (const name of WRAPPER_NAMES) {
@@ -287,13 +279,12 @@ for (const dir of wrapperDirs) {
287
279
  }
288
280
 
289
281
  // Remove from installed_plugins.json
290
- const installedPluginsPath = path.join(claudeDir, 'plugins', 'installed_plugins.json');
291
- if (fs.existsSync(installedPluginsPath)) {
282
+ if (fs.existsSync(INSTALLED_PLUGINS_PATH)) {
292
283
  try {
293
- const data = JSON.parse(fs.readFileSync(installedPluginsPath, 'utf-8'));
284
+ const data = JSON.parse(fs.readFileSync(INSTALLED_PLUGINS_PATH, 'utf-8'));
294
285
  if (data.plugins?.[PLUGIN_KEY]) {
295
286
  delete data.plugins[PLUGIN_KEY];
296
- fs.writeFileSync(installedPluginsPath, JSON.stringify(data, null, 2));
287
+ fs.writeFileSync(INSTALLED_PLUGINS_PATH, JSON.stringify(data, null, 2));
297
288
  console.log('Removed plugin from installed_plugins.json');
298
289
  }
299
290
  } catch {
@@ -302,13 +293,12 @@ if (fs.existsSync(installedPluginsPath)) {
302
293
  }
303
294
 
304
295
  // Remove marketplace from known_marketplaces.json if no plugins reference it
305
- const knownMarketplacesPath = path.join(claudeDir, 'plugins', 'known_marketplaces.json');
306
296
  try {
307
- if (fs.existsSync(knownMarketplacesPath)) {
297
+ if (fs.existsSync(KNOWN_MARKETPLACES_PATH)) {
308
298
  // Check if any remaining plugins reference this marketplace
309
299
  let hasMarketplacePlugins = false;
310
- if (fs.existsSync(installedPluginsPath)) {
311
- 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'));
312
302
  if (data.plugins) {
313
303
  hasMarketplacePlugins = Object.keys(data.plugins)
314
304
  .some((key) => key.endsWith(`@${MARKETPLACE_KEY}`));
@@ -316,13 +306,13 @@ try {
316
306
  }
317
307
 
318
308
  if (!hasMarketplacePlugins) {
319
- const marketplaces = JSON.parse(fs.readFileSync(knownMarketplacesPath, 'utf-8'));
309
+ const marketplaces = JSON.parse(fs.readFileSync(KNOWN_MARKETPLACES_PATH, 'utf-8'));
320
310
  if (marketplaces[MARKETPLACE_KEY]) {
321
311
  delete marketplaces[MARKETPLACE_KEY];
322
- fs.writeFileSync(knownMarketplacesPath, JSON.stringify(marketplaces, null, 2));
312
+ fs.writeFileSync(KNOWN_MARKETPLACES_PATH, JSON.stringify(marketplaces, null, 2));
323
313
  console.log(`Removed marketplace "${MARKETPLACE_KEY}" from known_marketplaces.json`);
324
314
  }
325
- const marketplaceDir = path.join(claudeDir, 'plugins', 'marketplaces', MARKETPLACE_KEY);
315
+ const marketplaceDir = path.join(CLAUDE_DIR, 'plugins', 'marketplaces', MARKETPLACE_KEY);
326
316
  if (fs.existsSync(marketplaceDir)) {
327
317
  fs.rmSync(marketplaceDir, { recursive: true, force: true });
328
318
  console.log(`Removed marketplace directory: ${marketplaceDir}`);
@@ -334,7 +324,7 @@ try {
334
324
  }
335
325
 
336
326
  // Remove plugin cache
337
- const pluginCacheDir = path.join(claudeDir, 'plugins', 'cache', 'bazilio-plugins', 'claude-notification-plugin');
327
+ const pluginCacheDir = path.join(CLAUDE_DIR, 'plugins', 'cache', 'bazilio-plugins', 'claude-notification-plugin');
338
328
  if (fs.existsSync(pluginCacheDir)) {
339
329
  fs.rmSync(pluginCacheDir, { recursive: true, force: true });
340
330
  if (fs.existsSync(pluginCacheDir)) {
package/commit-sha CHANGED
@@ -1 +1 @@
1
- 28f14a4cb1dfe7e6a2089eae6db7a5b05adc1f1e
1
+ 5b2891cb5b43d7ccdd81b8d6541498b52ef549ee
@@ -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]);
@@ -398,9 +390,12 @@ async function sendDesktopNotification (config, message) {
398
390
  }
399
391
  try {
400
392
  const { default: notifier } = await import('node-notifier');
393
+ const iconPath = new URL('../claude_img/claude.png', import.meta.url).pathname
394
+ .replace(/^\/([a-zA-Z]:)/, '$1');
401
395
  notifier.notify({
402
396
  title: 'Claude Code',
403
397
  message,
398
+ icon: iconPath,
404
399
  sound: false,
405
400
  wait: false,
406
401
  });
@@ -684,21 +679,21 @@ process.stdin.on('end', async () => {
684
679
  process.exit(0);
685
680
  }
686
681
 
687
- const title = eventType === 'Notification'
688
- ? 'Claude waiting for input'
689
- : 'Claude finished coding';
682
+ const statusEmoji = eventType === 'Notification' ? '⏸' : '✅';
683
+ const desktopStatus = eventType === 'Notification' ? 'Waiting' : 'Finished';
690
684
 
691
685
  const branch = getBranch(cwd);
692
- const branchLine = branch ? `\nBranch: ${branch}` : '';
693
- const branchLineHtml = branch ? `\nBranch: <b>${escapeHtml(branch)}</b>` : '';
686
+ const label = branch ? `@${project}/${branch}` : `@${project}`;
687
+ const labelHtml = branch
688
+ ? `@<b>${escapeHtml(project)}</b>/<b>${escapeHtml(branch)}</b>`
689
+ : `@<b>${escapeHtml(project)}</b>`;
694
690
 
695
691
  const triggerLine = config.debug ? `\nTrigger: ${eventType}` : '';
696
692
 
697
- let message =
698
- `${title}\n\nProject: ${project}${branchLine}\nDuration: ${duration}s${triggerLine}`;
693
+ const desktopMessage = `${desktopStatus}: ${label}`;
699
694
 
700
695
  let telegramMessage =
701
- `${escapeHtml(title)}\n\nProject: <b>${escapeHtml(project)}</b>${branchLineHtml}\nDuration: ${duration}s${triggerLine}`;
696
+ `${statusEmoji} ${labelHtml} (duration: ${duration}s)${triggerLine}`;
702
697
 
703
698
  if (config.telegram.includeLastCcMessageInTelegram && event.last_assistant_message) {
704
699
  const maxLen = 3500;
@@ -710,18 +705,14 @@ process.stdin.on('end', async () => {
710
705
  }
711
706
 
712
707
  if (config.debug) {
713
- const debugBlockMd = '\n\n*Debug:*\n'
714
- + (config.voice.enabled ? `\nVoice: ${getVoicePhrase(duration)}` : '')
715
- + `\n\nHook input:\n\`\`\`json\n${JSON.stringify(event, null, 2)}\n\`\`\``;
716
708
  const debugBlockHtml = '\n\n<b>Debug:</b>\n'
717
709
  + (config.voice.enabled ? `\nVoice: ${escapeHtml(getVoicePhrase(duration))}` : '')
718
710
  + `\n\nHook input:\n<pre>${escapeHtml(JSON.stringify(event, null, 2))}</pre>`;
719
- message += debugBlockMd;
720
711
  telegramMessage += debugBlockHtml;
721
712
  }
722
713
 
723
714
  await sendWebhook(config, {
724
- title,
715
+ title: `${desktopStatus}: ${label}`,
725
716
  project,
726
717
  branch: branch || undefined,
727
718
  duration,
@@ -730,7 +721,7 @@ process.stdin.on('end', async () => {
730
721
  hookEvent: event,
731
722
  });
732
723
 
733
- state._telegramText = `\u{1F916} ${telegramMessage}`;
724
+ state._telegramText = telegramMessage;
734
725
  await sendTelegram(config, state);
735
726
  delete state._telegramText;
736
727
  if (eventType === 'Stop') {
@@ -738,7 +729,7 @@ process.stdin.on('end', async () => {
738
729
  }
739
730
  saveState(state);
740
731
 
741
- await sendDesktopNotification(config, message);
732
+ await sendDesktopNotification(config, desktopMessage);
742
733
  playSound(config);
743
734
  speakResult(config, duration);
744
735
  });
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.9",
4
+ "version": "1.1.12",
5
5
  "description": "Claude Code task-completion notifications: Telegram, desktop notifications (Windows/macOS/Linux), sound, and voice",
6
6
  "type": "module",
7
7
  "engines": {