claude-notification-plugin 1.0.82 → 1.0.88
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 +13 -14
- package/README.md +276 -287
- package/bin/install.js +262 -16
- package/bin/uninstall.js +3 -2
- package/listener/LISTENER-DETAILED.md +3 -3
- package/listener/listener.js +1 -0
- package/package.json +60 -60
- package/bin/register-cli.js +0 -142
- package/commands/listener.md +0 -100
- package/commands/setup.md +0 -54
package/bin/install.js
CHANGED
|
@@ -4,17 +4,246 @@ import fs from 'fs';
|
|
|
4
4
|
import os from 'os';
|
|
5
5
|
import path from 'path';
|
|
6
6
|
import readline from 'readline';
|
|
7
|
+
import { execSync } from 'child_process';
|
|
8
|
+
import { fileURLToPath } from 'url';
|
|
9
|
+
|
|
10
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
11
|
+
const PACKAGE_ROOT = path.resolve(__dirname, '..');
|
|
7
12
|
|
|
8
13
|
const home = os.homedir();
|
|
9
14
|
const claudeDir = path.join(home, '.claude');
|
|
15
|
+
const pluginsDir = path.join(claudeDir, 'plugins');
|
|
10
16
|
const configPath = path.join(claudeDir, 'notifier.config.json');
|
|
11
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');
|
|
12
22
|
|
|
13
23
|
const HOOK_COMMAND = 'claude-notifier';
|
|
24
|
+
const MARKETPLACE_KEY = 'bazilio-plugins';
|
|
25
|
+
const PLUGIN_KEY = 'claude-notification-plugin@bazilio-plugins';
|
|
26
|
+
const MARKETPLACE_REPO = 'https://github.com/Bazilio-san/claude-plugins.git';
|
|
27
|
+
const MARKETPLACE_GITHUB = 'Bazilio-san/claude-plugins';
|
|
28
|
+
|
|
29
|
+
const CLI_BINS = {
|
|
30
|
+
'claude-notify-listener': 'bin/listener-cli.js',
|
|
31
|
+
'claude-notify-install': 'bin/install.js',
|
|
32
|
+
'claude-notify-uninstall': 'bin/uninstall.js',
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
// ──────────────────────────────────────
|
|
36
|
+
// Plugin registration
|
|
37
|
+
// ──────────────────────────────────────
|
|
38
|
+
|
|
39
|
+
function getVersion () {
|
|
40
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(PACKAGE_ROOT, 'package.json'), 'utf-8'));
|
|
41
|
+
return pkg.version;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function copyToCache (version) {
|
|
45
|
+
const cacheBase = path.join(pluginsDir, 'cache', MARKETPLACE_KEY, 'claude-notification-plugin');
|
|
46
|
+
const dest = path.join(cacheBase, version);
|
|
47
|
+
|
|
48
|
+
if (fs.existsSync(dest)) {
|
|
49
|
+
fs.rmSync(dest, { recursive: true });
|
|
50
|
+
}
|
|
51
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
52
|
+
|
|
53
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(PACKAGE_ROOT, 'package.json'), 'utf-8'));
|
|
54
|
+
const entries = [...(pkg.files || []), 'package.json', 'package-lock.json'];
|
|
55
|
+
|
|
56
|
+
for (const entry of entries) {
|
|
57
|
+
const src = path.join(PACKAGE_ROOT, entry);
|
|
58
|
+
if (!fs.existsSync(src)) {
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
const target = path.join(dest, entry);
|
|
62
|
+
const stat = fs.statSync(src);
|
|
63
|
+
if (stat.isDirectory()) {
|
|
64
|
+
fs.cpSync(src, target, { recursive: true });
|
|
65
|
+
} else {
|
|
66
|
+
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
67
|
+
fs.copyFileSync(src, target);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
execSync('npm install --production --ignore-scripts', {
|
|
73
|
+
cwd: dest,
|
|
74
|
+
stdio: 'pipe',
|
|
75
|
+
windowsHide: true,
|
|
76
|
+
});
|
|
77
|
+
} catch {
|
|
78
|
+
console.warn(' Warning: could not install dependencies in plugin cache.');
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return dest;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function updateInstalledPlugins (version, installPath) {
|
|
85
|
+
fs.mkdirSync(pluginsDir, { recursive: true });
|
|
86
|
+
|
|
87
|
+
let data = { version: 2, plugins: {} };
|
|
88
|
+
if (fs.existsSync(installedPluginsPath)) {
|
|
89
|
+
try {
|
|
90
|
+
data = JSON.parse(fs.readFileSync(installedPluginsPath, 'utf-8'));
|
|
91
|
+
} catch {
|
|
92
|
+
// ignore malformed file
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const now = new Date().toISOString();
|
|
97
|
+
const existing = data.plugins[PLUGIN_KEY]?.[0];
|
|
98
|
+
|
|
99
|
+
data.plugins[PLUGIN_KEY] = [{
|
|
100
|
+
scope: 'user',
|
|
101
|
+
installPath,
|
|
102
|
+
version,
|
|
103
|
+
installedAt: existing?.installedAt || now,
|
|
104
|
+
lastUpdated: now,
|
|
105
|
+
gitCommitSha: existing?.gitCommitSha || '',
|
|
106
|
+
}];
|
|
107
|
+
|
|
108
|
+
fs.writeFileSync(installedPluginsPath, JSON.stringify(data, null, 2));
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function updateKnownMarketplaces () {
|
|
112
|
+
let data = {};
|
|
113
|
+
if (fs.existsSync(knownMarketplacesPath)) {
|
|
114
|
+
try {
|
|
115
|
+
data = JSON.parse(fs.readFileSync(knownMarketplacesPath, 'utf-8'));
|
|
116
|
+
} catch {
|
|
117
|
+
// ignore malformed file
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const installLocation = path.join(marketplacesDir, MARKETPLACE_KEY);
|
|
122
|
+
|
|
123
|
+
data[MARKETPLACE_KEY] = {
|
|
124
|
+
...data[MARKETPLACE_KEY],
|
|
125
|
+
source: {
|
|
126
|
+
source: 'github',
|
|
127
|
+
repo: MARKETPLACE_GITHUB,
|
|
128
|
+
},
|
|
129
|
+
installLocation,
|
|
130
|
+
lastUpdated: data[MARKETPLACE_KEY]?.lastUpdated || new Date().toISOString(),
|
|
131
|
+
autoUpdate: true,
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
fs.writeFileSync(knownMarketplacesPath, JSON.stringify(data, null, 2));
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function cloneOrUpdateMarketplace () {
|
|
138
|
+
const dest = path.join(marketplacesDir, MARKETPLACE_KEY);
|
|
139
|
+
|
|
140
|
+
if (fs.existsSync(path.join(dest, '.git'))) {
|
|
141
|
+
try {
|
|
142
|
+
execSync('git pull --ff-only', { cwd: dest, stdio: 'pipe', windowsHide: true });
|
|
143
|
+
} catch {
|
|
144
|
+
// offline or conflict — not fatal
|
|
145
|
+
}
|
|
146
|
+
} else {
|
|
147
|
+
fs.mkdirSync(marketplacesDir, { recursive: true });
|
|
148
|
+
try {
|
|
149
|
+
execSync(`git clone "${MARKETPLACE_REPO}" "${dest}"`, {
|
|
150
|
+
stdio: 'pipe',
|
|
151
|
+
windowsHide: true,
|
|
152
|
+
});
|
|
153
|
+
} catch {
|
|
154
|
+
console.warn(' Warning: could not clone marketplace repo (offline?).');
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// ──────────────────────────────────────
|
|
160
|
+
// CLI wrapper registration
|
|
161
|
+
// ──────────────────────────────────────
|
|
162
|
+
|
|
163
|
+
function writeResolver () {
|
|
164
|
+
const content = `#!/usr/bin/env node
|
|
165
|
+
'use strict';
|
|
166
|
+
const fs = require('fs');
|
|
167
|
+
const path = require('path');
|
|
168
|
+
const { execFileSync } = require('child_process');
|
|
169
|
+
|
|
170
|
+
const pluginKey = 'claude-notification-plugin@bazilio-plugins';
|
|
171
|
+
const home = process.env.USERPROFILE || process.env.HOME;
|
|
172
|
+
const regPath = path.join(home, '.claude', 'plugins', 'installed_plugins.json');
|
|
173
|
+
|
|
174
|
+
let installPath;
|
|
175
|
+
try {
|
|
176
|
+
const reg = JSON.parse(fs.readFileSync(regPath, 'utf-8'));
|
|
177
|
+
const entries = reg.plugins[pluginKey];
|
|
178
|
+
if (entries && entries.length) installPath = entries[0].installPath;
|
|
179
|
+
} catch {}
|
|
180
|
+
|
|
181
|
+
if (!installPath) {
|
|
182
|
+
console.error('claude-notification-plugin is not installed.');
|
|
183
|
+
process.exit(1);
|
|
184
|
+
}
|
|
14
185
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
186
|
+
const target = path.join(installPath, process.argv[2]);
|
|
187
|
+
const args = process.argv.slice(3);
|
|
188
|
+
|
|
189
|
+
try {
|
|
190
|
+
execFileSync('node', [target, ...args], { stdio: 'inherit' });
|
|
191
|
+
} catch (e) {
|
|
192
|
+
process.exit(e.status || 1);
|
|
193
|
+
}
|
|
194
|
+
`;
|
|
195
|
+
fs.writeFileSync(RESOLVER_PATH, content, { mode: 0o755 });
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function findClaudeDir () {
|
|
199
|
+
try {
|
|
200
|
+
const cmd = process.platform === 'win32' ? 'where claude' : 'which claude';
|
|
201
|
+
const result = execSync(cmd, { encoding: 'utf-8', windowsHide: true }).trim();
|
|
202
|
+
const first = result.split('\n')[0].trim();
|
|
203
|
+
return path.dirname(first);
|
|
204
|
+
} catch {
|
|
205
|
+
return null;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function registerCli () {
|
|
210
|
+
const binDir = findClaudeDir();
|
|
211
|
+
if (!binDir) {
|
|
212
|
+
console.warn(' Warning: "claude" not found in PATH — CLI wrappers not registered.');
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
writeResolver();
|
|
217
|
+
|
|
218
|
+
const isWindows = process.platform === 'win32';
|
|
219
|
+
const resolverNative = RESOLVER_PATH.replace(/\//g, path.sep);
|
|
220
|
+
const created = [];
|
|
221
|
+
|
|
222
|
+
for (const [name, relPath] of Object.entries(CLI_BINS)) {
|
|
223
|
+
if (isWindows) {
|
|
224
|
+
const cmdFile = path.join(binDir, `${name}.cmd`);
|
|
225
|
+
const content = `@echo off\r\nnode "${resolverNative}" ${relPath} %*\r\n`;
|
|
226
|
+
fs.writeFileSync(cmdFile, content);
|
|
227
|
+
created.push(cmdFile);
|
|
228
|
+
} else {
|
|
229
|
+
const shFile = path.join(binDir, name);
|
|
230
|
+
const content = `#!/bin/sh\nexec node "${RESOLVER_PATH}" "${relPath}" "$@"\n`;
|
|
231
|
+
fs.writeFileSync(shFile, content, { mode: 0o755 });
|
|
232
|
+
created.push(shFile);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (created.length > 0) {
|
|
237
|
+
console.log(' CLI commands registered:');
|
|
238
|
+
for (const f of created) {
|
|
239
|
+
console.log(` ${f}`);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// ──────────────────────────────────────
|
|
245
|
+
// Helpers
|
|
246
|
+
// ──────────────────────────────────────
|
|
18
247
|
|
|
19
248
|
function ask (rl, question) {
|
|
20
249
|
return new Promise((resolve) => {
|
|
@@ -65,11 +294,34 @@ function addHook (settings, event) {
|
|
|
65
294
|
}
|
|
66
295
|
}
|
|
67
296
|
|
|
68
|
-
//
|
|
69
|
-
//
|
|
70
|
-
//
|
|
297
|
+
// ──────────────────────────────────────
|
|
298
|
+
// Main
|
|
299
|
+
// ──────────────────────────────────────
|
|
71
300
|
|
|
72
301
|
async function main () {
|
|
302
|
+
// 1. Register plugin in Claude Code
|
|
303
|
+
const version = getVersion();
|
|
304
|
+
|
|
305
|
+
console.log('');
|
|
306
|
+
console.log(`Registering plugin v${version} in Claude Code...`);
|
|
307
|
+
|
|
308
|
+
const installPath = copyToCache(version);
|
|
309
|
+
console.log(` Cached: ${installPath}`);
|
|
310
|
+
|
|
311
|
+
updateInstalledPlugins(version, installPath);
|
|
312
|
+
console.log(' Updated installed_plugins.json');
|
|
313
|
+
|
|
314
|
+
updateKnownMarketplaces();
|
|
315
|
+
console.log(' Updated known_marketplaces.json');
|
|
316
|
+
|
|
317
|
+
cloneOrUpdateMarketplace();
|
|
318
|
+
console.log(' Marketplace synced');
|
|
319
|
+
|
|
320
|
+
registerCli();
|
|
321
|
+
|
|
322
|
+
console.log(' Plugin registered.');
|
|
323
|
+
|
|
324
|
+
// 2. Interactive Telegram setup
|
|
73
325
|
const rl = readline.createInterface({
|
|
74
326
|
input: process.stdin,
|
|
75
327
|
output: process.stdout,
|
|
@@ -80,7 +332,6 @@ async function main () {
|
|
|
80
332
|
console.log('==================================');
|
|
81
333
|
console.log('');
|
|
82
334
|
|
|
83
|
-
// Load existing config
|
|
84
335
|
let existing = {};
|
|
85
336
|
if (fs.existsSync(configPath)) {
|
|
86
337
|
try {
|
|
@@ -93,7 +344,6 @@ async function main () {
|
|
|
93
344
|
const existingToken = existing.telegram?.token || '';
|
|
94
345
|
const existingChatId = existing.telegram?.chatId || '';
|
|
95
346
|
|
|
96
|
-
// Telegram setup
|
|
97
347
|
let token = existingToken;
|
|
98
348
|
let chatId = existingChatId;
|
|
99
349
|
|
|
@@ -103,7 +353,7 @@ async function main () {
|
|
|
103
353
|
const reuse = await ask(rl, 'Keep existing token? (Y/n): ');
|
|
104
354
|
if (reuse.toLowerCase() === 'n') {
|
|
105
355
|
token = await ask(rl, 'New Bot Token: ');
|
|
106
|
-
chatId = '';
|
|
356
|
+
chatId = '';
|
|
107
357
|
}
|
|
108
358
|
} else {
|
|
109
359
|
const useTelegram = await ask(rl, 'Configure Telegram? (y/N): ');
|
|
@@ -112,7 +362,6 @@ async function main () {
|
|
|
112
362
|
}
|
|
113
363
|
}
|
|
114
364
|
|
|
115
|
-
// Fetch chatId if we have a token but no chatId
|
|
116
365
|
if (token && !chatId) {
|
|
117
366
|
console.log('');
|
|
118
367
|
console.log('Send any message to your bot in Telegram, then press Enter.');
|
|
@@ -133,7 +382,7 @@ async function main () {
|
|
|
133
382
|
|
|
134
383
|
rl.close();
|
|
135
384
|
|
|
136
|
-
//
|
|
385
|
+
// 3. Write config
|
|
137
386
|
fs.mkdirSync(claudeDir, { recursive: true });
|
|
138
387
|
|
|
139
388
|
const platform = process.platform;
|
|
@@ -178,16 +427,13 @@ async function main () {
|
|
|
178
427
|
},
|
|
179
428
|
};
|
|
180
429
|
|
|
181
|
-
// Merge defaults with existing config (user values take priority)
|
|
182
430
|
const config = { ...defaults, ...existing };
|
|
183
|
-
// Deep-merge nested objects
|
|
184
431
|
for (const key of Object.keys(defaults)) {
|
|
185
432
|
if (defaults[key] && typeof defaults[key] === 'object' && !Array.isArray(defaults[key])) {
|
|
186
433
|
config[key] = { ...defaults[key], ...(existing[key] || {}) };
|
|
187
434
|
}
|
|
188
435
|
}
|
|
189
436
|
|
|
190
|
-
// Apply Telegram credentials from this run (if provided)
|
|
191
437
|
if (token) {
|
|
192
438
|
config.telegram.token = token;
|
|
193
439
|
}
|
|
@@ -197,7 +443,7 @@ async function main () {
|
|
|
197
443
|
|
|
198
444
|
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
199
445
|
|
|
200
|
-
//
|
|
446
|
+
// 4. Register hooks
|
|
201
447
|
let settings = {};
|
|
202
448
|
|
|
203
449
|
if (fs.existsSync(settingsPath)) {
|
|
@@ -216,7 +462,7 @@ async function main () {
|
|
|
216
462
|
|
|
217
463
|
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
|
|
218
464
|
|
|
219
|
-
//
|
|
465
|
+
// 5. Summary
|
|
220
466
|
console.log('');
|
|
221
467
|
console.log('Installed!');
|
|
222
468
|
console.log('');
|
package/bin/uninstall.js
CHANGED
|
@@ -40,8 +40,9 @@ if (fs.existsSync(settingsPath)) {
|
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
// Remove config and
|
|
44
|
-
|
|
43
|
+
// Remove config, state, and resolver files
|
|
44
|
+
const resolverPath = path.join(claudeDir, 'claude-notify-resolve.js');
|
|
45
|
+
for (const file of [configPath, statePath, resolverPath]) {
|
|
45
46
|
if (fs.existsSync(file)) {
|
|
46
47
|
fs.unlinkSync(file);
|
|
47
48
|
}
|
|
@@ -397,7 +397,7 @@ If the worktree doesn't exist, it will be created automatically.
|
|
|
397
397
|
5. When Claude finishes → sends the result to Telegram
|
|
398
398
|
6. If there's a next task in the queue → starts it
|
|
399
399
|
|
|
400
|
-
---
|
|
400
|
+
---
|
|
401
401
|
|
|
402
402
|
## Projects and worktrees
|
|
403
403
|
|
|
@@ -847,7 +847,7 @@ exec(`claude -p "${userText}"`)
|
|
|
847
847
|
|
|
848
848
|
- 10 tasks in the queue per workDir (spam protection)
|
|
849
849
|
- 50 tasks total (overload protection)
|
|
850
|
-
-
|
|
850
|
+
- 30-minute timeout per task (hang protection)
|
|
851
851
|
|
|
852
852
|
---
|
|
853
853
|
|
|
@@ -863,7 +863,7 @@ claude-notify-listener status
|
|
|
863
863
|
Check:
|
|
864
864
|
|
|
865
865
|
1. Does the config exist? `cat ~/.claude/notifier.config.json`
|
|
866
|
-
2. Are `
|
|
866
|
+
2. Are `telegram.token` and `telegram.chatId` present?
|
|
867
867
|
3. Is there a `listener.projects` section?
|
|
868
868
|
4. Logs: `claude-notify-listener logs`
|
|
869
869
|
|
package/listener/listener.js
CHANGED
package/package.json
CHANGED
|
@@ -1,60 +1,60 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "claude-notification-plugin",
|
|
3
|
-
"productName": "claude-notification-plugin",
|
|
4
|
-
"version": "1.0.
|
|
5
|
-
"description": "Claude Code task-completion notifications: Telegram, desktop notifications (Windows/macOS/Linux), sound, and voice",
|
|
6
|
-
"type": "module",
|
|
7
|
-
"engines": {
|
|
8
|
-
"node": ">=18.0.0"
|
|
9
|
-
},
|
|
10
|
-
"files": [
|
|
11
|
-
".claude-plugin/",
|
|
12
|
-
"bin/",
|
|
13
|
-
"
|
|
14
|
-
"
|
|
15
|
-
"
|
|
16
|
-
"
|
|
17
|
-
"
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
"claude-notify-
|
|
22
|
-
"claude-
|
|
23
|
-
"claude-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
"lint": "eslint .",
|
|
29
|
-
"lint:fix": "eslint --fix ."
|
|
30
|
-
},
|
|
31
|
-
"keywords": [
|
|
32
|
-
"claude",
|
|
33
|
-
"claude-code",
|
|
34
|
-
"notifications",
|
|
35
|
-
"telegram",
|
|
36
|
-
"hooks",
|
|
37
|
-
"macos",
|
|
38
|
-
"linux",
|
|
39
|
-
"cross-platform"
|
|
40
|
-
],
|
|
41
|
-
"author": {
|
|
42
|
-
"name": "Viacheslav Makarov",
|
|
43
|
-
"email": "npmjs@bazilio.ru"
|
|
44
|
-
},
|
|
45
|
-
"license": "MIT",
|
|
46
|
-
"repository": {
|
|
47
|
-
"type": "git",
|
|
48
|
-
"url": "git+https://github.com/Bazilio-san/claude-notification-plugin.git"
|
|
49
|
-
},
|
|
50
|
-
"homepage": "https://github.com/Bazilio-san/claude-notification-plugin#readme",
|
|
51
|
-
"publishConfig": {
|
|
52
|
-
"access": "public"
|
|
53
|
-
},
|
|
54
|
-
"dependencies": {
|
|
55
|
-
"node-notifier": "^10.0.1"
|
|
56
|
-
},
|
|
57
|
-
"devDependencies": {
|
|
58
|
-
"eslint-plugin-import": "^2.31.0"
|
|
59
|
-
}
|
|
60
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "claude-notification-plugin",
|
|
3
|
+
"productName": "claude-notification-plugin",
|
|
4
|
+
"version": "1.0.88",
|
|
5
|
+
"description": "Claude Code task-completion notifications: Telegram, desktop notifications (Windows/macOS/Linux), sound, and voice",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"engines": {
|
|
8
|
+
"node": ">=18.0.0"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
".claude-plugin/",
|
|
12
|
+
"bin/",
|
|
13
|
+
"hooks/",
|
|
14
|
+
"listener/",
|
|
15
|
+
"notifier/",
|
|
16
|
+
"README.md",
|
|
17
|
+
"LICENSE"
|
|
18
|
+
],
|
|
19
|
+
"bin": {
|
|
20
|
+
"claude-notify-install": "bin/install.js",
|
|
21
|
+
"claude-notify-uninstall": "bin/uninstall.js",
|
|
22
|
+
"claude-notifier": "notifier/notifier.js",
|
|
23
|
+
"claude-notify-listener": "bin/listener-cli.js"
|
|
24
|
+
},
|
|
25
|
+
"scripts": {
|
|
26
|
+
"preuninstall": "node bin/uninstall.js",
|
|
27
|
+
"postinstall": "node bin/install.js",
|
|
28
|
+
"lint": "eslint .",
|
|
29
|
+
"lint:fix": "eslint --fix ."
|
|
30
|
+
},
|
|
31
|
+
"keywords": [
|
|
32
|
+
"claude",
|
|
33
|
+
"claude-code",
|
|
34
|
+
"notifications",
|
|
35
|
+
"telegram",
|
|
36
|
+
"hooks",
|
|
37
|
+
"macos",
|
|
38
|
+
"linux",
|
|
39
|
+
"cross-platform"
|
|
40
|
+
],
|
|
41
|
+
"author": {
|
|
42
|
+
"name": "Viacheslav Makarov",
|
|
43
|
+
"email": "npmjs@bazilio.ru"
|
|
44
|
+
},
|
|
45
|
+
"license": "MIT",
|
|
46
|
+
"repository": {
|
|
47
|
+
"type": "git",
|
|
48
|
+
"url": "git+https://github.com/Bazilio-san/claude-notification-plugin.git"
|
|
49
|
+
},
|
|
50
|
+
"homepage": "https://github.com/Bazilio-san/claude-notification-plugin#readme",
|
|
51
|
+
"publishConfig": {
|
|
52
|
+
"access": "public"
|
|
53
|
+
},
|
|
54
|
+
"dependencies": {
|
|
55
|
+
"node-notifier": "^10.0.1"
|
|
56
|
+
},
|
|
57
|
+
"devDependencies": {
|
|
58
|
+
"eslint-plugin-import": "^2.31.0"
|
|
59
|
+
}
|
|
60
|
+
}
|
package/bin/register-cli.js
DELETED
|
@@ -1,142 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import fs from 'fs';
|
|
4
|
-
import path from 'path';
|
|
5
|
-
import { execSync } from 'child_process';
|
|
6
|
-
import { fileURLToPath } from 'url';
|
|
7
|
-
|
|
8
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
9
|
-
const __dirname = path.dirname(__filename);
|
|
10
|
-
|
|
11
|
-
const PLUGIN_ROOT = path.resolve(__dirname, '..');
|
|
12
|
-
|
|
13
|
-
const BINS = {
|
|
14
|
-
'claude-notify-listener': 'bin/listener-cli.js',
|
|
15
|
-
'claude-notify-install': 'bin/install.js',
|
|
16
|
-
'claude-notify-uninstall': 'bin/uninstall.js',
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
function findClaudeDir () {
|
|
20
|
-
try {
|
|
21
|
-
const cmd = process.platform === 'win32' ? 'where claude' : 'which claude';
|
|
22
|
-
const result = execSync(cmd, { encoding: 'utf-8', windowsHide: true }).trim();
|
|
23
|
-
// 'where' on Windows may return multiple lines
|
|
24
|
-
const first = result.split('\n')[0].trim();
|
|
25
|
-
return path.dirname(first);
|
|
26
|
-
} catch {
|
|
27
|
-
return null;
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function register () {
|
|
32
|
-
const claudeDir = findClaudeDir();
|
|
33
|
-
if (!claudeDir) {
|
|
34
|
-
console.error('Could not find "claude" in PATH.');
|
|
35
|
-
console.error('Make sure Claude Code CLI is installed and available.');
|
|
36
|
-
process.exit(1);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const isWindows = process.platform === 'win32';
|
|
40
|
-
const created = [];
|
|
41
|
-
|
|
42
|
-
for (const [name, relPath] of Object.entries(BINS)) {
|
|
43
|
-
const scriptPath = path.join(PLUGIN_ROOT, relPath);
|
|
44
|
-
|
|
45
|
-
if (!fs.existsSync(scriptPath)) {
|
|
46
|
-
console.warn(` Skip ${name}: ${scriptPath} not found`);
|
|
47
|
-
continue;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
if (isWindows) {
|
|
51
|
-
const cmdFile = path.join(claudeDir, `${name}.cmd`);
|
|
52
|
-
const content = `@echo off\r\nnode "${scriptPath}" %*\r\n`;
|
|
53
|
-
fs.writeFileSync(cmdFile, content);
|
|
54
|
-
created.push(cmdFile);
|
|
55
|
-
} else {
|
|
56
|
-
const shFile = path.join(claudeDir, name);
|
|
57
|
-
const content = `#!/bin/sh\nexec node "${scriptPath}" "$@"\n`;
|
|
58
|
-
fs.writeFileSync(shFile, content, { mode: 0o755 });
|
|
59
|
-
created.push(shFile);
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
if (created.length > 0) {
|
|
64
|
-
console.log('Registered CLI commands:');
|
|
65
|
-
for (const f of created) {
|
|
66
|
-
console.log(` ${f}`);
|
|
67
|
-
}
|
|
68
|
-
} else {
|
|
69
|
-
console.log('No commands were registered.');
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
function list () {
|
|
74
|
-
const claudeDir = findClaudeDir();
|
|
75
|
-
if (!claudeDir) {
|
|
76
|
-
console.log('Claude CLI not found in PATH.');
|
|
77
|
-
return;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
const isWindows = process.platform === 'win32';
|
|
81
|
-
const ext = isWindows ? '.cmd' : '';
|
|
82
|
-
let found = 0;
|
|
83
|
-
|
|
84
|
-
for (const name of Object.keys(BINS)) {
|
|
85
|
-
const filePath = path.join(claudeDir, `${name}${ext}`);
|
|
86
|
-
const exists = fs.existsSync(filePath);
|
|
87
|
-
console.log(` ${exists ? '+' : '-'} ${name}${exists ? ` (${filePath})` : ''}`);
|
|
88
|
-
if (exists) {
|
|
89
|
-
found++;
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
console.log('');
|
|
94
|
-
console.log(`${found}/${Object.keys(BINS).length} commands registered in ${claudeDir}`);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
function unregister () {
|
|
98
|
-
const claudeDir = findClaudeDir();
|
|
99
|
-
if (!claudeDir) {
|
|
100
|
-
console.log('Claude CLI not found in PATH — nothing to unregister.');
|
|
101
|
-
return;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
const isWindows = process.platform === 'win32';
|
|
105
|
-
const removed = [];
|
|
106
|
-
|
|
107
|
-
for (const name of Object.keys(BINS)) {
|
|
108
|
-
const ext = isWindows ? '.cmd' : '';
|
|
109
|
-
const filePath = path.join(claudeDir, `${name}${ext}`);
|
|
110
|
-
if (fs.existsSync(filePath)) {
|
|
111
|
-
fs.unlinkSync(filePath);
|
|
112
|
-
removed.push(filePath);
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
if (removed.length > 0) {
|
|
117
|
-
console.log('Removed CLI commands:');
|
|
118
|
-
for (const f of removed) {
|
|
119
|
-
console.log(` ${f}`);
|
|
120
|
-
}
|
|
121
|
-
} else {
|
|
122
|
-
console.log('No CLI commands found to remove.');
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
const action = process.argv[2];
|
|
127
|
-
|
|
128
|
-
if (action === 'unregister' || action === 'remove') {
|
|
129
|
-
unregister();
|
|
130
|
-
} else if (action === 'list' || action === 'status') {
|
|
131
|
-
list();
|
|
132
|
-
} else if (action === 'register' || !action) {
|
|
133
|
-
register();
|
|
134
|
-
} else {
|
|
135
|
-
console.log('Usage: claude-notify-register <register|unregister|list>');
|
|
136
|
-
console.log('');
|
|
137
|
-
console.log('Commands:');
|
|
138
|
-
console.log(' register Register CLI commands next to claude binary (default)');
|
|
139
|
-
console.log(' unregister Remove registered CLI commands');
|
|
140
|
-
console.log(' list Show registration status of CLI commands');
|
|
141
|
-
process.exit(1);
|
|
142
|
-
}
|