mountly-mcp 0.0.0 → 3.1.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/LICENSE +201 -0
- package/README.md +258 -20
- package/dist/artifact/index.d.ts +49 -0
- package/dist/artifact/index.js +21 -0
- package/dist/artifact/index.js.map +1 -0
- package/dist/bridge/iframe-entry.d.ts +1 -0
- package/dist/bridge/iframe-entry.js +251 -209
- package/dist/bridge/index.d.ts +17 -12
- package/dist/bridge/index.js +2 -2
- package/dist/build/emit-html.d.ts +30 -0
- package/dist/build/emit-meta.d.ts +17 -0
- package/dist/build/index.d.ts +32 -47
- package/dist/build/index.js +9 -135
- package/dist/build/index.js.map +1 -1
- package/dist/chunk-33DMFKKD.js +111 -0
- package/dist/chunk-33DMFKKD.js.map +1 -0
- package/dist/chunk-4HC3OWAQ.js +185 -0
- package/dist/chunk-4HC3OWAQ.js.map +1 -0
- package/dist/chunk-4WAKOYMK.js +196 -0
- package/dist/chunk-4WAKOYMK.js.map +1 -0
- package/dist/chunk-5EHNJCMF.js +58 -0
- package/dist/chunk-5EHNJCMF.js.map +1 -0
- package/dist/chunk-6QHKKFO3.js +30 -0
- package/dist/chunk-6QHKKFO3.js.map +1 -0
- package/dist/chunk-DKK26TRS.js +199 -0
- package/dist/chunk-DKK26TRS.js.map +1 -0
- package/dist/{chunk-A64ZILAS.js → chunk-DQLQOMDM.js} +2 -1
- package/dist/{chunk-A64ZILAS.js.map → chunk-DQLQOMDM.js.map} +1 -1
- package/dist/chunk-KRAS266U.js +260 -0
- package/dist/chunk-KRAS266U.js.map +1 -0
- package/dist/chunk-NLQDVNZP.js +115 -0
- package/dist/chunk-NLQDVNZP.js.map +1 -0
- package/dist/chunk-SNWNF2XI.js +171 -0
- package/dist/chunk-SNWNF2XI.js.map +1 -0
- package/dist/chunk-UECZA34Q.js +52 -0
- package/dist/chunk-UECZA34Q.js.map +1 -0
- package/dist/chunk-YJA4H6IB.js +13 -0
- package/dist/chunk-YJA4H6IB.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +286 -0
- package/dist/cli.js.map +1 -0
- package/dist/connect-server-7L4HGKKL.js +7 -0
- package/dist/connect-server-7L4HGKKL.js.map +1 -0
- package/dist/dev/connect-server.d.ts +13 -0
- package/dist/dev/host-entry.d.ts +13 -0
- package/dist/dev/host-entry.js +18330 -0
- package/dist/dev/index.d.ts +45 -0
- package/dist/dev/index.js +14 -0
- package/dist/dev/index.js.map +1 -0
- package/dist/dev/sandbox-entry.d.ts +7 -0
- package/dist/dev/sandbox-entry.js +80 -0
- package/dist/html.d.ts +4 -0
- package/dist/index.d.ts +6 -26
- package/dist/index.js +8 -3
- package/dist/json-render/app.d.ts +22 -0
- package/dist/json-render/app.js +10 -0
- package/dist/json-render/app.js.map +1 -0
- package/dist/json-render/index.d.ts +10 -0
- package/dist/json-render/index.js +119 -0
- package/dist/json-render/index.js.map +1 -0
- package/dist/json-render/mcp.d.ts +49 -0
- package/dist/json-render/mcp.js +11 -0
- package/dist/json-render/mcp.js.map +1 -0
- package/dist/json-render/server.d.ts +72 -0
- package/dist/json-render/server.js +98 -0
- package/dist/json-render/server.js.map +1 -0
- package/dist/json-render/spec-stream.d.ts +26 -0
- package/dist/json-render/use-spec-stream.d.ts +26 -0
- package/dist/json-render/widget.d.ts +59 -0
- package/dist/react/context.d.ts +14 -0
- package/dist/react/create-widget.d.ts +11 -0
- package/dist/react/hooks.d.ts +46 -0
- package/dist/react/index.d.ts +3 -0
- package/dist/react/index.js +35 -0
- package/dist/react/index.js.map +1 -0
- package/dist/sandbox/index.d.ts +16 -0
- package/dist/sandbox/index.js +8 -0
- package/dist/sandbox/index.js.map +1 -0
- package/dist/schema.d.ts +20 -0
- package/dist/server/index.d.ts +73 -0
- package/dist/server/index.js +235 -0
- package/dist/server/index.js.map +1 -0
- package/dist/svelte/index.d.ts +35 -0
- package/dist/svelte/index.js +9 -0
- package/dist/svelte/index.js.map +1 -0
- package/dist/testing/index.d.ts +27 -0
- package/dist/testing/index.js +312 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/testing/render-artifact.d.ts +35 -0
- package/dist/types.d.ts +55 -0
- package/dist/vite/index.d.ts +23 -0
- package/dist/vite/index.js +211 -0
- package/dist/vite/index.js.map +1 -0
- package/dist/vue/index.d.ts +56 -0
- package/dist/vue/index.js +89 -0
- package/dist/vue/index.js.map +1 -0
- package/package.json +166 -20
- package/dist/chunk-BUGGLMNA.js +0 -132
- package/dist/chunk-BUGGLMNA.js.map +0 -1
- package/dist/types-B9JAWm4W.d.ts +0 -62
package/dist/bridge/index.d.ts
CHANGED
|
@@ -1,8 +1,3 @@
|
|
|
1
|
-
import { App } from '@modelcontextprotocol/ext-apps';
|
|
2
|
-
export { App, McpUiAppCapabilities, McpUiHostCapabilities, McpUiHostContext, McpUiHostContextChangedNotification, McpUiToolCancelledNotification, McpUiToolInputNotification, McpUiToolResultNotification, PostMessageTransport } from '@modelcontextprotocol/ext-apps';
|
|
3
|
-
import { WidgetModule } from 'mountly/adapter';
|
|
4
|
-
import { D as DisplayMode } from '../types-B9JAWm4W.js';
|
|
5
|
-
|
|
6
1
|
/**
|
|
7
2
|
* View-side bridge that turns an MCP Apps `App` into a mountly `WidgetModule`
|
|
8
3
|
* lifecycle. The full spec handshake — `ui/initialize`,
|
|
@@ -11,8 +6,9 @@ import { D as DisplayMode } from '../types-B9JAWm4W.js';
|
|
|
11
6
|
* `ui/notifications/host-context-changed`, `ui/resource-teardown`,
|
|
12
7
|
* `ui/notifications/size-changed` — is delegated to ext-apps's `App`.
|
|
13
8
|
*/
|
|
14
|
-
|
|
15
|
-
|
|
9
|
+
import { App, PostMessageTransport, type McpUiDisplayMode } from "@modelcontextprotocol/ext-apps";
|
|
10
|
+
import type { WidgetModule } from "mountly/adapter";
|
|
11
|
+
export interface RunBridgeOptions {
|
|
16
12
|
/** Pre-constructed App (used by jsdom tests); otherwise one is created here. */
|
|
17
13
|
app?: App;
|
|
18
14
|
widget: WidgetModule;
|
|
@@ -26,8 +22,17 @@ interface RunBridgeOptions {
|
|
|
26
22
|
* only — for "stateless" views that drive themselves via `mcp.callServerTool`.
|
|
27
23
|
*/
|
|
28
24
|
awaitToolResult?: boolean;
|
|
25
|
+
/**
|
|
26
|
+
* Render progressive states from `ui/notifications/tool-input-partial`.
|
|
27
|
+
*
|
|
28
|
+
* Off by default — the spec lets views ignore partials, and they carry
|
|
29
|
+
* best-effort recovered JSON that MUST NOT drive critical operations. When
|
|
30
|
+
* on, partials arrive as `toolInputPartial` (mounting the widget early if
|
|
31
|
+
* needed) and are cleared once the complete `tool-input` lands.
|
|
32
|
+
*/
|
|
33
|
+
streamToolInput?: boolean;
|
|
29
34
|
/** Display modes this view supports. Declared to the host during `ui/initialize`. */
|
|
30
|
-
availableDisplayModes?: ReadonlyArray<
|
|
35
|
+
availableDisplayModes?: ReadonlyArray<McpUiDisplayMode>;
|
|
31
36
|
/** App identity sent during `ui/initialize`. */
|
|
32
37
|
appInfo?: {
|
|
33
38
|
name: string;
|
|
@@ -36,7 +41,7 @@ interface RunBridgeOptions {
|
|
|
36
41
|
/** Override for tests; defaults to `window.addEventListener('beforeunload', ...)`. */
|
|
37
42
|
onTeardown?: (handler: () => void) => () => void;
|
|
38
43
|
}
|
|
39
|
-
interface RunningBridge {
|
|
44
|
+
export interface RunningBridge {
|
|
40
45
|
/** Resolves once `ui/initialize` handshake completes and the first mount (or deferred mount) is wired. */
|
|
41
46
|
ready: Promise<void>;
|
|
42
47
|
/** The underlying App; exposed for hosts that want to drive the view directly. */
|
|
@@ -44,6 +49,6 @@ interface RunningBridge {
|
|
|
44
49
|
/** Synchronously triggers teardown. */
|
|
45
50
|
stop(): void;
|
|
46
51
|
}
|
|
47
|
-
declare function runBridge(options: RunBridgeOptions): RunningBridge;
|
|
48
|
-
|
|
49
|
-
export {
|
|
52
|
+
export declare function runBridge(options: RunBridgeOptions): RunningBridge;
|
|
53
|
+
export { App, PostMessageTransport };
|
|
54
|
+
export type { McpUiAppCapabilities, McpUiHostCapabilities, McpUiHostContext, McpUiToolInputNotification, McpUiToolInputPartialNotification, McpUiToolResultNotification, McpUiToolCancelledNotification, McpUiHostContextChangedNotification, } from "@modelcontextprotocol/ext-apps";
|
package/dist/bridge/index.js
CHANGED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { McpUiDisplayMode } from "@modelcontextprotocol/ext-apps";
|
|
2
|
+
/**
|
|
3
|
+
* Runtime config the emitted HTML hands to the inlined bridge
|
|
4
|
+
* (`bridge/iframe-entry.ts` reads these off `globalThis`). Without it the view
|
|
5
|
+
* falls back to `awaitToolResult: true` / `availableDisplayModes: ["inline"]`,
|
|
6
|
+
* regardless of what the build declared in the sidecar.
|
|
7
|
+
*/
|
|
8
|
+
export interface EmitHtmlConfig {
|
|
9
|
+
awaitToolResult?: boolean;
|
|
10
|
+
displayModes?: ReadonlyArray<McpUiDisplayMode>;
|
|
11
|
+
streamToolInput?: boolean;
|
|
12
|
+
}
|
|
13
|
+
export interface EmitHtmlSelfContainedInput {
|
|
14
|
+
mode: "self-contained";
|
|
15
|
+
uri: string;
|
|
16
|
+
js: string;
|
|
17
|
+
css: string;
|
|
18
|
+
bridgeRuntimeJs: string;
|
|
19
|
+
config?: EmitHtmlConfig;
|
|
20
|
+
}
|
|
21
|
+
export interface EmitHtmlCdnInput {
|
|
22
|
+
mode: "cdn";
|
|
23
|
+
uri: string;
|
|
24
|
+
jsUrl: string;
|
|
25
|
+
cssUrl: string;
|
|
26
|
+
bridgeRuntimeJs: string;
|
|
27
|
+
config?: EmitHtmlConfig;
|
|
28
|
+
}
|
|
29
|
+
export type EmitHtmlInput = EmitHtmlSelfContainedInput | EmitHtmlCdnInput;
|
|
30
|
+
export declare function emitHtml(input: EmitHtmlInput): string;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { McpUiDisplayMode, McpUiResourceCsp, McpUiResourcePermissions } from "@modelcontextprotocol/ext-apps";
|
|
2
|
+
import type { McpResourceDeclaration } from "../types.js";
|
|
3
|
+
export interface EmitMetaInput {
|
|
4
|
+
uri: string;
|
|
5
|
+
name: string;
|
|
6
|
+
description?: string;
|
|
7
|
+
awaitToolResult?: boolean;
|
|
8
|
+
displayModes?: ReadonlyArray<McpUiDisplayMode>;
|
|
9
|
+
csp?: McpUiResourceCsp;
|
|
10
|
+
/** Sandbox permissions requested by the view (camera/microphone/geolocation/clipboardWrite). */
|
|
11
|
+
permissions?: McpUiResourcePermissions;
|
|
12
|
+
/** Dedicated sandbox origin requested by the view (host-dependent format). */
|
|
13
|
+
domain?: string;
|
|
14
|
+
/** Whether the view requests the host to show a visible border + background. */
|
|
15
|
+
prefersBorder?: boolean;
|
|
16
|
+
}
|
|
17
|
+
export declare function emitMeta(input: EmitMetaInput): McpResourceDeclaration;
|
package/dist/build/index.d.ts
CHANGED
|
@@ -1,39 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
interface EmitHtmlSelfContainedInput {
|
|
5
|
-
mode: "self-contained";
|
|
6
|
-
uri: string;
|
|
7
|
-
js: string;
|
|
8
|
-
css: string;
|
|
9
|
-
bridgeRuntimeJs: string;
|
|
10
|
-
}
|
|
11
|
-
interface EmitHtmlCdnInput {
|
|
12
|
-
mode: "cdn";
|
|
13
|
-
uri: string;
|
|
14
|
-
jsUrl: string;
|
|
15
|
-
cssUrl: string;
|
|
16
|
-
bridgeRuntimeJs: string;
|
|
17
|
-
}
|
|
18
|
-
type EmitHtmlInput = EmitHtmlSelfContainedInput | EmitHtmlCdnInput;
|
|
19
|
-
declare function emitHtml(input: EmitHtmlInput): string;
|
|
20
|
-
|
|
21
|
-
interface EmitMetaInput {
|
|
22
|
-
uri: string;
|
|
23
|
-
name: string;
|
|
24
|
-
description?: string;
|
|
25
|
-
awaitToolResult?: boolean;
|
|
26
|
-
displayModes?: ReadonlyArray<DisplayMode>;
|
|
27
|
-
csp?: McpCsp;
|
|
28
|
-
/** Sandbox permissions requested by the view (camera/microphone/geolocation/clipboardWrite). */
|
|
29
|
-
permissions?: McpResourcePermissions;
|
|
30
|
-
/** Dedicated sandbox origin requested by the view (host-dependent format). */
|
|
31
|
-
domain?: string;
|
|
32
|
-
/** Whether the view requests the host to show a visible border + background. */
|
|
33
|
-
prefersBorder?: boolean;
|
|
34
|
-
}
|
|
35
|
-
declare function emitMeta(input: EmitMetaInput): McpResourceDeclaration;
|
|
36
|
-
|
|
1
|
+
import type { McpUiDisplayMode, McpUiResourceCsp, McpUiResourcePermissions } from "@modelcontextprotocol/ext-apps";
|
|
2
|
+
import type { McpResourceDeclaration } from "../types.js";
|
|
3
|
+
import type { McpAppArtifact } from "../artifact/index.js";
|
|
37
4
|
/**
|
|
38
5
|
* Absolute path to the iframe bridge runtime shipped with this package.
|
|
39
6
|
*
|
|
@@ -45,18 +12,23 @@ declare function emitMeta(input: EmitMetaInput): McpResourceDeclaration;
|
|
|
45
12
|
* Resolves relative to the installed package, so it works identically in
|
|
46
13
|
* monorepos and in projects that installed mountly-mcp from npm.
|
|
47
14
|
*/
|
|
48
|
-
declare function getBridgeRuntimePath(): string;
|
|
49
|
-
interface BuildSelfContainedOptions {
|
|
15
|
+
export declare function getBridgeRuntimePath(): string;
|
|
16
|
+
export interface BuildSelfContainedOptions {
|
|
50
17
|
entry: string;
|
|
51
18
|
uri: string;
|
|
52
19
|
name: string;
|
|
53
20
|
output: string;
|
|
54
21
|
description?: string;
|
|
55
22
|
awaitToolResult?: boolean;
|
|
56
|
-
displayModes?: ReadonlyArray<
|
|
57
|
-
|
|
23
|
+
displayModes?: ReadonlyArray<McpUiDisplayMode>;
|
|
24
|
+
/**
|
|
25
|
+
* Render progressive states from `ui/notifications/tool-input-partial`.
|
|
26
|
+
* Off by default — see `RunBridgeOptions.streamToolInput`.
|
|
27
|
+
*/
|
|
28
|
+
streamToolInput?: boolean;
|
|
29
|
+
csp?: McpUiResourceCsp;
|
|
58
30
|
/** Sandbox permissions requested by the view (camera/microphone/geolocation/clipboardWrite). */
|
|
59
|
-
permissions?:
|
|
31
|
+
permissions?: McpUiResourcePermissions;
|
|
60
32
|
/** Dedicated sandbox origin (host-dependent format). */
|
|
61
33
|
domain?: string;
|
|
62
34
|
/** Whether the view requests the host to show a visible border + background. */
|
|
@@ -64,18 +36,31 @@ interface BuildSelfContainedOptions {
|
|
|
64
36
|
cssEntry?: string;
|
|
65
37
|
bridgeRuntimePath?: string;
|
|
66
38
|
}
|
|
67
|
-
interface BuildCdnOptions extends BuildSelfContainedOptions {
|
|
39
|
+
export interface BuildCdnOptions extends BuildSelfContainedOptions {
|
|
68
40
|
cdn: {
|
|
69
41
|
jsUrl: string;
|
|
70
42
|
cssUrl: string;
|
|
71
43
|
};
|
|
72
44
|
}
|
|
73
|
-
type BuildOptions = BuildSelfContainedOptions | BuildCdnOptions;
|
|
74
|
-
interface BuildResult {
|
|
45
|
+
export type BuildOptions = BuildSelfContainedOptions | BuildCdnOptions;
|
|
46
|
+
export interface BuildResult {
|
|
75
47
|
htmlPath: string;
|
|
76
48
|
metaPath: string;
|
|
77
49
|
declaration: McpResourceDeclaration;
|
|
50
|
+
artifact: McpAppArtifact;
|
|
78
51
|
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
52
|
+
export interface BuildFromSourceOptions extends Omit<BuildSelfContainedOptions, "entry" | "cssEntry"> {
|
|
53
|
+
/** The bundled widget JS. */
|
|
54
|
+
js: string;
|
|
55
|
+
/** The widget's CSS, if it has any. */
|
|
56
|
+
css?: string;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Same as {@link buildMcpResource}, but taking the bundle in memory instead of
|
|
60
|
+
* from disk — for bundler plugins that already hold the emitted sources and
|
|
61
|
+
* shouldn't have to guess when they land on the filesystem.
|
|
62
|
+
*/
|
|
63
|
+
export declare function buildMcpResourceFromSource(options: BuildFromSourceOptions): Promise<BuildResult>;
|
|
64
|
+
export declare function buildMcpResource(options: BuildOptions): Promise<BuildResult>;
|
|
65
|
+
export { emitHtml } from "./emit-html.js";
|
|
66
|
+
export { emitMeta } from "./emit-meta.js";
|
package/dist/build/index.js
CHANGED
|
@@ -1,141 +1,15 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
import
|
|
9
|
-
import
|
|
10
|
-
|
|
11
|
-
// src/build/emit-html.ts
|
|
12
|
-
function escapeAttr(value) {
|
|
13
|
-
return value.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<").replace(/>/g, ">");
|
|
14
|
-
}
|
|
15
|
-
function escapeScript(value) {
|
|
16
|
-
return value.replace(/<\/script/gi, "<\\/script");
|
|
17
|
-
}
|
|
18
|
-
function escapeStyle(value) {
|
|
19
|
-
return value.replace(/<\/style/gi, "<\\/style");
|
|
20
|
-
}
|
|
21
|
-
function emitHtml(input) {
|
|
22
|
-
const head = [
|
|
23
|
-
`<meta charset="utf-8">`,
|
|
24
|
-
`<meta name="mountly-mcp-protocol" content="${LATEST_PROTOCOL_VERSION}">`,
|
|
25
|
-
`<meta name="mountly-mcp-uri" content="${escapeAttr(input.uri)}">`
|
|
26
|
-
];
|
|
27
|
-
if (input.mode === "self-contained") {
|
|
28
|
-
head.push(`<style>${escapeStyle(input.css)}</style>`);
|
|
29
|
-
const body2 = [
|
|
30
|
-
`<div id="mountly-mcp-root"></div>`,
|
|
31
|
-
`<script type="module">${escapeScript(input.js)}
|
|
32
|
-
${escapeScript(input.bridgeRuntimeJs)}</script>`
|
|
33
|
-
];
|
|
34
|
-
return template(head, body2);
|
|
35
|
-
}
|
|
36
|
-
head.push(`<link rel="stylesheet" href="${escapeAttr(input.cssUrl)}">`);
|
|
37
|
-
const body = [
|
|
38
|
-
`<div id="mountly-mcp-root"></div>`,
|
|
39
|
-
`<script type="module">${escapeScript(input.bridgeRuntimeJs)}</script>`,
|
|
40
|
-
`<script type="module" src="${escapeAttr(input.jsUrl)}"></script>`
|
|
41
|
-
];
|
|
42
|
-
return template(head, body);
|
|
43
|
-
}
|
|
44
|
-
function template(head, body) {
|
|
45
|
-
return `<!doctype html>
|
|
46
|
-
<html data-mountly-mcp>
|
|
47
|
-
<head>
|
|
48
|
-
${head.join("\n")}
|
|
49
|
-
</head>
|
|
50
|
-
<body>
|
|
51
|
-
${body.join("\n")}
|
|
52
|
-
</body>
|
|
53
|
-
</html>`;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// src/build/emit-meta.ts
|
|
57
|
-
function assertUiUri(uri) {
|
|
58
|
-
if (!uri.startsWith("ui://")) {
|
|
59
|
-
throw new Error(
|
|
60
|
-
`mountly-mcp: UI resource uri must use the 'ui://' scheme (received '${uri}')`
|
|
61
|
-
);
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
function emitMeta(input) {
|
|
65
|
-
assertUiUri(input.uri);
|
|
66
|
-
const ui = {};
|
|
67
|
-
if (input.csp) ui.csp = input.csp;
|
|
68
|
-
if (input.permissions) ui.permissions = input.permissions;
|
|
69
|
-
if (input.domain !== void 0) ui.domain = input.domain;
|
|
70
|
-
if (input.prefersBorder !== void 0) ui.prefersBorder = input.prefersBorder;
|
|
71
|
-
const decl = {
|
|
72
|
-
protocolVersion: LATEST_PROTOCOL_VERSION,
|
|
73
|
-
uri: input.uri,
|
|
74
|
-
name: input.name,
|
|
75
|
-
mimeType: RESOURCE_MIME_TYPE,
|
|
76
|
-
awaitToolResult: input.awaitToolResult ?? true,
|
|
77
|
-
displayModes: input.displayModes ?? ["inline"],
|
|
78
|
-
_meta: { ui }
|
|
79
|
-
};
|
|
80
|
-
if (input.description !== void 0) decl.description = input.description;
|
|
81
|
-
return decl;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// src/build/index.ts
|
|
85
|
-
var __dirname = dirname(fileURLToPath(import.meta.url));
|
|
86
|
-
function getBridgeRuntimePath() {
|
|
87
|
-
return resolve(__dirname, "../bridge/iframe-entry.js");
|
|
88
|
-
}
|
|
89
|
-
function defaultBridgeRuntimePath() {
|
|
90
|
-
return getBridgeRuntimePath();
|
|
91
|
-
}
|
|
92
|
-
async function buildMcpResource(options) {
|
|
93
|
-
const bridgeRuntimeJs = await readFile(
|
|
94
|
-
options.bridgeRuntimePath ?? defaultBridgeRuntimePath(),
|
|
95
|
-
"utf8"
|
|
96
|
-
);
|
|
97
|
-
const declaration = emitMeta({
|
|
98
|
-
uri: options.uri,
|
|
99
|
-
name: options.name,
|
|
100
|
-
description: options.description,
|
|
101
|
-
awaitToolResult: options.awaitToolResult,
|
|
102
|
-
displayModes: options.displayModes,
|
|
103
|
-
csp: "cdn" in options ? mergeCdnIntoCsp(options.csp, options.cdn) : options.csp,
|
|
104
|
-
permissions: options.permissions,
|
|
105
|
-
domain: options.domain,
|
|
106
|
-
prefersBorder: options.prefersBorder
|
|
107
|
-
});
|
|
108
|
-
let html;
|
|
109
|
-
if ("cdn" in options) {
|
|
110
|
-
html = emitHtml({
|
|
111
|
-
mode: "cdn",
|
|
112
|
-
uri: options.uri,
|
|
113
|
-
jsUrl: options.cdn.jsUrl,
|
|
114
|
-
cssUrl: options.cdn.cssUrl,
|
|
115
|
-
bridgeRuntimeJs
|
|
116
|
-
});
|
|
117
|
-
} else {
|
|
118
|
-
const js = await readFile(options.entry, "utf8");
|
|
119
|
-
const css = options.cssEntry ? await readFile(options.cssEntry, "utf8") : "";
|
|
120
|
-
html = emitHtml({ mode: "self-contained", uri: options.uri, js, css, bridgeRuntimeJs });
|
|
121
|
-
}
|
|
122
|
-
const metaPath = `${options.output}.meta.json`;
|
|
123
|
-
await writeFile(options.output, html, "utf8");
|
|
124
|
-
await writeFile(metaPath, JSON.stringify(declaration, null, 2), "utf8");
|
|
125
|
-
return { htmlPath: options.output, metaPath, declaration };
|
|
126
|
-
}
|
|
127
|
-
function mergeCdnIntoCsp(csp, cdn) {
|
|
128
|
-
const jsOrigin = new URL(cdn.jsUrl).origin;
|
|
129
|
-
const cssOrigin = new URL(cdn.cssUrl).origin;
|
|
130
|
-
const merged = { ...csp ?? {} };
|
|
131
|
-
const existing = new Set(merged.resourceDomains ?? []);
|
|
132
|
-
existing.add(jsOrigin);
|
|
133
|
-
existing.add(cssOrigin);
|
|
134
|
-
merged.resourceDomains = Array.from(existing);
|
|
135
|
-
return merged;
|
|
136
|
-
}
|
|
2
|
+
buildMcpResource,
|
|
3
|
+
buildMcpResourceFromSource,
|
|
4
|
+
emitHtml,
|
|
5
|
+
emitMeta,
|
|
6
|
+
getBridgeRuntimePath
|
|
7
|
+
} from "../chunk-4WAKOYMK.js";
|
|
8
|
+
import "../chunk-DQLQOMDM.js";
|
|
9
|
+
import "../chunk-YJA4H6IB.js";
|
|
137
10
|
export {
|
|
138
11
|
buildMcpResource,
|
|
12
|
+
buildMcpResourceFromSource,
|
|
139
13
|
emitHtml,
|
|
140
14
|
emitMeta,
|
|
141
15
|
getBridgeRuntimePath
|
package/dist/build/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/build/index.ts","../../src/build/emit-html.ts","../../src/build/emit-meta.ts"],"sourcesContent":["import { readFile, writeFile } from \"node:fs/promises\";\nimport { dirname, resolve } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport type {\n DisplayMode,\n McpCsp,\n McpResourceDeclaration,\n McpResourcePermissions,\n} from \"../types.js\";\nimport { emitHtml } from \"./emit-html.js\";\nimport { emitMeta } from \"./emit-meta.js\";\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\n\n/**\n * Absolute path to the iframe bridge runtime shipped with this package.\n *\n * `buildMcpResource` inlines this file's contents into the emitted HTML so\n * the widget can speak the MCP Apps postMessage protocol. Use this when you\n * need to point another build tool at the runtime, or when you want a\n * stable, version-pinned alternative to passing `bridgeRuntimePath` yourself.\n *\n * Resolves relative to the installed package, so it works identically in\n * monorepos and in projects that installed mountly-mcp from npm.\n */\nexport function getBridgeRuntimePath(): string {\n return resolve(__dirname, \"../bridge/iframe-entry.js\");\n}\n\nfunction defaultBridgeRuntimePath(): string {\n return getBridgeRuntimePath();\n}\n\nexport interface BuildSelfContainedOptions {\n entry: string;\n uri: string;\n name: string;\n output: string;\n description?: string;\n awaitToolResult?: boolean;\n displayModes?: ReadonlyArray<DisplayMode>;\n csp?: McpCsp;\n /** Sandbox permissions requested by the view (camera/microphone/geolocation/clipboardWrite). */\n permissions?: McpResourcePermissions;\n /** Dedicated sandbox origin (host-dependent format). */\n domain?: string;\n /** Whether the view requests the host to show a visible border + background. */\n prefersBorder?: boolean;\n cssEntry?: string;\n bridgeRuntimePath?: string;\n}\n\nexport interface BuildCdnOptions extends BuildSelfContainedOptions {\n cdn: { jsUrl: string; cssUrl: string };\n}\n\nexport type BuildOptions = BuildSelfContainedOptions | BuildCdnOptions;\n\nexport interface BuildResult {\n htmlPath: string;\n metaPath: string;\n declaration: McpResourceDeclaration;\n}\n\nexport async function buildMcpResource(options: BuildOptions): Promise<BuildResult> {\n const bridgeRuntimeJs = await readFile(\n options.bridgeRuntimePath ?? defaultBridgeRuntimePath(),\n \"utf8\",\n );\n\n const declaration = emitMeta({\n uri: options.uri,\n name: options.name,\n description: options.description,\n awaitToolResult: options.awaitToolResult,\n displayModes: options.displayModes,\n csp: \"cdn\" in options ? mergeCdnIntoCsp(options.csp, options.cdn) : options.csp,\n permissions: options.permissions,\n domain: options.domain,\n prefersBorder: options.prefersBorder,\n });\n\n let html: string;\n if (\"cdn\" in options) {\n html = emitHtml({\n mode: \"cdn\",\n uri: options.uri,\n jsUrl: options.cdn.jsUrl,\n cssUrl: options.cdn.cssUrl,\n bridgeRuntimeJs,\n });\n } else {\n const js = await readFile(options.entry, \"utf8\");\n const css = options.cssEntry ? await readFile(options.cssEntry, \"utf8\") : \"\";\n html = emitHtml({ mode: \"self-contained\", uri: options.uri, js, css, bridgeRuntimeJs });\n }\n\n const metaPath = `${options.output}.meta.json`;\n await writeFile(options.output, html, \"utf8\");\n await writeFile(metaPath, JSON.stringify(declaration, null, 2), \"utf8\");\n\n return { htmlPath: options.output, metaPath, declaration };\n}\n\nfunction mergeCdnIntoCsp(\n csp: McpCsp | undefined,\n cdn: { jsUrl: string; cssUrl: string },\n): McpCsp {\n const jsOrigin = new URL(cdn.jsUrl).origin;\n const cssOrigin = new URL(cdn.cssUrl).origin;\n const merged: McpCsp = { ...(csp ?? {}) };\n const existing = new Set(merged.resourceDomains ?? []);\n existing.add(jsOrigin);\n existing.add(cssOrigin);\n merged.resourceDomains = Array.from(existing);\n return merged;\n}\n\nexport { emitHtml } from \"./emit-html.js\";\nexport { emitMeta } from \"./emit-meta.js\";\n","import { MCP_APPS_PROTOCOL_VERSION } from \"../schema.js\";\n\nexport interface EmitHtmlSelfContainedInput {\n mode: \"self-contained\";\n uri: string;\n js: string;\n css: string;\n bridgeRuntimeJs: string;\n}\n\nexport interface EmitHtmlCdnInput {\n mode: \"cdn\";\n uri: string;\n jsUrl: string;\n cssUrl: string;\n bridgeRuntimeJs: string;\n}\n\nexport type EmitHtmlInput = EmitHtmlSelfContainedInput | EmitHtmlCdnInput;\n\nfunction escapeAttr(value: string): string {\n return value\n .replace(/&/g, \"&\")\n .replace(/\"/g, \""\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\");\n}\n\nfunction escapeScript(value: string): string {\n return value.replace(/<\\/script/gi, \"<\\\\/script\");\n}\n\nfunction escapeStyle(value: string): string {\n return value.replace(/<\\/style/gi, \"<\\\\/style\");\n}\n\nexport function emitHtml(input: EmitHtmlInput): string {\n const head = [\n `<meta charset=\"utf-8\">`,\n `<meta name=\"mountly-mcp-protocol\" content=\"${MCP_APPS_PROTOCOL_VERSION}\">`,\n `<meta name=\"mountly-mcp-uri\" content=\"${escapeAttr(input.uri)}\">`,\n ];\n\n if (input.mode === \"self-contained\") {\n head.push(`<style>${escapeStyle(input.css)}</style>`);\n const body = [\n `<div id=\"mountly-mcp-root\"></div>`,\n `<script type=\"module\">${escapeScript(input.js)}\\n${escapeScript(input.bridgeRuntimeJs)}</script>`,\n ];\n return template(head, body);\n }\n\n head.push(`<link rel=\"stylesheet\" href=\"${escapeAttr(input.cssUrl)}\">`);\n const body = [\n `<div id=\"mountly-mcp-root\"></div>`,\n `<script type=\"module\">${escapeScript(input.bridgeRuntimeJs)}</script>`,\n `<script type=\"module\" src=\"${escapeAttr(input.jsUrl)}\"></script>`,\n ];\n return template(head, body);\n}\n\nfunction template(head: string[], body: string[]): string {\n return `<!doctype html>\n<html data-mountly-mcp>\n<head>\n${head.join(\"\\n\")}\n</head>\n<body>\n${body.join(\"\\n\")}\n</body>\n</html>`;\n}\n","import { MCP_APPS_MIME, MCP_APPS_PROTOCOL_VERSION } from \"../schema.js\";\nimport type {\n DisplayMode,\n McpCsp,\n McpResourceDeclaration,\n McpResourcePermissions,\n} from \"../types.js\";\n\nexport interface EmitMetaInput {\n uri: string;\n name: string;\n description?: string;\n awaitToolResult?: boolean;\n displayModes?: ReadonlyArray<DisplayMode>;\n csp?: McpCsp;\n /** Sandbox permissions requested by the view (camera/microphone/geolocation/clipboardWrite). */\n permissions?: McpResourcePermissions;\n /** Dedicated sandbox origin requested by the view (host-dependent format). */\n domain?: string;\n /** Whether the view requests the host to show a visible border + background. */\n prefersBorder?: boolean;\n}\n\nfunction assertUiUri(uri: string): void {\n if (!uri.startsWith(\"ui://\")) {\n throw new Error(\n `mountly-mcp: UI resource uri must use the 'ui://' scheme (received '${uri}')`,\n );\n }\n}\n\nexport function emitMeta(input: EmitMetaInput): McpResourceDeclaration {\n assertUiUri(input.uri);\n\n const ui: McpResourceDeclaration[\"_meta\"][\"ui\"] = {};\n if (input.csp) ui.csp = input.csp;\n if (input.permissions) ui.permissions = input.permissions;\n if (input.domain !== undefined) ui.domain = input.domain;\n if (input.prefersBorder !== undefined) ui.prefersBorder = input.prefersBorder;\n\n const decl: McpResourceDeclaration = {\n protocolVersion: MCP_APPS_PROTOCOL_VERSION,\n uri: input.uri,\n name: input.name,\n mimeType: MCP_APPS_MIME,\n awaitToolResult: input.awaitToolResult ?? true,\n displayModes: input.displayModes ?? [\"inline\"],\n _meta: { ui },\n };\n if (input.description !== undefined) decl.description = input.description;\n return decl;\n}\n"],"mappings":"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","names":["body"]}
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import {
|
|
2
|
+
escapeInlineScript
|
|
3
|
+
} from "./chunk-YJA4H6IB.js";
|
|
4
|
+
|
|
5
|
+
// src/json-render/app.ts
|
|
6
|
+
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
7
|
+
import { App } from "@modelcontextprotocol/ext-apps";
|
|
8
|
+
function isSpec(value) {
|
|
9
|
+
return !!value && typeof value === "object" && "root" in value;
|
|
10
|
+
}
|
|
11
|
+
function pickSpec(payload) {
|
|
12
|
+
const structured = payload?.structuredContent;
|
|
13
|
+
if (isSpec(structured)) return structured;
|
|
14
|
+
const nested = structured?.spec;
|
|
15
|
+
if (nested) return nested;
|
|
16
|
+
for (const part of payload?.content ?? []) {
|
|
17
|
+
if (part?.type !== "text" || !part.text) continue;
|
|
18
|
+
try {
|
|
19
|
+
const parsed = JSON.parse(part.text);
|
|
20
|
+
if (isSpec(parsed)) return parsed;
|
|
21
|
+
const wrapped = parsed?.spec;
|
|
22
|
+
if (isSpec(wrapped)) return wrapped;
|
|
23
|
+
} catch {
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
function useJsonRenderApp(options) {
|
|
29
|
+
const [spec, setSpec] = useState(null);
|
|
30
|
+
const [loading, setLoading] = useState(true);
|
|
31
|
+
const [connected, setConnected] = useState(false);
|
|
32
|
+
const [connecting, setConnecting] = useState(true);
|
|
33
|
+
const [error, setError] = useState(null);
|
|
34
|
+
const [app, setApp] = useState(null);
|
|
35
|
+
useEffect(() => {
|
|
36
|
+
const current = new App({ name: options.name, version: options.version }, {});
|
|
37
|
+
setApp(current);
|
|
38
|
+
setConnecting(true);
|
|
39
|
+
setConnected(false);
|
|
40
|
+
setError(null);
|
|
41
|
+
const onToolResult = (params) => {
|
|
42
|
+
const next = pickSpec(params);
|
|
43
|
+
if (next) setSpec(next);
|
|
44
|
+
setLoading(false);
|
|
45
|
+
};
|
|
46
|
+
current.addEventListener("toolresult", onToolResult);
|
|
47
|
+
void current.connect().then(() => {
|
|
48
|
+
setConnected(true);
|
|
49
|
+
}).catch((err) => {
|
|
50
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
51
|
+
}).finally(() => {
|
|
52
|
+
setConnecting(false);
|
|
53
|
+
});
|
|
54
|
+
return () => {
|
|
55
|
+
current.removeEventListener("toolresult", onToolResult);
|
|
56
|
+
void current.close().catch(() => void 0);
|
|
57
|
+
};
|
|
58
|
+
}, [options.name, options.version]);
|
|
59
|
+
const callServerTool = useCallback(
|
|
60
|
+
async (name, args) => {
|
|
61
|
+
if (!app) throw new Error("json-render app is not connected yet");
|
|
62
|
+
setLoading(true);
|
|
63
|
+
const result = await app.callServerTool({
|
|
64
|
+
name,
|
|
65
|
+
arguments: args ?? {}
|
|
66
|
+
});
|
|
67
|
+
const next = pickSpec(result);
|
|
68
|
+
if (next) setSpec(next);
|
|
69
|
+
setLoading(false);
|
|
70
|
+
},
|
|
71
|
+
[app]
|
|
72
|
+
);
|
|
73
|
+
return useMemo(
|
|
74
|
+
() => ({
|
|
75
|
+
spec,
|
|
76
|
+
loading,
|
|
77
|
+
connected,
|
|
78
|
+
connecting,
|
|
79
|
+
error,
|
|
80
|
+
app,
|
|
81
|
+
callServerTool
|
|
82
|
+
}),
|
|
83
|
+
[spec, loading, connected, connecting, error, app, callServerTool]
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
function buildAppHtml(options) {
|
|
87
|
+
const title = options.title ?? "json-render MCP App";
|
|
88
|
+
const css = options.css ?? "";
|
|
89
|
+
const js = escapeInlineScript(options.js);
|
|
90
|
+
return [
|
|
91
|
+
"<!doctype html>",
|
|
92
|
+
"<html>",
|
|
93
|
+
"<head>",
|
|
94
|
+
' <meta charset="utf-8" />',
|
|
95
|
+
' <meta name="viewport" content="width=device-width, initial-scale=1" />',
|
|
96
|
+
` <title>${title}</title>`,
|
|
97
|
+
css ? ` <style>${css}</style>` : "",
|
|
98
|
+
"</head>",
|
|
99
|
+
"<body>",
|
|
100
|
+
' <div id="root"></div>',
|
|
101
|
+
` <script type="module">${js}</script>`,
|
|
102
|
+
"</body>",
|
|
103
|
+
"</html>"
|
|
104
|
+
].filter(Boolean).join("\n");
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export {
|
|
108
|
+
useJsonRenderApp,
|
|
109
|
+
buildAppHtml
|
|
110
|
+
};
|
|
111
|
+
//# sourceMappingURL=chunk-33DMFKKD.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/json-render/app.ts"],"sourcesContent":["import { useCallback, useEffect, useMemo, useState } from \"react\";\nimport { App } from \"@modelcontextprotocol/ext-apps\";\nimport type { Spec } from \"@json-render/core\";\nimport { escapeInlineScript } from \"../html.js\";\n\nexport interface UseJsonRenderAppOptions {\n name: string;\n version: string;\n}\n\nexport interface UseJsonRenderAppReturn {\n spec: Spec | null;\n loading: boolean;\n connected: boolean;\n connecting: boolean;\n error: Error | null;\n app: App | null;\n callServerTool: (name: string, args?: Record<string, unknown>) => Promise<void>;\n}\n\ntype ToolResultPayload = {\n structuredContent?: { spec?: Spec } | Spec;\n content?: ReadonlyArray<{ type?: string; text?: string }>;\n};\n\nfunction isSpec(value: unknown): value is Spec {\n return !!value && typeof value === \"object\" && \"root\" in (value as Record<string, unknown>);\n}\n\n/**\n * Read the spec from a tool result in either wire format: Mountly's\n * `structuredContent`, or the JSON-in-`content[0].text` that\n * `@json-render/mcp` servers emit. Supporting both means this hook renders\n * a server built with either package.\n */\nfunction pickSpec(payload: ToolResultPayload | undefined): Spec | null {\n const structured = payload?.structuredContent;\n if (isSpec(structured)) return structured;\n const nested = (structured as { spec?: Spec } | undefined)?.spec;\n if (nested) return nested;\n\n for (const part of payload?.content ?? []) {\n if (part?.type !== \"text\" || !part.text) continue;\n try {\n const parsed: unknown = JSON.parse(part.text);\n if (isSpec(parsed)) return parsed;\n const wrapped = (parsed as { spec?: unknown } | null)?.spec;\n if (isSpec(wrapped)) return wrapped;\n } catch {\n // Not a spec payload — hosts also send plain prose here.\n }\n }\n return null;\n}\n\nexport function useJsonRenderApp(options: UseJsonRenderAppOptions): UseJsonRenderAppReturn {\n const [spec, setSpec] = useState<Spec | null>(null);\n const [loading, setLoading] = useState(true);\n const [connected, setConnected] = useState(false);\n const [connecting, setConnecting] = useState(true);\n const [error, setError] = useState<Error | null>(null);\n const [app, setApp] = useState<App | null>(null);\n\n useEffect(() => {\n const current = new App({ name: options.name, version: options.version }, {});\n setApp(current);\n setConnecting(true);\n setConnected(false);\n setError(null);\n\n const onToolResult = (params: ToolResultPayload) => {\n const next = pickSpec(params);\n if (next) setSpec(next);\n setLoading(false);\n };\n current.addEventListener(\"toolresult\", onToolResult);\n\n void current\n .connect()\n .then(() => {\n setConnected(true);\n })\n .catch((err) => {\n setError(err instanceof Error ? err : new Error(String(err)));\n })\n .finally(() => {\n setConnecting(false);\n });\n\n return () => {\n current.removeEventListener(\"toolresult\", onToolResult);\n void current.close().catch(() => undefined);\n };\n }, [options.name, options.version]);\n\n const callServerTool = useCallback(\n async (name: string, args?: Record<string, unknown>) => {\n if (!app) throw new Error(\"json-render app is not connected yet\");\n setLoading(true);\n const result = (await app.callServerTool({\n name,\n arguments: args ?? {},\n })) as ToolResultPayload;\n const next = pickSpec(result);\n if (next) setSpec(next);\n setLoading(false);\n },\n [app],\n );\n\n return useMemo(\n () => ({\n spec,\n loading,\n connected,\n connecting,\n error,\n app,\n callServerTool,\n }),\n [spec, loading, connected, connecting, error, app, callServerTool],\n );\n}\n\nexport interface BuildAppHtmlOptions {\n title?: string;\n js: string;\n css?: string;\n}\n\nexport function buildAppHtml(options: BuildAppHtmlOptions): string {\n const title = options.title ?? \"json-render MCP App\";\n const css = options.css ?? \"\";\n const js = escapeInlineScript(options.js);\n return [\n \"<!doctype html>\",\n \"<html>\",\n \"<head>\",\n ' <meta charset=\"utf-8\" />',\n ' <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />',\n ` <title>${title}</title>`,\n css ? ` <style>${css}</style>` : \"\",\n \"</head>\",\n \"<body>\",\n ' <div id=\"root\"></div>',\n ` <script type=\"module\">${js}</script>`,\n \"</body>\",\n \"</html>\",\n ]\n .filter(Boolean)\n .join(\"\\n\");\n}\n"],"mappings":";;;;;AAAA,SAAS,aAAa,WAAW,SAAS,gBAAgB;AAC1D,SAAS,WAAW;AAwBpB,SAAS,OAAO,OAA+B;AAC7C,SAAO,CAAC,CAAC,SAAS,OAAO,UAAU,YAAY,UAAW;AAC5D;AAQA,SAAS,SAAS,SAAqD;AACrE,QAAM,aAAa,SAAS;AAC5B,MAAI,OAAO,UAAU,EAAG,QAAO;AAC/B,QAAM,SAAU,YAA4C;AAC5D,MAAI,OAAQ,QAAO;AAEnB,aAAW,QAAQ,SAAS,WAAW,CAAC,GAAG;AACzC,QAAI,MAAM,SAAS,UAAU,CAAC,KAAK,KAAM;AACzC,QAAI;AACF,YAAM,SAAkB,KAAK,MAAM,KAAK,IAAI;AAC5C,UAAI,OAAO,MAAM,EAAG,QAAO;AAC3B,YAAM,UAAW,QAAsC;AACvD,UAAI,OAAO,OAAO,EAAG,QAAO;AAAA,IAC9B,QAAQ;AAAA,IAER;AAAA,EACF;AACA,SAAO;AACT;AAEO,SAAS,iBAAiB,SAA0D;AACzF,QAAM,CAAC,MAAM,OAAO,IAAI,SAAsB,IAAI;AAClD,QAAM,CAAC,SAAS,UAAU,IAAI,SAAS,IAAI;AAC3C,QAAM,CAAC,WAAW,YAAY,IAAI,SAAS,KAAK;AAChD,QAAM,CAAC,YAAY,aAAa,IAAI,SAAS,IAAI;AACjD,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAuB,IAAI;AACrD,QAAM,CAAC,KAAK,MAAM,IAAI,SAAqB,IAAI;AAE/C,YAAU,MAAM;AACd,UAAM,UAAU,IAAI,IAAI,EAAE,MAAM,QAAQ,MAAM,SAAS,QAAQ,QAAQ,GAAG,CAAC,CAAC;AAC5E,WAAO,OAAO;AACd,kBAAc,IAAI;AAClB,iBAAa,KAAK;AAClB,aAAS,IAAI;AAEb,UAAM,eAAe,CAAC,WAA8B;AAClD,YAAM,OAAO,SAAS,MAAM;AAC5B,UAAI,KAAM,SAAQ,IAAI;AACtB,iBAAW,KAAK;AAAA,IAClB;AACA,YAAQ,iBAAiB,cAAc,YAAY;AAEnD,SAAK,QACF,QAAQ,EACR,KAAK,MAAM;AACV,mBAAa,IAAI;AAAA,IACnB,CAAC,EACA,MAAM,CAAC,QAAQ;AACd,eAAS,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,GAAG,CAAC,CAAC;AAAA,IAC9D,CAAC,EACA,QAAQ,MAAM;AACb,oBAAc,KAAK;AAAA,IACrB,CAAC;AAEH,WAAO,MAAM;AACX,cAAQ,oBAAoB,cAAc,YAAY;AACtD,WAAK,QAAQ,MAAM,EAAE,MAAM,MAAM,MAAS;AAAA,IAC5C;AAAA,EACF,GAAG,CAAC,QAAQ,MAAM,QAAQ,OAAO,CAAC;AAElC,QAAM,iBAAiB;AAAA,IACrB,OAAO,MAAc,SAAmC;AACtD,UAAI,CAAC,IAAK,OAAM,IAAI,MAAM,sCAAsC;AAChE,iBAAW,IAAI;AACf,YAAM,SAAU,MAAM,IAAI,eAAe;AAAA,QACvC;AAAA,QACA,WAAW,QAAQ,CAAC;AAAA,MACtB,CAAC;AACD,YAAM,OAAO,SAAS,MAAM;AAC5B,UAAI,KAAM,SAAQ,IAAI;AACtB,iBAAW,KAAK;AAAA,IAClB;AAAA,IACA,CAAC,GAAG;AAAA,EACN;AAEA,SAAO;AAAA,IACL,OAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,IACA,CAAC,MAAM,SAAS,WAAW,YAAY,OAAO,KAAK,cAAc;AAAA,EACnE;AACF;AAQO,SAAS,aAAa,SAAsC;AACjE,QAAM,QAAQ,QAAQ,SAAS;AAC/B,QAAM,MAAM,QAAQ,OAAO;AAC3B,QAAM,KAAK,mBAAmB,QAAQ,EAAE;AACxC,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,YAAY,KAAK;AAAA,IACjB,MAAM,YAAY,GAAG,aAAa;AAAA,IAClC;AAAA,IACA;AAAA,IACA;AAAA,IACA,2BAA2B,EAAE;AAAA,IAC7B;AAAA,IACA;AAAA,EACF,EACG,OAAO,OAAO,EACd,KAAK,IAAI;AACd;","names":[]}
|