claude-notification-plugin 1.0.80 → 1.0.82

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.80",
3
+ "version": "1.0.82",
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/README.md CHANGED
@@ -20,6 +20,8 @@ Cross-platform notifications for Claude Code task completion. Sends alerts to Te
20
20
 
21
21
  Auto-updates, seamless integration with the plugin ecosystem.
22
22
 
23
+ These commands are run in the Claude Code terminal:
24
+
23
25
  ```shell
24
26
  /plugin marketplace add Bazilio-san/claude-plugins
25
27
  /plugin install claude-notification-plugin@bazilio-plugins
@@ -239,13 +241,15 @@ Alternative for Chat ID: add **@userinfobot** to a chat and it will reply with t
239
241
 
240
242
  Your remote control for Claude: send a message in Telegram, and the task starts running on your PC. See **[LISTENER.md](LISTENER.md)** for the full guide.
241
243
 
242
- When installed as a plugin, you can manage the listener daemon directly from Claude Code with the `/listener` slash command:
244
+ When installed as a plugin, you can manage the listener daemon directly from Claude Code with the `/listener` slash command.
245
+ These commands are run in the Claude Code terminal:
243
246
  `/claude-notification-plugin:listener start | stop | status | logs | restart`
244
247
 
245
248
  ## CLI Commands Registration
246
249
 
247
250
  When installed as a **plugin**, CLI commands (`claude-notify-listener`, etc.) are not in PATH.
248
- The `/claude-notification-plugin:setup` command registers them automatically by placing wrapper scripts next to the `claude` binary.
251
+ The `/claude-notification-plugin:setup` command registers them automatically by
252
+ placing wrapper scripts next to the `claude` binary.
249
253
 
250
254
  To manage manually:
251
255
 
@@ -102,24 +102,35 @@ runner.on('complete', async (workDir, task, output) => {
102
102
  const entry = queue.queues[workDir];
103
103
  const label = formatLabel(entry);
104
104
 
105
- // Send result
106
- let text = `✅ [${label}] Done: ${escapeHtml(task.text)}`;
105
+ // Delete the "Running" message
106
+ await poller.deleteMessage(task.runningMessageId);
107
+
108
+ // Build result: try replying to user's original message without duplicating the task text.
109
+ // If reply fails (user deleted their message), resend with task text included.
110
+ const headerShort = `✅ [${label}] Done:`;
111
+ const headerFull = `✅ [${label}] Done: ${escapeHtml(task.text)}`;
112
+ let body = '';
107
113
  if (output) {
108
114
  if (output.length > 20000) {
109
115
  const head = output.slice(0, 2000);
110
116
  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
117
+ body = `\n\n<pre>${escapeHtml(head)}\n\n... (${output.length} chars) ...\n\n${escapeHtml(tail)}</pre>`;
113
118
  await poller.sendDocument(
114
119
  Buffer.from(output, 'utf-8'),
115
120
  `result_${task.id}.txt`,
116
121
  `Full output for: ${task.text.slice(0, 100)}`
117
122
  );
118
123
  } else {
119
- text += `\n\n<pre>${escapeHtml(output)}</pre>`;
124
+ body = `\n\n<pre>${escapeHtml(output)}</pre>`;
120
125
  }
121
126
  }
122
- await poller.sendMessage(text, task.telegramMessageId);
127
+
128
+ // Try reply to original message (short header, task text visible in quote)
129
+ const sentId = await poller.sendMessage(headerShort + body, task.telegramMessageId);
130
+ if (!sentId && task.telegramMessageId) {
131
+ // Reply failed — original message was deleted, send without reply but with full task text
132
+ await poller.sendMessage(headerFull + body);
133
+ }
123
134
 
124
135
  // Process next in queue
125
136
  const next = queue.onTaskComplete(workDir, output);
@@ -131,10 +142,14 @@ runner.on('complete', async (workDir, task, output) => {
131
142
  runner.on('error', async (workDir, task, errorMsg) => {
132
143
  const entry = queue.queues[workDir];
133
144
  const label = formatLabel(entry);
134
- await poller.sendMessage(
135
- `❌ [${label}] Error: ${escapeHtml(task.text)}\n\n<pre>${escapeHtml(errorMsg)}</pre>`,
136
- task.telegramMessageId,
137
- );
145
+
146
+ await poller.deleteMessage(task.runningMessageId);
147
+
148
+ const body = `\n\n<pre>${escapeHtml(errorMsg)}</pre>`;
149
+ const sentId = await poller.sendMessage(`❌ [${label}] Error:${body}`, task.telegramMessageId);
150
+ if (!sentId && task.telegramMessageId) {
151
+ await poller.sendMessage(`❌ [${label}] Error: ${escapeHtml(task.text)}${body}`);
152
+ }
138
153
 
139
154
  const next = queue.onTaskComplete(workDir, `ERROR: ${errorMsg}`);
140
155
  if (next) {
@@ -145,10 +160,16 @@ runner.on('error', async (workDir, task, errorMsg) => {
145
160
  runner.on('timeout', async (workDir, task) => {
146
161
  const entry = queue.queues[workDir];
147
162
  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
- );
163
+ const timeoutMin = Math.round(taskTimeout / 60000);
164
+
165
+ await poller.deleteMessage(task.runningMessageId);
166
+
167
+ const headerShort = `⏰ [${label}] Task forcefully stopped — timeout exceeded (${timeoutMin} min)`;
168
+ const headerFull = `${headerShort}: ${escapeHtml(task.text)}`;
169
+ const sentId = await poller.sendMessage(headerShort, task.telegramMessageId);
170
+ if (!sentId && task.telegramMessageId) {
171
+ await poller.sendMessage(headerFull);
172
+ }
152
173
 
153
174
  const next = queue.onTaskComplete(workDir, 'TIMEOUT');
154
175
  if (next) {
@@ -170,10 +191,11 @@ function formatLabel (entry) {
170
191
  return `@${entry.project}`;
171
192
  }
172
193
 
173
- function startTask (workDir, task) {
194
+ async function startTask (workDir, task) {
174
195
  const entry = queue.queues[workDir];
175
196
  const label = formatLabel(entry);
176
- poller.sendMessage(`⏳ [${label}] Running: ${escapeHtml(task.text)}`, task.telegramMessageId);
197
+ const runningMsgId = await poller.sendMessage(`⏳ [${label}] Running: ${escapeHtml(task.text)}`, task.telegramMessageId);
198
+ task.runningMessageId = runningMsgId;
177
199
  try {
178
200
  const started = runner.run(workDir, task);
179
201
  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,7 +1,7 @@
1
1
  {
2
2
  "name": "claude-notification-plugin",
3
3
  "productName": "claude-notification-plugin",
4
- "version": "1.0.80",
4
+ "version": "1.0.82",
5
5
  "description": "Claude Code task-completion notifications: Telegram, desktop notifications (Windows/macOS/Linux), sound, and voice",
6
6
  "type": "module",
7
7
  "engines": {