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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-notification-plugin",
3
- "version": "1.1.39",
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
- | `/help` | Show help |
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
- 99358845aa9de2dfad99bbc5091580dcc91df4ed
1
+ e6d8c5ca437e900daf65216e3742d35354a15ba6
@@ -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 `<b>๐Ÿ“– Commands:</b>
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
- await poller.sendMessage(response, msg.messageId);
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}`);
@@ -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=["message"]`;
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=["message"]`;
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.39",
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": {