claude-notification-plugin 1.0.80 → 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/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
- // HELPERS
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
- // MAIN
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 = ''; // reset chatId when token changes
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
- // Create / update config
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
- // Patch settings.json
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
- // Output
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 state files
44
- for (const file of [configPath, statePath]) {
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
- ---RFr
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
- - 10-minute timeout per task (hang protection)
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 `telegramToken` and `telegramChatId` present?
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
 
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env node
2
+ // noinspection UnnecessaryLocalVariableJS
2
3
 
3
4
  import fs from 'fs';
4
5
  import os from 'os';
@@ -102,24 +103,35 @@ runner.on('complete', async (workDir, task, output) => {
102
103
  const entry = queue.queues[workDir];
103
104
  const label = formatLabel(entry);
104
105
 
105
- // Send result
106
- let text = `✅ [${label}] Done: ${escapeHtml(task.text)}`;
106
+ // Delete the "Running" message
107
+ await poller.deleteMessage(task.runningMessageId);
108
+
109
+ // Build result: try replying to user's original message without duplicating the task text.
110
+ // If reply fails (user deleted their message), resend with task text included.
111
+ const headerShort = `✅ [${label}] Done:`;
112
+ const headerFull = `✅ [${label}] Done: ${escapeHtml(task.text)}`;
113
+ let body = '';
107
114
  if (output) {
108
115
  if (output.length > 20000) {
109
116
  const head = output.slice(0, 2000);
110
117
  const tail = output.slice(-2000);
111
- text += `\n\n<pre>${escapeHtml(head)}\n\n... (${output.length} chars) ...\n\n${escapeHtml(tail)}</pre>`;
112
- // Also send as file
118
+ body = `\n\n<pre>${escapeHtml(head)}\n\n... (${output.length} chars) ...\n\n${escapeHtml(tail)}</pre>`;
113
119
  await poller.sendDocument(
114
120
  Buffer.from(output, 'utf-8'),
115
121
  `result_${task.id}.txt`,
116
122
  `Full output for: ${task.text.slice(0, 100)}`
117
123
  );
118
124
  } else {
119
- text += `\n\n<pre>${escapeHtml(output)}</pre>`;
125
+ body = `\n\n<pre>${escapeHtml(output)}</pre>`;
120
126
  }
121
127
  }
122
- await poller.sendMessage(text, task.telegramMessageId);
128
+
129
+ // Try reply to original message (short header, task text visible in quote)
130
+ const sentId = await poller.sendMessage(headerShort + body, task.telegramMessageId);
131
+ if (!sentId && task.telegramMessageId) {
132
+ // Reply failed — original message was deleted, send without reply but with full task text
133
+ await poller.sendMessage(headerFull + body);
134
+ }
123
135
 
124
136
  // Process next in queue
125
137
  const next = queue.onTaskComplete(workDir, output);
@@ -131,10 +143,14 @@ runner.on('complete', async (workDir, task, output) => {
131
143
  runner.on('error', async (workDir, task, errorMsg) => {
132
144
  const entry = queue.queues[workDir];
133
145
  const label = formatLabel(entry);
134
- await poller.sendMessage(
135
- `❌ [${label}] Error: ${escapeHtml(task.text)}\n\n<pre>${escapeHtml(errorMsg)}</pre>`,
136
- task.telegramMessageId,
137
- );
146
+
147
+ await poller.deleteMessage(task.runningMessageId);
148
+
149
+ const body = `\n\n<pre>${escapeHtml(errorMsg)}</pre>`;
150
+ const sentId = await poller.sendMessage(`❌ [${label}] Error:${body}`, task.telegramMessageId);
151
+ if (!sentId && task.telegramMessageId) {
152
+ await poller.sendMessage(`❌ [${label}] Error: ${escapeHtml(task.text)}${body}`);
153
+ }
138
154
 
139
155
  const next = queue.onTaskComplete(workDir, `ERROR: ${errorMsg}`);
140
156
  if (next) {
@@ -145,10 +161,16 @@ runner.on('error', async (workDir, task, errorMsg) => {
145
161
  runner.on('timeout', async (workDir, task) => {
146
162
  const entry = queue.queues[workDir];
147
163
  const label = formatLabel(entry);
148
- await poller.sendMessage(
149
- `⏰ [${label}] Task forcefully stopped — timeout exceeded (${Math.round(taskTimeout / 60000)} min): ${escapeHtml(task.text)}`,
150
- task.telegramMessageId,
151
- );
164
+ const timeoutMin = Math.round(taskTimeout / 60000);
165
+
166
+ await poller.deleteMessage(task.runningMessageId);
167
+
168
+ const headerShort = `⏰ [${label}] Task forcefully stopped — timeout exceeded (${timeoutMin} min)`;
169
+ const headerFull = `${headerShort}: ${escapeHtml(task.text)}`;
170
+ const sentId = await poller.sendMessage(headerShort, task.telegramMessageId);
171
+ if (!sentId && task.telegramMessageId) {
172
+ await poller.sendMessage(headerFull);
173
+ }
152
174
 
153
175
  const next = queue.onTaskComplete(workDir, 'TIMEOUT');
154
176
  if (next) {
@@ -170,10 +192,11 @@ function formatLabel (entry) {
170
192
  return `@${entry.project}`;
171
193
  }
172
194
 
173
- function startTask (workDir, task) {
195
+ async function startTask (workDir, task) {
174
196
  const entry = queue.queues[workDir];
175
197
  const label = formatLabel(entry);
176
- poller.sendMessage(`⏳ [${label}] Running: ${escapeHtml(task.text)}`, task.telegramMessageId);
198
+ const runningMsgId = await poller.sendMessage(`⏳ [${label}] Running: ${escapeHtml(task.text)}`, task.telegramMessageId);
199
+ task.runningMessageId = runningMsgId;
177
200
  try {
178
201
  const started = runner.run(workDir, task);
179
202
  queue.markStarted(workDir, started.pid);
@@ -50,6 +50,7 @@ export class TelegramPoller {
50
50
 
51
51
  async sendMessage (text, replyToMessageId) {
52
52
  const chunks = splitMessage(text);
53
+ let firstMessageId = null;
53
54
  for (const chunk of chunks) {
54
55
  try {
55
56
  const body = {
@@ -68,7 +69,7 @@ export class TelegramPoller {
68
69
  const data = await res.json();
69
70
  if (!data.ok) {
70
71
  // Retry without HTML parse mode
71
- await fetch(`${this.baseUrl}/sendMessage`, {
72
+ const res2 = await fetch(`${this.baseUrl}/sendMessage`, {
72
73
  method: 'POST',
73
74
  headers: { 'Content-Type': 'application/json' },
74
75
  body: JSON.stringify({
@@ -77,11 +78,36 @@ export class TelegramPoller {
77
78
  ...(replyToMessageId ? { reply_to_message_id: replyToMessageId } : {}),
78
79
  }),
79
80
  });
81
+ const data2 = await res2.json();
82
+ if (data2.ok && !firstMessageId) {
83
+ firstMessageId = data2.result.message_id;
84
+ }
85
+ } else if (!firstMessageId) {
86
+ firstMessageId = data.result.message_id;
80
87
  }
81
88
  } catch (err) {
82
89
  this.logger.error(`sendMessage error: ${err.message}`);
83
90
  }
84
91
  }
92
+ return firstMessageId;
93
+ }
94
+
95
+ async deleteMessage (messageId) {
96
+ if (!messageId) {
97
+ return;
98
+ }
99
+ try {
100
+ await fetch(`${this.baseUrl}/deleteMessage`, {
101
+ method: 'POST',
102
+ headers: { 'Content-Type': 'application/json' },
103
+ body: JSON.stringify({
104
+ chat_id: this.chatId,
105
+ message_id: messageId,
106
+ }),
107
+ });
108
+ } catch (err) {
109
+ this.logger.error(`deleteMessage error: ${err.message}`);
110
+ }
85
111
  }
86
112
 
87
113
  async sendDocument (buffer, filename, caption) {
package/package.json CHANGED
@@ -1,60 +1,60 @@
1
- {
2
- "name": "claude-notification-plugin",
3
- "productName": "claude-notification-plugin",
4
- "version": "1.0.80",
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
- "commands/",
14
- "hooks/",
15
- "listener/",
16
- "notifier/",
17
- "README.md",
18
- "LICENSE"
19
- ],
20
- "bin": {
21
- "claude-notify-install": "bin/install.js",
22
- "claude-notify-uninstall": "bin/uninstall.js",
23
- "claude-notifier": "notifier/notifier.js",
24
- "claude-notify-listener": "bin/listener-cli.js",
25
- "claude-notify-register": "bin/register-cli.js"
26
- },
27
- "scripts": {
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
+ }