omni-notify-mcp 1.3.307 → 1.3.309

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.
@@ -172,7 +172,7 @@ export function ownerBoundLaunch(invocation, ownerPid, cwd, guardDir) {
172
172
  }
173
173
  return {
174
174
  cmd: process.execPath,
175
- args: [path.join(guardDir, "ownerGuard.js"), String(ownerPid), encoded(invocation.cmd), encoded(JSON.stringify(invocation.args)), encoded(cwd)],
175
+ args: [path.join(guardDir, "ownerGuard.cjs"), String(ownerPid), encoded(invocation.cmd), encoded(JSON.stringify(invocation.args)), encoded(cwd)],
176
176
  };
177
177
  }
178
178
  // GATE: how long a CLI tree gets to exit on SIGTERM before it is SIGKILLed — POSIX only; Windows stops it at once
@@ -51,8 +51,30 @@ const PICK_BODY_BYTES = 4_096;
51
51
  function wholeRecord(record) {
52
52
  return record !== null && typeof record === "object" && Number.isInteger(record.port) && /^[0-9a-f]+$/.test(String(record.token)) && typeof record.label === "string" && typeof record.extension === "string" && record.pages !== null && typeof record.pages === "object";
53
53
  }
54
- /** The live windows serving `extension` on this machine, grouped by the workspace each runs in: label -> { page -> entry address carrying that window's key }. Two windows on one workspace answer with the newest record. */
55
- function workspacesServed(extension) {
54
+ const PUBLIC_NAME = /^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)+$/i;
55
+ /**
56
+ * THE ONE PRODUCER OF A PUBLIC ADDRESS BASE (#3867). The gateway puts the public name it served the request under on
57
+ * `x-forwarded-host`; every address a page is handed for a request that came through it is built on this base, never on
58
+ * the loopback bind. Undefined for a request made on this machine, whose addresses are the loopback ones. A name that
59
+ * is not a host name THROWS a TypeError.
60
+ */
61
+ function publicBase(request) {
62
+ const forwarded = request.headers["x-forwarded-host"];
63
+ if (forwarded === undefined) {
64
+ return undefined;
65
+ }
66
+ if (typeof forwarded !== "string" || !PUBLIC_NAME.test(forwarded)) {
67
+ throw new TypeError(`x-forwarded-host is not a host name: ${JSON.stringify(forwarded)}`);
68
+ }
69
+ return `https://${forwarded.toLowerCase()}`;
70
+ }
71
+ /**
72
+ * The live windows serving `extension` on this machine, grouped by the workspace each runs in: label -> { page -> entry
73
+ * address }. On this machine the address carries that window's key on the loopback port; through the gateway (`base`
74
+ * is its public address) it is the public address naming that window, which the gateway's own session admits. Two
75
+ * windows on one workspace answer with the newest record.
76
+ */
77
+ function workspacesServed(extension, base) {
56
78
  const directory = recordDirectory(extension);
57
79
  const served = new Map();
58
80
  if (!fs.existsSync(directory)) {
@@ -83,7 +105,8 @@ function workspacesServed(extension) {
83
105
  served.set(label, Object.fromEntries(Object.entries(record.pages).map(([page, info]) => {
84
106
  // NULL-OK: field search is OPTIONAL and therefore it is ok to check for NULL
85
107
  const search = info.search ?? "";
86
- return [page, `http://127.0.0.1:${record.port}/${encodeURIComponent(page)}/${search}${search === "" ? "?" : "&"}${TOKEN_PARAMETER}=${record.token}`];
108
+ const at = `/${encodeURIComponent(page)}/${search}${search === "" ? "?" : "&"}`;
109
+ return [page, base === undefined ? `http://127.0.0.1:${record.port}${at}${TOKEN_PARAMETER}=${record.token}` : `${base}${at}window=${record.pid}`];
87
110
  })));
88
111
  }
89
112
  return served;
@@ -703,6 +726,16 @@ class WebHost {
703
726
  return send(response, 403, MIME[".txt"], "this host serves 127.0.0.1 only");
704
727
  }
705
728
  const url = new URL(request.url, `http://127.0.0.1:${this.port}`);
729
+ let base;
730
+ try {
731
+ base = publicBase(request);
732
+ }
733
+ catch (err) {
734
+ if (!(err instanceof TypeError))
735
+ throw err;
736
+ // OWNER: this request's trigger root — a gateway name that is not a host name is answered 400 with the reason, and no address is built on it.
737
+ return send(response, 400, MIME[".txt"], err.message);
738
+ }
706
739
  if (url.searchParams.get(TOKEN_PARAMETER) === PROCESS.token) {
707
740
  url.searchParams.delete(TOKEN_PARAMETER);
708
741
  return send(response, 302, MIME[".txt"], "", {
@@ -727,7 +760,7 @@ class WebHost {
727
760
  let workspaces;
728
761
  let current;
729
762
  try {
730
- workspaces = Object.fromEntries([...this.workspaces()].sort(([a], [b]) => a.localeCompare(b)));
763
+ workspaces = Object.fromEntries([...this.workspaces(base)].sort(([a], [b]) => a.localeCompare(b)));
731
764
  // NULL-OK: field at is OPTIONAL and therefore it is ok to check for NULL
732
765
  current = this.currentOf(new URLSearchParams(url.searchParams.get("at") ?? ""));
733
766
  }
@@ -763,7 +796,7 @@ class WebHost {
763
796
  return this.serveFile(url.pathname.slice("/fs".length), response);
764
797
  }
765
798
  if (url.pathname === "/") {
766
- return send(response, 200, MIME[".html"], indexPage(this), { "Cache-Control": "no-store" });
799
+ return send(response, 200, MIME[".html"], indexPage(this, base), { "Cache-Control": "no-store" });
767
800
  }
768
801
  const [, first, rest] = /^\/([^/]*)\/?(.*)$/.exec(url.pathname);
769
802
  const page = this.pages.get(decodeURIComponent(first));
@@ -778,13 +811,13 @@ class WebHost {
778
811
  }
779
812
  const home = url.searchParams.get("bullseye-peek");
780
813
  if (home !== null && ROSTER_EXTENSIONS.has(this.extension)) {
781
- return send(response, 200, MIME[".html"], peekedRosterPage(this.label, home, workspacesServed(this.extension).get(home)?.[page.name]), { "Cache-Control": "no-store" });
814
+ return send(response, 200, MIME[".html"], peekedRosterPage(this.label, home, workspacesServed(this.extension, base).get(home)?.[page.name]), { "Cache-Control": "no-store" });
782
815
  }
783
816
  return this.serveDocument(page, response);
784
817
  }
785
- /** The workspaces a page of this host can switch to: label -> { page -> address }. A window's host lists every live window serving the same extension. */
786
- workspaces() {
787
- return workspacesServed(this.extension);
818
+ /** The workspaces a page of this host can switch to: label -> { page -> address }, built on `base` when the request came through the gateway. A window's host lists every live window serving the same extension. */
819
+ workspaces(base) {
820
+ return workspacesServed(this.extension, base);
788
821
  }
789
822
  /** The workspace the page asking is on, from the address it opened (`at`). A window's host is on its own window's workspace whatever the page asked. */
790
823
  currentOf() {
@@ -1050,9 +1083,9 @@ class ServiceHost extends WebHost {
1050
1083
  }
1051
1084
  }
1052
1085
  }
1053
- function indexPage(host) {
1086
+ function indexPage(host, base) {
1054
1087
  const rows = [...host.pages.values()]
1055
- .map((page) => `<li><a href="${escapeAttribute(page.url)}">${page.live ? "🟢" : "⚪"} ${escapeAttribute(page.title)}</a></li>`)
1088
+ .map((page) => `<li><a href="${escapeAttribute(base === undefined ? page.url : `${base}/${encodeURIComponent(page.name)}/${page.search}`)}">${page.live ? "🟢" : "⚪"} ${escapeAttribute(page.title)}</a></li>`)
1056
1089
  .join("");
1057
1090
  return `<!DOCTYPE html><html><head><meta charset="utf-8"><title>${escapeAttribute(host.extension)}</title></head>
1058
1091
  <body style="font:14px system-ui,sans-serif;padding:24px;background:#1e1e1e;color:#ccc"><div>${escapeAttribute(host.extension)} · ${escapeAttribute(host.label)}</div><ul>${rows}</ul></body></html>`;
@@ -1,5 +1,60 @@
1
1
  "use strict";
2
2
  const PICKER_SOURCE = String.raw `
3
+ var BASE_CSS = [
4
+ "@media (max-width: 480px) {",
5
+ " html { -webkit-text-size-adjust: 100%; }",
6
+ " :root { --vscode-font-size: 14px !important; }",
7
+ " body { font-size: 14px; }",
8
+ " *, *::before, *::after { box-sizing: border-box; }",
9
+ " .filter-group, .be-seg { flex-wrap: wrap; max-width: 100%; }",
10
+ " table { display: block; max-width: 100%; overflow-x: auto; }",
11
+ " body { overflow-wrap: break-word; }",
12
+ " img, video, canvas { max-width: 100%; }",
13
+ " pre { max-width: 100%; overflow-x: auto; }",
14
+ " :where(textarea, input:not([type=checkbox]):not([type=radio]):not([type=range])) { font-size: 16px; }",
15
+ " :where(button:not([class*=be-]), select:not([class*=be-])) { min-height: 32px; }",
16
+ "}"
17
+ ].join(" ");
18
+
19
+ var MIN_PHONE_FONT = 12;
20
+ var floorTimer = 0;
21
+
22
+ function floorFonts() {
23
+ floorTimer = 0;
24
+ document.body.querySelectorAll("*").forEach(function (el) {
25
+ if (parseFloat(getComputedStyle(el).fontSize) < MIN_PHONE_FONT) {
26
+ el.style.setProperty("font-size", MIN_PHONE_FONT + "px", "important");
27
+ }
28
+ });
29
+ }
30
+
31
+ function floorFontsSoon() {
32
+ if (floorTimer === 0) {
33
+ floorTimer = setTimeout(floorFonts, 300);
34
+ }
35
+ }
36
+
37
+ function baseStyles() {
38
+ if (document.getElementById("__bullseye-base") !== null) {
39
+ return;
40
+ }
41
+ var style = document.createElement("style");
42
+ style.id = "__bullseye-base";
43
+ style.textContent = BASE_CSS;
44
+ document.head.insertBefore(style, document.head.firstChild);
45
+ if (window.matchMedia("(max-width: 480px)").matches) {
46
+ var watch = function () {
47
+ new MutationObserver(floorFontsSoon).observe(document.body, { childList: true, subtree: true });
48
+ floorFontsSoon();
49
+ };
50
+ if (document.readyState === "loading") {
51
+ document.addEventListener("DOMContentLoaded", watch);
52
+ } else {
53
+ watch();
54
+ }
55
+ }
56
+ }
57
+
3
58
  function notice(id, text) {
4
59
  if (document.getElementById(id) !== null) {
5
60
  return;
@@ -27,7 +82,7 @@ const PICKER_SOURCE = String.raw `
27
82
  function workspaceBox() {
28
83
  var box = document.createElement("div");
29
84
  box.id = "bullseye-workspace";
30
- box.style.cssText = "display:flex;flex-wrap:wrap;justify-content:flex-end;gap:8px;align-items:center;box-sizing:border-box;min-height:24px;font:12px system-ui,sans-serif;" + barColours + ";border-bottom:1px solid #454545;padding:2px 8px;pointer-events:auto";
85
+ box.style.cssText = "display:flex;flex-wrap:nowrap;justify-content:flex-end;gap:8px;align-items:center;box-sizing:border-box;min-height:24px;max-width:100%;overflow:hidden;font:12px system-ui,sans-serif;" + barColours + ";border-bottom:1px solid #454545;padding:2px 8px;pointer-events:auto";
31
86
  return box;
32
87
  }
33
88
 
@@ -37,10 +92,11 @@ const PICKER_SOURCE = String.raw `
37
92
 
38
93
  function choose(text, labels, current, onChange, emptyText) {
39
94
  var holder = document.createElement("label");
95
+ holder.style.cssText = "min-width:0;white-space:nowrap";
40
96
  holder.textContent = text;
41
97
  var select = document.createElement("select");
42
98
  select.setAttribute("aria-label", text.trim());
43
- select.style.cssText = "font:inherit;" + barColours + ";color-scheme:dark;border:0";
99
+ select.style.cssText = "font:inherit;" + barColours + ";color-scheme:dark;border:0;min-width:0;max-width:42vw;text-overflow:ellipsis";
44
100
  labels.forEach(function (name) {
45
101
  var option = document.createElement("option");
46
102
  option.style.cssText = name === current ? "background:#094771;color:#fff;font-weight:600" : barColours;
@@ -136,6 +192,7 @@ const PICKER_SOURCE = String.raw `
136
192
  function peekBadge(served, labels) {
137
193
  var box = workspaceBox();
138
194
  box.style.background = "#5a3d00";
195
+ box.style.flexWrap = "wrap";
139
196
  box.style.color = "#fff";
140
197
  var words = document.createElement("span");
141
198
  words.textContent = "👁 Peeking at " + served.current + " — look only: nothing here can start or change work";
@@ -255,6 +312,7 @@ const SHIM_SOURCE = String.raw `(function () {
255
312
  };
256
313
 
257
314
  function start() {
315
+ baseStyles();
258
316
  open();
259
317
  workspacePicker();
260
318
  }
@@ -270,6 +328,7 @@ const PICKER_PAGE_SOURCE = String.raw `(function () {
270
328
  var page = document.currentScript.getAttribute("data-page");
271
329
  var peekable = false;
272
330
  ` + PICKER_SOURCE + String.raw `
331
+ baseStyles();
273
332
  if (document.readyState === "loading") {
274
333
  document.addEventListener("DOMContentLoaded", workspacePicker);
275
334
  } else {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "omni-notify-mcp",
3
- "version": "1.3.307",
3
+ "version": "1.3.309",
4
4
  "description": "Reach your AI agents from anywhere — and let them reach you. One stdio process serves the whole tool surface to Claude Code, GitHub Copilot Chat and Codex: desktop, Telegram, Slack, ntfy, Discord, Teams and SMS are configured in one file, and agents 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",