@nimblebrain/synapse 0.1.4 → 0.2.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.
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/react/provider.tsx","../../src/react/hooks.ts"],"names":["useRef","useEffect"],"mappings":"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","file":"index.js","sourcesContent":["import { createContext, type ReactNode, useContext, useEffect, useRef } from \"react\";\nimport { createSynapse } from \"../core.js\";\nimport type { Synapse, SynapseOptions } from \"../types.js\";\n\nconst SynapseContext = createContext<Synapse | null>(null);\n\nexport interface SynapseProviderProps extends SynapseOptions {\n children: ReactNode;\n}\n\nexport function SynapseProvider({ children, ...options }: SynapseProviderProps) {\n const ref = useRef<Synapse | null>(null);\n\n if (ref.current === null) {\n ref.current = createSynapse(options);\n }\n\n useEffect(() => {\n // StrictMode: on double-mount, the ref may already have an instance.\n // The ref was created synchronously above, so it's always valid here.\n return () => {\n ref.current?.destroy();\n ref.current = null;\n };\n }, []);\n\n return <SynapseContext.Provider value={ref.current}>{children}</SynapseContext.Provider>;\n}\n\nexport function useSynapseContext(): Synapse {\n const ctx = useContext(SynapseContext);\n if (!ctx) {\n throw new Error(\n \"useSynapse must be used within a <SynapseProvider>. \" +\n \"Wrap your app component tree with <SynapseProvider>.\",\n );\n }\n return ctx;\n}\n","import { useCallback, useEffect, useRef, useState, useSyncExternalStore } from \"react\";\nimport type {\n ActionReducer,\n DataChangedEvent,\n Store,\n StoreDispatch,\n Synapse,\n SynapseTheme,\n ToolCallResult,\n} from \"../types.js\";\nimport { SynapseProvider, useSynapseContext } from \"./provider.js\";\n\n// Re-export provider components\nexport { SynapseProvider };\n\nexport function useSynapse(): Synapse {\n return useSynapseContext();\n}\n\nexport function useCallTool<TOutput = unknown>(\n toolName: string,\n): {\n call: (args?: Record<string, unknown>) => Promise<ToolCallResult<TOutput>>;\n isPending: boolean;\n error: Error | null;\n data: TOutput | null;\n} {\n const synapse = useSynapseContext();\n const [isPending, setIsPending] = useState(false);\n const [error, setError] = useState<Error | null>(null);\n const [data, setData] = useState<TOutput | null>(null);\n const callIdRef = useRef(0);\n\n const call = useCallback(\n async (args?: Record<string, unknown>): Promise<ToolCallResult<TOutput>> => {\n const id = ++callIdRef.current;\n setIsPending(true);\n setError(null);\n\n try {\n const result = await synapse.callTool<Record<string, unknown>, TOutput>(toolName, args);\n // Stale guard: only update if this is still the latest call\n if (id === callIdRef.current) {\n setData(result.data);\n setIsPending(false);\n }\n return result;\n } catch (err) {\n if (id === callIdRef.current) {\n const e = err instanceof Error ? err : new Error(String(err));\n setError(e);\n setIsPending(false);\n }\n throw err;\n }\n },\n [synapse, toolName],\n );\n\n return { call, isPending, error, data };\n}\n\nexport function useDataSync(callback: (event: DataChangedEvent) => void): void {\n const synapse = useSynapseContext();\n const callbackRef = useRef(callback);\n callbackRef.current = callback;\n\n useEffect(() => {\n return synapse.onDataChanged((event) => callbackRef.current(event));\n }, [synapse]);\n}\n\nexport function useTheme(): SynapseTheme {\n const synapse = useSynapseContext();\n const [theme, setTheme] = useState<SynapseTheme>(() => synapse.getTheme());\n\n useEffect(() => {\n // Sync in case theme changed between render and effect\n setTheme(synapse.getTheme());\n return synapse.onThemeChanged(setTheme);\n }, [synapse]);\n\n return theme;\n}\n\nexport function useAction(): (action: string, params?: Record<string, unknown>) => void {\n const synapse = useSynapseContext();\n return useCallback(\n (action: string, params?: Record<string, unknown>) => synapse.action(action, params),\n [synapse],\n );\n}\n\nexport function useChat(): (\n message: string,\n context?: { action?: string; entity?: string },\n) => void {\n const synapse = useSynapseContext();\n return useCallback(\n (message: string, context?: { action?: string; entity?: string }) =>\n synapse.chat(message, context),\n [synapse],\n );\n}\n\nexport function useVisibleState(): (state: Record<string, unknown>, summary?: string) => void {\n const synapse = useSynapseContext();\n return useCallback(\n (state: Record<string, unknown>, summary?: string) => synapse.setVisibleState(state, summary),\n [synapse],\n );\n}\n\nexport function useStore<TState, TActions extends Record<string, ActionReducer<TState, any>>>(\n store: Store<TState, TActions>,\n): {\n state: TState;\n dispatch: StoreDispatch<TActions>;\n} {\n const state = useSyncExternalStore(\n (onStoreChange) => store.subscribe(onStoreChange),\n () => store.getState(),\n () => store.getState(),\n );\n\n return { state, dispatch: store.dispatch };\n}\n"]}
1
+ {"version":3,"sources":["../../src/react/provider.tsx","../../src/react/hooks.ts"],"names":["useRef"],"mappings":"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","file":"index.js","sourcesContent":["import { createContext, type ReactNode, useContext, useRef } from \"react\";\nimport { createSynapse } from \"../core.js\";\nimport type { Synapse, SynapseOptions } from \"../types.js\";\n\nconst SynapseContext = createContext<Synapse | null>(null);\n\nexport interface SynapseProviderProps extends SynapseOptions {\n children: ReactNode;\n}\n\nexport function SynapseProvider({ children, ...options }: SynapseProviderProps) {\n // Use a ref so the same Synapse instance survives StrictMode's\n // unmount/remount cycle. We intentionally do NOT destroy on unmount\n // because StrictMode re-mounts immediately and the transport must\n // stay alive. The instance is GC'd when the provider is truly removed.\n const ref = useRef<Synapse | null>(null);\n\n if (ref.current === null || ref.current.destroyed) {\n ref.current = createSynapse(options);\n }\n\n return <SynapseContext.Provider value={ref.current}>{children}</SynapseContext.Provider>;\n}\n\nexport function useSynapseContext(): Synapse {\n const ctx = useContext(SynapseContext);\n if (!ctx) {\n throw new Error(\n \"useSynapse must be used within a <SynapseProvider>. \" +\n \"Wrap your app component tree with <SynapseProvider>.\",\n );\n }\n return ctx;\n}\n","import { useCallback, useEffect, useRef, useState, useSyncExternalStore } from \"react\";\nimport type {\n ActionReducer,\n AgentAction,\n DataChangedEvent,\n Store,\n StoreDispatch,\n Synapse,\n SynapseTheme,\n ToolCallResult,\n} from \"../types.js\";\nimport { SynapseProvider, useSynapseContext } from \"./provider.js\";\n\n// Re-export provider components\nexport { SynapseProvider };\n\nexport function useSynapse(): Synapse {\n return useSynapseContext();\n}\n\nexport function useCallTool<TOutput = unknown>(\n toolName: string,\n): {\n call: (args?: Record<string, unknown>) => Promise<ToolCallResult<TOutput>>;\n isPending: boolean;\n error: Error | null;\n data: TOutput | null;\n} {\n const synapse = useSynapseContext();\n const [isPending, setIsPending] = useState(false);\n const [error, setError] = useState<Error | null>(null);\n const [data, setData] = useState<TOutput | null>(null);\n const callIdRef = useRef(0);\n\n const call = useCallback(\n async (args?: Record<string, unknown>): Promise<ToolCallResult<TOutput>> => {\n const id = ++callIdRef.current;\n setIsPending(true);\n setError(null);\n\n try {\n const result = await synapse.callTool<Record<string, unknown>, TOutput>(toolName, args);\n // Stale guard: only update if this is still the latest call\n if (id === callIdRef.current) {\n setData(result.data);\n setIsPending(false);\n }\n return result;\n } catch (err) {\n if (id === callIdRef.current) {\n const e = err instanceof Error ? err : new Error(String(err));\n setError(e);\n setIsPending(false);\n }\n throw err;\n }\n },\n [synapse, toolName],\n );\n\n return { call, isPending, error, data };\n}\n\nexport function useDataSync(callback: (event: DataChangedEvent) => void): void {\n const synapse = useSynapseContext();\n const callbackRef = useRef(callback);\n callbackRef.current = callback;\n\n useEffect(() => {\n return synapse.onDataChanged((event) => callbackRef.current(event));\n }, [synapse]);\n}\n\n/**\n * Subscribe to agent actions — typed, declarative commands from the server.\n *\n * Actions are emitted by tools as deterministic side effects (e.g., \"navigate\n * to the board I just created\"). The UI decides how to handle each action type.\n *\n * @example\n * ```tsx\n * useAgentAction((action) => {\n * if (action.type === \"navigate\") {\n * const { entity, id } = action.payload as NavigatePayload;\n * if (entity === \"board\") setSelectedBoardId(id);\n * }\n * });\n * ```\n */\nexport function useAgentAction(callback: (action: AgentAction) => void): void {\n const synapse = useSynapseContext();\n const callbackRef = useRef(callback);\n callbackRef.current = callback;\n\n useEffect(() => {\n return synapse.onAction((action) => callbackRef.current(action));\n }, [synapse]);\n}\n\nexport function useTheme(): SynapseTheme {\n const synapse = useSynapseContext();\n const [theme, setTheme] = useState<SynapseTheme>(() => synapse.getTheme());\n\n useEffect(() => {\n // Sync in case theme changed between render and effect\n setTheme(synapse.getTheme());\n return synapse.onThemeChanged(setTheme);\n }, [synapse]);\n\n return theme;\n}\n\nexport function useAction(): (action: string, params?: Record<string, unknown>) => void {\n const synapse = useSynapseContext();\n return useCallback(\n (action: string, params?: Record<string, unknown>) => synapse.action(action, params),\n [synapse],\n );\n}\n\nexport function useChat(): (\n message: string,\n context?: { action?: string; entity?: string },\n) => void {\n const synapse = useSynapseContext();\n return useCallback(\n (message: string, context?: { action?: string; entity?: string }) =>\n synapse.chat(message, context),\n [synapse],\n );\n}\n\nexport function useVisibleState(): (state: Record<string, unknown>, summary?: string) => void {\n const synapse = useSynapseContext();\n return useCallback(\n (state: Record<string, unknown>, summary?: string) => synapse.setVisibleState(state, summary),\n [synapse],\n );\n}\n\nexport function useStore<TState, TActions extends Record<string, ActionReducer<TState, any>>>(\n store: Store<TState, TActions>,\n): {\n state: TState;\n dispatch: StoreDispatch<TActions>;\n} {\n const state = useSyncExternalStore(\n (onStoreChange) => store.subscribe(onStoreChange),\n () => store.getState(),\n () => store.getState(),\n );\n\n return { state, dispatch: store.dispatch };\n}\n"]}
@@ -37,19 +37,19 @@ var HOST_HTML = (uiPort, serverPort) => `<!DOCTYPE html>
37
37
 
38
38
  // Minimal NimbleBrain bridge host \u2014 just enough to make Synapse work
39
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",
40
+ "--color-background-primary": "#0f172a", "--color-text-primary": "#e2e8f0",
41
+ "--color-background-secondary": "#1e293b", "--color-text-primary": "#e2e8f0",
42
+ "--color-text-accent": "#6366f1", "--nb-color-accent-foreground": "#ffffff",
43
+ "--color-text-secondary": "#94a3b8", "--color-border-primary": "#334155",
44
+ "--color-ring-primary": "#6366f1", "--nb-color-danger": "#ef4444",
45
+ "--border-radius-sm": "0.5rem",
46
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",
47
+ "--color-background-primary": "#ffffff", "--color-text-primary": "#1a1a1a",
48
+ "--color-background-secondary": "#f9fafb", "--color-text-primary": "#1a1a1a",
49
+ "--color-text-accent": "#6366f1", "--nb-color-accent-foreground": "#ffffff",
50
+ "--color-text-secondary": "#6b7280", "--color-border-primary": "#e5e7eb",
51
+ "--color-ring-primary": "#6366f1", "--nb-color-danger": "#ef4444",
52
+ "--border-radius-sm": "0.5rem",
53
53
  };
54
54
 
55
55
  function post(msg) { iframe.contentWindow.postMessage(msg, "*"); }
@@ -126,19 +126,19 @@ var HOST_HTML = (uiPort, serverPort) => `<!DOCTYPE html>
126
126
  darkMode = !darkMode;
