@nimblebrain/synapse 0.1.0 → 0.1.2
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/chunk-42N5BB5O.cjs +118 -0
- package/dist/chunk-42N5BB5O.cjs.map +1 -0
- package/dist/{chunk-AW3YIXLE.cjs → chunk-HLT5UBJF.cjs} +2 -116
- package/dist/chunk-HLT5UBJF.cjs.map +1 -0
- package/dist/{chunk-M4I222LB.js → chunk-JKHGWDZI.js} +3 -114
- package/dist/chunk-JKHGWDZI.js.map +1 -0
- package/dist/chunk-YWX3D24J.js +114 -0
- package/dist/chunk-YWX3D24J.js.map +1 -0
- package/dist/codegen/cli.cjs +58 -47
- package/dist/codegen/cli.cjs.map +1 -1
- package/dist/codegen/cli.js +58 -47
- package/dist/codegen/cli.js.map +1 -1
- package/dist/codegen/index.cjs +9 -8
- package/dist/codegen/index.js +2 -1
- package/dist/schema-reader-776246MJ.js +3 -0
- package/dist/schema-reader-776246MJ.js.map +1 -0
- package/dist/schema-reader-B6LTGBMJ.cjs +20 -0
- package/dist/schema-reader-B6LTGBMJ.cjs.map +1 -0
- package/dist/server-RUCX2TIB.js +204 -0
- package/dist/server-RUCX2TIB.js.map +1 -0
- package/dist/server-SEI7XI3B.cjs +206 -0
- package/dist/server-SEI7XI3B.cjs.map +1 -0
- package/dist/type-generator-MZ4X4DE5.js +3 -0
- package/dist/type-generator-MZ4X4DE5.js.map +1 -0
- package/dist/type-generator-UA3L4OIA.cjs +12 -0
- package/dist/type-generator-UA3L4OIA.cjs.map +1 -0
- package/dist/vite/index.cjs +248 -18
- package/dist/vite/index.cjs.map +1 -1
- package/dist/vite/index.d.cts +26 -12
- package/dist/vite/index.d.ts +26 -12
- package/dist/vite/index.js +248 -18
- package/dist/vite/index.js.map +1 -1
- package/dist/writer-NDK4U6C5.cjs +14 -0
- package/dist/writer-NDK4U6C5.cjs.map +1 -0
- package/dist/writer-ZNBXYUYC.js +12 -0
- package/dist/writer-ZNBXYUYC.js.map +1 -0
- package/package.json +13 -10
- package/dist/chunk-AW3YIXLE.cjs.map +0 -1
- package/dist/chunk-M4I222LB.js.map +0 -1
package/dist/vite/index.cjs
CHANGED
|
@@ -1,48 +1,278 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
var child_process = require('child_process');
|
|
4
|
+
var fs = require('fs');
|
|
5
|
+
var path = require('path');
|
|
6
|
+
|
|
3
7
|
// src/vite/plugin.ts
|
|
4
|
-
function synapseVite(options) {
|
|
5
|
-
const
|
|
8
|
+
function synapseVite(options = {}) {
|
|
9
|
+
const enablePreview = options.preview !== false;
|
|
10
|
+
let manifest = null;
|
|
11
|
+
let appName = options.appName ?? "app";
|
|
12
|
+
let serverProcess = null;
|
|
13
|
+
let pendingRequests = /* @__PURE__ */ new Map();
|
|
14
|
+
let serverBuffer = "";
|
|
15
|
+
function loadManifest(root) {
|
|
16
|
+
const manifestPath = options.manifest ? path.resolve(options.manifest) : path.resolve(root, "..", "manifest.json");
|
|
17
|
+
if (!fs.existsSync(manifestPath)) return null;
|
|
18
|
+
try {
|
|
19
|
+
return JSON.parse(fs.readFileSync(manifestPath, "utf-8"));
|
|
20
|
+
} catch {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
function deriveServerCmd(m, root) {
|
|
25
|
+
if (options.serverCmd) return options.serverCmd;
|
|
26
|
+
const cfg = m.server?.mcp_config;
|
|
27
|
+
if (!cfg?.command) return null;
|
|
28
|
+
const serverDir = path.resolve(root, "..");
|
|
29
|
+
let cmd = cfg.command;
|
|
30
|
+
const args = cfg.args ?? [];
|
|
31
|
+
if (cmd === "python" && fs.existsSync(path.join(serverDir, "pyproject.toml"))) {
|
|
32
|
+
cmd = "uv run python";
|
|
33
|
+
}
|
|
34
|
+
return `cd ${JSON.stringify(serverDir)} && ${cmd} ${args.join(" ")}`;
|
|
35
|
+
}
|
|
36
|
+
function startServer(cmd) {
|
|
37
|
+
serverProcess = child_process.spawn(cmd, {
|
|
38
|
+
shell: true,
|
|
39
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
40
|
+
});
|
|
41
|
+
serverProcess.stderr?.on("data", (d) => {
|
|
42
|
+
process.stderr.write(` [mcp] ${d}`);
|
|
43
|
+
});
|
|
44
|
+
serverProcess.stdout?.on("data", (d) => {
|
|
45
|
+
serverBuffer += d.toString();
|
|
46
|
+
const lines = serverBuffer.split("\n");
|
|
47
|
+
serverBuffer = lines.pop();
|
|
48
|
+
for (const line of lines) {
|
|
49
|
+
if (!line.trim()) continue;
|
|
50
|
+
try {
|
|
51
|
+
const msg = JSON.parse(line);
|
|
52
|
+
if (msg.id && pendingRequests.has(msg.id)) {
|
|
53
|
+
const p = pendingRequests.get(msg.id);
|
|
54
|
+
pendingRequests.delete(msg.id);
|
|
55
|
+
p.resolve(msg);
|
|
56
|
+
}
|
|
57
|
+
} catch {
|
|
58
|
+
process.stderr.write(` [mcp] ${line}
|
|
59
|
+
`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
serverProcess.on("exit", (code) => {
|
|
64
|
+
if (code !== null && code !== 0) {
|
|
65
|
+
console.error(` [mcp] Server exited with code ${code}`);
|
|
66
|
+
}
|
|
67
|
+
serverProcess = null;
|
|
68
|
+
});
|
|
69
|
+
sendToServer({
|
|
70
|
+
jsonrpc: "2.0",
|
|
71
|
+
id: "init-1",
|
|
72
|
+
method: "initialize",
|
|
73
|
+
params: {
|
|
74
|
+
protocolVersion: "2024-11-05",
|
|
75
|
+
capabilities: {},
|
|
76
|
+
clientInfo: { name: "synapse-preview", version: "0.1.0" }
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
function sendToServer(msg) {
|
|
81
|
+
if (!serverProcess?.stdin?.writable) return;
|
|
82
|
+
serverProcess.stdin.write(JSON.stringify(msg) + "\n");
|
|
83
|
+
}
|
|
84
|
+
function callServerTool(name, args) {
|
|
85
|
+
return new Promise((resolve2, reject) => {
|
|
86
|
+
const id = `preview-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
87
|
+
pendingRequests.set(id, { resolve: resolve2, reject });
|
|
88
|
+
sendToServer({
|
|
89
|
+
jsonrpc: "2.0",
|
|
90
|
+
id,
|
|
91
|
+
method: "tools/call",
|
|
92
|
+
params: { name, arguments: args }
|
|
93
|
+
});
|
|
94
|
+
setTimeout(() => {
|
|
95
|
+
if (pendingRequests.has(id)) {
|
|
96
|
+
pendingRequests.delete(id);
|
|
97
|
+
reject(new Error("Tool call timed out (10s)"));
|
|
98
|
+
}
|
|
99
|
+
}, 1e4);
|
|
100
|
+
});
|
|
101
|
+
}
|
|
6
102
|
return {
|
|
7
103
|
name: "synapse",
|
|
8
|
-
config() {
|
|
104
|
+
config(_, { command }) {
|
|
9
105
|
return {
|
|
10
106
|
define: {
|
|
11
|
-
"import.meta.env.SYNAPSE_PLATFORM_URL": JSON.stringify(platformUrl),
|
|
12
107
|
"import.meta.env.SYNAPSE_APP_NAME": JSON.stringify(appName)
|
|
13
108
|
},
|
|
14
109
|
server: {
|
|
15
110
|
hmr: {
|
|
16
|
-
// Required for HMR inside sandboxed iframe
|
|
17
111
|
protocol: "ws",
|
|
18
112
|
host: "localhost"
|
|
19
113
|
}
|
|
20
114
|
}
|
|
21
115
|
};
|
|
22
116
|
},
|
|
117
|
+
configResolved(config) {
|
|
118
|
+
manifest = loadManifest(config.root);
|
|
119
|
+
if (manifest?.name) {
|
|
120
|
+
appName = options.appName ?? manifest.name;
|
|
121
|
+
}
|
|
122
|
+
},
|
|
23
123
|
configureServer(server) {
|
|
24
|
-
|
|
124
|
+
if (enablePreview && manifest) {
|
|
125
|
+
const cmd = deriveServerCmd(manifest, server.config.root);
|
|
126
|
+
if (cmd) {
|
|
127
|
+
console.log(`
|
|
128
|
+
[synapse] Starting MCP server: ${cmd}
|
|
129
|
+
`);
|
|
130
|
+
startServer(cmd);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
server.middlewares.use((req, res, next) => {
|
|
25
134
|
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
26
135
|
res.setHeader("Access-Control-Allow-Methods", "*");
|
|
27
136
|
res.setHeader("Access-Control-Allow-Headers", "*");
|
|
137
|
+
if (req.url === "/__preview" || req.url === "/__preview/") {
|
|
138
|
+
const port = server.config.server.port ?? 5173;
|
|
139
|
+
res.writeHead(200, { "Content-Type": "text/html" });
|
|
140
|
+
res.end(previewHostHtml(appName, port));
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
if (req.method === "POST" && req.url === "/__mcp") {
|
|
144
|
+
let body = "";
|
|
145
|
+
req.on("data", (chunk) => {
|
|
146
|
+
body += chunk.toString();
|
|
147
|
+
});
|
|
148
|
+
req.on("end", async () => {
|
|
149
|
+
try {
|
|
150
|
+
const msg = JSON.parse(body);
|
|
151
|
+
const result = await callServerTool(msg.params.name, msg.params.arguments || {});
|
|
152
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
153
|
+
res.end(JSON.stringify(result));
|
|
154
|
+
} catch (err) {
|
|
155
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
156
|
+
res.end(JSON.stringify({
|
|
157
|
+
jsonrpc: "2.0",
|
|
158
|
+
id: JSON.parse(body).id,
|
|
159
|
+
error: { code: -32e3, message: err.message }
|
|
160
|
+
}));
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
28
165
|
next();
|
|
29
166
|
});
|
|
30
167
|
},
|
|
31
|
-
|
|
32
|
-
if (
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
window.__synapse = createSynapse({
|
|
37
|
-
name: ${JSON.stringify(appName)},
|
|
38
|
-
version: "0.0.0-dev",
|
|
39
|
-
});
|
|
40
|
-
</script>`;
|
|
41
|
-
return html.replace("</head>", `${bridgeScript}
|
|
42
|
-
</head>`);
|
|
168
|
+
buildEnd() {
|
|
169
|
+
if (serverProcess) {
|
|
170
|
+
serverProcess.kill("SIGTERM");
|
|
171
|
+
serverProcess = null;
|
|
172
|
+
}
|
|
43
173
|
}
|
|
44
174
|
};
|
|
45
175
|
}
|
|
176
|
+
function previewHostHtml(appName, port) {
|
|
177
|
+
return `<!DOCTYPE html>
|
|
178
|
+
<html>
|
|
179
|
+
<head>
|
|
180
|
+
<meta charset="utf-8" />
|
|
181
|
+
<title>${appName} \u2014 Synapse Preview</title>
|
|
182
|
+
<style>
|
|
183
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
184
|
+
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; background: #0f172a; color: #e2e8f0; }
|
|
185
|
+
header { padding: 10px 16px; background: #1e293b; border-bottom: 1px solid #334155; display: flex; align-items: center; gap: 10px; font-size: 13px; }
|
|
186
|
+
header .dot { width: 8px; height: 8px; border-radius: 50%; background: #22c55e; }
|
|
187
|
+
header .name { font-weight: 600; }
|
|
188
|
+
header .spacer { flex: 1; }
|
|
189
|
+
header button { background: #334155; border: none; color: #e2e8f0; padding: 3px 8px; border-radius: 4px; cursor: pointer; font-size: 12px; }
|
|
190
|
+
header .url { color: #64748b; font-size: 11px; font-family: monospace; }
|
|
191
|
+
iframe { width: 100%; height: calc(100vh - 41px); border: none; }
|
|
192
|
+
</style>
|
|
193
|
+
</head>
|
|
194
|
+
<body>
|
|
195
|
+
<header>
|
|
196
|
+
<span class="dot"></span>
|
|
197
|
+
<span class="name">${appName}</span>
|
|
198
|
+
<span class="spacer"></span>
|
|
199
|
+
<button id="toggle">Toggle Theme</button>
|
|
200
|
+
<span class="url">localhost:${port}/__preview</span>
|
|
201
|
+
</header>
|
|
202
|
+
<iframe id="app" src="http://localhost:${port}/"></iframe>
|
|
203
|
+
|
|
204
|
+
<script>
|
|
205
|
+
var iframe = document.getElementById("app");
|
|
206
|
+
var dark = true;
|
|
207
|
+
|
|
208
|
+
function getTokens(d) {
|
|
209
|
+
return d ? {
|
|
210
|
+
"--nb-background":"#0f172a","--nb-foreground":"#e2e8f0",
|
|
211
|
+
"--nb-card":"#1e293b","--nb-card-foreground":"#e2e8f0",
|
|
212
|
+
"--nb-primary":"#6366f1","--nb-primary-foreground":"#fff",
|
|
213
|
+
"--nb-muted-foreground":"#94a3b8","--nb-border":"#334155",
|
|
214
|
+
"--nb-ring":"#6366f1","--nb-destructive":"#ef4444",
|
|
215
|
+
"--nb-radius":"0.5rem","--nb-font-sans":"-apple-system,BlinkMacSystemFont,sans-serif"
|
|
216
|
+
} : {
|
|
217
|
+
"--nb-background":"#ffffff","--nb-foreground":"#0f172a",
|
|
218
|
+
"--nb-card":"#f8fafc","--nb-card-foreground":"#0f172a",
|
|
219
|
+
"--nb-primary":"#6366f1","--nb-primary-foreground":"#fff",
|
|
220
|
+
"--nb-muted-foreground":"#64748b","--nb-border":"#e2e8f0",
|
|
221
|
+
"--nb-ring":"#6366f1","--nb-destructive":"#ef4444",
|
|
222
|
+
"--nb-radius":"0.5rem","--nb-font-sans":"-apple-system,BlinkMacSystemFont,sans-serif"
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function post(msg) { iframe.contentWindow.postMessage(msg, "*"); }
|
|
227
|
+
|
|
228
|
+
window.addEventListener("message", async function(e) {
|
|
229
|
+
if (e.source !== iframe.contentWindow) return;
|
|
230
|
+
var msg = e.data;
|
|
231
|
+
if (!msg || typeof msg !== "object") return;
|
|
232
|
+
|
|
233
|
+
// ext-apps handshake
|
|
234
|
+
if (msg.method === "ui/initialize" && msg.id) {
|
|
235
|
+
post({ jsonrpc:"2.0", id:msg.id, result: {
|
|
236
|
+
protocolVersion:"2026-01-26",
|
|
237
|
+
serverInfo:{name:"nimblebrain",version:"preview"},
|
|
238
|
+
capabilities:{openLinks:{},serverTools:{}},
|
|
239
|
+
hostContext:{theme:dark?"dark":"light",primaryColor:"#6366f1",tokens:getTokens(dark)}
|
|
240
|
+
}});
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
if (msg.method === "ui/notifications/initialized") return;
|
|
244
|
+
|
|
245
|
+
// Tool calls \u2014 proxy via Vite middleware
|
|
246
|
+
if (msg.method === "tools/call" && msg.id) {
|
|
247
|
+
try {
|
|
248
|
+
var r = await fetch("/__mcp", {
|
|
249
|
+
method:"POST", headers:{"Content-Type":"application/json"},
|
|
250
|
+
body: JSON.stringify({jsonrpc:"2.0",id:msg.id,method:"tools/call",params:{name:msg.params.name,arguments:msg.params.arguments||{}}})
|
|
251
|
+
});
|
|
252
|
+
post(await r.json());
|
|
253
|
+
} catch(err) {
|
|
254
|
+
post({jsonrpc:"2.0",id:msg.id,error:{code:-32000,message:err.message}});
|
|
255
|
+
}
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Log other messages
|
|
260
|
+
if (msg.method === "ui/chat") console.log("[chat]", msg.params?.message);
|
|
261
|
+
else if (msg.method === "ui/action") console.log("[action]", msg.params?.action, msg.params);
|
|
262
|
+
else if (msg.method === "ui/stateChanged") { console.log("[state]", msg.params?.state); post({jsonrpc:"2.0",method:"ui/stateAcknowledged",params:{truncated:false}}); }
|
|
263
|
+
else if (msg.method === "ui/keydown") { /* ignore */ }
|
|
264
|
+
else if (msg.method) console.log("[bridge]", msg.method, msg);
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
document.getElementById("toggle").onclick = function() {
|
|
268
|
+
dark = !dark;
|
|
269
|
+
document.body.style.background = dark ? "#0f172a" : "#f1f5f9";
|
|
270
|
+
post({jsonrpc:"2.0",method:"ui/themeChanged",params:{mode:dark?"dark":"light",tokens:getTokens(dark)}});
|
|
271
|
+
};
|
|
272
|
+
</script>
|
|
273
|
+
</body>
|
|
274
|
+
</html>`;
|
|
275
|
+
}
|
|
46
276
|
|
|
47
277
|
exports.synapseVite = synapseVite;
|
|
48
278
|
//# sourceMappingURL=index.cjs.map
|
package/dist/vite/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/vite/plugin.ts"],"names":[],"mappings":";;;AAmBO,SAAS,YAAY,OAAA,EAA2C;AACrE,EAAA,MAAM,EAAE,OAAA,EAAS,WAAA,GAAc,uBAAA,EAAyB,YAAA,GAAe,MAAK,GAAI,OAAA;AAEhF,EAAA,OAAO;AAAA,IACL,IAAA,EAAM,SAAA;AAAA,IAEN,MAAA,GAAS;AACP,MAAA,OAAO;AAAA,QACL,MAAA,EAAQ;AAAA,UACN,sCAAA,EAAwC,IAAA,CAAK,SAAA,CAAU,WAAW,CAAA;AAAA,UAClE,kCAAA,EAAoC,IAAA,CAAK,SAAA,CAAU,OAAO;AAAA,SAC5D;AAAA,QACA,MAAA,EAAQ;AAAA,UACN,GAAA,EAAK;AAAA;AAAA,YAEH,QAAA,EAAU,IAAA;AAAA,YACV,IAAA,EAAM;AAAA;AACR;AACF,OACF;AAAA,IACF,CAAA;AAAA,IAEA,gBAAgB,MAAA,EAAuB;AAErC,MAAA,MAAA,CAAO,WAAA,CAAY,GAAA,CAAI,CAAC,IAAA,EAAM,KAAK,IAAA,KAAS;AAC1C,QAAA,GAAA,CAAI,SAAA,CAAU,+BAA+B,GAAG,CAAA;AAChD,QAAA,GAAA,CAAI,SAAA,CAAU,gCAAgC,GAAG,CAAA;AACjD,QAAA,GAAA,CAAI,SAAA,CAAU,gCAAgC,GAAG,CAAA;AACjD,QAAA,IAAA,EAAK;AAAA,MACP,CAAC,CAAA;AAAA,IACH,CAAA;AAAA,IAEA,mBAAmB,IAAA,EAAc;AAC/B,MAAA,IAAI,CAAC,cAAc,OAAO,IAAA;AAE1B,MAAA,MAAM,YAAA,GAAe;AAAA;AAAA;AAAA;AAAA,UAAA,EAIf,IAAA,CAAK,SAAA,CAAU,OAAO,CAAC,CAAA;AAAA;AAAA;AAAA,SAAA,CAAA;AAM7B,MAAA,OAAO,IAAA,CAAK,OAAA,CAAQ,SAAA,EAAW,CAAA,EAAG,YAAY;AAAA,OAAA,CAAW,CAAA;AAAA,IAC3D;AAAA,GACF;AACF","file":"index.cjs","sourcesContent":["import type { Plugin, ViteDevServer } from \"vite\";\n\nexport interface SynapseVitePluginOptions {\n /** App name (must match manifest) */\n appName: string;\n /** Platform API URL (default: http://localhost:4321) */\n platformUrl?: string;\n /** Auto-inject bridge runtime into HTML entry (default: true) */\n injectBridge?: boolean;\n}\n\n/**\n * Vite plugin for Synapse app development.\n *\n * - Configures CORS for cross-origin iframe communication\n * - Injects ext-apps bridge runtime if `injectBridge` is true\n * - Sets up HMR WebSocket to work inside iframe sandbox\n * - Exposes platform URL as `import.meta.env.SYNAPSE_PLATFORM_URL`\n */\nexport function synapseVite(options: SynapseVitePluginOptions): Plugin {\n const { appName, platformUrl = \"http://localhost:4321\", injectBridge = true } = options;\n\n return {\n name: \"synapse\",\n\n config() {\n return {\n define: {\n \"import.meta.env.SYNAPSE_PLATFORM_URL\": JSON.stringify(platformUrl),\n \"import.meta.env.SYNAPSE_APP_NAME\": JSON.stringify(appName),\n },\n server: {\n hmr: {\n // Required for HMR inside sandboxed iframe\n protocol: \"ws\",\n host: \"localhost\",\n },\n },\n };\n },\n\n configureServer(server: ViteDevServer) {\n // Add CORS headers for cross-origin iframe communication\n server.middlewares.use((_req, res, next) => {\n res.setHeader(\"Access-Control-Allow-Origin\", \"*\");\n res.setHeader(\"Access-Control-Allow-Methods\", \"*\");\n res.setHeader(\"Access-Control-Allow-Headers\", \"*\");\n next();\n });\n },\n\n transformIndexHtml(html: string) {\n if (!injectBridge) return html;\n\n const bridgeScript = `\n<script type=\"module\">\n import { createSynapse } from \"@nimblebrain/synapse\";\n window.__synapse = createSynapse({\n name: ${JSON.stringify(appName)},\n version: \"0.0.0-dev\",\n });\n</script>`;\n\n // Inject before closing </head> tag\n return html.replace(\"</head>\", `${bridgeScript}\\n</head>`);\n },\n };\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/vite/plugin.ts"],"names":["resolve","existsSync","readFileSync","join","spawn"],"mappings":"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","file":"index.cjs","sourcesContent":["import type { Plugin, ViteDevServer } from \"vite\";\nimport { spawn, type ChildProcess } from \"node:child_process\";\nimport { readFileSync, existsSync } from \"node:fs\";\nimport { resolve, join } from \"node:path\";\n\nexport interface SynapseVitePluginOptions {\n /** App name. If omitted, reads from ../manifest.json */\n appName?: string;\n /** Path to manifest.json. Default: ../manifest.json (relative to ui/) */\n manifest?: string;\n /**\n * Shell command to start the MCP server. If omitted, derived from manifest.\n * The server runs in stdio mode — stdin/stdout JSON-RPC.\n */\n serverCmd?: string;\n /** Set to false to disable the preview host page at /__preview */\n preview?: boolean;\n}\n\ninterface Manifest {\n name: string;\n version?: string;\n server?: {\n type?: string;\n entry_point?: string;\n mcp_config?: {\n command?: string;\n args?: string[];\n };\n };\n}\n\n/**\n * Synapse Vite plugin — full local dev experience for MCP apps.\n *\n * What it does:\n * - Reads ../manifest.json to get app name and server config\n * - Spawns the MCP server as a child process (stdio mode)\n * - Serves a preview host page at /__preview that iframes your app\n * - Proxies tool calls from the iframe through POST /__mcp to the server\n * - Handles the ext-apps handshake so Synapse hooks work\n * - HMR works inside the iframe — edit .tsx, see changes instantly\n *\n * Usage in vite.config.ts:\n * import { synapseVite } from \"@nimblebrain/synapse/vite\";\n * export default { plugins: [react(), viteSingleFile(), synapseVite()] };\n *\n * Then: cd ui && npm run dev && open http://localhost:5173/__preview\n */\nexport function synapseVite(options: SynapseVitePluginOptions = {}): Plugin {\n const enablePreview = options.preview !== false;\n let manifest: Manifest | null = null;\n let appName = options.appName ?? \"app\";\n let serverProcess: ChildProcess | null = null;\n let pendingRequests = new Map<string, { resolve: (v: unknown) => void; reject: (e: Error) => void }>();\n let serverBuffer = \"\";\n\n function loadManifest(root: string): Manifest | null {\n const manifestPath = options.manifest\n ? resolve(options.manifest)\n : resolve(root, \"..\", \"manifest.json\");\n\n if (!existsSync(manifestPath)) return null;\n try {\n return JSON.parse(readFileSync(manifestPath, \"utf-8\"));\n } catch {\n return null;\n }\n }\n\n function deriveServerCmd(m: Manifest, root: string): string | null {\n if (options.serverCmd) return options.serverCmd;\n const cfg = m.server?.mcp_config;\n if (!cfg?.command) return null;\n\n const serverDir = resolve(root, \"..\");\n let cmd = cfg.command;\n const args = cfg.args ?? [];\n\n // Python projects: use `uv run` if pyproject.toml exists\n if (cmd === \"python\" && existsSync(join(serverDir, \"pyproject.toml\"))) {\n cmd = \"uv run python\";\n }\n\n return `cd ${JSON.stringify(serverDir)} && ${cmd} ${args.join(\" \")}`;\n }\n\n function startServer(cmd: string): void {\n serverProcess = spawn(cmd, {\n shell: true,\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n });\n\n serverProcess.stderr?.on(\"data\", (d: Buffer) => {\n process.stderr.write(` [mcp] ${d}`);\n });\n\n serverProcess.stdout?.on(\"data\", (d: Buffer) => {\n serverBuffer += d.toString();\n // Parse line-delimited JSON-RPC responses\n const lines = serverBuffer.split(\"\\n\");\n serverBuffer = lines.pop()!; // keep incomplete line\n for (const line of lines) {\n if (!line.trim()) continue;\n try {\n const msg = JSON.parse(line);\n if (msg.id && pendingRequests.has(msg.id)) {\n const p = pendingRequests.get(msg.id)!;\n pendingRequests.delete(msg.id);\n p.resolve(msg);\n }\n } catch {\n // Not JSON — log it\n process.stderr.write(` [mcp] ${line}\\n`);\n }\n }\n });\n\n serverProcess.on(\"exit\", (code) => {\n if (code !== null && code !== 0) {\n console.error(` [mcp] Server exited with code ${code}`);\n }\n serverProcess = null;\n });\n\n // Send initialize\n sendToServer({\n jsonrpc: \"2.0\",\n id: \"init-1\",\n method: \"initialize\",\n params: {\n protocolVersion: \"2024-11-05\",\n capabilities: {},\n clientInfo: { name: \"synapse-preview\", version: \"0.1.0\" },\n },\n });\n }\n\n function sendToServer(msg: Record<string, unknown>): void {\n if (!serverProcess?.stdin?.writable) return;\n serverProcess.stdin.write(JSON.stringify(msg) + \"\\n\");\n }\n\n function callServerTool(name: string, args: Record<string, unknown>): Promise<unknown> {\n return new Promise((resolve, reject) => {\n const id = `preview-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;\n pendingRequests.set(id, { resolve, reject });\n sendToServer({\n jsonrpc: \"2.0\",\n id,\n method: \"tools/call\",\n params: { name, arguments: args },\n });\n setTimeout(() => {\n if (pendingRequests.has(id)) {\n pendingRequests.delete(id);\n reject(new Error(\"Tool call timed out (10s)\"));\n }\n }, 10000);\n });\n }\n\n return {\n name: \"synapse\",\n\n config(_, { command }) {\n return {\n define: {\n \"import.meta.env.SYNAPSE_APP_NAME\": JSON.stringify(appName),\n },\n server: {\n hmr: {\n protocol: \"ws\",\n host: \"localhost\",\n },\n },\n };\n },\n\n configResolved(config) {\n manifest = loadManifest(config.root);\n if (manifest?.name) {\n appName = options.appName ?? manifest.name;\n }\n },\n\n configureServer(server: ViteDevServer) {\n // Start MCP server\n if (enablePreview && manifest) {\n const cmd = deriveServerCmd(manifest, server.config.root);\n if (cmd) {\n console.log(`\\n [synapse] Starting MCP server: ${cmd}\\n`);\n startServer(cmd);\n }\n }\n\n server.middlewares.use((req, res, next) => {\n // CORS for iframe communication\n res.setHeader(\"Access-Control-Allow-Origin\", \"*\");\n res.setHeader(\"Access-Control-Allow-Methods\", \"*\");\n res.setHeader(\"Access-Control-Allow-Headers\", \"*\");\n\n // /__preview — bridge host page\n if (req.url === \"/__preview\" || req.url === \"/__preview/\") {\n const port = server.config.server.port ?? 5173;\n res.writeHead(200, { \"Content-Type\": \"text/html\" });\n res.end(previewHostHtml(appName, port));\n return;\n }\n\n // POST /__mcp — tool call proxy\n if (req.method === \"POST\" && req.url === \"/__mcp\") {\n let body = \"\";\n req.on(\"data\", (chunk: Buffer) => { body += chunk.toString(); });\n req.on(\"end\", async () => {\n try {\n const msg = JSON.parse(body);\n const result = await callServerTool(msg.params.name, msg.params.arguments || {});\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(result));\n } catch (err) {\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({\n jsonrpc: \"2.0\",\n id: JSON.parse(body).id,\n error: { code: -32000, message: (err as Error).message },\n }));\n }\n });\n return;\n }\n\n next();\n });\n },\n\n buildEnd() {\n // Kill server on build end (for production builds)\n if (serverProcess) {\n serverProcess.kill(\"SIGTERM\");\n serverProcess = null;\n }\n },\n };\n}\n\nfunction previewHostHtml(appName: string, port: number): string {\n return `<!DOCTYPE html>\n<html>\n<head>\n <meta charset=\"utf-8\" />\n <title>${appName} — Synapse Preview</title>\n <style>\n * { margin: 0; padding: 0; box-sizing: border-box; }\n body { font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", sans-serif; background: #0f172a; color: #e2e8f0; }\n header { padding: 10px 16px; background: #1e293b; border-bottom: 1px solid #334155; display: flex; align-items: center; gap: 10px; font-size: 13px; }\n header .dot { width: 8px; height: 8px; border-radius: 50%; background: #22c55e; }\n header .name { font-weight: 600; }\n header .spacer { flex: 1; }\n header button { background: #334155; border: none; color: #e2e8f0; padding: 3px 8px; border-radius: 4px; cursor: pointer; font-size: 12px; }\n header .url { color: #64748b; font-size: 11px; font-family: monospace; }\n iframe { width: 100%; height: calc(100vh - 41px); border: none; }\n </style>\n</head>\n<body>\n <header>\n <span class=\"dot\"></span>\n <span class=\"name\">${appName}</span>\n <span class=\"spacer\"></span>\n <button id=\"toggle\">Toggle Theme</button>\n <span class=\"url\">localhost:${port}/__preview</span>\n </header>\n <iframe id=\"app\" src=\"http://localhost:${port}/\"></iframe>\n\n <script>\n var iframe = document.getElementById(\"app\");\n var dark = true;\n\n function getTokens(d) {\n return d ? {\n \"--nb-background\":\"#0f172a\",\"--nb-foreground\":\"#e2e8f0\",\n \"--nb-card\":\"#1e293b\",\"--nb-card-foreground\":\"#e2e8f0\",\n \"--nb-primary\":\"#6366f1\",\"--nb-primary-foreground\":\"#fff\",\n \"--nb-muted-foreground\":\"#94a3b8\",\"--nb-border\":\"#334155\",\n \"--nb-ring\":\"#6366f1\",\"--nb-destructive\":\"#ef4444\",\n \"--nb-radius\":\"0.5rem\",\"--nb-font-sans\":\"-apple-system,BlinkMacSystemFont,sans-serif\"\n } : {\n \"--nb-background\":\"#ffffff\",\"--nb-foreground\":\"#0f172a\",\n \"--nb-card\":\"#f8fafc\",\"--nb-card-foreground\":\"#0f172a\",\n \"--nb-primary\":\"#6366f1\",\"--nb-primary-foreground\":\"#fff\",\n \"--nb-muted-foreground\":\"#64748b\",\"--nb-border\":\"#e2e8f0\",\n \"--nb-ring\":\"#6366f1\",\"--nb-destructive\":\"#ef4444\",\n \"--nb-radius\":\"0.5rem\",\"--nb-font-sans\":\"-apple-system,BlinkMacSystemFont,sans-serif\"\n };\n }\n\n function post(msg) { iframe.contentWindow.postMessage(msg, \"*\"); }\n\n window.addEventListener(\"message\", async function(e) {\n if (e.source !== iframe.contentWindow) return;\n var msg = e.data;\n if (!msg || typeof msg !== \"object\") return;\n\n // ext-apps handshake\n if (msg.method === \"ui/initialize\" && msg.id) {\n post({ jsonrpc:\"2.0\", id:msg.id, result: {\n protocolVersion:\"2026-01-26\",\n serverInfo:{name:\"nimblebrain\",version:\"preview\"},\n capabilities:{openLinks:{},serverTools:{}},\n hostContext:{theme:dark?\"dark\":\"light\",primaryColor:\"#6366f1\",tokens:getTokens(dark)}\n }});\n return;\n }\n if (msg.method === \"ui/notifications/initialized\") return;\n\n // Tool calls — proxy via Vite middleware\n if (msg.method === \"tools/call\" && msg.id) {\n try {\n var r = await fetch(\"/__mcp\", {\n method:\"POST\", headers:{\"Content-Type\":\"application/json\"},\n body: JSON.stringify({jsonrpc:\"2.0\",id:msg.id,method:\"tools/call\",params:{name:msg.params.name,arguments:msg.params.arguments||{}}})\n });\n post(await r.json());\n } catch(err) {\n post({jsonrpc:\"2.0\",id:msg.id,error:{code:-32000,message:err.message}});\n }\n return;\n }\n\n // Log other messages\n if (msg.method === \"ui/chat\") console.log(\"[chat]\", msg.params?.message);\n else if (msg.method === \"ui/action\") console.log(\"[action]\", msg.params?.action, msg.params);\n else if (msg.method === \"ui/stateChanged\") { console.log(\"[state]\", msg.params?.state); post({jsonrpc:\"2.0\",method:\"ui/stateAcknowledged\",params:{truncated:false}}); }\n else if (msg.method === \"ui/keydown\") { /* ignore */ }\n else if (msg.method) console.log(\"[bridge]\", msg.method, msg);\n });\n\n document.getElementById(\"toggle\").onclick = function() {\n dark = !dark;\n document.body.style.background = dark ? \"#0f172a\" : \"#f1f5f9\";\n post({jsonrpc:\"2.0\",method:\"ui/themeChanged\",params:{mode:dark?\"dark\":\"light\",tokens:getTokens(dark)}});\n };\n </script>\n</body>\n</html>`;\n}\n"]}
|
package/dist/vite/index.d.cts
CHANGED
|
@@ -1,21 +1,35 @@
|
|
|
1
1
|
import { Plugin } from 'vite';
|
|
2
2
|
|
|
3
3
|
interface SynapseVitePluginOptions {
|
|
4
|
-
/** App name
|
|
5
|
-
appName
|
|
6
|
-
/**
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
|
|
4
|
+
/** App name. If omitted, reads from ../manifest.json */
|
|
5
|
+
appName?: string;
|
|
6
|
+
/** Path to manifest.json. Default: ../manifest.json (relative to ui/) */
|
|
7
|
+
manifest?: string;
|
|
8
|
+
/**
|
|
9
|
+
* Shell command to start the MCP server. If omitted, derived from manifest.
|
|
10
|
+
* The server runs in stdio mode — stdin/stdout JSON-RPC.
|
|
11
|
+
*/
|
|
12
|
+
serverCmd?: string;
|
|
13
|
+
/** Set to false to disable the preview host page at /__preview */
|
|
14
|
+
preview?: boolean;
|
|
10
15
|
}
|
|
11
16
|
/**
|
|
12
|
-
* Vite plugin for
|
|
17
|
+
* Synapse Vite plugin — full local dev experience for MCP apps.
|
|
13
18
|
*
|
|
14
|
-
*
|
|
15
|
-
* -
|
|
16
|
-
* -
|
|
17
|
-
* -
|
|
19
|
+
* What it does:
|
|
20
|
+
* - Reads ../manifest.json to get app name and server config
|
|
21
|
+
* - Spawns the MCP server as a child process (stdio mode)
|
|
22
|
+
* - Serves a preview host page at /__preview that iframes your app
|
|
23
|
+
* - Proxies tool calls from the iframe through POST /__mcp to the server
|
|
24
|
+
* - Handles the ext-apps handshake so Synapse hooks work
|
|
25
|
+
* - HMR works inside the iframe — edit .tsx, see changes instantly
|
|
26
|
+
*
|
|
27
|
+
* Usage in vite.config.ts:
|
|
28
|
+
* import { synapseVite } from "@nimblebrain/synapse/vite";
|
|
29
|
+
* export default { plugins: [react(), viteSingleFile(), synapseVite()] };
|
|
30
|
+
*
|
|
31
|
+
* Then: cd ui && npm run dev && open http://localhost:5173/__preview
|
|
18
32
|
*/
|
|
19
|
-
declare function synapseVite(options
|
|
33
|
+
declare function synapseVite(options?: SynapseVitePluginOptions): Plugin;
|
|
20
34
|
|
|
21
35
|
export { type SynapseVitePluginOptions, synapseVite };
|
package/dist/vite/index.d.ts
CHANGED
|
@@ -1,21 +1,35 @@
|
|
|
1
1
|
import { Plugin } from 'vite';
|
|
2
2
|
|
|
3
3
|
interface SynapseVitePluginOptions {
|
|
4
|
-
/** App name
|
|
5
|
-
appName
|
|
6
|
-
/**
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
|
|
4
|
+
/** App name. If omitted, reads from ../manifest.json */
|
|
5
|
+
appName?: string;
|
|
6
|
+
/** Path to manifest.json. Default: ../manifest.json (relative to ui/) */
|
|
7
|
+
manifest?: string;
|
|
8
|
+
/**
|
|
9
|
+
* Shell command to start the MCP server. If omitted, derived from manifest.
|
|
10
|
+
* The server runs in stdio mode — stdin/stdout JSON-RPC.
|
|
11
|
+
*/
|
|
12
|
+
serverCmd?: string;
|
|
13
|
+
/** Set to false to disable the preview host page at /__preview */
|
|
14
|
+
preview?: boolean;
|
|
10
15
|
}
|
|
11
16
|
/**
|
|
12
|
-
* Vite plugin for
|
|
17
|
+
* Synapse Vite plugin — full local dev experience for MCP apps.
|
|
13
18
|
*
|
|
14
|
-
*
|
|
15
|
-
* -
|
|
16
|
-
* -
|
|
17
|
-
* -
|
|
19
|
+
* What it does:
|
|
20
|
+
* - Reads ../manifest.json to get app name and server config
|
|
21
|
+
* - Spawns the MCP server as a child process (stdio mode)
|
|
22
|
+
* - Serves a preview host page at /__preview that iframes your app
|
|
23
|
+
* - Proxies tool calls from the iframe through POST /__mcp to the server
|
|
24
|
+
* - Handles the ext-apps handshake so Synapse hooks work
|
|
25
|
+
* - HMR works inside the iframe — edit .tsx, see changes instantly
|
|
26
|
+
*
|
|
27
|
+
* Usage in vite.config.ts:
|
|
28
|
+
* import { synapseVite } from "@nimblebrain/synapse/vite";
|
|
29
|
+
* export default { plugins: [react(), viteSingleFile(), synapseVite()] };
|
|
30
|
+
*
|
|
31
|
+
* Then: cd ui && npm run dev && open http://localhost:5173/__preview
|
|
18
32
|
*/
|
|
19
|
-
declare function synapseVite(options
|
|
33
|
+
declare function synapseVite(options?: SynapseVitePluginOptions): Plugin;
|
|
20
34
|
|
|
21
35
|
export { type SynapseVitePluginOptions, synapseVite };
|