@nimblebrain/synapse 0.2.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +181 -45
- package/dist/{chunk-Y4ZDNAYQ.cjs → chunk-B3T6NB32.cjs} +349 -80
- package/dist/chunk-B3T6NB32.cjs.map +1 -0
- package/dist/{chunk-7KEYXJWD.js → chunk-GQ4L63CL.js} +349 -81
- package/dist/chunk-GQ4L63CL.js.map +1 -0
- package/dist/codegen/cli.cjs +1 -1
- package/dist/codegen/cli.js +1 -1
- package/dist/codegen/index.d.cts +1 -1
- package/dist/codegen/index.d.ts +1 -1
- package/dist/connect.iife.global.js +1 -0
- package/dist/index.cjs +8 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +11 -3
- package/dist/index.d.ts +11 -3
- package/dist/index.js +3 -3
- package/dist/index.js.map +1 -1
- package/dist/react/index.cjs +119 -3
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.d.cts +18 -2
- package/dist/react/index.d.ts +18 -2
- package/dist/react/index.js +114 -5
- package/dist/react/index.js.map +1 -1
- package/dist/{server-NNW54YW5.js → server-7BRGSPT3.js} +13 -13
- package/dist/{server-NNW54YW5.js.map → server-7BRGSPT3.js.map} +1 -1
- package/dist/{server-3BDZ5S72.cjs → server-SRE7E3G3.cjs} +13 -13
- package/dist/{server-3BDZ5S72.cjs.map → server-SRE7E3G3.cjs.map} +1 -1
- package/dist/synapse-runtime.iife.global.js +1 -1
- package/dist/{types-DElq_otH.d.cts → types-DJ32F5EL.d.cts} +79 -4
- package/dist/{types-DElq_otH.d.ts → types-DJ32F5EL.d.ts} +79 -4
- package/dist/vite/index.cjs +6 -6
- package/dist/vite/index.cjs.map +1 -1
- package/dist/vite/index.js +6 -6
- package/dist/vite/index.js.map +1 -1
- package/package.json +4 -1
- package/dist/chunk-7KEYXJWD.js.map +0 -1
- package/dist/chunk-Y4ZDNAYQ.cjs.map +0 -1
package/dist/react/index.js
CHANGED
|
@@ -1,14 +1,96 @@
|
|
|
1
|
-
import { createSynapse } from '../chunk-
|
|
2
|
-
import { createContext, useRef,
|
|
3
|
-
import { jsx } from 'react/jsx-runtime';
|
|
1
|
+
import { connect, createSynapse } from '../chunk-GQ4L63CL.js';
|
|
2
|
+
import { createContext, useState, useRef, useEffect, useCallback, useSyncExternalStore, useContext } from 'react';
|
|
3
|
+
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
4
4
|
|
|
5
|
+
var AppContext = createContext(null);
|
|
6
|
+
function AppProvider({ children, name, version, autoResize }) {
|
|
7
|
+
const [app, setApp] = useState(null);
|
|
8
|
+
const connectingRef = useRef(false);
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
if (connectingRef.current) return;
|
|
11
|
+
connectingRef.current = true;
|
|
12
|
+
connect({ name, version, autoResize }).then((a) => {
|
|
13
|
+
setApp(a);
|
|
14
|
+
});
|
|
15
|
+
}, []);
|
|
16
|
+
if (!app) return null;
|
|
17
|
+
return /* @__PURE__ */ jsx(AppContext.Provider, { value: app, children });
|
|
18
|
+
}
|
|
19
|
+
function useAppContext() {
|
|
20
|
+
const ctx = useContext(AppContext);
|
|
21
|
+
if (!ctx) {
|
|
22
|
+
throw new Error(
|
|
23
|
+
"useApp must be used within an <AppProvider>. Wrap your component tree with <AppProvider>."
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
return ctx;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// src/react/connect-hooks.ts
|
|
30
|
+
function useApp() {
|
|
31
|
+
return useAppContext();
|
|
32
|
+
}
|
|
33
|
+
function useToolResult() {
|
|
34
|
+
const app = useAppContext();
|
|
35
|
+
const [data, setData] = useState(null);
|
|
36
|
+
useEffect(() => {
|
|
37
|
+
return app.on("tool-result", (result) => {
|
|
38
|
+
setData(result);
|
|
39
|
+
});
|
|
40
|
+
}, [app]);
|
|
41
|
+
return data;
|
|
42
|
+
}
|
|
43
|
+
function useToolInput() {
|
|
44
|
+
const app = useAppContext();
|
|
45
|
+
const [input, setInput] = useState(null);
|
|
46
|
+
useEffect(() => {
|
|
47
|
+
return app.on("tool-input", (args) => {
|
|
48
|
+
setInput(args);
|
|
49
|
+
});
|
|
50
|
+
}, [app]);
|
|
51
|
+
return input;
|
|
52
|
+
}
|
|
53
|
+
function useConnectTheme() {
|
|
54
|
+
const app = useAppContext();
|
|
55
|
+
const [theme, setTheme] = useState(() => app.theme);
|
|
56
|
+
useEffect(() => {
|
|
57
|
+
setTheme(app.theme);
|
|
58
|
+
return app.on("theme-changed", (t) => {
|
|
59
|
+
setTheme(t);
|
|
60
|
+
});
|
|
61
|
+
}, [app]);
|
|
62
|
+
return theme;
|
|
63
|
+
}
|
|
64
|
+
function useResize() {
|
|
65
|
+
const app = useAppContext();
|
|
66
|
+
return useCallback((width, height) => app.resize(width, height), [app]);
|
|
67
|
+
}
|
|
5
68
|
var SynapseContext = createContext(null);
|
|
6
69
|
function SynapseProvider({ children, ...options }) {
|
|
7
70
|
const ref = useRef(null);
|
|
8
71
|
if (ref.current === null || ref.current.destroyed) {
|
|
9
72
|
ref.current = createSynapse(options);
|
|
10
73
|
}
|
|
11
|
-
return /* @__PURE__ */
|
|
74
|
+
return /* @__PURE__ */ jsxs(SynapseContext.Provider, { value: ref.current, children: [
|
|
75
|
+
/* @__PURE__ */ jsx(ThemeInjector, { synapse: ref.current }),
|
|
76
|
+
children
|
|
77
|
+
] });
|
|
78
|
+
}
|
|
79
|
+
function ThemeInjector({ synapse }) {
|
|
80
|
+
const [theme, setTheme] = useState(() => synapse.getTheme());
|
|
81
|
+
useEffect(() => {
|
|
82
|
+
setTheme(synapse.getTheme());
|
|
83
|
+
return synapse.onThemeChanged(setTheme);
|
|
84
|
+
}, [synapse]);
|
|
85
|
+
useEffect(() => {
|
|
86
|
+
if (theme.tokens) {
|
|
87
|
+
const style = document.documentElement.style;
|
|
88
|
+
for (const [k, v] of Object.entries(theme.tokens)) {
|
|
89
|
+
style.setProperty(k, v);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}, [theme]);
|
|
93
|
+
return null;
|
|
12
94
|
}
|
|
13
95
|
function useSynapseContext() {
|
|
14
96
|
const ctx = useContext(SynapseContext);
|
|
@@ -109,6 +191,33 @@ function useVisibleState(factory, deps) {
|
|
|
109
191
|
}, [...deps ?? [], push]);
|
|
110
192
|
if (!factory) return push;
|
|
111
193
|
}
|
|
194
|
+
function useFileUpload() {
|
|
195
|
+
const synapse = useSynapseContext();
|
|
196
|
+
const [isPending, setIsPending] = useState(false);
|
|
197
|
+
const pickFile = useCallback(
|
|
198
|
+
async (options) => {
|
|
199
|
+
setIsPending(true);
|
|
200
|
+
try {
|
|
201
|
+
return await synapse.pickFile(options);
|
|
202
|
+
} finally {
|
|
203
|
+
setIsPending(false);
|
|
204
|
+
}
|
|
205
|
+
},
|
|
206
|
+
[synapse]
|
|
207
|
+
);
|
|
208
|
+
const pickFiles = useCallback(
|
|
209
|
+
async (options) => {
|
|
210
|
+
setIsPending(true);
|
|
211
|
+
try {
|
|
212
|
+
return await synapse.pickFiles(options);
|
|
213
|
+
} finally {
|
|
214
|
+
setIsPending(false);
|
|
215
|
+
}
|
|
216
|
+
},
|
|
217
|
+
[synapse]
|
|
218
|
+
);
|
|
219
|
+
return { pickFile, pickFiles, isPending };
|
|
220
|
+
}
|
|
112
221
|
function useStore(store) {
|
|
113
222
|
const state = useSyncExternalStore(
|
|
114
223
|
(onStoreChange) => store.subscribe(onStoreChange),
|
|
@@ -118,6 +227,6 @@ function useStore(store) {
|
|
|
118
227
|
return { state, dispatch: store.dispatch };
|
|
119
228
|
}
|
|
120
229
|
|
|
121
|
-
export { SynapseProvider, useAction, useAgentAction, useCallTool, useChat, useDataSync, useStore, useSynapse, useTheme, useVisibleState };
|
|
230
|
+
export { AppProvider, SynapseProvider, useAction, useAgentAction, useApp, useCallTool, useChat, useConnectTheme, useDataSync, useFileUpload, useResize, useStore, useSynapse, useTheme, useToolInput, useToolResult, useVisibleState };
|
|
122
231
|
//# sourceMappingURL=index.js.map
|
|
123
232
|
//# sourceMappingURL=index.js.map
|
package/dist/react/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
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\n/**\n * Push the app's visible state to the agent via ext-apps `ui/update-model-context`.\n *\n * **Imperative** (no args) — returns a push function you call manually:\n * ```tsx\n * const push = useVisibleState();\n * push({ board: selectedBoard }, \"Viewing board X\");\n * ```\n *\n * **Declarative** (factory + deps) — auto-pushes when deps change:\n * ```tsx\n * useVisibleState(() => ({\n * state: { board: selectedBoard },\n * summary: `Viewing \"${selectedBoard?.name}\"`,\n * }), [selectedBoard]);\n * ```\n */\nexport function useVisibleState(): (state: Record<string, unknown>, summary?: string) => void;\nexport function useVisibleState(\n factory: () => { state: Record<string, unknown>; summary?: string },\n deps: unknown[],\n): void;\nexport function useVisibleState(\n factory?: () => { state: Record<string, unknown>; summary?: string },\n deps?: unknown[],\n): ((state: Record<string, unknown>, summary?: string) => void) | undefined {\n const synapse = useSynapseContext();\n const push = useCallback(\n (state: Record<string, unknown>, summary?: string) => synapse.setVisibleState(state, summary),\n [synapse],\n );\n\n // Declarative mode: auto-push when deps change.\n // The deps array is caller-provided (mirrors useMemo/useEffect pattern).\n const factoryRef = useRef(factory);\n factoryRef.current = factory;\n useEffect(() => {\n if (!factoryRef.current) return;\n const { state, summary } = factoryRef.current();\n push(state, summary);\n }, [...(deps ?? []), push]);\n\n if (!factory) return push;\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/app-provider.tsx","../../src/react/connect-hooks.ts","../../src/react/provider.tsx","../../src/react/hooks.ts"],"names":["useState","useEffect","createContext","useRef","jsx","useContext","useCallback"],"mappings":"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","file":"index.js","sourcesContent":["import { createContext, type ReactNode, useContext, useEffect, useRef, useState } from \"react\";\nimport { connect } from \"../connect.js\";\nimport type { App, ConnectOptions } from \"../types.js\";\n\nconst AppContext = createContext<App | null>(null);\n\nexport interface AppProviderProps extends ConnectOptions {\n children: ReactNode;\n}\n\nexport function AppProvider({ children, name, version, autoResize }: AppProviderProps) {\n const [app, setApp] = useState<App | null>(null);\n const connectingRef = useRef(false);\n\n // biome-ignore lint/correctness/useExhaustiveDependencies: connect once on mount\n useEffect(() => {\n if (connectingRef.current) return;\n connectingRef.current = true;\n\n connect({ name, version, autoResize }).then((a) => {\n setApp(a);\n });\n }, []);\n\n if (!app) return null; // Don't render children until connected\n\n return <AppContext.Provider value={app}>{children}</AppContext.Provider>;\n}\n\nexport function useAppContext(): App {\n const ctx = useContext(AppContext);\n if (!ctx) {\n throw new Error(\n \"useApp must be used within an <AppProvider>. Wrap your component tree with <AppProvider>.\",\n );\n }\n return ctx;\n}\n","import { useCallback, useEffect, useState } from \"react\";\nimport type { App, Theme, ToolResultData } from \"../types.js\";\nimport { AppProvider, useAppContext } from \"./app-provider.js\";\n\nexport { AppProvider };\n\nexport function useApp(): App {\n return useAppContext();\n}\n\nexport function useToolResult(): ToolResultData | null {\n const app = useAppContext();\n const [data, setData] = useState<ToolResultData | null>(null);\n\n useEffect(() => {\n return app.on(\"tool-result\", (result: ToolResultData) => {\n setData(result);\n });\n }, [app]);\n\n return data;\n}\n\nexport function useToolInput(): Record<string, unknown> | null {\n const app = useAppContext();\n const [input, setInput] = useState<Record<string, unknown> | null>(null);\n\n useEffect(() => {\n return app.on(\"tool-input\", (args: Record<string, unknown>) => {\n setInput(args);\n });\n }, [app]);\n\n return input;\n}\n\nexport function useConnectTheme(): Theme {\n const app = useAppContext();\n const [theme, setTheme] = useState<Theme>(() => app.theme);\n\n useEffect(() => {\n setTheme(app.theme); // Sync in case it changed between render and effect\n return app.on(\"theme-changed\", (t: Theme) => {\n setTheme(t);\n });\n }, [app]);\n\n return theme;\n}\n\nexport function useResize(): (width?: number, height?: number) => void {\n const app = useAppContext();\n return useCallback((width?: number, height?: number) => app.resize(width, height), [app]);\n}\n","import { createContext, type ReactNode, useContext, useEffect, useRef, useState } from \"react\";\nimport { createSynapse } from \"../core.js\";\nimport type { Synapse, SynapseOptions, SynapseTheme } 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 (\n <SynapseContext.Provider value={ref.current}>\n <ThemeInjector synapse={ref.current} />\n {children}\n </SynapseContext.Provider>\n );\n}\n\n/** Injects theme CSS variables onto document.documentElement whenever the theme changes. */\nfunction ThemeInjector({ synapse }: { synapse: Synapse }) {\n const [theme, setTheme] = useState<SynapseTheme>(() => synapse.getTheme());\n\n useEffect(() => {\n setTheme(synapse.getTheme());\n return synapse.onThemeChanged(setTheme);\n }, [synapse]);\n\n useEffect(() => {\n if (theme.tokens) {\n const style = document.documentElement.style;\n for (const [k, v] of Object.entries(theme.tokens)) {\n style.setProperty(k, v);\n }\n }\n }, [theme]);\n\n return null;\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 FileResult,\n RequestFileOptions,\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\n/**\n * Push the app's visible state to the agent via ext-apps `ui/update-model-context`.\n *\n * **Imperative** (no args) — returns a push function you call manually:\n * ```tsx\n * const push = useVisibleState();\n * push({ board: selectedBoard }, \"Viewing board X\");\n * ```\n *\n * **Declarative** (factory + deps) — auto-pushes when deps change:\n * ```tsx\n * useVisibleState(() => ({\n * state: { board: selectedBoard },\n * summary: `Viewing \"${selectedBoard?.name}\"`,\n * }), [selectedBoard]);\n * ```\n */\nexport function useVisibleState(): (state: Record<string, unknown>, summary?: string) => void;\nexport function useVisibleState(\n factory: () => { state: Record<string, unknown>; summary?: string },\n deps: unknown[],\n): void;\nexport function useVisibleState(\n factory?: () => { state: Record<string, unknown>; summary?: string },\n deps?: unknown[],\n): ((state: Record<string, unknown>, summary?: string) => void) | undefined {\n const synapse = useSynapseContext();\n const push = useCallback(\n (state: Record<string, unknown>, summary?: string) => synapse.setVisibleState(state, summary),\n [synapse],\n );\n\n // Declarative mode: auto-push when deps change.\n // The deps array is caller-provided (mirrors useMemo/useEffect pattern).\n const factoryRef = useRef(factory);\n factoryRef.current = factory;\n useEffect(() => {\n if (!factoryRef.current) return;\n const { state, summary } = factoryRef.current();\n push(state, summary);\n }, [...(deps ?? []), push]);\n\n if (!factory) return push;\n}\n\nexport function useFileUpload(): {\n pickFile: (options?: RequestFileOptions) => Promise<FileResult | null>;\n pickFiles: (options?: RequestFileOptions) => Promise<FileResult[]>;\n isPending: boolean;\n} {\n const synapse = useSynapseContext();\n const [isPending, setIsPending] = useState(false);\n\n const pickFile = useCallback(\n async (options?: RequestFileOptions) => {\n setIsPending(true);\n try {\n return await synapse.pickFile(options);\n } finally {\n setIsPending(false);\n }\n },\n [synapse],\n );\n\n const pickFiles = useCallback(\n async (options?: RequestFileOptions) => {\n setIsPending(true);\n try {\n return await synapse.pickFiles(options);\n } finally {\n setIsPending(false);\n }\n },\n [synapse],\n );\n\n return { pickFile, pickFiles, isPending };\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"]}
|
|
@@ -94,25 +94,25 @@ var HOST_HTML = (uiPort, serverPort) => `<!DOCTYPE html>
|
|
|
94
94
|
return;
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
-
//
|
|
98
|
-
if (msg.method === "
|
|
97
|
+
// synapse/chat \u2014 log to console
|
|
98
|
+
if (msg.method === "synapse/chat") {
|
|
99
99
|
console.log("[chat]", msg.params?.message);
|
|
100
100
|
return;
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
-
//
|
|
104
|
-
if (msg.method === "
|
|
103
|
+
// synapse/action \u2014 log to console
|
|
104
|
+
if (msg.method === "synapse/action") {
|
|
105
105
|
console.log("[action]", msg.params?.action, msg.params);
|
|
106
106
|
return;
|
|
107
107
|
}
|
|
108
108
|
|
|
109
|
-
//
|
|
110
|
-
if (msg.method === "
|
|
109
|
+
// synapse/keydown \u2014 ignore in preview
|
|
110
|
+
if (msg.method === "synapse/keydown") return;
|
|
111
111
|
|
|
112
|
-
// ui/
|
|
113
|
-
if (msg.method === "ui/
|
|
114
|
-
console.log("[
|
|
115
|
-
post({ jsonrpc: "2.0",
|
|
112
|
+
// ui/update-model-context \u2014 log (ext-apps spec)
|
|
113
|
+
if (msg.method === "ui/update-model-context") {
|
|
114
|
+
console.log("[model-context]", msg.params?.structuredContent);
|
|
115
|
+
if (msg.id) post({ jsonrpc: "2.0", id: msg.id, result: {} });
|
|
116
116
|
return;
|
|
117
117
|
}
|
|
118
118
|
|
|
@@ -138,7 +138,7 @@ var HOST_HTML = (uiPort, serverPort) => `<!DOCTYPE html>
|
|
|
138
138
|
"--color-ring-primary": "#6366f1", "--nb-color-danger": "#ef4444",
|
|
139
139
|
"--border-radius-sm": "0.5rem",
|
|
140
140
|
};
|
|
141
|
-
post({ jsonrpc: "2.0", method: "
|
|
141
|
+
post({ jsonrpc: "2.0", method: "synapse/theme-changed", params: { mode: darkMode ? "dark" : "light", tokens: tokens } });
|
|
142
142
|
});
|
|
143
143
|
</script>
|
|
144
144
|
</body>
|
|
@@ -200,5 +200,5 @@ async function startPreview(options) {
|
|
|
200
200
|
}
|
|
201
201
|
|
|
202
202
|
export { startPreview };
|
|
203
|
-
//# sourceMappingURL=server-
|
|
204
|
-
//# sourceMappingURL=server-
|
|
203
|
+
//# sourceMappingURL=server-7BRGSPT3.js.map
|
|
204
|
+
//# sourceMappingURL=server-7BRGSPT3.js.map
|
|
@@ -1 +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
|
+
{"version":3,"sources":["../src/preview/server.ts"],"names":[],"mappings":"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","file":"server-7BRGSPT3.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 // synapse/chat — log to console\n if (msg.method === \"synapse/chat\") {\n console.log(\"[chat]\", msg.params?.message);\n return;\n }\n\n // synapse/action — log to console\n if (msg.method === \"synapse/action\") {\n console.log(\"[action]\", msg.params?.action, msg.params);\n return;\n }\n\n // synapse/keydown — ignore in preview\n if (msg.method === \"synapse/keydown\") return;\n\n // ui/update-model-context — log (ext-apps spec)\n if (msg.method === \"ui/update-model-context\") {\n console.log(\"[model-context]\", msg.params?.structuredContent);\n if (msg.id) post({ jsonrpc: \"2.0\", id: msg.id, result: {} });\n return;\n }\n\n console.log(\"[bridge] unhandled:\", msg.method, msg);\n });\n\n // Theme toggle\n document.getElementById(\"toggle\").addEventListener(\"click\", function () {\n darkMode = !darkMode;\n document.body.style.background = darkMode ? \"#0f172a\" : \"#f1f5f9\";\n tokens = darkMode ? {\n \"--color-background-primary\": \"#0f172a\", \"--color-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: \"synapse/theme-changed\", 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"]}
|
|
@@ -96,25 +96,25 @@ var HOST_HTML = (uiPort, serverPort) => `<!DOCTYPE html>
|
|
|
96
96
|
return;
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
-
//
|
|
100
|
-
if (msg.method === "
|
|
99
|
+
// synapse/chat \u2014 log to console
|
|
100
|
+
if (msg.method === "synapse/chat") {
|
|
101
101
|
console.log("[chat]", msg.params?.message);
|
|
102
102
|
return;
|
|
103
103
|
}
|
|
104
104
|
|
|
105
|
-
//
|
|
106
|
-
if (msg.method === "
|
|
105
|
+
// synapse/action \u2014 log to console
|
|
106
|
+
if (msg.method === "synapse/action") {
|
|
107
107
|
console.log("[action]", msg.params?.action, msg.params);
|
|
108
108
|
return;
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
-
//
|
|
112
|
-
if (msg.method === "
|
|
111
|
+
// synapse/keydown \u2014 ignore in preview
|
|
112
|
+
if (msg.method === "synapse/keydown") return;
|
|
113
113
|
|
|
114
|
-
// ui/
|
|
115
|
-
if (msg.method === "ui/
|
|
116
|
-
console.log("[
|
|
117
|
-
post({ jsonrpc: "2.0",
|
|
114
|
+
// ui/update-model-context \u2014 log (ext-apps spec)
|
|
115
|
+
if (msg.method === "ui/update-model-context") {
|
|
116
|
+
console.log("[model-context]", msg.params?.structuredContent);
|
|
117
|
+
if (msg.id) post({ jsonrpc: "2.0", id: msg.id, result: {} });
|
|
118
118
|
return;
|
|
119
119
|
}
|
|
120
120
|
|
|
@@ -140,7 +140,7 @@ var HOST_HTML = (uiPort, serverPort) => `<!DOCTYPE html>
|
|
|
140
140
|
"--color-ring-primary": "#6366f1", "--nb-color-danger": "#ef4444",
|
|
141
141
|
"--border-radius-sm": "0.5rem",
|
|
142
142
|
};
|
|
143
|
-
post({ jsonrpc: "2.0", method: "
|
|
143
|
+
post({ jsonrpc: "2.0", method: "synapse/theme-changed", params: { mode: darkMode ? "dark" : "light", tokens: tokens } });
|
|
144
144
|
});
|
|
145
145
|
</script>
|
|
146
146
|
</body>
|
|
@@ -202,5 +202,5 @@ async function startPreview(options) {
|
|
|
202
202
|
}
|
|
203
203
|
|
|
204
204
|
exports.startPreview = startPreview;
|
|
205
|
-
//# sourceMappingURL=server-
|
|
206
|
-
//# sourceMappingURL=server-
|
|
205
|
+
//# sourceMappingURL=server-SRE7E3G3.cjs.map
|
|
206
|
+
//# sourceMappingURL=server-SRE7E3G3.cjs.map
|
|
@@ -1 +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"]}
|
|
1
|
+
{"version":3,"sources":["../src/preview/server.ts"],"names":["spawn","resolve","existsSync","join","createServer"],"mappings":";;;;;;;;AA6BA,IAAM,SAAA,GAAY,CAAC,MAAA,EAAgB,UAAA,KAAuB,CAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,sCAAA,EAqBlB,UAAU,oBAAoB,MAAM,CAAA;AAAA;AAAA,yCAAA,EAEjC,MAAM,CAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA,mDAAA,EAiDI,UAAU,CAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,OAAA,CAAA;AAoE/D,eAAsB,aAAa,OAAA,EAAwC;AACzE,EAAA,MAAM,EAAE,SAAA,EAAW,UAAA,EAAY,KAAA,EAAO,MAAA,EAAQ,aAAY,GAAI,OAAA;AAC9D,EAAA,MAAM,WAA2B,EAAC;AAElC,EAAA,OAAA,CAAQ,GAAA,CAAI;AAAA;AAAA,CAAuB,CAAA;AAGnC,EAAA,OAAA,CAAQ,GAAA,CAAI,CAAA,WAAA,EAAc,SAAS,CAAA,CAAE,CAAA;AACrC,EAAA,MAAM,UAAA,GAAaA,oBAAM,SAAA,EAAW;AAAA,IAClC,KAAA,EAAO,IAAA;AAAA,IACP,KAAA,EAAO,CAAC,QAAA,EAAU,MAAA,EAAQ,MAAM;AAAA,GACjC,CAAA;AACD,EAAA,QAAA,CAAS,KAAK,UAAU,CAAA;AACxB,EAAA,UAAA,CAAW,MAAA,EAAQ,EAAA,CAAG,MAAA,EAAQ,CAAC,CAAA,KAAc,OAAA,CAAQ,MAAA,CAAO,KAAA,CAAM,CAAA,WAAA,EAAc,CAAC,CAAA,CAAE,CAAC,CAAA;AACpF,EAAA,UAAA,CAAW,MAAA,EAAQ,EAAA,CAAG,MAAA,EAAQ,CAAC,CAAA,KAAc,OAAA,CAAQ,MAAA,CAAO,KAAA,CAAM,CAAA,WAAA,EAAc,CAAC,CAAA,CAAE,CAAC,CAAA;AAGpF,EAAA,MAAM,UAAA,GAAaC,aAAQ,KAAK,CAAA;AAChC,EAAA,IAAI,CAACC,aAAA,CAAWC,SAAA,CAAK,UAAA,EAAY,cAAc,CAAC,CAAA,EAAG;AACjD,IAAA,OAAA,CAAQ,KAAA,CAAM,CAAA,uCAAA,EAA0C,UAAU,CAAA,CAAE,CAAA;AACpE,IAAA,OAAA,CAAQ,KAAK,CAAC,CAAA;AAAA,EAChB;AACA,EAAA,OAAA,CAAQ,GAAA,CAAI,CAAA,UAAA,EAAa,UAAU,CAAA,oBAAA,EAAuB,MAAM,CAAA,CAAE,CAAA;AAClE,EAAA,MAAM,MAAA,GAASH,oBAAM,KAAA,EAAO,CAAC,QAAQ,QAAA,EAAU,MAAA,CAAO,MAAM,CAAC,CAAA,EAAG;AAAA,IAC9D,GAAA,EAAK,UAAA;AAAA,IACL,KAAA,EAAO,CAAC,QAAA,EAAU,MAAA,EAAQ,MAAM;AAAA,GACjC,CAAA;AACD,EAAA,QAAA,CAAS,KAAK,MAAM,CAAA;AACpB,EAAA,MAAA,CAAO,MAAA,EAAQ,EAAA,CAAG,MAAA,EAAQ,CAAC,CAAA,KAAc,OAAA,CAAQ,MAAA,CAAO,KAAA,CAAM,CAAA,OAAA,EAAU,CAAC,CAAA,CAAE,CAAC,CAAA;AAC5E,EAAA,MAAA,CAAO,MAAA,EAAQ,EAAA,CAAG,MAAA,EAAQ,CAAC,CAAA,KAAc,OAAA,CAAQ,MAAA,CAAO,KAAA,CAAM,CAAA,OAAA,EAAU,CAAC,CAAA,CAAE,CAAC,CAAA;AAG5E,EAAA,MAAM,IAAA,GAAO,SAAA,CAAU,MAAA,EAAQ,UAAU,CAAA;AACzC,EAAA,MAAM,IAAA,GAAOI,iBAAA,CAAa,CAAC,IAAA,EAAuB,GAAA,KAAwB;AACxE,IAAA,GAAA,CAAI,SAAA,CAAU,GAAA,EAAK,EAAE,cAAA,EAAgB,aAAa,CAAA;AAClD,IAAA,GAAA,CAAI,IAAI,IAAI,CAAA;AAAA,EACd,CAAC,CAAA;AACD,EAAA,IAAA,CAAK,MAAA,CAAO,aAAa,MAAM;AAC7B,IAAA,OAAA,CAAQ,GAAA,CAAI;AAAA,6BAAA,EAAkC,WAAW,CAAA,CAAE,CAAA;AAC3D,IAAA,OAAA,CAAQ,GAAA,CAAI,CAAA,6BAAA,EAAgC,MAAM,CAAA,CAAE,CAAA;AACpD,IAAA,OAAA,CAAQ,GAAA,CAAI,CAAA,6BAAA,EAAgC,UAAU,CAAA,CAAE,CAAA;AACxD,IAAA,OAAA,CAAQ,GAAA,CAAI;AAAA;AAAA,CAA6B,CAAA;AAAA,EAC3C,CAAC,CAAA;AAGD,EAAA,MAAM,WAAW,MAAM;AACrB,IAAA,OAAA,CAAQ,IAAI,sBAAsB,CAAA;AAClC,IAAA,IAAA,CAAK,KAAA,EAAM;AACX,IAAA,KAAA,MAAW,SAAS,QAAA,EAAU;AAC5B,MAAA,IAAI;AACF,QAAA,KAAA,CAAM,KAAK,SAAS,CAAA;AAAA,MACtB,CAAA,CAAA,MAAQ;AAAA,MAER;AAAA,IACF;AACA,IAAA,OAAA,CAAQ,KAAK,CAAC,CAAA;AAAA,EAChB,CAAA;AACA,EAAA,OAAA,CAAQ,EAAA,CAAG,UAAU,QAAQ,CAAA;AAC7B,EAAA,OAAA,CAAQ,EAAA,CAAG,WAAW,QAAQ,CAAA;AAChC","file":"server-SRE7E3G3.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 // synapse/chat — log to console\n if (msg.method === \"synapse/chat\") {\n console.log(\"[chat]\", msg.params?.message);\n return;\n }\n\n // synapse/action — log to console\n if (msg.method === \"synapse/action\") {\n console.log(\"[action]\", msg.params?.action, msg.params);\n return;\n }\n\n // synapse/keydown — ignore in preview\n if (msg.method === \"synapse/keydown\") return;\n\n // ui/update-model-context — log (ext-apps spec)\n if (msg.method === \"ui/update-model-context\") {\n console.log(\"[model-context]\", msg.params?.structuredContent);\n if (msg.id) post({ jsonrpc: \"2.0\", id: msg.id, result: {} });\n return;\n }\n\n console.log(\"[bridge] unhandled:\", msg.method, msg);\n });\n\n // Theme toggle\n document.getElementById(\"toggle\").addEventListener(\"click\", function () {\n darkMode = !darkMode;\n document.body.style.background = darkMode ? \"#0f172a\" : \"#f1f5f9\";\n tokens = darkMode ? {\n \"--color-background-primary\": \"#0f172a\", \"--color-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: \"synapse/theme-changed\", 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 C={mode:"light",primaryColor:"#6366f1",tokens:{}};function x(r){let e=r,o=A(e?.serverInfo),t=typeof o?.name=="string"?o.name:"unknown",i=typeof e?.protocolVersion=="string"?e.protocolVersion:"unknown",s=A(e?.hostContext),c=H(s?.theme);return {isNimbleBrain:t==="nimblebrain",serverName:t,protocolVersion:i,theme:c}}function H(r){let e=A(r);if(!e)return {...C};let o=e.mode==="light"||e.mode==="dark"?e.mode:C.mode,t=typeof e.primaryColor=="string"?e.primaryColor:C.primaryColor,i=e.tokens!==null&&typeof e.tokens=="object"&&!Array.isArray(e.tokens)?e.tokens:{};return {mode:o,primaryColor:t,tokens:i}}function A(r){if(r!==null&&typeof r=="object"&&!Array.isArray(r))return r}var S=class{listener;destroyed=false;constructor(e,o){let t=o??null;this.listener=i=>{this.destroyed||this.shouldForward(i,t)&&(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,o){if(o&&o.length===0)return false;if(o)return o.some(t=>e.key.toLowerCase()===t.key.toLowerCase()&&(t.ctrl===void 0||e.ctrlKey===t.ctrl)&&(t.meta===void 0||e.metaKey===t.meta)&&(t.shift===void 0||e.shiftKey===t.shift)&&(t.alt===void 0||e.altKey===t.alt));if(e.key==="Escape")return true;if(e.ctrlKey||e.metaKey){let t=e.key.toLowerCase();return !(t==="c"||t==="v"||t==="x"||t==="a")}return false}};function E(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,o=r.content;if(o.length===0)return {data:null,isError:e};let t=o.find(L);if(!t)return {data:o,isError:e};try{return {data:JSON.parse(t.text),isError:e}}catch{return {data:t.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,o){if(this.destroyed)return;let t={jsonrpc:"2.0",method:e,...o!==void 0&&{params:o}};window.parent.postMessage(t,"*");}request(e,o){if(this.destroyed)return Promise.reject(new Error("Transport destroyed"));let t=`syn-${++this.counter}`,i={jsonrpc:"2.0",method:e,id:t,...o!==void 0&&{params:o}};return new Promise((s,c)=>{this.pending.set(t,{resolve:s,reject:c}),window.parent.postMessage(i,"*");})}onMessage(e,o){return this.handlers.has(e)||this.handlers.set(e,new Set),this.handlers.get(e)?.add(o),()=>{let t=this.handlers.get(e);t&&(t.delete(o),t.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 o of this.pending.values())o.reject(e);this.pending.clear(),this.handlers.clear();}handleMessage(e){if(this.destroyed)return;let o=e.data;if(!(!o||o.jsonrpc!=="2.0")){if("id"in o&&o.id&&!("method"in o)){let t=o,i=this.pending.get(t.id);if(!i)return;if(this.pending.delete(t.id),t.error){let s=new Error(t.error.message);s.code=t.error.code,s.data=t.error.data,i.reject(s);}else i.resolve(t.result);return}if("method"in o&&!("id"in o&&o.id)){let t=o,i=this.handlers.get(t.method);if(i)for(let s of i)s(t.params);}}}};function M(r){let{name:e,version:o,internal:t=false,forwardKeys:i}=r,s=new b,c=null,l={mode:"light",primaryColor:"#6366f1",tokens:{}},w=false,y=null,T=null,R=s.request("ui/initialize",{protocolVersion:"2026-01-26",clientInfo:{name:e,version:o},capabilities:{}}).then(n=>{c=x(n),l=c.theme,s.send("ui/notifications/initialized",{}),T=new S(s,i);}),d=s.onMessage("ui/notifications/host-context-changed",n=>{if(!n)return;let a=n.theme==="dark"?"dark":"light",u=n.tokens&&typeof n.tokens=="object"?n.tokens:l.tokens;l={mode:a,primaryColor:l.primaryColor,tokens:u};for(let g of p)g(l);}),f=s.onMessage("ui/themeChanged",n=>{if(!n)return;let a=n.mode==="dark"||n.mode==="light"?n.mode:l.mode,u=n.tokens&&typeof n.tokens=="object"?n.tokens:l.tokens;l={mode:a,primaryColor:l.primaryColor,tokens:u};for(let g of p)g(l);}),p=new Set,m=new Set,h=new Set,k=s.onMessage("ui/datachanged",n=>{if(!n)return;let a={source:"agent",server:n.server??"",tool:n.tool??""};for(let u of m)u(a);}),K=s.onMessage("ui/action",n=>{if(!n||typeof n.type!="string")return;let a={type:n.type,payload:n.payload??{},requiresConfirmation:n.requiresConfirmation===true,label:typeof n.label=="string"?n.label:void 0};for(let u of h)u(a);}),v=()=>c?.isNimbleBrain===true;return {get ready(){return R},get isNimbleBrainHost(){return v()},get destroyed(){return w},async callTool(n,a){let u={name:n,arguments:a??{}};t&&(u.server=e);let g=await s.request("tools/call",u);return E(g)},onDataChanged(n){return m.add(n),()=>{m.delete(n);}},onAction(n){return h.add(n),()=>{h.delete(n);}},getTheme(){return {...l}},onThemeChanged(n){return p.add(n),()=>{p.delete(n);}},action(n,a){v()&&s.send("ui/action",{action:n,...a});},chat(n,a){let u={type:"text",text:n};v()&&a&&(u._meta={context:a}),s.send("ui/message",{role:"user",content:[u]});},setVisibleState(n,a){y&&clearTimeout(y),y=setTimeout(()=>{s.send("ui/update-model-context",{structuredContent:n,...a!==void 0&&{content:[{type:"text",text:a}]}}),y=null;},250);},downloadFile(n,a,u){if(!v())return;let g=typeof a=="string"?a:"[Blob content not serializable]";s.send("ui/download-file",{data:g,filename:n,mimeType:u??"application/octet-stream"});},openLink(n){s.send("ui/open-link",{url:n}),v()||window.open(n,"_blank","noopener");},_onMessage(n,a){return s.onMessage(n,a)},_request(n,a){return s.request(n,a)},destroy(){w||(w=true,y&&clearTimeout(y),T?.destroy(),d(),f(),k(),K(),p.clear(),m.clear(),h.clear(),s.destroy());}}}function j(r,e){let o=structuredClone(e.initialState),t=new Set,i=false,s=null,c={};for(let d of Object.keys(e.actions))c[d]=f=>{i||(o=e.actions[d](o,f),l());};function l(){for(let d of t)d(o);e.visibleToAgent&&w(),e.persist&&y();}function w(){let d=e.summarize?.(o);r.setVisibleState(o,d);}function y(){s&&clearTimeout(s),s=setTimeout(()=>{r._request("ui/persistState",{state:o,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,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 o},subscribe(d){return t.add(d),()=>{t.delete(d);}},dispatch:c,hydrate(d){o=d;for(let f of t)f(o);},destroy(){i||(i=true,s&&clearTimeout(s),t.clear(),T?.());}};return R}window.NbSynapse={createSynapse:M,createStore:j};})();
|
|
1
|
+
(function(){'use strict';function I(s){let e=s??{},o=e.structuredContent??null;if(o!=null)return {content:o,structuredContent:o,raw:e};let n=e.content;if(Array.isArray(n)){let a=n.filter(r=>r!=null&&typeof r=="object"&&r.type==="text"&&typeof r.text=="string").map(r=>r.text);if(a.length>0){let r=a.join("");try{return {content:JSON.parse(r),structuredContent:null,raw:e}}catch{return {content:r,structuredContent:null,raw:e}}}return {content:n,structuredContent:null,raw:e}}if(typeof n=="string")try{return {content:JSON.parse(n),structuredContent:null,raw:e}}catch{return {content:n,structuredContent:null,raw:e}}return {content:n??null,structuredContent:null,raw:e}}var _={"tool-result":"ui/notifications/tool-result","tool-input":"ui/notifications/tool-input","tool-input-partial":"ui/notifications/tool-input-partial","tool-cancelled":"ui/notifications/tool-cancelled","theme-changed":"ui/notifications/host-context-changed",teardown:"ui/resource-teardown"};function C(s){return _[s]??s}function K(s,e){let o=false,n=null,a=null;function r(){if(o)return;let m=document.body.scrollWidth,p=document.body.scrollHeight;s("ui/notifications/size-changed",{width:m,height:p});}function d(m,p){o||(m!==void 0&&p!==void 0?s("ui/notifications/size-changed",{width:m,height:p}):r());}e&&typeof ResizeObserver<"u"&&(n=new ResizeObserver(()=>{o||(a!==null&&cancelAnimationFrame(a),a=requestAnimationFrame(()=>{a=null,r();}));}),n.observe(document.body));function c(){o||(o=true,a!==null&&cancelAnimationFrame(a),n?.disconnect(),n=null);}return {resize:d,measureAndSend:r,destroy:c}}function A(s){return s==null?{data:null,isError:false}:H(s)?B(s):{data:s,isError:false}}function H(s){return s===null||typeof s!="object"||Array.isArray(s)?false:Array.isArray(s.content)}function J(s){if(s===null||typeof s!="object"||Array.isArray(s))return false;let e=s;return e.type==="text"&&typeof e.text=="string"}function B(s){let e=s.isError===true,o=s.content;if(o.length===0)return {data:null,isError:e};let n=o.find(J);if(!n)return {data:o,isError:e};try{return {data:JSON.parse(n.text),isError:e}}catch{return {data:n.text,isError:e}}}var S=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,o){if(this.destroyed)return;let n={jsonrpc:"2.0",method:e,...o!==void 0&&{params:o}};window.parent.postMessage(n,"*");}request(e,o){if(this.destroyed)return Promise.reject(new Error("Transport destroyed"));let n=`syn-${++this.counter}`,a={jsonrpc:"2.0",method:e,id:n,...o!==void 0&&{params:o}};return new Promise((r,d)=>{this.pending.set(n,{resolve:r,reject:d}),window.parent.postMessage(a,"*");})}onMessage(e,o){return this.handlers.has(e)||this.handlers.set(e,new Set),this.handlers.get(e)?.add(o),()=>{let n=this.handlers.get(e);n&&(n.delete(o),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 o of this.pending.values())o.reject(e);this.pending.clear(),this.handlers.clear();}handleMessage(e){if(this.destroyed)return;let o=e.data;if(!(!o||o.jsonrpc!=="2.0")){if("id"in o&&o.id&&!("method"in o)){let n=o,a=this.pending.get(n.id);if(!a)return;if(this.pending.delete(n.id),n.error){let r=new Error(n.error.message);r.code=n.error.code,r.data=n.error.data,a.reject(r);}else a.resolve(n.result);return}if("method"in o&&!("id"in o&&o.id)){let n=o,a=this.handlers.get(n.method);if(a)for(let r of a)r(n.params);}}}};async function O(s){let{name:e,version:o,autoResize:n=false}=s,a=new S,r=false,d={mode:"light",tokens:{}},c={name:"unknown",version:"unknown"},m=null,p=null,g=new Map,v=K((u,t)=>a.send(u,t),n);v.measureAndSend();let w=await a.request("ui/initialize",{protocolVersion:"2026-01-26",clientInfo:{name:e,version:o},capabilities:{}})??{},h=x(w.serverInfo);c={name:typeof h?.name=="string"?h.name:"unknown",version:typeof h?.version=="string"?h.version:"unknown"};let y=x(w.hostContext);if(y){let u=x(y.theme);if(u&&(d={mode:u.mode==="dark"?"dark":"light",tokens:u.tokens&&typeof u.tokens=="object"&&!Array.isArray(u.tokens)?u.tokens:{}}),y.toolInfo&&typeof y.toolInfo=="object"){let i=y.toolInfo;m={tool:i.tool??i};}y.containerDimensions&&typeof y.containerDimensions=="object"&&(p=y.containerDimensions);let t=x(y.styles);D(t?.variables);}a.send("ui/notifications/initialized",{});let R=C("theme-changed");a.onMessage(R,u=>{if(r||!u)return;let t=u.theme==="dark"?"dark":"light",i=u.tokens&&typeof u.tokens=="object"&&!Array.isArray(u.tokens)?u.tokens:d.tokens;d={mode:t,tokens:i},D(i);let l=g.get(R);if(l)for(let k of l)k(d);});let T=new Set([R]);function j(u){if(T.has(u))return;T.add(u);let t=C("tool-result"),i=u===t;a.onMessage(u,l=>{if(r)return;let k=g.get(u);if(k)for(let F of k)F(i?I(l):l);});}return {get theme(){return {...d}},get hostInfo(){return {...c}},get toolInfo(){return m},get containerDimensions(){return p},on(u,t){let i=C(u);return g.has(i)||g.set(i,new Set),g.get(i)?.add(t),j(i),()=>{let l=g.get(i);l&&(l.delete(t),l.size===0&&g.delete(i));}},resize(u,t){v.resize(u,t);},openLink(u){r||a.send("ui/open-link",{url:u});},updateModelContext(u,t){r||a.send("ui/update-model-context",{structuredContent:u,...t!==void 0&&{content:[{type:"text",text:t}]}});},async callTool(u,t){let i=await a.request("tools/call",{name:u,arguments:t??{}});return A(i)},sendMessage(u,t){if(r)return;let i={type:"text",text:u};t&&(i._meta={context:t}),a.send("ui/message",{role:"user",content:[i]});},destroy(){r||(r=true,v.destroy(),g.clear(),a.destroy());}}}function x(s){if(s!==null&&typeof s=="object"&&!Array.isArray(s))return s}function D(s){if(!(!s||typeof s!="object"))for(let[e,o]of Object.entries(s))typeof e=="string"&&typeof o=="string"&&document.documentElement.style.setProperty(e,o);}var E={mode:"light",primaryColor:"#6366f1",tokens:{}};function q(s){let e=s,o=z(e?.serverInfo),n=typeof o?.name=="string"?o.name:"unknown",a=typeof e?.protocolVersion=="string"?e.protocolVersion:"unknown",r=z(e?.hostContext),d=L(r?.theme);return {isNimbleBrain:n==="nimblebrain",serverName:n,protocolVersion:a,theme:d}}function L(s){let e=z(s);if(!e)return {...E};let o=e.mode==="light"||e.mode==="dark"?e.mode:E.mode,n=typeof e.primaryColor=="string"?e.primaryColor:E.primaryColor,a=e.tokens!==null&&typeof e.tokens=="object"&&!Array.isArray(e.tokens)?e.tokens:{};return {mode:o,primaryColor:n,tokens:a}}function z(s){if(s!==null&&typeof s=="object"&&!Array.isArray(s))return s}var M=class{listener;destroyed=false;constructor(e,o){let n=o??null;this.listener=a=>{this.destroyed||this.shouldForward(a,n)&&(a.preventDefault(),e.send("synapse/keydown",{key:a.key,ctrlKey:a.ctrlKey,metaKey:a.metaKey,shiftKey:a.shiftKey,altKey:a.altKey}));},document.addEventListener("keydown",this.listener);}destroy(){this.destroyed||(this.destroyed=true,document.removeEventListener("keydown",this.listener));}shouldForward(e,o){if(o&&o.length===0)return false;if(o)return o.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));if(e.key==="Escape")return true;if(e.ctrlKey||e.metaKey){let n=e.key.toLowerCase();return !(n==="c"||n==="v"||n==="x"||n==="a")}return false}};function P(s){let{name:e,version:o,internal:n=false,forwardKeys:a}=s,r=new S,d=null,c={mode:"light",primaryColor:"#6366f1",tokens:{}},m=false,p=null,g=null,v=r.request("ui/initialize",{protocolVersion:"2026-01-26",clientInfo:{name:e,version:o},capabilities:{}}).then(t=>{d=q(t),c=d.theme,r.send("ui/notifications/initialized",{}),g=new M(r,a);}),f=r.onMessage("ui/notifications/host-context-changed",t=>{if(!t)return;let i=t.theme==="dark"?"dark":"light",l=t.tokens&&typeof t.tokens=="object"?t.tokens:c.tokens;c={mode:i,primaryColor:c.primaryColor,tokens:l};for(let k of h)k(c);}),w=r.onMessage("synapse/theme-changed",t=>{if(!t)return;let i=t.mode==="dark"||t.mode==="light"?t.mode:c.mode,l=t.tokens&&typeof t.tokens=="object"?t.tokens:c.tokens;c={mode:i,primaryColor:c.primaryColor,tokens:l};for(let k of h)k(c);}),h=new Set,y=new Set,R=new Set,T=r.onMessage("synapse/data-changed",t=>{if(!t)return;let i={source:"agent",server:t.server??"",tool:t.tool??""};for(let l of y)l(i);}),j=r.onMessage("synapse/action",t=>{if(!t||typeof t.type!="string")return;let i={type:t.type,payload:t.payload??{},requiresConfirmation:t.requiresConfirmation===true,label:typeof t.label=="string"?t.label:void 0};for(let l of R)l(i);}),b=()=>d?.isNimbleBrain===true;return {get ready(){return v},get isNimbleBrainHost(){return b()},get destroyed(){return m},async callTool(t,i){let l={name:t,arguments:i??{}};n&&(l.server=e);let k=await r.request("tools/call",l);return A(k)},onDataChanged(t){return y.add(t),()=>{y.delete(t);}},onAction(t){return R.add(t),()=>{R.delete(t);}},getTheme(){return {...c}},onThemeChanged(t){return h.add(t),()=>{h.delete(t);}},action(t,i){b()&&r.send("synapse/action",{action:t,...i});},chat(t,i){let l={type:"text",text:t};b()&&i&&(l._meta={context:i}),r.send("ui/message",{role:"user",content:[l]});},setVisibleState(t,i){p&&clearTimeout(p),p=setTimeout(()=>{r.send("ui/update-model-context",{structuredContent:t,...i!==void 0&&{content:[{type:"text",text:i}]}}),p=null;},250);},saveFile(t,i,l){let k=typeof i=="string"?i:"[Blob content not serializable]";r.send("synapse/save-file",{data:k,filename:t,mimeType:l??"application/octet-stream"});},openLink(t){r.send("ui/open-link",{url:t}),b()||window.open(t,"_blank","noopener");},async pickFile(t){if(!b())throw new Error("pickFile is not supported in this host");return await r.request("synapse/pick-file",{accept:t?.accept,maxSize:t?.maxSize??26214400,multiple:false})??null},async pickFiles(t){if(!b())throw new Error("pickFiles is not supported in this host");let i=await r.request("synapse/pick-file",{accept:t?.accept,maxSize:t?.maxSize??26214400,multiple:true});return i?Array.isArray(i)?i:[i]:[]},_onMessage(t,i){return r.onMessage(t,i)},_request(t,i){return r.request(t,i)},destroy(){m||(m=true,p&&clearTimeout(p),g?.destroy(),f(),w(),T(),j(),h.clear(),y.clear(),R.clear(),r.destroy());}}}function N(s,e){let o=structuredClone(e.initialState),n=new Set,a=false,r=null,d={};for(let f of Object.keys(e.actions))d[f]=w=>{a||(o=e.actions[f](o,w),c());};function c(){for(let f of n)f(o);e.visibleToAgent&&m(),e.persist&&p();}function m(){let f=e.summarize?.(o);s.setVisibleState(o,f);}function p(){r&&clearTimeout(r),r=setTimeout(()=>{s._request("synapse/persist-state",{state:o,version:e.version}).catch(()=>{}),r=null;},500);}let g;e.persist&&(g=s._onMessage("synapse/state-loaded",f=>{if(!f?.state)return;let w=f.state,h=f.version??1,y=e.version??1;if(e.migrations&&h<y){let R=h-1;for(let T=R;T<e.migrations.length;T++)w=e.migrations[T](w);}v.hydrate(w);}));let v={getState(){return o},subscribe(f){return n.add(f),()=>{n.delete(f);}},dispatch:d,hydrate(f){o=f;for(let w of n)w(o);},destroy(){a||(a=true,r&&clearTimeout(r),n.clear(),g?.());}};return v}window.Synapse={connect:O,createSynapse:P,createStore:N};})();
|
|
@@ -68,6 +68,22 @@ interface ToolCallResult<T = unknown> {
|
|
|
68
68
|
data: T;
|
|
69
69
|
isError: boolean;
|
|
70
70
|
}
|
|
71
|
+
/** Result from a file picker request */
|
|
72
|
+
interface FileResult {
|
|
73
|
+
filename: string;
|
|
74
|
+
mimeType: string;
|
|
75
|
+
size: number;
|
|
76
|
+
base64Data: string;
|
|
77
|
+
}
|
|
78
|
+
/** Options for requesting a file from the user */
|
|
79
|
+
interface RequestFileOptions {
|
|
80
|
+
/** File type filter (e.g., ".csv,.json", "image/*") */
|
|
81
|
+
accept?: string;
|
|
82
|
+
/** Max file size in bytes. Default: 25 MB */
|
|
83
|
+
maxSize?: number;
|
|
84
|
+
/** Allow multiple file selection. Default: false */
|
|
85
|
+
multiple?: boolean;
|
|
86
|
+
}
|
|
71
87
|
interface Synapse {
|
|
72
88
|
readonly ready: Promise<void>;
|
|
73
89
|
readonly isNimbleBrainHost: boolean;
|
|
@@ -105,11 +121,23 @@ interface Synapse {
|
|
|
105
121
|
* Debounced at 250ms. Each call overwrites the previous context.
|
|
106
122
|
*/
|
|
107
123
|
setVisibleState(state: Record<string, unknown>, summary?: string): void;
|
|
108
|
-
|
|
124
|
+
saveFile(filename: string, content: string | Blob, mimeType?: string): void;
|
|
109
125
|
openLink(url: string): void;
|
|
110
|
-
/**
|
|
126
|
+
/**
|
|
127
|
+
* Request a file from the user via the host's native file picker.
|
|
128
|
+
* NimbleBrain-only: throws in non-NimbleBrain hosts.
|
|
129
|
+
* Returns null if the user cancels.
|
|
130
|
+
*/
|
|
131
|
+
pickFile(options?: RequestFileOptions): Promise<FileResult | null>;
|
|
132
|
+
/**
|
|
133
|
+
* Pick multiple files from the user.
|
|
134
|
+
* NimbleBrain-only: throws in non-NimbleBrain hosts.
|
|
135
|
+
* Returns empty array if the user cancels.
|
|
136
|
+
*/
|
|
137
|
+
pickFiles(options?: RequestFileOptions): Promise<FileResult[]>;
|
|
138
|
+
/** @internal — used by createStore for synapse/state-loaded */
|
|
111
139
|
_onMessage(method: string, callback: (params: Record<string, unknown> | undefined) => void): () => void;
|
|
112
|
-
/** @internal — used by createStore for
|
|
140
|
+
/** @internal — used by createStore for synapse/persist-state */
|
|
113
141
|
_request(method: string, params?: Record<string, unknown>): Promise<unknown>;
|
|
114
142
|
/** True after destroy() has been called. */
|
|
115
143
|
readonly destroyed: boolean;
|
|
@@ -161,5 +189,52 @@ interface HostInfo {
|
|
|
161
189
|
protocolVersion: string;
|
|
162
190
|
theme: SynapseTheme;
|
|
163
191
|
}
|
|
192
|
+
interface ConnectOptions {
|
|
193
|
+
name: string;
|
|
194
|
+
version: string;
|
|
195
|
+
autoResize?: boolean;
|
|
196
|
+
}
|
|
197
|
+
interface Theme {
|
|
198
|
+
mode: "light" | "dark";
|
|
199
|
+
tokens: Record<string, string>;
|
|
200
|
+
}
|
|
201
|
+
interface Dimensions {
|
|
202
|
+
width?: number;
|
|
203
|
+
height?: number;
|
|
204
|
+
maxWidth?: number;
|
|
205
|
+
maxHeight?: number;
|
|
206
|
+
}
|
|
207
|
+
interface ToolResultData {
|
|
208
|
+
content: unknown;
|
|
209
|
+
structuredContent: unknown;
|
|
210
|
+
raw: Record<string, unknown>;
|
|
211
|
+
}
|
|
212
|
+
/** Known short event names for App.on() */
|
|
213
|
+
type AppEventName = "tool-result" | "tool-input" | "tool-input-partial" | "tool-cancelled" | "theme-changed" | "teardown";
|
|
214
|
+
interface App {
|
|
215
|
+
readonly theme: Theme;
|
|
216
|
+
readonly hostInfo: {
|
|
217
|
+
name: string;
|
|
218
|
+
version: string;
|
|
219
|
+
};
|
|
220
|
+
readonly toolInfo: {
|
|
221
|
+
tool: Record<string, unknown>;
|
|
222
|
+
} | null;
|
|
223
|
+
readonly containerDimensions: Dimensions | null;
|
|
224
|
+
on(event: "tool-input", handler: (args: Record<string, unknown>) => void): () => void;
|
|
225
|
+
on(event: "tool-result", handler: (data: ToolResultData) => void): () => void;
|
|
226
|
+
on(event: "theme-changed", handler: (theme: Theme) => void): () => void;
|
|
227
|
+
on(event: "teardown", handler: () => void): () => void;
|
|
228
|
+
on(event: string, handler: (params: unknown) => void): () => void;
|
|
229
|
+
resize(width?: number, height?: number): void;
|
|
230
|
+
openLink(url: string): void;
|
|
231
|
+
updateModelContext(state: Record<string, unknown>, summary?: string): void;
|
|
232
|
+
callTool(name: string, args?: Record<string, unknown>): Promise<ToolCallResult>;
|
|
233
|
+
sendMessage(text: string, context?: {
|
|
234
|
+
action?: string;
|
|
235
|
+
entity?: string;
|
|
236
|
+
}): void;
|
|
237
|
+
destroy(): void;
|
|
238
|
+
}
|
|
164
239
|
|
|
165
|
-
export type {
|
|
240
|
+
export type { App as A, BuiltinActionType as B, ConnectOptions as C, DataChangedEvent as D, FileResult as F, HostInfo as H, KeyForwardConfig as K, NavigatePayload as N, RequestFileOptions as R, SynapseOptions as S, ToolDefinition as T, VisibleState as V, Synapse as a, ActionReducer as b, StoreConfig as c, Store as d, AgentAction as e, AppEventName as f, Dimensions as g, NotifyPayload as h, StateAcknowledgement as i, StoreDispatch as j, SynapseTheme as k, Theme as l, ToolCallResult as m, ToolResultData as n };
|