claude-notification-plugin 1.0.104 → 1.0.106
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/.claude-plugin/plugin.json +1 -1
- package/bin/uninstall.js +377 -239
- package/commit-sha +1 -1
- package/listener/LISTENER-DETAILED.md +1 -1
- package/listener/listener.js +34 -19
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-notification-plugin",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.106",
|
|
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
|
@@ -1,239 +1,377 @@
|
|
|
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
|
-
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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
)
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
const
|
|
137
|
-
const
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
if (
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
const
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
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
|
+
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
|
+
function isProcessAlive (pid) {
|
|
33
|
+
if (!Number.isInteger(pid) || pid <= 0) {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
try {
|
|
38
|
+
if (process.platform === 'win32') {
|
|
39
|
+
const result = execSync(`tasklist /FI "PID eq ${pid}" /NH`, {
|
|
40
|
+
encoding: 'utf-8',
|
|
41
|
+
windowsHide: true,
|
|
42
|
+
stdio: ['pipe', 'pipe', 'ignore'],
|
|
43
|
+
});
|
|
44
|
+
return result.includes(String(pid));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
process.kill(pid, 0);
|
|
48
|
+
return true;
|
|
49
|
+
} catch {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function killProcessTree (pid) {
|
|
55
|
+
if (!isProcessAlive(pid)) {
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
try {
|
|
60
|
+
if (process.platform === 'win32') {
|
|
61
|
+
execSync(`taskkill /PID ${pid} /T /F`, {
|
|
62
|
+
stdio: 'ignore',
|
|
63
|
+
windowsHide: true,
|
|
64
|
+
});
|
|
65
|
+
console.log(`\x1b[33mListener process ${pid} killed\x1b[0m`);
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
process.kill(pid, 'SIGTERM');
|
|
70
|
+
let tries = 10;
|
|
71
|
+
while (tries-- > 0 && isProcessAlive(pid)) {
|
|
72
|
+
execSync('sleep 0.2', { stdio: 'ignore' });
|
|
73
|
+
}
|
|
74
|
+
if (isProcessAlive(pid)) {
|
|
75
|
+
process.kill(pid, 'SIGKILL');
|
|
76
|
+
}
|
|
77
|
+
console.log(`\x1b[33mListener process ${pid} killed\x1b[0m`);
|
|
78
|
+
return true;
|
|
79
|
+
} catch {
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function readPid (filePath) {
|
|
85
|
+
try {
|
|
86
|
+
if (!fs.existsSync(filePath)) {
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
const pid = parseInt(fs.readFileSync(filePath, 'utf-8').trim(), 10);
|
|
90
|
+
return Number.isInteger(pid) && pid > 0 ? pid : null;
|
|
91
|
+
} catch {
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function removeFileIfExists (filePath) {
|
|
97
|
+
try {
|
|
98
|
+
if (fs.existsSync(filePath)) {
|
|
99
|
+
fs.unlinkSync(filePath);
|
|
100
|
+
}
|
|
101
|
+
} catch {
|
|
102
|
+
// ignore
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function findListenerPids () {
|
|
107
|
+
try {
|
|
108
|
+
if (process.platform === 'win32') {
|
|
109
|
+
const raw = execSync(
|
|
110
|
+
'powershell -NoProfile -Command "Get-CimInstance Win32_Process | Select-Object ProcessId, CommandLine | ConvertTo-Json -Compress"',
|
|
111
|
+
{
|
|
112
|
+
encoding: 'utf-8',
|
|
113
|
+
windowsHide: true,
|
|
114
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
115
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
116
|
+
},
|
|
117
|
+
).trim();
|
|
118
|
+
|
|
119
|
+
if (!raw) {
|
|
120
|
+
return [];
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const rows = JSON.parse(raw);
|
|
124
|
+
const processes = Array.isArray(rows) ? rows : [rows];
|
|
125
|
+
return processes
|
|
126
|
+
.filter((row) => /claude-notification-plugin[\\/]+listener[\\/]+listener\.js/i.test(row?.CommandLine || ''))
|
|
127
|
+
.map((row) => Number(row?.ProcessId))
|
|
128
|
+
.filter((pid) => Number.isInteger(pid) && pid > 0);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const raw = execSync('ps -eo pid=,args=', {
|
|
132
|
+
encoding: 'utf-8',
|
|
133
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
134
|
+
});
|
|
135
|
+
const pids = [];
|
|
136
|
+
for (const line of raw.split('\n')) {
|
|
137
|
+
const match = line.trim().match(/^(\d+)\s+([\s\S]+)$/);
|
|
138
|
+
if (!match) {
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const pid = parseInt(match[1], 10);
|
|
143
|
+
const args = match[2];
|
|
144
|
+
if (/claude-notification-plugin[\\/]+listener[\\/]+listener\.js/i.test(args)) {
|
|
145
|
+
pids.push(pid);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return pids;
|
|
149
|
+
} catch {
|
|
150
|
+
return [];
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function stopListenerIfRunning () {
|
|
155
|
+
let stopped = false;
|
|
156
|
+
const processed = new Set();
|
|
157
|
+
|
|
158
|
+
const pidFromFile = readPid(pidFile);
|
|
159
|
+
if (pidFromFile) {
|
|
160
|
+
if (killProcessTree(pidFromFile)) {
|
|
161
|
+
stopped = true;
|
|
162
|
+
}
|
|
163
|
+
processed.add(pidFromFile);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
for (const pid of findListenerPids()) {
|
|
167
|
+
if (processed.has(pid)) {
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
if (killProcessTree(pid)) {
|
|
171
|
+
stopped = true;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
removeFileIfExists(pidFile);
|
|
176
|
+
return stopped;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Stop listener daemon if running
|
|
180
|
+
const listenerStopped = stopListenerIfRunning();
|
|
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
|
-
|
|
1
|
+
3299ff296778c37204e4b4e5832343865faf035a
|
package/listener/listener.js
CHANGED
|
@@ -195,7 +195,20 @@ function formatLabel (entry) {
|
|
|
195
195
|
async function startTask (workDir, task) {
|
|
196
196
|
const entry = queue.queues[workDir];
|
|
197
197
|
const label = formatLabel(entry);
|
|
198
|
-
const
|
|
198
|
+
const runningShort = `⏳ [${label}] Running...`;
|
|
199
|
+
const runningFull = `⏳ [${label}] Running: ${escapeHtml(task.text)}`;
|
|
200
|
+
let runningMsgId = null;
|
|
201
|
+
|
|
202
|
+
if (task.telegramMessageId) {
|
|
203
|
+
// In replies, the quoted user message already contains task text.
|
|
204
|
+
runningMsgId = await poller.sendMessage(runningShort, task.telegramMessageId);
|
|
205
|
+
if (!runningMsgId) {
|
|
206
|
+
runningMsgId = await poller.sendMessage(runningFull);
|
|
207
|
+
}
|
|
208
|
+
} else {
|
|
209
|
+
runningMsgId = await poller.sendMessage(runningFull);
|
|
210
|
+
}
|
|
211
|
+
|
|
199
212
|
task.runningMessageId = runningMsgId;
|
|
200
213
|
try {
|
|
201
214
|
const started = runner.run(workDir, task);
|
|
@@ -511,24 +524,26 @@ async function handleStop () {
|
|
|
511
524
|
}
|
|
512
525
|
|
|
513
526
|
function handleHelp () {
|
|
514
|
-
return
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
527
|
+
return `<b>📖 Commands:</b>
|
|
528
|
+
|
|
529
|
+
/status — status of all projects
|
|
530
|
+
/status @project — project status
|
|
531
|
+
/queue — all queues
|
|
532
|
+
/cancel [@project[/branch]] — cancel task
|
|
533
|
+
/drop @project N — remove task from queue
|
|
534
|
+
/clear @project[/branch] — clear queue
|
|
535
|
+
/projects — list projects
|
|
536
|
+
/worktrees @project — project worktrees
|
|
537
|
+
/worktree @project branch — create worktree
|
|
538
|
+
/rmworktree @project branch — remove worktree
|
|
539
|
+
/history — task history
|
|
540
|
+
/stop — stop 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`;
|
|
532
547
|
}
|
|
533
548
|
|
|
534
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.
|
|
4
|
+
"version": "1.0.106",
|
|
5
5
|
"description": "Claude Code task-completion notifications: Telegram, desktop notifications (Windows/macOS/Linux), sound, and voice",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"engines": {
|