claude-notification-plugin 1.1.49 → 1.1.55
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 +34 -32
- package/commit-sha +1 -1
- package/hooks/hooks.json +89 -34
- package/listener/LISTENER-DETAILED.md +115 -93
- package/listener/listener.js +77 -43
- package/listener/message-parser.js +96 -111
- package/listener/pty-runner.js +123 -41
- package/notifier/NOTIFIER-DETAILED.md +82 -0
- package/notifier/notifier.js +88 -19
- package/package.json +1 -1
package/listener/pty-runner.js
CHANGED
|
@@ -68,12 +68,9 @@ export class PtyRunner extends EventEmitter {
|
|
|
68
68
|
|
|
69
69
|
/**
|
|
70
70
|
* Check for new marker files in the signal directory.
|
|
71
|
+
* Handles typed signals: stop (default), error, ready, activity, compact.
|
|
71
72
|
*/
|
|
72
73
|
_checkMarkerFiles () {
|
|
73
|
-
if (this.pendingMarkers.size === 0) {
|
|
74
|
-
return;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
74
|
let files;
|
|
78
75
|
try {
|
|
79
76
|
files = fs.readdirSync(PTY_SIGNAL_DIR);
|
|
@@ -94,26 +91,96 @@ export class PtyRunner extends EventEmitter {
|
|
|
94
91
|
continue;
|
|
95
92
|
}
|
|
96
93
|
|
|
97
|
-
// Try to match by cwd (primary matching for PTY runner)
|
|
98
94
|
const cwd = marker.cwd;
|
|
99
|
-
if (cwd) {
|
|
95
|
+
if (!cwd) {
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const type = marker.type || 'stop';
|
|
100
|
+
|
|
101
|
+
if (type === 'stop') {
|
|
102
|
+
// Completion signal — resolve pending marker
|
|
103
|
+
if (this.pendingMarkers.size === 0) {
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
100
106
|
for (const [pid, resolve] of this.pendingMarkers) {
|
|
101
107
|
const session = this._findSessionByPendingId(pid);
|
|
102
108
|
if (session && this._normalizePath(session.workDir) === this._normalizePath(cwd)) {
|
|
103
109
|
this.pendingMarkers.delete(pid);
|
|
104
|
-
|
|
105
|
-
fs.unlinkSync(filePath);
|
|
106
|
-
} catch {
|
|
107
|
-
// ignore
|
|
108
|
-
}
|
|
110
|
+
this._unlinkSafe(filePath);
|
|
109
111
|
resolve(marker);
|
|
110
112
|
break;
|
|
111
113
|
}
|
|
112
114
|
}
|
|
115
|
+
} else if (type === 'error') {
|
|
116
|
+
// StopFailure — emit error, abort task
|
|
117
|
+
this._unlinkSafe(filePath);
|
|
118
|
+
for (const [workDir, session] of this.sessions) {
|
|
119
|
+
if (session.state === 'busy' && this._normalizePath(session.workDir) === this._normalizePath(cwd)) {
|
|
120
|
+
if (session._pendingId && this.pendingMarkers.has(session._pendingId)) {
|
|
121
|
+
this.pendingMarkers.delete(session._pendingId);
|
|
122
|
+
}
|
|
123
|
+
const task = session.currentTask;
|
|
124
|
+
session.state = 'idle';
|
|
125
|
+
session.currentTask = null;
|
|
126
|
+
this._destroyPty(workDir);
|
|
127
|
+
const errorMsg = `API error: ${marker.error}${marker.errorDetails ? ' — ' + marker.errorDetails : ''}`;
|
|
128
|
+
this.logger.error(`Hook signal: ${errorMsg} in ${workDir}`);
|
|
129
|
+
if (this.taskLogger) {
|
|
130
|
+
this.taskLogger.logAnswer(task?.project || 'unknown', task?.branch || 'main', errorMsg, 1);
|
|
131
|
+
}
|
|
132
|
+
this.emit('error', workDir, task, errorMsg);
|
|
133
|
+
break;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
} else if (type === 'ready') {
|
|
137
|
+
// SessionStart — emit ready event
|
|
138
|
+
this._unlinkSafe(filePath);
|
|
139
|
+
for (const [workDir, session] of this.sessions) {
|
|
140
|
+
if (this._normalizePath(session.workDir) === this._normalizePath(cwd)) {
|
|
141
|
+
session._model = marker.model || '';
|
|
142
|
+
this.emit('ready', workDir, marker);
|
|
143
|
+
break;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
} else if (type === 'activity') {
|
|
147
|
+
// PostToolUse — update activity data (don't delete, gets overwritten)
|
|
148
|
+
for (const [, session] of this.sessions) {
|
|
149
|
+
if (this._normalizePath(session.workDir) === this._normalizePath(cwd)) {
|
|
150
|
+
session._lastActivity = {
|
|
151
|
+
toolName: marker.toolName,
|
|
152
|
+
toolInput: marker.toolInput,
|
|
153
|
+
timestamp: marker.timestamp,
|
|
154
|
+
};
|
|
155
|
+
break;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
} else if (type === 'compact') {
|
|
159
|
+
// PostCompact — update compaction info
|
|
160
|
+
this._unlinkSafe(filePath);
|
|
161
|
+
for (const [workDir, session] of this.sessions) {
|
|
162
|
+
if (this._normalizePath(session.workDir) === this._normalizePath(cwd)) {
|
|
163
|
+
session._lastCompact = {
|
|
164
|
+
summary: marker.summary,
|
|
165
|
+
trigger: marker.trigger,
|
|
166
|
+
timestamp: marker.timestamp,
|
|
167
|
+
};
|
|
168
|
+
this.emit('compact', workDir, marker);
|
|
169
|
+
break;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
113
172
|
}
|
|
114
173
|
}
|
|
115
174
|
}
|
|
116
175
|
|
|
176
|
+
_unlinkSafe (filePath) {
|
|
177
|
+
try {
|
|
178
|
+
fs.unlinkSync(filePath);
|
|
179
|
+
} catch {
|
|
180
|
+
// ignore
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
117
184
|
_normalizePath (p) {
|
|
118
185
|
return p.replace(/\\/g, '/').replace(/\/+$/, '').toLowerCase();
|
|
119
186
|
}
|
|
@@ -261,36 +328,11 @@ export class PtyRunner extends EventEmitter {
|
|
|
261
328
|
writeLines();
|
|
262
329
|
this.logger.info(`PTY task sent to ${workDir}: ${task.text.slice(0, 100)}`);
|
|
263
330
|
|
|
264
|
-
//
|
|
265
|
-
|
|
266
|
-
{ pattern: 'auto mode temporarily unavailable', msg: 'Claude auto mode temporarily unavailable — retry later' },
|
|
267
|
-
{ pattern: 'Session expired', msg: 'Claude session expired' },
|
|
268
|
-
{ pattern: 'Authentication required', msg: 'Claude authentication required' },
|
|
269
|
-
];
|
|
270
|
-
const errorCheckInterval = setInterval(() => {
|
|
271
|
-
const buf = session._buffer || '';
|
|
272
|
-
for (const { pattern, msg } of errorPatterns) {
|
|
273
|
-
if (buf.includes(pattern)) {
|
|
274
|
-
clearInterval(errorCheckInterval);
|
|
275
|
-
this.logger.error(`PTY fatal: ${msg} in ${workDir}`);
|
|
276
|
-
if (session._pendingId && this.pendingMarkers.has(session._pendingId)) {
|
|
277
|
-
this.pendingMarkers.delete(session._pendingId);
|
|
278
|
-
}
|
|
279
|
-
session.state = 'idle';
|
|
280
|
-
session.currentTask = null;
|
|
281
|
-
this._destroyPty(workDir);
|
|
282
|
-
this.emit('error', workDir, task, msg);
|
|
283
|
-
return;
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
}, 3000);
|
|
287
|
-
|
|
288
|
-
// Clean up error monitor when task completes normally
|
|
289
|
-
const clearErrorCheck = () => clearInterval(errorCheckInterval);
|
|
331
|
+
// Error detection is now handled by the StopFailure hook signal,
|
|
332
|
+
// which writes an error signal file processed by _checkMarkerFiles.
|
|
290
333
|
|
|
291
334
|
// Handle completion asynchronously
|
|
292
335
|
markerPromise.then((marker) => {
|
|
293
|
-
clearErrorCheck();
|
|
294
336
|
session.state = 'idle';
|
|
295
337
|
session.currentTask = null;
|
|
296
338
|
session.sessionId = marker.sessionId;
|
|
@@ -312,7 +354,6 @@ export class PtyRunner extends EventEmitter {
|
|
|
312
354
|
}
|
|
313
355
|
this.emit('complete', workDir, task, result);
|
|
314
356
|
}).catch((err) => {
|
|
315
|
-
clearErrorCheck();
|
|
316
357
|
session.state = 'idle';
|
|
317
358
|
session.currentTask = null;
|
|
318
359
|
|
|
@@ -352,8 +393,8 @@ export class PtyRunner extends EventEmitter {
|
|
|
352
393
|
// Reduce PTY output noise: disable animations, progress bar, tips
|
|
353
394
|
if (!args.includes('--settings')) {
|
|
354
395
|
args.push('--settings', JSON.stringify({
|
|
355
|
-
prefersReducedMotion: true,
|
|
356
|
-
outputStyle: 'plain',
|
|
396
|
+
// prefersReducedMotion: true,
|
|
397
|
+
// outputStyle: 'plain',
|
|
357
398
|
terminalProgressBarEnabled: false,
|
|
358
399
|
spinnerTipsEnabled: false,
|
|
359
400
|
showTurnDuration: false,
|
|
@@ -389,6 +430,9 @@ export class PtyRunner extends EventEmitter {
|
|
|
389
430
|
_buffer: '',
|
|
390
431
|
};
|
|
391
432
|
|
|
433
|
+
// Permission auto-approval is now handled by the PermissionRequest hook
|
|
434
|
+
// (returns auto-approve JSON when CLAUDE_NOTIFY_FROM_LISTENER=1).
|
|
435
|
+
|
|
392
436
|
ptyProcess.onData((data) => {
|
|
393
437
|
session._buffer += data;
|
|
394
438
|
// Keep buffer reasonable size
|
|
@@ -551,6 +595,44 @@ export class PtyRunner extends EventEmitter {
|
|
|
551
595
|
return session?._buffer || '';
|
|
552
596
|
}
|
|
553
597
|
|
|
598
|
+
/**
|
|
599
|
+
* Get last tool activity for a workDir (from PostToolUse hook signals).
|
|
600
|
+
*/
|
|
601
|
+
getActivity (workDir) {
|
|
602
|
+
const session = this.sessions.get(workDir);
|
|
603
|
+
return session?._lastActivity || null;
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
/**
|
|
607
|
+
* Clean up activity signal file for a workDir.
|
|
608
|
+
*/
|
|
609
|
+
cleanActivitySignal (workDir) {
|
|
610
|
+
const session = this.sessions.get(workDir);
|
|
611
|
+
if (session) {
|
|
612
|
+
session._lastActivity = null;
|
|
613
|
+
}
|
|
614
|
+
// Also try to delete any activity files matching this workDir
|
|
615
|
+
try {
|
|
616
|
+
const files = fs.readdirSync(PTY_SIGNAL_DIR);
|
|
617
|
+
for (const f of files) {
|
|
618
|
+
if (!f.startsWith('act_') || !f.endsWith('.json')) {
|
|
619
|
+
continue;
|
|
620
|
+
}
|
|
621
|
+
const filePath = path.join(PTY_SIGNAL_DIR, f);
|
|
622
|
+
try {
|
|
623
|
+
const data = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
|
|
624
|
+
if (data.cwd && this._normalizePath(data.cwd) === this._normalizePath(workDir)) {
|
|
625
|
+
fs.unlinkSync(filePath);
|
|
626
|
+
}
|
|
627
|
+
} catch {
|
|
628
|
+
// ignore
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
} catch {
|
|
632
|
+
// ignore
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
|
|
554
636
|
/**
|
|
555
637
|
* Get diagnostic info about a PTY session.
|
|
556
638
|
*/
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# Notifier Hook Events - Detailed Guide
|
|
2
|
+
|
|
3
|
+
The notifier (`notifier/notifier.js`) is the plugin's hook handler — a single script invoked by Claude Code for every registered hook event. It reads JSON from stdin, determines the event type and operating mode, and dispatches accordingly.
|
|
4
|
+
|
|
5
|
+
## Operating modes
|
|
6
|
+
|
|
7
|
+
| Mode | Condition | Behavior |
|
|
8
|
+
|---|---|---|
|
|
9
|
+
| **Disabled** | `CLAUDE_NOTIFY_DISABLE=1` | Exit immediately |
|
|
10
|
+
| **Listener-only** | `CLAUDE_NOTIFY_FROM_LISTENER=1` (and `CLAUDE_NOTIFY_AFTER_LISTENER` not `1`) | Write signal files to `~/.claude/pty-signals/`, no user notifications |
|
|
11
|
+
| **Normal** | Default | Send notifications (Telegram, desktop, sound, voice, webhook) |
|
|
12
|
+
|
|
13
|
+
## Hook events
|
|
14
|
+
|
|
15
|
+
| Hook Event | Mode | Sync | Purpose |
|
|
16
|
+
|---|---|---|---|
|
|
17
|
+
| `UserPromptSubmit` | Normal | sync | Starts the notification timer (records session start time) |
|
|
18
|
+
| `Stop` | Both | sync | Normal: sends completion notification. Listener: writes completion signal file |
|
|
19
|
+
| `StopFailure` | Both | async | Normal: sends error notification. Listener: writes error signal file |
|
|
20
|
+
| `Notification` | Normal | sync | Sends waiting-for-input notification (when `notifyOnWaiting` is enabled) |
|
|
21
|
+
| `SessionStart` | Listener | async | Writes session ready signal (model name, startup/resume source) |
|
|
22
|
+
| `PermissionRequest` | Listener | sync | Auto-approves permission prompts via JSON output to stdout |
|
|
23
|
+
| `PostToolUse` | Listener | async | Writes tool activity signal (tool name, input parameters) |
|
|
24
|
+
| `PostCompact` | Listener | async | Writes context compaction signal (summary, trigger type) |
|
|
25
|
+
|
|
26
|
+
**Sync** hooks block Claude until the script completes (Claude waits for the response).
|
|
27
|
+
**Async** hooks run in the background (Claude continues immediately).
|
|
28
|
+
|
|
29
|
+
## Normal mode flow
|
|
30
|
+
|
|
31
|
+
```
|
|
32
|
+
UserPromptSubmit
|
|
33
|
+
→ Record session start time in state file
|
|
34
|
+
→ Send webhook (if configured)
|
|
35
|
+
|
|
36
|
+
Stop / StopFailure / Notification
|
|
37
|
+
→ Check elapsed time since session start
|
|
38
|
+
→ Skip if duration < notifyAfterSeconds (default 15s)
|
|
39
|
+
→ Build notification text (project, branch, duration, last message)
|
|
40
|
+
→ Send: Telegram, desktop toast, sound, voice, webhook
|
|
41
|
+
→ Clean up old Telegram messages (deleteAfterHours)
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Listener-only mode flow
|
|
45
|
+
|
|
46
|
+
```
|
|
47
|
+
PermissionRequest
|
|
48
|
+
→ Output auto-approve JSON to stdout:
|
|
49
|
+
{ hookSpecificOutput: { hookEventName: "PermissionRequest",
|
|
50
|
+
decision: { behavior: "allow" } } }
|
|
51
|
+
|
|
52
|
+
Stop
|
|
53
|
+
→ Write ~/.claude/pty-signals/{sessionId}.json
|
|
54
|
+
{ sessionId, cwd, lastAssistantMessage, cost, numTurns, durationMs }
|
|
55
|
+
|
|
56
|
+
StopFailure
|
|
57
|
+
→ Write ~/.claude/pty-signals/err_{sessionId}.json
|
|
58
|
+
{ type: "error", cwd, error, errorDetails, lastAssistantMessage }
|
|
59
|
+
|
|
60
|
+
SessionStart
|
|
61
|
+
→ Write ~/.claude/pty-signals/rdy_{sessionId}.json
|
|
62
|
+
{ type: "ready", cwd, model, source }
|
|
63
|
+
|
|
64
|
+
PostToolUse
|
|
65
|
+
→ Write ~/.claude/pty-signals/act_{sessionId}.json (overwritten each call)
|
|
66
|
+
{ type: "activity", cwd, toolName, toolInput }
|
|
67
|
+
|
|
68
|
+
PostCompact
|
|
69
|
+
→ Write ~/.claude/pty-signals/cmp_{sessionId}.json
|
|
70
|
+
{ type: "compact", cwd, summary, trigger }
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Configuration
|
|
74
|
+
|
|
75
|
+
See the main [README](../README.md) for configuration options.
|
|
76
|
+
|
|
77
|
+
Key settings affecting notifier behavior:
|
|
78
|
+
- `notifyAfterSeconds` (default: 15) — minimum task duration to trigger notifications
|
|
79
|
+
- `notifyOnWaiting` (default: false) — send notifications for idle/waiting events
|
|
80
|
+
- `telegram.includeLastCcMessageInTelegram` (default: true) — include Claude's last message in Telegram notification
|
|
81
|
+
- `telegram.deleteAfterHours` (default: 24) — auto-delete old notification messages
|
|
82
|
+
- `debug` (default: false) — include hook event JSON and trigger type in notifications
|
package/notifier/notifier.js
CHANGED
|
@@ -158,26 +158,73 @@ function isNotifierDisabled () {
|
|
|
158
158
|
return false;
|
|
159
159
|
}
|
|
160
160
|
|
|
161
|
-
function
|
|
162
|
-
const sessionId = event.session_id || 'unknown';
|
|
163
|
-
const cwd = event.cwd || process.cwd();
|
|
161
|
+
function writeSignalFile (name, data) {
|
|
164
162
|
try {
|
|
165
163
|
fs.mkdirSync(PTY_SIGNAL_DIR, { recursive: true });
|
|
166
|
-
|
|
167
|
-
fs.writeFileSync(signalFile, JSON.stringify({
|
|
168
|
-
sessionId,
|
|
169
|
-
cwd,
|
|
170
|
-
lastAssistantMessage: event.last_assistant_message || '',
|
|
171
|
-
cost: event.total_cost_usd || 0,
|
|
172
|
-
numTurns: event.num_turns || 0,
|
|
173
|
-
durationMs: event.duration_ms || 0,
|
|
174
|
-
timestamp: Date.now(),
|
|
175
|
-
}));
|
|
164
|
+
fs.writeFileSync(path.join(PTY_SIGNAL_DIR, name), JSON.stringify(data));
|
|
176
165
|
} catch {
|
|
177
166
|
// silent fail
|
|
178
167
|
}
|
|
179
168
|
}
|
|
180
169
|
|
|
170
|
+
function writePtySignalFile (event) {
|
|
171
|
+
const sessionId = event.session_id || 'unknown';
|
|
172
|
+
writeSignalFile(`${sessionId}.json`, {
|
|
173
|
+
sessionId,
|
|
174
|
+
cwd: event.cwd || process.cwd(),
|
|
175
|
+
lastAssistantMessage: event.last_assistant_message || '',
|
|
176
|
+
cost: event.total_cost_usd || 0,
|
|
177
|
+
numTurns: event.num_turns || 0,
|
|
178
|
+
durationMs: event.duration_ms || 0,
|
|
179
|
+
timestamp: Date.now(),
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function writeErrorSignalFile (event) {
|
|
184
|
+
const sessionId = event.session_id || 'unknown';
|
|
185
|
+
writeSignalFile(`err_${sessionId}.json`, {
|
|
186
|
+
type: 'error',
|
|
187
|
+
cwd: event.cwd || process.cwd(),
|
|
188
|
+
error: event.error || 'unknown',
|
|
189
|
+
errorDetails: event.error_details || '',
|
|
190
|
+
lastAssistantMessage: event.last_assistant_message || '',
|
|
191
|
+
timestamp: Date.now(),
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function writeReadySignalFile (event) {
|
|
196
|
+
const sessionId = event.session_id || 'unknown';
|
|
197
|
+
writeSignalFile(`rdy_${sessionId}.json`, {
|
|
198
|
+
type: 'ready',
|
|
199
|
+
cwd: event.cwd || process.cwd(),
|
|
200
|
+
model: event.model || '',
|
|
201
|
+
source: event.source || '',
|
|
202
|
+
timestamp: Date.now(),
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function writeActivitySignalFile (event) {
|
|
207
|
+
const sessionId = event.session_id || 'unknown';
|
|
208
|
+
writeSignalFile(`act_${sessionId}.json`, {
|
|
209
|
+
type: 'activity',
|
|
210
|
+
cwd: event.cwd || process.cwd(),
|
|
211
|
+
toolName: event.tool_name || '',
|
|
212
|
+
toolInput: event.tool_input || {},
|
|
213
|
+
timestamp: Date.now(),
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function writeCompactSignalFile (event) {
|
|
218
|
+
const sessionId = event.session_id || 'unknown';
|
|
219
|
+
writeSignalFile(`cmp_${sessionId}.json`, {
|
|
220
|
+
type: 'compact',
|
|
221
|
+
cwd: event.cwd || process.cwd(),
|
|
222
|
+
summary: event.compact_summary || '',
|
|
223
|
+
trigger: event.trigger || '',
|
|
224
|
+
timestamp: Date.now(),
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
|
|
181
228
|
// ----------------------
|
|
182
229
|
// STATE FILE
|
|
183
230
|
// ----------------------
|
|
@@ -659,10 +706,32 @@ process.stdin.on('end', async () => {
|
|
|
659
706
|
process.exit(0);
|
|
660
707
|
}
|
|
661
708
|
|
|
662
|
-
// For listener-only mode:
|
|
709
|
+
// For listener-only mode: handle events via signal files, then exit
|
|
663
710
|
if (disabled === 'listener-only') {
|
|
664
|
-
|
|
665
|
-
|
|
711
|
+
switch (eventType) {
|
|
712
|
+
case 'PermissionRequest':
|
|
713
|
+
process.stdout.write(JSON.stringify({
|
|
714
|
+
hookSpecificOutput: {
|
|
715
|
+
hookEventName: 'PermissionRequest',
|
|
716
|
+
decision: { behavior: 'allow' },
|
|
717
|
+
},
|
|
718
|
+
}));
|
|
719
|
+
break;
|
|
720
|
+
case 'Stop':
|
|
721
|
+
writePtySignalFile(event);
|
|
722
|
+
break;
|
|
723
|
+
case 'StopFailure':
|
|
724
|
+
writeErrorSignalFile(event);
|
|
725
|
+
break;
|
|
726
|
+
case 'SessionStart':
|
|
727
|
+
writeReadySignalFile(event);
|
|
728
|
+
break;
|
|
729
|
+
case 'PostToolUse':
|
|
730
|
+
writeActivitySignalFile(event);
|
|
731
|
+
break;
|
|
732
|
+
case 'PostCompact':
|
|
733
|
+
writeCompactSignalFile(event);
|
|
734
|
+
break;
|
|
666
735
|
}
|
|
667
736
|
process.exit(0);
|
|
668
737
|
}
|
|
@@ -691,7 +760,7 @@ process.stdin.on('end', async () => {
|
|
|
691
760
|
// STOP / NOTIFICATION EVENT
|
|
692
761
|
// ----------------------
|
|
693
762
|
|
|
694
|
-
if (eventType !== 'Stop' && eventType !== 'Notification') {
|
|
763
|
+
if (eventType !== 'Stop' && eventType !== 'Notification' && eventType !== 'StopFailure') {
|
|
695
764
|
process.exit(0);
|
|
696
765
|
}
|
|
697
766
|
|
|
@@ -709,8 +778,8 @@ process.stdin.on('end', async () => {
|
|
|
709
778
|
process.exit(0);
|
|
710
779
|
}
|
|
711
780
|
|
|
712
|
-
const statusEmoji = eventType === 'Notification' ? '⏸' : '✅';
|
|
713
|
-
const desktopStatus = eventType === 'Notification' ? 'Waiting' : 'Finished';
|
|
781
|
+
const statusEmoji = eventType === 'Notification' ? '⏸' : eventType === 'StopFailure' ? '❌' : '✅';
|
|
782
|
+
const desktopStatus = eventType === 'Notification' ? 'Waiting' : eventType === 'StopFailure' ? `Error: ${event.error || 'unknown'}` : 'Finished';
|
|
714
783
|
|
|
715
784
|
const branch = getBranch(cwd);
|
|
716
785
|
let label = `/${project}`;
|
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.55",
|
|
5
5
|
"description": "Claude Code task-completion notifications: Telegram, desktop notifications (Windows/macOS/Linux), sound, and voice",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"engines": {
|