@nimblebrain/synapse 0.1.0 → 0.1.1

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.
@@ -0,0 +1,204 @@
1
+ import { spawn } from 'child_process';
2
+ import { existsSync } from 'fs';
3
+ import { createServer } from 'http';
4
+ import { resolve, join } from 'path';
5
+
6
+ // src/preview/server.ts
7
+ var HOST_HTML = (uiPort, serverPort) => `<!DOCTYPE html>
8
+ <html>
9
+ <head>
10
+ <meta charset="utf-8" />
11
+ <title>Synapse Preview</title>
12
+ <style>
13
+ * { margin: 0; padding: 0; box-sizing: border-box; }
14
+ body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; background: #0f172a; color: #e2e8f0; }
15
+ header { padding: 12px 20px; background: #1e293b; border-bottom: 1px solid #334155; display: flex; align-items: center; gap: 12px; }
16
+ header h1 { font-size: 14px; font-weight: 500; }
17
+ header .dot { width: 8px; height: 8px; border-radius: 50%; background: #22c55e; }
18
+ header .info { font-size: 12px; color: #94a3b8; margin-left: auto; }
19
+ .theme-toggle { background: #334155; border: none; color: #e2e8f0; padding: 4px 10px; border-radius: 4px; cursor: pointer; font-size: 12px; }
20
+ iframe { width: 100%; height: calc(100vh - 45px); border: none; }
21
+ </style>
22
+ </head>
23
+ <body>
24
+ <header>
25
+ <span class="dot"></span>
26
+ <h1>Synapse Preview</h1>
27
+ <button class="theme-toggle" id="toggle">Toggle Dark/Light</button>
28
+ <span class="info">MCP: localhost:${serverPort} | UI: localhost:${uiPort}</span>
29
+ </header>
30
+ <iframe id="app" src="http://localhost:${uiPort}"></iframe>
31
+
32
+ <script>
33
+ var iframe = document.getElementById("app");
34
+ var darkMode = true;
35
+
36
+ // Minimal NimbleBrain bridge host \u2014 just enough to make Synapse work
37
+ var tokens = darkMode ? {
38
+ "--nb-background": "#0f172a", "--nb-foreground": "#e2e8f0",
39
+ "--nb-card": "#1e293b", "--nb-card-foreground": "#e2e8f0",
40
+ "--nb-primary": "#6366f1", "--nb-primary-foreground": "#ffffff",
41
+ "--nb-muted-foreground": "#94a3b8", "--nb-border": "#334155",
42
+ "--nb-ring": "#6366f1", "--nb-destructive": "#ef4444",
43
+ "--nb-radius": "0.5rem",
44
+ } : {
45
+ "--nb-background": "#ffffff", "--nb-foreground": "#1a1a1a",
46
+ "--nb-card": "#f9fafb", "--nb-card-foreground": "#1a1a1a",
47
+ "--nb-primary": "#6366f1", "--nb-primary-foreground": "#ffffff",
48
+ "--nb-muted-foreground": "#6b7280", "--nb-border": "#e5e7eb",
49
+ "--nb-ring": "#6366f1", "--nb-destructive": "#ef4444",
50
+ "--nb-radius": "0.5rem",
51
+ };
52
+
53
+ function post(msg) { iframe.contentWindow.postMessage(msg, "*"); }
54
+
55
+ window.addEventListener("message", async function (event) {
56
+ if (event.source !== iframe.contentWindow) return;
57
+ var msg = event.data;
58
+ if (!msg || typeof msg !== "object") return;
59
+
60
+ // ext-apps handshake
61
+ if (msg.method === "ui/initialize" && msg.id) {
62
+ post({
63
+ jsonrpc: "2.0", id: msg.id,
64
+ result: {
65
+ protocolVersion: "2026-01-26",
66
+ serverInfo: { name: "nimblebrain", version: "preview" },
67
+ capabilities: { openLinks: {}, serverTools: {} },
68
+ hostContext: { theme: darkMode ? "dark" : "light", primaryColor: "#6366f1", tokens: tokens }
69
+ }
70
+ });
71
+ return;
72
+ }
73
+
74
+ if (msg.method === "ui/notifications/initialized") return;
75
+
76
+ // Tool call proxy \u2014 forward to the MCP server
77
+ if (msg.method === "tools/call" && msg.id) {
78
+ try {
79
+ var resp = await fetch("http://localhost:${serverPort}/mcp", {
80
+ method: "POST",
81
+ headers: { "Content-Type": "application/json" },
82
+ body: JSON.stringify({
83
+ jsonrpc: "2.0", id: msg.id,
84
+ method: "tools/call",
85
+ params: { name: msg.params.name, arguments: msg.params.arguments || {} }
86
+ })
87
+ });
88
+ var result = await resp.json();
89
+ // Forward the JSON-RPC response back to the iframe
90
+ post(result);
91
+ } catch (err) {
92
+ post({ jsonrpc: "2.0", id: msg.id, error: { code: -32000, message: err.message || "Server error" } });
93
+ }
94
+ return;
95
+ }
96
+
97
+ // ui/chat \u2014 log to console
98
+ if (msg.method === "ui/chat") {
99
+ console.log("[chat]", msg.params?.message);
100
+ return;
101
+ }
102
+
103
+ // ui/action \u2014 log to console
104
+ if (msg.method === "ui/action") {
105
+ console.log("[action]", msg.params?.action, msg.params);
106
+ return;
107
+ }
108
+
109
+ // ui/keydown \u2014 ignore in preview
110
+ if (msg.method === "ui/keydown") return;
111
+
112
+ // ui/stateChanged \u2014 log
113
+ if (msg.method === "ui/stateChanged") {
114
+ console.log("[state]", msg.params?.state);
115
+ post({ jsonrpc: "2.0", method: "ui/stateAcknowledged", params: { truncated: false } });
116
+ return;
117
+ }
118
+
119
+ console.log("[bridge] unhandled:", msg.method, msg);
120
+ });
121
+
122
+ // Theme toggle
123
+ document.getElementById("toggle").addEventListener("click", function () {
124
+ darkMode = !darkMode;
125
+ document.body.style.background = darkMode ? "#0f172a" : "#f1f5f9";
126
+ tokens = darkMode ? {
127
+ "--nb-background": "#0f172a", "--nb-foreground": "#e2e8f0",
128
+ "--nb-card": "#1e293b", "--nb-card-foreground": "#e2e8f0",
129
+ "--nb-primary": "#6366f1", "--nb-primary-foreground": "#ffffff",
130
+ "--nb-muted-foreground": "#94a3b8", "--nb-border": "#334155",
131
+ "--nb-ring": "#6366f1", "--nb-destructive": "#ef4444",
132
+ "--nb-radius": "0.5rem",
133
+ } : {
134
+ "--nb-background": "#ffffff", "--nb-foreground": "#1a1a1a",
135
+ "--nb-card": "#f9fafb", "--nb-card-foreground": "#1a1a1a",
136
+ "--nb-primary": "#6366f1", "--nb-primary-foreground": "#ffffff",
137
+ "--nb-muted-foreground": "#6b7280", "--nb-border": "#e5e7eb",
138
+ "--nb-ring": "#6366f1", "--nb-destructive": "#ef4444",
139
+ "--nb-radius": "0.5rem",
140
+ };
141
+ post({ jsonrpc: "2.0", method: "ui/themeChanged", params: { mode: darkMode ? "dark" : "light", tokens: tokens } });
142
+ });
143
+ </script>
144
+ </body>
145
+ </html>`;
146
+ async function startPreview(options) {
147
+ const { serverCmd, serverPort, uiDir, uiPort, previewPort } = options;
148
+ const children = [];
149
+ console.log(`
150
+ Synapse Preview
151
+ `);
152
+ console.log(` [server] ${serverCmd}`);
153
+ const serverProc = spawn(serverCmd, {
154
+ shell: true,
155
+ stdio: ["ignore", "pipe", "pipe"]
156
+ });
157
+ children.push(serverProc);
158
+ serverProc.stdout?.on("data", (d) => process.stdout.write(` [server] ${d}`));
159
+ serverProc.stderr?.on("data", (d) => process.stderr.write(` [server] ${d}`));
160
+ const resolvedUi = resolve(uiDir);
161
+ if (!existsSync(join(resolvedUi, "package.json"))) {
162
+ console.error(` [ui] Error: no package.json found at ${resolvedUi}`);
163
+ process.exit(1);
164
+ }
165
+ console.log(` [ui] cd ${resolvedUi} && npx vite --port ${uiPort}`);
166
+ const uiProc = spawn("npx", ["vite", "--port", String(uiPort)], {
167
+ cwd: resolvedUi,
168
+ stdio: ["ignore", "pipe", "pipe"]
169
+ });
170
+ children.push(uiProc);
171
+ uiProc.stdout?.on("data", (d) => process.stdout.write(` [ui] ${d}`));
172
+ uiProc.stderr?.on("data", (d) => process.stderr.write(` [ui] ${d}`));
173
+ const html = HOST_HTML(uiPort, serverPort);
174
+ const host = createServer((_req, res) => {
175
+ res.writeHead(200, { "Content-Type": "text/html" });
176
+ res.end(html);
177
+ });
178
+ host.listen(previewPort, () => {
179
+ console.log(`
180
+ Preview: http://localhost:${previewPort}`);
181
+ console.log(` UI: http://localhost:${uiPort}`);
182
+ console.log(` Server: http://localhost:${serverPort}`);
183
+ console.log(`
184
+ Press Ctrl+C to stop.
185
+ `);
186
+ });
187
+ const shutdown = () => {
188
+ console.log("\n Shutting down...");
189
+ host.close();
190
+ for (const child of children) {
191
+ try {
192
+ child.kill("SIGTERM");
193
+ } catch {
194
+ }
195
+ }
196
+ process.exit(0);
197
+ };
198
+ process.on("SIGINT", shutdown);
199
+ process.on("SIGTERM", shutdown);
200
+ }
201
+
202
+ export { startPreview };
203
+ //# sourceMappingURL=server-RUCX2TIB.js.map
204
+ //# sourceMappingURL=server-RUCX2TIB.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/preview/server.ts"],"names":[],"mappings":"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","file":"server-RUCX2TIB.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\nconst HOST_HTML = (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 var tokens = darkMode ? {\n \"--nb-background\": \"#0f172a\", \"--nb-foreground\": \"#e2e8f0\",\n \"--nb-card\": \"#1e293b\", \"--nb-card-foreground\": \"#e2e8f0\",\n \"--nb-primary\": \"#6366f1\", \"--nb-primary-foreground\": \"#ffffff\",\n \"--nb-muted-foreground\": \"#94a3b8\", \"--nb-border\": \"#334155\",\n \"--nb-ring\": \"#6366f1\", \"--nb-destructive\": \"#ef4444\",\n \"--nb-radius\": \"0.5rem\",\n } : {\n \"--nb-background\": \"#ffffff\", \"--nb-foreground\": \"#1a1a1a\",\n \"--nb-card\": \"#f9fafb\", \"--nb-card-foreground\": \"#1a1a1a\",\n \"--nb-primary\": \"#6366f1\", \"--nb-primary-foreground\": \"#ffffff\",\n \"--nb-muted-foreground\": \"#6b7280\", \"--nb-border\": \"#e5e7eb\",\n \"--nb-ring\": \"#6366f1\", \"--nb-destructive\": \"#ef4444\",\n \"--nb-radius\": \"0.5rem\",\n };\n\n function post(msg) { iframe.contentWindow.postMessage(msg, \"*\"); }\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\n if (msg.method === \"ui/initialize\" && msg.id) {\n post({\n jsonrpc: \"2.0\", id: msg.id,\n result: {\n protocolVersion: \"2026-01-26\",\n serverInfo: { name: \"nimblebrain\", version: \"preview\" },\n capabilities: { openLinks: {}, serverTools: {} },\n hostContext: { theme: darkMode ? \"dark\" : \"light\", primaryColor: \"#6366f1\", tokens: 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\" && msg.id) {\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 // ui/chat — log to console\n if (msg.method === \"ui/chat\") {\n console.log(\"[chat]\", msg.params?.message);\n return;\n }\n\n // ui/action — log to console\n if (msg.method === \"ui/action\") {\n console.log(\"[action]\", msg.params?.action, msg.params);\n return;\n }\n\n // ui/keydown — ignore in preview\n if (msg.method === \"ui/keydown\") return;\n\n // ui/stateChanged — log\n if (msg.method === \"ui/stateChanged\") {\n console.log(\"[state]\", msg.params?.state);\n post({ jsonrpc: \"2.0\", method: \"ui/stateAcknowledged\", params: { truncated: false } });\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 \"--nb-background\": \"#0f172a\", \"--nb-foreground\": \"#e2e8f0\",\n \"--nb-card\": \"#1e293b\", \"--nb-card-foreground\": \"#e2e8f0\",\n \"--nb-primary\": \"#6366f1\", \"--nb-primary-foreground\": \"#ffffff\",\n \"--nb-muted-foreground\": \"#94a3b8\", \"--nb-border\": \"#334155\",\n \"--nb-ring\": \"#6366f1\", \"--nb-destructive\": \"#ef4444\",\n \"--nb-radius\": \"0.5rem\",\n } : {\n \"--nb-background\": \"#ffffff\", \"--nb-foreground\": \"#1a1a1a\",\n \"--nb-card\": \"#f9fafb\", \"--nb-card-foreground\": \"#1a1a1a\",\n \"--nb-primary\": \"#6366f1\", \"--nb-primary-foreground\": \"#ffffff\",\n \"--nb-muted-foreground\": \"#6b7280\", \"--nb-border\": \"#e5e7eb\",\n \"--nb-ring\": \"#6366f1\", \"--nb-destructive\": \"#ef4444\",\n \"--nb-radius\": \"0.5rem\",\n };\n post({ jsonrpc: \"2.0\", method: \"ui/themeChanged\", params: { mode: darkMode ? \"dark\" : \"light\", tokens: 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 = HOST_HTML(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"]}
@@ -0,0 +1,206 @@
1
+ 'use strict';
2
+
3
+ var child_process = require('child_process');
4
+ var fs = require('fs');
5
+ var http = require('http');
6
+ var path = require('path');
7
+
8
+ // src/preview/server.ts
9
+ var HOST_HTML = (uiPort, serverPort) => `<!DOCTYPE html>
10
+ <html>
11
+ <head>
12
+ <meta charset="utf-8" />
13
+ <title>Synapse Preview</title>
14
+ <style>
15
+ * { margin: 0; padding: 0; box-sizing: border-box; }
16
+ body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; background: #0f172a; color: #e2e8f0; }
17
+ header { padding: 12px 20px; background: #1e293b; border-bottom: 1px solid #334155; display: flex; align-items: center; gap: 12px; }
18
+ header h1 { font-size: 14px; font-weight: 500; }
19
+ header .dot { width: 8px; height: 8px; border-radius: 50%; background: #22c55e; }
20
+ header .info { font-size: 12px; color: #94a3b8; margin-left: auto; }
21
+ .theme-toggle { background: #334155; border: none; color: #e2e8f0; padding: 4px 10px; border-radius: 4px; cursor: pointer; font-size: 12px; }
22
+ iframe { width: 100%; height: calc(100vh - 45px); border: none; }
23
+ </style>
24
+ </head>
25
+ <body>
26
+ <header>
27
+ <span class="dot"></span>
28
+ <h1>Synapse Preview</h1>
29
+ <button class="theme-toggle" id="toggle">Toggle Dark/Light</button>
30
+ <span class="info">MCP: localhost:${serverPort} | UI: localhost:${uiPort}</span>
31
+ </header>
32
+ <iframe id="app" src="http://localhost:${uiPort}"></iframe>
33
+
34
+ <script>
35
+ var iframe = document.getElementById("app");
36
+ var darkMode = true;
37
+
38
+ // Minimal NimbleBrain bridge host \u2014 just enough to make Synapse work
39
+ var tokens = darkMode ? {
40
+ "--nb-background": "#0f172a", "--nb-foreground": "#e2e8f0",
41
+ "--nb-card": "#1e293b", "--nb-card-foreground": "#e2e8f0",
42
+ "--nb-primary": "#6366f1", "--nb-primary-foreground": "#ffffff",
43
+ "--nb-muted-foreground": "#94a3b8", "--nb-border": "#334155",
44
+ "--nb-ring": "#6366f1", "--nb-destructive": "#ef4444",
45
+ "--nb-radius": "0.5rem",
46
+ } : {
47
+ "--nb-background": "#ffffff", "--nb-foreground": "#1a1a1a",
48
+ "--nb-card": "#f9fafb", "--nb-card-foreground": "#1a1a1a",
49
+ "--nb-primary": "#6366f1", "--nb-primary-foreground": "#ffffff",
50
+ "--nb-muted-foreground": "#6b7280", "--nb-border": "#e5e7eb",
51
+ "--nb-ring": "#6366f1", "--nb-destructive": "#ef4444",
52
+ "--nb-radius": "0.5rem",
53
+ };
54
+
55
+ function post(msg) { iframe.contentWindow.postMessage(msg, "*"); }
56
+
57
+ window.addEventListener("message", async function (event) {
58
+ if (event.source !== iframe.contentWindow) return;
59
+ var msg = event.data;
60
+ if (!msg || typeof msg !== "object") return;
61
+
62
+ // ext-apps handshake
63
+ if (msg.method === "ui/initialize" && msg.id) {
64
+ post({
65
+ jsonrpc: "2.0", id: msg.id,
66
+ result: {
67
+ protocolVersion: "2026-01-26",
68
+ serverInfo: { name: "nimblebrain", version: "preview" },
69
+ capabilities: { openLinks: {}, serverTools: {} },
70
+ hostContext: { theme: darkMode ? "dark" : "light", primaryColor: "#6366f1", tokens: tokens }
71
+ }
72
+ });
73
+ return;
74
+ }
75
+
76
+ if (msg.method === "ui/notifications/initialized") return;
77
+
78
+ // Tool call proxy \u2014 forward to the MCP server
79
+ if (msg.method === "tools/call" && msg.id) {
80
+ try {
81
+ var resp = await fetch("http://localhost:${serverPort}/mcp", {
82
+ method: "POST",
83
+ headers: { "Content-Type": "application/json" },
84
+ body: JSON.stringify({
85
+ jsonrpc: "2.0", id: msg.id,
86
+ method: "tools/call",
87
+ params: { name: msg.params.name, arguments: msg.params.arguments || {} }
88
+ })
89
+ });
90
+ var result = await resp.json();
91
+ // Forward the JSON-RPC response back to the iframe
92
+ post(result);
93
+ } catch (err) {
94
+ post({ jsonrpc: "2.0", id: msg.id, error: { code: -32000, message: err.message || "Server error" } });
95
+ }
96
+ return;
97
+ }
98
+
99
+ // ui/chat \u2014 log to console
100
+ if (msg.method === "ui/chat") {
101
+ console.log("[chat]", msg.params?.message);
102
+ return;
103
+ }
104
+
105
+ // ui/action \u2014 log to console
106
+ if (msg.method === "ui/action") {
107
+ console.log("[action]", msg.params?.action, msg.params);
108
+ return;
109
+ }
110
+
111
+ // ui/keydown \u2014 ignore in preview
112
+ if (msg.method === "ui/keydown") return;
113
+
114
+ // ui/stateChanged \u2014 log
115
+ if (msg.method === "ui/stateChanged") {
116
+ console.log("[state]", msg.params?.state);
117
+ post({ jsonrpc: "2.0", method: "ui/stateAcknowledged", params: { truncated: false } });
118
+ return;
119
+ }
120
+
121
+ console.log("[bridge] unhandled:", msg.method, msg);
122
+ });
123
+
124
+ // Theme toggle
125
+ document.getElementById("toggle").addEventListener("click", function () {
126
+ darkMode = !darkMode;
127
+ document.body.style.background = darkMode ? "#0f172a" : "#f1f5f9";
128
+ tokens = darkMode ? {
129
+ "--nb-background": "#0f172a", "--nb-foreground": "#e2e8f0",
130
+ "--nb-card": "#1e293b", "--nb-card-foreground": "#e2e8f0",
131
+ "--nb-primary": "#6366f1", "--nb-primary-foreground": "#ffffff",
132
+ "--nb-muted-foreground": "#94a3b8", "--nb-border": "#334155",
133
+ "--nb-ring": "#6366f1", "--nb-destructive": "#ef4444",
134
+ "--nb-radius": "0.5rem",
135
+ } : {
136
+ "--nb-background": "#ffffff", "--nb-foreground": "#1a1a1a",
137
+ "--nb-card": "#f9fafb", "--nb-card-foreground": "#1a1a1a",
138
+ "--nb-primary": "#6366f1", "--nb-primary-foreground": "#ffffff",
139
+ "--nb-muted-foreground": "#6b7280", "--nb-border": "#e5e7eb",
140
+ "--nb-ring": "#6366f1", "--nb-destructive": "#ef4444",
141
+ "--nb-radius": "0.5rem",
142
+ };
143
+ post({ jsonrpc: "2.0", method: "ui/themeChanged", params: { mode: darkMode ? "dark" : "light", tokens: tokens } });
144
+ });
145
+ </script>
146
+ </body>
147
+ </html>`;
148
+ async function startPreview(options) {
149
+ const { serverCmd, serverPort, uiDir, uiPort, previewPort } = options;
150
+ const children = [];
151
+ console.log(`
152
+ Synapse Preview
153
+ `);
154
+ console.log(` [server] ${serverCmd}`);
155
+ const serverProc = child_process.spawn(serverCmd, {
156
+ shell: true,
157
+ stdio: ["ignore", "pipe", "pipe"]
158
+ });
159
+ children.push(serverProc);
160
+ serverProc.stdout?.on("data", (d) => process.stdout.write(` [server] ${d}`));
161
+ serverProc.stderr?.on("data", (d) => process.stderr.write(` [server] ${d}`));
162
+ const resolvedUi = path.resolve(uiDir);
163
+ if (!fs.existsSync(path.join(resolvedUi, "package.json"))) {
164
+ console.error(` [ui] Error: no package.json found at ${resolvedUi}`);
165
+ process.exit(1);
166
+ }
167
+ console.log(` [ui] cd ${resolvedUi} && npx vite --port ${uiPort}`);
168
+ const uiProc = child_process.spawn("npx", ["vite", "--port", String(uiPort)], {
169
+ cwd: resolvedUi,
170
+ stdio: ["ignore", "pipe", "pipe"]
171
+ });
172
+ children.push(uiProc);
173
+ uiProc.stdout?.on("data", (d) => process.stdout.write(` [ui] ${d}`));
174
+ uiProc.stderr?.on("data", (d) => process.stderr.write(` [ui] ${d}`));
175
+ const html = HOST_HTML(uiPort, serverPort);
176
+ const host = http.createServer((_req, res) => {
177
+ res.writeHead(200, { "Content-Type": "text/html" });
178
+ res.end(html);
179
+ });
180
+ host.listen(previewPort, () => {
181
+ console.log(`
182
+ Preview: http://localhost:${previewPort}`);
183
+ console.log(` UI: http://localhost:${uiPort}`);
184
+ console.log(` Server: http://localhost:${serverPort}`);
185
+ console.log(`
186
+ Press Ctrl+C to stop.
187
+ `);
188
+ });
189
+ const shutdown = () => {
190
+ console.log("\n Shutting down...");
191
+ host.close();
192
+ for (const child of children) {
193
+ try {
194
+ child.kill("SIGTERM");
195
+ } catch {
196
+ }
197
+ }
198
+ process.exit(0);
199
+ };
200
+ process.on("SIGINT", shutdown);
201
+ process.on("SIGTERM", shutdown);
202
+ }
203
+
204
+ exports.startPreview = startPreview;
205
+ //# sourceMappingURL=server-SEI7XI3B.cjs.map
206
+ //# sourceMappingURL=server-SEI7XI3B.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-SEI7XI3B.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\nconst HOST_HTML = (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 var tokens = darkMode ? {\n \"--nb-background\": \"#0f172a\", \"--nb-foreground\": \"#e2e8f0\",\n \"--nb-card\": \"#1e293b\", \"--nb-card-foreground\": \"#e2e8f0\",\n \"--nb-primary\": \"#6366f1\", \"--nb-primary-foreground\": \"#ffffff\",\n \"--nb-muted-foreground\": \"#94a3b8\", \"--nb-border\": \"#334155\",\n \"--nb-ring\": \"#6366f1\", \"--nb-destructive\": \"#ef4444\",\n \"--nb-radius\": \"0.5rem\",\n } : {\n \"--nb-background\": \"#ffffff\", \"--nb-foreground\": \"#1a1a1a\",\n \"--nb-card\": \"#f9fafb\", \"--nb-card-foreground\": \"#1a1a1a\",\n \"--nb-primary\": \"#6366f1\", \"--nb-primary-foreground\": \"#ffffff\",\n \"--nb-muted-foreground\": \"#6b7280\", \"--nb-border\": \"#e5e7eb\",\n \"--nb-ring\": \"#6366f1\", \"--nb-destructive\": \"#ef4444\",\n \"--nb-radius\": \"0.5rem\",\n };\n\n function post(msg) { iframe.contentWindow.postMessage(msg, \"*\"); }\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\n if (msg.method === \"ui/initialize\" && msg.id) {\n post({\n jsonrpc: \"2.0\", id: msg.id,\n result: {\n protocolVersion: \"2026-01-26\",\n serverInfo: { name: \"nimblebrain\", version: \"preview\" },\n capabilities: { openLinks: {}, serverTools: {} },\n hostContext: { theme: darkMode ? \"dark\" : \"light\", primaryColor: \"#6366f1\", tokens: 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\" && msg.id) {\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 // ui/chat — log to console\n if (msg.method === \"ui/chat\") {\n console.log(\"[chat]\", msg.params?.message);\n return;\n }\n\n // ui/action — log to console\n if (msg.method === \"ui/action\") {\n console.log(\"[action]\", msg.params?.action, msg.params);\n return;\n }\n\n // ui/keydown — ignore in preview\n if (msg.method === \"ui/keydown\") return;\n\n // ui/stateChanged — log\n if (msg.method === \"ui/stateChanged\") {\n console.log(\"[state]\", msg.params?.state);\n post({ jsonrpc: \"2.0\", method: \"ui/stateAcknowledged\", params: { truncated: false } });\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 \"--nb-background\": \"#0f172a\", \"--nb-foreground\": \"#e2e8f0\",\n \"--nb-card\": \"#1e293b\", \"--nb-card-foreground\": \"#e2e8f0\",\n \"--nb-primary\": \"#6366f1\", \"--nb-primary-foreground\": \"#ffffff\",\n \"--nb-muted-foreground\": \"#94a3b8\", \"--nb-border\": \"#334155\",\n \"--nb-ring\": \"#6366f1\", \"--nb-destructive\": \"#ef4444\",\n \"--nb-radius\": \"0.5rem\",\n } : {\n \"--nb-background\": \"#ffffff\", \"--nb-foreground\": \"#1a1a1a\",\n \"--nb-card\": \"#f9fafb\", \"--nb-card-foreground\": \"#1a1a1a\",\n \"--nb-primary\": \"#6366f1\", \"--nb-primary-foreground\": \"#ffffff\",\n \"--nb-muted-foreground\": \"#6b7280\", \"--nb-border\": \"#e5e7eb\",\n \"--nb-ring\": \"#6366f1\", \"--nb-destructive\": \"#ef4444\",\n \"--nb-radius\": \"0.5rem\",\n };\n post({ jsonrpc: \"2.0\", method: \"ui/themeChanged\", params: { mode: darkMode ? \"dark\" : \"light\", tokens: 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 = HOST_HTML(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"]}
@@ -0,0 +1,3 @@
1
+ export { generateTypes } from './chunk-JKHGWDZI.js';
2
+ //# sourceMappingURL=type-generator-MZ4X4DE5.js.map
3
+ //# sourceMappingURL=type-generator-MZ4X4DE5.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"type-generator-MZ4X4DE5.js"}
@@ -0,0 +1,12 @@
1
+ 'use strict';
2
+
3
+ var chunkHLT5UBJF_cjs = require('./chunk-HLT5UBJF.cjs');
4
+
5
+
6
+
7
+ Object.defineProperty(exports, "generateTypes", {
8
+ enumerable: true,
9
+ get: function () { return chunkHLT5UBJF_cjs.generateTypes; }
10
+ });
11
+ //# sourceMappingURL=type-generator-UA3L4OIA.cjs.map
12
+ //# sourceMappingURL=type-generator-UA3L4OIA.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"type-generator-UA3L4OIA.cjs"}
@@ -0,0 +1,14 @@
1
+ 'use strict';
2
+
3
+ var fs = require('fs');
4
+ var path = require('path');
5
+
6
+ // src/codegen/writer.ts
7
+ function writeOutput(content, outputPath) {
8
+ fs.mkdirSync(path.dirname(outputPath), { recursive: true });
9
+ fs.writeFileSync(outputPath, content, "utf-8");
10
+ }
11
+
12
+ exports.writeOutput = writeOutput;
13
+ //# sourceMappingURL=writer-NDK4U6C5.cjs.map
14
+ //# sourceMappingURL=writer-NDK4U6C5.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/codegen/writer.ts"],"names":["mkdirSync","dirname","writeFileSync"],"mappings":";;;;;;AAMO,SAAS,WAAA,CAAY,SAAiB,UAAA,EAA0B;AACrE,EAAAA,YAAA,CAAUC,aAAQ,UAAU,CAAA,EAAG,EAAE,SAAA,EAAW,MAAM,CAAA;AAClD,EAAAC,gBAAA,CAAc,UAAA,EAAY,SAAS,OAAO,CAAA;AAC5C","file":"writer-NDK4U6C5.cjs","sourcesContent":["import { mkdirSync, writeFileSync } from \"node:fs\";\nimport { dirname } from \"node:path\";\n\n/**\n * Write generated TypeScript to a file, creating parent directories as needed.\n */\nexport function writeOutput(content: string, outputPath: string): void {\n mkdirSync(dirname(outputPath), { recursive: true });\n writeFileSync(outputPath, content, \"utf-8\");\n}\n"]}
@@ -0,0 +1,12 @@
1
+ import { mkdirSync, writeFileSync } from 'fs';
2
+ import { dirname } from 'path';
3
+
4
+ // src/codegen/writer.ts
5
+ function writeOutput(content, outputPath) {
6
+ mkdirSync(dirname(outputPath), { recursive: true });
7
+ writeFileSync(outputPath, content, "utf-8");
8
+ }
9
+
10
+ export { writeOutput };
11
+ //# sourceMappingURL=writer-ZNBXYUYC.js.map
12
+ //# sourceMappingURL=writer-ZNBXYUYC.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/codegen/writer.ts"],"names":[],"mappings":";;;;AAMO,SAAS,WAAA,CAAY,SAAiB,UAAA,EAA0B;AACrE,EAAA,SAAA,CAAU,QAAQ,UAAU,CAAA,EAAG,EAAE,SAAA,EAAW,MAAM,CAAA;AAClD,EAAA,aAAA,CAAc,UAAA,EAAY,SAAS,OAAO,CAAA;AAC5C","file":"writer-ZNBXYUYC.js","sourcesContent":["import { mkdirSync, writeFileSync } from \"node:fs\";\nimport { dirname } from \"node:path\";\n\n/**\n * Write generated TypeScript to a file, creating parent directories as needed.\n */\nexport function writeOutput(content: string, outputPath: string): void {\n mkdirSync(dirname(outputPath), { recursive: true });\n writeFileSync(outputPath, content, \"utf-8\");\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nimblebrain/synapse",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Agent-aware app SDK for the NimbleBrain platform",
5
5
  "type": "module",
