claude-notification-plugin 1.0.101 → 1.0.102

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.101",
3
+ "version": "1.0.102",
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/bin/uninstall.js CHANGED
@@ -9,48 +9,87 @@ const home = os.homedir();
9
9
  const claudeDir = path.join(home, '.claude');
10
10
  const configPath = path.join(claudeDir, 'notifier.config.json');
11
11
  const settingsPath = path.join(claudeDir, 'settings.json');
12
- const statePath = path.join(claudeDir, '.notifier_state.json');
13
-
14
- const HOOK_COMMAND = 'claude-notify';
15
- const PLUGIN_KEY = 'claude-notification-plugin@bazilio-plugins';
16
- const MARKETPLACE_KEY = 'bazilio-plugins';
17
-
18
- function isPluginHookCommand (command) {
19
- if (typeof command !== 'string') {
20
- return false;
21
- }
22
-
23
- const normalized = command.trim().toLowerCase();
24
- if (!normalized) {
25
- return false;
26
- }
27
-
28
- return normalized === HOOK_COMMAND || normalized.startsWith(`${HOOK_COMMAND} `);
29
- }
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
+ function isPluginHookCommand (command) {
20
+ if (typeof command !== 'string') {
21
+ return false;
22
+ }
23
+
24
+ const normalized = command.trim().toLowerCase();
25
+ if (!normalized) {
26
+ return false;
27
+ }
28
+
29
+ return normalized === HOOK_COMMAND || normalized.startsWith(`${HOOK_COMMAND} `);
30
+ }
31
+
32
+ // Stop listener daemon if running
33
+ let listenerStopped = false;
34
+ try {
35
+ if (fs.existsSync(pidFile)) {
36
+ const pid = parseInt(fs.readFileSync(pidFile, 'utf-8').trim(), 10);
37
+ if (!isNaN(pid)) {
38
+ try {
39
+ if (process.platform === 'win32') {
40
+ execSync(`taskkill /PID ${pid} /T /F`, { stdio: 'ignore', windowsHide: true });
41
+ } else {
42
+ process.kill(pid, 'SIGTERM');
43
+ let tries = 10;
44
+ const isAlive = (p) => {
45
+ try {
46
+ process.kill(p, 0);
47
+ return true;
48
+ } catch {
49
+ return false;
50
+ }
51
+ };
52
+ while (tries-- > 0 && isAlive(pid)) {
53
+ execSync('sleep 0.5', { stdio: 'ignore' });
54
+ }
55
+ if (isAlive(pid)) {
56
+ process.kill(pid, 'SIGKILL');
57
+ }
58
+ }
59
+ listenerStopped = true;
60
+ } catch {
61
+ // process may already be dead
62
+ }
63
+ fs.unlinkSync(pidFile);
64
+ }
65
+ }
66
+ } catch {
67
+ // ignore
68
+ }
30
69
 
31
70
  // Remove hooks from settings.json
