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.
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +6 -2
- package/listener/listener.js +38 -16
- package/listener/telegram-poller.js +27 -1
- 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.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
|
|
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
|
|
package/listener/listener.js
CHANGED
|
@@ -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
|
-
//
|
|
106
|
-
|
|
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
|
-
|
|
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
|
-
|
|
124
|
+
body = `\n\n<pre>${escapeHtml(output)}</pre>`;
|
|
120
125
|
}
|
|
121
126
|
}
|
|
122
|
-
|
|
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
|
-
|
|
135
|
-
|
|
136
|
-
|
|
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
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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.
|
|
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": {
|