claude-notification-plugin 1.1.14 → 1.1.15
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 +18 -18
- package/commit-sha +1 -1
- package/listener/LISTENER-DETAILED.md +77 -77
- package/listener/listener.js +30 -30
- package/listener/message-parser.js +26 -23
- package/listener/task-logger.js +1 -1
- package/listener/worktree-manager.js +1 -1
- 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.15",
|
|
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
|
@@ -195,7 +195,7 @@ Alternatively, add a `listener` section to config manually:
|
|
|
195
195
|
}
|
|
196
196
|
```
|
|
197
197
|
|
|
198
|
-
The `"default"` alias receives messages without a
|
|
198
|
+
The `"default"` alias receives messages without a `/project` prefix.
|
|
199
199
|
`api` and `web` are project aliases for easy reference from Telegram.
|
|
200
200
|
|
|
201
201
|
### 2. Start the listener
|
|
@@ -208,16 +208,16 @@ claude-notify listener start
|
|
|
208
208
|
|
|
209
209
|
```
|
|
210
210
|
fix the login bug → runs in "default" project
|
|
211
|
-
|
|
212
|
-
|
|
211
|
+
/api add pagination to GET /users → runs in "api" project
|
|
212
|
+
/api/feature/auth implement OAuth2 → runs in a worktree (auto-created)
|
|
213
213
|
```
|
|
214
214
|
|
|
215
215
|
The bot replies with status and results:
|
|
216
216
|
|
|
217
217
|
```
|
|
218
|
-
⏳ [
|
|
218
|
+
⏳ [/api] Running: add pagination to GET /users
|
|
219
219
|
...
|
|
220
|
-
✅ [
|
|
220
|
+
✅ [/api] Done: add pagination to GET /users
|
|
221
221
|
<claude's output>
|
|
222
222
|
```
|
|
223
223
|
|
|
@@ -238,15 +238,15 @@ All commands start with `/` and execute instantly (not queued).
|
|
|
238
238
|
| Command | Description |
|
|
239
239
|
|-------------------------------|--------------------------------------|
|
|
240
240
|
| `/status` | Status of all projects and worktrees |
|
|
241
|
-
| `/status
|
|
241
|
+
| `/status /project` | Status of a specific project |
|
|
242
242
|
| `/queue` | Show all queues |
|
|
243
|
-
| `/cancel
|
|
244
|
-
| `/drop
|
|
245
|
-
| `/clear
|
|
243
|
+
| `/cancel /project[/branch]` | Cancel the active task |
|
|
244
|
+
| `/drop /project N` | Remove task N from queue |
|
|
245
|
+
| `/clear /project[/branch]` | Clear queue |
|
|
246
246
|
| `/projects` | List projects and paths |
|
|
247
|
-
| `/worktrees
|
|
248
|
-
| `/worktree
|
|
249
|
-
| `/rmworktree
|
|
247
|
+
| `/worktrees /project` | List worktrees |
|
|
248
|
+
| `/worktree /project/branch` | Create a worktree |
|
|
249
|
+
| `/rmworktree /project/branch` | Remove a worktree |
|
|
250
250
|
| `/history` | Recent task history |
|
|
251
251
|
| `/stop` | Stop the listener |
|
|
252
252
|
| `/help` | Show help |
|
|
@@ -267,15 +267,15 @@ All commands start with `/` and execute instantly (not queued).
|
|
|
267
267
|
### Projects and worktrees
|
|
268
268
|
|
|
269
269
|
**The queue is tied to the working directory, not the project name:**
|
|
270
|
-
-
|
|
271
|
-
-
|
|
270
|
+
- `/api task` and `/api/feature/auth task` → **different queues** (parallel)
|
|
271
|
+
- `/api task1` and `/api task2` → **same queue** (sequential)
|
|
272
272
|
|
|
273
|
-
Worktrees are auto-created when you use
|
|
273
|
+
Worktrees are auto-created when you use `/project/branch` syntax (controlled by `autoCreateWorktree`).
|
|
274
274
|
|
|
275
275
|
```
|
|
276
|
-
/worktree
|
|
277
|
-
/worktrees
|
|
278
|
-
/rmworktree
|
|
276
|
+
/worktree /api/feature/payments ← create
|
|
277
|
+
/worktrees /api ← list
|
|
278
|
+
/rmworktree /api/feature/payments ← remove
|
|
279
279
|
```
|
|
280
280
|
|
|
281
281
|
|
package/commit-sha
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
b092cf465b465f338c3e5ab0e073a8c91aac4d51
|
|
@@ -60,7 +60,7 @@ GET /getUpdates?timeout=30 ────► "Let's wait up to 30 seconds,
|
|
|
60
60
|
connection is open)
|
|
61
61
|
|
|
62
62
|
After 15 sec a user
|
|
63
|
-
wrote "
|
|
63
|
+
wrote "/proj1 fix bug"
|
|
64
64
|
|
|
65
65
|
◄──── {"result": [{"message":...}]} "There's a message, here it is!"
|
|
66
66
|
|
|
@@ -180,7 +180,7 @@ Running two listeners is impossible — the PID file prevents it. And this is im
|
|
|
180
180
|
│ ┌───────┴────────┐ ┌───────┴───────┐ │
|
|
181
181
|
│ │ MessageParser │ │ TaskRunner │ │
|
|
182
182
|
│ │ │ │ │ │
|
|
183
|
-
│ │
|
|
183
|
+
│ │ /proj/branch │ │ spawn claude │ │
|
|
184
184
|
│ │ /commands │ │ timeouts │ │
|
|
185
185
|
│ └────────────────┘ │ kill │ │
|
|
186
186
|
│ └───────────────┘ │
|
|
@@ -197,10 +197,10 @@ Running two listeners is impossible — the PID file prevents it. And this is im
|
|
|
197
197
|
| Module | File | Description |
|
|
198
198
|
|---|---|---|
|
|
199
199
|
| **TelegramPoller** | `telegram-poller.js` | Long polling to the Telegram API. Receives messages, sends replies. Splits long messages into chunks |
|
|
200
|
-
| **MessageParser** | `message-parser.js` | Parses message text: is it a command (`/status`) or a task (
|
|
200
|
+
| **MessageParser** | `message-parser.js` | Parses message text: is it a command (`/status`) or a task (`/proj1 fix bug`)? Extracts project, branch, task text |
|
|
201
201
|
| **WorkQueue** | `work-queue.js` | Manages task queues. Each working directory has a separate FIFO queue. Guarantees: one `claude` process per directory. Persists state to disk |
|
|
202
202
|
| **TaskRunner** | `task-runner.js` | Runs `claude -p "task"` as a child process. Monitors timeouts. Can kill the process on cancellation. Emits events: complete, error, timeout |
|
|
203
|
-
| **WorktreeManager** | `worktree-manager.js` | Creates and removes git worktrees. Auto-discovery via `git worktree list`. Maps
|
|
203
|
+
| **WorktreeManager** | `worktree-manager.js` | Creates and removes git worktrees. Auto-discovery via `git worktree list`. Maps `/project/branch` to a path on disk |
|
|
204
204
|
| **Logger** | `logger.js` | Writes operational log to `~/.claude/.cc-n-listener.log`. Rotation when exceeding 5 MB (old file → `.log.old`) |
|
|
205
205
|
| **TaskLogger** | `task-logger.js` | Writes task Q&A logs (questions to Claude and answers). Separate file per project/branch. Rotation at 5 MB |
|
|
206
206
|
|
|
@@ -227,12 +227,12 @@ MessageParser.parse(text)
|
|
|
227
227
|
│
|
|
228
228
|
└─ Otherwise ──► Task
|
|
229
229
|
│
|
|
230
|
-
├─ "
|
|
230
|
+
├─ "/proj1/feature/auth fix bug"
|
|
231
231
|
│ → project = "proj1"
|
|
232
232
|
│ → branch = "feature/auth"
|
|
233
233
|
│ → text = "fix bug"
|
|
234
234
|
│
|
|
235
|
-
├─ "
|
|
235
|
+
├─ "/proj1 fix bug"
|
|
236
236
|
│ → project = "proj1"
|
|
237
237
|
│ → branch = null (main)
|
|
238
238
|
│ → text = "fix bug"
|
|
@@ -357,9 +357,9 @@ Each project is an alias (short name) + path to a directory on disk:
|
|
|
357
357
|
}
|
|
358
358
|
```
|
|
359
359
|
|
|
360
|
-
Now in Telegram you can write
|
|
360
|
+
Now in Telegram you can write `/api refactor the code`, and Claude will run in the `/home/user/projects/api-server` directory.
|
|
361
361
|
|
|
362
|
-
The **`default`** alias is special. Messages without
|
|
362
|
+
The **`default`** alias is special. Messages without `/project` prefix go to it:
|
|
363
363
|
|
|
364
364
|
```json
|
|
365
365
|
{
|
|
@@ -378,9 +378,9 @@ The **`default`** alias is special. Messages without `@project` go to it:
|
|
|
378
378
|
In the Telegram chat with the bot:
|
|
379
379
|
|
|
380
380
|
```
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
task without
|
|
381
|
+
/project task ← task in the main worktree of the project
|
|
382
|
+
/project/branch task ← task in the worktree of a specific branch
|
|
383
|
+
task without /project prefix ← task in the "default" project
|
|
384
384
|
```
|
|
385
385
|
|
|
386
386
|
### Examples
|
|
@@ -391,25 +391,25 @@ add a README to the project
|
|
|
391
391
|
→ runs in the `default` project (if configured)
|
|
392
392
|
|
|
393
393
|
```
|
|
394
|
-
|
|
394
|
+
/api fix the authentication bug
|
|
395
395
|
```
|
|
396
396
|
→ runs in `/home/user/projects/api-server`
|
|
397
397
|
|
|
398
398
|
```
|
|
399
|
-
|
|
399
|
+
/api/feature/payments add Stripe integration
|
|
400
400
|
```
|
|
401
401
|
→ runs in the `feature/payments` worktree of the `api` project.
|
|
402
402
|
If the worktree doesn't exist, it will be created automatically.
|
|
403
403
|
|
|
404
404
|
```
|
|
405
|
-
|
|
405
|
+
/web update dependencies
|
|
406
406
|
```
|
|
407
407
|
→ runs in `/home/user/projects/web-app`
|
|
408
408
|
|
|
409
409
|
### What happens when a task is sent
|
|
410
410
|
|
|
411
411
|
1. The Listener receives the message from Telegram
|
|
412
|
-
2. Parses
|
|
412
|
+
2. Parses `/project/branch` from the beginning of the message
|
|
413
413
|
3. Determines the working directory (workDir)
|
|
414
414
|
4. Checks: is this workDir busy with another task?
|
|
415
415
|
- **No** → runs `claude -p "task"` immediately, replies with `⏳ Running...`
|
|
@@ -445,8 +445,8 @@ api-server/ ← main worktree, branch main
|
|
|
445
445
|
**The queue is tied to the working directory, not to the project name.**
|
|
446
446
|
|
|
447
447
|
This means:
|
|
448
|
-
-
|
|
449
|
-
-
|
|
448
|
+
- `/api task` and `/api/feature/auth task` are **different queues**, because they're different directories. They run **in parallel**.
|
|
449
|
+
- `/api task1` and `/api task2` are **the same queue** (both go to the main worktree). `task2` will wait for `task1` to complete.
|
|
450
450
|
|
|
451
451
|
```
|
|
452
452
|
Project "api"
|
|
@@ -466,7 +466,7 @@ Within each — strictly one task at a time.
|
|
|
466
466
|
|
|
467
467
|
### Auto-creation of worktrees
|
|
468
468
|
|
|
469
|
-
When you write
|
|
469
|
+
When you write `/api/feature/new task`, and a worktree for the `feature/new` branch doesn't exist:
|
|
470
470
|
|
|
471
471
|
1. The Listener checks: does the `feature/new` branch exist in git?
|
|
472
472
|
- Yes → `git worktree add ~/.claude/worktrees/api/feature-new feature/new`
|
|
@@ -484,9 +484,9 @@ On startup, the listener scans each project with `git worktree list` and picks u
|
|
|
484
484
|
### Manual worktree management from Telegram
|
|
485
485
|
|
|
486
486
|
```
|
|
487
|
-
/worktree
|
|
488
|
-
/worktrees
|
|
489
|
-
/rmworktree
|
|
487
|
+
/worktree /api/feature/payments ← create a worktree
|
|
488
|
+
/worktrees /api ← list all worktrees for a project
|
|
489
|
+
/rmworktree /api/feature/payments ← remove a worktree
|
|
490
490
|
```
|
|
491
491
|
|
|
492
492
|
---
|
|
@@ -504,41 +504,41 @@ While `active !== null`, all new tasks for this workDir go into the `queue`.
|
|
|
504
504
|
### Example: 4 tasks, 2 projects
|
|
505
505
|
|
|
506
506
|
```
|
|
507
|
-
10:00 You:
|
|
508
|
-
Bot: ⏳ [
|
|
507
|
+
10:00 You: /api fix the router bug
|
|
508
|
+
Bot: ⏳ [/api] Running: fix the router bug
|
|
509
509
|
(api/main: active = "fix the router bug", queue = [])
|
|
510
510
|
|
|
511
|
-
10:01 You:
|
|
512
|
-
Bot: ⏳ [
|
|
511
|
+
10:01 You: /web update dependencies
|
|
512
|
+
Bot: ⏳ [/web] Running: update dependencies
|
|
513
513
|
(web/main: active = "update dependencies", queue = [])
|
|
514
514
|
(api and web are running in parallel!)
|
|
515
515
|
|
|
516
|
-
10:02 You:
|
|
517
|
-
Bot: 📋 [
|
|
516
|
+
10:02 You: /api add tests
|
|
517
|
+
Bot: 📋 [/api] Queued (position 1).
|
|
518
518
|
Currently running: fix the router bug
|
|
519
519
|
(api/main: active = "fix the router bug", queue = ["add tests"])
|
|
520
520
|
|
|
521
|
-
10:03 You:
|
|
522
|
-
Bot: 📋 [
|
|
521
|
+
10:03 You: /api refactor the code
|
|
522
|
+
Bot: 📋 [/api] Queued (position 2).
|
|
523
523
|
Currently running: fix the router bug
|
|
524
524
|
(api/main: active = "fix the router bug", queue = ["add tests", "refactor"])
|
|
525
525
|
|
|
526
|
-
10:05 Bot: ✅ [
|
|
526
|
+
10:05 Bot: ✅ [/web] Done: update dependencies
|
|
527
527
|
<result>
|
|
528
528
|
(web/main: active = null, queue = [])
|
|
529
529
|
|
|
530
|
-
10:08 Bot: ✅ [
|
|
530
|
+
10:08 Bot: ✅ [/api] Done: fix the router bug
|
|
531
531
|
<result>
|
|
532
|
-
Bot: ⏳ [
|
|
532
|
+
Bot: ⏳ [/api] Running: add tests
|
|
533
533
|
(api/main: active = "add tests", queue = ["refactor"])
|
|
534
534
|
(next task started automatically!)
|
|
535
535
|
|
|
536
|
-
10:15 Bot: ✅ [
|
|
536
|
+
10:15 Bot: ✅ [/api] Done: add tests
|
|
537
537
|
<result>
|
|
538
|
-
Bot: ⏳ [
|
|
538
|
+
Bot: ⏳ [/api] Running: refactor the code
|
|
539
539
|
(api/main: active = "refactor", queue = [])
|
|
540
540
|
|
|
541
|
-
10:25 Bot: ✅ [
|
|
541
|
+
10:25 Bot: ✅ [/api] Done: refactor the code
|
|
542
542
|
<result>
|
|
543
543
|
(api/main: active = null, queue = [])
|
|
544
544
|
(all tasks completed)
|
|
@@ -555,7 +555,7 @@ While `active !== null`, all new tasks for this workDir go into the `queue`.
|
|
|
555
555
|
If a task runs longer than 30 minutes (configurable: `taskTimeoutMinutes`), it is forcefully stopped:
|
|
556
556
|
|
|
557
557
|
```
|
|
558
|
-
Bot: ⏰ [
|
|
558
|
+
Bot: ⏰ [/api] Task forcefully stopped — timeout exceeded (30 min): refactor the code
|
|
559
559
|
```
|
|
560
560
|
|
|
561
561
|
After a timeout, the next task from the queue starts automatically.
|
|
@@ -581,7 +581,7 @@ Bot: 📊 Status:
|
|
|
581
581
|
```
|
|
582
582
|
|
|
583
583
|
```
|
|
584
|
-
You: /status
|
|
584
|
+
You: /status /api
|
|
585
585
|
Bot: 📊 Project "api":
|
|
586
586
|
|
|
587
587
|
main:
|
|
@@ -598,7 +598,7 @@ Bot: 📊 Project "api":
|
|
|
598
598
|
You: /queue
|
|
599
599
|
Bot: 📋 Queues:
|
|
600
600
|
|
|
601
|
-
|
|
601
|
+
/api:
|
|
602
602
|
▶ fix the router bug
|
|
603
603
|
1. add tests
|
|
604
604
|
2. refactor the code
|
|
@@ -607,16 +607,16 @@ Bot: 📋 Queues:
|
|
|
607
607
|
### /cancel — stop a running task
|
|
608
608
|
|
|
609
609
|
```
|
|
610
|
-
You: /cancel
|
|
611
|
-
Bot: 🛑 [
|
|
612
|
-
⏳ [
|
|
610
|
+
You: /cancel /api
|
|
611
|
+
Bot: 🛑 [/api] Task cancelled. Starting next.
|
|
612
|
+
⏳ [/api] Running: add tests
|
|
613
613
|
```
|
|
614
614
|
|
|
615
615
|
Cancelling a task in a worktree:
|
|
616
616
|
|
|
617
617
|
```
|
|
618
|
-
You: /cancel
|
|
619
|
-
Bot: 🛑 [
|
|
618
|
+
You: /cancel /api/feature/auth
|
|
619
|
+
Bot: 🛑 [/api/feature/auth] Task cancelled
|
|
620
620
|
```
|
|
621
621
|
|
|
622
622
|
### /drop — remove from queue
|
|
@@ -624,7 +624,7 @@ Bot: 🛑 [@api/feature/auth] Task cancelled
|
|
|
624
624
|
Removes a task that **hasn't started executing yet** (waiting in the queue):
|
|
625
625
|
|
|
626
626
|
```
|
|
627
|
-
You: /drop
|
|
627
|
+
You: /drop /api 2
|
|
628
628
|
Bot: 🗑 Removed from queue: refactor the code
|
|
629
629
|
```
|
|
630
630
|
|
|
@@ -635,8 +635,8 @@ The task number is the position in the queue (starting from 1). You can check nu
|
|
|
635
635
|
Removes all tasks from the queue (the active task continues running):
|
|
636
636
|
|
|
637
637
|
```
|
|
638
|
-
You: /clear
|
|
639
|
-
Bot: 🧹 [
|
|
638
|
+
You: /clear /api
|
|
639
|
+
Bot: 🧹 [/api] Queue cleared (3 tasks)
|
|
640
640
|
```
|
|
641
641
|
|
|
642
642
|
### /projects — list projects
|
|
@@ -646,15 +646,15 @@ You: /projects
|
|
|
646
646
|
Bot: 📂 Projects:
|
|
647
647
|
|
|
648
648
|
@default → /home/user/main-project
|
|
649
|
-
|
|
649
|
+
/api → /home/user/projects/api-server
|
|
650
650
|
/feature/auth → ~/.claude/worktrees/api/feature-auth
|
|
651
|
-
|
|
651
|
+
/web → /home/user/projects/web-app
|
|
652
652
|
```
|
|
653
653
|
|
|
654
654
|
### /worktrees — project worktrees
|
|
655
655
|
|
|
656
656
|
```
|
|
657
|
-
You: /worktrees
|
|
657
|
+
You: /worktrees /api
|
|
658
658
|
Bot: 🌳 Worktrees for project "api":
|
|
659
659
|
• main → /home/user/projects/api-server
|
|
660
660
|
• feature/auth → ~/.claude/worktrees/api/feature-auth
|
|
@@ -664,7 +664,7 @@ Bot: 🌳 Worktrees for project "api":
|
|
|
664
664
|
### /worktree — create a worktree
|
|
665
665
|
|
|
666
666
|
```
|
|
667
|
-
You: /worktree
|
|
667
|
+
You: /worktree /api/feature/payments
|
|
668
668
|
Bot: 🌿 Created worktree for project "api":
|
|
669
669
|
Branch: feature/payments
|
|
670
670
|
Path: ~/.claude/worktrees/api/feature-payments
|
|
@@ -673,7 +673,7 @@ Bot: 🌿 Created worktree for project "api":
|
|
|
673
673
|
### /rmworktree — remove a worktree
|
|
674
674
|
|
|
675
675
|
```
|
|
676
|
-
You: /rmworktree
|
|
676
|
+
You: /rmworktree /api/feature/payments
|
|
677
677
|
Bot: 🗑 Worktree feature/payments removed from project "api"
|
|
678
678
|
```
|
|
679
679
|
|
|
@@ -681,7 +681,7 @@ If a task is running in the worktree, removal will be rejected:
|
|
|
681
681
|
|
|
682
682
|
```
|
|
683
683
|
Bot: ❌ Cannot remove worktree: a task is running in it.
|
|
684
|
-
First /cancel
|
|
684
|
+
First /cancel /api/feature/payments
|
|
685
685
|
```
|
|
686
686
|
|
|
687
687
|
### /history — history
|
|
@@ -690,10 +690,10 @@ Bot: ❌ Cannot remove worktree: a task is running in it.
|
|
|
690
690
|
You: /history
|
|
691
691
|
Bot: 📜 Recent tasks:
|
|
692
692
|
|
|
693
|
-
✅ [
|
|
694
|
-
✅ [
|
|
695
|
-
🛑 [
|
|
696
|
-
✅ [
|
|
693
|
+
✅ [/api] fix the router bug
|
|
694
|
+
✅ [/web] update dependencies
|
|
695
|
+
🛑 [/api/feature/auth] implement OAuth2
|
|
696
|
+
✅ [/api] add tests
|
|
697
697
|
```
|
|
698
698
|
|
|
699
699
|
### /stop — stop the listener
|
|
@@ -720,7 +720,7 @@ Shows a brief reference for all commands.
|
|
|
720
720
|
Telegram message → getUpdates() → parsing
|
|
721
721
|
|
|
722
722
|
2. ROUTING
|
|
723
|
-
"
|
|
723
|
+
"/api/feature/auth task"
|
|
724
724
|
→ project = "api"
|
|
725
725
|
→ branch = "feature/auth"
|
|
726
726
|
→ workDir = ~/.claude/worktrees/api/feature-auth
|
|
@@ -896,7 +896,7 @@ Check:
|
|
|
896
896
|
### Task is stuck
|
|
897
897
|
|
|
898
898
|
```
|
|
899
|
-
/cancel
|
|
899
|
+
/cancel /project
|
|
900
900
|
```
|
|
901
901
|
|
|
902
902
|
Or restart the listener:
|
|
@@ -950,8 +950,8 @@ You (terminal): claude-notify listener start
|
|
|
950
950
|
|
|
951
951
|
=== 10:01 — First task ===
|
|
952
952
|
|
|
953
|
-
You:
|
|
954
|
-
Bot: ⏳ [
|
|
953
|
+
You: /api add endpoint GET /users with pagination
|
|
954
|
+
Bot: ⏳ [/api] Running: add endpoint GET /users with pagination
|
|
955
955
|
|
|
956
956
|
Behind the scenes: process started
|
|
957
957
|
claude -p "add endpoint GET /users with pagination" --output-format text
|
|
@@ -959,23 +959,23 @@ Bot: ⏳ [@api] Running: add endpoint GET /users with pagination
|
|
|
959
959
|
|
|
960
960
|
=== 10:02 — Task to another project (in parallel!) ===
|
|
961
961
|
|
|
962
|
-
You:
|
|
963
|
-
Bot: ⏳ [
|
|
962
|
+
You: /web add a /users page that calls GET /users
|
|
963
|
+
Bot: ⏳ [/web] Running: add a /users page that calls GET /users
|
|
964
964
|
|
|
965
965
|
Now two claude processes are running in parallel:
|
|
966
966
|
one in /home/user/projects/api, another in /home/user/projects/web
|
|
967
967
|
|
|
968
968
|
=== 10:03 — Another task for api (queued) ===
|
|
969
969
|
|
|
970
|
-
You:
|
|
971
|
-
Bot: 📋 [
|
|
970
|
+
You: /api add tests for /users
|
|
971
|
+
Bot: 📋 [/api] Queued (position 1).
|
|
972
972
|
Currently running: add endpoint GET /users with pagination
|
|
973
973
|
|
|
974
974
|
=== 10:04 — Task in a worktree (in parallel with api/main!) ===
|
|
975
975
|
|
|
976
|
-
You:
|
|
976
|
+
You: /api/feature/auth add JWT authorization middleware
|
|
977
977
|
Bot: 🌿 Created worktree feature/auth for project "api"
|
|
978
|
-
⏳ [
|
|
978
|
+
⏳ [/api/feature/auth] Running: add JWT authorization middleware
|
|
979
979
|
|
|
980
980
|
Three claude processes running in parallel:
|
|
981
981
|
1. api/main → GET /users
|
|
@@ -996,7 +996,7 @@ Bot: 📊 Status:
|
|
|
996
996
|
|
|
997
997
|
=== 10:07 — web finished ===
|
|
998
998
|
|
|
999
|
-
Bot: ✅ [
|
|
999
|
+
Bot: ✅ [/web] Done: add a /users page that calls GET /users
|
|
1000
1000
|
|
|
1001
1001
|
Created file src/pages/Users.vue with a user table.
|
|
1002
1002
|
Added route in src/router.js.
|
|
@@ -1004,24 +1004,24 @@ Bot: ✅ [@web] Done: add a /users page that calls GET /users
|
|
|
1004
1004
|
|
|
1005
1005
|
=== 10:09 — api/main finished, automatically starts the next task ===
|
|
1006
1006
|
|
|
1007
|
-
Bot: ✅ [
|
|
1007
|
+
Bot: ✅ [/api] Done: add endpoint GET /users with pagination
|
|
1008
1008
|
|
|
1009
1009
|
Created controller src/controllers/users.js.
|
|
1010
1010
|
Added route GET /users in src/routes.js.
|
|
1011
1011
|
Supports query parameters: page, limit, sort.
|
|
1012
1012
|
|
|
1013
|
-
Bot: ⏳ [
|
|
1013
|
+
Bot: ⏳ [/api] Running: add tests for /users
|
|
1014
1014
|
|
|
1015
1015
|
Next task from the queue started automatically!
|
|
1016
1016
|
|
|
1017
1017
|
=== 10:12 — Cancel a worktree task ===
|
|
1018
1018
|
|
|
1019
|
-
You: /cancel
|
|
1020
|
-
Bot: 🛑 [
|
|
1019
|
+
You: /cancel /api/feature/auth
|
|
1020
|
+
Bot: 🛑 [/api/feature/auth] Task cancelled
|
|
1021
1021
|
|
|
1022
1022
|
=== 10:15 — api/main (tests) finished ===
|
|
1023
1023
|
|
|
1024
|
-
Bot: ✅ [
|
|
1024
|
+
Bot: ✅ [/api] Done: add tests for /users
|
|
1025
1025
|
|
|
1026
1026
|
Created tests/users.test.js.
|
|
1027
1027
|
Covered cases: pagination, sorting, empty result, errors.
|
|
@@ -1031,14 +1031,14 @@ Bot: ✅ [@api] Done: add tests for /users
|
|
|
1031
1031
|
You: /history
|
|
1032
1032
|
Bot: 📜 Recent tasks:
|
|
1033
1033
|
|
|
1034
|
-
✅ [
|
|
1035
|
-
🛑 [
|
|
1036
|
-
✅ [
|
|
1037
|
-
✅ [
|
|
1034
|
+
✅ [/api] add tests for /users
|
|
1035
|
+
🛑 [/api/feature/auth] add JWT authorization middleware
|
|
1036
|
+
✅ [/api] add endpoint GET /users with pagination
|
|
1037
|
+
✅ [/web] add a /users page...
|
|
1038
1038
|
|
|
1039
1039
|
=== 10:17 — Remove unneeded worktree ===
|
|
1040
1040
|
|
|
1041
|
-
You: /rmworktree
|
|
1041
|
+
You: /rmworktree /api/feature/auth
|
|
1042
1042
|
Bot: 🗑 Worktree feature/auth removed from project "api"
|
|
1043
1043
|
|
|
1044
1044
|
=== Evening — Shut down ===
|
package/listener/listener.js
CHANGED
|
@@ -216,9 +216,9 @@ function formatLabel (entry) {
|
|
|
216
216
|
return 'unknown';
|
|
217
217
|
}
|
|
218
218
|
if (entry.branch && entry.branch !== 'main' && entry.branch !== 'master') {
|
|
219
|
-
return
|
|
219
|
+
return `/${entry.project}/${entry.branch}`;
|
|
220
220
|
}
|
|
221
|
-
return
|
|
221
|
+
return `/${entry.project}`;
|
|
222
222
|
}
|
|
223
223
|
|
|
224
224
|
async function startTask (workDir, task) {
|
|
@@ -358,8 +358,8 @@ function handleQueue () {
|
|
|
358
358
|
for (const [project, statuses] of Object.entries(all)) {
|
|
359
359
|
for (const s of statuses) {
|
|
360
360
|
const label = s.branch && s.branch !== 'main' && s.branch !== 'master'
|
|
361
|
-
?
|
|
362
|
-
:
|
|
361
|
+
? `/${project}/${s.branch}`
|
|
362
|
+
: `/${project}`;
|
|
363
363
|
if (s.active || s.queueLength > 0) {
|
|
364
364
|
text += `\n<b>${escapeHtml(label)}</b>:`;
|
|
365
365
|
if (s.active) {
|
|
@@ -392,12 +392,12 @@ async function handleCancel (args) {
|
|
|
392
392
|
}
|
|
393
393
|
|
|
394
394
|
if (!runner.isRunning(workDir)) {
|
|
395
|
-
return `❌ No active task in
|
|
395
|
+
return `❌ No active task in /${escapeHtml(projectAlias)}${branch ? '/' + escapeHtml(branch) : ''}`;
|
|
396
396
|
}
|
|
397
397
|
|
|
398
398
|
runner.cancel(workDir);
|
|
399
399
|
const next = queue.cancelActive(workDir);
|
|
400
|
-
const label = branch ?
|
|
400
|
+
const label = branch ? `/${projectAlias}/${branch}` : `/${projectAlias}`;
|
|
401
401
|
|
|
402
402
|
if (next) {
|
|
403
403
|
startTask(workDir, next);
|
|
@@ -409,7 +409,7 @@ async function handleCancel (args) {
|
|
|
409
409
|
function handleDrop (args) {
|
|
410
410
|
const target = parseTarget(args);
|
|
411
411
|
if (!target) {
|
|
412
|
-
return '❌ Usage: /drop
|
|
412
|
+
return '❌ Usage: /drop /project N';
|
|
413
413
|
}
|
|
414
414
|
const index = parseInt(target.rest, 10);
|
|
415
415
|
if (!index || index < 1) {
|
|
@@ -443,7 +443,7 @@ function handleClear (args) {
|
|
|
443
443
|
}
|
|
444
444
|
|
|
445
445
|
const count = queue.clearQueue(workDir);
|
|
446
|
-
const label = branch ?
|
|
446
|
+
const label = branch ? `/${projectAlias}/${branch}` : `/${projectAlias}`;
|
|
447
447
|
return `🧹 [${escapeHtml(label)}] Queue cleared (${count} tasks)`;
|
|
448
448
|
}
|
|
449
449
|
|
|
@@ -452,7 +452,7 @@ function handleProjects () {
|
|
|
452
452
|
let text = '📂 <b>Projects:</b>\n';
|
|
453
453
|
for (const [alias, proj] of Object.entries(projects)) {
|
|
454
454
|
const projPath = typeof proj === 'string' ? proj : proj.path;
|
|
455
|
-
text += `\n<b
|
|
455
|
+
text += `\n<b>/${escapeHtml(alias)}</b> → <code>${escapeHtml(projPath)}</code>`;
|
|
456
456
|
const worktrees = typeof proj === 'object' ? proj.worktrees : null;
|
|
457
457
|
if (worktrees && Object.keys(worktrees).length > 0) {
|
|
458
458
|
for (const [branch, wtPath] of Object.entries(worktrees)) {
|
|
@@ -466,7 +466,7 @@ function handleProjects () {
|
|
|
466
466
|
function handleWorktrees (args) {
|
|
467
467
|
const target = parseTarget(args);
|
|
468
468
|
if (!target) {
|
|
469
|
-
return '❌ Usage: /worktrees
|
|
469
|
+
return '❌ Usage: /worktrees /project';
|
|
470
470
|
}
|
|
471
471
|
|
|
472
472
|
const result = worktreeManager.listWorktrees(target.project);
|
|
@@ -484,11 +484,11 @@ function handleWorktrees (args) {
|
|
|
484
484
|
|
|
485
485
|
function handleCreateWorktree (args) {
|
|
486
486
|
const target = parseTarget(args);
|
|
487
|
-
if (!target || !target.
|
|
488
|
-
return '❌ Usage: /worktree
|
|
487
|
+
if (!target || !target.branch) {
|
|
488
|
+
return '❌ Usage: /worktree /project/branch';
|
|
489
489
|
}
|
|
490
490
|
|
|
491
|
-
const branch = target.
|
|
491
|
+
const branch = target.branch;
|
|
492
492
|
try {
|
|
493
493
|
const wtDir = worktreeManager.createWorktree(target.project, branch);
|
|
494
494
|
return `🌿 Created worktree for "<b>${escapeHtml(target.project)}</b>":\n`
|
|
@@ -501,11 +501,11 @@ function handleCreateWorktree (args) {
|
|
|
501
501
|
|
|
502
502
|
function handleRemoveWorktree (args) {
|
|
503
503
|
const target = parseTarget(args);
|
|
504
|
-
if (!target || !target.
|
|
505
|
-
return '❌ Usage: /rmworktree
|
|
504
|
+
if (!target || !target.branch) {
|
|
505
|
+
return '❌ Usage: /rmworktree /project/branch';
|
|
506
506
|
}
|
|
507
507
|
|
|
508
|
-
const branch = target.
|
|
508
|
+
const branch = target.branch;
|
|
509
509
|
|
|
510
510
|
// Check if there's an active task in this worktree
|
|
511
511
|
let workDir;
|
|
@@ -517,7 +517,7 @@ function handleRemoveWorktree (args) {
|
|
|
517
517
|
}
|
|
518
518
|
|
|
519
519
|
if (workDir && runner.isRunning(workDir)) {
|
|
520
|
-
return `❌ Cannot remove worktree: task is running. First /cancel
|
|
520
|
+
return `❌ Cannot remove worktree: task is running. First /cancel /${escapeHtml(target.project)}/${escapeHtml(branch)}`;
|
|
521
521
|
}
|
|
522
522
|
|
|
523
523
|
try {
|
|
@@ -536,8 +536,8 @@ function handleHistory () {
|
|
|
536
536
|
let text = '📜 <b>Recent tasks:</b>\n';
|
|
537
537
|
for (const h of history.reverse()) {
|
|
538
538
|
const label = h.branch && h.branch !== 'main' && h.branch !== 'master'
|
|
539
|
-
?
|
|
540
|
-
:
|
|
539
|
+
? `/${h.project}/${h.branch}`
|
|
540
|
+
: `/${h.project}`;
|
|
541
541
|
const status = h.result === 'CANCELLED' ? '🛑' : h.result?.startsWith('ERROR') ? '❌' : '✅';
|
|
542
542
|
text += `\n${status} [${escapeHtml(label)}] ${escapeHtml(h.text)}`;
|
|
543
543
|
}
|
|
@@ -556,22 +556,22 @@ function handleHelp () {
|
|
|
556
556
|
return `<b>📖 Commands:</b>
|
|
557
557
|
|
|
558
558
|
/status — status of all projects
|
|
559
|
-
/status
|
|
559
|
+
/status /project — project status
|
|
560
560
|
/queue — all queues
|
|
561
|
-
/cancel [
|
|
562
|
-
/drop
|
|
563
|
-
/clear
|
|
561
|
+
/cancel [/project[/branch]] — cancel task
|
|
562
|
+
/drop /project N — remove task from queue
|
|
563
|
+
/clear /project[/branch] — clear queue
|
|
564
564
|
/projects — list projects
|
|
565
|
-
/worktrees
|
|
566
|
-
/worktree
|
|
567
|
-
/rmworktree
|
|
565
|
+
/worktrees /project — project worktrees
|
|
566
|
+
/worktree /project/branch — create worktree
|
|
567
|
+
/rmworktree /project/branch — remove worktree
|
|
568
568
|
/history — task history
|
|
569
569
|
/stop — stop listener
|
|
570
570
|
/help — this help
|
|
571
571
|
|
|
572
572
|
<b>Tasks:</b>
|
|
573
|
-
<code
|
|
574
|
-
<code
|
|
573
|
+
<code>/project task</code> — main worktree
|
|
574
|
+
<code>/project/branch task</code> — worktree
|
|
575
575
|
<code>task</code> — default project`;
|
|
576
576
|
}
|
|
577
577
|
|
|
@@ -606,7 +606,7 @@ async function handleTask (parsed, telegramMessageId) {
|
|
|
606
606
|
|
|
607
607
|
if (autoCreated) {
|
|
608
608
|
await poller.sendMessage(`🌿 Created worktree <b>${escapeHtml(parsed.branch)}</b> for "<b>${escapeHtml(parsed.project)}</b>"`);
|
|
609
|
-
logger.info(`Auto-created worktree for task:
|
|
609
|
+
logger.info(`Auto-created worktree for task: /${parsed.project}/${parsed.branch} → ${workDir}`);
|
|
610
610
|
}
|
|
611
611
|
|
|
612
612
|
const result = queue.enqueue(
|
|
@@ -672,7 +672,7 @@ async function mainLoop () {
|
|
|
672
672
|
await poller.sendMessage(response, msg.messageId);
|
|
673
673
|
}
|
|
674
674
|
} else if (parsed.type === 'task') {
|
|
675
|
-
logger.info(`Task for
|
|
675
|
+
logger.info(`Task for /${parsed.project}${parsed.branch ? '/' + parsed.branch : ''}: ${parsed.text}`);
|
|
676
676
|
await handleTask(parsed, msg.messageId);
|
|
677
677
|
}
|
|
678
678
|
}
|
|
@@ -11,9 +11,12 @@ const COMMANDS = [
|
|
|
11
11
|
*
|
|
12
12
|
* Formats:
|
|
13
13
|
* /command args → { type: 'command', cmd, args }
|
|
14
|
-
*
|
|
15
|
-
*
|
|
14
|
+
* /project/branch text → { type: 'task', project, branch, text }
|
|
15
|
+
* /project text → { type: 'task', project, branch: null, text }
|
|
16
16
|
* text → { type: 'task', project: 'default', branch: null, text }
|
|
17
|
+
*
|
|
18
|
+
* If /word is a known command, it's treated as a command.
|
|
19
|
+
* Otherwise /word is treated as a project alias.
|
|
17
20
|
*/
|
|
18
21
|
export function parseMessage (text) {
|
|
19
22
|
if (!text || typeof text !== 'string') {
|
|
@@ -25,10 +28,11 @@ export function parseMessage (text) {
|
|
|
25
28
|
return null;
|
|
26
29
|
}
|
|
27
30
|
|
|
28
|
-
// Check for commands
|
|
29
31
|
if (trimmed.startsWith('/')) {
|
|
30
32
|
const parts = trimmed.split(/\s+/);
|
|
31
33
|
const cmd = parts[0].toLowerCase().replace(/@\w+$/, ''); // strip @botname
|
|
34
|
+
|
|
35
|
+
// Known command
|
|
32
36
|
if (COMMANDS.includes(cmd)) {
|
|
33
37
|
return {
|
|
34
38
|
type: 'command',
|
|
@@ -36,30 +40,29 @@ export function parseMessage (text) {
|
|
|
36
40
|
args: parts.slice(1).join(' '),
|
|
37
41
|
};
|
|
38
42
|
}
|
|
39
|
-
}
|
|
40
43
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
44
|
+
// Not a known command → treat as /project[/branch] task
|
|
45
|
+
const projectMatch = trimmed.match(/^\/(\S+)\s+([\s\S]+)$/);
|
|
46
|
+
if (projectMatch) {
|
|
47
|
+
const target = projectMatch[1];
|
|
48
|
+
const taskText = projectMatch[2].trim();
|
|
46
49
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
+
const slashIndex = target.indexOf('/');
|
|
51
|
+
if (slashIndex > 0) {
|
|
52
|
+
return {
|
|
53
|
+
type: 'task',
|
|
54
|
+
project: target.substring(0, slashIndex),
|
|
55
|
+
branch: target.substring(slashIndex + 1),
|
|
56
|
+
text: taskText,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
50
59
|
return {
|
|
51
60
|
type: 'task',
|
|
52
|
-
project: target
|
|
53
|
-
branch:
|
|
61
|
+
project: target,
|
|
62
|
+
branch: null,
|
|
54
63
|
text: taskText,
|
|
55
64
|
};
|
|
56
65
|
}
|
|
57
|
-
return {
|
|
58
|
-
type: 'task',
|
|
59
|
-
project: target,
|
|
60
|
-
branch: null,
|
|
61
|
-
text: taskText,
|
|
62
|
-
};
|
|
63
66
|
}
|
|
64
67
|
|
|
65
68
|
// Plain text → default project
|
|
@@ -72,14 +75,14 @@ export function parseMessage (text) {
|
|
|
72
75
|
}
|
|
73
76
|
|
|
74
77
|
/**
|
|
75
|
-
* Parse
|
|
76
|
-
* Returns { project, branch } or null.
|
|
78
|
+
* Parse /project or /project/branch from command args.
|
|
79
|
+
* Returns { project, branch, rest } or null.
|
|
77
80
|
*/
|
|
78
81
|
export function parseTarget (args) {
|
|
79
82
|
if (!args) {
|
|
80
83
|
return null;
|
|
81
84
|
}
|
|
82
|
-
const match = args.trim().match(
|
|
85
|
+
const match = args.trim().match(/^\/(\S+)/);
|
|
83
86
|
if (!match) {
|
|
84
87
|
return null;
|
|
85
88
|
}
|
package/listener/task-logger.js
CHANGED
|
@@ -49,7 +49,7 @@ export function createTaskLogger (logDir) {
|
|
|
49
49
|
const ts = new Date().toISOString();
|
|
50
50
|
const entry = `\n${'='.repeat(80)}\n`
|
|
51
51
|
+ `[${ts}] QUESTION\n`
|
|
52
|
-
+ `Project:
|
|
52
|
+
+ `Project: /${project}${branch && branch !== 'main' && branch !== 'master' ? '/' + branch : ''}\n`
|
|
53
53
|
+ `WorkDir: ${workDir}\n`
|
|
54
54
|
+ `Task: ${taskText}\n`;
|
|
55
55
|
fs.appendFileSync(logPath, entry);
|
|
@@ -269,7 +269,7 @@ export class WorktreeManager {
|
|
|
269
269
|
|
|
270
270
|
throw new Error(
|
|
271
271
|
`Worktree "${branch}" not found for project "${projectAlias}". `
|
|
272
|
-
+ `Create it: /worktree
|
|
272
|
+
+ `Create it: /worktree /${projectAlias}/${branch}`
|
|
273
273
|
);
|
|
274
274
|
}
|
|
275
275
|
|
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.15",
|
|
5
5
|
"description": "Claude Code task-completion notifications: Telegram, desktop notifications (Windows/macOS/Linux), sound, and voice",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"engines": {
|