32
- let hooksRemoved = false;
33
- let hooksRemoveError = '';
34
- if (fs.existsSync(settingsPath)) {
35
- try {
36
- const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf-8'));
37
- let hadPluginHooks = false;
38
-
39
- if (settings.hooks) {
40
- for (const event of Object.keys(settings.hooks)) {
41
- const eventHooks = Array.isArray(settings.hooks[event]) ? settings.hooks[event] : [];
42
- const hadInEvent = eventHooks.some((matcher) =>
43
- matcher.hooks?.some((h) => isPluginHookCommand(h.command)),
44
- );
45
- hadPluginHooks = hadPluginHooks || hadInEvent;
46
-
47
- settings.hooks[event] = settings.hooks[event].filter((matcher) =>
48
- !matcher.hooks?.some((h) => isPluginHookCommand(h.command)),
49
- );
50
-
51
- if (settings.hooks[event].length === 0) {
52
- delete settings.hooks[event];
53
- }
71
+ let hooksRemoved = false;
72
+ let hooksRemoveError = '';
73
+ if (fs.existsSync(settingsPath)) {
74
+ try {
75
+ const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf-8'));
76
+ let hadPluginHooks = false;
77
+
78
+ if (settings.hooks) {
79
+ for (const event of Object.keys(settings.hooks)) {
80
+ const eventHooks = Array.isArray(settings.hooks[event]) ? settings.hooks[event] : [];
81
+ const hadInEvent = eventHooks.some((matcher) =>
82
+ matcher.hooks?.some((h) => isPluginHookCommand(h.command)),
83
+ );
84
+ hadPluginHooks = hadPluginHooks || hadInEvent;
85
+
86
+ settings.hooks[event] = settings.hooks[event].filter((matcher) =>
87
+ !matcher.hooks?.some((h) => isPluginHookCommand(h.command)),
88
+ );
89
+
90
+ if (settings.hooks[event].length === 0) {
91
+ delete settings.hooks[event];
92
+ }
54
93
  }
55
94
 
56
95
  if (Object.keys(settings.hooks).length === 0) {
@@ -76,33 +115,34 @@ if (fs.existsSync(settingsPath)) {
76
115
 
77
116
  fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
78
117
 
79
- // Verify hooks were actually removed
80
- const verify = JSON.parse(fs.readFileSync(settingsPath, 'utf-8'));
81
- const remainingPluginHooks = verify.hooks
82
- ? Object.values(verify.hooks).some((matchers) =>
83
- Array.isArray(matchers) &&
84
- matchers.some((m) => m.hooks?.some((h) => isPluginHookCommand(h.command))),
85
- )
86
- : false;
87
- hooksRemoved = hadPluginHooks && !remainingPluginHooks;
88
- if (hadPluginHooks && remainingPluginHooks) {
89
- hooksRemoveError = 'Hooks still present in settings.json after removal attempt';
90
- }
91
- } catch (err) {
92
- hooksRemoveError = `Failed to update settings.json: ${err.message}`;
93
- }
118
+ // Verify hooks were actually removed
119
+ const verify = JSON.parse(fs.readFileSync(settingsPath, 'utf-8'));
120
+ const remainingPluginHooks = verify.hooks
121
+ ? Object.values(verify.hooks).some((matchers) =>
122
+ Array.isArray(matchers) &&
123
+ matchers.some((m) => m.hooks?.some((h) => isPluginHookCommand(h.command))),
124
+ )
125
+ : false;
126
+ hooksRemoved = hadPluginHooks && !remainingPluginHooks;
127
+ if (hadPluginHooks && remainingPluginHooks) {
128
+ hooksRemoveError = 'Hooks still present in settings.json after removal attempt';
129
+ }
130
+ } catch (err) {
131
+ hooksRemoveError = `Failed to update settings.json: ${err.message}`;
132
+ }
94
133
  }
95
134
 
96
- // Remove config, state, and resolver files
135
+ // Remove config, state, resolver, and listener files
97
136
  const resolverPath = path.join(claudeDir, 'claude-notify-resolve.js');
98
- for (const file of [configPath, statePath, resolverPath]) {
137
+ const listenerLogFile = path.join(claudeDir, '.cc-n-listener.log');
138
+ for (const file of [configPath, statePath, resolverPath, pidFile, listenerLogFile]) {
99
139
  if (fs.existsSync(file)) {
100
140
  fs.unlinkSync(file);
101
141
  }
102
142
  }
103
143
 
104
- // Remove CLI wrapper script
105
- const WRAPPER_NAMES = ['claude-notify'];
144
+ // Remove CLI wrapper script
145
+ const WRAPPER_NAMES = ['claude-notify'];
106
146
  let cliBinsRemoved = false;
107
147
  const ext = process.platform === 'win32' ? '.cmd' : '';
108
148
 
@@ -162,6 +202,7 @@ const hooksStatus = hooksRemoved
162
202
  : 'No hooks found in settings.json';
163
203
 
164
204
  const extras = [
205
+ listenerStopped ? 'Listener daemon stopped.' : '',
165
206
  pluginEntryRemoved || cacheRemoved ? 'Plugin registration cleaned.' : '',
166
207
  cliBinsRemoved ? 'CLI wrapper scripts removed.' : '',
167
208
  ].filter(Boolean).join('\n');
package/commit-sha CHANGED
@@ -1 +1 @@
1
- e766d03df0c9561a16f5f2e1dff3dc896c1ee862
1
+ 8cd1016fa338ed33fd68b1c9350ca62df5b00bf4
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.101",
4
+ "version": "1.0.102",
5
5
  "description": "Claude Code task-completion notifications: Telegram, desktop notifications (Windows/macOS/Linux), sound, and voice",
6
6
  "type": "module",
7
7
  "engines": {