127
127
  document.body.style.background = darkMode ? "#0f172a" : "#f1f5f9";
128
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",
129
+ "--color-background-primary": "#0f172a", "--color-text-primary": "#e2e8f0",
130
+ "--color-background-secondary": "#1e293b", "--color-text-primary": "#e2e8f0",
131
+ "--color-text-accent": "#6366f1", "--nb-color-accent-foreground": "#ffffff",
132
+ "--color-text-secondary": "#94a3b8", "--color-border-primary": "#334155",
133
+ "--color-ring-primary": "#6366f1", "--nb-color-danger": "#ef4444",
134
+ "--border-radius-sm": "0.5rem",
135
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",
136
+ "--color-background-primary": "#ffffff", "--color-text-primary": "#1a1a1a",
137
+ "--color-background-secondary": "#f9fafb", "--color-text-primary": "#1a1a1a",
138
+ "--color-text-accent": "#6366f1", "--nb-color-accent-foreground": "#ffffff",
139
+ "--color-text-secondary": "#6b7280", "--color-border-primary": "#e5e7eb",
140
+ "--color-ring-primary": "#6366f1", "--nb-color-danger": "#ef4444",
141
+ "--border-radius-sm": "0.5rem",
142
142
  };
143
143
  post({ jsonrpc: "2.0", method: "ui/themeChanged", params: { mode: darkMode ? "dark" : "light", tokens: tokens } });
