omni-notify-mcp 1.1.5 → 1.1.6
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/README.md +1 -1
- package/assets/screenshots/main-ui.png +0 -0
- package/dist/ui/server.js +24 -2
- package/package.json +1 -1
- package/ui/public/app.js +42 -1
- package/ui/public/index.html +3 -0
package/README.md
CHANGED
|
@@ -86,7 +86,7 @@ Priority routing for `notify`:
|
|
|
86
86
|
## Features
|
|
87
87
|
|
|
88
88
|
### Channels
|
|
89
|
-
- **Desktop** — native `node-notifier` (macOS/Windows/Linux). Per-channel **system-sound toggle** and optional **text-to-speech**
|
|
89
|
+
- **Desktop** — native `node-notifier` (macOS/Windows/Linux). Per-channel **system-sound toggle** and optional **text-to-speech** with a voice picker covering 30+ neural voices (US/UK/AU/CA/IN/IE/NZ/…) via `msedge-tts`, no API key.
|
|
90
90
|
- **Telegram** — bidirectional. The bot **replies in-thread** to user messages and acknowledges every inbound message so the user knows it landed.
|
|
91
91
|
- **SMS** — Twilio.
|
|
92
92
|
- **Email** — Gmail App Password (one click) or any SMTP. `ask` over email sends a reply link the user clicks to answer.
|
|
Binary file
|
package/dist/ui/server.js
CHANGED
|
@@ -190,10 +190,12 @@ async function speakText(text, voice) {
|
|
|
190
190
|
spawn("aplay", [audioFilePath], { stdio: "ignore" });
|
|
191
191
|
}
|
|
192
192
|
}
|
|
193
|
-
app.post("/api/test/tts", async (
|
|
193
|
+
app.post("/api/test/tts", async (req, res) => {
|
|
194
194
|
try {
|
|
195
195
|
const cfg = loadConfig();
|
|
196
|
-
const voice =
|
|
196
|
+
const voice = (typeof req.body?.voice === "string" && req.body.voice) ||
|
|
197
|
+
cfg.desktop?.ttsVoice ||
|
|
198
|
+
"en-US-AndrewMultilingualNeural";
|
|
197
199
|
await speakText("Notification from Claude. This is a voice test.", voice);
|
|
198
200
|
res.json({ ok: true, message: `TTS played (${voice})` });
|
|
199
201
|
}
|
|
@@ -201,6 +203,26 @@ app.post("/api/test/tts", async (_req, res) => {
|
|
|
201
203
|
res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
|
|
202
204
|
}
|
|
203
205
|
});
|
|
206
|
+
let voiceCache = null;
|
|
207
|
+
app.get("/api/voices", async (_req, res) => {
|
|
208
|
+
try {
|
|
209
|
+
if (!voiceCache || Date.now() - voiceCache.ts > 24 * 60 * 60 * 1000) {
|
|
210
|
+
const mod = await import("msedge-tts");
|
|
211
|
+
const tts = new mod.MsEdgeTTS();
|
|
212
|
+
const all = await tts.getVoices();
|
|
213
|
+
voiceCache = {
|
|
214
|
+
ts: Date.now(),
|
|
215
|
+
voices: all
|
|
216
|
+
.filter((v) => v.Locale.startsWith("en-") && v.ShortName.includes("Neural"))
|
|
217
|
+
.map((v) => ({ shortName: v.ShortName, gender: v.Gender, locale: v.Locale })),
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
res.json({ voices: voiceCache.voices });
|
|
221
|
+
}
|
|
222
|
+
catch (err) {
|
|
223
|
+
res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
|
|
224
|
+
}
|
|
225
|
+
});
|
|
204
226
|
app.post("/api/test/desktop", (_req, res) => {
|
|
205
227
|
const time = new Date().toLocaleTimeString();
|
|
206
228
|
const cfg = loadConfig();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "omni-notify-mcp",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.6",
|
|
4
4
|
"description": "An MCP server that lets AI agents (Claude, Cursor, etc.) reach you on any channel — desktop, Telegram, SMS, email — with two-way ask/reply, real-time inbox push, Do Not Disturb, idle gating, multi-session routing, and a one-page web UI for setup. Zero config code; configure once, agents call notify/ask.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "module",
|
package/ui/public/app.js
CHANGED
|
@@ -45,6 +45,8 @@ function populateForm() {
|
|
|
45
45
|
$("desktop-enabled").checked = !!config.desktop?.enabled;
|
|
46
46
|
$("desktop-sound").checked = config.desktop?.sound !== false; // default on
|
|
47
47
|
$("desktop-tts").checked = !!config.desktop?.tts; // default off
|
|
48
|
+
updateTtsVoiceRow();
|
|
49
|
+
loadVoices().catch(() => {});
|
|
48
50
|
|
|
49
51
|
// Email / Gmail
|
|
50
52
|
const email = config.email ?? {};
|
|
@@ -151,15 +153,49 @@ function setBadge(channel, type, text) {
|
|
|
151
153
|
// ── Save handlers ─────────────────────────────────────────────────────────
|
|
152
154
|
|
|
153
155
|
function saveDesktop() {
|
|
156
|
+
updateTtsVoiceRow();
|
|
157
|
+
const ttsVoice = $("desktop-tts-voice").value || undefined;
|
|
154
158
|
patch({
|
|
155
159
|
desktop: {
|
|
156
160
|
enabled: $("desktop-enabled").checked,
|
|
157
161
|
sound: $("desktop-sound").checked,
|
|
158
162
|
tts: $("desktop-tts").checked,
|
|
163
|
+
ttsVoice,
|
|
159
164
|
},
|
|
160
165
|
});
|
|
161
166
|
}
|
|
162
167
|
|
|
168
|
+
function updateTtsVoiceRow() {
|
|
169
|
+
const row = $("tts-voice-row");
|
|
170
|
+
row.style.display = $("desktop-tts").checked ? "" : "none";
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
let voicesLoaded = false;
|
|
174
|
+
async function loadVoices() {
|
|
175
|
+
if (voicesLoaded) return;
|
|
176
|
+
const res = await fetch("/api/voices");
|
|
177
|
+
if (!res.ok) return;
|
|
178
|
+
const { voices } = await res.json();
|
|
179
|
+
const sel = $("desktop-tts-voice");
|
|
180
|
+
const current = config.desktop?.ttsVoice || "en-US-AndrewMultilingualNeural";
|
|
181
|
+
const byLocale = {};
|
|
182
|
+
for (const v of voices) (byLocale[v.locale] ??= []).push(v);
|
|
183
|
+
sel.innerHTML = "";
|
|
184
|
+
for (const locale of Object.keys(byLocale).sort()) {
|
|
185
|
+
const og = document.createElement("optgroup");
|
|
186
|
+
og.label = locale;
|
|
187
|
+
for (const v of byLocale[locale].sort((a, b) => a.shortName.localeCompare(b.shortName))) {
|
|
188
|
+
const opt = document.createElement("option");
|
|
189
|
+
opt.value = v.shortName;
|
|
190
|
+
opt.textContent = `${v.shortName.replace(locale + "-", "")} (${v.gender})`;
|
|
191
|
+
if (v.shortName === current) opt.selected = true;
|
|
192
|
+
og.appendChild(opt);
|
|
193
|
+
}
|
|
194
|
+
sel.appendChild(og);
|
|
195
|
+
}
|
|
196
|
+
voicesLoaded = true;
|
|
197
|
+
}
|
|
198
|
+
|
|
163
199
|
async function saveEmail() {
|
|
164
200
|
const to = $("gmail-to-connected").value.trim();
|
|
165
201
|
const enabled = $("email-enabled").checked;
|
|
@@ -402,7 +438,12 @@ async function testSound() {
|
|
|
402
438
|
|
|
403
439
|
async function testTts() {
|
|
404
440
|
try {
|
|
405
|
-
const
|
|
441
|
+
const voice = $("desktop-tts-voice").value || undefined;
|
|
442
|
+
const res = await fetch("/api/test/tts", {
|
|
443
|
+
method: "POST",
|
|
444
|
+
headers: { "Content-Type": "application/json" },
|
|
445
|
+
body: JSON.stringify({ voice }),
|
|
446
|
+
});
|
|
406
447
|
const json = await res.json();
|
|
407
448
|
if (!res.ok) throw new Error(json.error);
|
|
408
449
|
toast(json.message, "ok");
|
package/ui/public/index.html
CHANGED
|
@@ -63,6 +63,9 @@
|
|
|
63
63
|
<span class="toggle-lbl">Speak notification</span>
|
|
64
64
|
<button class="btn btn-sm btn-ghost" onclick="testTts()">Test voice</button>
|
|
65
65
|
</div>
|
|
66
|
+
<div id="tts-voice-row" class="actions" style="margin-top:6px; display:none">
|
|
67
|
+
<select id="desktop-tts-voice" onchange="saveDesktop()" style="flex:1; min-width:0"></select>
|
|
68
|
+
</div>
|
|
66
69
|
<span id="os-hint" class="os-tag hidden"></span>
|
|
67
70
|
</div>
|
|
68
71
|
</div>
|