omni-notify-mcp 1.0.1 → 1.1.3
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/LICENSE +21 -0
- package/README.md +143 -61
- package/assets/logo.svg +111 -0
- package/assets/screenshots/help-page.png +0 -0
- package/assets/screenshots/main-ui.png +0 -0
- package/config.example.json +26 -26
- package/dist/channels/desktop.js +11 -6
- package/dist/index.js +0 -0
- package/dist/ui/server.js +409 -45
- package/package.json +71 -62
- package/ui/public/app.js +550 -467
- package/ui/public/assets/logo.svg +111 -0
- package/ui/public/help.css +189 -0
- package/ui/public/help.html +269 -0
- package/ui/public/help.js +30 -0
- package/ui/public/index.html +303 -192
- package/ui/public/style.css +579 -437
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 menih
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,14 +1,47 @@
|
|
|
1
|
-
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="assets/logo.svg" width="128" height="128" alt="omni-notify-mcp">
|
|
3
|
+
</p>
|
|
2
4
|
|
|
3
|
-
|
|
5
|
+
<h1 align="center">omni-notify-mcp</h1>
|
|
4
6
|
|
|
5
|
-
|
|
7
|
+
<p align="center">
|
|
8
|
+
<em>Reach me on any channel. Ask me anything. Get out of my way when I'm busy.</em><br>
|
|
9
|
+
An MCP server that gives AI agents (Claude, Cursor, etc.) a single
|
|
10
|
+
<code>notify</code> / <code>ask</code> interface — desktop, Telegram, SMS, email —
|
|
11
|
+
with two-way replies, idle gating, and Do Not Disturb.
|
|
12
|
+
</p>
|
|
13
|
+
|
|
14
|
+
<p align="center">
|
|
15
|
+
<a href="https://www.npmjs.com/package/omni-notify-mcp"><img src="https://img.shields.io/npm/v/omni-notify-mcp.svg" alt="npm"></a>
|
|
16
|
+
<a href="https://marketplace.visualstudio.com/items?itemName=MeniHillel.omni-notify-mcp"><img src="https://img.shields.io/visual-studio-marketplace/v/MeniHillel.omni-notify-mcp?label=marketplace" alt="VS Code Marketplace"></a>
|
|
17
|
+
<img src="https://img.shields.io/badge/license-MIT-4ea3ff.svg" alt="MIT license">
|
|
18
|
+
<img src="https://img.shields.io/badge/MCP-compatible-4ea3ff.svg" alt="MCP compatible">
|
|
19
|
+
</p>
|
|
20
|
+
|
|
21
|
+
<p align="center">
|
|
22
|
+
<img src="assets/screenshots/main-ui.png" width="900" alt="omni-notify-mcp config UI — channels and policies side by side, with live activity log">
|
|
23
|
+
</p>
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Why
|
|
28
|
+
|
|
29
|
+
You step away from your machine and the AI is still working. **It needs to**:
|
|
30
|
+
|
|
31
|
+
- tell you something important happened (`notify`)
|
|
32
|
+
- ask you a question and wait for your answer (`ask`)
|
|
33
|
+
- check whether you've sent it an unsolicited message (`poll`, or live SSE)
|
|
34
|
+
- **not buzz your phone every 90 seconds** while you're sitting at the keyboard
|
|
35
|
+
|
|
36
|
+
`omni-notify-mcp` is the one MCP server that does all of that, on whatever channels you've configured, with the right level of "shut up" built in.
|
|
37
|
+
|
|
38
|
+
## Quick start
|
|
6
39
|
|
|
7
40
|
```bash
|
|
8
41
|
npx omni-notify-mcp
|
|
9
42
|
```
|
|
10
43
|
|
|
11
|
-
Add to your MCP config (`~/.claude.json`, `.vscode/mcp.json`,
|
|
44
|
+
Add to your MCP config (`~/.claude.json`, `.vscode/mcp.json`, `claude_desktop_config.json`, etc.):
|
|
12
45
|
|
|
13
46
|
```json
|
|
14
47
|
{
|
|
@@ -21,89 +54,138 @@ Add to your MCP config (`~/.claude.json`, `.vscode/mcp.json`, or `claude_desktop
|
|
|
21
54
|
}
|
|
22
55
|
```
|
|
23
56
|
|
|
24
|
-
Then
|
|
57
|
+
Then run the config UI to wire up your channels:
|
|
25
58
|
|
|
26
|
-
|
|
59
|
+
```bash
|
|
60
|
+
npx omni-notify-mcp ui
|
|
61
|
+
```
|
|
27
62
|
|
|
28
|
-
|
|
29
|
-
|
|
63
|
+
Open <http://localhost:3737>, toggle the channels you want, and hit Save. The MCP server picks up changes immediately — no restart.
|
|
64
|
+
|
|
65
|
+
## What the agent gets
|
|
66
|
+
|
|
67
|
+
Six tools, all server-configured (the agent never names a channel):
|
|
68
|
+
|
|
69
|
+
| Tool | What it does |
|
|
70
|
+
|---|---|
|
|
71
|
+
| **`notify`** | Send a message to the user. Priority controls fan-out (see below). |
|
|
72
|
+
| **`ask`** | Send a question and **wait** for the user's reply (Telegram, or web link via email). |
|
|
73
|
+
| **`poll`** | Drain any unsolicited messages the user sent. |
|
|
74
|
+
| **`get_idle_seconds`** | Seconds since last keyboard/mouse input. -1 if unsupported. |
|
|
75
|
+
| **`get_idle_config`** | The server's idle-gating policy `{ enabled, thresholdSeconds }`. |
|
|
76
|
+
| **`get_dnd_status`** | Current DND state `{ active, reason }`. |
|
|
77
|
+
|
|
78
|
+
Priority routing for `notify`:
|
|
30
79
|
|
|
31
80
|
| Priority | Channels |
|
|
32
|
-
|
|
33
|
-
| `low`
|
|
81
|
+
|---|---|
|
|
82
|
+
| `low` | email only |
|
|
34
83
|
| `normal` | desktop + Telegram + email |
|
|
35
|
-
| `high`
|
|
84
|
+
| `high` | desktop + Telegram + SMS + email — **bypasses DND and idle gating** |
|
|
85
|
+
|
|
86
|
+
## Features
|
|
87
|
+
|
|
88
|
+
### Channels
|
|
89
|
+
- **Desktop** — native `node-notifier` (macOS/Windows/Linux). Per-channel **system-sound toggle**.
|
|
90
|
+
- **Telegram** — bidirectional. The bot **replies in-thread** to user messages and acknowledges every inbound message so the user knows it landed.
|
|
91
|
+
- **SMS** — Twilio.
|
|
92
|
+
- **Email** — Gmail App Password (one click) or any SMTP. `ask` over email sends a reply link the user clicks to answer.
|
|
93
|
+
|
|
94
|
+
### Two-way (`ask`)
|
|
95
|
+
The agent calls `ask`, the question goes out on Telegram and email, and the call **blocks until the user replies** (or times out). Reply on Telegram → agent gets the text. Click the email reply link → agent gets the text. No glue code, no polling loop.
|
|
96
|
+
|
|
97
|
+
### Real-time inbox push (SSE)
|
|
98
|
+
Subscribe to `GET /api/inbox/stream` (text/event-stream) to receive unsolicited user messages **the moment they arrive** — no polling. Ideal for an always-on agent that wants to react instantly. Per-session tag filtering supported (`?tag=alphawave`). Falls back gracefully to `poll` for clients without SSE.
|
|
99
|
+
|
|
100
|
+
### Multi-session tagging
|
|
101
|
+
Run multiple agents against the same notify server (e.g. one Claude session in `repo-a`, another in `repo-b`). Each connects with `?tag=<name>` and the user can route a Telegram message to a specific agent by prefixing `@<name>`. Untagged messages broadcast to every session.
|
|
102
|
+
|
|
103
|
+
### Do Not Disturb
|
|
104
|
+
- **Manual toggle** — flip it on, all `priority < high` notifs drop on the floor.
|
|
105
|
+
- **Scheduled quiet hours** — e.g. 22:00 → 08:00, configurable per-day.
|
|
106
|
+
- `priority='high'` always punches through.
|
|
107
|
+
- Agents can pre-flight with `get_dnd_status` to skip the round-trip when DND is on.
|
|
36
108
|
|
|
37
|
-
###
|
|
38
|
-
|
|
109
|
+
### Idle gating (anti-buzz)
|
|
110
|
+
The server publishes a policy `{ enabled, thresholdSeconds }`. Agents are **instructed** (via the MCP `instructions` field, surfaced to every connecting client) to call `get_idle_seconds` first, and **skip** sending a notification if you're actively at the keyboard. They can already see what they'd send. Only fire when you've stepped away. `priority='high'` always fires.
|
|
39
111
|
|
|
40
|
-
|
|
41
|
-
|
|
112
|
+
Cross-platform idle detection: Windows (PowerShell + `GetLastInputInfo`), macOS (`ioreg`), Linux (`xprintidle`).
|
|
113
|
+
|
|
114
|
+
### Web config UI
|
|
115
|
+
One page, dark theme, live activity log streaming over SSE, one-click test buttons per channel, secrets masked at rest. Plus a copy-paste help page that walks any AI client through registration in 30 seconds:
|
|
116
|
+
|
|
117
|
+
<p align="center">
|
|
118
|
+
<img src="assets/screenshots/help-page.png" width="800" alt="Help page — copy-paste snippets for Claude Code, Cursor, VS Code, Claude Desktop, Windsurf, Zed">
|
|
119
|
+
</p>
|
|
120
|
+
|
|
121
|
+
### Activity log
|
|
122
|
+
Every notify, ask, reply, and inbox event is logged with timestamp, direction (`→` `←` `·`), channel, and (color-coded) client/session id. Visible live in the UI; last 500 entries replayed on connect.
|
|
123
|
+
|
|
124
|
+
### Behavioral rules baked in
|
|
125
|
+
The MCP server ships with `instructions` that tell every connecting client:
|
|
126
|
+
1. Pre-flight with `get_idle_seconds` and skip if user is active.
|
|
127
|
+
2. Echo the message in chat too — don't trust the user is checking their phone.
|
|
128
|
+
3. Use channel-agnostic wording ("notif", not "Telegram").
|
|
129
|
+
4. Reply to inbox messages **through `notify`**, not just in chat.
|
|
130
|
+
5. `priority='high'` is for blockers, not noise.
|
|
131
|
+
|
|
132
|
+
This means well-behaved agents get the right behavior automatically — no per-prompt nagging required.
|
|
42
133
|
|
|
43
134
|
## Configuration
|
|
44
135
|
|
|
45
|
-
|
|
136
|
+
Default location: `~/.notify-mcp/config.json`. The web UI manages this file for you, but the schema is straightforward:
|
|
46
137
|
|
|
47
138
|
```json
|
|
48
139
|
{
|
|
49
|
-
"desktop": {
|
|
50
|
-
|
|
51
|
-
},
|
|
52
|
-
"telegram": {
|
|
53
|
-
"enabled": true,
|
|
54
|
-
"token": "YOUR_BOT_TOKEN",
|
|
55
|
-
"chatId": "YOUR_CHAT_ID"
|
|
56
|
-
},
|
|
57
|
-
"whatsapp": {
|
|
58
|
-
"enabled": false,
|
|
59
|
-
"instanceId": "YOUR_GREEN_API_INSTANCE_ID",
|
|
60
|
-
"apiToken": "YOUR_GREEN_API_TOKEN",
|
|
61
|
-
"phone": "+1234567890"
|
|
62
|
-
},
|
|
140
|
+
"desktop": { "enabled": true, "sound": true },
|
|
141
|
+
"telegram": { "enabled": true, "token": "BOT_TOKEN", "chatId": "CHAT_ID" },
|
|
63
142
|
"sms": {
|
|
64
143
|
"enabled": false,
|
|
65
|
-
"accountSid": "
|
|
66
|
-
"authToken": "
|
|
67
|
-
"from": "+
|
|
68
|
-
"to": "+
|
|
144
|
+
"accountSid": "ACxxxx",
|
|
145
|
+
"authToken": "...",
|
|
146
|
+
"from": "+15550000000",
|
|
147
|
+
"to": "+15550000001"
|
|
69
148
|
},
|
|
70
149
|
"email": {
|
|
71
150
|
"enabled": true,
|
|
72
|
-
"host": "smtp.gmail.com",
|
|
73
|
-
"
|
|
74
|
-
"
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
"
|
|
78
|
-
|
|
151
|
+
"host": "smtp.gmail.com", "port": 587, "secure": false,
|
|
152
|
+
"user": "you@gmail.com", "pass": "GMAIL_APP_PASSWORD",
|
|
153
|
+
"to": "you@gmail.com"
|
|
154
|
+
},
|
|
155
|
+
"dnd": {
|
|
156
|
+
"enabled": false,
|
|
157
|
+
"schedule": {
|
|
158
|
+
"enabled": false,
|
|
159
|
+
"quietStart": "22:00", "quietEnd": "08:00",
|
|
160
|
+
"days": [0, 1, 2, 3, 4, 5, 6]
|
|
161
|
+
}
|
|
162
|
+
},
|
|
163
|
+
"idle": { "enabled": true, "thresholdSeconds": 120 }
|
|
79
164
|
}
|
|
80
165
|
```
|
|
81
166
|
|
|
82
|
-
|
|
167
|
+
Disabled channels are silently skipped. Secrets are masked when read back via the API.
|
|
83
168
|
|
|
84
|
-
### Channel
|
|
169
|
+
### Channel setup
|
|
85
170
|
|
|
86
|
-
**Desktop** — works out of the box
|
|
171
|
+
- **Desktop** — works out of the box. Toggle `sound` to mute the system chime.
|
|
172
|
+
- **Telegram** — create a bot via [@BotFather](https://t.me/botfather), then click **Detect** in the UI to auto-fill the chat ID.
|
|
173
|
+
- **SMS** — Twilio account SID + auth token + a Twilio number.
|
|
174
|
+
- **Email** — Gmail [App Password](https://myaccount.google.com/apppasswords) (the UI walks you through it) or any SMTP host/user/pass.
|
|
87
175
|
|
|
88
|
-
|
|
89
|
-
1. Create a bot via [@BotFather](https://t.me/botfather) → get a token
|
|
90
|
-
2. Message your bot, then get your chat ID:
|
|
91
|
-
```
|
|
92
|
-
https://api.telegram.org/bot<TOKEN>/getUpdates
|
|
93
|
-
```
|
|
176
|
+
## Endpoints (for power users)
|
|
94
177
|
|
|
95
|
-
|
|
178
|
+
The UI server (default `:3737`) also exposes:
|
|
96
179
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
Then open http://localhost:3737.
|
|
180
|
+
| Path | Purpose |
|
|
181
|
+
|---|---|
|
|
182
|
+
| `POST /mcp[?tag=<name>]` | StreamableHTTP MCP transport. Optional session tag. |
|
|
183
|
+
| `GET /api/inbox/stream[?tag=<name>]` | SSE push of unsolicited user messages. |
|
|
184
|
+
| `GET /api/logs` | SSE stream of the activity log. |
|
|
185
|
+
| `GET/POST /api/config` | Read/write the config (secrets masked on read). |
|
|
186
|
+
| `POST /api/test/<channel>` | One-shot test send for desktop/telegram/sms/email. |
|
|
187
|
+
| `GET /reply/:token` | Web reply page for `ask` over email. |
|
|
106
188
|
|
|
107
189
|
## License
|
|
108
190
|
|
|
109
|
-
MIT
|
|
191
|
+
MIT — see [LICENSE](LICENSE).
|
package/assets/logo.svg
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" width="256" height="256" role="img" aria-label="omni-notify-mcp">
|
|
3
|
+
<title>omni-notify-mcp</title>
|
|
4
|
+
<defs>
|
|
5
|
+
<!-- Deep slate → bright blue, matches the UI accent -->
|
|
6
|
+
<linearGradient id="bg" x1="0" y1="0" x2="1" y2="1">
|
|
7
|
+
<stop offset="0%" stop-color="#1a3550"/>
|
|
8
|
+
<stop offset="55%" stop-color="#1f5a96"/>
|
|
9
|
+
<stop offset="100%" stop-color="#4ea3ff"/>
|
|
10
|
+
</linearGradient>
|
|
11
|
+
<linearGradient id="bell" x1="0" y1="0" x2="0" y2="1">
|
|
12
|
+
<stop offset="0%" stop-color="#ffffff"/>
|
|
13
|
+
<stop offset="100%" stop-color="#dbe8ff"/>
|
|
14
|
+
</linearGradient>
|
|
15
|
+
<!-- Sweep wedge: bright leading edge fading to transparent -->
|
|
16
|
+
<linearGradient id="sweep" x1="1" y1="0" x2="0" y2="1">
|
|
17
|
+
<stop offset="0%" stop-color="#a7ffb6" stop-opacity="0.95"/>
|
|
18
|
+
<stop offset="55%" stop-color="#34d399" stop-opacity="0.45"/>
|
|
19
|
+
<stop offset="100%" stop-color="#34d399" stop-opacity="0"/>
|
|
20
|
+
</linearGradient>
|
|
21
|
+
<radialGradient id="glow" cx="0.5" cy="0.4" r="0.6">
|
|
22
|
+
<stop offset="0%" stop-color="#ffffff" stop-opacity="0.18"/>
|
|
23
|
+
<stop offset="100%" stop-color="#ffffff" stop-opacity="0"/>
|
|
24
|
+
</radialGradient>
|
|
25
|
+
|
|
26
|
+
<!-- Clip the sweep wedge so it stays inside the outer radar ring -->
|
|
27
|
+
<clipPath id="radarClip">
|
|
28
|
+
<circle cx="128" cy="128" r="118"/>
|
|
29
|
+
</clipPath>
|
|
30
|
+
</defs>
|
|
31
|
+
|
|
32
|
+
<!-- Background fills the entire canvas (was inset 8px before — wasted space) -->
|
|
33
|
+
<rect x="0" y="0" width="256" height="256" rx="48" fill="url(#bg)"/>
|
|
34
|
+
<rect x="0" y="0" width="256" height="256" rx="48" fill="url(#glow)"/>
|
|
35
|
+
|
|
36
|
+
<!-- ── RADAR (full concentric range rings + sweep wedge) ───────────────── -->
|
|
37
|
+
<g>
|
|
38
|
+
<!-- Three full range rings — bigger, stronger -->
|
|
39
|
+
<circle cx="128" cy="128" r="118" fill="none" stroke="#ffffff" stroke-opacity="0.32" stroke-width="3"/>
|
|
40
|
+
<circle cx="128" cy="128" r="86" fill="none" stroke="#ffffff" stroke-opacity="0.42" stroke-width="3"/>
|
|
41
|
+
<circle cx="128" cy="128" r="54" fill="none" stroke="#ffffff" stroke-opacity="0.55" stroke-width="3"/>
|
|
42
|
+
|
|
43
|
+
<!-- Crosshair lines through center, edge-to-edge -->
|
|
44
|
+
<line x1="10" y1="128" x2="246" y2="128" stroke="#ffffff" stroke-opacity="0.22" stroke-width="1.8"/>
|
|
45
|
+
<line x1="128" y1="10" x2="128" y2="246" stroke="#ffffff" stroke-opacity="0.22" stroke-width="1.8"/>
|
|
46
|
+
|
|
47
|
+
<!-- Sweep wedge: 60° pie slice from center, leading edge to upper-right -->
|
|
48
|
+
<g clip-path="url(#radarClip)">
|
|
49
|
+
<path d="M128 128 L128 10 A118 118 0 0 1 230 69 Z"
|
|
50
|
+
fill="url(#sweep)"/>
|
|
51
|
+
</g>
|
|
52
|
+
|
|
53
|
+
<!-- Bright leading edge so the rotation reads -->
|
|
54
|
+
<line x1="128" y1="128" x2="230" y2="69"
|
|
55
|
+
stroke="#a7ffb6" stroke-width="3" stroke-opacity="0.95" stroke-linecap="round"/>
|
|
56
|
+
|
|
57
|
+
<!-- Center "ping" dot -->
|
|
58
|
+
<circle cx="128" cy="128" r="4" fill="#a7ffb6"/>
|
|
59
|
+
</g>
|
|
60
|
+
|
|
61
|
+
<!-- ── BELL (foreground icon, bigger — fills ~50% of the canvas) ──────── -->
|
|
62
|
+
<g>
|
|
63
|
+
<!-- Soft drop shadow -->
|
|
64
|
+
<path d="M128 56
|
|
65
|
+
C 96 56, 80 80, 80 116
|
|
66
|
+
L 80 152
|
|
67
|
+
C 80 162, 72 170, 66 178
|
|
68
|
+
L 190 178
|
|
69
|
+
C 184 170, 176 162, 176 152
|
|
70
|
+
L 176 116
|
|
71
|
+
C 176 80, 160 56, 128 56 Z"
|
|
72
|
+
fill="#0a1426" opacity="0.40" transform="translate(0,4)"/>
|
|
73
|
+
<!-- Bell body -->
|
|
74
|
+
<path d="M128 56
|
|
75
|
+
C 96 56, 80 80, 80 116
|
|
76
|
+
L 80 152
|
|
77
|
+
C 80 162, 72 170, 66 178
|
|
78
|
+
L 190 178
|
|
79
|
+
C 184 170, 176 162, 176 152
|
|
80
|
+
L 176 116
|
|
81
|
+
C 176 80, 160 56, 128 56 Z"
|
|
82
|
+
fill="url(#bell)" stroke="#1a3550" stroke-width="2.5"/>
|
|
83
|
+
<!-- Bell top stud -->
|
|
84
|
+
<circle cx="128" cy="48" r="8" fill="url(#bell)" stroke="#1a3550" stroke-width="2.5"/>
|
|
85
|
+
<!-- Bell clapper -->
|
|
86
|
+
<path d="M118 188
|
|
87
|
+
C 118 200, 138 200, 138 188 Z"
|
|
88
|
+
fill="url(#bell)" stroke="#1a3550" stroke-width="2.5"/>
|
|
89
|
+
</g>
|
|
90
|
+
|
|
91
|
+
<!-- ── CHAT BUBBLE (lower-right, bigger — denotes bidirectional ask/reply) -->
|
|
92
|
+
<g>
|
|
93
|
+
<path d="M158 154
|
|
94
|
+
h 50
|
|
95
|
+
a 16 16 0 0 1 16 16
|
|
96
|
+
v 32
|
|
97
|
+
a 16 16 0 0 1 -16 16
|
|
98
|
+
h -26
|
|
99
|
+
l -16 16
|
|
100
|
+
v -16
|
|
101
|
+
h -8
|
|
102
|
+
a 16 16 0 0 1 -16 -16
|
|
103
|
+
v -32
|
|
104
|
+
a 16 16 0 0 1 16 -16 Z"
|
|
105
|
+
fill="#0a1426" stroke="#ffffff" stroke-width="3.5" stroke-linejoin="round"/>
|
|
106
|
+
<!-- Three bubble dots = "typing / message" -->
|
|
107
|
+
<circle cx="170" cy="186" r="3.8" fill="#ffffff"/>
|
|
108
|
+
<circle cx="186" cy="186" r="3.8" fill="#ffffff"/>
|
|
109
|
+
<circle cx="202" cy="186" r="3.8" fill="#ffffff"/>
|
|
110
|
+
</g>
|
|
111
|
+
</svg>
|
|
Binary file
|
|
Binary file
|
package/config.example.json
CHANGED
|
@@ -1,26 +1,26 @@
|
|
|
1
|
-
{
|
|
2
|
-
"desktop": {
|
|
3
|
-
"enabled": true
|
|
4
|
-
},
|
|
5
|
-
"whatsapp": {
|
|
6
|
-
"enabled": true,
|
|
7
|
-
"phone": "+1234567890",
|
|
8
|
-
"apikey": "YOUR_CALLMEBOT_APIKEY"
|
|
9
|
-
},
|
|
10
|
-
"sms": {
|
|
11
|
-
"enabled": true,
|
|
12
|
-
"accountSid": "YOUR_TWILIO_ACCOUNT_SID",
|
|
13
|
-
"authToken": "YOUR_TWILIO_AUTH_TOKEN",
|
|
14
|
-
"from": "+1YOUR_TWILIO_NUMBER",
|
|
15
|
-
"to": "+1YOUR_PERSONAL_NUMBER"
|
|
16
|
-
},
|
|
17
|
-
"email": {
|
|
18
|
-
"enabled": true,
|
|
19
|
-
"host": "smtp.gmail.com",
|
|
20
|
-
"port": 587,
|
|
21
|
-
"secure": false,
|
|
22
|
-
"user": "your-email@gmail.com",
|
|
23
|
-
"pass": "YOUR_GMAIL_APP_PASSWORD",
|
|
24
|
-
"to": "your-email@gmail.com"
|
|
25
|
-
}
|
|
26
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"desktop": {
|
|
3
|
+
"enabled": true
|
|
4
|
+
},
|
|
5
|
+
"whatsapp": {
|
|
6
|
+
"enabled": true,
|
|
7
|
+
"phone": "+1234567890",
|
|
8
|
+
"apikey": "YOUR_CALLMEBOT_APIKEY"
|
|
9
|
+
},
|
|
10
|
+
"sms": {
|
|
11
|
+
"enabled": true,
|
|
12
|
+
"accountSid": "YOUR_TWILIO_ACCOUNT_SID",
|
|
13
|
+
"authToken": "YOUR_TWILIO_AUTH_TOKEN",
|
|
14
|
+
"from": "+1YOUR_TWILIO_NUMBER",
|
|
15
|
+
"to": "+1YOUR_PERSONAL_NUMBER"
|
|
16
|
+
},
|
|
17
|
+
"email": {
|
|
18
|
+
"enabled": true,
|
|
19
|
+
"host": "smtp.gmail.com",
|
|
20
|
+
"port": 587,
|
|
21
|
+
"secure": false,
|
|
22
|
+
"user": "your-email@gmail.com",
|
|
23
|
+
"pass": "YOUR_GMAIL_APP_PASSWORD",
|
|
24
|
+
"to": "your-email@gmail.com"
|
|
25
|
+
}
|
|
26
|
+
}
|
package/dist/channels/desktop.js
CHANGED
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
import notifier from "node-notifier";
|
|
2
|
+
import { spawn } from "child_process";
|
|
2
3
|
export async function sendDesktop(config, message) {
|
|
3
4
|
if (!config.enabled)
|
|
4
5
|
return;
|
|
6
|
+
const wantSound = config.sound !== false;
|
|
7
|
+
// On Windows, SnoreToast's per-app sound is often muted in Windows settings.
|
|
8
|
+
// Fire a PowerShell beep alongside the toast so audio is reliable.
|
|
9
|
+
if (wantSound && process.platform === "win32") {
|
|
10
|
+
spawn("powershell", [
|
|
11
|
+
"-NoProfile", "-Command",
|
|
12
|
+
"[console]::beep(880,180); Start-Sleep -Milliseconds 60; [console]::beep(660,180)",
|
|
13
|
+
], { windowsHide: true, stdio: "ignore" });
|
|
14
|
+
}
|
|
5
15
|
await new Promise((resolve, reject) => {
|
|
6
|
-
notifier.notify({ title: "Claude", message, sound:
|
|
7
|
-
if (err)
|
|
8
|
-
reject(err);
|
|
9
|
-
else
|
|
10
|
-
resolve();
|
|
11
|
-
});
|
|
16
|
+
notifier.notify({ title: "Claude", message, sound: wantSound && process.platform !== "win32" }, (err) => err ? reject(err) : resolve());
|
|
12
17
|
});
|
|
13
18
|
}
|
package/dist/index.js
CHANGED
|
File without changes
|