@pixerate/editor 0.52.0 → 0.54.0
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/agent/index.cjs +37 -29
- package/dist/agent/index.cjs.map +1 -1
- package/dist/agent/index.d.cts +4 -3
- package/dist/agent/index.d.ts +4 -3
- package/dist/agent/index.js +16 -8
- package/dist/agent/index.js.map +1 -1
- package/dist/animation/index.cjs +15 -1
- package/dist/animation/index.cjs.map +1 -1
- package/dist/animation/index.d.cts +3 -226
- package/dist/animation/index.d.ts +3 -226
- package/dist/animation/index.js +14 -0
- package/dist/bridge/cli.cjs +36 -0
- package/dist/bridge/cli.cjs.map +1 -0
- package/dist/bridge/cli.d.cts +1 -0
- package/dist/bridge/cli.d.ts +1 -0
- package/dist/bridge/cli.js +36 -0
- package/dist/bridge/cli.js.map +1 -0
- package/dist/bridge/index.cjs +16 -0
- package/dist/bridge/index.cjs.map +1 -0
- package/dist/bridge/index.d.cts +65 -0
- package/dist/bridge/index.d.ts +65 -0
- package/dist/bridge/index.js +16 -0
- package/dist/bridge/index.js.map +1 -0
- package/dist/bridge-client-C5OziCRK.d.cts +148 -0
- package/dist/bridge-client-C5dRjrz5.d.ts +148 -0
- package/dist/canvas/index.cjs +1308 -528
- package/dist/canvas/index.cjs.map +1 -1
- package/dist/canvas/index.d.cts +309 -5
- package/dist/canvas/index.d.ts +309 -5
- package/dist/canvas/index.js +1295 -515
- package/dist/canvas/index.js.map +1 -1
- package/dist/chart/index.cjs +193 -192
- package/dist/chart/index.cjs.map +1 -1
- package/dist/chart/index.d.cts +77 -59
- package/dist/chart/index.d.ts +77 -59
- package/dist/chart/index.js +87 -86
- package/dist/chart/index.js.map +1 -1
- package/dist/{chunk-UAW5FEMD.js → chunk-3S72F46R.js} +66 -8
- package/dist/chunk-3S72F46R.js.map +1 -0
- package/dist/{chunk-5TWIN7ZJ.js → chunk-47WWV3LI.js} +34 -4
- package/dist/chunk-47WWV3LI.js.map +1 -0
- package/dist/{chunk-OV7UAKIY.js → chunk-633QC34G.js} +172 -1
- package/dist/chunk-633QC34G.js.map +1 -0
- package/dist/chunk-AWKPSOSX.js +401 -0
- package/dist/chunk-AWKPSOSX.js.map +1 -0
- package/dist/chunk-B6DSIEXA.js +96 -0
- package/dist/chunk-B6DSIEXA.js.map +1 -0
- package/dist/{chunk-5N6AQCVB.js → chunk-BQQZ6MOI.js} +3 -3
- package/dist/{chunk-M5Z77GFH.cjs → chunk-C3JHII3M.cjs} +53 -53
- package/dist/{chunk-M5Z77GFH.cjs.map → chunk-C3JHII3M.cjs.map} +1 -1
- package/dist/{chunk-JOX6MJW4.cjs → chunk-CKRNELPO.cjs} +176 -5
- package/dist/chunk-CKRNELPO.cjs.map +1 -0
- package/dist/{chunk-TGBCBQFV.cjs → chunk-DK6QBGCS.cjs} +45 -15
- package/dist/chunk-DK6QBGCS.cjs.map +1 -0
- package/dist/chunk-EGFUVXKK.cjs +96 -0
- package/dist/chunk-EGFUVXKK.cjs.map +1 -0
- package/dist/{chunk-AMWXSYCI.js → chunk-GPWNYQ7R.js} +2 -370
- package/dist/chunk-GPWNYQ7R.js.map +1 -0
- package/dist/chunk-H5K77C7Y.cjs +3848 -0
- package/dist/chunk-H5K77C7Y.cjs.map +1 -0
- package/dist/chunk-K73CKYUM.cjs +458 -0
- package/dist/chunk-K73CKYUM.cjs.map +1 -0
- package/dist/{chunk-GTZXF4BI.cjs → chunk-NK3WL5TJ.cjs} +298 -133
- package/dist/chunk-NK3WL5TJ.cjs.map +1 -0
- package/dist/{chunk-3ZBAXET6.cjs → chunk-NN3JGUSX.cjs} +80 -22
- package/dist/chunk-NN3JGUSX.cjs.map +1 -0
- package/dist/chunk-NV22EMOZ.cjs +401 -0
- package/dist/chunk-NV22EMOZ.cjs.map +1 -0
- package/dist/{chunk-YMKPMXZ4.js → chunk-OIKYP7XC.js} +4 -4
- package/dist/{chunk-FEMIVHN3.cjs → chunk-P43HUQRX.cjs} +16 -16
- package/dist/{chunk-FEMIVHN3.cjs.map → chunk-P43HUQRX.cjs.map} +1 -1
- package/dist/chunk-PTMIGXZ2.js +467 -0
- package/dist/chunk-PTMIGXZ2.js.map +1 -0
- package/dist/chunk-QIWFEEWG.cjs +467 -0
- package/dist/chunk-QIWFEEWG.cjs.map +1 -0
- package/dist/{chunk-QFTVLEVN.js → chunk-RIXTW3C4.js} +3 -3
- package/dist/{chunk-WBOAXDKD.js → chunk-VODBAO32.js} +222 -57
- package/dist/chunk-VODBAO32.js.map +1 -0
- package/dist/chunk-WWJNC7SX.js +3848 -0
- package/dist/chunk-WWJNC7SX.js.map +1 -0
- package/dist/{chunk-2DEBI6KU.cjs → chunk-YZZMX6V7.cjs} +10 -10
- package/dist/{chunk-2DEBI6KU.cjs.map → chunk-YZZMX6V7.cjs.map} +1 -1
- package/dist/commands/index.d.cts +1 -1
- package/dist/commands/index.d.ts +1 -1
- package/dist/{controller-DS3drIvy.d.cts → controller-CX0QfSv0.d.cts} +53 -2
- package/dist/{controller-BdKzaxs2.d.ts → controller-DscWQbjO.d.ts} +53 -2
- package/dist/diagram/index.cjs +1146 -0
- package/dist/diagram/index.cjs.map +1 -0
- package/dist/diagram/index.d.cts +787 -0
- package/dist/diagram/index.d.ts +787 -0
- package/dist/diagram/index.js +1146 -0
- package/dist/diagram/index.js.map +1 -0
- package/dist/{easing-DTmX99QG.d.cts → easing-CNGk-W6d.d.cts} +1 -1
- package/dist/{easing-DTmX99QG.d.ts → easing-CNGk-W6d.d.ts} +1 -1
- package/dist/export/index.cjs +4 -4
- package/dist/export/index.js +3 -3
- package/dist/extensions/index.cjs +6 -6
- package/dist/extensions/index.d.cts +2 -2
- package/dist/extensions/index.d.ts +2 -2
- package/dist/extensions/index.js +5 -5
- package/dist/generation/index.d.cts +2 -2
- package/dist/generation/index.d.ts +2 -2
- package/dist/image-editor/index.d.cts +1 -1
- package/dist/image-editor/index.d.ts +1 -1
- package/dist/{index-Dt9lzydb.d.ts → index-CALDD35B.d.ts} +1 -1
- package/dist/{index-DOy9HzfC.d.cts → index-DIWB6xMf.d.cts} +1 -1
- package/dist/index.cjs +13 -13
- package/dist/index.d.cts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +6 -6
- package/dist/parameters/index.d.cts +1 -1
- package/dist/parameters/index.d.ts +1 -1
- package/dist/player-DSMjdgQ9.d.cts +225 -0
- package/dist/player-DdfVrKWq.d.ts +225 -0
- package/dist/presence-Cb6WhcrQ.d.cts +77 -0
- package/dist/presence-Cb6WhcrQ.d.ts +77 -0
- package/dist/{registry-CSlH4iUn.d.ts → registry-7AZFuand.d.ts} +1 -1
- package/dist/{registry-B4l-lYts.d.cts → registry-DIpIaCkH.d.cts} +1 -1
- package/dist/tools/index.cjs +10 -2
- package/dist/tools/index.cjs.map +1 -1
- package/dist/tools/index.d.cts +3 -74
- package/dist/tools/index.d.ts +3 -74
- package/dist/tools/index.js +12 -4
- package/dist/types-BULrFyuQ.d.cts +316 -0
- package/dist/types-BULrFyuQ.d.ts +316 -0
- package/dist/vector/index.d.cts +1 -1
- package/dist/vector/index.d.ts +1 -1
- package/package.json +14 -1
- package/dist/chunk-3ZBAXET6.cjs.map +0 -1
- package/dist/chunk-5TWIN7ZJ.js.map +0 -1
- package/dist/chunk-AMWXSYCI.js.map +0 -1
- package/dist/chunk-GTZXF4BI.cjs.map +0 -1
- package/dist/chunk-JOX6MJW4.cjs.map +0 -1
- package/dist/chunk-OV7UAKIY.js.map +0 -1
- package/dist/chunk-TGBCBQFV.cjs.map +0 -1
- package/dist/chunk-UAW5FEMD.js.map +0 -1
- package/dist/chunk-WBOAXDKD.js.map +0 -1
- package/dist/chunk-XFYUFVAX.cjs +0 -826
- package/dist/chunk-XFYUFVAX.cjs.map +0 -1
- /package/dist/{chunk-5N6AQCVB.js.map → chunk-BQQZ6MOI.js.map} +0 -0
- /package/dist/{chunk-YMKPMXZ4.js.map → chunk-OIKYP7XC.js.map} +0 -0
- /package/dist/{chunk-QFTVLEVN.js.map → chunk-RIXTW3C4.js.map} +0 -0
|
@@ -0,0 +1,401 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
var _chunkQIWFEEWGcjs = require('./chunk-QIWFEEWG.cjs');
|
|
7
|
+
|
|
8
|
+
// src/bridge/index.ts
|
|
9
|
+
var _http = require('http');
|
|
10
|
+
var _readline = require('readline');
|
|
11
|
+
|
|
12
|
+
// src/bridge/websocket.ts
|
|
13
|
+
var _crypto = require('crypto');
|
|
14
|
+
var GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
|
|
15
|
+
var MAX_MESSAGE = 16 * 1024 * 1024;
|
|
16
|
+
function acceptWebSocket(req, socket) {
|
|
17
|
+
const key = req.headers["sec-websocket-key"];
|
|
18
|
+
if (_optionalChain([req, 'access', _ => _.headers, 'access', _2 => _2.upgrade, 'optionalAccess', _3 => _3.toLowerCase, 'call', _4 => _4()]) !== "websocket" || typeof key !== "string" || req.headers["sec-websocket-version"] !== "13") {
|
|
19
|
+
rejectUpgrade(socket, 400, "Bad Request");
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
const accept = _crypto.createHash.call(void 0, "sha1").update(key + GUID).digest("base64");
|
|
23
|
+
socket.write(`HTTP/1.1 101 Switching Protocols\r
|
|
24
|
+
Upgrade: websocket\r
|
|
25
|
+
Connection: Upgrade\r
|
|
26
|
+
Sec-WebSocket-Accept: ${accept}\r
|
|
27
|
+
\r
|
|
28
|
+
`);
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
function rejectUpgrade(socket, status, message) {
|
|
32
|
+
socket.write(`HTTP/1.1 ${status} ${message}\r
|
|
33
|
+
Connection: close\r
|
|
34
|
+
Content-Type: text/plain\r
|
|
35
|
+
Content-Length: ${Buffer.byteLength(message)}\r
|
|
36
|
+
\r
|
|
37
|
+
${message}`);
|
|
38
|
+
socket.destroy();
|
|
39
|
+
}
|
|
40
|
+
function frame(opcode, payload) {
|
|
41
|
+
const length = payload.length;
|
|
42
|
+
const head = length < 126 ? Buffer.alloc(2) : length < 65536 ? Buffer.alloc(4) : Buffer.alloc(10);
|
|
43
|
+
head[0] = 128 | opcode;
|
|
44
|
+
if (length < 126) head[1] = length;
|
|
45
|
+
else if (length < 65536) {
|
|
46
|
+
head[1] = 126;
|
|
47
|
+
head.writeUInt16BE(length, 2);
|
|
48
|
+
} else {
|
|
49
|
+
head[1] = 127;
|
|
50
|
+
head.writeBigUInt64BE(BigInt(length), 2);
|
|
51
|
+
}
|
|
52
|
+
return Buffer.concat([head, payload]);
|
|
53
|
+
}
|
|
54
|
+
function wrapWebSocket(socket) {
|
|
55
|
+
let buffer = Buffer.alloc(0);
|
|
56
|
+
let fragments = [];
|
|
57
|
+
let open = true;
|
|
58
|
+
const messageListeners = [];
|
|
59
|
+
const closeListeners = [];
|
|
60
|
+
let closeInfo = null;
|
|
61
|
+
const finish = (code, reason) => {
|
|
62
|
+
if (closeInfo) return;
|
|
63
|
+
closeInfo = { code, reason };
|
|
64
|
+
open = false;
|
|
65
|
+
closeListeners.forEach((l) => l(code, reason));
|
|
66
|
+
};
|
|
67
|
+
const close = (code = 1e3, reason = "") => {
|
|
68
|
+
if (!open) return;
|
|
69
|
+
open = false;
|
|
70
|
+
const body = Buffer.alloc(2 + Buffer.byteLength(reason));
|
|
71
|
+
body.writeUInt16BE(code, 0);
|
|
72
|
+
body.write(reason, 2);
|
|
73
|
+
socket.write(frame(8, body));
|
|
74
|
+
socket.end();
|
|
75
|
+
_optionalChain([setTimeout, 'call', _5 => _5(() => socket.destroy(), 1e3), 'access', _6 => _6.unref, 'optionalCall', _7 => _7()]);
|
|
76
|
+
finish(code, reason);
|
|
77
|
+
};
|
|
78
|
+
socket.on("data", (chunk) => {
|
|
79
|
+
buffer = Buffer.concat([buffer, chunk]);
|
|
80
|
+
while (buffer.length >= 2) {
|
|
81
|
+
const fin = (buffer[0] & 128) !== 0;
|
|
82
|
+
const opcode = buffer[0] & 15;
|
|
83
|
+
const masked = (buffer[1] & 128) !== 0;
|
|
84
|
+
let length = buffer[1] & 127;
|
|
85
|
+
let offset = 2;
|
|
86
|
+
if (length === 126) {
|
|
87
|
+
if (buffer.length < 4) return;
|
|
88
|
+
length = buffer.readUInt16BE(2);
|
|
89
|
+
offset = 4;
|
|
90
|
+
} else if (length === 127) {
|
|
91
|
+
if (buffer.length < 10) return;
|
|
92
|
+
const big = buffer.readBigUInt64BE(2);
|
|
93
|
+
if (big > BigInt(MAX_MESSAGE)) return close(1009, "Message too big.");
|
|
94
|
+
length = Number(big);
|
|
95
|
+
offset = 10;
|
|
96
|
+
}
|
|
97
|
+
if (!masked) return close(1002, "Unmasked frame.");
|
|
98
|
+
if (buffer.length < offset + 4 + length) return;
|
|
99
|
+
const mask = buffer.subarray(offset, offset + 4);
|
|
100
|
+
const payload = Buffer.from(buffer.subarray(offset + 4, offset + 4 + length));
|
|
101
|
+
for (let i = 0; i < payload.length; i++) payload[i] = payload[i] ^ mask[i % 4];
|
|
102
|
+
buffer = buffer.subarray(offset + 4 + length);
|
|
103
|
+
if (opcode === 8) {
|
|
104
|
+
const code = payload.length >= 2 ? payload.readUInt16BE(0) : 1005;
|
|
105
|
+
const reason = payload.length > 2 ? payload.subarray(2).toString("utf8") : "";
|
|
106
|
+
if (open) {
|
|
107
|
+
open = false;
|
|
108
|
+
socket.write(frame(8, payload.subarray(0, 2)));
|
|
109
|
+
socket.end();
|
|
110
|
+
}
|
|
111
|
+
return finish(code, reason);
|
|
112
|
+
}
|
|
113
|
+
if (opcode === 9) {
|
|
114
|
+
socket.write(frame(10, payload));
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
if (opcode === 10) continue;
|
|
118
|
+
if (opcode === 2) return close(1003, "Only text messages are supported.");
|
|
119
|
+
if (opcode === 1 || opcode === 0) {
|
|
120
|
+
fragments.push(payload);
|
|
121
|
+
if (fragments.reduce((n, f) => n + f.length, 0) > MAX_MESSAGE) return close(1009, "Message too big.");
|
|
122
|
+
if (fin) {
|
|
123
|
+
const text = Buffer.concat(fragments).toString("utf8");
|
|
124
|
+
fragments = [];
|
|
125
|
+
messageListeners.forEach((l) => l(text));
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
socket.on("close", () => finish(1006, ""));
|
|
131
|
+
socket.on("error", () => finish(1006, ""));
|
|
132
|
+
return {
|
|
133
|
+
get open() {
|
|
134
|
+
return open;
|
|
135
|
+
},
|
|
136
|
+
send(text) {
|
|
137
|
+
if (open) socket.write(frame(1, Buffer.from(text, "utf8")));
|
|
138
|
+
},
|
|
139
|
+
close,
|
|
140
|
+
terminate() {
|
|
141
|
+
if (open) close(1001, "Going away.");
|
|
142
|
+
socket.destroy();
|
|
143
|
+
},
|
|
144
|
+
onMessage(listener) {
|
|
145
|
+
messageListeners.push(listener);
|
|
146
|
+
},
|
|
147
|
+
onClose(listener) {
|
|
148
|
+
closeListeners.push(listener);
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// src/bridge/index.ts
|
|
154
|
+
function isLocalOrigin(origin) {
|
|
155
|
+
try {
|
|
156
|
+
const { hostname } = new URL(origin);
|
|
157
|
+
return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "[::1]" || hostname.endsWith(".localhost");
|
|
158
|
+
} catch (e2) {
|
|
159
|
+
return false;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
var EDITOR_ARG = {
|
|
163
|
+
type: "string",
|
|
164
|
+
description: 'Editor id (e.g. "e1") or part of its name, from list_editors. Default: the most recently active editor that has this tool.'
|
|
165
|
+
};
|
|
166
|
+
var BRIDGE_INSTRUCTIONS = "Tools come from editors open in a browser. Call list_editors to see them; when several are open, pass `editor` (an id or part of a name) to pick one, otherwise the most recently active editor that has the tool answers. If no editor is connected, ask the user to open the app and connect it to the bridge.";
|
|
167
|
+
async function createToolBridge(options) {
|
|
168
|
+
const log = _nullishCoalesce(options.log, () => ( ((message) => process.stderr.write(`[${options.name}] ${message}
|
|
169
|
+
`))));
|
|
170
|
+
const port = _nullishCoalesce(options.port, () => ( _chunkQIWFEEWGcjs.TOOL_BRIDGE_DEFAULT_PORT));
|
|
171
|
+
const retryMs = _nullishCoalesce(options.retryMs, () => ( 2e3));
|
|
172
|
+
const timeoutMs = _nullishCoalesce(options.timeoutMs, () => ( 6e4));
|
|
173
|
+
const extraOrigins = new Set(_nullishCoalesce(options.origins, () => ( [])));
|
|
174
|
+
const editors = /* @__PURE__ */ new Map();
|
|
175
|
+
const pending = /* @__PURE__ */ new Map();
|
|
176
|
+
let editorCounter = 0;
|
|
177
|
+
let activity = 0;
|
|
178
|
+
const sockets = /* @__PURE__ */ new Set();
|
|
179
|
+
let callCounter = 0;
|
|
180
|
+
let listenError = null;
|
|
181
|
+
let closed = false;
|
|
182
|
+
let retry;
|
|
183
|
+
const send = (editor, message) => editor.socket.send(JSON.stringify(message));
|
|
184
|
+
const registry = new (0, _chunkQIWFEEWGcjs.ToolRegistry)();
|
|
185
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
186
|
+
const toolsChanged = () => listeners.forEach((l) => l());
|
|
187
|
+
const describeEditors = () => [...editors.values()].map((e) => `${e.id} (${e.name})`).join(", ");
|
|
188
|
+
function pickEditor(tool, ref) {
|
|
189
|
+
if (listenError) throw new Error(listenError);
|
|
190
|
+
const all = [...editors.values()];
|
|
191
|
+
if (!all.length) throw new Error(`No editor is connected to ws://127.0.0.1:${actualPort()}. Ask the user to open the app and connect it to the bridge.`);
|
|
192
|
+
const having = all.filter((e) => e.tools.some((t) => t.name === tool));
|
|
193
|
+
if (typeof ref === "string" && ref) {
|
|
194
|
+
const byId = editors.get(ref);
|
|
195
|
+
const matches = byId ? [byId] : all.filter((e) => e.name.toLowerCase().includes(ref.toLowerCase()));
|
|
196
|
+
if (matches.length !== 1) throw new Error(`No single editor matches "${ref}". Connected: ${describeEditors()}.`);
|
|
197
|
+
const editor = matches[0];
|
|
198
|
+
if (!editor.tools.some((t) => t.name === tool))
|
|
199
|
+
throw new Error(`${editor.id} (${editor.name}) has no tool "${tool}"${having.length ? `; ${having.map((e) => e.id).join(", ")} do` : ""}.`);
|
|
200
|
+
return editor;
|
|
201
|
+
}
|
|
202
|
+
if (!having.length) throw new Error(`No connected editor has the tool "${tool}". Connected: ${describeEditors()}.`);
|
|
203
|
+
return having.sort((a, b) => b.activeSeq - a.activeSeq)[0];
|
|
204
|
+
}
|
|
205
|
+
function call(editor, name, args, signal) {
|
|
206
|
+
const id = `c${++callCounter}`;
|
|
207
|
+
return new Promise((resolve, reject) => {
|
|
208
|
+
const giveUp = (reason) => {
|
|
209
|
+
if (!pending.delete(id)) return;
|
|
210
|
+
clearTimeout(timer);
|
|
211
|
+
send(editor, { t: "cancel", id, reason });
|
|
212
|
+
reject(new Error(reason));
|
|
213
|
+
};
|
|
214
|
+
const timer = setTimeout(() => giveUp(`${editor.id} (${editor.name}) did not answer within ${Math.round(timeoutMs / 1e3)} s (is its tab in the background?).`), timeoutMs);
|
|
215
|
+
pending.set(id, { editor, resolve, reject, timer });
|
|
216
|
+
_optionalChain([signal, 'optionalAccess', _8 => _8.addEventListener, 'call', _9 => _9("abort", () => giveUp("Cancelled."), { once: true })]);
|
|
217
|
+
send(editor, { t: "call", id, name, arguments: args });
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
const listEditors = {
|
|
221
|
+
name: "list_editors",
|
|
222
|
+
title: "List connected editors",
|
|
223
|
+
description: "Lists the editors open in a browser and connected to this bridge: id, name, kind, how many tools each has and how long since it was last active. Pass an id or part of a name as `editor` to other tools to pick one.",
|
|
224
|
+
inputSchema: { type: "object", properties: {}, additionalProperties: false },
|
|
225
|
+
annotations: { title: "List connected editors", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
226
|
+
handler: () => {
|
|
227
|
+
if (listenError) throw new Error(listenError);
|
|
228
|
+
return { editors: infos() };
|
|
229
|
+
}
|
|
230
|
+
};
|
|
231
|
+
const forwardedTools = () => {
|
|
232
|
+
const seen = /* @__PURE__ */ new Map();
|
|
233
|
+
for (const editor of editors.values()) for (const tool of editor.tools) if (!seen.has(tool.name) && tool.name !== "list_editors") seen.set(tool.name, tool);
|
|
234
|
+
return [...seen.values()].map((tool) => {
|
|
235
|
+
const schema = tool.inputSchema;
|
|
236
|
+
return {
|
|
237
|
+
...tool,
|
|
238
|
+
title: _nullishCoalesce(_nullishCoalesce(tool.title, () => ( _optionalChain([tool, 'access', _10 => _10.annotations, 'optionalAccess', _11 => _11.title]))), () => ( tool.name)),
|
|
239
|
+
description: _nullishCoalesce(tool.description, () => ( tool.name)),
|
|
240
|
+
inputSchema: { ...schema, type: "object", properties: { editor: EDITOR_ARG, ..._nullishCoalesce(schema.properties, () => ( {})) } },
|
|
241
|
+
handler: async (args, context) => {
|
|
242
|
+
const { editor: ref, ...rest } = args;
|
|
243
|
+
const editor = pickEditor(tool.name, ref);
|
|
244
|
+
const result = await call(editor, tool.name, rest, context.signal);
|
|
245
|
+
editor.activeAt = Date.now();
|
|
246
|
+
editor.activeSeq = ++activity;
|
|
247
|
+
return result;
|
|
248
|
+
}
|
|
249
|
+
};
|
|
250
|
+
});
|
|
251
|
+
};
|
|
252
|
+
registry.register({
|
|
253
|
+
id: "bridge",
|
|
254
|
+
tools: () => [listEditors, ...forwardedTools()],
|
|
255
|
+
subscribe: (notify) => {
|
|
256
|
+
listeners.add(notify);
|
|
257
|
+
return () => listeners.delete(notify);
|
|
258
|
+
}
|
|
259
|
+
});
|
|
260
|
+
const infos = () => [...editors.values()].map((e) => ({ id: e.id, name: e.name, ...e.kind ? { kind: e.kind } : {}, tools: e.tools.length, idleSeconds: Math.round((Date.now() - e.activeAt) / 1e3) }));
|
|
261
|
+
const http = _http.createServer.call(void 0, (_req, res) => {
|
|
262
|
+
res.writeHead(426, { "Content-Type": "text/plain" });
|
|
263
|
+
res.end("This is a WebSocket endpoint for editors (connectToolBridge).");
|
|
264
|
+
});
|
|
265
|
+
http.on("upgrade", (req, socket) => {
|
|
266
|
+
const origin = req.headers.origin;
|
|
267
|
+
if (origin && !extraOrigins.has(origin) && !isLocalOrigin(origin)) {
|
|
268
|
+
log(`refused an editor from ${origin}`);
|
|
269
|
+
return rejectUpgrade(socket, 403, "Origin not allowed");
|
|
270
|
+
}
|
|
271
|
+
if (options.token) {
|
|
272
|
+
const token = new URL(_nullishCoalesce(req.url, () => ( "/")), "http://localhost").searchParams.get("token");
|
|
273
|
+
if (token !== options.token) {
|
|
274
|
+
log("refused an editor with a wrong token");
|
|
275
|
+
return rejectUpgrade(socket, 401, "Bad token");
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
if (!acceptWebSocket(req, socket)) return;
|
|
279
|
+
const ws = wrapWebSocket(socket);
|
|
280
|
+
sockets.add(ws);
|
|
281
|
+
let editor = null;
|
|
282
|
+
ws.onMessage((text) => {
|
|
283
|
+
let message;
|
|
284
|
+
try {
|
|
285
|
+
message = JSON.parse(text);
|
|
286
|
+
} catch (e3) {
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
if (message.t === "hello") {
|
|
290
|
+
if (message.v !== _chunkQIWFEEWGcjs.TOOL_BRIDGE_PROTOCOL) {
|
|
291
|
+
log(`refused an editor speaking protocol v${String(message.v)}`);
|
|
292
|
+
return ws.close(1008, `This bridge speaks protocol v${_chunkQIWFEEWGcjs.TOOL_BRIDGE_PROTOCOL}; update @pixerate/editor.`);
|
|
293
|
+
}
|
|
294
|
+
editor = { id: `e${++editorCounter}`, name: String(message.name || "Editor"), ...message.kind ? { kind: message.kind } : {}, socket: ws, tools: Array.isArray(message.tools) ? message.tools : [], activeAt: Date.now(), activeSeq: ++activity };
|
|
295
|
+
editors.set(editor.id, editor);
|
|
296
|
+
ws.send(JSON.stringify({ t: "welcome", v: _chunkQIWFEEWGcjs.TOOL_BRIDGE_PROTOCOL, id: editor.id }));
|
|
297
|
+
log(`editor ${editor.id} connected: ${editor.name} (${editor.tools.length} tools)`);
|
|
298
|
+
toolsChanged();
|
|
299
|
+
} else if (message.t === "tools" && editor) {
|
|
300
|
+
editor.tools = Array.isArray(message.tools) ? message.tools : [];
|
|
301
|
+
toolsChanged();
|
|
302
|
+
} else if (message.t === "result") {
|
|
303
|
+
const waiting = pending.get(message.id);
|
|
304
|
+
if (!waiting || waiting.editor !== editor) return;
|
|
305
|
+
pending.delete(message.id);
|
|
306
|
+
clearTimeout(waiting.timer);
|
|
307
|
+
waiting.resolve(message.result);
|
|
308
|
+
}
|
|
309
|
+
});
|
|
310
|
+
ws.onClose(() => {
|
|
311
|
+
sockets.delete(ws);
|
|
312
|
+
if (!editor) return;
|
|
313
|
+
editors.delete(editor.id);
|
|
314
|
+
for (const [id, waiting] of pending) {
|
|
315
|
+
if (waiting.editor !== editor) continue;
|
|
316
|
+
pending.delete(id);
|
|
317
|
+
clearTimeout(waiting.timer);
|
|
318
|
+
waiting.reject(new Error(`${editor.id} (${editor.name}) disconnected before answering.`));
|
|
319
|
+
}
|
|
320
|
+
log(`editor ${editor.id} disconnected`);
|
|
321
|
+
toolsChanged();
|
|
322
|
+
});
|
|
323
|
+
});
|
|
324
|
+
const actualPort = () => {
|
|
325
|
+
const address = http.address();
|
|
326
|
+
return address && typeof address === "object" ? address.port : port;
|
|
327
|
+
};
|
|
328
|
+
const listen = () => http.listen(port, "127.0.0.1");
|
|
329
|
+
const listening = new Promise((resolve) => {
|
|
330
|
+
http.on("listening", () => {
|
|
331
|
+
if (listenError) log(`port ${port} is free now`);
|
|
332
|
+
listenError = null;
|
|
333
|
+
log(`waiting for editors on ws://127.0.0.1:${actualPort()}`);
|
|
334
|
+
resolve();
|
|
335
|
+
});
|
|
336
|
+
http.on("error", (error) => {
|
|
337
|
+
const taken = error.code === "EADDRINUSE";
|
|
338
|
+
const again = taken && retryMs > 0 && !closed;
|
|
339
|
+
const message = taken ? `Port ${port} is in use (another bridge?). ${again ? "This bridge keeps trying it; editors can connect once it is free. Or start" : "Start"} this one on another port.` : String(error);
|
|
340
|
+
if (message !== listenError) log(message);
|
|
341
|
+
listenError = message;
|
|
342
|
+
if (again) retry = setTimeout(listen, retryMs);
|
|
343
|
+
resolve();
|
|
344
|
+
});
|
|
345
|
+
});
|
|
346
|
+
listen();
|
|
347
|
+
let output = null;
|
|
348
|
+
const write = (message) => _optionalChain([output, 'optionalAccess', _12 => _12.write, 'call', _13 => _13(`${JSON.stringify(message)}
|
|
349
|
+
`)]);
|
|
350
|
+
const mcp = _chunkQIWFEEWGcjs.createMcpServer.call(void 0, registry, {
|
|
351
|
+
name: options.name,
|
|
352
|
+
version: _nullishCoalesce(options.version, () => ( "0.1.0")),
|
|
353
|
+
instructions: [options.instructions, BRIDGE_INSTRUCTIONS].filter(Boolean).join("\n\n"),
|
|
354
|
+
send: write
|
|
355
|
+
});
|
|
356
|
+
let lines = null;
|
|
357
|
+
if (options.stdio !== false) {
|
|
358
|
+
const io = _nullishCoalesce(options.stdio, () => ( { input: process.stdin, output: process.stdout }));
|
|
359
|
+
output = io.output;
|
|
360
|
+
lines = _readline.createInterface.call(void 0, { input: io.input });
|
|
361
|
+
lines.on("line", (line) => {
|
|
362
|
+
if (!line.trim()) return;
|
|
363
|
+
void mcp.handle(line).then((response) => {
|
|
364
|
+
if (response) write(response);
|
|
365
|
+
});
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
await listening;
|
|
369
|
+
return {
|
|
370
|
+
get port() {
|
|
371
|
+
return actualPort();
|
|
372
|
+
},
|
|
373
|
+
mcp,
|
|
374
|
+
registry,
|
|
375
|
+
editors: infos,
|
|
376
|
+
async close() {
|
|
377
|
+
closed = true;
|
|
378
|
+
clearTimeout(retry);
|
|
379
|
+
for (const [id, waiting] of pending) {
|
|
380
|
+
clearTimeout(waiting.timer);
|
|
381
|
+
waiting.reject(new Error("The bridge closed."));
|
|
382
|
+
pending.delete(id);
|
|
383
|
+
}
|
|
384
|
+
for (const socket of [...sockets]) socket.terminate();
|
|
385
|
+
editors.clear();
|
|
386
|
+
mcp.close();
|
|
387
|
+
_optionalChain([lines, 'optionalAccess', _14 => _14.close, 'call', _15 => _15()]);
|
|
388
|
+
if (http.listening) {
|
|
389
|
+
const done = new Promise((resolve) => http.close(() => resolve()));
|
|
390
|
+
http.closeAllConnections();
|
|
391
|
+
await done;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
|
|
399
|
+
|
|
400
|
+
exports.isLocalOrigin = isLocalOrigin; exports.createToolBridge = createToolBridge;
|
|
401
|
+
//# sourceMappingURL=chunk-NV22EMOZ.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["/home/runner/work/editor/editor/packages/core/dist/chunk-NV22EMOZ.cjs","../src/bridge/index.ts","../src/bridge/websocket.ts"],"names":[],"mappings":"AAAA;AACE;AACA;AACA;AACA;AACF,wDAA6B;AAC7B;AACA;ACYA,4BAAwD;AACxD,oCAAgC;ADVhC;AACA;AEPA,gCAA2B;AAI3B,IAAM,KAAA,EAAO,sCAAA;AAEb,IAAM,YAAA,EAAc,GAAA,EAAK,KAAA,EAAO,IAAA;AAazB,SAAS,eAAA,CAAgB,GAAA,EAAsB,MAAA,EAAyB;AAC9E,EAAA,MAAM,IAAA,EAAM,GAAA,CAAI,OAAA,CAAQ,mBAAmB,CAAA;AAC3C,EAAA,GAAA,iBAAI,GAAA,mBAAI,OAAA,qBAAQ,OAAA,6BAAS,WAAA,mBAAY,IAAA,IAAM,YAAA,GAAe,OAAO,IAAA,IAAQ,SAAA,GAAY,GAAA,CAAI,OAAA,CAAQ,uBAAuB,EAAA,IAAM,IAAA,EAAM;AACnI,IAAA,aAAA,CAAc,MAAA,EAAQ,GAAA,EAAK,aAAa,CAAA;AACxC,IAAA,OAAO,KAAA;AAAA,EACR;AACA,EAAA,MAAM,OAAA,EAAS,gCAAA,MAAiB,CAAA,CAAE,MAAA,CAAO,IAAA,EAAM,IAAI,CAAA,CAAE,MAAA,CAAO,QAAQ,CAAA;AACpE,EAAA,MAAA,CAAO,KAAA,CAAM,CAAA;AAAA;AAAA;AAAA,sBAAA,EAA0G,MAAM,CAAA;AAAA;AAAA,CAAU,CAAA;AACvI,EAAA,OAAO,IAAA;AACR;AAGO,SAAS,aAAA,CAAc,MAAA,EAAgB,MAAA,EAAgB,OAAA,EAAuB;AACpF,EAAA,MAAA,CAAO,KAAA,CAAM,CAAA,SAAA,EAAY,MAAM,CAAA,CAAA,EAAI,OAAO,CAAA;AAAA;AAAA;AAAA,gBAAA,EAAwE,MAAA,CAAO,UAAA,CAAW,OAAO,CAAC,CAAA;AAAA;AAAA,EAAW,OAAO,CAAA,CAAA;AACvJ,EAAA;AACR;AAES;AACF,EAAA;AACA,EAAA;AACA,EAAA;AACF,EAAA;AAAwB,EAAA;AAErB,IAAA;AACD,IAAA;AACC,EAAA;AACA,IAAA;AACD,IAAA;AACN,EAAA;AACO,EAAA;AACR;AAGgB;AACX,EAAA;AACA,EAAA;AACA,EAAA;AACE,EAAA;AACA,EAAA;AACF,EAAA;AAEE,EAAA;AACD,IAAA;AACJ,IAAA;AACA,IAAA;AACA,IAAA;AACD,EAAA;AACM,EAAA;AACA,IAAA;AACL,IAAA;AACM,IAAA;AACD,IAAA;AACA,IAAA;AACL,IAAA;AACA,IAAA;AAEA,oBAAA;AACA,IAAA;AACD,EAAA;AAEO,EAAA;AACN,IAAA;AACA,IAAA;AACC,MAAA;AACA,MAAA;AACA,MAAA;AACI,MAAA;AACA,MAAA;AACA,MAAA;AACH,QAAA;AACA,QAAA;AACA,QAAA;AACD,MAAA;AACC,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACD,MAAA;AAEI,MAAA;AACA,MAAA;AACJ,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AAEI,MAAA;AACH,QAAA;AACA,QAAA;AACA,QAAA;AACC,UAAA;AACA,UAAA;AACA,UAAA;AACD,QAAA;AACA,QAAA;AACD,MAAA;AACI,MAAA;AACH,QAAA;AACA,QAAA;AACD,MAAA;AACI,MAAA;AACA,MAAA;AACA,MAAA;AACH,QAAA;AACA,QAAA;AACA,QAAA;AACC,UAAA;AACA,UAAA;AACA,UAAA;AACD,QAAA;AACD,MAAA;AACD,IAAA;AACA,EAAA;AACM,EAAA;AACA,EAAA;AAEA,EAAA;AACF,IAAA;AACH,MAAA;AACD,IAAA;AACK,IAAA;AACA,MAAA;AACL,IAAA;AACA,IAAA;AACA,IAAA;AACK,MAAA;AACJ,MAAA;AACD,IAAA;AACA,IAAA;AACC,MAAA;AACD,IAAA;AACA,IAAA;AACC,MAAA;AACD,IAAA;AACD,EAAA;AACD;AFRU;AACA;ACrDM;AACX,EAAA;AACG,IAAA;AACN,IAAA;AACO,EAAA;AACP,IAAA;AACD,EAAA;AACD;AAEM;AACC,EAAA;AACN,EAAA;AACD;AAEM;AAON;AACO,EAAA;AAAgG;AAChG,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACF,EAAA;AACA,EAAA;AACE,EAAA;AACF,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AAEE,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AAGA,EAAA;AACN,EAAA;AACK,IAAA;AACE,IAAA;AACD,IAAA;AACC,IAAA;AACF,IAAA;AACH,MAAA;AACA,MAAA;AACI,MAAA;AACJ,MAAA;AACI,MAAA;AACH,QAAA;AACD,MAAA;AACD,IAAA;AACK,IAAA;AACL,IAAA;AACD,EAAA;AAEA,EAAA;AACO,IAAA;AACN,IAAA;AACC,MAAA;AACC,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACD,MAAA;AACA,MAAA;AACA,MAAA;AACA,sBAAA;AACA,MAAA;AACA,IAAA;AACF,EAAA;AAEM,EAAA;AACC,IAAA;AACN,IAAA;AACA,IAAA;AAEA,IAAA;AACA,IAAA;AACA,IAAA;AACK,MAAA;AACJ,MAAA;AACD,IAAA;AACD,EAAA;AAGM,EAAA;AACC,IAAA;AACN,IAAA;AACA,IAAA;AACC,MAAA;AACA,MAAA;AACC,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACC,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACD,QAAA;AACD,MAAA;AACA,IAAA;AACF,EAAA;AAEA,EAAA;AACK,IAAA;AACJ,IAAA;AACA,IAAA;AACC,MAAA;AACA,MAAA;AACD,IAAA;AACA,EAAA;AAEK,EAAA;AAIA,EAAA;AACD,IAAA;AACA,IAAA;AACJ,EAAA;AACO,EAAA;AACD,IAAA;AAEF,IAAA;AACC,MAAA;AACJ,MAAA;AACD,IAAA;AACI,IAAA;AACH,MAAA;AACI,MAAA;AACH,QAAA;AACA,QAAA;AACD,MAAA;AACD,IAAA;AACK,IAAA;AACC,IAAA;AACN,IAAA;AACI,IAAA;AACD,IAAA;AACE,MAAA;AACA,MAAA;AACH,QAAA;AACD,MAAA;AACC,QAAA;AACD,MAAA;AACI,MAAA;AACH,QAAA;AACC,UAAA;AACA,UAAA;AACD,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACD,MAAA;AACC,QAAA;AACA,QAAA;AACD,MAAA;AACC,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACD,MAAA;AACA,IAAA;AACE,IAAA;AACF,MAAA;AACI,MAAA;AACJ,MAAA;AACA,MAAA;AACC,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACD,MAAA;AACI,MAAA;AACJ,MAAA;AACA,IAAA;AACD,EAAA;AAEK,EAAA;AACC,IAAA;AACN,IAAA;AACD,EAAA;AACM,EAAA;AACA,EAAA;AACA,IAAA;AACA,MAAA;AACJ,MAAA;AACI,MAAA;AACJ,MAAA;AACA,IAAA;AACI,IAAA;AACJ,MAAA;AACA,MAAA;AACA,MAAA;AAGI,MAAA;AACJ,MAAA;AACI,MAAA;AAEJ,MAAA;AACA,IAAA;AACD,EAAA;AACM,EAAA;AAGH,EAAA;AACE,EAAA;AAAwG;AACxG,EAAA;AACC,IAAA;AACN,IAAA;AACA,IAAA;AACM,IAAA;AACN,EAAA;AACG,EAAA;AACA,EAAA;AACG,IAAA;AACN,IAAA;AACA,IAAA;AACM,IAAA;AACD,MAAA;AACJ,MAAA;AACC,QAAA;AACA,MAAA;AACD,IAAA;AACF,EAAA;AACM,EAAA;AAEC,EAAA;AACF,IAAA;AACH,MAAA;AACD,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACM,IAAA;AACL,MAAA;AACA,MAAA;AACA,MAAA;AACC,QAAA;AACA,QAAA;AACA,QAAA;AACD,MAAA;AACA,MAAA;AACA,MAAA;AACI,MAAA;AACJ,sBAAA;AACI,MAAA;AACH,QAAA;AACA,QAAA;AACA,QAAA;AACD,MAAA;AACD,IAAA;AACD,EAAA;AACD;AD4BU;AACA;AACA;AACA;AACA","file":"/home/runner/work/editor/editor/packages/core/dist/chunk-NV22EMOZ.cjs","sourcesContent":[null,"/// <reference types=\"node\" />\n/**\n * `@pixerate/editor/bridge` (Node only): one MCP server process that drives editors open in\n * browser tabs. The MCP client (Claude Desktop, Claude Code, an IDE) talks MCP over stdio;\n * pages connect their `ToolRegistry` over a localhost WebSocket with `connectToolBridge`\n * (`@pixerate/editor/tools`) and answer the calls. The bridge forwards every tool the pages\n * expose, adds an `editor` argument to pick one when several are open, and `list_editors`.\n *\n * Only 127.0.0.1 is listened on; browser origins must be local (or allowed explicitly), and\n * an optional token keeps other local pages out. MCP `notifications/cancelled` cancels the\n * call in the page. Ported from Ursula's `createRemoteBridge`, built on `createMcpServer`.\n *\n * ```ts\n * import { createToolBridge } from \"@pixerate/editor/bridge\";\n * await createToolBridge({ name: \"my-app\", token: process.env.BRIDGE_TOKEN });\n * ```\n *\n * or run `npx pixerate-mcp-bridge --token <secret>` and add it to the MCP client's config.\n */\nimport { createServer, type Server as HttpServer } from 'node:http';\nimport { createInterface } from 'node:readline';\nimport { TOOL_BRIDGE_DEFAULT_PORT, TOOL_BRIDGE_PROTOCOL, type ToolBridgeEditorMessage, type ToolBridgeServerMessage } from '../tools/bridge-client.js';\nimport { createMcpServer, type JsonRpcNotification, type JsonRpcResponse, type McpServer } from '../tools/mcp-server.js';\nimport { ToolRegistry, type AgentTool, type McpToolDefinition, type McpToolResult } from '../tools/registry.js';\nimport type { JsonSchema } from '../tools/schema.js';\nimport { acceptWebSocket, rejectUpgrade, wrapWebSocket, type BridgeSocket } from './websocket.js';\n\nexport { TOOL_BRIDGE_DEFAULT_PORT, TOOL_BRIDGE_PROTOCOL } from '../tools/bridge-client.js';\n\nexport interface ToolBridgeOptions {\n\t/** The MCP server's name. */\n\tname: string;\n\tversion?: string;\n\t/** Said to the model in the MCP `initialize` result, before the bridge's own usage notes. */\n\tinstructions?: string;\n\t/** WebSocket port editors connect to (default 4477; 0 picks a free one). */\n\tport?: number;\n\t/** Shared secret pages must send as `?token=`. Recommended: any local page can otherwise connect. */\n\ttoken?: string;\n\t/** Browser origins allowed besides localhost ones (e.g. \"https://app.example.com\"). */\n\torigins?: readonly string[];\n\t/** How long a page has to answer a call (ms, default 60 000). */\n\ttimeoutMs?: number;\n\t/**\n\t * While another process holds the port, how often to try again (ms, default 2000; 0 never).\n\t * Until it gets the port, tools say why.\n\t */\n\tretryMs?: number;\n\t/**\n\t * Where MCP messages come from and go to. Default: stdio (one JSON message per line on\n\t * stdin / stdout). `false` serves no transport; call `bridge.mcp.handle` yourself.\n\t */\n\tstdio?: { input: NodeJS.ReadableStream; output: NodeJS.WritableStream } | false;\n\t/** Diagnostics (default: stderr, since stdout carries MCP). */\n\tlog?: (message: string) => void;\n}\n\n/** An editor as `list_editors` reports it. */\nexport interface ToolBridgeEditorInfo {\n\tid: string;\n\tname: string;\n\tkind?: string;\n\ttools: number;\n\t/** Seconds since the editor last connected or answered a call. */\n\tidleSeconds: number;\n}\n\nexport interface ToolBridge {\n\t/** The WebSocket port editors connect to. */\n\treadonly port: number;\n\t/** The MCP server (handle messages yourself when `stdio` is false). */\n\treadonly mcp: McpServer;\n\t/** The registry the MCP server serves: `list_editors` plus every tool the pages expose. */\n\treadonly registry: ToolRegistry;\n\t/** The connected editors. */\n\teditors(): ToolBridgeEditorInfo[];\n\t/** Stops listening, disconnects editors, fails pending calls and closes MCP. */\n\tclose(): Promise<void>;\n}\n\ninterface Editor {\n\tid: string;\n\tname: string;\n\tkind?: string;\n\tsocket: BridgeSocket;\n\ttools: McpToolDefinition[];\n\tactiveAt: number;\n\t/** Bumped on connect and on every answered call; picks the most recently active editor. */\n\tactiveSeq: number;\n}\n\ninterface Pending {\n\teditor: Editor;\n\tresolve: (result: McpToolResult) => void;\n\treject: (error: Error) => void;\n\ttimer: ReturnType<typeof setTimeout>;\n}\n\n/** Whether a browser origin is this machine (localhost, 127.0.0.1, [::1], *.localhost). */\nexport function isLocalOrigin(origin: string): boolean {\n\ttry {\n\t\tconst { hostname } = new URL(origin);\n\t\treturn hostname === 'localhost' || hostname === '127.0.0.1' || hostname === '[::1]' || hostname.endsWith('.localhost');\n\t} catch {\n\t\treturn false;\n\t}\n}\n\nconst EDITOR_ARG: JsonSchema = {\n\ttype: 'string',\n\tdescription: 'Editor id (e.g. \"e1\") or part of its name, from list_editors. Default: the most recently active editor that has this tool.'\n};\n\nconst BRIDGE_INSTRUCTIONS =\n\t'Tools come from editors open in a browser. Call list_editors to see them; when several are open, pass `editor` (an id or part of a name) to pick one, otherwise the most recently active editor that has the tool answers. If no editor is connected, ask the user to open the app and connect it to the bridge.';\n\n/**\n * Starts a bridge: listens for editors on 127.0.0.1 and serves their tools, plus\n * `list_editors`, over MCP. Resolves once the port is bound (or known to be taken).\n */\nexport async function createToolBridge(options: ToolBridgeOptions): Promise<ToolBridge> {\n\tconst log = options.log ?? ((message: string) => process.stderr.write(`[${options.name}] ${message}\\n`));\n\tconst port = options.port ?? TOOL_BRIDGE_DEFAULT_PORT;\n\tconst retryMs = options.retryMs ?? 2000;\n\tconst timeoutMs = options.timeoutMs ?? 60_000;\n\tconst extraOrigins = new Set(options.origins ?? []);\n\tconst editors = new Map<string, Editor>();\n\tconst pending = new Map<string, Pending>();\n\tlet editorCounter = 0;\n\tlet activity = 0;\n\tconst sockets = new Set<BridgeSocket>();\n\tlet callCounter = 0;\n\tlet listenError: string | null = null;\n\tlet closed = false;\n\tlet retry: ReturnType<typeof setTimeout> | undefined;\n\n\tconst send = (editor: Editor, message: ToolBridgeServerMessage) => editor.socket.send(JSON.stringify(message));\n\tconst registry = new ToolRegistry();\n\tconst listeners = new Set<() => void>();\n\tconst toolsChanged = () => listeners.forEach((l) => l());\n\n\t// ---------------------------------------------------------------- routing\n\tconst describeEditors = () => [...editors.values()].map((e) => `${e.id} (${e.name})`).join(', ');\n\tfunction pickEditor(tool: string, ref: unknown): Editor {\n\t\tif (listenError) throw new Error(listenError);\n\t\tconst all = [...editors.values()];\n\t\tif (!all.length) throw new Error(`No editor is connected to ws://127.0.0.1:${actualPort()}. Ask the user to open the app and connect it to the bridge.`);\n\t\tconst having = all.filter((e) => e.tools.some((t) => t.name === tool));\n\t\tif (typeof ref === 'string' && ref) {\n\t\t\tconst byId = editors.get(ref);\n\t\t\tconst matches = byId ? [byId] : all.filter((e) => e.name.toLowerCase().includes(ref.toLowerCase()));\n\t\t\tif (matches.length !== 1) throw new Error(`No single editor matches \"${ref}\". Connected: ${describeEditors()}.`);\n\t\t\tconst editor = matches[0]!;\n\t\t\tif (!editor.tools.some((t) => t.name === tool))\n\t\t\t\tthrow new Error(`${editor.id} (${editor.name}) has no tool \"${tool}\"${having.length ? `; ${having.map((e) => e.id).join(', ')} do` : ''}.`);\n\t\t\treturn editor;\n\t\t}\n\t\tif (!having.length) throw new Error(`No connected editor has the tool \"${tool}\". Connected: ${describeEditors()}.`);\n\t\treturn having.sort((a, b) => b.activeSeq - a.activeSeq)[0]!;\n\t}\n\n\tfunction call(editor: Editor, name: string, args: Record<string, unknown>, signal?: AbortSignal): Promise<McpToolResult> {\n\t\tconst id = `c${++callCounter}`;\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tconst giveUp = (reason: string) => {\n\t\t\t\tif (!pending.delete(id)) return;\n\t\t\t\tclearTimeout(timer);\n\t\t\t\tsend(editor, { t: 'cancel', id, reason });\n\t\t\t\treject(new Error(reason));\n\t\t\t};\n\t\t\tconst timer = setTimeout(() => giveUp(`${editor.id} (${editor.name}) did not answer within ${Math.round(timeoutMs / 1000)} s (is its tab in the background?).`), timeoutMs);\n\t\t\tpending.set(id, { editor, resolve, reject, timer });\n\t\t\tsignal?.addEventListener('abort', () => giveUp('Cancelled.'), { once: true });\n\t\t\tsend(editor, { t: 'call', id, name, arguments: args });\n\t\t});\n\t}\n\n\tconst listEditors: AgentTool = {\n\t\tname: 'list_editors',\n\t\ttitle: 'List connected editors',\n\t\tdescription:\n\t\t\t'Lists the editors open in a browser and connected to this bridge: id, name, kind, how many tools each has and how long since it was last active. Pass an id or part of a name as `editor` to other tools to pick one.',\n\t\tinputSchema: { type: 'object', properties: {}, additionalProperties: false },\n\t\tannotations: { title: 'List connected editors', readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },\n\t\thandler: () => {\n\t\t\tif (listenError) throw new Error(listenError);\n\t\t\treturn { editors: infos() };\n\t\t}\n\t};\n\n\t/** One tool per name across editors; the first editor's definition wins, with `editor` added. */\n\tconst forwardedTools = (): AgentTool[] => {\n\t\tconst seen = new Map<string, McpToolDefinition>();\n\t\tfor (const editor of editors.values()) for (const tool of editor.tools) if (!seen.has(tool.name) && tool.name !== 'list_editors') seen.set(tool.name, tool);\n\t\treturn [...seen.values()].map((tool) => {\n\t\t\tconst schema = tool.inputSchema as JsonSchema & { properties?: Record<string, JsonSchema> };\n\t\t\treturn {\n\t\t\t\t...tool,\n\t\t\t\ttitle: tool.title ?? tool.annotations?.title ?? tool.name,\n\t\t\t\tdescription: tool.description ?? tool.name,\n\t\t\t\tinputSchema: { ...schema, type: 'object', properties: { editor: EDITOR_ARG, ...(schema.properties ?? {}) } },\n\t\t\t\thandler: async (args: Record<string, any>, context: { signal?: AbortSignal }) => {\n\t\t\t\t\tconst { editor: ref, ...rest } = args;\n\t\t\t\t\tconst editor = pickEditor(tool.name, ref);\n\t\t\t\t\tconst result = await call(editor, tool.name, rest, context.signal);\n\t\t\t\t\teditor.activeAt = Date.now();\n\t\t\t\t\teditor.activeSeq = ++activity;\n\t\t\t\t\treturn result;\n\t\t\t\t}\n\t\t\t} as AgentTool;\n\t\t});\n\t};\n\n\tregistry.register({\n\t\tid: 'bridge',\n\t\ttools: () => [listEditors, ...forwardedTools()],\n\t\tsubscribe: (notify) => {\n\t\t\tlisteners.add(notify);\n\t\t\treturn () => listeners.delete(notify);\n\t\t}\n\t});\n\n\tconst infos = (): ToolBridgeEditorInfo[] =>\n\t\t[...editors.values()].map((e) => ({ id: e.id, name: e.name, ...(e.kind ? { kind: e.kind } : {}), tools: e.tools.length, idleSeconds: Math.round((Date.now() - e.activeAt) / 1000) }));\n\n\t// ------------------------------------------------------------- WebSocket\n\tconst http: HttpServer = createServer((_req, res) => {\n\t\tres.writeHead(426, { 'Content-Type': 'text/plain' });\n\t\tres.end('This is a WebSocket endpoint for editors (connectToolBridge).');\n\t});\n\thttp.on('upgrade', (req, socket) => {\n\t\tconst origin = req.headers.origin;\n\t\t// No origin: not a browser (a local script, a test). Browsers always send one.\n\t\tif (origin && !extraOrigins.has(origin) && !isLocalOrigin(origin)) {\n\t\t\tlog(`refused an editor from ${origin}`);\n\t\t\treturn rejectUpgrade(socket, 403, 'Origin not allowed');\n\t\t}\n\t\tif (options.token) {\n\t\t\tconst token = new URL(req.url ?? '/', 'http://localhost').searchParams.get('token');\n\t\t\tif (token !== options.token) {\n\t\t\t\tlog('refused an editor with a wrong token');\n\t\t\t\treturn rejectUpgrade(socket, 401, 'Bad token');\n\t\t\t}\n\t\t}\n\t\tif (!acceptWebSocket(req, socket)) return;\n\t\tconst ws = wrapWebSocket(socket);\n\t\tsockets.add(ws);\n\t\tlet editor: Editor | null = null;\n\t\tws.onMessage((text) => {\n\t\t\tlet message: ToolBridgeEditorMessage;\n\t\t\ttry {\n\t\t\t\tmessage = JSON.parse(text);\n\t\t\t} catch {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (message.t === 'hello') {\n\t\t\t\tif (message.v !== TOOL_BRIDGE_PROTOCOL) {\n\t\t\t\t\tlog(`refused an editor speaking protocol v${String(message.v)}`);\n\t\t\t\t\treturn ws.close(1008, `This bridge speaks protocol v${TOOL_BRIDGE_PROTOCOL}; update @pixerate/editor.`);\n\t\t\t\t}\n\t\t\t\teditor = { id: `e${++editorCounter}`, name: String(message.name || 'Editor'), ...(message.kind ? { kind: message.kind } : {}), socket: ws, tools: Array.isArray(message.tools) ? message.tools : [], activeAt: Date.now(), activeSeq: ++activity };\n\t\t\t\teditors.set(editor.id, editor);\n\t\t\t\tws.send(JSON.stringify({ t: 'welcome', v: TOOL_BRIDGE_PROTOCOL, id: editor.id } satisfies ToolBridgeServerMessage));\n\t\t\t\tlog(`editor ${editor.id} connected: ${editor.name} (${editor.tools.length} tools)`);\n\t\t\t\ttoolsChanged();\n\t\t\t} else if (message.t === 'tools' && editor) {\n\t\t\t\teditor.tools = Array.isArray(message.tools) ? message.tools : [];\n\t\t\t\ttoolsChanged();\n\t\t\t} else if (message.t === 'result') {\n\t\t\t\tconst waiting = pending.get(message.id);\n\t\t\t\tif (!waiting || waiting.editor !== editor) return;\n\t\t\t\tpending.delete(message.id);\n\t\t\t\tclearTimeout(waiting.timer);\n\t\t\t\twaiting.resolve(message.result);\n\t\t\t}\n\t\t});\n\t\tws.onClose(() => {\n\t\t\tsockets.delete(ws);\n\t\t\tif (!editor) return;\n\t\t\teditors.delete(editor.id);\n\t\t\tfor (const [id, waiting] of pending) {\n\t\t\t\tif (waiting.editor !== editor) continue;\n\t\t\t\tpending.delete(id);\n\t\t\t\tclearTimeout(waiting.timer);\n\t\t\t\twaiting.reject(new Error(`${editor.id} (${editor.name}) disconnected before answering.`));\n\t\t\t}\n\t\t\tlog(`editor ${editor.id} disconnected`);\n\t\t\ttoolsChanged();\n\t\t});\n\t});\n\n\tconst actualPort = () => {\n\t\tconst address = http.address();\n\t\treturn address && typeof address === 'object' ? address.port : port;\n\t};\n\tconst listen = () => http.listen(port, '127.0.0.1');\n\tconst listening = new Promise<void>((resolve) => {\n\t\thttp.on('listening', () => {\n\t\t\tif (listenError) log(`port ${port} is free now`);\n\t\t\tlistenError = null;\n\t\t\tlog(`waiting for editors on ws://127.0.0.1:${actualPort()}`);\n\t\t\tresolve();\n\t\t});\n\t\thttp.on('error', (error: NodeJS.ErrnoException) => {\n\t\t\tconst taken = error.code === 'EADDRINUSE';\n\t\t\tconst again = taken && retryMs > 0 && !closed;\n\t\t\tconst message = taken\n\t\t\t\t? `Port ${port} is in use (another bridge?). ${again ? 'This bridge keeps trying it; editors can connect once it is free. Or start' : 'Start'} this one on another port.`\n\t\t\t\t: String(error);\n\t\t\tif (message !== listenError) log(message);\n\t\t\tlistenError = message;\n\t\t\tif (again) retry = setTimeout(listen, retryMs);\n\t\t\t// Still serve MCP, so the agent hears why nothing connects.\n\t\t\tresolve();\n\t\t});\n\t});\n\tlisten();\n\n\t// ------------------------------------------------------------------- MCP\n\tlet output: NodeJS.WritableStream | null = null;\n\tconst write = (message: JsonRpcResponse | JsonRpcNotification) => output?.write(`${JSON.stringify(message)}\\n`);\n\tconst mcp = createMcpServer(registry, {\n\t\tname: options.name,\n\t\tversion: options.version ?? '0.1.0',\n\t\tinstructions: [options.instructions, BRIDGE_INSTRUCTIONS].filter(Boolean).join('\\n\\n'),\n\t\tsend: write\n\t});\n\tlet lines: ReturnType<typeof createInterface> | null = null;\n\tif (options.stdio !== false) {\n\t\tconst io = options.stdio ?? { input: process.stdin, output: process.stdout };\n\t\toutput = io.output;\n\t\tlines = createInterface({ input: io.input });\n\t\tlines.on('line', (line) => {\n\t\t\tif (!line.trim()) return;\n\t\t\tvoid mcp.handle(line).then((response) => {\n\t\t\t\tif (response) write(response);\n\t\t\t});\n\t\t});\n\t}\n\tawait listening;\n\n\treturn {\n\t\tget port() {\n\t\t\treturn actualPort();\n\t\t},\n\t\tmcp,\n\t\tregistry,\n\t\teditors: infos,\n\t\tasync close() {\n\t\t\tclosed = true;\n\t\t\tclearTimeout(retry);\n\t\t\tfor (const [id, waiting] of pending) {\n\t\t\t\tclearTimeout(waiting.timer);\n\t\t\t\twaiting.reject(new Error('The bridge closed.'));\n\t\t\t\tpending.delete(id);\n\t\t\t}\n\t\t\tfor (const socket of [...sockets]) socket.terminate();\n\t\t\teditors.clear();\n\t\t\tmcp.close();\n\t\t\tlines?.close();\n\t\t\tif (http.listening) {\n\t\t\t\tconst done = new Promise<void>((resolve) => http.close(() => resolve()));\n\t\t\t\thttp.closeAllConnections();\n\t\t\t\tawait done;\n\t\t\t}\n\t\t}\n\t};\n}\n","/// <reference types=\"node\" />\n// A small RFC 6455 WebSocket server on node:http, enough for the tool bridge: text frames\n// (fragmented or not), ping / pong, close, and client masking as browsers send it. No\n// compression or binary messages. Keeps the bridge free of a `ws` dependency.\nimport { createHash } from 'node:crypto';\nimport type { IncomingMessage } from 'node:http';\nimport type { Duplex } from 'node:stream';\n\nconst GUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';\n/** Largest message accepted from an editor (16 MiB): tool results can carry images. */\nconst MAX_MESSAGE = 16 * 1024 * 1024;\n\nexport interface BridgeSocket {\n\tsend(text: string): void;\n\tclose(code?: number, reason?: string): void;\n\t/** Closes without waiting for the other side (shutting down). */\n\tterminate(): void;\n\tonMessage(listener: (text: string) => void): void;\n\tonClose(listener: (code: number, reason: string) => void): void;\n\treadonly open: boolean;\n}\n\n/** Answers the HTTP upgrade handshake; returns false (and writes a 400) for a bad request. */\nexport function acceptWebSocket(req: IncomingMessage, socket: Duplex): boolean {\n\tconst key = req.headers['sec-websocket-key'];\n\tif (req.headers.upgrade?.toLowerCase() !== 'websocket' || typeof key !== 'string' || req.headers['sec-websocket-version'] !== '13') {\n\t\trejectUpgrade(socket, 400, 'Bad Request');\n\t\treturn false;\n\t}\n\tconst accept = createHash('sha1').update(key + GUID).digest('base64');\n\tsocket.write(`HTTP/1.1 101 Switching Protocols\\r\\nUpgrade: websocket\\r\\nConnection: Upgrade\\r\\nSec-WebSocket-Accept: ${accept}\\r\\n\\r\\n`);\n\treturn true;\n}\n\n/** Refuses an upgrade with an HTTP status (the browser sees the connection fail). */\nexport function rejectUpgrade(socket: Duplex, status: number, message: string): void {\n\tsocket.write(`HTTP/1.1 ${status} ${message}\\r\\nConnection: close\\r\\nContent-Type: text/plain\\r\\nContent-Length: ${Buffer.byteLength(message)}\\r\\n\\r\\n${message}`);\n\tsocket.destroy();\n}\n\nfunction frame(opcode: number, payload: Buffer): Buffer {\n\tconst length = payload.length;\n\tconst head = length < 126 ? Buffer.alloc(2) : length < 65536 ? Buffer.alloc(4) : Buffer.alloc(10);\n\thead[0] = 0x80 | opcode;\n\tif (length < 126) head[1] = length;\n\telse if (length < 65536) {\n\t\thead[1] = 126;\n\t\thead.writeUInt16BE(length, 2);\n\t} else {\n\t\thead[1] = 127;\n\t\thead.writeBigUInt64BE(BigInt(length), 2);\n\t}\n\treturn Buffer.concat([head, payload]);\n}\n\n/** Wraps an upgraded socket as a text-message WebSocket. */\nexport function wrapWebSocket(socket: Duplex): BridgeSocket {\n\tlet buffer = Buffer.alloc(0);\n\tlet fragments: Buffer[] = [];\n\tlet open = true;\n\tconst messageListeners: Array<(text: string) => void> = [];\n\tconst closeListeners: Array<(code: number, reason: string) => void> = [];\n\tlet closeInfo: { code: number; reason: string } | null = null;\n\n\tconst finish = (code: number, reason: string) => {\n\t\tif (closeInfo) return;\n\t\tcloseInfo = { code, reason };\n\t\topen = false;\n\t\tcloseListeners.forEach((l) => l(code, reason));\n\t};\n\tconst close = (code = 1000, reason = '') => {\n\t\tif (!open) return;\n\t\topen = false;\n\t\tconst body = Buffer.alloc(2 + Buffer.byteLength(reason));\n\t\tbody.writeUInt16BE(code, 0);\n\t\tbody.write(reason, 2);\n\t\tsocket.write(frame(0x8, body));\n\t\tsocket.end();\n\t\t// A peer that never answers the close must not keep the server open.\n\t\tsetTimeout(() => socket.destroy(), 1000).unref?.();\n\t\tfinish(code, reason);\n\t};\n\n\tsocket.on('data', (chunk: Buffer) => {\n\t\tbuffer = Buffer.concat([buffer, chunk]);\n\t\twhile (buffer.length >= 2) {\n\t\t\tconst fin = (buffer[0]! & 0x80) !== 0;\n\t\t\tconst opcode = buffer[0]! & 0x0f;\n\t\t\tconst masked = (buffer[1]! & 0x80) !== 0;\n\t\t\tlet length = buffer[1]! & 0x7f;\n\t\t\tlet offset = 2;\n\t\t\tif (length === 126) {\n\t\t\t\tif (buffer.length < 4) return;\n\t\t\t\tlength = buffer.readUInt16BE(2);\n\t\t\t\toffset = 4;\n\t\t\t} else if (length === 127) {\n\t\t\t\tif (buffer.length < 10) return;\n\t\t\t\tconst big = buffer.readBigUInt64BE(2);\n\t\t\t\tif (big > BigInt(MAX_MESSAGE)) return close(1009, 'Message too big.');\n\t\t\t\tlength = Number(big);\n\t\t\t\toffset = 10;\n\t\t\t}\n\t\t\t// Clients must mask every frame (RFC 6455 §5.1).\n\t\t\tif (!masked) return close(1002, 'Unmasked frame.');\n\t\t\tif (buffer.length < offset + 4 + length) return;\n\t\t\tconst mask = buffer.subarray(offset, offset + 4);\n\t\t\tconst payload = Buffer.from(buffer.subarray(offset + 4, offset + 4 + length));\n\t\t\tfor (let i = 0; i < payload.length; i++) payload[i] = payload[i]! ^ mask[i % 4]!;\n\t\t\tbuffer = buffer.subarray(offset + 4 + length);\n\n\t\t\tif (opcode === 0x8) {\n\t\t\t\tconst code = payload.length >= 2 ? payload.readUInt16BE(0) : 1005;\n\t\t\t\tconst reason = payload.length > 2 ? payload.subarray(2).toString('utf8') : '';\n\t\t\t\tif (open) {\n\t\t\t\t\topen = false;\n\t\t\t\t\tsocket.write(frame(0x8, payload.subarray(0, 2)));\n\t\t\t\t\tsocket.end();\n\t\t\t\t}\n\t\t\t\treturn finish(code, reason);\n\t\t\t}\n\t\t\tif (opcode === 0x9) {\n\t\t\t\tsocket.write(frame(0xa, payload));\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (opcode === 0xa) continue;\n\t\t\tif (opcode === 0x2) return close(1003, 'Only text messages are supported.');\n\t\t\tif (opcode === 0x1 || opcode === 0x0) {\n\t\t\t\tfragments.push(payload);\n\t\t\t\tif (fragments.reduce((n, f) => n + f.length, 0) > MAX_MESSAGE) return close(1009, 'Message too big.');\n\t\t\t\tif (fin) {\n\t\t\t\t\tconst text = Buffer.concat(fragments).toString('utf8');\n\t\t\t\t\tfragments = [];\n\t\t\t\t\tmessageListeners.forEach((l) => l(text));\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t});\n\tsocket.on('close', () => finish(1006, ''));\n\tsocket.on('error', () => finish(1006, ''));\n\n\treturn {\n\t\tget open() {\n\t\t\treturn open;\n\t\t},\n\t\tsend(text) {\n\t\t\tif (open) socket.write(frame(0x1, Buffer.from(text, 'utf8')));\n\t\t},\n\t\tclose,\n\t\tterminate() {\n\t\t\tif (open) close(1001, 'Going away.');\n\t\t\tsocket.destroy();\n\t\t},\n\t\tonMessage(listener) {\n\t\t\tmessageListeners.push(listener);\n\t\t},\n\t\tonClose(listener) {\n\t\t\tcloseListeners.push(listener);\n\t\t}\n\t};\n}\n"]}
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
CHART_OOXML_NS,
|
|
9
9
|
chartFromOoxml,
|
|
10
10
|
readZip
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-3S72F46R.js";
|
|
12
12
|
import {
|
|
13
13
|
Chart,
|
|
14
14
|
MAX_TABLE_COLUMNS,
|
|
@@ -32,10 +32,10 @@ import {
|
|
|
32
32
|
searchDocument,
|
|
33
33
|
sliceContainsTable,
|
|
34
34
|
syncInitialContent
|
|
35
|
-
} from "./chunk-
|
|
35
|
+
} from "./chunk-RIXTW3C4.js";
|
|
36
36
|
import {
|
|
37
37
|
chartDataToTable
|
|
38
|
-
} from "./chunk-
|
|
38
|
+
} from "./chunk-633QC34G.js";
|
|
39
39
|
import {
|
|
40
40
|
compareDocuments
|
|
41
41
|
} from "./chunk-QJSOZFCZ.js";
|
|
@@ -6753,4 +6753,4 @@ export {
|
|
|
6753
6753
|
validateFindPattern,
|
|
6754
6754
|
createTableCommands
|
|
6755
6755
|
};
|
|
6756
|
-
//# sourceMappingURL=chunk-
|
|
6756
|
+
//# sourceMappingURL=chunk-OIKYP7XC.js.map
|
|
@@ -6,11 +6,11 @@
|
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
|
|
9
|
-
var
|
|
9
|
+
var _chunkNK3WL5TJcjs = require('./chunk-NK3WL5TJ.cjs');
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
|
|
13
|
-
var
|
|
13
|
+
var _chunkCKRNELPOcjs = require('./chunk-CKRNELPO.cjs');
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
var _chunkIFHKKVXHcjs = require('./chunk-IFHKKVXH.cjs');
|
|
@@ -1204,7 +1204,7 @@ var ChartNodeView = (_class = class {
|
|
|
1204
1204
|
this.dom.contentEditable = "false";
|
|
1205
1205
|
this.syncAttributes();
|
|
1206
1206
|
this.key = specKey(this.node.attrs.spec);
|
|
1207
|
-
this.controller = new (0,
|
|
1207
|
+
this.controller = new (0, _chunkNK3WL5TJcjs.ChartController)({ spec: this.node.attrs.spec });
|
|
1208
1208
|
this.unsubscribe = this.controller.subscribe((state) => {
|
|
1209
1209
|
if (!state.gesture) this.writeBack();
|
|
1210
1210
|
});
|
|
@@ -1437,7 +1437,7 @@ function readSpec(value) {
|
|
|
1437
1437
|
}
|
|
1438
1438
|
}
|
|
1439
1439
|
if (!input || typeof input !== "object" || Array.isArray(input)) return null;
|
|
1440
|
-
return
|
|
1440
|
+
return _chunkCKRNELPOcjs.normalizeChartSpec.call(void 0, input).spec;
|
|
1441
1441
|
}
|
|
1442
1442
|
var readSize = (value) => {
|
|
1443
1443
|
const n = value === null ? NaN : Number(value);
|
|
@@ -1463,10 +1463,10 @@ function staticChartSvg(node, options) {
|
|
|
1463
1463
|
const spec = node.attrs.spec;
|
|
1464
1464
|
const width = _nullishCoalesce(node.attrs.width, () => ( options.defaultWidth));
|
|
1465
1465
|
const height = _nullishCoalesce(node.attrs.height, () => ( options.defaultHeight));
|
|
1466
|
-
const scene =
|
|
1467
|
-
return
|
|
1466
|
+
const scene = _chunkNK3WL5TJcjs.layoutChart.call(void 0, spec, { width, height, theme: options.theme, selection: null });
|
|
1467
|
+
return _chunkNK3WL5TJcjs.chartSceneToDomSpec.call(void 0, scene, {
|
|
1468
1468
|
ariaLabel: spec.title || "Chart",
|
|
1469
|
-
description:
|
|
1469
|
+
description: _chunkNK3WL5TJcjs.describeChart.call(void 0, spec),
|
|
1470
1470
|
attrs: { style: "max-width: 100%; height: auto; display: block" }
|
|
1471
1471
|
});
|
|
1472
1472
|
}
|
|
@@ -1482,7 +1482,7 @@ var Chart = _core.Node.create({
|
|
|
1482
1482
|
return {
|
|
1483
1483
|
HTMLAttributes: {},
|
|
1484
1484
|
nodeView: null,
|
|
1485
|
-
theme:
|
|
1485
|
+
theme: _chunkCKRNELPOcjs.DEFAULT_CHART_THEME,
|
|
1486
1486
|
defaultWidth: 640,
|
|
1487
1487
|
defaultHeight: 320,
|
|
1488
1488
|
generateId: () => generateStructureId("chart")
|
|
@@ -1496,7 +1496,7 @@ var Chart = _core.Node.create({
|
|
|
1496
1496
|
renderHTML: (attributes) => attributes.id ? { "data-chart-id": attributes.id } : {}
|
|
1497
1497
|
},
|
|
1498
1498
|
spec: {
|
|
1499
|
-
default:
|
|
1499
|
+
default: _chunkCKRNELPOcjs.normalizeChartSpec.call(void 0, {}).spec,
|
|
1500
1500
|
// null (no `data-chart`) keeps what the parse rule found, e.g. a mermaid block's spec.
|
|
1501
1501
|
parseHTML: (element) => readSpec(element.getAttribute("data-chart")),
|
|
1502
1502
|
renderHTML: (attributes) => ({
|
|
@@ -1534,7 +1534,7 @@ var Chart = _core.Node.create({
|
|
|
1534
1534
|
const code = element.querySelector("code");
|
|
1535
1535
|
const language = _nullishCoalesce(_nullishCoalesce(_optionalChain([code, 'optionalAccess', _33 => _33.getAttribute, 'call', _34 => _34("class")]), () => ( element.getAttribute("data-language"))), () => ( ""));
|
|
1536
1536
|
if (!/(?:^|\s)language-mermaid(?:\s|$)/.test(language) && language !== "mermaid") return false;
|
|
1537
|
-
const result =
|
|
1537
|
+
const result = _chunkNK3WL5TJcjs.chartFromMermaid.call(void 0, _nullishCoalesce((_nullishCoalesce(code, () => ( element))).textContent, () => ( "")));
|
|
1538
1538
|
return result.family ? { spec: result.spec } : false;
|
|
1539
1539
|
}
|
|
1540
1540
|
}
|
|
@@ -1558,7 +1558,7 @@ var Chart = _core.Node.create({
|
|
|
1558
1558
|
return {
|
|
1559
1559
|
markdown: {
|
|
1560
1560
|
serialize(state, node) {
|
|
1561
|
-
const text =
|
|
1561
|
+
const text = _chunkNK3WL5TJcjs.chartToMermaid.call(void 0, node.attrs.spec);
|
|
1562
1562
|
const fence = "`".repeat(Math.max(3, longestBacktickRun(text) + 1));
|
|
1563
1563
|
state.write(`${fence}mermaid
|
|
1564
1564
|
`);
|
|
@@ -1580,7 +1580,7 @@ var Chart = _core.Node.create({
|
|
|
1580
1580
|
type: this.name,
|
|
1581
1581
|
attrs: {
|
|
1582
1582
|
id: _nullishCoalesce(options.id, () => ( this.options.generateId())),
|
|
1583
|
-
spec:
|
|
1583
|
+
spec: _chunkCKRNELPOcjs.normalizeChartSpec.call(void 0, _nullishCoalesce(options.spec, () => ( {}))).spec,
|
|
1584
1584
|
width: _nullishCoalesce(options.width, () => ( null)),
|
|
1585
1585
|
height: _nullishCoalesce(options.height, () => ( this.options.defaultHeight))
|
|
1586
1586
|
}
|
|
@@ -1591,7 +1591,7 @@ var Chart = _core.Node.create({
|
|
|
1591
1591
|
if (dispatch) {
|
|
1592
1592
|
tr.setNodeMarkup(chart.pos, void 0, {
|
|
1593
1593
|
...chart.node.attrs,
|
|
1594
|
-
...changes.spec !== void 0 ? { spec:
|
|
1594
|
+
...changes.spec !== void 0 ? { spec: _chunkCKRNELPOcjs.normalizeChartSpec.call(void 0, changes.spec).spec } : {},
|
|
1595
1595
|
...changes.width !== void 0 ? { width: changes.width } : {},
|
|
1596
1596
|
...changes.height !== void 0 ? { height: changes.height } : {}
|
|
1597
1597
|
});
|
|
@@ -1698,14 +1698,14 @@ var Sparkline = _core.Node.create({
|
|
|
1698
1698
|
return [{ tag: 'span[data-type="sparkline"]', priority: 60 }];
|
|
1699
1699
|
},
|
|
1700
1700
|
renderHTML({ node, HTMLAttributes }) {
|
|
1701
|
-
const scene =
|
|
1701
|
+
const scene = _chunkNK3WL5TJcjs.layoutSparkline.call(void 0, node.attrs.values, {
|
|
1702
1702
|
kind: node.attrs.kind,
|
|
1703
1703
|
width: node.attrs.width,
|
|
1704
1704
|
height: node.attrs.height,
|
|
1705
1705
|
color: _nullishCoalesce(node.attrs.color, () => ( this.options.color)),
|
|
1706
1706
|
negativeColor: this.options.negativeColor
|
|
1707
1707
|
});
|
|
1708
|
-
const svg =
|
|
1708
|
+
const svg = _chunkNK3WL5TJcjs.chartSceneToDomSpec.call(void 0, scene, {
|
|
1709
1709
|
ariaLabel: sparklineLabel(node),
|
|
1710
1710
|
attrs: { style: "display: inline-block; vertical-align: middle" }
|
|
1711
1711
|
});
|
|
@@ -2057,4 +2057,4 @@ function createTableStore(editor) {
|
|
|
2057
2057
|
|
|
2058
2058
|
|
|
2059
2059
|
exports.ALLOW_NESTED_TABLES_META = ALLOW_NESTED_TABLES_META; exports.tableNestingPluginKey = tableNestingPluginKey; exports.outermostTableDepth = outermostTableDepth; exports.isInTableCell = isInTableCell; exports.sliceContainsTable = sliceContainsTable; exports.insertSliceAfterTable = insertSliceAfterTable; exports.handleTablePasteWithoutNesting = handleTablePasteWithoutNesting; exports.unnestTables = unnestTables; exports.createTableNestingPlugin = createTableNestingPlugin; exports.generateStructureId = generateStructureId; exports.presenceStateSpec = presenceStateSpec; exports.arraysEqual = arraysEqual; exports.scrollDomAtPos = scrollDomAtPos; exports.syncInitialContent = syncInitialContent; exports.STRUCTURE_PRIORITY = STRUCTURE_PRIORITY; exports.FIND_PARAGRAPH_MARK = FIND_PARAGRAPH_MARK; exports.MAX_FIND_PATTERN_LENGTH = MAX_FIND_PATTERN_LENGTH; exports.createSearchBudget = createSearchBudget; exports.foldCodePoint = foldCodePoint; exports.compileFindPattern = compileFindPattern; exports.findAllMatches = findAllMatches; exports.parseReplacement = parseReplacement; exports.expandReplacement = expandReplacement; exports.searchDocument = searchDocument; exports.applyReplacements = applyReplacements; exports.normalizeTableCellAlignment = normalizeTableCellAlignment; exports.normalizeTableCellColor = normalizeTableCellColor; exports.isGfmTable = isGfmTable; exports.DEFAULT_TABLE_OPTIONS = DEFAULT_TABLE_OPTIONS; exports.Table = Table; exports.TableRow = TableRow; exports.TableCell = TableCell; exports.TableHeader = TableHeader; exports.createTableExtensions = createTableExtensions; exports.ChartNodeView = ChartNodeView; exports.createChartNodeView = createChartNodeView; exports.chartPluginKey = chartPluginKey; exports.getDocumentCharts = getDocumentCharts; exports.isChartEnabled = isChartEnabled; exports.staticChartSvg = staticChartSvg; exports.Chart = Chart; exports.SPARKLINE_KINDS = SPARKLINE_KINDS; exports.MAX_SPARKLINE_VALUES = MAX_SPARKLINE_VALUES; exports.normalizeSparklineValues = normalizeSparklineValues; exports.sparklineLabel = sparklineLabel; exports.Sparkline = Sparkline; exports.TABLE_ACTIONS = TABLE_ACTIONS; exports.DEFAULT_TABLE_TOOLBAR = DEFAULT_TABLE_TOOLBAR; exports.MAX_TABLE_ROWS = MAX_TABLE_ROWS; exports.MAX_TABLE_COLUMNS = MAX_TABLE_COLUMNS; exports.isTableEnabled = isTableEnabled; exports.isSelectionInTable = isSelectionInTable; exports.shouldShowTableBubbleMenu = shouldShowTableBubbleMenu; exports.getTableActionState = getTableActionState; exports.runTableAction = runTableAction; exports.EMPTY_TABLE_STATE = EMPTY_TABLE_STATE; exports.getTableState = getTableState; exports.createTableStore = createTableStore;
|
|
2060
|
-
//# sourceMappingURL=chunk-
|
|
2060
|
+
//# sourceMappingURL=chunk-P43HUQRX.cjs.map
|