@nimblebrain/synapse 0.18.0 → 0.19.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/README.md +13 -13
- package/dist/chunk-54W6IAHM.js +24 -0
- package/dist/chunk-54W6IAHM.js.map +1 -0
- package/dist/{chunk-HKRTDGXN.js → chunk-DRLVEHCD.js} +3 -13
- package/dist/chunk-DRLVEHCD.js.map +1 -0
- package/dist/chunk-NP7ROSH2.cjs +33 -0
- package/dist/chunk-NP7ROSH2.cjs.map +1 -0
- package/dist/{chunk-ZLASWV4N.js → chunk-OYLZ5OCK.js} +4 -4
- package/dist/chunk-OYLZ5OCK.js.map +1 -0
- package/dist/chunk-P5DBJFSQ.js +8459 -0
- package/dist/chunk-P5DBJFSQ.js.map +1 -0
- package/dist/chunk-PZ5AY32C.js +9 -0
- package/dist/chunk-PZ5AY32C.js.map +1 -0
- package/dist/chunk-Q7SFCCGT.cjs +11 -0
- package/dist/chunk-Q7SFCCGT.cjs.map +1 -0
- package/dist/chunk-QXDCA7QW.cjs +8466 -0
- package/dist/chunk-QXDCA7QW.cjs.map +1 -0
- package/dist/{chunk-VDQZQDU3.cjs → chunk-RU64F5RS.cjs} +7 -7
- package/dist/chunk-RU64F5RS.cjs.map +1 -0
- package/dist/{chunk-LPWEQCZV.cjs → chunk-SZWHFTXV.cjs} +2 -13
- package/dist/chunk-SZWHFTXV.cjs.map +1 -0
- package/dist/codegen/cli.cjs +4 -4
- package/dist/codegen/cli.js +4 -4
- package/dist/codegen/index.cjs +1 -0
- package/dist/codegen/index.d.cts +1 -1
- package/dist/codegen/index.d.ts +1 -1
- package/dist/codegen/index.js +1 -0
- package/dist/connect.iife.global.js +66 -43
- package/dist/{detect-Bf8Q_0dK.d.ts → detect-DBwc8a21.d.ts} +2 -2
- package/dist/{detect-BHYg26_d.d.cts → detect-DZMJsyvZ.d.cts} +2 -2
- package/dist/host/index.cjs +15 -14
- package/dist/host/index.d.cts +3 -3
- package/dist/host/index.d.ts +3 -3
- package/dist/host/index.js +3 -2
- package/dist/index.cjs +42 -18
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +40 -13
- package/dist/index.d.ts +40 -13
- package/dist/index.js +35 -3
- package/dist/index.js.map +1 -1
- package/dist/react/index.cjs +12 -10
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.d.cts +14 -5
- package/dist/react/index.d.ts +14 -5
- package/dist/react/index.js +7 -5
- package/dist/react/index.js.map +1 -1
- package/dist/schema-reader-GM4A4L44.js +4 -0
- package/dist/{schema-reader-776246MJ.js.map → schema-reader-GM4A4L44.js.map} +1 -1
- package/dist/{schema-reader-B6LTGBMJ.cjs → schema-reader-MWBRQ2TQ.cjs} +3 -2
- package/dist/{schema-reader-B6LTGBMJ.cjs.map → schema-reader-MWBRQ2TQ.cjs.map} +1 -1
- package/dist/{server-QIYIJHD5.cjs → server-LR6T6C44.cjs} +32 -20
- package/dist/server-LR6T6C44.cjs.map +1 -0
- package/dist/{server-5N76YCWC.js → server-UHPEE55X.js} +32 -21
- package/dist/server-UHPEE55X.js.map +1 -0
- package/dist/synapse-runtime.iife.global.js +66 -43
- package/dist/synapse-ui.iife.global.js +2 -2
- package/dist/{type-generator-UA3L4OIA.cjs → type-generator-YTPFWLVG.cjs} +3 -2
- package/dist/{type-generator-UA3L4OIA.cjs.map → type-generator-YTPFWLVG.cjs.map} +1 -1
- package/dist/type-generator-ZYSVFK3X.js +4 -0
- package/dist/{type-generator-MZ4X4DE5.js.map → type-generator-ZYSVFK3X.js.map} +1 -1
- package/dist/{types-uEO4VFJ2.d.cts → types-CG08aO82.d.cts} +14 -83
- package/dist/{types-uEO4VFJ2.d.ts → types-CG08aO82.d.ts} +14 -83
- package/dist/ui/base.cjs +1 -0
- package/dist/ui/base.cjs.map +1 -1
- package/dist/ui/base.js +1 -0
- package/dist/ui/base.js.map +1 -1
- package/dist/ui/index.cjs +1 -0
- package/dist/ui/index.cjs.map +1 -1
- package/dist/ui/index.js +1 -0
- package/dist/ui/index.js.map +1 -1
- package/dist/vite/index.cjs +74 -30
- package/dist/vite/index.cjs.map +1 -1
- package/dist/vite/index.d.cts +5 -1
- package/dist/vite/index.d.ts +5 -1
- package/dist/vite/index.js +74 -30
- package/dist/vite/index.js.map +1 -1
- package/dist/{writer-NDK4U6C5.cjs → writer-HDRCQSX2.cjs} +3 -3
- package/dist/{writer-NDK4U6C5.cjs.map → writer-HDRCQSX2.cjs.map} +1 -1
- package/dist/{writer-ZNBXYUYC.js → writer-QNDPMEYD.js} +3 -3
- package/dist/{writer-ZNBXYUYC.js.map → writer-QNDPMEYD.js.map} +1 -1
- package/package.json +8 -5
- package/dist/chunk-3YSXPBEQ.js +0 -781
- package/dist/chunk-3YSXPBEQ.js.map +0 -1
- package/dist/chunk-FB7GPBEM.cjs +0 -788
- package/dist/chunk-FB7GPBEM.cjs.map +0 -1
- package/dist/chunk-HKRTDGXN.js.map +0 -1
- package/dist/chunk-LPWEQCZV.cjs.map +0 -1
- package/dist/chunk-VDQZQDU3.cjs.map +0 -1
- package/dist/chunk-ZLASWV4N.js.map +0 -1
- package/dist/schema-reader-776246MJ.js +0 -3
- package/dist/server-5N76YCWC.js.map +0 -1
- package/dist/server-QIYIJHD5.cjs.map +0 -1
- package/dist/type-generator-MZ4X4DE5.js +0 -3
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
require('./chunk-Q7SFCCGT.cjs');
|
|
3
4
|
var child_process = require('child_process');
|
|
4
5
|
var fs = require('fs');
|
|
5
6
|
var http = require('http');
|
|
6
7
|
var path = require('path');
|
|
7
8
|
|
|
8
|
-
|
|
9
|
-
var HOST_HTML = (uiPort, serverPort) => `<!DOCTYPE html>
|
|
9
|
+
var previewHostHtml = (uiPort, serverPort) => `<!DOCTYPE html>
|
|
10
10
|
<html>
|
|
11
11
|
<head>
|
|
12
12
|
<meta charset="utf-8" />
|
|
@@ -35,40 +35,53 @@ var HOST_HTML = (uiPort, serverPort) => `<!DOCTYPE html>
|
|
|
35
35
|
var iframe = document.getElementById("app");
|
|
36
36
|
var darkMode = true;
|
|
37
37
|
|
|
38
|
-
// Minimal NimbleBrain bridge host \u2014 just enough to make Synapse work
|
|
38
|
+
// Minimal NimbleBrain bridge host \u2014 just enough to make Synapse work.
|
|
39
|
+
//
|
|
40
|
+
// Every key here is one the spec's style-variable enum names. That enum is
|
|
41
|
+
// a strict record, so a single extra key makes a spec client reject the
|
|
42
|
+
// whole \`ui/initialize\` result and refuse to connect \u2014 not just ignore the
|
|
43
|
+
// key. The kit's own neutral defaults back anything a host leaves out, so
|
|
44
|
+
// the accent and danger colours still render; they come from the cascade
|
|
45
|
+
// layer instead of the wire.
|
|
39
46
|
var tokens = darkMode ? {
|
|
40
47
|
"--color-background-primary": "#0f172a", "--color-background-secondary": "#1e293b",
|
|
41
48
|
"--color-background-tertiary": "#2a374a", "--color-text-primary": "#e2e8f0",
|
|
42
49
|
"--color-text-secondary": "#94a3b8", "--color-text-tertiary": "#64748b",
|
|
43
|
-
"--color-text-accent": "#6366f1", "--nb-color-accent-foreground": "#ffffff",
|
|
44
50
|
"--color-border-primary": "#334155", "--color-border-secondary": "#475569",
|
|
45
|
-
"--color-ring-primary": "#6366f1",
|
|
51
|
+
"--color-ring-primary": "#6366f1",
|
|
46
52
|
"--border-radius-sm": "0.5rem",
|
|
47
53
|
} : {
|
|
48
54
|
"--color-background-primary": "#ffffff", "--color-background-secondary": "#f9fafb",
|
|
49
55
|
"--color-background-tertiary": "#f1f5f9", "--color-text-primary": "#1a1a1a",
|
|
50
56
|
"--color-text-secondary": "#6b7280", "--color-text-tertiary": "#94a3b8",
|
|
51
|
-
"--color-text-accent": "#6366f1", "--nb-color-accent-foreground": "#ffffff",
|
|
52
57
|
"--color-border-primary": "#e5e7eb", "--color-border-secondary": "#cbd5e1",
|
|
53
|
-
"--color-ring-primary": "#6366f1",
|
|
58
|
+
"--color-ring-primary": "#6366f1",
|
|
54
59
|
"--border-radius-sm": "0.5rem",
|
|
55
60
|
};
|
|
56
61
|
|
|
57
62
|
function post(msg) { iframe.contentWindow.postMessage(msg, "*"); }
|
|
58
63
|
|
|
64
|
+
// A request is a frame carrying an id \u2014 and \`0\` is a perfectly good id.
|
|
65
|
+
// The MCP SDK numbers from zero, so a truthiness test drops the first
|
|
66
|
+
// request every spec client ever sends, which is its \`ui/initialize\`.
|
|
67
|
+
function isRequest(msg) { return msg.id !== undefined && msg.id !== null; }
|
|
68
|
+
|
|
59
69
|
window.addEventListener("message", async function (event) {
|
|
60
70
|
if (event.source !== iframe.contentWindow) return;
|
|
61
71
|
var msg = event.data;
|
|
62
72
|
if (!msg || typeof msg !== "object") return;
|
|
63
73
|
|
|
64
|
-
// ext-apps handshake
|
|
65
|
-
|
|
74
|
+
// ext-apps handshake. \`hostInfo\`/\`hostCapabilities\` are the spec's field
|
|
75
|
+
// names, and a spec client validates the result against them \u2014 the
|
|
76
|
+
// official ext-apps \`App\` refuses to connect to anything else, so an app
|
|
77
|
+
// built on it would render only in the real host and not in preview.
|
|
78
|
+
if (msg.method === "ui/initialize" && isRequest(msg)) {
|
|
66
79
|
post({
|
|
67
80
|
jsonrpc: "2.0", id: msg.id,
|
|
68
81
|
result: {
|
|
69
82
|
protocolVersion: "2026-01-26",
|
|
70
|
-
|
|
71
|
-
|
|
83
|
+
hostInfo: { name: "nimblebrain", version: "preview" },
|
|
84
|
+
hostCapabilities: { openLinks: {}, serverTools: {} },
|
|
72
85
|
hostContext: { theme: darkMode ? "dark" : "light", styles: { variables: tokens } }
|
|
73
86
|
}
|
|
74
87
|
});
|
|
@@ -78,7 +91,7 @@ var HOST_HTML = (uiPort, serverPort) => `<!DOCTYPE html>
|
|
|
78
91
|
if (msg.method === "ui/notifications/initialized") return;
|
|
79
92
|
|
|
80
93
|
// Tool call proxy \u2014 forward to the MCP server
|
|
81
|
-
if (msg.method === "tools/call" && msg
|
|
94
|
+
if (msg.method === "tools/call" && isRequest(msg)) {
|
|
82
95
|
try {
|
|
83
96
|
var resp = await fetch("http://localhost:${serverPort}/mcp", {
|
|
84
97
|
method: "POST",
|
|
@@ -116,7 +129,7 @@ var HOST_HTML = (uiPort, serverPort) => `<!DOCTYPE html>
|
|
|
116
129
|
// ui/update-model-context \u2014 log (ext-apps spec)
|
|
117
130
|
if (msg.method === "ui/update-model-context") {
|
|
118
131
|
console.log("[model-context]", msg.params?.structuredContent);
|
|
119
|
-
if (msg
|
|
132
|
+
if (isRequest(msg)) post({ jsonrpc: "2.0", id: msg.id, result: {} });
|
|
120
133
|
return;
|
|
121
134
|
}
|
|
122
135
|
|
|
@@ -131,17 +144,15 @@ var HOST_HTML = (uiPort, serverPort) => `<!DOCTYPE html>
|
|
|
131
144
|
"--color-background-primary": "#0f172a", "--color-background-secondary": "#1e293b",
|
|
132
145
|
"--color-background-tertiary": "#2a374a", "--color-text-primary": "#e2e8f0",
|
|
133
146
|
"--color-text-secondary": "#94a3b8", "--color-text-tertiary": "#64748b",
|
|
134
|
-
"--color-text-accent": "#6366f1", "--nb-color-accent-foreground": "#ffffff",
|
|
135
147
|
"--color-border-primary": "#334155", "--color-border-secondary": "#475569",
|
|
136
|
-
"--color-ring-primary": "#6366f1",
|
|
148
|
+
"--color-ring-primary": "#6366f1",
|
|
137
149
|
"--border-radius-sm": "0.5rem",
|
|
138
150
|
} : {
|
|
139
151
|
"--color-background-primary": "#ffffff", "--color-background-secondary": "#f9fafb",
|
|
140
152
|
"--color-background-tertiary": "#f1f5f9", "--color-text-primary": "#1a1a1a",
|
|
141
153
|
"--color-text-secondary": "#6b7280", "--color-text-tertiary": "#94a3b8",
|
|
142
|
-
"--color-text-accent": "#6366f1", "--nb-color-accent-foreground": "#ffffff",
|
|
143
154
|
"--color-border-primary": "#e5e7eb", "--color-border-secondary": "#cbd5e1",
|
|
144
|
-
"--color-ring-primary": "#6366f1",
|
|
155
|
+
"--color-ring-primary": "#6366f1",
|
|
145
156
|
"--border-radius-sm": "0.5rem",
|
|
146
157
|
};
|
|
147
158
|
post({ jsonrpc: "2.0", method: "ui/notifications/host-context-changed", params: { theme: darkMode ? "dark" : "light", styles: { variables: tokens } } });
|
|
@@ -176,7 +187,7 @@ async function startPreview(options) {
|
|
|
176
187
|
children.push(uiProc);
|
|
177
188
|
uiProc.stdout?.on("data", (d) => process.stdout.write(` [ui] ${d}`));
|
|
178
189
|
uiProc.stderr?.on("data", (d) => process.stderr.write(` [ui] ${d}`));
|
|
179
|
-
const html =
|
|
190
|
+
const html = previewHostHtml(uiPort, serverPort);
|
|
180
191
|
const host = http.createServer((_req, res) => {
|
|
181
192
|
res.writeHead(200, { "Content-Type": "text/html" });
|
|
182
193
|
res.end(html);
|
|
@@ -205,6 +216,7 @@ async function startPreview(options) {
|
|
|
205
216
|
process.on("SIGTERM", shutdown);
|
|
206
217
|
}
|
|
207
218
|
|
|
219
|
+
exports.previewHostHtml = previewHostHtml;
|
|
208
220
|
exports.startPreview = startPreview;
|
|
209
|
-
//# sourceMappingURL=server-
|
|
210
|
-
//# sourceMappingURL=server-
|
|
221
|
+
//# sourceMappingURL=server-LR6T6C44.cjs.map
|
|
222
|
+
//# sourceMappingURL=server-LR6T6C44.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/preview/server.ts"],"names":["spawn","resolve","existsSync","join","createServer"],"mappings":"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","file":"server-LR6T6C44.cjs","sourcesContent":["/**\n * Synapse Preview — standalone dev harness for MCP apps with UIs.\n *\n * Starts the MCP server (HTTP mode) and a minimal bridge host page\n * that iframes the app UI and proxies tool calls to the server.\n *\n * Usage:\n * npx synapse preview --server \"uv run uvicorn mcp_hello.server:app --port 8001\" --ui ./ui\n * npx synapse preview --server \"node dist/index.js\" --ui ./ui --server-port 8001\n */\n\nimport { type ChildProcess, spawn } from \"node:child_process\";\nimport { existsSync } from \"node:fs\";\nimport { createServer, type IncomingMessage, type ServerResponse } from \"node:http\";\nimport { join, resolve } from \"node:path\";\n\nexport interface PreviewOptions {\n /** Shell command to start the MCP server in HTTP mode */\n serverCmd: string;\n /** Port the MCP server listens on (default: 8001) */\n serverPort: number;\n /** Path to the UI directory (must have package.json with dev script) */\n uiDir: string;\n /** Port for the UI Vite dev server (default: 5173) */\n uiPort: number;\n /** Port for the preview harness (default: 5180) */\n previewPort: number;\n}\n\n/**\n * The preview harness page: the bridge host that frames the app UI on\n * `uiPort` and proxies its `tools/call` to the MCP server on `serverPort`.\n *\n * Exported so the conformance suite can serve this exact page and connect a\n * real spec client to it. Asserting the handshake against a copy would prove\n * nothing about what the preview actually answers.\n */\nexport const previewHostHtml = (uiPort: number, serverPort: number) => `<!DOCTYPE html>\n<html>\n<head>\n <meta charset=\"utf-8\" />\n <title>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: 12px 20px; background: #1e293b; border-bottom: 1px solid #334155; display: flex; align-items: center; gap: 12px; }\n header h1 { font-size: 14px; font-weight: 500; }\n header .dot { width: 8px; height: 8px; border-radius: 50%; background: #22c55e; }\n header .info { font-size: 12px; color: #94a3b8; margin-left: auto; }\n .theme-toggle { background: #334155; border: none; color: #e2e8f0; padding: 4px 10px; border-radius: 4px; cursor: pointer; font-size: 12px; }\n iframe { width: 100%; height: calc(100vh - 45px); border: none; }\n </style>\n</head>\n<body>\n <header>\n <span class=\"dot\"></span>\n <h1>Synapse Preview</h1>\n <button class=\"theme-toggle\" id=\"toggle\">Toggle Dark/Light</button>\n <span class=\"info\">MCP: localhost:${serverPort} | UI: localhost:${uiPort}</span>\n </header>\n <iframe id=\"app\" src=\"http://localhost:${uiPort}\"></iframe>\n\n <script>\n var iframe = document.getElementById(\"app\");\n var darkMode = true;\n\n // Minimal NimbleBrain bridge host — just enough to make Synapse work.\n //\n // Every key here is one the spec's style-variable enum names. That enum is\n // a strict record, so a single extra key makes a spec client reject the\n // whole \\`ui/initialize\\` result and refuse to connect — not just ignore the\n // key. The kit's own neutral defaults back anything a host leaves out, so\n // the accent and danger colours still render; they come from the cascade\n // layer instead of the wire.\n var tokens = darkMode ? {\n \"--color-background-primary\": \"#0f172a\", \"--color-background-secondary\": \"#1e293b\",\n \"--color-background-tertiary\": \"#2a374a\", \"--color-text-primary\": \"#e2e8f0\",\n \"--color-text-secondary\": \"#94a3b8\", \"--color-text-tertiary\": \"#64748b\",\n \"--color-border-primary\": \"#334155\", \"--color-border-secondary\": \"#475569\",\n \"--color-ring-primary\": \"#6366f1\",\n \"--border-radius-sm\": \"0.5rem\",\n } : {\n \"--color-background-primary\": \"#ffffff\", \"--color-background-secondary\": \"#f9fafb\",\n \"--color-background-tertiary\": \"#f1f5f9\", \"--color-text-primary\": \"#1a1a1a\",\n \"--color-text-secondary\": \"#6b7280\", \"--color-text-tertiary\": \"#94a3b8\",\n \"--color-border-primary\": \"#e5e7eb\", \"--color-border-secondary\": \"#cbd5e1\",\n \"--color-ring-primary\": \"#6366f1\",\n \"--border-radius-sm\": \"0.5rem\",\n };\n\n function post(msg) { iframe.contentWindow.postMessage(msg, \"*\"); }\n\n // A request is a frame carrying an id — and \\`0\\` is a perfectly good id.\n // The MCP SDK numbers from zero, so a truthiness test drops the first\n // request every spec client ever sends, which is its \\`ui/initialize\\`.\n function isRequest(msg) { return msg.id !== undefined && msg.id !== null; }\n\n window.addEventListener(\"message\", async function (event) {\n if (event.source !== iframe.contentWindow) return;\n var msg = event.data;\n if (!msg || typeof msg !== \"object\") return;\n\n // ext-apps handshake. \\`hostInfo\\`/\\`hostCapabilities\\` are the spec's field\n // names, and a spec client validates the result against them — the\n // official ext-apps \\`App\\` refuses to connect to anything else, so an app\n // built on it would render only in the real host and not in preview.\n if (msg.method === \"ui/initialize\" && isRequest(msg)) {\n post({\n jsonrpc: \"2.0\", id: msg.id,\n result: {\n protocolVersion: \"2026-01-26\",\n hostInfo: { name: \"nimblebrain\", version: \"preview\" },\n hostCapabilities: { openLinks: {}, serverTools: {} },\n hostContext: { theme: darkMode ? \"dark\" : \"light\", styles: { variables: tokens } }\n }\n });\n return;\n }\n\n if (msg.method === \"ui/notifications/initialized\") return;\n\n // Tool call proxy — forward to the MCP server\n if (msg.method === \"tools/call\" && isRequest(msg)) {\n try {\n var resp = await fetch(\"http://localhost:${serverPort}/mcp\", {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n jsonrpc: \"2.0\", id: msg.id,\n method: \"tools/call\",\n params: { name: msg.params.name, arguments: msg.params.arguments || {} }\n })\n });\n var result = await resp.json();\n // Forward the JSON-RPC response back to the iframe\n post(result);\n } catch (err) {\n post({ jsonrpc: \"2.0\", id: msg.id, error: { code: -32000, message: err.message || \"Server error\" } });\n }\n return;\n }\n\n // synapse/chat — log to console\n if (msg.method === \"synapse/chat\") {\n console.log(\"[chat]\", msg.params?.message);\n return;\n }\n\n // synapse/action — log to console\n if (msg.method === \"synapse/action\") {\n console.log(\"[action]\", msg.params?.action, msg.params);\n return;\n }\n\n // synapse/keydown — ignore in preview\n if (msg.method === \"synapse/keydown\") return;\n\n // ui/update-model-context — log (ext-apps spec)\n if (msg.method === \"ui/update-model-context\") {\n console.log(\"[model-context]\", msg.params?.structuredContent);\n if (isRequest(msg)) post({ jsonrpc: \"2.0\", id: msg.id, result: {} });\n return;\n }\n\n console.log(\"[bridge] unhandled:\", msg.method, msg);\n });\n\n // Theme toggle\n document.getElementById(\"toggle\").addEventListener(\"click\", function () {\n darkMode = !darkMode;\n document.body.style.background = darkMode ? \"#0f172a\" : \"#f1f5f9\";\n tokens = darkMode ? {\n \"--color-background-primary\": \"#0f172a\", \"--color-background-secondary\": \"#1e293b\",\n \"--color-background-tertiary\": \"#2a374a\", \"--color-text-primary\": \"#e2e8f0\",\n \"--color-text-secondary\": \"#94a3b8\", \"--color-text-tertiary\": \"#64748b\",\n \"--color-border-primary\": \"#334155\", \"--color-border-secondary\": \"#475569\",\n \"--color-ring-primary\": \"#6366f1\",\n \"--border-radius-sm\": \"0.5rem\",\n } : {\n \"--color-background-primary\": \"#ffffff\", \"--color-background-secondary\": \"#f9fafb\",\n \"--color-background-tertiary\": \"#f1f5f9\", \"--color-text-primary\": \"#1a1a1a\",\n \"--color-text-secondary\": \"#6b7280\", \"--color-text-tertiary\": \"#94a3b8\",\n \"--color-border-primary\": \"#e5e7eb\", \"--color-border-secondary\": \"#cbd5e1\",\n \"--color-ring-primary\": \"#6366f1\",\n \"--border-radius-sm\": \"0.5rem\",\n };\n post({ jsonrpc: \"2.0\", method: \"ui/notifications/host-context-changed\", params: { theme: darkMode ? \"dark\" : \"light\", styles: { variables: tokens } } });\n });\n </script>\n</body>\n</html>`;\n\nexport async function startPreview(options: PreviewOptions): Promise<void> {\n const { serverCmd, serverPort, uiDir, uiPort, previewPort } = options;\n const children: ChildProcess[] = [];\n\n console.log(`\\n Synapse Preview\\n`);\n\n // 1. Start MCP server\n console.log(` [server] ${serverCmd}`);\n const serverProc = spawn(serverCmd, {\n shell: true,\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n });\n children.push(serverProc);\n serverProc.stdout?.on(\"data\", (d: Buffer) => process.stdout.write(` [server] ${d}`));\n serverProc.stderr?.on(\"data\", (d: Buffer) => process.stderr.write(` [server] ${d}`));\n\n // 2. Start UI Vite dev server\n const resolvedUi = resolve(uiDir);\n if (!existsSync(join(resolvedUi, \"package.json\"))) {\n console.error(` [ui] Error: no package.json found at ${resolvedUi}`);\n process.exit(1);\n }\n console.log(` [ui] cd ${resolvedUi} && npx vite --port ${uiPort}`);\n const uiProc = spawn(\"npx\", [\"vite\", \"--port\", String(uiPort)], {\n cwd: resolvedUi,\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n });\n children.push(uiProc);\n uiProc.stdout?.on(\"data\", (d: Buffer) => process.stdout.write(` [ui] ${d}`));\n uiProc.stderr?.on(\"data\", (d: Buffer) => process.stderr.write(` [ui] ${d}`));\n\n // 3. Start preview host\n const html = previewHostHtml(uiPort, serverPort);\n const host = createServer((_req: IncomingMessage, res: ServerResponse) => {\n res.writeHead(200, { \"Content-Type\": \"text/html\" });\n res.end(html);\n });\n host.listen(previewPort, () => {\n console.log(`\\n Preview: http://localhost:${previewPort}`);\n console.log(` UI: http://localhost:${uiPort}`);\n console.log(` Server: http://localhost:${serverPort}`);\n console.log(`\\n Press Ctrl+C to stop.\\n`);\n });\n\n // Shutdown\n const shutdown = () => {\n console.log(\"\\n Shutting down...\");\n host.close();\n for (const child of children) {\n try {\n child.kill(\"SIGTERM\");\n } catch {\n /* already dead */\n }\n }\n process.exit(0);\n };\n process.on(\"SIGINT\", shutdown);\n process.on(\"SIGTERM\", shutdown);\n}\n"]}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
+
import './chunk-PZ5AY32C.js';
|
|
1
2
|
import { spawn } from 'child_process';
|
|
2
3
|
import { existsSync } from 'fs';
|
|
3
4
|
import { createServer } from 'http';
|
|
4
5
|
import { resolve, join } from 'path';
|
|
5
6
|
|
|
6
|
-
|
|
7
|
-
var HOST_HTML = (uiPort, serverPort) => `<!DOCTYPE html>
|
|
7
|
+
var previewHostHtml = (uiPort, serverPort) => `<!DOCTYPE html>
|
|
8
8
|
<html>
|
|
9
9
|
<head>
|
|
10
10
|
<meta charset="utf-8" />
|
|
@@ -33,40 +33,53 @@ var HOST_HTML = (uiPort, serverPort) => `<!DOCTYPE html>
|
|
|
33
33
|
var iframe = document.getElementById("app");
|
|
34
34
|
var darkMode = true;
|
|
35
35
|
|
|
36
|
-
// Minimal NimbleBrain bridge host \u2014 just enough to make Synapse work
|
|
36
|
+
// Minimal NimbleBrain bridge host \u2014 just enough to make Synapse work.
|
|
37
|
+
//
|
|
38
|
+
// Every key here is one the spec's style-variable enum names. That enum is
|
|
39
|
+
// a strict record, so a single extra key makes a spec client reject the
|
|
40
|
+
// whole \`ui/initialize\` result and refuse to connect \u2014 not just ignore the
|
|
41
|
+
// key. The kit's own neutral defaults back anything a host leaves out, so
|
|
42
|
+
// the accent and danger colours still render; they come from the cascade
|
|
43
|
+
// layer instead of the wire.
|
|
37
44
|
var tokens = darkMode ? {
|
|
38
45
|
"--color-background-primary": "#0f172a", "--color-background-secondary": "#1e293b",
|
|
39
46
|
"--color-background-tertiary": "#2a374a", "--color-text-primary": "#e2e8f0",
|
|
40
47
|
"--color-text-secondary": "#94a3b8", "--color-text-tertiary": "#64748b",
|
|
41
|
-
"--color-text-accent": "#6366f1", "--nb-color-accent-foreground": "#ffffff",
|
|
42
48
|
"--color-border-primary": "#334155", "--color-border-secondary": "#475569",
|
|
43
|
-
"--color-ring-primary": "#6366f1",
|
|
49
|
+
"--color-ring-primary": "#6366f1",
|
|
44
50
|
"--border-radius-sm": "0.5rem",
|
|
45
51
|
} : {
|
|
46
52
|
"--color-background-primary": "#ffffff", "--color-background-secondary": "#f9fafb",
|
|
47
53
|
"--color-background-tertiary": "#f1f5f9", "--color-text-primary": "#1a1a1a",
|
|
48
54
|
"--color-text-secondary": "#6b7280", "--color-text-tertiary": "#94a3b8",
|
|
49
|
-
"--color-text-accent": "#6366f1", "--nb-color-accent-foreground": "#ffffff",
|
|
50
55
|
"--color-border-primary": "#e5e7eb", "--color-border-secondary": "#cbd5e1",
|
|
51
|
-
"--color-ring-primary": "#6366f1",
|
|
56
|
+
"--color-ring-primary": "#6366f1",
|
|
52
57
|
"--border-radius-sm": "0.5rem",
|
|
53
58
|
};
|
|
54
59
|
|
|
55
60
|
function post(msg) { iframe.contentWindow.postMessage(msg, "*"); }
|
|
56
61
|
|
|
62
|
+
// A request is a frame carrying an id \u2014 and \`0\` is a perfectly good id.
|
|
63
|
+
// The MCP SDK numbers from zero, so a truthiness test drops the first
|
|
64
|
+
// request every spec client ever sends, which is its \`ui/initialize\`.
|
|
65
|
+
function isRequest(msg) { return msg.id !== undefined && msg.id !== null; }
|
|
66
|
+
|
|
57
67
|
window.addEventListener("message", async function (event) {
|
|
58
68
|
if (event.source !== iframe.contentWindow) return;
|
|
59
69
|
var msg = event.data;
|
|
60
70
|
if (!msg || typeof msg !== "object") return;
|
|
61
71
|
|
|
62
|
-
// ext-apps handshake
|
|
63
|
-
|
|
72
|
+
// ext-apps handshake. \`hostInfo\`/\`hostCapabilities\` are the spec's field
|
|
73
|
+
// names, and a spec client validates the result against them \u2014 the
|
|
74
|
+
// official ext-apps \`App\` refuses to connect to anything else, so an app
|
|
75
|
+
// built on it would render only in the real host and not in preview.
|
|
76
|
+
if (msg.method === "ui/initialize" && isRequest(msg)) {
|
|
64
77
|
post({
|
|
65
78
|
jsonrpc: "2.0", id: msg.id,
|
|
66
79
|
result: {
|
|
67
80
|
protocolVersion: "2026-01-26",
|
|
68
|
-
|
|
69
|
-
|
|
81
|
+
hostInfo: { name: "nimblebrain", version: "preview" },
|
|
82
|
+
hostCapabilities: { openLinks: {}, serverTools: {} },
|
|
70
83
|
hostContext: { theme: darkMode ? "dark" : "light", styles: { variables: tokens } }
|
|
71
84
|
}
|
|
72
85
|
});
|
|
@@ -76,7 +89,7 @@ var HOST_HTML = (uiPort, serverPort) => `<!DOCTYPE html>
|
|
|
76
89
|
if (msg.method === "ui/notifications/initialized") return;
|
|
77
90
|
|
|
78
91
|
// Tool call proxy \u2014 forward to the MCP server
|
|
79
|
-
if (msg.method === "tools/call" && msg
|
|
92
|
+
if (msg.method === "tools/call" && isRequest(msg)) {
|
|
80
93
|
try {
|
|
81
94
|
var resp = await fetch("http://localhost:${serverPort}/mcp", {
|
|
82
95
|
method: "POST",
|
|
@@ -114,7 +127,7 @@ var HOST_HTML = (uiPort, serverPort) => `<!DOCTYPE html>
|
|
|
114
127
|
// ui/update-model-context \u2014 log (ext-apps spec)
|
|
115
128
|
if (msg.method === "ui/update-model-context") {
|
|
116
129
|
console.log("[model-context]", msg.params?.structuredContent);
|
|
117
|
-
if (msg
|
|
130
|
+
if (isRequest(msg)) post({ jsonrpc: "2.0", id: msg.id, result: {} });
|
|
118
131
|
return;
|
|
119
132
|
}
|
|
120
133
|
|
|
@@ -129,17 +142,15 @@ var HOST_HTML = (uiPort, serverPort) => `<!DOCTYPE html>
|
|
|
129
142
|
"--color-background-primary": "#0f172a", "--color-background-secondary": "#1e293b",
|
|
130
143
|
"--color-background-tertiary": "#2a374a", "--color-text-primary": "#e2e8f0",
|
|
131
144
|
"--color-text-secondary": "#94a3b8", "--color-text-tertiary": "#64748b",
|
|
132
|
-
"--color-text-accent": "#6366f1", "--nb-color-accent-foreground": "#ffffff",
|
|
133
145
|
"--color-border-primary": "#334155", "--color-border-secondary": "#475569",
|
|
134
|
-
"--color-ring-primary": "#6366f1",
|
|
146
|
+
"--color-ring-primary": "#6366f1",
|
|
135
147
|
"--border-radius-sm": "0.5rem",
|
|
136
148
|
} : {
|
|
137
149
|
"--color-background-primary": "#ffffff", "--color-background-secondary": "#f9fafb",
|
|
138
150
|
"--color-background-tertiary": "#f1f5f9", "--color-text-primary": "#1a1a1a",
|
|
139
151
|
"--color-text-secondary": "#6b7280", "--color-text-tertiary": "#94a3b8",
|
|
140
|
-
"--color-text-accent": "#6366f1", "--nb-color-accent-foreground": "#ffffff",
|
|
141
152
|
"--color-border-primary": "#e5e7eb", "--color-border-secondary": "#cbd5e1",
|
|
142
|
-
"--color-ring-primary": "#6366f1",
|
|
153
|
+
"--color-ring-primary": "#6366f1",
|
|
143
154
|
"--border-radius-sm": "0.5rem",
|
|
144
155
|
};
|
|
145
156
|
post({ jsonrpc: "2.0", method: "ui/notifications/host-context-changed", params: { theme: darkMode ? "dark" : "light", styles: { variables: tokens } } });
|
|
@@ -174,7 +185,7 @@ async function startPreview(options) {
|
|
|
174
185
|
children.push(uiProc);
|
|
175
186
|
uiProc.stdout?.on("data", (d) => process.stdout.write(` [ui] ${d}`));
|
|
176
187
|
uiProc.stderr?.on("data", (d) => process.stderr.write(` [ui] ${d}`));
|
|
177
|
-
const html =
|
|
188
|
+
const html = previewHostHtml(uiPort, serverPort);
|
|
178
189
|
const host = createServer((_req, res) => {
|
|
179
190
|
res.writeHead(200, { "Content-Type": "text/html" });
|
|
180
191
|
res.end(html);
|
|
@@ -203,6 +214,6 @@ async function startPreview(options) {
|
|
|
203
214
|
process.on("SIGTERM", shutdown);
|
|
204
215
|
}
|
|
205
216
|
|
|
206
|
-
export { startPreview };
|
|
207
|
-
//# sourceMappingURL=server-
|
|
208
|
-
//# sourceMappingURL=server-
|
|
217
|
+
export { previewHostHtml, startPreview };
|
|
218
|
+
//# sourceMappingURL=server-UHPEE55X.js.map
|
|
219
|
+
//# sourceMappingURL=server-UHPEE55X.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/preview/server.ts"],"names":[],"mappings":"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","file":"server-UHPEE55X.js","sourcesContent":["/**\n * Synapse Preview — standalone dev harness for MCP apps with UIs.\n *\n * Starts the MCP server (HTTP mode) and a minimal bridge host page\n * that iframes the app UI and proxies tool calls to the server.\n *\n * Usage:\n * npx synapse preview --server \"uv run uvicorn mcp_hello.server:app --port 8001\" --ui ./ui\n * npx synapse preview --server \"node dist/index.js\" --ui ./ui --server-port 8001\n */\n\nimport { type ChildProcess, spawn } from \"node:child_process\";\nimport { existsSync } from \"node:fs\";\nimport { createServer, type IncomingMessage, type ServerResponse } from \"node:http\";\nimport { join, resolve } from \"node:path\";\n\nexport interface PreviewOptions {\n /** Shell command to start the MCP server in HTTP mode */\n serverCmd: string;\n /** Port the MCP server listens on (default: 8001) */\n serverPort: number;\n /** Path to the UI directory (must have package.json with dev script) */\n uiDir: string;\n /** Port for the UI Vite dev server (default: 5173) */\n uiPort: number;\n /** Port for the preview harness (default: 5180) */\n previewPort: number;\n}\n\n/**\n * The preview harness page: the bridge host that frames the app UI on\n * `uiPort` and proxies its `tools/call` to the MCP server on `serverPort`.\n *\n * Exported so the conformance suite can serve this exact page and connect a\n * real spec client to it. Asserting the handshake against a copy would prove\n * nothing about what the preview actually answers.\n */\nexport const previewHostHtml = (uiPort: number, serverPort: number) => `<!DOCTYPE html>\n<html>\n<head>\n <meta charset=\"utf-8\" />\n <title>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: 12px 20px; background: #1e293b; border-bottom: 1px solid #334155; display: flex; align-items: center; gap: 12px; }\n header h1 { font-size: 14px; font-weight: 500; }\n header .dot { width: 8px; height: 8px; border-radius: 50%; background: #22c55e; }\n header .info { font-size: 12px; color: #94a3b8; margin-left: auto; }\n .theme-toggle { background: #334155; border: none; color: #e2e8f0; padding: 4px 10px; border-radius: 4px; cursor: pointer; font-size: 12px; }\n iframe { width: 100%; height: calc(100vh - 45px); border: none; }\n </style>\n</head>\n<body>\n <header>\n <span class=\"dot\"></span>\n <h1>Synapse Preview</h1>\n <button class=\"theme-toggle\" id=\"toggle\">Toggle Dark/Light</button>\n <span class=\"info\">MCP: localhost:${serverPort} | UI: localhost:${uiPort}</span>\n </header>\n <iframe id=\"app\" src=\"http://localhost:${uiPort}\"></iframe>\n\n <script>\n var iframe = document.getElementById(\"app\");\n var darkMode = true;\n\n // Minimal NimbleBrain bridge host — just enough to make Synapse work.\n //\n // Every key here is one the spec's style-variable enum names. That enum is\n // a strict record, so a single extra key makes a spec client reject the\n // whole \\`ui/initialize\\` result and refuse to connect — not just ignore the\n // key. The kit's own neutral defaults back anything a host leaves out, so\n // the accent and danger colours still render; they come from the cascade\n // layer instead of the wire.\n var tokens = darkMode ? {\n \"--color-background-primary\": \"#0f172a\", \"--color-background-secondary\": \"#1e293b\",\n \"--color-background-tertiary\": \"#2a374a\", \"--color-text-primary\": \"#e2e8f0\",\n \"--color-text-secondary\": \"#94a3b8\", \"--color-text-tertiary\": \"#64748b\",\n \"--color-border-primary\": \"#334155\", \"--color-border-secondary\": \"#475569\",\n \"--color-ring-primary\": \"#6366f1\",\n \"--border-radius-sm\": \"0.5rem\",\n } : {\n \"--color-background-primary\": \"#ffffff\", \"--color-background-secondary\": \"#f9fafb\",\n \"--color-background-tertiary\": \"#f1f5f9\", \"--color-text-primary\": \"#1a1a1a\",\n \"--color-text-secondary\": \"#6b7280\", \"--color-text-tertiary\": \"#94a3b8\",\n \"--color-border-primary\": \"#e5e7eb\", \"--color-border-secondary\": \"#cbd5e1\",\n \"--color-ring-primary\": \"#6366f1\",\n \"--border-radius-sm\": \"0.5rem\",\n };\n\n function post(msg) { iframe.contentWindow.postMessage(msg, \"*\"); }\n\n // A request is a frame carrying an id — and \\`0\\` is a perfectly good id.\n // The MCP SDK numbers from zero, so a truthiness test drops the first\n // request every spec client ever sends, which is its \\`ui/initialize\\`.\n function isRequest(msg) { return msg.id !== undefined && msg.id !== null; }\n\n window.addEventListener(\"message\", async function (event) {\n if (event.source !== iframe.contentWindow) return;\n var msg = event.data;\n if (!msg || typeof msg !== \"object\") return;\n\n // ext-apps handshake. \\`hostInfo\\`/\\`hostCapabilities\\` are the spec's field\n // names, and a spec client validates the result against them — the\n // official ext-apps \\`App\\` refuses to connect to anything else, so an app\n // built on it would render only in the real host and not in preview.\n if (msg.method === \"ui/initialize\" && isRequest(msg)) {\n post({\n jsonrpc: \"2.0\", id: msg.id,\n result: {\n protocolVersion: \"2026-01-26\",\n hostInfo: { name: \"nimblebrain\", version: \"preview\" },\n hostCapabilities: { openLinks: {}, serverTools: {} },\n hostContext: { theme: darkMode ? \"dark\" : \"light\", styles: { variables: tokens } }\n }\n });\n return;\n }\n\n if (msg.method === \"ui/notifications/initialized\") return;\n\n // Tool call proxy — forward to the MCP server\n if (msg.method === \"tools/call\" && isRequest(msg)) {\n try {\n var resp = await fetch(\"http://localhost:${serverPort}/mcp\", {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n jsonrpc: \"2.0\", id: msg.id,\n method: \"tools/call\",\n params: { name: msg.params.name, arguments: msg.params.arguments || {} }\n })\n });\n var result = await resp.json();\n // Forward the JSON-RPC response back to the iframe\n post(result);\n } catch (err) {\n post({ jsonrpc: \"2.0\", id: msg.id, error: { code: -32000, message: err.message || \"Server error\" } });\n }\n return;\n }\n\n // synapse/chat — log to console\n if (msg.method === \"synapse/chat\") {\n console.log(\"[chat]\", msg.params?.message);\n return;\n }\n\n // synapse/action — log to console\n if (msg.method === \"synapse/action\") {\n console.log(\"[action]\", msg.params?.action, msg.params);\n return;\n }\n\n // synapse/keydown — ignore in preview\n if (msg.method === \"synapse/keydown\") return;\n\n // ui/update-model-context — log (ext-apps spec)\n if (msg.method === \"ui/update-model-context\") {\n console.log(\"[model-context]\", msg.params?.structuredContent);\n if (isRequest(msg)) post({ jsonrpc: \"2.0\", id: msg.id, result: {} });\n return;\n }\n\n console.log(\"[bridge] unhandled:\", msg.method, msg);\n });\n\n // Theme toggle\n document.getElementById(\"toggle\").addEventListener(\"click\", function () {\n darkMode = !darkMode;\n document.body.style.background = darkMode ? \"#0f172a\" : \"#f1f5f9\";\n tokens = darkMode ? {\n \"--color-background-primary\": \"#0f172a\", \"--color-background-secondary\": \"#1e293b\",\n \"--color-background-tertiary\": \"#2a374a\", \"--color-text-primary\": \"#e2e8f0\",\n \"--color-text-secondary\": \"#94a3b8\", \"--color-text-tertiary\": \"#64748b\",\n \"--color-border-primary\": \"#334155\", \"--color-border-secondary\": \"#475569\",\n \"--color-ring-primary\": \"#6366f1\",\n \"--border-radius-sm\": \"0.5rem\",\n } : {\n \"--color-background-primary\": \"#ffffff\", \"--color-background-secondary\": \"#f9fafb\",\n \"--color-background-tertiary\": \"#f1f5f9\", \"--color-text-primary\": \"#1a1a1a\",\n \"--color-text-secondary\": \"#6b7280\", \"--color-text-tertiary\": \"#94a3b8\",\n \"--color-border-primary\": \"#e5e7eb\", \"--color-border-secondary\": \"#cbd5e1\",\n \"--color-ring-primary\": \"#6366f1\",\n \"--border-radius-sm\": \"0.5rem\",\n };\n post({ jsonrpc: \"2.0\", method: \"ui/notifications/host-context-changed\", params: { theme: darkMode ? \"dark\" : \"light\", styles: { variables: tokens } } });\n });\n </script>\n</body>\n</html>`;\n\nexport async function startPreview(options: PreviewOptions): Promise<void> {\n const { serverCmd, serverPort, uiDir, uiPort, previewPort } = options;\n const children: ChildProcess[] = [];\n\n console.log(`\\n Synapse Preview\\n`);\n\n // 1. Start MCP server\n console.log(` [server] ${serverCmd}`);\n const serverProc = spawn(serverCmd, {\n shell: true,\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n });\n children.push(serverProc);\n serverProc.stdout?.on(\"data\", (d: Buffer) => process.stdout.write(` [server] ${d}`));\n serverProc.stderr?.on(\"data\", (d: Buffer) => process.stderr.write(` [server] ${d}`));\n\n // 2. Start UI Vite dev server\n const resolvedUi = resolve(uiDir);\n if (!existsSync(join(resolvedUi, \"package.json\"))) {\n console.error(` [ui] Error: no package.json found at ${resolvedUi}`);\n process.exit(1);\n }\n console.log(` [ui] cd ${resolvedUi} && npx vite --port ${uiPort}`);\n const uiProc = spawn(\"npx\", [\"vite\", \"--port\", String(uiPort)], {\n cwd: resolvedUi,\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n });\n children.push(uiProc);\n uiProc.stdout?.on(\"data\", (d: Buffer) => process.stdout.write(` [ui] ${d}`));\n uiProc.stderr?.on(\"data\", (d: Buffer) => process.stderr.write(` [ui] ${d}`));\n\n // 3. Start preview host\n const html = previewHostHtml(uiPort, serverPort);\n const host = createServer((_req: IncomingMessage, res: ServerResponse) => {\n res.writeHead(200, { \"Content-Type\": \"text/html\" });\n res.end(html);\n });\n host.listen(previewPort, () => {\n console.log(`\\n Preview: http://localhost:${previewPort}`);\n console.log(` UI: http://localhost:${uiPort}`);\n console.log(` Server: http://localhost:${serverPort}`);\n console.log(`\\n Press Ctrl+C to stop.\\n`);\n });\n\n // Shutdown\n const shutdown = () => {\n console.log(\"\\n Shutting down...\");\n host.close();\n for (const child of children) {\n try {\n child.kill(\"SIGTERM\");\n } catch {\n /* already dead */\n }\n }\n process.exit(0);\n };\n process.on(\"SIGINT\", shutdown);\n process.on(\"SIGTERM\", shutdown);\n}\n"]}
|