omni-notify-mcp 1.3.23 → 1.3.24
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/dist/ui/server.js +70 -3
- package/package.json +1 -1
package/dist/ui/server.js
CHANGED
|
@@ -147,6 +147,16 @@ const SECRETS_PATH = process.env.NOTIFY_MCP_SECRETS_PATH
|
|
|
147
147
|
|| join(CONFIG_DIR, "notify-secrets.json");
|
|
148
148
|
// Backward compatibility for an existing per-user install: older builds,
|
|
149
149
|
// `setup-secrets.sh` and the README all placed the file at `~/.notify-mcp-secrets`.
|
|
150
|
+
//
|
|
151
|
+
// #444: that exact filename is ALSO where release.sh/setup-secrets.sh (this
|
|
152
|
+
// repo, and BullseyeShared's shared scripts/vscode-extension/release.sh and
|
|
153
|
+
// publish-ext.sh) now write PUBLISH credentials — shell `KEY=VALUE` lines
|
|
154
|
+
// (NPM_TOKEN=…, VSCE_PAT=…), a different, incompatible format from the JSON
|
|
155
|
+
// this constant reads. Those scripts were moved to `~/.bullseye-release-secrets`
|
|
156
|
+
// on 2026-09-13 specifically to stop colliding with this legacy path. Do NOT
|
|
157
|
+
// widen this constant's name, and do not let release tooling point back at it
|
|
158
|
+
// — see loadSecrets() below, which now names the mismatch instead of throwing
|
|
159
|
+
// a bare parse error when it happens anyway.
|
|
150
160
|
const LEGACY_SECRETS_PATH = join(homedir(), ".notify-mcp-secrets");
|
|
151
161
|
// AWS SendTextMessage requires E.164 (e.g. +14089812202) — strip spaces, dashes,
|
|
152
162
|
// parens, dots so a user-entered "+1 408 981 2202" is accepted.
|
|
@@ -2306,23 +2316,47 @@ async function sendNotificationSummary(message, priority, client) {
|
|
|
2306
2316
|
const out = await sendNotification(message, priority, client);
|
|
2307
2317
|
return out.text;
|
|
2308
2318
|
}
|
|
2319
|
+
// A shell `KEY=VALUE` file — the format release.sh/setup-secrets.sh (this repo
|
|
2320
|
+
// and BullseyeShared's shared release scripts) write — never starts with `{`
|
|
2321
|
+
// and always has at least one bare `NAME=` line. Used only to make the #444
|
|
2322
|
+
// mismatch diagnosable: this is a heuristic, not a parser, and only runs after
|
|
2323
|
+
// JSON.parse already failed.
|
|
2324
|
+
function looksLikeShellSecrets(raw) {
|
|
2325
|
+
const trimmed = raw.trimStart();
|
|
2326
|
+
return !trimmed.startsWith("{") && /^[A-Z_][A-Z0-9_]*=/m.test(raw);
|
|
2327
|
+
}
|
|
2309
2328
|
function loadSecrets() {
|
|
2310
2329
|
const path = secretsFilePath();
|
|
2311
2330
|
if (!path)
|
|
2312
2331
|
return {};
|
|
2332
|
+
let raw;
|
|
2313
2333
|
try {
|
|
2314
|
-
|
|
2334
|
+
raw = readFileSync(path, "utf8");
|
|
2315
2335
|
}
|
|
2316
2336
|
catch (err) {
|
|
2317
2337
|
throw new SecretsUnreadable(path, err);
|
|
2318
2338
|
}
|
|
2339
|
+
try {
|
|
2340
|
+
return decodeB64Fields(JSON.parse(raw));
|
|
2341
|
+
}
|
|
2342
|
+
catch (err) {
|
|
2343
|
+
if (path === LEGACY_SECRETS_PATH && looksLikeShellSecrets(raw)) {
|
|
2344
|
+
throw new SecretsUnreadable(path, err, "this looks like a shell KEY=VALUE file (NPM_TOKEN=…/VSCE_PAT=…), the " +
|
|
2345
|
+
"format release.sh/setup-secrets.sh write — those moved to " +
|
|
2346
|
+
"~/.bullseye-release-secrets on 2026-09-13 (#444) specifically because " +
|
|
2347
|
+
"this path is reserved for JSON notify-channel secrets; move or delete " +
|
|
2348
|
+
"the file at this path rather than editing it");
|
|
2349
|
+
}
|
|
2350
|
+
throw new SecretsUnreadable(path, err);
|
|
2351
|
+
}
|
|
2319
2352
|
}
|
|
2320
2353
|
// A secrets file that EXISTS but cannot be read is a hard defect, not an empty
|
|
2321
2354
|
// result: the caller would otherwise import nothing and believe the user has no
|
|
2322
2355
|
// credentials, which is the silent-escalation failure this file exists to stop.
|
|
2323
2356
|
class SecretsUnreadable extends Error {
|
|
2324
|
-
constructor(path, cause) {
|
|
2325
|
-
|
|
2357
|
+
constructor(path, cause, hint) {
|
|
2358
|
+
const base = `secrets file at ${path} exists but could not be read: ${cause instanceof Error ? cause.message : String(cause)}`;
|
|
2359
|
+
super(hint ? `${base} — ${hint}` : base);
|
|
2326
2360
|
}
|
|
2327
2361
|
}
|
|
2328
2362
|
function secretsFilePath() {
|
|
@@ -2436,6 +2470,36 @@ function importCredsOnStart() {
|
|
|
2436
2470
|
log("·", "import", "secrets file present but every config field it maps to is already set — nothing imported");
|
|
2437
2471
|
}
|
|
2438
2472
|
}
|
|
2473
|
+
// Story #445 (#438's real lesson): a server with no channel configured
|
|
2474
|
+
// answers `/v1/health` and every MCP tool call exactly like a healthy one —
|
|
2475
|
+
// nothing else here would ever say otherwise. That is how a machine ran for
|
|
2476
|
+
// five days delivering nothing while looking fine. Called once at startup,
|
|
2477
|
+
// right after importCredsOnStart() has had its one chance to fix the
|
|
2478
|
+
// problem — loud, impossible to miss in the console, and naming every path
|
|
2479
|
+
// this process actually checked, so the next time this happens it is
|
|
2480
|
+
// diagnosed in the first minute, not five days later by someone measuring
|
|
2481
|
+
// real sends.
|
|
2482
|
+
const CHANNEL_LABELS = [
|
|
2483
|
+
["desktop", "Desktop"], ["telegram", "Telegram"], ["email", "Email"],
|
|
2484
|
+
["ntfy", "ntfy"], ["discord", "Discord"], ["slack", "Slack"],
|
|
2485
|
+
["teams", "Teams"], ["sms", "SMS"],
|
|
2486
|
+
];
|
|
2487
|
+
function warnIfNoChannelConfigured(cfg) {
|
|
2488
|
+
if (CHANNEL_LABELS.some(([key]) => cfg[key]?.enabled === true))
|
|
2489
|
+
return;
|
|
2490
|
+
const bar = "!".repeat(78);
|
|
2491
|
+
const exists = (p) => (existsSync(p) ? "exists" : "MISSING");
|
|
2492
|
+
console.log(`\n${bar}`);
|
|
2493
|
+
console.log(" NO NOTIFICATION CHANNEL IS CONFIGURED — every notify call will reach nobody.");
|
|
2494
|
+
console.log(bar);
|
|
2495
|
+
console.log(` Config file (${exists(CONFIG_PATH)}) : ${CONFIG_PATH}`);
|
|
2496
|
+
console.log(` Secrets file (${exists(SECRETS_PATH)}) : ${SECRETS_PATH}`);
|
|
2497
|
+
console.log(` Legacy path (${exists(LEGACY_SECRETS_PATH)}) : ${LEGACY_SECRETS_PATH}`);
|
|
2498
|
+
console.log(` Secrets actually in use: ${secretsFilePath() ?? "none of the above"}`);
|
|
2499
|
+
console.log(` Fix: open http://localhost:${PORT} and turn a channel on, or set`);
|
|
2500
|
+
console.log(` NOTIFY_MCP_SECRETS_PATH to a notify-secrets.json with real credentials.`);
|
|
2501
|
+
console.log(`${bar}\n`);
|
|
2502
|
+
}
|
|
2439
2503
|
async function slackPost(text) {
|
|
2440
2504
|
const { webhook } = slackCreds();
|
|
2441
2505
|
if (!webhook)
|
|
@@ -3301,6 +3365,9 @@ const httpServer = app.listen(PORT, () => {
|
|
|
3301
3365
|
// config.json cannot serve a process whose secrets file IS populated. It
|
|
3302
3366
|
// reports the channel names it filled — never a secret value.
|
|
3303
3367
|
importCredsOnStart();
|
|
3368
|
+
// Import has now had its one chance to fix a channel-less config — if
|
|
3369
|
+
// nothing is enabled even after that, say so loudly (story #445).
|
|
3370
|
+
warnIfNoChannelConfigured(loadConfig());
|
|
3304
3371
|
// Live Telegram/Slack pollers hit real external channels — never under test.
|
|
3305
3372
|
if (!TEST_ENDPOINTS) {
|
|
3306
3373
|
startTelegramListener();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "omni-notify-mcp",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.24",
|
|
4
4
|
"description": "Reach your AI agents from anywhere — and let them reach you. Every notification channel is set up in one config UI with one-click server launch: desktop, Telegram, Slack, SMS, email, ntfy. Agents then notify you when long work finishes or a decision is needed, ask you a question and wait for your answer, and receive and reply to the messages you send them — with Do Not Disturb and idle detection deciding what actually gets through.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "module",
|