@nimblebrain/synapse 0.1.4 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -1
- package/dist/{chunk-JZC3VC2C.js → chunk-7KEYXJWD.js} +45 -13
- package/dist/chunk-7KEYXJWD.js.map +1 -0
- package/dist/{chunk-Q7OSHSGZ.cjs → chunk-Y4ZDNAYQ.cjs} +45 -13
- package/dist/chunk-Y4ZDNAYQ.cjs.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/index.cjs +2 -2
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/react/index.cjs +22 -11
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.d.cts +40 -2
- package/dist/react/index.d.ts +40 -2
- package/dist/react/index.js +22 -12
- package/dist/react/index.js.map +1 -1
- package/dist/{server-SEI7XI3B.cjs → server-3BDZ5S72.cjs} +26 -26
- package/dist/server-3BDZ5S72.cjs.map +1 -0
- package/dist/{server-RUCX2TIB.js → server-NNW54YW5.js} +26 -26
- package/dist/server-NNW54YW5.js.map +1 -0
- package/dist/synapse-runtime.iife.global.js +1 -1
- package/dist/types-DElq_otH.d.cts +165 -0
- package/dist/types-DElq_otH.d.ts +165 -0
- package/dist/vite/index.cjs +23 -13
- package/dist/vite/index.cjs.map +1 -1
- package/dist/vite/index.js +23 -13
- package/dist/vite/index.js.map +1 -1
- package/package.json +2 -2
- package/dist/chunk-JZC3VC2C.js.map +0 -1
- package/dist/chunk-Q7OSHSGZ.cjs.map +0 -1
- package/dist/server-RUCX2TIB.js.map +0 -1
- package/dist/server-SEI7XI3B.cjs.map +0 -1
- package/dist/types-BP0SNrpo.d.cts +0 -96
- package/dist/types-BP0SNrpo.d.ts +0 -96
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
interface SynapseOptions {
|
|
2
|
+
/** App name — must match the bundle name registered with the host */
|
|
3
|
+
name: string;
|
|
4
|
+
/** Semver version string */
|
|
5
|
+
version: string;
|
|
6
|
+
/**
|
|
7
|
+
* Mark as internal NimbleBrain app. Enables cross-server tool calls.
|
|
8
|
+
* External apps MUST NOT set this.
|
|
9
|
+
*/
|
|
10
|
+
internal?: boolean;
|
|
11
|
+
/** Key combos to forward from iframe to host. Default: all Ctrl/Cmd combos + Escape. */
|
|
12
|
+
forwardKeys?: KeyForwardConfig[];
|
|
13
|
+
}
|
|
14
|
+
interface SynapseTheme {
|
|
15
|
+
mode: "light" | "dark";
|
|
16
|
+
primaryColor: string;
|
|
17
|
+
tokens: Record<string, string>;
|
|
18
|
+
}
|
|
19
|
+
interface DataChangedEvent {
|
|
20
|
+
source: "agent";
|
|
21
|
+
server: string;
|
|
22
|
+
tool: string;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Built-in action types that Synapse handles natively.
|
|
26
|
+
*
|
|
27
|
+
* - `navigate` — select/focus a resource in the UI (e.g., a board, document, record)
|
|
28
|
+
* - `notify` — display a transient message (toast/banner)
|
|
29
|
+
* - `refresh` — force a full data refresh (heavier than datachanged)
|
|
30
|
+
* - `confirm` — request user confirmation before the agent proceeds
|
|
31
|
+
*
|
|
32
|
+
* Apps may also receive custom string types for domain-specific actions.
|
|
33
|
+
*/
|
|
34
|
+
type BuiltinActionType = "navigate" | "notify" | "refresh" | "confirm";
|
|
35
|
+
/**
|
|
36
|
+
* A typed, declarative action sent from the agent/server to the UI.
|
|
37
|
+
*
|
|
38
|
+
* Actions are deterministic side effects of tool execution — the tool decides
|
|
39
|
+
* what action to emit, not the LLM. The UI decides how to handle it.
|
|
40
|
+
*
|
|
41
|
+
* This mirrors Studio's ClientAction pattern, adapted for iframe postMessage.
|
|
42
|
+
*/
|
|
43
|
+
interface AgentAction<TPayload = Record<string, unknown>> {
|
|
44
|
+
/** Discriminator — a BuiltinActionType or custom string. */
|
|
45
|
+
type: BuiltinActionType | (string & {});
|
|
46
|
+
/** Typed payload — shape depends on `type`. */
|
|
47
|
+
payload: TPayload;
|
|
48
|
+
/** If true, the UI should confirm with the user before executing. */
|
|
49
|
+
requiresConfirmation?: boolean;
|
|
50
|
+
/** Human-readable label for confirmation dialogs or logs. */
|
|
51
|
+
label?: string;
|
|
52
|
+
}
|
|
53
|
+
/** Payload for the built-in "navigate" action. */
|
|
54
|
+
interface NavigatePayload {
|
|
55
|
+
/** Entity type (e.g., "board", "document", "task"). */
|
|
56
|
+
entity: string;
|
|
57
|
+
/** Entity ID to select/focus. */
|
|
58
|
+
id: string;
|
|
59
|
+
/** Optional sub-view or section within the entity. */
|
|
60
|
+
view?: string;
|
|
61
|
+
}
|
|
62
|
+
/** Payload for the built-in "notify" action. */
|
|
63
|
+
interface NotifyPayload {
|
|
64
|
+
message: string;
|
|
65
|
+
level?: "info" | "success" | "warning" | "error";
|
|
66
|
+
}
|
|
67
|
+
interface ToolCallResult<T = unknown> {
|
|
68
|
+
data: T;
|
|
69
|
+
isError: boolean;
|
|
70
|
+
}
|
|
71
|
+
interface Synapse {
|
|
72
|
+
readonly ready: Promise<void>;
|
|
73
|
+
readonly isNimbleBrainHost: boolean;
|
|
74
|
+
callTool<TInput = Record<string, unknown>, TOutput = unknown>(name: string, args?: TInput): Promise<ToolCallResult<TOutput>>;
|
|
75
|
+
onDataChanged(callback: (event: DataChangedEvent) => void): () => void;
|
|
76
|
+
/**
|
|
77
|
+
* Subscribe to agent actions — typed, declarative commands from the server.
|
|
78
|
+
*
|
|
79
|
+
* Actions are deterministic side effects of tool execution. The server/tool
|
|
80
|
+
* decides what action to emit; the UI decides how to handle it.
|
|
81
|
+
*
|
|
82
|
+
* The callback receives an AgentAction with a `type` discriminator and typed
|
|
83
|
+
* `payload`. Apps should handle known types and ignore unknown ones.
|
|
84
|
+
*/
|
|
85
|
+
onAction(callback: (action: AgentAction) => void): () => void;
|
|
86
|
+
getTheme(): SynapseTheme;
|
|
87
|
+
onThemeChanged(callback: (theme: SynapseTheme) => void): () => void;
|
|
88
|
+
/** NimbleBrain-only: trigger a host-side action. No-op in other hosts. */
|
|
89
|
+
action(action: string, params?: Record<string, unknown>): void;
|
|
90
|
+
/**
|
|
91
|
+
* Send a user message into the agent conversation (ext-apps `ui/message`).
|
|
92
|
+
*
|
|
93
|
+
* @param context NimbleBrain-specific metadata, included as `_meta.context`
|
|
94
|
+
* on the content block. Ignored by non-NimbleBrain hosts.
|
|
95
|
+
*/
|
|
96
|
+
chat(message: string, context?: {
|
|
97
|
+
action?: string;
|
|
98
|
+
entity?: string;
|
|
99
|
+
}): void;
|
|
100
|
+
/**
|
|
101
|
+
* Push the app's current visible state to the agent (ext-apps `ui/update-model-context`).
|
|
102
|
+
*
|
|
103
|
+
* The `summary` string is what the LLM reads as a text content block.
|
|
104
|
+
* The `state` object is included as `structuredContent` for tools that need IDs/values.
|
|
105
|
+
* Debounced at 250ms. Each call overwrites the previous context.
|
|
106
|
+
*/
|
|
107
|
+
setVisibleState(state: Record<string, unknown>, summary?: string): void;
|
|
108
|
+
downloadFile(filename: string, content: string | Blob, mimeType?: string): void;
|
|
109
|
+
openLink(url: string): void;
|
|
110
|
+
/** @internal — used by createStore for ui/stateLoaded */
|
|
111
|
+
_onMessage(method: string, callback: (params: Record<string, unknown> | undefined) => void): () => void;
|
|
112
|
+
/** @internal — used by createStore for ui/persistState */
|
|
113
|
+
_request(method: string, params?: Record<string, unknown>): Promise<unknown>;
|
|
114
|
+
/** True after destroy() has been called. */
|
|
115
|
+
readonly destroyed: boolean;
|
|
116
|
+
destroy(): void;
|
|
117
|
+
}
|
|
118
|
+
interface VisibleState {
|
|
119
|
+
state: Record<string, unknown>;
|
|
120
|
+
summary?: string;
|
|
121
|
+
}
|
|
122
|
+
interface StateAcknowledgement {
|
|
123
|
+
truncated: boolean;
|
|
124
|
+
}
|
|
125
|
+
type ActionReducer<TState, TPayload = unknown> = (state: TState, payload: TPayload) => TState;
|
|
126
|
+
interface StoreConfig<TState> {
|
|
127
|
+
initialState: TState;
|
|
128
|
+
actions: Record<string, ActionReducer<TState, any>>;
|
|
129
|
+
persist?: boolean;
|
|
130
|
+
visibleToAgent?: boolean;
|
|
131
|
+
summarize?: (state: TState) => string;
|
|
132
|
+
version?: number;
|
|
133
|
+
migrations?: Array<(oldState: any) => any>;
|
|
134
|
+
}
|
|
135
|
+
type StoreDispatch<TActions extends Record<string, ActionReducer<any, any>>> = {
|
|
136
|
+
[K in keyof TActions]: Parameters<TActions[K]>[1] extends undefined ? () => void : (payload: Parameters<TActions[K]>[1]) => void;
|
|
137
|
+
};
|
|
138
|
+
interface Store<TState, TActions extends Record<string, ActionReducer<TState, any>> = Record<string, ActionReducer<TState, any>>> {
|
|
139
|
+
getState(): TState;
|
|
140
|
+
subscribe(callback: (state: TState) => void): () => void;
|
|
141
|
+
dispatch: StoreDispatch<TActions>;
|
|
142
|
+
hydrate(state: TState): void;
|
|
143
|
+
destroy(): void;
|
|
144
|
+
}
|
|
145
|
+
interface KeyForwardConfig {
|
|
146
|
+
key: string;
|
|
147
|
+
ctrl?: boolean;
|
|
148
|
+
meta?: boolean;
|
|
149
|
+
shift?: boolean;
|
|
150
|
+
alt?: boolean;
|
|
151
|
+
}
|
|
152
|
+
interface ToolDefinition {
|
|
153
|
+
name: string;
|
|
154
|
+
description?: string;
|
|
155
|
+
inputSchema: Record<string, unknown>;
|
|
156
|
+
outputSchema?: Record<string, unknown>;
|
|
157
|
+
}
|
|
158
|
+
interface HostInfo {
|
|
159
|
+
isNimbleBrain: boolean;
|
|
160
|
+
serverName: string;
|
|
161
|
+
protocolVersion: string;
|
|
162
|
+
theme: SynapseTheme;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export type { ActionReducer as A, BuiltinActionType as B, DataChangedEvent as D, HostInfo as H, KeyForwardConfig as K, NavigatePayload as N, SynapseOptions as S, ToolCallResult as T, VisibleState as V, Synapse as a, StoreConfig as b, Store as c, AgentAction as d, NotifyPayload as e, StateAcknowledgement as f, StoreDispatch as g, SynapseTheme as h, ToolDefinition as i };
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
interface SynapseOptions {
|
|
2
|
+
/** App name — must match the bundle name registered with the host */
|
|
3
|
+
name: string;
|
|
4
|
+
/** Semver version string */
|
|
5
|
+
version: string;
|
|
6
|
+
/**
|
|
7
|
+
* Mark as internal NimbleBrain app. Enables cross-server tool calls.
|
|
8
|
+
* External apps MUST NOT set this.
|
|
9
|
+
*/
|
|
10
|
+
internal?: boolean;
|
|
11
|
+
/** Key combos to forward from iframe to host. Default: all Ctrl/Cmd combos + Escape. */
|
|
12
|
+
forwardKeys?: KeyForwardConfig[];
|
|
13
|
+
}
|
|
14
|
+
interface SynapseTheme {
|
|
15
|
+
mode: "light" | "dark";
|
|
16
|
+
primaryColor: string;
|
|
17
|
+
tokens: Record<string, string>;
|
|
18
|
+
}
|
|
19
|
+
interface DataChangedEvent {
|
|
20
|
+
source: "agent";
|
|
21
|
+
server: string;
|
|
22
|
+
tool: string;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Built-in action types that Synapse handles natively.
|
|
26
|
+
*
|
|
27
|
+
* - `navigate` — select/focus a resource in the UI (e.g., a board, document, record)
|
|
28
|
+
* - `notify` — display a transient message (toast/banner)
|
|
29
|
+
* - `refresh` — force a full data refresh (heavier than datachanged)
|
|
30
|
+
* - `confirm` — request user confirmation before the agent proceeds
|
|
31
|
+
*
|
|
32
|
+
* Apps may also receive custom string types for domain-specific actions.
|
|
33
|
+
*/
|
|
34
|
+
type BuiltinActionType = "navigate" | "notify" | "refresh" | "confirm";
|
|
35
|
+
/**
|
|
36
|
+
* A typed, declarative action sent from the agent/server to the UI.
|
|
37
|
+
*
|
|
38
|
+
* Actions are deterministic side effects of tool execution — the tool decides
|
|
39
|
+
* what action to emit, not the LLM. The UI decides how to handle it.
|
|
40
|
+
*
|
|
41
|
+
* This mirrors Studio's ClientAction pattern, adapted for iframe postMessage.
|
|
42
|
+
*/
|
|
43
|
+
interface AgentAction<TPayload = Record<string, unknown>> {
|
|
44
|
+
/** Discriminator — a BuiltinActionType or custom string. */
|
|
45
|
+
type: BuiltinActionType | (string & {});
|
|
46
|
+
/** Typed payload — shape depends on `type`. */
|
|
47
|
+
payload: TPayload;
|
|
48
|
+
/** If true, the UI should confirm with the user before executing. */
|
|
49
|
+
requiresConfirmation?: boolean;
|
|
50
|
+
/** Human-readable label for confirmation dialogs or logs. */
|
|
51
|
+
label?: string;
|
|
52
|
+
}
|
|
53
|
+
/** Payload for the built-in "navigate" action. */
|
|
54
|
+
interface NavigatePayload {
|
|
55
|
+
/** Entity type (e.g., "board", "document", "task"). */
|
|
56
|
+
entity: string;
|
|
57
|
+
/** Entity ID to select/focus. */
|
|
58
|
+
id: string;
|
|
59
|
+
/** Optional sub-view or section within the entity. */
|
|
60
|
+
view?: string;
|
|
61
|
+
}
|
|
62
|
+
/** Payload for the built-in "notify" action. */
|
|
63
|
+
interface NotifyPayload {
|
|
64
|
+
message: string;
|
|
65
|
+
level?: "info" | "success" | "warning" | "error";
|
|
66
|
+
}
|
|
67
|
+
interface ToolCallResult<T = unknown> {
|
|
68
|
+
data: T;
|
|
69
|
+
isError: boolean;
|
|
70
|
+
}
|
|
71
|
+
interface Synapse {
|
|
72
|
+
readonly ready: Promise<void>;
|
|
73
|
+
readonly isNimbleBrainHost: boolean;
|
|
74
|
+
callTool<TInput = Record<string, unknown>, TOutput = unknown>(name: string, args?: TInput): Promise<ToolCallResult<TOutput>>;
|
|
75
|
+
onDataChanged(callback: (event: DataChangedEvent) => void): () => void;
|
|
76
|
+
/**
|
|
77
|
+
* Subscribe to agent actions — typed, declarative commands from the server.
|
|
78
|
+
*
|
|
79
|
+
* Actions are deterministic side effects of tool execution. The server/tool
|
|
80
|
+
* decides what action to emit; the UI decides how to handle it.
|
|
81
|
+
*
|
|
82
|
+
* The callback receives an AgentAction with a `type` discriminator and typed
|
|
83
|
+
* `payload`. Apps should handle known types and ignore unknown ones.
|
|
84
|
+
*/
|
|
85
|
+
onAction(callback: (action: AgentAction) => void): () => void;
|
|
86
|
+
getTheme(): SynapseTheme;
|
|
87
|
+
onThemeChanged(callback: (theme: SynapseTheme) => void): () => void;
|
|
88
|
+
/** NimbleBrain-only: trigger a host-side action. No-op in other hosts. */
|
|
89
|
+
action(action: string, params?: Record<string, unknown>): void;
|
|
90
|
+
/**
|
|
91
|
+
* Send a user message into the agent conversation (ext-apps `ui/message`).
|
|
92
|
+
*
|
|
93
|
+
* @param context NimbleBrain-specific metadata, included as `_meta.context`
|
|
94
|
+
* on the content block. Ignored by non-NimbleBrain hosts.
|
|
95
|
+
*/
|
|
96
|
+
chat(message: string, context?: {
|
|
97
|
+
action?: string;
|
|
98
|
+
entity?: string;
|
|
99
|
+
}): void;
|
|
100
|
+
/**
|
|
101
|
+
* Push the app's current visible state to the agent (ext-apps `ui/update-model-context`).
|
|
102
|
+
*
|
|
103
|
+
* The `summary` string is what the LLM reads as a text content block.
|
|
104
|
+
* The `state` object is included as `structuredContent` for tools that need IDs/values.
|
|
105
|
+
* Debounced at 250ms. Each call overwrites the previous context.
|
|
106
|
+
*/
|
|
107
|
+
setVisibleState(state: Record<string, unknown>, summary?: string): void;
|
|
108
|
+
downloadFile(filename: string, content: string | Blob, mimeType?: string): void;
|
|
109
|
+
openLink(url: string): void;
|
|
110
|
+
/** @internal — used by createStore for ui/stateLoaded */
|
|
111
|
+
_onMessage(method: string, callback: (params: Record<string, unknown> | undefined) => void): () => void;
|
|
112
|
+
/** @internal — used by createStore for ui/persistState */
|
|
113
|
+
_request(method: string, params?: Record<string, unknown>): Promise<unknown>;
|
|
114
|
+
/** True after destroy() has been called. */
|
|
115
|
+
readonly destroyed: boolean;
|
|
116
|
+
destroy(): void;
|
|
117
|
+
}
|
|
118
|
+
interface VisibleState {
|
|
119
|
+
state: Record<string, unknown>;
|
|
120
|
+
summary?: string;
|
|
121
|
+
}
|
|
122
|
+
interface StateAcknowledgement {
|
|
123
|
+
truncated: boolean;
|
|
124
|
+
}
|
|
125
|
+
type ActionReducer<TState, TPayload = unknown> = (state: TState, payload: TPayload) => TState;
|
|
126
|
+
interface StoreConfig<TState> {
|
|
127
|
+
initialState: TState;
|
|
128
|
+
actions: Record<string, ActionReducer<TState, any>>;
|
|
129
|
+
persist?: boolean;
|
|
130
|
+
visibleToAgent?: boolean;
|
|
131
|
+
summarize?: (state: TState) => string;
|
|
132
|
+
version?: number;
|
|
133
|
+
migrations?: Array<(oldState: any) => any>;
|
|
134
|
+
}
|
|
135
|
+
type StoreDispatch<TActions extends Record<string, ActionReducer<any, any>>> = {
|
|
136
|
+
[K in keyof TActions]: Parameters<TActions[K]>[1] extends undefined ? () => void : (payload: Parameters<TActions[K]>[1]) => void;
|
|
137
|
+
};
|
|
138
|
+
interface Store<TState, TActions extends Record<string, ActionReducer<TState, any>> = Record<string, ActionReducer<TState, any>>> {
|
|
139
|
+
getState(): TState;
|
|
140
|
+
subscribe(callback: (state: TState) => void): () => void;
|
|
141
|
+
dispatch: StoreDispatch<TActions>;
|
|
142
|
+
hydrate(state: TState): void;
|
|
143
|
+
destroy(): void;
|
|
144
|
+
}
|
|
145
|
+
interface KeyForwardConfig {
|
|
146
|
+
key: string;
|
|
147
|
+
ctrl?: boolean;
|
|
148
|
+
meta?: boolean;
|
|
149
|
+
shift?: boolean;
|
|
150
|
+
alt?: boolean;
|
|
151
|
+
}
|
|
152
|
+
interface ToolDefinition {
|
|
153
|
+
name: string;
|
|
154
|
+
description?: string;
|
|
155
|
+
inputSchema: Record<string, unknown>;
|
|
156
|
+
outputSchema?: Record<string, unknown>;
|
|
157
|
+
}
|
|
158
|
+
interface HostInfo {
|
|
159
|
+
isNimbleBrain: boolean;
|
|
160
|
+
serverName: string;
|
|
161
|
+
protocolVersion: string;
|
|
162
|
+
theme: SynapseTheme;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export type { ActionReducer as A, BuiltinActionType as B, DataChangedEvent as D, HostInfo as H, KeyForwardConfig as K, NavigatePayload as N, SynapseOptions as S, ToolCallResult as T, VisibleState as V, Synapse as a, StoreConfig as b, Store as c, AgentAction as d, NotifyPayload as e, StateAcknowledgement as f, StoreDispatch as g, SynapseTheme as h, ToolDefinition as i };
|
package/dist/vite/index.cjs
CHANGED
|
@@ -201,7 +201,7 @@ function previewHostHtml(appName) {
|
|
|
201
201
|
<button id="toggle">Toggle Theme</button>
|
|
202
202
|
<span class="url">Synapse Preview</span>
|
|
203
203
|
</header>
|
|
204
|
-
<iframe id="app"
|
|
204
|
+
<iframe id="app"></iframe>
|
|
205
205
|
|
|
206
206
|
<script>
|
|
207
207
|
var iframe = document.getElementById("app");
|
|
@@ -209,19 +209,19 @@ function previewHostHtml(appName) {
|
|
|
209
209
|
|
|
210
210
|
function getTokens(d) {
|
|
211
211
|
return d ? {
|
|
212
|
-
"--
|
|
213
|
-
"--
|
|
214
|
-
"--
|
|
215
|
-
"--
|
|
216
|
-
"--
|
|
217
|
-
"--
|
|
212
|
+
"--color-background-primary":"#0f172a","--color-text-primary":"#e2e8f0",
|
|
213
|
+
"--color-background-secondary":"#1e293b","--color-text-primary":"#e2e8f0",
|
|
214
|
+
"--color-text-accent":"#6366f1","--nb-color-accent-foreground":"#fff",
|
|
215
|
+
"--color-text-secondary":"#94a3b8","--color-border-primary":"#334155",
|
|
216
|
+
"--color-ring-primary":"#6366f1","--nb-color-danger":"#ef4444",
|
|
217
|
+
"--border-radius-sm":"0.5rem","--font-sans":"-apple-system,BlinkMacSystemFont,sans-serif"
|
|
218
218
|
} : {
|
|
219
|
-
"--
|
|
220
|
-
"--
|
|
221
|
-
"--
|
|
222
|
-
"--
|
|
223
|
-
"--
|
|
224
|
-
"--
|
|
219
|
+
"--color-background-primary":"#ffffff","--color-text-primary":"#0f172a",
|
|
220
|
+
"--color-background-secondary":"#f8fafc","--color-text-primary":"#0f172a",
|
|
221
|
+
"--color-text-accent":"#6366f1","--nb-color-accent-foreground":"#fff",
|
|
222
|
+
"--color-text-secondary":"#64748b","--color-border-primary":"#e2e8f0",
|
|
223
|
+
"--color-ring-primary":"#6366f1","--nb-color-danger":"#ef4444",
|
|
224
|
+
"--border-radius-sm":"0.5rem","--font-sans":"-apple-system,BlinkMacSystemFont,sans-serif"
|
|
225
225
|
};
|
|
226
226
|
}
|
|
227
227
|
|
|
@@ -255,6 +255,12 @@ function previewHostHtml(appName) {
|
|
|
255
255
|
var response = await r.json();
|
|
256
256
|
response.id = originalId;
|
|
257
257
|
post(response);
|
|
258
|
+
// Notify the app that data changed so hooks auto-refresh
|
|
259
|
+
// Only for mutating operations (not list/search/get which are read-only)
|
|
260
|
+
var tn = msg.params.name || "";
|
|
261
|
+
if (!response.error && !tn.startsWith("list_") && !tn.startsWith("search_") && !tn.startsWith("get_") && !tn.startsWith("query_")) {
|
|
262
|
+
post({jsonrpc:"2.0",method:"ui/datachanged",params:{tool:tn,server:"preview"}});
|
|
263
|
+
}
|
|
258
264
|
} catch(err) {
|
|
259
265
|
post({jsonrpc:"2.0",id:originalId,error:{code:-32000,message:err.message}});
|
|
260
266
|
}
|
|
@@ -274,6 +280,10 @@ function previewHostHtml(appName) {
|
|
|
274
280
|
document.body.style.background = dark ? "#0f172a" : "#f1f5f9";
|
|
275
281
|
post({jsonrpc:"2.0",method:"ui/themeChanged",params:{mode:dark?"dark":"light",tokens:getTokens(dark)}});
|
|
276
282
|
};
|
|
283
|
+
|
|
284
|
+
// Load the iframe AFTER the message listener is attached to avoid
|
|
285
|
+
// a race where the app sends ui/initialize before the bridge is ready.
|
|
286
|
+
iframe.src = "/";
|
|
277
287
|
</script>
|
|
278
288
|
</body>
|
|
279
289
|
</html>`;
|
package/dist/vite/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/vite/plugin.ts"],"names":["resolve","existsSync","readFileSync","join","spawn"],"mappings":"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","file":"index.cjs","sourcesContent":["import { type ChildProcess, spawn } from \"node:child_process\";\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { join, resolve } from \"node:path\";\nimport type { Plugin, ViteDevServer } from \"vite\";\n\nexport interface SynapseVitePluginOptions {\n /** App name. If omitted, reads from ../manifest.json */\n appName?: string;\n /** Path to manifest.json. Default: ../manifest.json (relative to ui/) */\n manifest?: string;\n /**\n * Shell command to start the MCP server. If omitted, derived from manifest.\n * The server runs in stdio mode — stdin/stdout JSON-RPC.\n */\n serverCmd?: string;\n /** Set to false to disable the preview host page at /__preview */\n preview?: boolean;\n}\n\ninterface Manifest {\n name: string;\n version?: string;\n server?: {\n type?: string;\n entry_point?: string;\n mcp_config?: {\n command?: string;\n args?: string[];\n };\n };\n}\n\n/**\n * Synapse Vite plugin — full local dev experience for MCP apps.\n *\n * What it does:\n * - Reads ../manifest.json to get app name and server config\n * - Spawns the MCP server as a child process (stdio mode)\n * - Serves a preview host page at /__preview that iframes your app\n * - Proxies tool calls from the iframe through POST /__mcp to the server\n * - Handles the ext-apps handshake so Synapse hooks work\n * - HMR works inside the iframe — edit .tsx, see changes instantly\n *\n * Usage in vite.config.ts:\n * import { synapseVite } from \"@nimblebrain/synapse/vite\";\n * export default { plugins: [react(), viteSingleFile(), synapseVite()] };\n *\n * Then: cd ui && npm run dev && open http://localhost:5173/__preview\n */\nexport function synapseVite(options: SynapseVitePluginOptions = {}): Plugin {\n const enablePreview = options.preview !== false;\n let manifest: Manifest | null = null;\n let appName = options.appName ?? \"app\";\n let serverProcess: ChildProcess | null = null;\n const pendingRequests = new Map<\n string,\n { resolve: (v: unknown) => void; reject: (e: Error) => void }\n >();\n let serverBuffer = \"\";\n\n function loadManifest(root: string): Manifest | null {\n const manifestPath = options.manifest\n ? resolve(options.manifest)\n : resolve(root, \"..\", \"manifest.json\");\n\n if (!existsSync(manifestPath)) return null;\n try {\n return JSON.parse(readFileSync(manifestPath, \"utf-8\"));\n } catch {\n return null;\n }\n }\n\n function deriveServerCmd(m: Manifest, root: string): string | null {\n if (options.serverCmd) return options.serverCmd;\n const cfg = m.server?.mcp_config;\n if (!cfg?.command) return null;\n\n const serverDir = resolve(root, \"..\");\n let cmd = cfg.command;\n const args = cfg.args ?? [];\n\n // Python projects: use `uv run` if pyproject.toml exists\n if (cmd === \"python\" && existsSync(join(serverDir, \"pyproject.toml\"))) {\n cmd = \"uv run python\";\n }\n\n return `cd ${JSON.stringify(serverDir)} && ${cmd} ${args.join(\" \")}`;\n }\n\n function startServer(cmd: string): void {\n serverProcess = spawn(cmd, {\n shell: true,\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n });\n\n serverProcess.stderr?.on(\"data\", (d: Buffer) => {\n process.stderr.write(` [mcp] ${d}`);\n });\n\n serverProcess.stdout?.on(\"data\", (d: Buffer) => {\n serverBuffer += d.toString();\n // Parse line-delimited JSON-RPC responses\n const lines = serverBuffer.split(\"\\n\");\n serverBuffer = lines.pop() ?? \"\"; // keep incomplete line\n for (const line of lines) {\n if (!line.trim()) continue;\n try {\n const msg = JSON.parse(line);\n if (msg.id && pendingRequests.has(msg.id)) {\n const p = pendingRequests.get(msg.id);\n pendingRequests.delete(msg.id);\n p?.resolve(msg);\n }\n } catch {\n // Not JSON — log it\n process.stderr.write(` [mcp] ${line}\\n`);\n }\n }\n });\n\n serverProcess.on(\"exit\", (code) => {\n if (code !== null && code !== 0) {\n console.error(` [mcp] Server exited with code ${code}`);\n }\n serverProcess = null;\n });\n\n // Send initialize\n sendToServer({\n jsonrpc: \"2.0\",\n id: \"init-1\",\n method: \"initialize\",\n params: {\n protocolVersion: \"2024-11-05\",\n capabilities: {},\n clientInfo: { name: \"synapse-preview\", version: \"0.1.0\" },\n },\n });\n }\n\n function sendToServer(msg: Record<string, unknown>): void {\n if (!serverProcess?.stdin?.writable) return;\n serverProcess.stdin.write(`${JSON.stringify(msg)}\\n`);\n }\n\n function callServerTool(name: string, args: Record<string, unknown>): Promise<unknown> {\n return new Promise((resolve, reject) => {\n const id = `preview-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;\n pendingRequests.set(id, { resolve, reject });\n sendToServer({\n jsonrpc: \"2.0\",\n id,\n method: \"tools/call\",\n params: { name, arguments: args },\n });\n setTimeout(() => {\n if (pendingRequests.has(id)) {\n pendingRequests.delete(id);\n reject(new Error(\"Tool call timed out (10s)\"));\n }\n }, 10000);\n });\n }\n\n return {\n name: \"synapse\",\n\n config() {\n return {\n define: {\n \"import.meta.env.SYNAPSE_APP_NAME\": JSON.stringify(appName),\n },\n server: {\n hmr: {\n protocol: \"ws\",\n host: \"localhost\",\n },\n },\n };\n },\n\n configResolved(config) {\n manifest = loadManifest(config.root);\n if (manifest?.name) {\n appName = options.appName ?? manifest.name;\n }\n },\n\n configureServer(server: ViteDevServer) {\n // Start MCP server\n if (enablePreview && manifest) {\n const cmd = deriveServerCmd(manifest, server.config.root);\n if (cmd) {\n console.log(`\\n [synapse] Starting MCP server: ${cmd}\\n`);\n startServer(cmd);\n }\n }\n\n server.middlewares.use((req, res, next) => {\n // CORS for iframe communication\n res.setHeader(\"Access-Control-Allow-Origin\", \"*\");\n res.setHeader(\"Access-Control-Allow-Methods\", \"*\");\n res.setHeader(\"Access-Control-Allow-Headers\", \"*\");\n\n // /__preview — bridge host page\n if (req.url === \"/__preview\" || req.url === \"/__preview/\") {\n res.writeHead(200, { \"Content-Type\": \"text/html\" });\n res.end(previewHostHtml(appName));\n return;\n }\n\n // POST /__mcp — tool call proxy\n if (req.method === \"POST\" && req.url === \"/__mcp\") {\n let body = \"\";\n req.on(\"data\", (chunk: Buffer) => {\n body += chunk.toString();\n });\n req.on(\"end\", async () => {\n try {\n const msg = JSON.parse(body);\n const result = await callServerTool(msg.params.name, msg.params.arguments || {});\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(result));\n } catch (err) {\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(\n JSON.stringify({\n jsonrpc: \"2.0\",\n id: JSON.parse(body).id,\n error: { code: -32000, message: (err as Error).message },\n }),\n );\n }\n });\n return;\n }\n\n next();\n });\n },\n\n buildEnd() {\n // Kill server on build end (for production builds)\n if (serverProcess) {\n serverProcess.kill(\"SIGTERM\");\n serverProcess = null;\n }\n },\n };\n}\n\nfunction previewHostHtml(appName: string): string {\n return `<!DOCTYPE html>\n<html>\n<head>\n <meta charset=\"utf-8\" />\n <title>${appName} — Synapse Preview</title>\n <style>\n * { margin: 0; padding: 0; box-sizing: border-box; }\n body { font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", sans-serif; background: #0f172a; color: #e2e8f0; }\n header { padding: 10px 16px; background: #1e293b; border-bottom: 1px solid #334155; display: flex; align-items: center; gap: 10px; font-size: 13px; }\n header .dot { width: 8px; height: 8px; border-radius: 50%; background: #22c55e; }\n header .name { font-weight: 600; }\n header .spacer { flex: 1; }\n header button { background: #334155; border: none; color: #e2e8f0; padding: 3px 8px; border-radius: 4px; cursor: pointer; font-size: 12px; }\n header .url { color: #64748b; font-size: 11px; font-family: monospace; }\n iframe { width: 100%; height: calc(100vh - 41px); border: none; }\n </style>\n</head>\n<body>\n <header>\n <span class=\"dot\"></span>\n <span class=\"name\">${appName}</span>\n <span class=\"spacer\"></span>\n <button id=\"toggle\">Toggle Theme</button>\n <span class=\"url\">Synapse Preview</span>\n </header>\n <iframe id=\"app\" src=\"/\"></iframe>\n\n <script>\n var iframe = document.getElementById(\"app\");\n var dark = true;\n\n function getTokens(d) {\n return d ? {\n \"--nb-background\":\"#0f172a\",\"--nb-foreground\":\"#e2e8f0\",\n \"--nb-card\":\"#1e293b\",\"--nb-card-foreground\":\"#e2e8f0\",\n \"--nb-primary\":\"#6366f1\",\"--nb-primary-foreground\":\"#fff\",\n \"--nb-muted-foreground\":\"#94a3b8\",\"--nb-border\":\"#334155\",\n \"--nb-ring\":\"#6366f1\",\"--nb-destructive\":\"#ef4444\",\n \"--nb-radius\":\"0.5rem\",\"--nb-font-sans\":\"-apple-system,BlinkMacSystemFont,sans-serif\"\n } : {\n \"--nb-background\":\"#ffffff\",\"--nb-foreground\":\"#0f172a\",\n \"--nb-card\":\"#f8fafc\",\"--nb-card-foreground\":\"#0f172a\",\n \"--nb-primary\":\"#6366f1\",\"--nb-primary-foreground\":\"#fff\",\n \"--nb-muted-foreground\":\"#64748b\",\"--nb-border\":\"#e2e8f0\",\n \"--nb-ring\":\"#6366f1\",\"--nb-destructive\":\"#ef4444\",\n \"--nb-radius\":\"0.5rem\",\"--nb-font-sans\":\"-apple-system,BlinkMacSystemFont,sans-serif\"\n };\n }\n\n function post(msg) { iframe.contentWindow.postMessage(msg, \"*\"); }\n\n window.addEventListener(\"message\", async function(e) {\n if (e.source !== iframe.contentWindow) return;\n var msg = e.data;\n if (!msg || typeof msg !== \"object\") return;\n\n // ext-apps handshake\n if (msg.method === \"ui/initialize\" && msg.id) {\n post({ jsonrpc:\"2.0\", id:msg.id, result: {\n protocolVersion:\"2026-01-26\",\n serverInfo:{name:\"nimblebrain\",version:\"preview\"},\n capabilities:{openLinks:{},serverTools:{}},\n hostContext:{theme:dark?\"dark\":\"light\",primaryColor:\"#6366f1\",tokens:getTokens(dark)}\n }});\n return;\n }\n if (msg.method === \"ui/notifications/initialized\") return;\n\n // Tool calls — proxy via Vite middleware\n if (msg.method === \"tools/call\" && msg.id) {\n var originalId = msg.id;\n try {\n var r = await fetch(\"/__mcp\", {\n method:\"POST\", headers:{\"Content-Type\":\"application/json\"},\n body: JSON.stringify({jsonrpc:\"2.0\",id:msg.id,method:\"tools/call\",params:{name:msg.params.name,arguments:msg.params.arguments||{}}})\n });\n var response = await r.json();\n response.id = originalId;\n post(response);\n } catch(err) {\n post({jsonrpc:\"2.0\",id:originalId,error:{code:-32000,message:err.message}});\n }\n return;\n }\n\n // Log other messages\n if (msg.method === \"ui/chat\") console.log(\"[chat]\", msg.params?.message);\n else if (msg.method === \"ui/action\") console.log(\"[action]\", msg.params?.action, msg.params);\n else if (msg.method === \"ui/stateChanged\") { console.log(\"[state]\", msg.params?.state); post({jsonrpc:\"2.0\",method:\"ui/stateAcknowledged\",params:{truncated:false}}); }\n else if (msg.method === \"ui/keydown\") { /* ignore */ }\n else if (msg.method) console.log(\"[bridge]\", msg.method, msg);\n });\n\n document.getElementById(\"toggle\").onclick = function() {\n dark = !dark;\n document.body.style.background = dark ? \"#0f172a\" : \"#f1f5f9\";\n post({jsonrpc:\"2.0\",method:\"ui/themeChanged\",params:{mode:dark?\"dark\":\"light\",tokens:getTokens(dark)}});\n };\n </script>\n</body>\n</html>`;\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/vite/plugin.ts"],"names":["resolve","existsSync","readFileSync","join","spawn"],"mappings":"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","file":"index.cjs","sourcesContent":["import { type ChildProcess, spawn } from \"node:child_process\";\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { join, resolve } from \"node:path\";\nimport type { Plugin, ViteDevServer } from \"vite\";\n\nexport interface SynapseVitePluginOptions {\n /** App name. If omitted, reads from ../manifest.json */\n appName?: string;\n /** Path to manifest.json. Default: ../manifest.json (relative to ui/) */\n manifest?: string;\n /**\n * Shell command to start the MCP server. If omitted, derived from manifest.\n * The server runs in stdio mode — stdin/stdout JSON-RPC.\n */\n serverCmd?: string;\n /** Set to false to disable the preview host page at /__preview */\n preview?: boolean;\n}\n\ninterface Manifest {\n name: string;\n version?: string;\n server?: {\n type?: string;\n entry_point?: string;\n mcp_config?: {\n command?: string;\n args?: string[];\n };\n };\n}\n\n/**\n * Synapse Vite plugin — full local dev experience for MCP apps.\n *\n * What it does:\n * - Reads ../manifest.json to get app name and server config\n * - Spawns the MCP server as a child process (stdio mode)\n * - Serves a preview host page at /__preview that iframes your app\n * - Proxies tool calls from the iframe through POST /__mcp to the server\n * - Handles the ext-apps handshake so Synapse hooks work\n * - HMR works inside the iframe — edit .tsx, see changes instantly\n *\n * Usage in vite.config.ts:\n * import { synapseVite } from \"@nimblebrain/synapse/vite\";\n * export default { plugins: [react(), viteSingleFile(), synapseVite()] };\n *\n * Then: cd ui && npm run dev && open http://localhost:5173/__preview\n */\nexport function synapseVite(options: SynapseVitePluginOptions = {}): Plugin {\n const enablePreview = options.preview !== false;\n let manifest: Manifest | null = null;\n let appName = options.appName ?? \"app\";\n let serverProcess: ChildProcess | null = null;\n const pendingRequests = new Map<\n string,\n { resolve: (v: unknown) => void; reject: (e: Error) => void }\n >();\n let serverBuffer = \"\";\n\n function loadManifest(root: string): Manifest | null {\n const manifestPath = options.manifest\n ? resolve(options.manifest)\n : resolve(root, \"..\", \"manifest.json\");\n\n if (!existsSync(manifestPath)) return null;\n try {\n return JSON.parse(readFileSync(manifestPath, \"utf-8\"));\n } catch {\n return null;\n }\n }\n\n function deriveServerCmd(m: Manifest, root: string): string | null {\n if (options.serverCmd) return options.serverCmd;\n const cfg = m.server?.mcp_config;\n if (!cfg?.command) return null;\n\n const serverDir = resolve(root, \"..\");\n let cmd = cfg.command;\n const args = cfg.args ?? [];\n\n // Python projects: use `uv run` if pyproject.toml exists\n if (cmd === \"python\" && existsSync(join(serverDir, \"pyproject.toml\"))) {\n cmd = \"uv run python\";\n }\n\n return `cd ${JSON.stringify(serverDir)} && ${cmd} ${args.join(\" \")}`;\n }\n\n function startServer(cmd: string): void {\n serverProcess = spawn(cmd, {\n shell: true,\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n });\n\n serverProcess.stderr?.on(\"data\", (d: Buffer) => {\n process.stderr.write(` [mcp] ${d}`);\n });\n\n serverProcess.stdout?.on(\"data\", (d: Buffer) => {\n serverBuffer += d.toString();\n // Parse line-delimited JSON-RPC responses\n const lines = serverBuffer.split(\"\\n\");\n serverBuffer = lines.pop() ?? \"\"; // keep incomplete line\n for (const line of lines) {\n if (!line.trim()) continue;\n try {\n const msg = JSON.parse(line);\n if (msg.id && pendingRequests.has(msg.id)) {\n const p = pendingRequests.get(msg.id);\n pendingRequests.delete(msg.id);\n p?.resolve(msg);\n }\n } catch {\n // Not JSON — log it\n process.stderr.write(` [mcp] ${line}\\n`);\n }\n }\n });\n\n serverProcess.on(\"exit\", (code) => {\n if (code !== null && code !== 0) {\n console.error(` [mcp] Server exited with code ${code}`);\n }\n serverProcess = null;\n });\n\n // Send initialize\n sendToServer({\n jsonrpc: \"2.0\",\n id: \"init-1\",\n method: \"initialize\",\n params: {\n protocolVersion: \"2024-11-05\",\n capabilities: {},\n clientInfo: { name: \"synapse-preview\", version: \"0.1.0\" },\n },\n });\n }\n\n function sendToServer(msg: Record<string, unknown>): void {\n if (!serverProcess?.stdin?.writable) return;\n serverProcess.stdin.write(`${JSON.stringify(msg)}\\n`);\n }\n\n function callServerTool(name: string, args: Record<string, unknown>): Promise<unknown> {\n return new Promise((resolve, reject) => {\n const id = `preview-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;\n pendingRequests.set(id, { resolve, reject });\n sendToServer({\n jsonrpc: \"2.0\",\n id,\n method: \"tools/call\",\n params: { name, arguments: args },\n });\n setTimeout(() => {\n if (pendingRequests.has(id)) {\n pendingRequests.delete(id);\n reject(new Error(\"Tool call timed out (10s)\"));\n }\n }, 10000);\n });\n }\n\n return {\n name: \"synapse\",\n\n config() {\n return {\n define: {\n \"import.meta.env.SYNAPSE_APP_NAME\": JSON.stringify(appName),\n },\n server: {\n hmr: {\n protocol: \"ws\",\n host: \"localhost\",\n },\n },\n };\n },\n\n configResolved(config) {\n manifest = loadManifest(config.root);\n if (manifest?.name) {\n appName = options.appName ?? manifest.name;\n }\n },\n\n configureServer(server: ViteDevServer) {\n // Start MCP server\n if (enablePreview && manifest) {\n const cmd = deriveServerCmd(manifest, server.config.root);\n if (cmd) {\n console.log(`\\n [synapse] Starting MCP server: ${cmd}\\n`);\n startServer(cmd);\n }\n }\n\n server.middlewares.use((req, res, next) => {\n // CORS for iframe communication\n res.setHeader(\"Access-Control-Allow-Origin\", \"*\");\n res.setHeader(\"Access-Control-Allow-Methods\", \"*\");\n res.setHeader(\"Access-Control-Allow-Headers\", \"*\");\n\n // /__preview — bridge host page\n if (req.url === \"/__preview\" || req.url === \"/__preview/\") {\n res.writeHead(200, { \"Content-Type\": \"text/html\" });\n res.end(previewHostHtml(appName));\n return;\n }\n\n // POST /__mcp — tool call proxy\n if (req.method === \"POST\" && req.url === \"/__mcp\") {\n let body = \"\";\n req.on(\"data\", (chunk: Buffer) => {\n body += chunk.toString();\n });\n req.on(\"end\", async () => {\n try {\n const msg = JSON.parse(body);\n const result = await callServerTool(msg.params.name, msg.params.arguments || {});\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(result));\n } catch (err) {\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(\n JSON.stringify({\n jsonrpc: \"2.0\",\n id: JSON.parse(body).id,\n error: { code: -32000, message: (err as Error).message },\n }),\n );\n }\n });\n return;\n }\n\n next();\n });\n },\n\n buildEnd() {\n // Kill server on build end (for production builds)\n if (serverProcess) {\n serverProcess.kill(\"SIGTERM\");\n serverProcess = null;\n }\n },\n };\n}\n\nfunction previewHostHtml(appName: string): string {\n return `<!DOCTYPE html>\n<html>\n<head>\n <meta charset=\"utf-8\" />\n <title>${appName} — Synapse Preview</title>\n <style>\n * { margin: 0; padding: 0; box-sizing: border-box; }\n body { font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", sans-serif; background: #0f172a; color: #e2e8f0; }\n header { padding: 10px 16px; background: #1e293b; border-bottom: 1px solid #334155; display: flex; align-items: center; gap: 10px; font-size: 13px; }\n header .dot { width: 8px; height: 8px; border-radius: 50%; background: #22c55e; }\n header .name { font-weight: 600; }\n header .spacer { flex: 1; }\n header button { background: #334155; border: none; color: #e2e8f0; padding: 3px 8px; border-radius: 4px; cursor: pointer; font-size: 12px; }\n header .url { color: #64748b; font-size: 11px; font-family: monospace; }\n iframe { width: 100%; height: calc(100vh - 41px); border: none; }\n </style>\n</head>\n<body>\n <header>\n <span class=\"dot\"></span>\n <span class=\"name\">${appName}</span>\n <span class=\"spacer\"></span>\n <button id=\"toggle\">Toggle Theme</button>\n <span class=\"url\">Synapse Preview</span>\n </header>\n <iframe id=\"app\"></iframe>\n\n <script>\n var iframe = document.getElementById(\"app\");\n var dark = true;\n\n function getTokens(d) {\n return d ? {\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\":\"#fff\",\n \"--color-text-secondary\":\"#94a3b8\",\"--color-border-primary\":\"#334155\",\n \"--color-ring-primary\":\"#6366f1\",\"--nb-color-danger\":\"#ef4444\",\n \"--border-radius-sm\":\"0.5rem\",\"--font-sans\":\"-apple-system,BlinkMacSystemFont,sans-serif\"\n } : {\n \"--color-background-primary\":\"#ffffff\",\"--color-text-primary\":\"#0f172a\",\n \"--color-background-secondary\":\"#f8fafc\",\"--color-text-primary\":\"#0f172a\",\n \"--color-text-accent\":\"#6366f1\",\"--nb-color-accent-foreground\":\"#fff\",\n \"--color-text-secondary\":\"#64748b\",\"--color-border-primary\":\"#e2e8f0\",\n \"--color-ring-primary\":\"#6366f1\",\"--nb-color-danger\":\"#ef4444\",\n \"--border-radius-sm\":\"0.5rem\",\"--font-sans\":\"-apple-system,BlinkMacSystemFont,sans-serif\"\n };\n }\n\n function post(msg) { iframe.contentWindow.postMessage(msg, \"*\"); }\n\n window.addEventListener(\"message\", async function(e) {\n if (e.source !== iframe.contentWindow) return;\n var msg = e.data;\n if (!msg || typeof msg !== \"object\") return;\n\n // ext-apps handshake\n if (msg.method === \"ui/initialize\" && msg.id) {\n post({ jsonrpc:\"2.0\", id:msg.id, result: {\n protocolVersion:\"2026-01-26\",\n serverInfo:{name:\"nimblebrain\",version:\"preview\"},\n capabilities:{openLinks:{},serverTools:{}},\n hostContext:{theme:dark?\"dark\":\"light\",primaryColor:\"#6366f1\",tokens:getTokens(dark)}\n }});\n return;\n }\n if (msg.method === \"ui/notifications/initialized\") return;\n\n // Tool calls — proxy via Vite middleware\n if (msg.method === \"tools/call\" && msg.id) {\n var originalId = msg.id;\n try {\n var r = await fetch(\"/__mcp\", {\n method:\"POST\", headers:{\"Content-Type\":\"application/json\"},\n body: JSON.stringify({jsonrpc:\"2.0\",id:msg.id,method:\"tools/call\",params:{name:msg.params.name,arguments:msg.params.arguments||{}}})\n });\n var response = await r.json();\n response.id = originalId;\n post(response);\n // Notify the app that data changed so hooks auto-refresh\n // Only for mutating operations (not list/search/get which are read-only)\n var tn = msg.params.name || \"\";\n if (!response.error && !tn.startsWith(\"list_\") && !tn.startsWith(\"search_\") && !tn.startsWith(\"get_\") && !tn.startsWith(\"query_\")) {\n post({jsonrpc:\"2.0\",method:\"ui/datachanged\",params:{tool:tn,server:\"preview\"}});\n }\n } catch(err) {\n post({jsonrpc:\"2.0\",id:originalId,error:{code:-32000,message:err.message}});\n }\n return;\n }\n\n // Log other messages\n if (msg.method === \"ui/chat\") console.log(\"[chat]\", msg.params?.message);\n else if (msg.method === \"ui/action\") console.log(\"[action]\", msg.params?.action, msg.params);\n else if (msg.method === \"ui/stateChanged\") { console.log(\"[state]\", msg.params?.state); post({jsonrpc:\"2.0\",method:\"ui/stateAcknowledged\",params:{truncated:false}}); }\n else if (msg.method === \"ui/keydown\") { /* ignore */ }\n else if (msg.method) console.log(\"[bridge]\", msg.method, msg);\n });\n\n document.getElementById(\"toggle\").onclick = function() {\n dark = !dark;\n document.body.style.background = dark ? \"#0f172a\" : \"#f1f5f9\";\n post({jsonrpc:\"2.0\",method:\"ui/themeChanged\",params:{mode:dark?\"dark\":\"light\",tokens:getTokens(dark)}});\n };\n\n // Load the iframe AFTER the message listener is attached to avoid\n // a race where the app sends ui/initialize before the bridge is ready.\n iframe.src = \"/\";\n </script>\n</body>\n</html>`;\n}\n"]}
|
package/dist/vite/index.js
CHANGED
|
@@ -199,7 +199,7 @@ function previewHostHtml(appName) {
|
|
|
199
199
|
<button id="toggle">Toggle Theme</button>
|
|
200
200
|
<span class="url">Synapse Preview</span>
|
|
201
201
|
</header>
|
|
202
|
-
<iframe id="app"
|
|
202
|
+
<iframe id="app"></iframe>
|
|
203
203
|
|
|
204
204
|
<script>
|
|
205
205
|
var iframe = document.getElementById("app");
|
|
@@ -207,19 +207,19 @@ function previewHostHtml(appName) {
|
|
|
207
207
|
|
|
208
208
|
function getTokens(d) {
|
|
209
209
|
return d ? {
|
|
210
|
-
"--
|
|
211
|
-
"--
|
|
212
|
-
"--
|
|
213
|
-
"--
|
|
214
|
-
"--
|
|
215
|
-
"--
|
|
210
|
+
"--color-background-primary":"#0f172a","--color-text-primary":"#e2e8f0",
|
|
211
|
+
"--color-background-secondary":"#1e293b","--color-text-primary":"#e2e8f0",
|
|
212
|
+
"--color-text-accent":"#6366f1","--nb-color-accent-foreground":"#fff",
|
|
213
|
+
"--color-text-secondary":"#94a3b8","--color-border-primary":"#334155",
|
|
214
|
+
"--color-ring-primary":"#6366f1","--nb-color-danger":"#ef4444",
|
|
215
|
+
"--border-radius-sm":"0.5rem","--font-sans":"-apple-system,BlinkMacSystemFont,sans-serif"
|
|
216
216
|
} : {
|
|
217
|
-
"--
|
|
218
|
-
"--
|
|
219
|
-
"--
|
|
220
|
-
"--
|
|
221
|
-
"--
|
|
222
|
-
"--
|
|
217
|
+
"--color-background-primary":"#ffffff","--color-text-primary":"#0f172a",
|
|
218
|
+
"--color-background-secondary":"#f8fafc","--color-text-primary":"#0f172a",
|
|
219
|
+
"--color-text-accent":"#6366f1","--nb-color-accent-foreground":"#fff",
|
|
220
|
+
"--color-text-secondary":"#64748b","--color-border-primary":"#e2e8f0",
|
|
221
|
+
"--color-ring-primary":"#6366f1","--nb-color-danger":"#ef4444",
|
|
222
|
+
"--border-radius-sm":"0.5rem","--font-sans":"-apple-system,BlinkMacSystemFont,sans-serif"
|
|
223
223
|
};
|
|
224
224
|
}
|
|
225
225
|
|
|
@@ -253,6 +253,12 @@ function previewHostHtml(appName) {
|
|
|
253
253
|
var response = await r.json();
|
|
254
254
|
response.id = originalId;
|
|
255
255
|
post(response);
|
|
256
|
+
// Notify the app that data changed so hooks auto-refresh
|
|
257
|
+
// Only for mutating operations (not list/search/get which are read-only)
|
|
258
|
+
var tn = msg.params.name || "";
|
|
259
|
+
if (!response.error && !tn.startsWith("list_") && !tn.startsWith("search_") && !tn.startsWith("get_") && !tn.startsWith("query_")) {
|
|
260
|
+
post({jsonrpc:"2.0",method:"ui/datachanged",params:{tool:tn,server:"preview"}});
|
|
261
|
+
}
|
|
256
262
|
} catch(err) {
|
|
257
263
|
post({jsonrpc:"2.0",id:originalId,error:{code:-32000,message:err.message}});
|
|
258
264
|
}
|
|
@@ -272,6 +278,10 @@ function previewHostHtml(appName) {
|
|
|
272
278
|
document.body.style.background = dark ? "#0f172a" : "#f1f5f9";
|
|
273
279
|
post({jsonrpc:"2.0",method:"ui/themeChanged",params:{mode:dark?"dark":"light",tokens:getTokens(dark)}});
|
|
274
280
|
};
|
|
281
|
+
|
|
282
|
+
// Load the iframe AFTER the message listener is attached to avoid
|
|
283
|
+
// a race where the app sends ui/initialize before the bridge is ready.
|
|
284
|
+
iframe.src = "/";
|
|
275
285
|
</script>
|
|
276
286
|
</body>
|
|
277
287
|
</html>`;
|