claude-notification-plugin 1.1.38 → 1.1.39
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 -0
- package/commit-sha +1 -1
- package/listener/LISTENER-DETAILED.md +64 -0
- package/listener/listener.js +67 -10
- package/listener/pty-runner.js +70 -2
- package/listener/telegram-poller.js +62 -7
- 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.39",
|
|
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
|
@@ -248,6 +248,7 @@ All commands start with `/` and execute instantly (not queued).
|
|
|
248
248
|
| `/worktrees /project` | List worktrees |
|
|
249
249
|
| `/worktree /project/branch` | Create a worktree |
|
|
250
250
|
| `/rmworktree /project/branch` | Remove a worktree |
|
|
251
|
+
| `/pty [/project[/branch]]` | PTY session diagnostics (state, buffer, output) |
|
|
251
252
|
| `/history` | Recent task history |
|
|
252
253
|
| `/stop` | Stop the listener |
|
|
253
254
|
| `/help` | Show help |
|
|
@@ -269,6 +270,7 @@ All commands start with `/` and execute instantly (not queued).
|
|
|
269
270
|
| `liveConsole` | `true` | Stream PTY output to the "Running..." Telegram message in real-time |
|
|
270
271
|
| `liveConsoleInterval`| `5` | Live console update interval in seconds |
|
|
271
272
|
|
|
273
|
+
|
|
272
274
|
### Projects and worktrees
|
|
273
275
|
|
|
274
276
|
**The queue is tied to the working directory, not the project name:**
|
package/commit-sha
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
99358845aa9de2dfad99bbc5091580dcc91df4ed
|
|
@@ -20,6 +20,7 @@ and executes them on your machine via an interactive Claude Code PTY session. Th
|
|
|
20
20
|
- [Projects and worktrees](#projects-and-worktrees)
|
|
21
21
|
- [Task queues](#task-queues)
|
|
22
22
|
- [Bot commands](#bot-commands)
|
|
23
|
+
- [Live console and PTY diagnostics](#live-console-and-pty-diagnostics)
|
|
23
24
|
- [Task lifecycle](#task-lifecycle)
|
|
24
25
|
- [State files](#state-files)
|
|
25
26
|
- [Security](#security)
|
|
@@ -722,12 +723,75 @@ Bot: 👋 Listener is shutting down...
|
|
|
722
723
|
|
|
723
724
|
All active tasks will be terminated. Queues are saved to disk and will be restored on the next startup.
|
|
724
725
|
|
|
726
|
+
### /pty — PTY diagnostics
|
|
727
|
+
|
|
728
|
+
Shows real-time information about PTY sessions: state, buffer size, live console status, and the last 15 lines of cleaned output.
|
|
729
|
+
|
|
730
|
+
```
|
|
731
|
+
You: /pty
|
|
732
|
+
Bot: 🖥 PTY Sessions:
|
|
733
|
+
|
|
734
|
+
/api
|
|
735
|
+
State: busy
|
|
736
|
+
Buffer: 12480 bytes
|
|
737
|
+
Elapsed: 2m 35s
|
|
738
|
+
Live console: ✅
|
|
739
|
+
PTY log: writing
|
|
740
|
+
|
|
741
|
+
◐ Reading src/auth.js
|
|
742
|
+
● Editing src/middleware.js
|
|
743
|
+
Added JWT validation...
|
|
744
|
+
```
|
|
745
|
+
|
|
746
|
+
```
|
|
747
|
+
You: /pty /api
|
|
748
|
+
Bot: (same, but for a specific project)
|
|
749
|
+
```
|
|
750
|
+
|
|
725
751
|
### /help — help
|
|
726
752
|
|
|
727
753
|
Shows a brief reference for all commands.
|
|
728
754
|
|
|
729
755
|
---
|
|
730
756
|
|
|
757
|
+
## Live console and PTY diagnostics
|
|
758
|
+
|
|
759
|
+
### Live console
|
|
760
|
+
|
|
761
|
+
When **`liveConsole`** is enabled (default: `true`), the "⏳ Running..." message in Telegram is periodically updated with the cleaned tail of Claude Code's PTY output, so you can see what Claude is doing in real-time.
|
|
762
|
+
|
|
763
|
+
The output is cleaned from ANSI escape codes and Claude Code UI chrome (logo, status bar, prompts), leaving only meaningful content.
|
|
764
|
+
|
|
765
|
+
Configuration:
|
|
766
|
+
- `liveConsole` — enable/disable (default: `true`)
|
|
767
|
+
- `liveConsoleInterval` — update interval in seconds (default: `5`)
|
|
768
|
+
|
|
769
|
+
### PTY logs
|
|
770
|
+
|
|
771
|
+
Each running task writes raw PTY output to a file: `{taskLogDir}/{project}_{branch}_pty.log`.
|
|
772
|
+
The file is overwritten when a new task starts for the same project/branch.
|
|
773
|
+
|
|
774
|
+
Monitor in real-time:
|
|
775
|
+
```bash
|
|
776
|
+
# Linux / macOS / Git Bash
|
|
777
|
+
tail -f ~/.claude/myproject_main_pty.log
|
|
778
|
+
|
|
779
|
+
# Windows PowerShell
|
|
780
|
+
Get-Content ~/.claude/myproject_main_pty.log -Wait -Tail 50
|
|
781
|
+
```
|
|
782
|
+
|
|
783
|
+
### /pty command
|
|
784
|
+
|
|
785
|
+
Send `/pty` or `/pty /project` in Telegram to get instant diagnostics:
|
|
786
|
+
- Session state (`busy` / `idle` / `starting`)
|
|
787
|
+
- Buffer size in bytes
|
|
788
|
+
- Elapsed time since task start
|
|
789
|
+
- Whether live console interval is active
|
|
790
|
+
- Whether PTY log stream is writing
|
|
791
|
+
- Last 15 lines of cleaned output
|
|
792
|
+
|
|
793
|
+
---
|
|
794
|
+
|
|
731
795
|
## Task lifecycle
|
|
732
796
|
|
|
733
797
|
### Path of a task from message to result
|
package/listener/listener.js
CHANGED
|
@@ -6,7 +6,7 @@ import path from 'path';
|
|
|
6
6
|
import process from 'process';
|
|
7
7
|
import { createLogger } from './logger.js';
|
|
8
8
|
import { createTaskLogger } from './task-logger.js';
|
|
9
|
-
import { TelegramPoller, escapeHtml,
|
|
9
|
+
import { TelegramPoller, escapeHtml, cleanPtyOutput } from './telegram-poller.js';
|
|
10
10
|
import { WorkQueue } from './work-queue.js';
|
|
11
11
|
import { PtyRunner } from './pty-runner.js';
|
|
12
12
|
import { WorktreeManager } from './worktree-manager.js';
|
|
@@ -100,7 +100,7 @@ const taskLogDir = config.listener?.taskLogDir || listenerLogDir;
|
|
|
100
100
|
fs.mkdirSync(taskLogDir, { recursive: true });
|
|
101
101
|
const taskLogger = createTaskLogger(taskLogDir);
|
|
102
102
|
|
|
103
|
-
const runner = new PtyRunner(logger, taskTimeout, taskLogger);
|
|
103
|
+
const runner = new PtyRunner(logger, taskTimeout, taskLogger, taskLogDir);
|
|
104
104
|
|
|
105
105
|
const worktreeManager = new WorktreeManager(config, logger);
|
|
106
106
|
|
|
@@ -300,11 +300,7 @@ function startLiveConsole (workDir, messageId, header) {
|
|
|
300
300
|
if (!raw) {
|
|
301
301
|
return;
|
|
302
302
|
}
|
|
303
|
-
const cleaned =
|
|
304
|
-
.split('\n')
|
|
305
|
-
.map((l) => l.trimEnd())
|
|
306
|
-
.filter((l) => l.length > 0)
|
|
307
|
-
.join('\n');
|
|
303
|
+
const cleaned = cleanPtyOutput(raw);
|
|
308
304
|
if (!cleaned) {
|
|
309
305
|
return;
|
|
310
306
|
}
|
|
@@ -320,10 +316,11 @@ function startLiveConsole (workDir, messageId, header) {
|
|
|
320
316
|
return;
|
|
321
317
|
}
|
|
322
318
|
lastSentText = output;
|
|
323
|
-
const
|
|
319
|
+
const elapsed = formatDuration(Date.now() - new Date(runner.getActive(workDir)?.startedAt || Date.now()).getTime());
|
|
320
|
+
const text = `${header}\n<i>${elapsed}</i>\n\n<pre>${escapeHtml(output)}</pre>`;
|
|
324
321
|
await poller.editMessage(messageId, text);
|
|
325
|
-
} catch {
|
|
326
|
-
|
|
322
|
+
} catch (err) {
|
|
323
|
+
logger.warn(`Live console edit error: ${err.message}`);
|
|
327
324
|
}
|
|
328
325
|
}, liveConsoleInterval);
|
|
329
326
|
liveConsoleTimers.set(workDir, timer);
|
|
@@ -415,6 +412,8 @@ async function handleCommand (cmd, args) {
|
|
|
415
412
|
return handleRemoveWorktree(args);
|
|
416
413
|
case '/history':
|
|
417
414
|
return handleHistory();
|
|
415
|
+
case '/pty':
|
|
416
|
+
return handlePty(args);
|
|
418
417
|
case '/stop':
|
|
419
418
|
return handleStop();
|
|
420
419
|
case '/help':
|
|
@@ -691,6 +690,63 @@ function handleRemoveWorktree (args) {
|
|
|
691
690
|
}
|
|
692
691
|
}
|
|
693
692
|
|
|
693
|
+
function handlePty (args) {
|
|
694
|
+
const target = parseTarget(args);
|
|
695
|
+
|
|
696
|
+
if (target) {
|
|
697
|
+
let workDir;
|
|
698
|
+
try {
|
|
699
|
+
workDir = worktreeManager.resolveWorkDir(target.project, target.branch);
|
|
700
|
+
} catch (err) {
|
|
701
|
+
return `❌ ${escapeHtml(err.message)}`;
|
|
702
|
+
}
|
|
703
|
+
const info = runner.getSessionInfo(workDir);
|
|
704
|
+
if (!info) {
|
|
705
|
+
return `🖥 No PTY session for /${escapeHtml(target.project)}${target.branch ? '/' + escapeHtml(target.branch) : ''}`;
|
|
706
|
+
}
|
|
707
|
+
return formatPtyInfo(target.project, target.branch, workDir, info);
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
// All sessions
|
|
711
|
+
const allInfo = runner.getAllSessionInfo();
|
|
712
|
+
if (Object.keys(allInfo).length === 0) {
|
|
713
|
+
return '🖥 No active PTY sessions';
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
let text = '🖥 <b>PTY Sessions:</b>\n';
|
|
717
|
+
for (const [workDir, info] of Object.entries(allInfo)) {
|
|
718
|
+
const entry = queue.queues[workDir];
|
|
719
|
+
const project = entry?.project || '?';
|
|
720
|
+
const branch = entry?.branch || null;
|
|
721
|
+
text += '\n' + formatPtyInfo(project, branch, workDir, info);
|
|
722
|
+
}
|
|
723
|
+
return text;
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
function formatPtyInfo (project, branch, workDir, info) {
|
|
727
|
+
const label = branch && branch !== 'main' && branch !== 'master'
|
|
728
|
+
? `/${project}/${branch}`
|
|
729
|
+
: `/${project}`;
|
|
730
|
+
const elapsed = info.startedAt
|
|
731
|
+
? formatDuration(Date.now() - new Date(info.startedAt).getTime())
|
|
732
|
+
: '-';
|
|
733
|
+
const liveTimer = liveConsoleTimers.has(workDir) ? '✅' : '❌';
|
|
734
|
+
const raw = runner.getBuffer(workDir);
|
|
735
|
+
const cleaned = raw ? cleanPtyOutput(raw) : '';
|
|
736
|
+
const lastLines = cleaned
|
|
737
|
+
? cleaned.split('\n').slice(-15).join('\n')
|
|
738
|
+
: '(empty)';
|
|
739
|
+
|
|
740
|
+
return `<b>${escapeHtml(label)}</b>
|
|
741
|
+
State: <code>${info.state}</code>
|
|
742
|
+
Buffer: <code>${info.bufferSize}</code> bytes
|
|
743
|
+
Elapsed: ${elapsed}
|
|
744
|
+
Live console: ${liveTimer}
|
|
745
|
+
PTY log: <code>${info.hasLogStream ? 'writing' : 'off'}</code>
|
|
746
|
+
|
|
747
|
+
<pre>${escapeHtml(lastLines)}</pre>`;
|
|
748
|
+
}
|
|
749
|
+
|
|
694
750
|
function handleHistory () {
|
|
695
751
|
const history = queue.getHistory(10);
|
|
696
752
|
if (history.length === 0) {
|
|
@@ -729,6 +785,7 @@ function handleHelp () {
|
|
|
729
785
|
/worktrees /project — project worktrees
|
|
730
786
|
/worktree /project/branch — create worktree
|
|
731
787
|
/rmworktree /project/branch — remove worktree
|
|
788
|
+
/pty [/project[/branch]] — PTY session diagnostics
|
|
732
789
|
/history — task history
|
|
733
790
|
/stop — stop listener
|
|
734
791
|
/help — this help
|
package/listener/pty-runner.js
CHANGED
|
@@ -13,12 +13,13 @@ const DEFAULT_TIMEOUT = 600_000; // 10 minutes
|
|
|
13
13
|
* receives completion signals via marker files written by the notifier hook.
|
|
14
14
|
*/
|
|
15
15
|
export class PtyRunner extends EventEmitter {
|
|
16
|
-
constructor (logger, timeout, taskLogger) {
|
|
16
|
+
constructor (logger, timeout, taskLogger, ptyLogDir) {
|
|
17
17
|
super();
|
|
18
18
|
this.logger = logger;
|
|
19
19
|
this.timeout = timeout || DEFAULT_TIMEOUT;
|
|
20
20
|
this.taskLogger = taskLogger || null;
|
|
21
|
-
|
|
21
|
+
this.ptyLogDir = ptyLogDir || null;
|
|
22
|
+
// workDir -> { pty, state, currentTask, sessionId, workDir, _pendingId, _buffer, _logStream }
|
|
22
23
|
this.sessions = new Map();
|
|
23
24
|
this.pendingMarkers = new Map(); // pendingId -> resolve callback
|
|
24
25
|
this._pty = null; // lazy-loaded node-pty module
|
|
@@ -196,6 +197,27 @@ export class PtyRunner extends EventEmitter {
|
|
|
196
197
|
});
|
|
197
198
|
}
|
|
198
199
|
|
|
200
|
+
_openPtyLog (session, task) {
|
|
201
|
+
if (session._logStream) {
|
|
202
|
+
session._logStream.end();
|
|
203
|
+
session._logStream = null;
|
|
204
|
+
}
|
|
205
|
+
if (!this.ptyLogDir) {
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
try {
|
|
209
|
+
const project = task.project || 'unknown';
|
|
210
|
+
const branch = (task.branch || 'main').replace(/[/\\:*?"<>|]/g, '_');
|
|
211
|
+
const logFile = path.join(this.ptyLogDir, `${project}_${branch}_pty.log`);
|
|
212
|
+
session._logStream = fs.createWriteStream(logFile, { flags: 'w' });
|
|
213
|
+
session._logStream.on('error', () => {
|
|
214
|
+
session._logStream = null;
|
|
215
|
+
});
|
|
216
|
+
} catch {
|
|
217
|
+
// ignore — logging is best-effort
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
199
221
|
/**
|
|
200
222
|
* Send a task to an existing PTY session and wait for completion.
|
|
201
223
|
*/
|
|
@@ -205,6 +227,8 @@ export class PtyRunner extends EventEmitter {
|
|
|
205
227
|
session.state = 'busy';
|
|
206
228
|
session.currentTask = task;
|
|
207
229
|
session._pendingId = pendingId;
|
|
230
|
+
session._buffer = '';
|
|
231
|
+
this._openPtyLog(session, task);
|
|
208
232
|
|
|
209
233
|
// Set up marker wait + timeout
|
|
210
234
|
const markerPromise = this._waitForMarker(pendingId, this.timeout);
|
|
@@ -304,6 +328,9 @@ export class PtyRunner extends EventEmitter {
|
|
|
304
328
|
if (session._buffer.length > 50000) {
|
|
305
329
|
session._buffer = session._buffer.slice(-25000);
|
|
306
330
|
}
|
|
331
|
+
if (session._logStream) {
|
|
332
|
+
session._logStream.write(data);
|
|
333
|
+
}
|
|
307
334
|
});
|
|
308
335
|
|
|
309
336
|
ptyProcess.onExit(({ exitCode }) => {
|
|
@@ -382,6 +409,11 @@ export class PtyRunner extends EventEmitter {
|
|
|
382
409
|
this.pendingMarkers.delete(session._pendingId);
|
|
383
410
|
}
|
|
384
411
|
|
|
412
|
+
if (session._logStream) {
|
|
413
|
+
session._logStream.end();
|
|
414
|
+
session._logStream = null;
|
|
415
|
+
}
|
|
416
|
+
|
|
385
417
|
try {
|
|
386
418
|
if (session.pty) {
|
|
387
419
|
session.pty.kill();
|
|
@@ -440,6 +472,42 @@ export class PtyRunner extends EventEmitter {
|
|
|
440
472
|
return session?._buffer || '';
|
|
441
473
|
}
|
|
442
474
|
|
|
475
|
+
/**
|
|
476
|
+
* Get diagnostic info about a PTY session.
|
|
477
|
+
*/
|
|
478
|
+
getSessionInfo (workDir) {
|
|
479
|
+
const session = this.sessions.get(workDir);
|
|
480
|
+
if (!session) {
|
|
481
|
+
return null;
|
|
482
|
+
}
|
|
483
|
+
return {
|
|
484
|
+
state: session.state,
|
|
485
|
+
bufferSize: session._buffer?.length || 0,
|
|
486
|
+
hasLogStream: !!session._logStream,
|
|
487
|
+
taskText: session.currentTask?.text || null,
|
|
488
|
+
startedAt: session.currentTask?.startedAt || null,
|
|
489
|
+
sessionId: session.sessionId || null,
|
|
490
|
+
pendingId: session._pendingId || null,
|
|
491
|
+
};
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
/**
|
|
495
|
+
* Get diagnostic info for all sessions.
|
|
496
|
+
*/
|
|
497
|
+
getAllSessionInfo () {
|
|
498
|
+
const result = {};
|
|
499
|
+
for (const [workDir, session] of this.sessions) {
|
|
500
|
+
result[workDir] = {
|
|
501
|
+
state: session.state,
|
|
502
|
+
bufferSize: session._buffer?.length || 0,
|
|
503
|
+
hasLogStream: !!session._logStream,
|
|
504
|
+
taskText: session.currentTask?.text || null,
|
|
505
|
+
startedAt: session.currentTask?.startedAt || null,
|
|
506
|
+
};
|
|
507
|
+
}
|
|
508
|
+
return result;
|
|
509
|
+
}
|
|
510
|
+
|
|
443
511
|
/**
|
|
444
512
|
* Cancel all active tasks (for graceful shutdown).
|
|
445
513
|
*/
|
|
@@ -237,14 +237,14 @@ function splitMessage (text) {
|
|
|
237
237
|
return chunks;
|
|
238
238
|
}
|
|
239
239
|
|
|
240
|
-
// Strip ANSI escape codes and
|
|
240
|
+
// Strip ANSI escape codes and terminal control sequences from PTY output
|
|
241
241
|
function stripAnsi (text) {
|
|
242
242
|
return text
|
|
243
|
-
//
|
|
244
|
-
.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, '')
|
|
245
|
-
// OSC sequences (
|
|
246
|
-
.replace(/\x1b
|
|
247
|
-
// Other escape sequences
|
|
243
|
+
// CSI sequences: \x1b[ followed by optional ?/>/! prefix, params, and terminator
|
|
244
|
+
.replace(/\x1b\[[?>=!]?[0-9;]*[a-zA-Z~]/g, '')
|
|
245
|
+
// OSC sequences: \x1b] ... (terminated by BEL or ST)
|
|
246
|
+
.replace(/\x1b][^\x07\x1b]*(?:\x07|\x1b\\)/g, '')
|
|
247
|
+
// Other two-char escape sequences (\x1b followed by any single char)
|
|
248
248
|
.replace(/\x1b[^[\]]/g, '')
|
|
249
249
|
// Carriage returns (overwrite lines)
|
|
250
250
|
.replace(/\r/g, '')
|
|
@@ -252,4 +252,59 @@ function stripAnsi (text) {
|
|
|
252
252
|
.replace(/[\x00-\x08\x0b\x0c\x0e-\x1f]/g, '');
|
|
253
253
|
}
|
|
254
254
|
|
|
255
|
-
|
|
255
|
+
// Clean PTY output for display: strip ANSI + remove Claude Code UI chrome
|
|
256
|
+
function cleanPtyOutput (raw) {
|
|
257
|
+
const stripped = stripAnsi(raw);
|
|
258
|
+
const lines = stripped.split('\n');
|
|
259
|
+
const cleaned = [];
|
|
260
|
+
for (const line of lines) {
|
|
261
|
+
const trimmed = line.trimEnd();
|
|
262
|
+
// Skip empty lines
|
|
263
|
+
if (!trimmed) {
|
|
264
|
+
continue;
|
|
265
|
+
}
|
|
266
|
+
// Skip Claude Code UI: logo, banner, horizontal rules, prompts, status
|
|
267
|
+
if (/^[▐▝▘▛▜█▌▀▄░▒▓\s]+$/.test(trimmed)) {
|
|
268
|
+
continue;
|
|
269
|
+
}
|
|
270
|
+
if (/^[─━═╌┄]+$/.test(trimmed)) {
|
|
271
|
+
continue;
|
|
272
|
+
}
|
|
273
|
+
if (/^❯\s/.test(trimmed)) {
|
|
274
|
+
continue;
|
|
275
|
+
}
|
|
276
|
+
if (/^[⏵⏴]\s*[⏵⏴]?\s*(bypass|auto|plan|permissions?)/i.test(trimmed)) {
|
|
277
|
+
continue;
|
|
278
|
+
}
|
|
279
|
+
if (/^◐\s/.test(trimmed) || /^\s*◐\s/.test(trimmed)) {
|
|
280
|
+
continue;
|
|
281
|
+
}
|
|
282
|
+
if (/Pasting\s*text/i.test(trimmed)) {
|
|
283
|
+
continue;
|
|
284
|
+
}
|
|
285
|
+
if (/^Claude\s*Code\s*v/i.test(trimmed)) {
|
|
286
|
+
continue;
|
|
287
|
+
}
|
|
288
|
+
if (/Opus|Sonnet|Haiku|Claude\s*Max/i.test(trimmed) && trimmed.length < 80) {
|
|
289
|
+
continue;
|
|
290
|
+
}
|
|
291
|
+
if (/shift\+tab\s*to\s*cycle/i.test(trimmed)) {
|
|
292
|
+
continue;
|
|
293
|
+
}
|
|
294
|
+
if (/ctrl\+[a-z]\s+to\s/i.test(trimmed)) {
|
|
295
|
+
continue;
|
|
296
|
+
}
|
|
297
|
+
if (/^Try\s*"/.test(trimmed)) {
|
|
298
|
+
continue;
|
|
299
|
+
}
|
|
300
|
+
// Skip lines that are mostly box-drawing or block chars (>50%)
|
|
301
|
+
const specialChars = (trimmed.match(/[▐▝▘▛▜█▌▀▄░▒▓─━═╌┄│┃┌┐└┘├┤┬┴┼╔╗╚╝╠╣╦╩╬]/g) || []).length;
|
|
302
|
+
if (specialChars > trimmed.length * 0.5) {
|
|
303
|
+
continue;
|
|
304
|
+
}
|
|
305
|
+
cleaned.push(trimmed);
|
|
306
|
+
}
|
|
307
|
+
return cleaned.join('\n');
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
export { escapeHtml, stripAnsi, cleanPtyOutput };
|
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.39",
|
|
5
5
|
"description": "Claude Code task-completion notifications: Telegram, desktop notifications (Windows/macOS/Linux), sound, and voice",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"engines": {
|