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.
|
|
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
|
-
|
|
55
|
-
|
|
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
|
-
|
|
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:
|
|
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.
|
|
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",
|
|
File without changes
|