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.
Files changed (2) hide show
  1. package/dist/ui/server.js +70 -3
  2. 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
- return decodeB64Fields(JSON.parse(readFileSync(path, "utf8")));
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
- super(`secrets file at ${path} exists but could not be read: ${cause instanceof Error ? cause.message : String(cause)}`);
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.23",
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",