claude-notification-plugin 1.1.39 โ 1.1.41
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 +2 -1
- package/commit-sha +1 -1
- package/listener/listener.js +65 -3
- package/listener/pty-runner.js +5 -0
- package/listener/telegram-poller.js +40 -3
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-notification-plugin",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.41",
|
|
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
|
@@ -251,7 +251,8 @@ All commands start with `/` and execute instantly (not queued).
|
|
|
251
251
|
| `/pty [/project[/branch]]` | PTY session diagnostics (state, buffer, output) |
|
|
252
252
|
| `/history` | Recent task history |
|
|
253
253
|
| `/stop` | Stop the listener |
|
|
254
|
-
| `/
|
|
254
|
+
| `/menu` | Show inline button menu |
|
|
255
|
+
| `/help` | Show help with inline buttons |
|
|
255
256
|
|
|
256
257
|
### Listener configuration
|
|
257
258
|
|
package/commit-sha
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
e6d8c5ca437e900daf65216e3742d35354a15ba6
|
package/listener/listener.js
CHANGED
|
@@ -418,6 +418,8 @@ async function handleCommand (cmd, args) {
|
|
|
418
418
|
return handleStop();
|
|
419
419
|
case '/help':
|
|
420
420
|
return handleHelp();
|
|
421
|
+
case '/menu':
|
|
422
|
+
return handleMenu();
|
|
421
423
|
default:
|
|
422
424
|
return `Unknown command: ${cmd}`;
|
|
423
425
|
}
|
|
@@ -432,8 +434,12 @@ function handleStatus (args) {
|
|
|
432
434
|
return `๐ Project "${target.project}": no active queues`;
|
|
433
435
|
}
|
|
434
436
|
let text = `๐ Project "<b>${escapeHtml(target.project)}</b>":\n`;
|
|
437
|
+
const buttons = [];
|
|
435
438
|
for (const s of statuses) {
|
|
436
439
|
const branchLabel = s.branch || 'main';
|
|
440
|
+
const label = s.branch && s.branch !== 'main' && s.branch !== 'master'
|
|
441
|
+
? `/${target.project}/${s.branch}`
|
|
442
|
+
: `/${target.project}`;
|
|
437
443
|
if (s.active) {
|
|
438
444
|
const elapsed = s.active.startedAt
|
|
439
445
|
? formatDuration(Date.now() - new Date(s.active.startedAt).getTime())
|
|
@@ -441,10 +447,20 @@ function handleStatus (args) {
|
|
|
441
447
|
text += `\n<b>${escapeHtml(branchLabel)}</b>:\n`;
|
|
442
448
|
text += ` โถ ${escapeHtml(s.active.text)} (${elapsed})\n`;
|
|
443
449
|
text += ` Queue: ${s.queueLength} tasks\n`;
|
|
450
|
+
buttons.push([
|
|
451
|
+
{ text: `๐ Cancel ${label}`, callback_data: `/cancel ${label}` },
|
|
452
|
+
{ text: `๐งน Clear ${label}`, callback_data: `/clear ${label}` },
|
|
453
|
+
]);
|
|
444
454
|
} else {
|
|
445
455
|
text += `\n<b>${escapeHtml(branchLabel)}</b>: โ
idle\n`;
|
|
446
456
|
text += ` Queue: ${s.queueLength} tasks\n`;
|
|
447
457
|
}
|
|
458
|
+
buttons.push([
|
|
459
|
+
{ text: `๐ New session ${label}`, callback_data: `/newsession ${label}` },
|
|
460
|
+
]);
|
|
461
|
+
}
|
|
462
|
+
if (buttons.length > 0) {
|
|
463
|
+
return { text, replyMarkup: { inline_keyboard: buttons } };
|
|
448
464
|
}
|
|
449
465
|
return text;
|
|
450
466
|
}
|
|
@@ -459,10 +475,14 @@ function handleStatus (args) {
|
|
|
459
475
|
let text = '๐ <b>Status:</b>\n';
|
|
460
476
|
const uptime = formatDuration(Date.now() - startTime);
|
|
461
477
|
text += `Uptime: ${uptime}\n`;
|
|
478
|
+
const buttons = [];
|
|
462
479
|
for (const [project, statuses] of Object.entries(all)) {
|
|
463
480
|
text += `\n<b>${escapeHtml(project)}</b>:`;
|
|
464
481
|
for (const s of statuses) {
|
|
465
482
|
const branchLabel = s.branch || 'main';
|
|
483
|
+
const label = s.branch && s.branch !== 'main' && s.branch !== 'master'
|
|
484
|
+
? `/${project}/${s.branch}`
|
|
485
|
+
: `/${project}`;
|
|
466
486
|
if (s.active) {
|
|
467
487
|
const elapsed = s.active.startedAt
|
|
468
488
|
? formatDuration(Date.now() - new Date(s.active.startedAt).getTime())
|
|
@@ -471,11 +491,21 @@ function handleStatus (args) {
|
|
|
471
491
|
if (s.queueLength > 0) {
|
|
472
492
|
text += ` +${s.queueLength} queued`;
|
|
473
493
|
}
|
|
494
|
+
buttons.push([
|
|
495
|
+
{ text: `๐ Cancel ${label}`, callback_data: `/cancel ${label}` },
|
|
496
|
+
{ text: `๐งน Clear ${label}`, callback_data: `/clear ${label}` },
|
|
497
|
+
]);
|
|
474
498
|
} else {
|
|
475
499
|
text += `\n ${escapeHtml(branchLabel)}: โ
idle`;
|
|
476
500
|
}
|
|
501
|
+
buttons.push([
|
|
502
|
+
{ text: `๐ New session ${label}`, callback_data: `/newsession ${label}` },
|
|
503
|
+
]);
|
|
477
504
|
}
|
|
478
505
|
}
|
|
506
|
+
if (buttons.length > 0) {
|
|
507
|
+
return { text, replyMarkup: { inline_keyboard: buttons } };
|
|
508
|
+
}
|
|
479
509
|
return text;
|
|
480
510
|
}
|
|
481
511
|
|
|
@@ -771,8 +801,28 @@ async function handleStop () {
|
|
|
771
801
|
return null; // Message already sent
|
|
772
802
|
}
|
|
773
803
|
|
|
804
|
+
const MENU_KEYBOARD = {
|
|
805
|
+
inline_keyboard: [
|
|
806
|
+
[
|
|
807
|
+
{ text: '๐ Status', callback_data: '/status' },
|
|
808
|
+
{ text: '๐ Queue', callback_data: '/queue' },
|
|
809
|
+
{ text: '๐ Projects', callback_data: '/projects' },
|
|
810
|
+
],
|
|
811
|
+
[
|
|
812
|
+
{ text: '๐ History', callback_data: '/history' },
|
|
813
|
+
{ text: '๐ฅ PTY', callback_data: '/pty' },
|
|
814
|
+
{ text: '๐ Help', callback_data: '/help' },
|
|
815
|
+
],
|
|
816
|
+
],
|
|
817
|
+
};
|
|
818
|
+
|
|
819
|
+
function handleMenu () {
|
|
820
|
+
return { text: '๐ <b>Menu:</b>', replyMarkup: MENU_KEYBOARD };
|
|
821
|
+
}
|
|
822
|
+
|
|
774
823
|
function handleHelp () {
|
|
775
|
-
return
|
|
824
|
+
return {
|
|
825
|
+
text: `<b>๐ Commands:</b>
|
|
776
826
|
|
|
777
827
|
/status โ status of all projects
|
|
778
828
|
/status /project โ project status
|
|
@@ -788,6 +838,7 @@ function handleHelp () {
|
|
|
788
838
|
/pty [/project[/branch]] โ PTY session diagnostics
|
|
789
839
|
/history โ task history
|
|
790
840
|
/stop โ stop listener
|
|
841
|
+
/menu โ command buttons
|
|
791
842
|
/help โ this help
|
|
792
843
|
|
|
793
844
|
<b>Tasks:</b>
|
|
@@ -797,7 +848,9 @@ function handleHelp () {
|
|
|
797
848
|
|
|
798
849
|
<b>Session:</b>
|
|
799
850
|
๐ = new session, ๐ = continuing session
|
|
800
|
-
ctx N% = context window usage
|
|
851
|
+
ctx N% = context window usage`,
|
|
852
|
+
replyMarkup: MENU_KEYBOARD,
|
|
853
|
+
};
|
|
801
854
|
}
|
|
802
855
|
|
|
803
856
|
// ----------------------
|
|
@@ -891,6 +944,11 @@ async function mainLoop () {
|
|
|
891
944
|
try {
|
|
892
945
|
const messages = await poller.getUpdates();
|
|
893
946
|
for (const msg of messages) {
|
|
947
|
+
// Answer callback query (Telegram requires this)
|
|
948
|
+
if (msg.callbackQueryId) {
|
|
949
|
+
await poller.answerCallbackQuery(msg.callbackQueryId);
|
|
950
|
+
}
|
|
951
|
+
|
|
894
952
|
const parsed = parseMessage(msg.text);
|
|
895
953
|
if (!parsed) {
|
|
896
954
|
continue;
|
|
@@ -900,7 +958,11 @@ async function mainLoop () {
|
|
|
900
958
|
logger.info(`Command: ${parsed.cmd} ${parsed.args}`);
|
|
901
959
|
const response = await handleCommand(parsed.cmd, parsed.args);
|
|
902
960
|
if (response) {
|
|
903
|
-
|
|
961
|
+
if (typeof response === 'object' && response.text) {
|
|
962
|
+
await poller.sendMessage(response.text, msg.callbackQueryId ? null : msg.messageId, response.replyMarkup);
|
|
963
|
+
} else {
|
|
964
|
+
await poller.sendMessage(response, msg.callbackQueryId ? null : msg.messageId);
|
|
965
|
+
}
|
|
904
966
|
}
|
|
905
967
|
} else if (parsed.type === 'task') {
|
|
906
968
|
logger.info(`Task for /${parsed.project}${parsed.branch ? '/' + parsed.branch : ''}: ${parsed.text}`);
|
package/listener/pty-runner.js
CHANGED
|
@@ -293,6 +293,11 @@ export class PtyRunner extends EventEmitter {
|
|
|
293
293
|
// Filter out pipe-mode-specific args
|
|
294
294
|
const args = claudeArgs.filter(a => a !== '-p' && a !== '--output-format' && a !== 'json');
|
|
295
295
|
|
|
296
|
+
// Ensure --permission-mode is set (default: auto) to prevent interactive permission prompts
|
|
297
|
+
if (!args.includes('--permission-mode')) {
|
|
298
|
+
args.push('--permission-mode', 'auto');
|
|
299
|
+
}
|
|
300
|
+
|
|
296
301
|
this.logger.info(`Creating PTY session in ${workDir} with args: ${JSON.stringify(args)}`);
|
|
297
302
|
|
|
298
303
|
const shell = process.platform === 'win32' ? 'cmd.exe' : '/bin/bash';
|
|
@@ -16,7 +16,7 @@ export class TelegramPoller {
|
|
|
16
16
|
|
|
17
17
|
async flush () {
|
|
18
18
|
try {
|
|
19
|
-
const url = `${this.baseUrl}/getUpdates?offset=-1&timeout=0&allowed_updates
|
|
19
|
+
const url = `${this.baseUrl}/getUpdates?offset=-1&timeout=0&allowed_updates=${encodeURIComponent('["message","callback_query"]')}`;
|
|
20
20
|
const res = await fetch(url, { signal: AbortSignal.timeout(10000) });
|
|
21
21
|
const data = await res.json();
|
|
22
22
|
if (data.ok && data.result?.length) {
|
|
@@ -34,7 +34,7 @@ export class TelegramPoller {
|
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
try {
|
|
37
|
-
const url = `${this.baseUrl}/getUpdates?offset=${this.offset}&timeout=${POLL_TIMEOUT}&allowed_updates
|
|
37
|
+
const url = `${this.baseUrl}/getUpdates?offset=${this.offset}&timeout=${POLL_TIMEOUT}&allowed_updates=${encodeURIComponent('["message","callback_query"]')}`;
|
|
38
38
|
const res = await fetch(url, { signal: AbortSignal.timeout((POLL_TIMEOUT + 10) * 1000) });
|
|
39
39
|
const data = await res.json();
|
|
40
40
|
if (!data.ok) {
|
|
@@ -52,6 +52,23 @@ export class TelegramPoller {
|
|
|
52
52
|
const messages = [];
|
|
53
53
|
for (const update of data.result || []) {
|
|
54
54
|
this.offset = update.update_id + 1;
|
|
55
|
+
|
|
56
|
+
// Handle callback_query (inline button press)
|
|
57
|
+
const cb = update.callback_query;
|
|
58
|
+
if (cb) {
|
|
59
|
+
if (String(cb.message?.chat?.id) !== this.chatId) {
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
messages.push({
|
|
63
|
+
messageId: cb.message.message_id,
|
|
64
|
+
text: cb.data,
|
|
65
|
+
chatId: cb.message.chat.id,
|
|
66
|
+
date: cb.message.date,
|
|
67
|
+
callbackQueryId: cb.id,
|
|
68
|
+
});
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
|
|
55
72
|
const msg = update.message;
|
|
56
73
|
if (!msg || !msg.text) {
|
|
57
74
|
continue;
|
|
@@ -83,7 +100,7 @@ export class TelegramPoller {
|
|
|
83
100
|
this._errorBackoff = Math.min(1000 * Math.pow(2, this._consecutiveErrors - 1), 30000);
|
|
84
101
|
}
|
|
85
102
|
|
|
86
|
-
async sendMessage (text, replyToMessageId) {
|
|
103
|
+
async sendMessage (text, replyToMessageId, replyMarkup) {
|
|
87
104
|
const chunks = splitMessage(text);
|
|
88
105
|
let firstMessageId = null;
|
|
89
106
|
for (const chunk of chunks) {
|
|
@@ -96,6 +113,10 @@ export class TelegramPoller {
|
|
|
96
113
|
if (replyToMessageId) {
|
|
97
114
|
body.reply_to_message_id = replyToMessageId;
|
|
98
115
|
}
|
|
116
|
+
// Attach inline keyboard only to the last chunk
|
|
117
|
+
if (replyMarkup && chunk === chunks[chunks.length - 1]) {
|
|
118
|
+
body.reply_markup = replyMarkup;
|
|
119
|
+
}
|
|
99
120
|
const res = await fetch(`${this.baseUrl}/sendMessage`, {
|
|
100
121
|
method: 'POST',
|
|
101
122
|
headers: { 'Content-Type': 'application/json' },
|
|
@@ -127,6 +148,22 @@ export class TelegramPoller {
|
|
|
127
148
|
return firstMessageId;
|
|
128
149
|
}
|
|
129
150
|
|
|
151
|
+
async answerCallbackQuery (callbackQueryId, text) {
|
|
152
|
+
try {
|
|
153
|
+
const body = { callback_query_id: callbackQueryId };
|
|
154
|
+
if (text) {
|
|
155
|
+
body.text = text;
|
|
156
|
+
}
|
|
157
|
+
await fetch(`${this.baseUrl}/answerCallbackQuery`, {
|
|
158
|
+
method: 'POST',
|
|
159
|
+
headers: { 'Content-Type': 'application/json' },
|
|
160
|
+
body: JSON.stringify(body),
|
|
161
|
+
});
|
|
162
|
+
} catch (err) {
|
|
163
|
+
this.logger.error(`answerCallbackQuery error: ${err.message}`);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
130
167
|
async deleteMessage (messageId) {
|
|
131
168
|
if (!messageId) {
|
|
132
169
|
return;
|
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.1.
|
|
4
|
+
"version": "1.1.41",
|
|
5
5
|
"description": "Claude Code task-completion notifications: Telegram, desktop notifications (Windows/macOS/Linux), sound, and voice",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"engines": {
|