6
6
  "exports": {
@@ -63,15 +63,18 @@
63
63
  "devDependencies": {
64
64
  "@biomejs/biome": "^2.4.9",
65
65
  "@modelcontextprotocol/ext-apps": "^1.3.1",
66
- "@testing-library/react": "^16.0.0",
67
- "@types/node": "^22.19.15",
68
- "@types/react": "^19.0.0",
69
- "happy-dom": "^15.0.0",
70
- "react": "^19.0.0",
71
- "tsup": "^8.0.0",
72
- "typescript": "^5.7.0",
73
- "vite": "^6.0.0",
74
- "vitest": "^3.0.0"
66
+ "@testing-library/react": "^16.3.2",
67
+ "@types/node": "^25.5.0",
68
+ "@types/react": "^19.2.14",
69
+ "happy-dom": "^20.8.8",
70
+ "react": "^19.2.4",
71
+ "tsup": "^8.5.1",
72
+ "typescript": "^5.8.3",
73
+ "vite": "^8.0.3",
74
+ "vitest": "^4.1.2"
75
+ },
76
+ "publishConfig": {
77
+ "access": "public"
75
78
  },
76
79
  "license": "MIT",
77
80
  "keywords": [
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/codegen/schema-reader.ts","../src/codegen/type-generator.ts"],"names":["existsSync","readFileSync","readdirSync","join","basename"],"mappings":"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","file":"chunk-AW3YIXLE.cjs","sourcesContent":["import { existsSync, readdirSync, readFileSync } from \"node:fs\";\nimport { basename, join } from \"node:path\";\nimport type { ToolDefinition } from \"../types.js\";\n\n/**\n * Read tool definitions from a manifest.json file.\n * Extracts tools from the MCP standard `tools` array.\n */\nexport function readFromManifest(manifestPath: string): ToolDefinition[] {\n if (!existsSync(manifestPath)) {\n throw new Error(`Manifest not found: ${manifestPath}`);\n }\n\n const raw = JSON.parse(readFileSync(manifestPath, \"utf-8\"));\n const tools = raw.tools ?? [];\n\n if (!Array.isArray(tools)) {\n return [];\n }\n\n return tools.map((t: any) => ({\n name: t.name as string,\n description: t.description as string | undefined,\n inputSchema: (t.inputSchema ?? {}) as Record<string, unknown>,\n outputSchema: t.outputSchema as Record<string, unknown> | undefined,\n }));\n}\n\n/**\n * Read tool definitions from a running MCP server via tools/list.\n * Connects to the server's HTTP endpoint and calls the JSON-RPC method.\n */\nexport async function readFromServer(url: string): Promise<ToolDefinition[]> {\n const response = await fetch(url, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n jsonrpc: \"2.0\",\n method: \"tools/list\",\n id: \"codegen-1\",\n params: {},\n }),\n });\n\n if (!response.ok) {\n throw new Error(`Server responded with ${response.status}: ${response.statusText}`);\n }\n\n const result = await response.json();\n if (result.error) {\n throw new Error(`Server error: ${result.error.message}`);\n }\n\n const tools = result.result?.tools ?? [];\n return tools.map((t: any) => ({\n name: t.name as string,\n description: t.description as string | undefined,\n inputSchema: (t.inputSchema ?? {}) as Record<string, unknown>,\n outputSchema: t.outputSchema as Record<string, unknown> | undefined,\n }));\n}\n\n/**\n * Read entity schemas from a directory and generate CRUD tool definitions.\n * Each .schema.json file becomes create/read/update/delete/list tools.\n */\nexport function readFromSchemaDir(dirPath: string): ToolDefinition[] {\n if (!existsSync(dirPath)) {\n throw new Error(`Schema directory not found: ${dirPath}`);\n }\n\n const files = readdirSync(dirPath).filter((f: string) => f.endsWith(\".schema.json\"));\n const tools: ToolDefinition[] = [];\n\n for (const file of files) {\n const schema = JSON.parse(readFileSync(join(dirPath, file), \"utf-8\"));\n const entityName = basename(file, \".schema.json\");\n\n tools.push(\n {\n name: `create_${entityName}`,\n description: `Create a new ${entityName}`,\n inputSchema: schema,\n outputSchema: schema,\n },\n {\n name: `read_${entityName}`,\n description: `Read a ${entityName} by ID`,\n inputSchema: {\n type: \"object\",\n properties: { id: { type: \"string\" } },\n required: [\"id\"],\n },\n outputSchema: schema,\n },\n {\n name: `update_${entityName}`,\n description: `Update an existing ${entityName}`,\n inputSchema: schema,\n outputSchema: schema,\n },\n {\n name: `delete_${entityName}`,\n description: `Delete a ${entityName} by ID`,\n inputSchema: {\n type: \"object\",\n properties: { id: { type: \"string\" } },\n required: [\"id\"],\n },\n },\n {\n name: `list_${entityName}s`,\n description: `List all ${entityName}s`,\n inputSchema: {\n type: \"object\",\n properties: {\n filter: { type: \"string\" },\n limit: { type: \"number\" },\n },\n },\n outputSchema: {\n type: \"object\",\n properties: {\n items: { type: \"array\", items: schema },\n total: { type: \"number\" },\n },\n required: [\"items\", \"total\"],\n },\n },\n );\n }\n\n return tools;\n}\n","import type { ToolDefinition } from \"../types.js\";\n\n/**\n * Generate TypeScript interfaces from tool definitions.\n */\nexport function generateTypes(tools: ToolDefinition[], appName: string): string {\n const lines: string[] = [];\n const mapName = `${toPascalCase(appName)}ToolMap`;\n\n lines.push(\"/**\");\n lines.push(` * Auto-generated by @nimblebrain/synapse codegen`);\n lines.push(` * Source: ${appName}`);\n lines.push(` *`);\n lines.push(\n ` * DO NOT EDIT — regenerate with: npx synapse codegen --from-manifest ./manifest.json`,\n );\n lines.push(` */`);\n lines.push(\"\");\n\n const mapEntries: string[] = [];\n\n for (const tool of tools) {\n const baseName = toPascalCase(tool.name);\n\n // Input type\n const inputName = `${baseName}Input`;\n lines.push(schemaToInterface(inputName, tool.inputSchema));\n lines.push(\"\");\n\n // Output type\n let outputName = \"unknown\";\n if (tool.outputSchema) {\n outputName = `${baseName}Output`;\n lines.push(schemaToInterface(outputName, tool.outputSchema));\n lines.push(\"\");\n }\n\n mapEntries.push(` ${tool.name}: { input: ${inputName}; output: ${outputName} };`);\n }\n\n // Tool map\n lines.push(`/** Tool type map for typed useCallTool */`);\n lines.push(`export interface ${mapName} {`);\n for (const entry of mapEntries) {\n lines.push(entry);\n }\n lines.push(`}`);\n lines.push(\"\");\n\n return lines.join(\"\\n\");\n}\n\n// ---------------------------------------------------------------------------\n// JSON Schema -> TypeScript conversion\n// ---------------------------------------------------------------------------\n\nfunction schemaToInterface(name: string, schema: Record<string, unknown>): string {\n const type = schemaToType(schema, name);\n\n // If the schema is an object, generate a named interface\n if (schema.type === \"object\" || schema.properties) {\n return generateInterface(name, schema);\n }\n\n // Otherwise, generate a type alias\n return `export type ${name} = ${type};`;\n}\n\nfunction generateInterface(name: string, schema: Record<string, unknown>): string {\n const lines: string[] = [];\n lines.push(`export interface ${name} {`);\n\n const props = (schema.properties ?? {}) as Record<string, Record<string, unknown>>;\n const required = new Set((schema.required ?? []) as string[]);\n\n for (const [key, propSchema] of Object.entries(props)) {\n const isRequired = required.has(key);\n const type = schemaToType(propSchema, name + toPascalCase(key));\n const desc = propSchema.description;\n if (desc) {\n lines.push(` /** ${desc} */`);\n }\n lines.push(` ${key}${isRequired ? \"\" : \"?\"}: ${type};`);\n }\n\n lines.push(`}`);\n return lines.join(\"\\n\");\n}\n\nfunction schemaToType(schema: Record<string, unknown>, context: string): string {\n // Handle enum\n if (schema.enum && Array.isArray(schema.enum)) {\n return (schema.enum as unknown[])\n .map((v) => (typeof v === \"string\" ? `\"${v}\"` : String(v)))\n .join(\" | \");\n }\n\n // Handle oneOf / anyOf\n if (schema.oneOf && Array.isArray(schema.oneOf)) {\n return (schema.oneOf as Record<string, unknown>[])\n .map((s, i) => schemaToType(s, `${context}Option${i}`))\n .join(\" | \");\n }\n if (schema.anyOf && Array.isArray(schema.anyOf)) {\n return (schema.anyOf as Record<string, unknown>[])\n .map((s, i) => schemaToType(s, `${context}Option${i}`))\n .join(\" | \");\n }\n\n // Handle allOf (intersection)\n if (schema.allOf && Array.isArray(schema.allOf)) {\n return (schema.allOf as Record<string, unknown>[])\n .map((s, i) => schemaToType(s, `${context}Part${i}`))\n .join(\" & \");\n }\n\n // Handle type-based conversion\n const type = schema.type as string | string[] | undefined;\n\n if (Array.isArray(type)) {\n // Multi-type (e.g., [\"string\", \"null\"])\n return type.map((t) => primitiveType(t)).join(\" | \");\n }\n\n switch (type) {\n case \"string\":\n return \"string\";\n case \"number\":\n case \"integer\":\n return \"number\";\n case \"boolean\":\n return \"boolean\";\n case \"null\":\n return \"null\";\n case \"array\": {\n const items = schema.items as Record<string, unknown> | undefined;\n if (items) {\n return `${schemaToType(items, `${context}Item`)}[]`;\n }\n return \"unknown[]\";\n }\n case \"object\": {\n if (schema.properties) {\n // Inline object — generate property types\n const props = schema.properties as Record<string, Record<string, unknown>>;\n const required = new Set((schema.required ?? []) as string[]);\n const fields = Object.entries(props)\n .map(([key, propSchema]) => {\n const t = schemaToType(propSchema, context + toPascalCase(key));\n return `${key}${required.has(key) ? \"\" : \"?\"}: ${t}`;\n })\n .join(\"; \");\n return `{ ${fields} }`;\n }\n return \"Record<string, unknown>\";\n }\n default:\n return \"unknown\";\n }\n}\n\nfunction primitiveType(t: string): string {\n switch (t) {\n case \"string\":\n return \"string\";\n case \"number\":\n case \"integer\":\n return \"number\";\n case \"boolean\":\n return \"boolean\";\n case \"null\":\n return \"null\";\n default:\n return \"unknown\";\n }\n}\n\nfunction toPascalCase(str: string): string {\n return str\n .split(/[-_@/]/)\n .filter(Boolean)\n .map((word) => word.charAt(0).toUpperCase() + word.slice(1))\n .join(\"\");\n}\n"]}