144
144
  });
@@ -202,5 +202,5 @@ async function startPreview(options) {
202
202
  }
203
203
 
204
204
  exports.startPreview = startPreview;
205
- //# sourceMappingURL=server-SEI7XI3B.cjs.map
206
- //# sourceMappingURL=server-SEI7XI3B.cjs.map
205
+ //# sourceMappingURL=server-3BDZ5S72.cjs.map
206
+ //# sourceMappingURL=server-3BDZ5S72.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-3BDZ5S72.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 \"--color-background-primary\": \"#0f172a\", \"--color-text-primary\": \"#e2e8f0\",\n \"--color-background-secondary\": \"#1e293b\", \"--color-text-primary\": \"#e2e8f0\",\n \"--color-text-accent\": \"#6366f1\", \"--nb-color-accent-foreground\": \"#ffffff\",\n \"--color-text-secondary\": \"#94a3b8\", \"--color-border-primary\": \"#334155\",\n \"--color-ring-primary\": \"#6366f1\", \"--nb-color-danger\": \"#ef4444\",\n \"--border-radius-sm\": \"0.5rem\",\n } : {\n \"--color-background-primary\": \"#ffffff\", \"--color-text-primary\": \"#1a1a1a\",\n \"--color-background-secondary\": \"#f9fafb\", \"--color-text-primary\": \"#1a1a1a\",\n \"--color-text-accent\": \"#6366f1\", \"--nb-color-accent-foreground\": \"#ffffff\",\n \"--color-text-secondary\": \"#6b7280\", \"--color-border-primary\": \"#e5e7eb\",\n \"--color-ring-primary\": \"#6366f1\", \"--nb-color-danger\": \"#ef4444\",\n \"--border-radius-sm\": \"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 \"--color-background-primary\": \"#0f172a\", \"--color-text-primary\": \"#e2e8f0\",\n \"--color-background-secondary\": \"#1e293b\", \"--color-text-primary\": \"#e2e8f0\",\n \"--color-text-accent\": \"#6366f1\", \"--nb-color-accent-foreground\": \"#ffffff\",\n \"--color-text-secondary\": \"#94a3b8\", \"--color-border-primary\": \"#334155\",\n \"--color-ring-primary\": \"#6366f1\", \"--nb-color-danger\": \"#ef4444\",\n \"--border-radius-sm\": \"0.5rem\",\n } : {\n \"--color-background-primary\": \"#ffffff\", \"--color-text-primary\": \"#1a1a1a\",\n \"--color-background-secondary\": \"#f9fafb\", \"--color-text-primary\": \"#1a1a1a\",\n \"--color-text-accent\": \"#6366f1\", \"--nb-color-accent-foreground\": \"#ffffff\",\n \"--color-text-secondary\": \"#6b7280\", \"--color-border-primary\": \"#e5e7eb\",\n \"--color-ring-primary\": \"#6366f1\", \"--nb-color-danger\": \"#ef4444\",\n \"--border-radius-sm\": \"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"]}
@@ -35,19 +35,19 @@ var HOST_HTML = (uiPort, serverPort) => `<!DOCTYPE html>
35
35
 
36
36
  // Minimal NimbleBrain bridge host \u2014 just enough to make Synapse work
37
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",
38
+ "--color-background-primary": "#0f172a", "--color-text-primary": "#e2e8f0",
39
+ "--color-background-secondary": "#1e293b", "--color-text-primary": "#e2e8f0",
40
+ "--color-text-accent": "#6366f1", "--nb-color-accent-foreground": "#ffffff",
41
+ "--color-text-secondary": "#94a3b8", "--color-border-primary": "#334155",
42
+ "--color-ring-primary": "#6366f1", "--nb-color-danger": "#ef4444",
43
+ "--border-radius-sm": "0.5rem",
44
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",
45
+ "--color-background-primary": "#ffffff", "--color-text-primary": "#1a1a1a",
46
+ "--color-background-secondary": "#f9fafb", "--color-text-primary": "#1a1a1a",
47
+ "--color-text-accent": "#6366f1", "--nb-color-accent-foreground": "#ffffff",
48
+ "--color-text-secondary": "#6b7280", "--color-border-primary": "#e5e7eb",
49
+ "--color-ring-primary": "#6366f1", "--nb-color-danger": "#ef4444",
50
+ "--border-radius-sm": "0.5rem",
51
51
  };
52
52
 
53
53
  function post(msg) { iframe.contentWindow.postMessage(msg, "*"); }
@@ -124,19 +124,19 @@ var HOST_HTML = (uiPort, serverPort) => `<!DOCTYPE html>
124
124
  darkMode = !darkMode;
125
125
  document.body.style.background = darkMode ? "#0f172a" : "#f1f5f9";
126
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",
127
+ "--color-background-primary": "#0f172a", "--color-text-primary": "#e2e8f0",
128
+ "--color-background-secondary": "#1e293b", "--color-text-primary": "#e2e8f0",
129
+ "--color-text-accent": "#6366f1", "--nb-color-accent-foreground": "#ffffff",
130
+ "--color-text-secondary": "#94a3b8", "--color-border-primary": "#334155",
131
+ "--color-ring-primary": "#6366f1", "--nb-color-danger": "#ef4444",
132
+ "--border-radius-sm": "0.5rem",
133
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",
134
+ "--color-background-primary": "#ffffff", "--color-text-primary": "#1a1a1a",
135
+ "--color-background-secondary": "#f9fafb", "--color-text-primary": "#1a1a1a",
136
+ "--color-text-accent": "#6366f1", "--nb-color-accent-foreground": "#ffffff",
137
+ "--color-text-secondary": "#6b7280", "--color-border-primary": "#e5e7eb",
138
+ "--color-ring-primary": "#6366f1", "--nb-color-danger": "#ef4444",
139
+ "--border-radius-sm": "0.5rem",
140
140
  };
