omni-notify-mcp 1.3.14 → 1.3.21

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.
@@ -1,48 +0,0 @@
1
- import notifier from "node-notifier";
2
- import { spawn } from "child_process";
3
- import { tmpdir } from "os";
4
- import { join } from "path";
5
- const DEFAULT_TTS_VOICE = "en-US-AndrewMultilingualNeural";
6
- export async function sendDesktop(config, message) {
7
- if (!config.enabled)
8
- return;
9
- const wantSound = config.sound !== false;
10
- // On Windows, SnoreToast's per-app sound is often muted in Windows settings.
11
- // Fire a PowerShell beep alongside the toast so audio is reliable.
12
- if (wantSound && process.platform === "win32") {
13
- spawn("powershell", [
14
- "-NoProfile", "-Command",
15
- "Add-Type -AssemblyName System.Windows.Forms; [System.Media.SystemSounds]::Asterisk.Play(); Start-Sleep -Milliseconds 600",
16
- ], { windowsHide: true, stdio: "ignore" });
17
- }
18
- if (config.tts) {
19
- // Fire-and-forget: synthesize and play in the background so we don't
20
- // block the MCP request on network + audio playback.
21
- speak(message, config.ttsVoice ?? DEFAULT_TTS_VOICE).catch(() => { });
22
- }
23
- await new Promise((resolve, reject) => {
24
- notifier.notify({ title: "Claude", message, sound: wantSound && process.platform !== "win32" }, (err) => err ? reject(err) : resolve());
25
- });
26
- }
27
- export async function speak(text, voice = DEFAULT_TTS_VOICE) {
28
- // Lazy import so boot doesn't pay for msedge-tts when TTS is off.
29
- const mod = await import("msedge-tts");
30
- const { MsEdgeTTS, OUTPUT_FORMAT } = mod;
31
- const tts = new MsEdgeTTS();
32
- await tts.setMetadata(voice, OUTPUT_FORMAT.AUDIO_24KHZ_48KBITRATE_MONO_MP3);
33
- const { mkdtempSync } = await import("fs");
34
- const outDir = mkdtempSync(join(tmpdir(), "notify-tts-"));
35
- const { audioFilePath } = await tts.toFile(outDir, text);
36
- if (process.platform === "win32") {
37
- spawn("powershell", [
38
- "-NoProfile", "-Command",
39
- `Add-Type -AssemblyName presentationCore; $p = New-Object System.Windows.Media.MediaPlayer; $done = $false; Register-ObjectEvent $p MediaEnded -Action { $script:done = $true } | Out-Null; $p.Open([uri]'${audioFilePath.replace(/\\/g, "\\\\")}'); $p.Play(); while (-not $done) { Start-Sleep -Milliseconds 200 }`,
40
- ], { windowsHide: true, stdio: "ignore" });
41
- }
42
- else if (process.platform === "darwin") {
43
- spawn("afplay", [audioFilePath], { stdio: "ignore" });
44
- }
45
- else {
46
- spawn("aplay", [audioFilePath], { stdio: "ignore" });
47
- }
48
- }
@@ -1,20 +0,0 @@
1
- const COLOR_MAP = { low: 0x6b7280, normal: 0x7c6dfa, high: 0xef4444 };
2
- export async function sendDiscord(config, message, priority = "normal", title = "Claude Notify") {
3
- if (!config.enabled)
4
- return;
5
- const res = await fetch(config.webhookUrl, {
6
- method: "POST",
7
- headers: { "Content-Type": "application/json" },
8
- body: JSON.stringify({
9
- username: config.username ?? "Claude Notify",
10
- embeds: [{
11
- title,
12
- description: message,
13
- color: COLOR_MAP[priority] ?? COLOR_MAP.normal,
14
- timestamp: new Date().toISOString(),
15
- }],
16
- }),
17
- });
18
- if (!res.ok)
19
- throw new Error(`Discord ${res.status}: ${await res.text()}`);
20
- }
@@ -1,29 +0,0 @@
1
- import nodemailer from "nodemailer";
2
- export async function sendEmail(config, message) {
3
- if (!config.enabled)
4
- return;
5
- const transport = config.refreshToken && config.clientId && config.clientSecret
6
- ? nodemailer.createTransport({
7
- service: "gmail",
8
- auth: {
9
- type: "OAuth2",
10
- user: config.connectedEmail ?? config.to,
11
- clientId: config.clientId,
12
- clientSecret: config.clientSecret,
13
- refreshToken: config.refreshToken,
14
- accessToken: config.accessToken,
15
- },
16
- })
17
- : nodemailer.createTransport({
18
- host: config.host,
19
- port: config.port ?? 587,
20
- secure: config.secure ?? false,
21
- auth: { user: config.user, pass: config.pass },
22
- });
23
- await transport.sendMail({
24
- from: config.from ?? config.connectedEmail ?? config.user,
25
- to: config.to,
26
- subject: "Claude notification",
27
- text: message,
28
- });
29
- }
@@ -1,22 +0,0 @@
1
- const PRIORITY_MAP = { low: 2, normal: 3, high: 5 };
2
- export async function sendNtfy(config, message, priority = "normal", title = "Claude Notify") {
3
- if (!config.enabled)
4
- return;
5
- const base = (config.serverUrl ?? "https://ntfy.sh").replace(/\/$/, "");
6
- const safeTitle = encodeURIComponent(title);
7
- const headers = {
8
- "Content-Type": "text/plain; charset=utf-8",
9
- "Title": safeTitle,
10
- "Priority": String(PRIORITY_MAP[priority] ?? 3),
11
- "Tags": priority === "high" ? "rotating_light" : "bell",
12
- };
13
- if (config.token)
14
- headers["Authorization"] = `Bearer ${config.token}`;
15
- const res = await fetch(`${base}/${encodeURIComponent(config.topic)}`, {
16
- method: "POST",
17
- headers,
18
- body: message,
19
- });
20
- if (!res.ok)
21
- throw new Error(`ntfy ${res.status}: ${await res.text()}`);
22
- }
@@ -1,26 +0,0 @@
1
- const PRIORITY_MAP = { low: -1, normal: 0, high: 1 };
2
- export async function sendPushover(config, message, priority = "normal", title = "Claude Notify") {
3
- if (!config.enabled)
4
- return;
5
- const params = new URLSearchParams({
6
- token: config.appToken,
7
- user: config.userKey,
8
- message,
9
- title,
10
- priority: String(PRIORITY_MAP[priority] ?? 0),
11
- sound: config.sound ?? "pushover",
12
- });
13
- if (config.device)
14
- params.set("device", config.device);
15
- // priority=1 (high) bypasses quiet hours on the device but doesn't require
16
- // retry/expire like emergency (2). We deliberately avoid emergency mode to
17
- // prevent infinite retries for agent notifications.
18
- const res = await fetch("https://api.pushover.net/1/messages.json", {
19
- method: "POST",
20
- headers: { "Content-Type": "application/x-www-form-urlencoded" },
21
- body: params.toString(),
22
- });
23
- const json = await res.json();
24
- if (json.status !== 1)
25
- throw new Error(`Pushover error: ${JSON.stringify(json.errors ?? json)}`);
26
- }
@@ -1,36 +0,0 @@
1
- const EMOJI_MAP = { low: "ℹ️", normal: "🔔", high: "🚨" };
2
- export async function sendSlack(config, message, priority = "normal", title = "Claude Notify") {
3
- if (!config.enabled)
4
- return;
5
- const emoji = EMOJI_MAP[priority] ?? EMOJI_MAP.normal;
6
- const blocks = [
7
- { type: "section", text: { type: "mrkdwn", text: `${emoji} *${title}*\n${message}` } },
8
- { type: "context", elements: [{ type: "mrkdwn", text: `Priority: ${priority} · <!date^${Math.floor(Date.now() / 1000)}^{time}|now>` }] },
9
- ];
10
- if (config.botToken && config.channels?.length) {
11
- const errors = [];
12
- let sent = 0;
13
- for (const channel of config.channels) {
14
- const res = await fetch("https://slack.com/api/chat.postMessage", {
15
- method: "POST",
16
- headers: { "Content-Type": "application/json", Authorization: `Bearer ${config.botToken}` },
17
- body: JSON.stringify({ channel, text: `${emoji} *${title}*`, blocks }),
18
- });
19
- const json = await res.json().catch(() => ({}));
20
- if (res.ok && json.ok)
21
- sent++;
22
- else
23
- errors.push(`${channel}: ${json.error ?? res.status}`);
24
- }
25
- if (sent === 0 && errors.length)
26
- throw new Error(`Slack error — ${errors.join("; ")}`);
27
- return;
28
- }
29
- const res = await fetch(config.webhookUrl, {
30
- method: "POST",
31
- headers: { "Content-Type": "application/json" },
32
- body: JSON.stringify({ text: `${emoji} *${title}*`, blocks }),
33
- });
34
- if (!res.ok)
35
- throw new Error(`Slack ${res.status}: ${await res.text()}`);
36
- }
@@ -1,27 +0,0 @@
1
- import { PinpointSMSVoiceV2Client, SendTextMessageCommand } from "@aws-sdk/client-pinpoint-sms-voice-v2";
2
- export async function sendSms(config, message) {
3
- if (!config.enabled)
4
- return;
5
- const client = new PinpointSMSVoiceV2Client({
6
- region: config.region,
7
- credentials: { accessKeyId: config.accessKeyId, secretAccessKey: config.secretAccessKey },
8
- });
9
- const errors = [];
10
- let sent = 0;
11
- const e164 = (s) => String(s ?? "").replace(/[^\d+]/g, "");
12
- for (const to of config.to ?? []) {
13
- try {
14
- await client.send(new SendTextMessageCommand({
15
- DestinationPhoneNumber: e164(to),
16
- OriginationIdentity: e164(config.originationNumber) || undefined,
17
- MessageBody: message,
18
- }));
19
- sent++;
20
- }
21
- catch (err) {
22
- errors.push(`${to}: ${err instanceof Error ? err.message : String(err)}`);
23
- }
24
- }
25
- if (sent === 0 && errors.length)
26
- throw new Error(`SMS error — ${errors.join("; ")}`);
27
- }
@@ -1,43 +0,0 @@
1
- const COLOR_MAP = { low: "Default", normal: "Accent", high: "Attention" };
2
- export async function sendTeams(config, message, priority = "normal", title = "Claude Notify") {
3
- if (!config.enabled)
4
- return;
5
- const res = await fetch(config.webhookUrl, {
6
- method: "POST",
7
- headers: { "Content-Type": "application/json" },
8
- body: JSON.stringify({
9
- type: "message",
10
- attachments: [{
11
- contentType: "application/vnd.microsoft.card.adaptive",
12
- contentUrl: null,
13
- content: {
14
- $schema: "http://adaptivecards.io/schemas/adaptive-card.json",
15
- type: "AdaptiveCard",
16
- version: "1.2",
17
- body: [
18
- {
19
- type: "TextBlock",
20
- size: "Medium",
21
- weight: "Bolder",
22
- text: title,
23
- color: COLOR_MAP[priority] ?? "Default",
24
- },
25
- {
26
- type: "TextBlock",
27
- text: message,
28
- wrap: true,
29
- },
30
- {
31
- type: "TextBlock",
32
- text: `Priority: ${priority} · ${new Date().toLocaleTimeString()}`,
33
- isSubtle: true,
34
- size: "Small",
35
- },
36
- ],
37
- },
38
- }],
39
- }),
40
- });
41
- if (!res.ok)
42
- throw new Error(`Teams ${res.status}: ${await res.text()}`);
43
- }
@@ -1,19 +0,0 @@
1
- export async function sendTelegram(config, message) {
2
- if (!config.enabled)
3
- return;
4
- const errors = [];
5
- let sent = 0;
6
- for (const chatId of config.chatIds ?? []) {
7
- const res = await fetch(`https://api.telegram.org/bot${config.token}/sendMessage`, {
8
- method: "POST",
9
- headers: { "Content-Type": "application/json" },
10
- body: JSON.stringify({ chat_id: chatId, text: message }),
11
- });
12
- if (res.ok)
13
- sent++;
14
- else
15
- errors.push(`${chatId}: ${res.status} ${await res.text()}`);
16
- }
17
- if (sent === 0 && errors.length)
18
- throw new Error(`Telegram error — ${errors.join("; ")}`);
19
- }
@@ -1,11 +0,0 @@
1
- export async function sendWhatsApp(config, message) {
2
- if (!config.enabled)
3
- return;
4
- const res = await fetch(`https://api.green-api.com/waInstance${config.instanceId}/sendMessage/${config.apiToken}`, {
5
- method: "POST",
6
- headers: { "Content-Type": "application/json" },
7
- body: JSON.stringify({ chatId: `${config.phone}@c.us`, message }),
8
- });
9
- if (!res.ok)
10
- throw new Error(`Green API error ${res.status}: ${await res.text()}`);
11
- }
package/dist/config.js DELETED
@@ -1,16 +0,0 @@
1
- import { readFileSync, existsSync } from "fs";
2
- import { homedir } from "os";
3
- import { join } from "path";
4
- const CONFIG_PATHS = [
5
- join(process.cwd(), "config.json"),
6
- join(homedir(), ".notify-mcp", "config.json"),
7
- ];
8
- export function loadConfig() {
9
- for (const path of CONFIG_PATHS) {
10
- if (existsSync(path)) {
11
- const raw = readFileSync(path, "utf-8");
12
- return JSON.parse(raw);
13
- }
14
- }
15
- throw new Error(`No config.json found. Run: npm run ui (then configure at http://localhost:3737)`);
16
- }
@@ -1 +0,0 @@
1
- export {};