@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.
Files changed (39) hide show
  1. package/dist/chunk-42N5BB5O.cjs +118 -0
  2. package/dist/chunk-42N5BB5O.cjs.map +1 -0
  3. package/dist/{chunk-AW3YIXLE.cjs → chunk-HLT5UBJF.cjs} +2 -116
  4. package/dist/chunk-HLT5UBJF.cjs.map +1 -0
  5. package/dist/{chunk-M4I222LB.js → chunk-JKHGWDZI.js} +3 -114
  6. package/dist/chunk-JKHGWDZI.js.map +1 -0
  7. package/dist/chunk-YWX3D24J.js +114 -0
  8. package/dist/chunk-YWX3D24J.js.map +1 -0
  9. package/dist/codegen/cli.cjs +58 -47
  10. package/dist/codegen/cli.cjs.map +1 -1
  11. package/dist/codegen/cli.js +58 -47
  12. package/dist/codegen/cli.js.map +1 -1
  13. package/dist/codegen/index.cjs +9 -8
  14. package/dist/codegen/index.js +2 -1
  15. package/dist/schema-reader-776246MJ.js +3 -0
  16. package/dist/schema-reader-776246MJ.js.map +1 -0
  17. package/dist/schema-reader-B6LTGBMJ.cjs +20 -0
  18. package/dist/schema-reader-B6LTGBMJ.cjs.map +1 -0
  19. package/dist/server-RUCX2TIB.js +204 -0
  20. package/dist/server-RUCX2TIB.js.map +1 -0
  21. package/dist/server-SEI7XI3B.cjs +206 -0
  22. package/dist/server-SEI7XI3B.cjs.map +1 -0
  23. package/dist/type-generator-MZ4X4DE5.js +3 -0
  24. package/dist/type-generator-MZ4X4DE5.js.map +1 -0
  25. package/dist/type-generator-UA3L4OIA.cjs +12 -0
  26. package/dist/type-generator-UA3L4OIA.cjs.map +1 -0
  27. package/dist/vite/index.cjs +248 -18
  28. package/dist/vite/index.cjs.map +1 -1
  29. package/dist/vite/index.d.cts +26 -12
  30. package/dist/vite/index.d.ts +26 -12
  31. package/dist/vite/index.js +248 -18
  32. package/dist/vite/index.js.map +1 -1
  33. package/dist/writer-NDK4U6C5.cjs +14 -0
  34. package/dist/writer-NDK4U6C5.cjs.map +1 -0
  35. package/dist/writer-ZNBXYUYC.js +12 -0
  36. package/dist/writer-ZNBXYUYC.js.map +1 -0
  37. package/package.json +13 -10
  38. package/dist/chunk-AW3YIXLE.cjs.map +0 -1
  39. package/dist/chunk-M4I222LB.js.map +0 -1
@@ -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 { appName, platformUrl = "http://localhost:4321", injectBridge = true } = options;
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
- server.middlewares.use((_req, res, next) => {
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
- transformIndexHtml(html) {
32
- if (!injectBridge) return html;
33
- const bridgeScript = `
34
- <script type="module">
35
- import { createSynapse } from "@nimblebrain/synapse";
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
@@ -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"]}
@@ -1,21 +1,35 @@
1
1
  import { Plugin } from 'vite';
2
2
 
3
3
  interface SynapseVitePluginOptions {
4
- /** App name (must match manifest) */
5
- appName: string;
6
- /** Platform API URL (default: http://localhost:4321) */
7
- platformUrl?: string;
8
- /** Auto-inject bridge runtime into HTML entry (default: true) */
9
- injectBridge?: boolean;
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 Synapse app development.
17
+ * Synapse Vite plugin — full local dev experience for MCP apps.
13
18
  *
14
- * - Configures CORS for cross-origin iframe communication
15
- * - Injects ext-apps bridge runtime if `injectBridge` is true
16
- * - Sets up HMR WebSocket to work inside iframe sandbox
17
- * - Exposes platform URL as `import.meta.env.SYNAPSE_PLATFORM_URL`
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: SynapseVitePluginOptions): Plugin;
33
+ declare function synapseVite(options?: SynapseVitePluginOptions): Plugin;
20
34
 
21
35
  export { type SynapseVitePluginOptions, synapseVite };
@@ -1,21 +1,35 @@
1
1
  import { Plugin } from 'vite';
2
2
 
3
3
  interface SynapseVitePluginOptions {
4
- /** App name (must match manifest) */
5
- appName: string;
6
- /** Platform API URL (default: http://localhost:4321) */
7
- platformUrl?: string;
8
- /** Auto-inject bridge runtime into HTML entry (default: true) */
9
- injectBridge?: boolean;
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 Synapse app development.
17
+ * Synapse Vite plugin — full local dev experience for MCP apps.
13
18
  *
14
- * - Configures CORS for cross-origin iframe communication
15
- * - Injects ext-apps bridge runtime if `injectBridge` is true
16
- * - Sets up HMR WebSocket to work inside iframe sandbox
17
- * - Exposes platform URL as `import.meta.env.SYNAPSE_PLATFORM_URL`
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: SynapseVitePluginOptions): Plugin;
33
+ declare function synapseVite(options?: SynapseVitePluginOptions): Plugin;
20
34
 
21
35
  export { type SynapseVitePluginOptions, synapseVite };