141
141
  post({ jsonrpc: "2.0", method: "ui/themeChanged", params: { mode: darkMode ? "dark" : "light", tokens: tokens } });
142
142
  });
@@ -200,5 +200,5 @@ async function startPreview(options) {
200
200
  }
201
201
 
202
202
  export { startPreview };
203
- //# sourceMappingURL=server-RUCX2TIB.js.map
204
- //# sourceMappingURL=server-RUCX2TIB.js.map
203
+ //# sourceMappingURL=server-NNW54YW5.js.map
204
+ //# sourceMappingURL=server-NNW54YW5.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/preview/server.ts"],"names":[],"mappings":"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","file":"server-NNW54YW5.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 \"--color-background-primary\": \"#0f172a\", \"--color-text-primary\": \"#e2e8f0\",\n \"--color-background-secondary\": \"#1e293b\", \"--color-text-primary\": \"#e2e8f0\",\n \"--color-text-accent\": \"#6366f1\", \"--nb-color-accent-foreground\": \"#ffffff\",\n \"--color-text-secondary\": \"#94a3b8\", \"--color-border-primary\": \"#334155\",\n \"--color-ring-primary\": \"#6366f1\", \"--nb-color-danger\": \"#ef4444\",\n \"--border-radius-sm\": \"0.5rem\",\n } : {\n \"--color-background-primary\": \"#ffffff\", \"--color-text-primary\": \"#1a1a1a\",\n \"--color-background-secondary\": \"#f9fafb\", \"--color-text-primary\": \"#1a1a1a\",\n \"--color-text-accent\": \"#6366f1\", \"--nb-color-accent-foreground\": \"#ffffff\",\n \"--color-text-secondary\": \"#6b7280\", \"--color-border-primary\": \"#e5e7eb\",\n \"--color-ring-primary\": \"#6366f1\", \"--nb-color-danger\": \"#ef4444\",\n \"--border-radius-sm\": \"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 \"--color-background-primary\": \"#0f172a\", \"--color-text-primary\": \"#e2e8f0\",\n \"--color-background-secondary\": \"#1e293b\", \"--color-text-primary\": \"#e2e8f0\",\n \"--color-text-accent\": \"#6366f1\", \"--nb-color-accent-foreground\": \"#ffffff\",\n \"--color-text-secondary\": \"#94a3b8\", \"--color-border-primary\": \"#334155\",\n \"--color-ring-primary\": \"#6366f1\", \"--nb-color-danger\": \"#ef4444\",\n \"--border-radius-sm\": \"0.5rem\",\n } : {\n \"--color-background-primary\": \"#ffffff\", \"--color-text-primary\": \"#1a1a1a\",\n \"--color-background-secondary\": \"#f9fafb\", \"--color-text-primary\": \"#1a1a1a\",\n \"--color-text-accent\": \"#6366f1\", \"--nb-color-accent-foreground\": \"#ffffff\",\n \"--color-text-secondary\": \"#6b7280\", \"--color-border-primary\": \"#e5e7eb\",\n \"--color-ring-primary\": \"#6366f1\", \"--nb-color-danger\": \"#ef4444\",\n \"--border-radius-sm\": \"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"]}
@@ -1 +1 @@
1
- (function(){'use strict';var b={mode:"light",primaryColor:"#6366f1",tokens:{}};function E(r){let e=r,t=C(e?.serverInfo),n=typeof t?.name=="string"?t.name:"unknown",i=typeof e?.protocolVersion=="string"?e.protocolVersion:"unknown",s=C(e?.hostContext),l=K(s?.theme);return {isNimbleBrain:n==="nimblebrain",serverName:n,protocolVersion:i,theme:l}}function K(r){let e=C(r);if(!e)return {...b};let t=e.mode==="light"||e.mode==="dark"?e.mode:b.mode,n=typeof e.primaryColor=="string"?e.primaryColor:b.primaryColor,i=e.tokens!==null&&typeof e.tokens=="object"&&!Array.isArray(e.tokens)?e.tokens:{};return {mode:t,primaryColor:n,tokens:i}}function C(r){if(r!==null&&typeof r=="object"&&!Array.isArray(r))return r}var k=class{listener;destroyed=false;constructor(e,t){let n=t??null;this.listener=i=>{this.destroyed||this.shouldForward(i,n)&&(i.preventDefault(),e.send("ui/keydown",{key:i.key,ctrlKey:i.ctrlKey,metaKey:i.metaKey,shiftKey:i.shiftKey,altKey:i.altKey}));},document.addEventListener("keydown",this.listener);}destroy(){this.destroyed||(this.destroyed=true,document.removeEventListener("keydown",this.listener));}shouldForward(e,t){return t&&t.length===0?false:t?t.some(n=>e.key.toLowerCase()===n.key.toLowerCase()&&(n.ctrl===void 0||e.ctrlKey===n.ctrl)&&(n.meta===void 0||e.metaKey===n.meta)&&(n.shift===void 0||e.shiftKey===n.shift)&&(n.alt===void 0||e.altKey===n.alt)):!!(e.key==="Escape"||e.ctrlKey||e.metaKey)}};function M(r){return r==null?{data:null,isError:false}:A(r)?I(r):{data:r,isError:false}}function A(r){return r===null||typeof r!="object"||Array.isArray(r)?false:Array.isArray(r.content)}function H(r){if(r===null||typeof r!="object"||Array.isArray(r))return false;let e=r;return e.type==="text"&&typeof e.text=="string"}function I(r){let e=r.isError===true,t=r.content;if(t.length===0)return {data:null,isError:e};let n=t.find(H);if(!n)return {data:t,isError:e};try{return {data:JSON.parse(n.text),isError:e}}catch{return {data:n.text,isError:e}}}var R=class{counter=0;destroyed=false;pending=new Map;handlers=new Map;listener;constructor(){this.listener=e=>this.handleMessage(e),window.addEventListener("message",this.listener);}send(e,t){if(this.destroyed)return;let n={jsonrpc:"2.0",method:e,...t!==void 0&&{params:t}};window.parent.postMessage(n,"*");}request(e,t){if(this.destroyed)return Promise.reject(new Error("Transport destroyed"));let n=`syn-${++this.counter}`,i={jsonrpc:"2.0",method:e,id:n,...t!==void 0&&{params:t}};return new Promise((s,l)=>{this.pending.set(n,{resolve:s,reject:l}),window.parent.postMessage(i,"*");})}onMessage(e,t){return this.handlers.has(e)||this.handlers.set(e,new Set),this.handlers.get(e)?.add(t),()=>{let n=this.handlers.get(e);n&&(n.delete(t),n.size===0&&this.handlers.delete(e));}}destroy(){if(this.destroyed)return;this.destroyed=true,window.removeEventListener("message",this.listener);let e=new Error("Transport destroyed");for(let t of this.pending.values())t.reject(e);this.pending.clear(),this.handlers.clear();}handleMessage(e){if(this.destroyed)return;let t=e.data;if(!(!t||t.jsonrpc!=="2.0")){if("id"in t&&t.id&&!("method"in t)){let n=t,i=this.pending.get(n.id);if(!i)return;if(this.pending.delete(n.id),n.error){let s=new Error(n.error.message);s.code=n.error.code,s.data=n.error.data,i.reject(s);}else i.resolve(n.result);return}if("method"in t&&!("id"in t&&t.id)){let n=t,i=this.handlers.get(n.method);if(i)for(let s of i)s(n.params);}}}};function j(r){let{name:e,version:t,internal:n=false,forwardKeys:i}=r,s=new R,l=null,u={mode:"light",primaryColor:"#6366f1",tokens:{}},w=false,y=null,T=null,v=s.request("ui/initialize",{protocolVersion:"2026-01-26",clientInfo:{name:e,version:t},capabilities:{}}).then(o=>{l=E(o),u=l.theme,s.send("ui/notifications/initialized",{}),T=new k(s,i);}),d=s.onMessage("ui/notifications/host-context-changed",o=>{if(!o)return;let a=o.theme==="dark"?"dark":"light",p=o.tokens&&typeof o.tokens=="object"?o.tokens:u.tokens;u={mode:a,primaryColor:u.primaryColor,tokens:p};for(let g of m)g(u);}),f=s.onMessage("ui/themeChanged",o=>{if(!o)return;let a=o.mode==="dark"||o.mode==="light"?o.mode:u.mode,p=o.tokens&&typeof o.tokens=="object"?o.tokens:u.tokens;u={mode:a,primaryColor:u.primaryColor,tokens:p};for(let g of m)g(u);}),m=new Set,h=new Set,S=s.onMessage("ui/datachanged",o=>{if(!o)return;let a={source:"agent",server:o.server??"",tool:o.tool??""};for(let p of h)p(a);}),c=()=>l?.isNimbleBrain===true;return {get ready(){return v},get isNimbleBrainHost(){return c()},async callTool(o,a){let p={name:o,arguments:a??{}};n&&(p.server=e);let g=await s.request("tools/call",p);return M(g)},onDataChanged(o){return h.add(o),()=>{h.delete(o);}},getTheme(){return {...u}},onThemeChanged(o){return m.add(o),()=>{m.delete(o);}},action(o,a){c()&&s.send("ui/action",{action:o,...a});},chat(o,a){c()&&s.send("ui/chat",{message:o,context:a});},setVisibleState(o,a){c()&&(y&&clearTimeout(y),y=setTimeout(()=>{s.send("ui/stateChanged",{state:o,...a!==void 0&&{summary:a}}),y=null;},250));},downloadFile(o,a,p){if(!c())return;let g=typeof a=="string"?a:"[Blob content not serializable]";s.send("ui/downloadFile",{data:g,filename:o,mimeType:p??"application/octet-stream"});},openLink(o){c()?s.send("ui/openLink",{url:o}):window.open(o,"_blank","noopener");},_onMessage(o,a){return s.onMessage(o,a)},_request(o,a){return s.request(o,a)},destroy(){w||(w=true,y&&clearTimeout(y),T?.destroy(),d(),f(),S(),m.clear(),h.clear(),s.destroy());}}}function x(r,e){let t=structuredClone(e.initialState),n=new Set,i=false,s=null,l={};for(let d of Object.keys(e.actions))l[d]=f=>{i||(t=e.actions[d](t,f),u());};function u(){for(let d of n)d(t);e.visibleToAgent&&w(),e.persist&&y();}function w(){let d=e.summarize?.(t);r.setVisibleState(t,d);}function y(){s&&clearTimeout(s),s=setTimeout(()=>{r._request("ui/persistState",{state:t,version:e.version}).catch(()=>{}),s=null;},500);}let T;e.persist&&(T=r._onMessage("ui/stateLoaded",d=>{if(!d?.state)return;let f=d.state,m=d.version??1,h=e.version??1;if(e.migrations&&m<h){let S=m-1;for(let c=S;c<e.migrations.length;c++)f=e.migrations[c](f);}v.hydrate(f);}));let v={getState(){return t},subscribe(d){return n.add(d),()=>{n.delete(d);}},dispatch:l,hydrate(d){t=d;for(let f of n)f(t);},destroy(){i||(i=true,s&&clearTimeout(s),n.clear(),T?.());}};return v}window.NbSynapse={createSynapse:j,createStore:x};})();
1
+ (function(){'use strict';var C={mode:"light",primaryColor:"#6366f1",tokens:{}};function E(r){let e=r,n=A(e?.serverInfo),o=typeof n?.name=="string"?n.name:"unknown",i=typeof e?.protocolVersion=="string"?e.protocolVersion:"unknown",s=A(e?.hostContext),c=H(s?.theme);return {isNimbleBrain:o==="nimblebrain",serverName:o,protocolVersion:i,theme:c}}function H(r){let e=A(r);if(!e)return {...C};let n=e.mode==="light"||e.mode==="dark"?e.mode:C.mode,o=typeof e.primaryColor=="string"?e.primaryColor:C.primaryColor,i=e.tokens!==null&&typeof e.tokens=="object"&&!Array.isArray(e.tokens)?e.tokens:{};return {mode:n,primaryColor:o,tokens:i}}function A(r){if(r!==null&&typeof r=="object"&&!Array.isArray(r))return r}var S=class{listener;destroyed=false;constructor(e,n){let o=n??null;this.listener=i=>{this.destroyed||this.shouldForward(i,o)&&(i.preventDefault(),e.send("ui/keydown",{key:i.key,ctrlKey:i.ctrlKey,metaKey:i.metaKey,shiftKey:i.shiftKey,altKey:i.altKey}));},document.addEventListener("keydown",this.listener);}destroy(){this.destroyed||(this.destroyed=true,document.removeEventListener("keydown",this.listener));}shouldForward(e,n){return n&&n.length===0?false:n?n.some(o=>e.key.toLowerCase()===o.key.toLowerCase()&&(o.ctrl===void 0||e.ctrlKey===o.ctrl)&&(o.meta===void 0||e.metaKey===o.meta)&&(o.shift===void 0||e.shiftKey===o.shift)&&(o.alt===void 0||e.altKey===o.alt)):!!(e.key==="Escape"||e.ctrlKey||e.metaKey)}};function M(r){return r==null?{data:null,isError:false}:I(r)?N(r):{data:r,isError:false}}function I(r){return r===null||typeof r!="object"||Array.isArray(r)?false:Array.isArray(r.content)}function L(r){if(r===null||typeof r!="object"||Array.isArray(r))return false;let e=r;return e.type==="text"&&typeof e.text=="string"}function N(r){let e=r.isError===true,n=r.content;if(n.length===0)return {data:null,isError:e};let o=n.find(L);if(!o)return {data:n,isError:e};try{return {data:JSON.parse(o.text),isError:e}}catch{return {data:o.text,isError:e}}}var b=class{counter=0;destroyed=false;pending=new Map;handlers=new Map;listener;constructor(){this.listener=e=>this.handleMessage(e),window.addEventListener("message",this.listener);}send(e,n){if(this.destroyed)return;let o={jsonrpc:"2.0",method:e,...n!==void 0&&{params:n}};window.parent.postMessage(o,"*");}request(e,n){if(this.destroyed)return Promise.reject(new Error("Transport destroyed"));let o=`syn-${++this.counter}`,i={jsonrpc:"2.0",method:e,id:o,...n!==void 0&&{params:n}};return new Promise((s,c)=>{this.pending.set(o,{resolve:s,reject:c}),window.parent.postMessage(i,"*");})}onMessage(e,n){return this.handlers.has(e)||this.handlers.set(e,new Set),this.handlers.get(e)?.add(n),()=>{let o=this.handlers.get(e);o&&(o.delete(n),o.size===0&&this.handlers.delete(e));}}destroy(){if(this.destroyed)return;this.destroyed=true,window.removeEventListener("message",this.listener);let e=new Error("Transport destroyed");for(let n of this.pending.values())n.reject(e);this.pending.clear(),this.handlers.clear();}handleMessage(e){if(this.destroyed)return;let n=e.data;if(!(!n||n.jsonrpc!=="2.0")){if("id"in n&&n.id&&!("method"in n)){let o=n,i=this.pending.get(o.id);if(!i)return;if(this.pending.delete(o.id),o.error){let s=new Error(o.error.message);s.code=o.error.code,s.data=o.error.data,i.reject(s);}else i.resolve(o.result);return}if("method"in n&&!("id"in n&&n.id)){let o=n,i=this.handlers.get(o.method);if(i)for(let s of i)s(o.params);}}}};function j(r){let{name:e,version:n,internal:o=false,forwardKeys:i}=r,s=new b,c=null,u={mode:"light",primaryColor:"#6366f1",tokens:{}},w=false,y=null,v=null,R=s.request("ui/initialize",{protocolVersion:"2026-01-26",clientInfo:{name:e,version:n},capabilities:{}}).then(t=>{c=E(t),u=c.theme,s.send("ui/notifications/initialized",{}),v=new S(s,i);}),d=s.onMessage("ui/notifications/host-context-changed",t=>{if(!t)return;let a=t.theme==="dark"?"dark":"light",l=t.tokens&&typeof t.tokens=="object"?t.tokens:u.tokens;u={mode:a,primaryColor:u.primaryColor,tokens:l};for(let g of p)g(u);}),f=s.onMessage("ui/themeChanged",t=>{if(!t)return;let a=t.mode==="dark"||t.mode==="light"?t.mode:u.mode,l=t.tokens&&typeof t.tokens=="object"?t.tokens:u.tokens;u={mode:a,primaryColor:u.primaryColor,tokens:l};for(let g of p)g(u);}),p=new Set,m=new Set,h=new Set,k=s.onMessage("ui/datachanged",t=>{if(!t)return;let a={source:"agent",server:t.server??"",tool:t.tool??""};for(let l of m)l(a);}),K=s.onMessage("ui/action",t=>{if(!t||typeof t.type!="string")return;let a={type:t.type,payload:t.payload??{},requiresConfirmation:t.requiresConfirmation===true,label:typeof t.label=="string"?t.label:void 0};for(let l of h)l(a);}),T=()=>c?.isNimbleBrain===true;return {get ready(){return R},get isNimbleBrainHost(){return T()},get destroyed(){return w},async callTool(t,a){let l={name:t,arguments:a??{}};o&&(l.server=e);let g=await s.request("tools/call",l);return M(g)},onDataChanged(t){return m.add(t),()=>{m.delete(t);}},onAction(t){return h.add(t),()=>{h.delete(t);}},getTheme(){return {...u}},onThemeChanged(t){return p.add(t),()=>{p.delete(t);}},action(t,a){T()&&s.send("ui/action",{action:t,...a});},chat(t,a){T()&&s.send("ui/chat",{message:t,context:a});},setVisibleState(t,a){T()&&(y&&clearTimeout(y),y=setTimeout(()=>{s.send("ui/stateChanged",{state:t,...a!==void 0&&{summary:a}}),y=null;},250));},downloadFile(t,a,l){if(!T())return;let g=typeof a=="string"?a:"[Blob content not serializable]";s.send("ui/downloadFile",{data:g,filename:t,mimeType:l??"application/octet-stream"});},openLink(t){T()?s.send("ui/openLink",{url:t}):window.open(t,"_blank","noopener");},_onMessage(t,a){return s.onMessage(t,a)},_request(t,a){return s.request(t,a)},destroy(){w||(w=true,y&&clearTimeout(y),v?.destroy(),d(),f(),k(),K(),p.clear(),m.clear(),h.clear(),s.destroy());}}}function x(r,e){let n=structuredClone(e.initialState),o=new Set,i=false,s=null,c={};for(let d of Object.keys(e.actions))c[d]=f=>{i||(n=e.actions[d](n,f),u());};function u(){for(let d of o)d(n);e.visibleToAgent&&w(),e.persist&&y();}function w(){let d=e.summarize?.(n);r.setVisibleState(n,d);}function y(){s&&clearTimeout(s),s=setTimeout(()=>{r._request("ui/persistState",{state:n,version:e.version}).catch(()=>{}),s=null;},500);}let v;e.persist&&(v=r._onMessage("ui/stateLoaded",d=>{if(!d?.state)return;let f=d.state,p=d.version??1,m=e.version??1;if(e.migrations&&p<m){let h=p-1;for(let k=h;k<e.migrations.length;k++)f=e.migrations[k](f);}R.hydrate(f);}));let R={getState(){return n},subscribe(d){return o.add(d),()=>{o.delete(d);}},dispatch:c,hydrate(d){n=d;for(let f of o)f(n);},destroy(){i||(i=true,s&&clearTimeout(s),o.clear(),v?.());}};return R}window.NbSynapse={createSynapse:j,createStore:x};})();
@@ -21,6 +21,49 @@ interface DataChangedEvent {
21
21
  server: string;
22
22
  tool: string;
23
23
  }
24
+ /**
25
+ * Built-in action types that Synapse handles natively.
26
+ *
27
+ * - `navigate` — select/focus a resource in the UI (e.g., a board, document, record)
28
+ * - `notify` — display a transient message (toast/banner)
29
+ * - `refresh` — force a full data refresh (heavier than datachanged)
30
+ * - `confirm` — request user confirmation before the agent proceeds
31
+ *
32
+ * Apps may also receive custom string types for domain-specific actions.
33
+ */
34
+ type BuiltinActionType = "navigate" | "notify" | "refresh" | "confirm";
35
+ /**
36
+ * A typed, declarative action sent from the agent/server to the UI.
37
+ *
38
+ * Actions are deterministic side effects of tool execution — the tool decides
39
+ * what action to emit, not the LLM. The UI decides how to handle it.
40
+ *
41
+ * This mirrors Studio's ClientAction pattern, adapted for iframe postMessage.
42
+ */
43
+ interface AgentAction<TPayload = Record<string, unknown>> {
44
+ /** Discriminator — a BuiltinActionType or custom string. */
45
+ type: BuiltinActionType | (string & {});
46
+ /** Typed payload — shape depends on `type`. */
47
+ payload: TPayload;
48
+ /** If true, the UI should confirm with the user before executing. */
49
+ requiresConfirmation?: boolean;
50
+ /** Human-readable label for confirmation dialogs or logs. */
51
+ label?: string;
52
+ }
53
+ /** Payload for the built-in "navigate" action. */
54
+ interface NavigatePayload {
55
+ /** Entity type (e.g., "board", "document", "task"). */
56
+ entity: string;
57
+ /** Entity ID to select/focus. */
58
+ id: string;
59
+ /** Optional sub-view or section within the entity. */
60
+ view?: string;
61
+ }
62
+ /** Payload for the built-in "notify" action. */
63
+ interface NotifyPayload {
64
+ message: string;
65
+ level?: "info" | "success" | "warning" | "error";
66
+ }
24
67
  interface ToolCallResult<T = unknown> {
25
68
  data: T;
26
69
  isError: boolean;
@@ -30,6 +73,16 @@ interface Synapse {
30
73
  readonly isNimbleBrainHost: boolean;
31
74
  callTool<TInput = Record<string, unknown>, TOutput = unknown>(name: string, args?: TInput): Promise<ToolCallResult<TOutput>>;
32
75
  onDataChanged(callback: (event: DataChangedEvent) => void): () => void;
76
+ /**
77
+ * Subscribe to agent actions — typed, declarative commands from the server.
78
+ *
79
+ * Actions are deterministic side effects of tool execution. The server/tool
80
+ * decides what action to emit; the UI decides how to handle it.
81
+ *
82
+ * The callback receives an AgentAction with a `type` discriminator and typed
83
+ * `payload`. Apps should handle known types and ignore unknown ones.
84
+ */
85
+ onAction(callback: (action: AgentAction) => void): () => void;
33
86
  getTheme(): SynapseTheme;
34
87
  onThemeChanged(callback: (theme: SynapseTheme) => void): () => void;
35
88
  action(action: string, params?: Record<string, unknown>): void;
@@ -44,6 +97,8 @@ interface Synapse {
44
97
  _onMessage(method: string, callback: (params: Record<string, unknown> | undefined) => void): () => void;
45
98
  /** @internal — used by createStore for ui/persistState */
46
99
  _request(method: string, params?: Record<string, unknown>): Promise<unknown>;
100
+ /** True after destroy() has been called. */
101
+ readonly destroyed: boolean;
47
102
  destroy(): void;
48
103
  }
49
104
  interface VisibleState {
@@ -93,4 +148,4 @@ interface HostInfo {
93
148
  theme: SynapseTheme;
94
149
  }
95
150
 
96
- export type { ActionReducer as A, DataChangedEvent as D, HostInfo as H, KeyForwardConfig as K, SynapseOptions as S, ToolCallResult as T, VisibleState as V, Synapse as a, StoreConfig as b, Store as c, StateAcknowledgement as d, StoreDispatch as e, SynapseTheme as f, ToolDefinition as g };
151
+ export type { ActionReducer as A, BuiltinActionType as B, DataChangedEvent as D, HostInfo as H, KeyForwardConfig as K, NavigatePayload as N, SynapseOptions as S, ToolCallResult as T, VisibleState as V, Synapse as a, StoreConfig as b, Store as c, AgentAction as d, NotifyPayload as e, StateAcknowledgement as f, StoreDispatch as g, SynapseTheme as h, ToolDefinition as i };
@@ -21,6 +21,49 @@ interface DataChangedEvent {
21
21
  server: string;
22
22
  tool: string;
23
23
  }
24
+ /**
25
+ * Built-in action types that Synapse handles natively.
26
+ *
27
+ * - `navigate` — select/focus a resource in the UI (e.g., a board, document, record)
28
+ * - `notify` — display a transient message (toast/banner)
29
+ * - `refresh` — force a full data refresh (heavier than datachanged)
30
+ * - `confirm` — request user confirmation before the agent proceeds
31
+ *
32
+ * Apps may also receive custom string types for domain-specific actions.
33
+ */
34
+ type BuiltinActionType = "navigate" | "notify" | "refresh" | "confirm";
35
+ /**
36
+ * A typed, declarative action sent from the agent/server to the UI.
37
+ *
38
+ * Actions are deterministic side effects of tool execution — the tool decides
39
+ * what action to emit, not the LLM. The UI decides how to handle it.
40
+ *
41
+ * This mirrors Studio's ClientAction pattern, adapted for iframe postMessage.
42
+ */
43
+ interface AgentAction<TPayload = Record<string, unknown>> {
44
+ /** Discriminator — a BuiltinActionType or custom string. */
45
+ type: BuiltinActionType | (string & {});
46
+ /** Typed payload — shape depends on `type`. */
47
+ payload: TPayload;
48
+ /** If true, the UI should confirm with the user before executing. */
49
+ requiresConfirmation?: boolean;
50
+ /** Human-readable label for confirmation dialogs or logs. */
51
+ label?: string;
52
+ }
53
+ /** Payload for the built-in "navigate" action. */
54
+ interface NavigatePayload {
55
+ /** Entity type (e.g., "board", "document", "task"). */
56
+ entity: string;
57
+ /** Entity ID to select/focus. */
58
+ id: string;
59
+ /** Optional sub-view or section within the entity. */
60
+ view?: string;
61
+ }
62
+ /** Payload for the built-in "notify" action. */
63
+ interface NotifyPayload {
64
+ message: string;
65
+ level?: "info" | "success" | "warning" | "error";
66
+ }
24
67
  interface ToolCallResult<T = unknown> {
25
68
  data: T;
26
69
  isError: boolean;
@@ -30,6 +73,16 @@ interface Synapse {
30
73
  readonly isNimbleBrainHost: boolean;
31
74
  callTool<TInput = Record<string, unknown>, TOutput = unknown>(name: string, args?: TInput): Promise<ToolCallResult<TOutput>>;
32
75
  onDataChanged(callback: (event: DataChangedEvent) => void): () => void;
76
+ /**
77
+ * Subscribe to agent actions — typed, declarative commands from the server.
78
+ *
79
+ * Actions are deterministic side effects of tool execution. The server/tool
80
+ * decides what action to emit; the UI decides how to handle it.
81
+ *
82
+ * The callback receives an AgentAction with a `type` discriminator and typed
83
+ * `payload`. Apps should handle known types and ignore unknown ones.
84
+ */
85
+ onAction(callback: (action: AgentAction) => void): () => void;
33
86
  getTheme(): SynapseTheme;
34
87
  onThemeChanged(callback: (theme: SynapseTheme) => void): () => void;
35
88
  action(action: string, params?: Record<string, unknown>): void;
@@ -44,6 +97,8 @@ interface Synapse {
44
97
  _onMessage(method: string, callback: (params: Record<string, unknown> | undefined) => void): () => void;
45
98
  /** @internal — used by createStore for ui/persistState */
46
99
  _request(method: string, params?: Record<string, unknown>): Promise<unknown>;
100
+ /** True after destroy() has been called. */
101
+ readonly destroyed: boolean;
47
102
  destroy(): void;
48
103
  }
49
104
  interface VisibleState {
@@ -93,4 +148,4 @@ interface HostInfo {
93
148
  theme: SynapseTheme;
94
149
  }
95
150
 
96
- export type { ActionReducer as A, DataChangedEvent as D, HostInfo as H, KeyForwardConfig as K, SynapseOptions as S, ToolCallResult as T, VisibleState as V, Synapse as a, StoreConfig as b, Store as c, StateAcknowledgement as d, StoreDispatch as e, SynapseTheme as f, ToolDefinition as g };
151
+ export type { ActionReducer as A, BuiltinActionType as B, DataChangedEvent as D, HostInfo as H, KeyForwardConfig as K, NavigatePayload as N, SynapseOptions as S, ToolCallResult as T, VisibleState as V, Synapse as a, StoreConfig as b, Store as c, AgentAction as d, NotifyPayload as e, StateAcknowledgement as f, StoreDispatch as g, SynapseTheme as h, ToolDefinition as i };
@@ -201,7 +201,7 @@ function previewHostHtml(appName) {
201
201
  <button id="toggle">Toggle Theme</button>
202
202
  <span class="url">Synapse Preview</span>
203
203
  </header>
204
- <iframe id="app" src="/"></iframe>
204
+ <iframe id="app"></iframe>
205
205
 
206
206
  <script>
207
207
  var iframe = document.getElementById("app");
@@ -209,19 +209,19 @@ function previewHostHtml(appName) {
209
209
 
210
210
  function getTokens(d) {
211
211
  return d ? {
212
- "--nb-background":"#0f172a","--nb-foreground":"#e2e8f0",
213
- "--nb-card":"#1e293b","--nb-card-foreground":"#e2e8f0",
214
- "--nb-primary":"#6366f1","--nb-primary-foreground":"#fff",
215
- "--nb-muted-foreground":"#94a3b8","--nb-border":"#334155",
216
- "--nb-ring":"#6366f1","--nb-destructive":"#ef4444",
217
- "--nb-radius":"0.5rem","--nb-font-sans":"-apple-system,BlinkMacSystemFont,sans-serif"
212
+ "--color-background-primary":"#0f172a","--color-text-primary":"#e2e8f0",
213
+ "--color-background-secondary":"#1e293b","--color-text-primary":"#e2e8f0",
214
+ "--color-text-accent":"#6366f1","--nb-color-accent-foreground":"#fff",
215
+ "--color-text-secondary":"#94a3b8","--color-border-primary":"#334155",
216
+ "--color-ring-primary":"#6366f1","--nb-color-danger":"#ef4444",
217
+ "--border-radius-sm":"0.5rem","--font-sans":"-apple-system,BlinkMacSystemFont,sans-serif"
218
218
  } : {
219
- "--nb-background":"#ffffff","--nb-foreground":"#0f172a",
220
- "--nb-card":"#f8fafc","--nb-card-foreground":"#0f172a",
221
- "--nb-primary":"#6366f1","--nb-primary-foreground":"#fff",
222
- "--nb-muted-foreground":"#64748b","--nb-border":"#e2e8f0",
223
- "--nb-ring":"#6366f1","--nb-destructive":"#ef4444",
224
- "--nb-radius":"0.5rem","--nb-font-sans":"-apple-system,BlinkMacSystemFont,sans-serif"
219
+ "--color-background-primary":"#ffffff","--color-text-primary":"#0f172a",
220
+ "--color-background-secondary":"#f8fafc","--color-text-primary":"#0f172a",
221
+ "--color-text-accent":"#6366f1","--nb-color-accent-foreground":"#fff",
222
+ "--color-text-secondary":"#64748b","--color-border-primary":"#e2e8f0",
223
+ "--color-ring-primary":"#6366f1","--nb-color-danger":"#ef4444",
224
+ "--border-radius-sm":"0.5rem","--font-sans":"-apple-system,BlinkMacSystemFont,sans-serif"
225
225
  };
226
226
  }
227
227
 
@@ -255,6 +255,12 @@ function previewHostHtml(appName) {
255
255
  var response = await r.json();
256
256
  response.id = originalId;
257
257
  post(response);
258
+ // Notify the app that data changed so hooks auto-refresh
259
+ // Only for mutating operations (not list/search/get which are read-only)
260
+ var tn = msg.params.name || "";
261
+ if (!response.error && !tn.startsWith("list_") && !tn.startsWith("search_") && !tn.startsWith("get_") && !tn.startsWith("query_")) {
262
+ post({jsonrpc:"2.0",method:"ui/datachanged",params:{tool:tn,server:"preview"}});
263
+ }
258
264
  } catch(err) {
259
265
  post({jsonrpc:"2.0",id:originalId,error:{code:-32000,message:err.message}});
260
266
  }
@@ -274,6 +280,10 @@ function previewHostHtml(appName) {
274
280
  document.body.style.background = dark ? "#0f172a" : "#f1f5f9";
275
281
  post({jsonrpc:"2.0",method:"ui/themeChanged",params:{mode:dark?"dark":"light",tokens:getTokens(dark)}});
276
282
  };
283
+
284
+ // Load the iframe AFTER the message listener is attached to avoid
285
+ // a race where the app sends ui/initialize before the bridge is ready.
286
+ iframe.src = "/";
277
287
  </script>
278
288
  </body>
279
289
  </html>`;