@ggui-ai/dev-stack 0.2.0-alpha.3 → 0.3.0-rc.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/dist/dev-server/auth.js +3 -3
- package/dist/dev-server/events.js +2 -2
- package/dist/dev-server/http.d.ts +1 -1
- package/dist/dev-server/http.js +5 -5
- package/dist/dev-server/hub.d.ts +1 -1
- package/dist/dev-server/hub.js +3 -3
- package/dist/hub-preview/client.js +8840 -26714
- package/dist/index.d.ts +1 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -10
- package/dist/local-registry/compile-ui.d.ts +8 -0
- package/dist/local-registry/compile-ui.d.ts.map +1 -1
- package/dist/local-registry/compile-ui.js +12 -11
- package/dist/local-registry/local-registry.d.ts.map +1 -1
- package/dist/local-registry/local-registry.js +3 -5
- package/dist/local-registry/watcher.d.ts +2 -2
- package/dist/local-registry/watcher.js +1 -1
- package/package.json +8 -9
- package/dist/dev-server/sandbox-proxy.d.ts +0 -46
- package/dist/dev-server/sandbox-proxy.d.ts.map +0 -1
- package/dist/dev-server/sandbox-proxy.js +0 -270
package/dist/index.d.ts
CHANGED
|
@@ -21,15 +21,13 @@
|
|
|
21
21
|
* adapter seam.
|
|
22
22
|
*/
|
|
23
23
|
export { LocalUiRegistry, type LocalUiRegistryOptions, type LocalBundleResult, type BundleErrorLocation, } from './local-registry/local-registry.js';
|
|
24
|
-
export {
|
|
25
|
-
export { compileUiOnDemand, resolveEntryFile, COMPILED_BUNDLE_CONTENT_TYPE, type CompileResult, } from './local-registry/compile-ui.js';
|
|
24
|
+
export { compileUiOnDemand, resolveEntryFile, COMPILED_BUNDLE_CONTENT_TYPE, SANDBOX_EXTERNALS, type CompileResult, } from './local-registry/compile-ui.js';
|
|
26
25
|
export { createLocalWatcher, statOrNull, type LocalWatcher, type LocalWatcherOptions, type WatchListener, } from './local-registry/watcher.js';
|
|
27
26
|
export { startDevServer, type DevServerOptions, type DevServerHandle, } from './dev-server/http.js';
|
|
28
27
|
export { openEventStream, type SseOptions } from './dev-server/events.js';
|
|
29
28
|
export { renderHubHtml, serveHubShell, type HubShellContext, } from './dev-server/hub.js';
|
|
30
29
|
export { renderHubPreviewHtml, serveHubPreviewShell, serveHubPreviewBundle, resolveHubPreviewBundlePath, extractSelectedId, type HubPreviewShellContext, } from './dev-server/hub-preview.js';
|
|
31
30
|
export { createSecurityPolicy, type CreatePolicyOptions, type DevServerSecurityPolicy, type PolicyOutcome, } from './dev-server/auth.js';
|
|
32
|
-
export { startSandboxProxyServer, type SandboxProxyServerOptions, type SandboxProxyServerHandle, } from './dev-server/sandbox-proxy.js';
|
|
33
31
|
export { runDev, GguiDevError, DEFAULT_DEV_PORT, DEFAULT_DEV_HOST, type DevOptions, type DevBootstrap, } from './run-dev.js';
|
|
34
32
|
export { RuntimeSupervisor, DEFAULT_RUNTIME_BUFFER_SIZE, emptyRuntimeSnapshot, formatRuntimeEventLine, type RuntimeSupervisorOptions, type RuntimeStateSnapshot, type EmptyRuntimeStateSnapshot, type AnyRuntimeStateSnapshot, type RuntimeEventRecord, } from './runtime-supervisor.js';
|
|
35
33
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAGH,OAAO,EACL,eAAe,EACf,KAAK,sBAAsB,EAC3B,KAAK,iBAAiB,EACtB,KAAK,mBAAmB,GACzB,MAAM,oCAAoC,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAGH,OAAO,EACL,eAAe,EACf,KAAK,sBAAsB,EAC3B,KAAK,iBAAiB,EACtB,KAAK,mBAAmB,GACzB,MAAM,oCAAoC,CAAC;AAG5C,OAAO,EACL,iBAAiB,EACjB,gBAAgB,EAChB,4BAA4B,EAC5B,iBAAiB,EACjB,KAAK,aAAa,GACnB,MAAM,gCAAgC,CAAC;AAGxC,OAAO,EACL,kBAAkB,EAClB,UAAU,EACV,KAAK,YAAY,EACjB,KAAK,mBAAmB,EACxB,KAAK,aAAa,GACnB,MAAM,6BAA6B,CAAC;AAGrC,OAAO,EACL,cAAc,EACd,KAAK,gBAAgB,EACrB,KAAK,eAAe,GACrB,MAAM,sBAAsB,CAAC;AAG9B,OAAO,EAAE,eAAe,EAAE,KAAK,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAG1E,OAAO,EACL,aAAa,EACb,aAAa,EACb,KAAK,eAAe,GACrB,MAAM,qBAAqB,CAAC;AAG7B,OAAO,EACL,oBAAoB,EACpB,oBAAoB,EACpB,qBAAqB,EACrB,2BAA2B,EAC3B,iBAAiB,EACjB,KAAK,sBAAsB,GAC5B,MAAM,6BAA6B,CAAC;AAGrC,OAAO,EACL,oBAAoB,EACpB,KAAK,mBAAmB,EACxB,KAAK,uBAAuB,EAC5B,KAAK,aAAa,GACnB,MAAM,sBAAsB,CAAC;AAG9B,OAAO,EACL,MAAM,EACN,YAAY,EACZ,gBAAgB,EAChB,gBAAgB,EAChB,KAAK,UAAU,EACf,KAAK,YAAY,GAClB,MAAM,cAAc,CAAC;AAKtB,OAAO,EACL,iBAAiB,EACjB,2BAA2B,EAC3B,oBAAoB,EACpB,sBAAsB,EACtB,KAAK,wBAAwB,EAC7B,KAAK,oBAAoB,EACzB,KAAK,yBAAyB,EAC9B,KAAK,uBAAuB,EAC5B,KAAK,kBAAkB,GACxB,MAAM,yBAAyB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -22,13 +22,8 @@
|
|
|
22
22
|
*/
|
|
23
23
|
// Local UI registry
|
|
24
24
|
export { LocalUiRegistry, } from './local-registry/local-registry.js';
|
|
25
|
-
// Discovery — re-export from @ggui-ai/project-config/node for
|
|
26
|
-
// consumers that have historically imported these symbols from the
|
|
27
|
-
// dev-stack barrel. The canonical home is the schema-owner package;
|
|
28
|
-
// dev-stack's `LocalUiRegistry` consumes the same helper directly.
|
|
29
|
-
export { discoverLocalUis, discoverFromGguiJsonPath, } from '@ggui-ai/project-config/node';
|
|
30
25
|
// Compile-on-demand
|
|
31
|
-
export { compileUiOnDemand, resolveEntryFile, COMPILED_BUNDLE_CONTENT_TYPE, } from './local-registry/compile-ui.js';
|
|
26
|
+
export { compileUiOnDemand, resolveEntryFile, COMPILED_BUNDLE_CONTENT_TYPE, SANDBOX_EXTERNALS, } from './local-registry/compile-ui.js';
|
|
32
27
|
// Watcher
|
|
33
28
|
export { createLocalWatcher, statOrNull, } from './local-registry/watcher.js';
|
|
34
29
|
// HTTP dev server
|
|
@@ -41,10 +36,6 @@ export { renderHubHtml, serveHubShell, } from './dev-server/hub.js';
|
|
|
41
36
|
export { renderHubPreviewHtml, serveHubPreviewShell, serveHubPreviewBundle, resolveHubPreviewBundlePath, extractSelectedId, } from './dev-server/hub-preview.js';
|
|
42
37
|
// Auth / CORS policy
|
|
43
38
|
export { createSecurityPolicy, } from './dev-server/auth.js';
|
|
44
|
-
// MCP-Apps sandbox proxy — second HTTP server on a different origin
|
|
45
|
-
// that serves `sandbox.html` for `<AppRenderer>`'s two-iframe
|
|
46
|
-
// sandboxing pattern (R5; see ./dev-server/sandbox-proxy.ts).
|
|
47
|
-
export { startSandboxProxyServer, } from './dev-server/sandbox-proxy.js';
|
|
48
39
|
// Orchestration — the seam every local-dev host composes against.
|
|
49
40
|
export { runDev, GguiDevError, DEFAULT_DEV_PORT, DEFAULT_DEV_HOST, } from './run-dev.js';
|
|
50
41
|
// Runtime supervision — ring-buffered view of the supervised agent
|
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
import { type Message } from 'esbuild';
|
|
2
2
|
import type { UiManifest } from '@ggui-ai/project-config';
|
|
3
|
+
/**
|
|
4
|
+
* Packages the runtime renderer already resolves from its
|
|
5
|
+
* own bundle (via `@ggui-ai/design/module-loader`'s import-map).
|
|
6
|
+
* Keeping them out of the compiled bundle keeps the artifact small
|
|
7
|
+
* and lets the rendering host's in-tree React instance be shared
|
|
8
|
+
* across every rendered UI.
|
|
9
|
+
*/
|
|
10
|
+
export declare const SANDBOX_EXTERNALS: string[];
|
|
3
11
|
/** Content type the compiled bundle is served with. Matches the
|
|
4
12
|
* precompiled path for consistency. */
|
|
5
13
|
export declare const COMPILED_BUNDLE_CONTENT_TYPE = "application/javascript+react";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"compile-ui.d.ts","sourceRoot":"","sources":["../../src/local-registry/compile-ui.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"compile-ui.d.ts","sourceRoot":"","sources":["../../src/local-registry/compile-ui.ts"],"names":[],"mappings":"AAqCA,OAAO,EAA4B,KAAK,OAAO,EAAE,MAAM,SAAS,CAAC;AACjE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAE1D;;;;;;GAMG;AACH,eAAO,MAAM,iBAAiB,UAW7B,CAAC;AASF;uCACuC;AACvC,eAAO,MAAM,4BAA4B,iCAAiC,CAAC;AAE3E,MAAM,WAAW,aAAa;IAC5B,gEAAgE;IAChE,WAAW,EAAE,MAAM,CAAC;IACpB,+DAA+D;IAC/D,YAAY,EAAE,MAAM,CAAC;IACrB,6EAA6E;IAC7E,QAAQ,EAAE,UAAU,CAAC;CACtB;AAED,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,IAAI,CAAC;IACX,oBAAoB;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,iEAAiE;IACjE,KAAK,EAAE,MAAM,CAAC;IACd,6CAA6C;IAC7C,QAAQ,EAAE,OAAO,EAAE,CAAC;CACrB;AAED,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,eAAe,CAAC;IACtB;;;;OAIG;IACH,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,SAAS,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,OAAO,EAAE,CAAC;IAClB,QAAQ,EAAE,OAAO,EAAE,CAAC;CACrB;AAED,MAAM,MAAM,aAAa,GAAG,SAAS,GAAG,mBAAmB,GAAG,cAAc,CAAC;AAE7E;;;;;;;;;;;GAWG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,aAAa,GAAG;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,KAAK,EAAE,MAAM,EAAE,CAAA;CAAE,CAkB/F;AAED;;;;;;;;;GASG;AACH,wBAAsB,iBAAiB,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,aAAa,CAAC,CAqCrF"}
|
|
@@ -2,10 +2,11 @@
|
|
|
2
2
|
* Compile-on-demand for local `ggui dev` UIs.
|
|
3
3
|
*
|
|
4
4
|
* Resolves the authored TSX entry for a `ggui.ui.json` and runs
|
|
5
|
-
* esbuild against it, producing an ESM bundle
|
|
6
|
-
* `DynamicComponent`. This replaces the "must pre-compile"
|
|
7
|
-
* the foundation slice left behind — `ggui dev` now feels
|
|
8
|
-
* real dev server: start it, open
|
|
5
|
+
* esbuild against it, producing an ESM bundle an authoring UI can
|
|
6
|
+
* hand to `DynamicComponent`. This replaces the "must pre-compile"
|
|
7
|
+
* state the foundation slice left behind — `ggui dev` now feels
|
|
8
|
+
* like a real dev server: start it, open the authoring UI, see the
|
|
9
|
+
* render.
|
|
9
10
|
*
|
|
10
11
|
* Trust model:
|
|
11
12
|
*
|
|
@@ -23,11 +24,11 @@
|
|
|
23
24
|
*
|
|
24
25
|
* - esbuild `build` mode with bundle: true, so multi-file UIs
|
|
25
26
|
* work (import sibling helpers, theme tokens, wire hooks).
|
|
26
|
-
* - Sandbox externals match the runtime
|
|
27
|
+
* - Sandbox externals match the runtime renderer
|
|
27
28
|
* (`DynamicComponent` in `@ggui-ai/react`). React, the design
|
|
28
29
|
* system, `@ggui-ai/wire`, and `@ggui-ai/react` stay external
|
|
29
|
-
* and resolve against the import map
|
|
30
|
-
* render time.
|
|
30
|
+
* and resolve against the import map the rendering host
|
|
31
|
+
* provides at render time.
|
|
31
32
|
* - No caching. Every request recompiles. esbuild TSX transform
|
|
32
33
|
* is ~50-200ms for a typical single-screen UI — fine for the
|
|
33
34
|
* dev loop. A watcher-driven cache lands with HMR, not here.
|
|
@@ -36,13 +37,13 @@ import { existsSync } from 'node:fs';
|
|
|
36
37
|
import { dirname, isAbsolute, resolve } from 'node:path';
|
|
37
38
|
import { build } from 'esbuild';
|
|
38
39
|
/**
|
|
39
|
-
* Packages the
|
|
40
|
+
* Packages the runtime renderer already resolves from its
|
|
40
41
|
* own bundle (via `@ggui-ai/design/module-loader`'s import-map).
|
|
41
42
|
* Keeping them out of the compiled bundle keeps the artifact small
|
|
42
|
-
* and lets
|
|
43
|
-
* rendered UI.
|
|
43
|
+
* and lets the rendering host's in-tree React instance be shared
|
|
44
|
+
* across every rendered UI.
|
|
44
45
|
*/
|
|
45
|
-
const SANDBOX_EXTERNALS = [
|
|
46
|
+
export const SANDBOX_EXTERNALS = [
|
|
46
47
|
'react',
|
|
47
48
|
'react/*',
|
|
48
49
|
'react-dom',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"local-registry.d.ts","sourceRoot":"","sources":["../../src/local-registry/local-registry.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"local-registry.d.ts","sourceRoot":"","sources":["../../src/local-registry/local-registry.ts"],"names":[],"mappings":"AAkCA,OAAO,KAAK,EACV,QAAQ,EACR,eAAe,EACf,UAAU,EACV,sBAAsB,EACvB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAGL,KAAK,cAAc,EACnB,KAAK,UAAU,EAChB,MAAM,8BAA8B,CAAC;AAOtC,OAAO,EAAyC,KAAK,aAAa,EAAE,MAAM,cAAc,CAAC;AAQzF,MAAM,WAAW,sBAAsB;IACrC,gEAAgE;IAChE,WAAW,EAAE,MAAM,CAAC;IACpB,2EAA2E;IAC3E,QAAQ,EAAE,UAAU,CAAC;CACtB;AAED;;;;;GAKG;AACH,MAAM,MAAM,iBAAiB,GACzB;IAAE,IAAI,EAAE,IAAI,CAAC;IAAC,MAAM,EAAE,QAAQ,CAAC;IAAC,MAAM,EAAE,aAAa,GAAG,UAAU,CAAA;CAAE,GACpE;IAAE,IAAI,EAAE,WAAW,CAAA;CAAE,GACrB;IAAE,IAAI,EAAE,eAAe,CAAC;IAAC,KAAK,EAAE,MAAM,EAAE,CAAA;CAAE,GAC1C;IACE,IAAI,EAAE,gBAAgB,CAAC;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,mBAAmB,GAAG,IAAI,CAAA;KAAE,CAAC,CAAC;IACtE,QAAQ,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,mBAAmB,GAAG,IAAI,CAAA;KAAE,CAAC,CAAC;CACzE,CAAC;AAEN,0DAA0D;AAC1D,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,qBAAa,eAAgB,YAAW,UAAU;IAChD,QAAQ,CAAC,YAAY,EAAE,sBAAsB,CAE3C;IAEF,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAa;IACtC,OAAO,CAAC,WAAW,CAAmC;IACtD;oEACgE;IAChE,OAAO,CAAC,gBAAgB,CAA6B;IACrD,OAAO,CAAC,UAAU,CAAwB;IAC1C,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,OAAO,CAA6B;gBAEhC,OAAO,EAAE,sBAAsB;IAK3C;;;;;OAKG;IACG,OAAO,IAAI,OAAO,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,cAAc,EAAE,CAAA;KAAE,CAAC;IAkBvE;;;;;;;OAOG;IACH,SAAS,CAAC,QAAQ,EAAE,aAAa,GAAG,MAAM,IAAI;IAU9C,yDAAyD;IACnD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAO5B,OAAO,CAAC,aAAa;IAYrB,kEAAkE;IAClE,SAAS,IAAI,SAAS,cAAc,EAAE;IAIhC,IAAI,IAAI,OAAO,CAAC,eAAe,EAAE,CAAC;IAKlC,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,GAAG,SAAS,CAAC;IAMrD,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,GAAG,SAAS,CAAC;IAK1D;;;;;OAKG;IACG,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC;YA0D3C,YAAY;CAK3B"}
|
|
@@ -4,13 +4,12 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Scope (compile-on-demand slice, 2026-04-18):
|
|
6
6
|
*
|
|
7
|
-
* - Read-only
|
|
8
|
-
* `capabilities.observable ===
|
|
7
|
+
* - Read-only — the `UiRegistry` contract has no write arm;
|
|
8
|
+
* `capabilities.observable === true` (watcher-backed).
|
|
9
9
|
* - Manifest-backed. Discovery runs against `ggui.json` +
|
|
10
10
|
* `blueprints.include` globs via `./discovery.js` and is
|
|
11
11
|
* cached in memory. `refresh()` re-runs discovery on demand;
|
|
12
|
-
*
|
|
13
|
-
* HMR slice.
|
|
12
|
+
* `subscribe()` is watcher-backed (see `./watcher.ts`).
|
|
14
13
|
* - `getBundle(id)` resolution order:
|
|
15
14
|
* 1. **Precompiled fast-path** — colocated `ggui.ui.js` or
|
|
16
15
|
* `ggui.ui.mjs` beside the manifest. Lets a project with
|
|
@@ -42,7 +41,6 @@ const BUNDLE_FILENAMES = ['ggui.ui.js', 'ggui.ui.mjs'];
|
|
|
42
41
|
const BUNDLE_CONTENT_TYPE = 'application/javascript+react';
|
|
43
42
|
export class LocalUiRegistry {
|
|
44
43
|
capabilities = {
|
|
45
|
-
writable: false,
|
|
46
44
|
observable: true,
|
|
47
45
|
};
|
|
48
46
|
projectRoot;
|
|
@@ -2,8 +2,8 @@ import type { UiRegistryEvent } from '@ggui-ai/ui-registry';
|
|
|
2
2
|
/**
|
|
3
3
|
* Listener for raw events the watcher emits. Delivery is
|
|
4
4
|
* at-least-once — consumers dedupe where needed (e.g. the SSE
|
|
5
|
-
* handler just forwards;
|
|
6
|
-
*
|
|
5
|
+
* handler just forwards; a subscribing UI bumps a counter and
|
|
6
|
+
* doesn't care about dupes).
|
|
7
7
|
*/
|
|
8
8
|
export type WatchListener = (event: UiRegistryEvent) => void;
|
|
9
9
|
export interface LocalWatcherOptions {
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Maps raw chokidar events to the contract's `UiRegistryEvent`
|
|
5
5
|
* union (`added` / `changed` / `removed`). The watcher is scoped
|
|
6
|
-
* to what actually affects what
|
|
6
|
+
* to what actually affects what a registry consumer can see:
|
|
7
7
|
*
|
|
8
8
|
* - Any `ggui.ui.json` under the project tree → affects the
|
|
9
9
|
* manifest itself. A new one means `added`, a deletion
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ggui-ai/dev-stack",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0-rc.0",
|
|
4
4
|
"description": "Shared local dev engine for the ggui protocol: local UI registry discovery, compile-on-demand, filesystem watcher with SSE, and a read-only HTTP surface for dev tooling. Framework-neutral — agent runtime supervision plugs in via an adapter seam, so no agent SDK is hardcoded.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ggui",
|
|
@@ -28,10 +28,9 @@
|
|
|
28
28
|
"dependencies": {
|
|
29
29
|
"chokidar": "^4.0.3",
|
|
30
30
|
"esbuild": "^0.25.12",
|
|
31
|
-
"
|
|
32
|
-
"@ggui-ai/
|
|
33
|
-
"@ggui-ai/ui-registry": "0.
|
|
34
|
-
"@ggui-ai/project-config": "0.2.0-alpha.3"
|
|
31
|
+
"@ggui-ai/agent-runtime": "0.3.0-rc.0",
|
|
32
|
+
"@ggui-ai/project-config": "0.3.0-rc.0",
|
|
33
|
+
"@ggui-ai/ui-registry": "0.3.0-rc.0"
|
|
35
34
|
},
|
|
36
35
|
"devDependencies": {
|
|
37
36
|
"@types/node": "^24.0.0",
|
|
@@ -41,9 +40,9 @@
|
|
|
41
40
|
"react-dom": "^19.0.0",
|
|
42
41
|
"typescript": "^5.0.0",
|
|
43
42
|
"vitest": "^3.0.0",
|
|
44
|
-
"@ggui-ai/design": "0.
|
|
45
|
-
"@ggui-ai/react": "0.
|
|
46
|
-
"@ggui-ai/wire": "0.
|
|
43
|
+
"@ggui-ai/design": "0.3.0-rc.0",
|
|
44
|
+
"@ggui-ai/react": "0.3.0-rc.0",
|
|
45
|
+
"@ggui-ai/wire": "0.3.0-rc.0"
|
|
47
46
|
},
|
|
48
47
|
"repository": {
|
|
49
48
|
"type": "git",
|
|
@@ -62,7 +61,7 @@
|
|
|
62
61
|
},
|
|
63
62
|
"author": "ggui contributors <hello@ggui.ai>",
|
|
64
63
|
"scripts": {
|
|
65
|
-
"build": "tsc -p tsconfig.build.json && node scripts/build-hub-preview.mjs",
|
|
64
|
+
"build": "rm -rf dist && tsc -p tsconfig.build.json && node scripts/build-hub-preview.mjs",
|
|
66
65
|
"build:hub-preview": "node scripts/build-hub-preview.mjs",
|
|
67
66
|
"dev": "tsc --watch",
|
|
68
67
|
"typecheck": "tsc --noEmit && tsc -p hub-preview-client/tsconfig.json --noEmit",
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Options for {@link startSandboxProxyServer}.
|
|
3
|
-
*/
|
|
4
|
-
export interface SandboxProxyServerOptions {
|
|
5
|
-
/**
|
|
6
|
-
* Port to bind. `0` asks the OS for a free port — the chosen port
|
|
7
|
-
* is reflected in the returned `url`. Tests pass `0`; samples pass
|
|
8
|
-
* a fixed port so the AppRenderer config is static.
|
|
9
|
-
*/
|
|
10
|
-
readonly port: number;
|
|
11
|
-
/**
|
|
12
|
-
* Bind address. Defaults to `127.0.0.1` — loopback-only. Pass
|
|
13
|
-
* `'0.0.0.0'` only if LAN access is required.
|
|
14
|
-
*/
|
|
15
|
-
readonly host?: string;
|
|
16
|
-
}
|
|
17
|
-
/**
|
|
18
|
-
* Handle to a running sandbox proxy server.
|
|
19
|
-
*/
|
|
20
|
-
export interface SandboxProxyServerHandle {
|
|
21
|
-
/**
|
|
22
|
-
* Absolute URL the AppRenderer's `sandbox.url` prop should point at.
|
|
23
|
-
* Includes scheme, host, and port. Path is `/sandbox.html` — the
|
|
24
|
-
* spec-canonical sandbox entry point.
|
|
25
|
-
*/
|
|
26
|
-
readonly url: string;
|
|
27
|
-
/** Actual bound port (resolved when `options.port` was `0`). */
|
|
28
|
-
readonly port: number;
|
|
29
|
-
/** Stops the server and releases the port. */
|
|
30
|
-
readonly close: () => Promise<void>;
|
|
31
|
-
}
|
|
32
|
-
/**
|
|
33
|
-
* Start an HTTP server serving `sandbox.html` for use as
|
|
34
|
-
* `<AppRenderer>`'s `sandbox.url`. The server has exactly two
|
|
35
|
-
* behaviors:
|
|
36
|
-
*
|
|
37
|
-
* - `GET /` and `GET /sandbox.html` → sandbox HTML + CSP header
|
|
38
|
-
* derived from `?csp=<urlencoded-json>` (mirrors upstream
|
|
39
|
-
* basic-host serve.ts).
|
|
40
|
-
* - Anything else → 404.
|
|
41
|
-
*
|
|
42
|
-
* Returns immediately once the server is listening; the handle's
|
|
43
|
-
* `close()` releases the port.
|
|
44
|
-
*/
|
|
45
|
-
export declare function startSandboxProxyServer(options: SandboxProxyServerOptions): Promise<SandboxProxyServerHandle>;
|
|
46
|
-
//# sourceMappingURL=sandbox-proxy.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"sandbox-proxy.d.ts","sourceRoot":"","sources":["../../src/dev-server/sandbox-proxy.ts"],"names":[],"mappings":"AAoDA;;GAEG;AACH,MAAM,WAAW,yBAAyB;IACxC;;;;OAIG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB;;;OAGG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;GAEG;AACH,MAAM,WAAW,wBAAwB;IACvC;;;;OAIG;IACH,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,gEAAgE;IAChE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,8CAA8C;IAC9C,QAAQ,CAAC,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CACrC;AAuKD;;;;;;;;;;;;GAYG;AACH,wBAAgB,uBAAuB,CACrC,OAAO,EAAE,yBAAyB,GACjC,OAAO,CAAC,wBAAwB,CAAC,CAuEnC"}
|
|
@@ -1,270 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `startSandboxProxyServer` — MCP-Apps spec-compliant sandbox proxy host
|
|
3
|
-
* for `<AppRenderer>`.
|
|
4
|
-
*
|
|
5
|
-
* # Why this exists
|
|
6
|
-
*
|
|
7
|
-
* The MCP Apps spec (specification/2026-01-26/apps.mdx, double-iframe
|
|
8
|
-
* sandbox architecture) mandates that the host and the sandbox iframe
|
|
9
|
-
* live on DIFFERENT origins. The host wraps the sandbox iframe; the
|
|
10
|
-
* sandbox iframe wraps the untrusted app HTML. Separating origins
|
|
11
|
-
* means a compromise of the app cannot reach host APIs via
|
|
12
|
-
* same-origin DOM access.
|
|
13
|
-
*
|
|
14
|
-
* `@mcp-ui/client`'s `<AppRenderer>` accepts a `sandbox: { url }` prop
|
|
15
|
-
* pointing at a public URL serving `sandbox.html`. We ship that file +
|
|
16
|
-
* its message-relay JS as a self-contained HTML string and serve it
|
|
17
|
-
* over plain Node `http` on a separate port. Samples (and any consumer
|
|
18
|
-
* of the dev-stack) boot this alongside their agent server.
|
|
19
|
-
*
|
|
20
|
-
* # Reference impl
|
|
21
|
-
*
|
|
22
|
-
* Adapted from
|
|
23
|
-
* `github.com/modelcontextprotocol/ext-apps/examples/basic-host/`
|
|
24
|
-
* (sandbox.html + src/sandbox.ts + serve.ts). Inlined into a single
|
|
25
|
-
* HTML so the dev-stack doesn't need to bundle a static asset.
|
|
26
|
-
*
|
|
27
|
-
* # Security
|
|
28
|
-
*
|
|
29
|
-
* - CSP set via HTTP headers per `?csp=<urlencoded-json>` query
|
|
30
|
-
* (tamper-proof; meta tags can be overridden by inline scripts).
|
|
31
|
-
* - Sanitizes CSP domain entries to block `;`, newlines, quotes,
|
|
32
|
-
* spaces — defends against directive injection.
|
|
33
|
-
* - Binds to `127.0.0.1` by default. Pass `host: '0.0.0.0'` only
|
|
34
|
-
* when LAN exposure is required.
|
|
35
|
-
*
|
|
36
|
-
* # Caller pattern
|
|
37
|
-
*
|
|
38
|
-
* ```ts
|
|
39
|
-
* import { startSandboxProxyServer } from '@ggui-ai/dev-stack';
|
|
40
|
-
* const proxy = await startSandboxProxyServer({ port: 7790 });
|
|
41
|
-
* console.log('Sandbox proxy:', proxy.url);
|
|
42
|
-
* // ... pass proxy.url to AppRenderer's sandbox.url prop ...
|
|
43
|
-
* await proxy.close();
|
|
44
|
-
* ```
|
|
45
|
-
*/
|
|
46
|
-
import { createServer, } from 'node:http';
|
|
47
|
-
/**
|
|
48
|
-
* Reject CSP entries containing directive-injection characters
|
|
49
|
-
* (`;`, newlines, quotes, spaces). Returns the kept entries.
|
|
50
|
-
*/
|
|
51
|
-
function sanitizeCspDomains(domains) {
|
|
52
|
-
if (!domains)
|
|
53
|
-
return [];
|
|
54
|
-
return domains.filter((d) => typeof d === 'string' && !/[;\r\n'" ]/.test(d));
|
|
55
|
-
}
|
|
56
|
-
/**
|
|
57
|
-
* Build the `Content-Security-Policy` header value from an
|
|
58
|
-
* (already-sanitized) CSP record. Mirrors `serve.ts` in the upstream
|
|
59
|
-
* basic-host example so behavior matches the spec reference.
|
|
60
|
-
*/
|
|
61
|
-
function buildCspHeader(csp) {
|
|
62
|
-
const resourceDomains = sanitizeCspDomains(csp?.resourceDomains).join(' ');
|
|
63
|
-
const connectDomains = sanitizeCspDomains(csp?.connectDomains).join(' ');
|
|
64
|
-
const frameDomains = sanitizeCspDomains(csp?.frameDomains).join(' ');
|
|
65
|
-
const baseUriDomains = sanitizeCspDomains(csp?.baseUriDomains).join(' ');
|
|
66
|
-
const directives = [
|
|
67
|
-
"default-src 'self' 'unsafe-inline'",
|
|
68
|
-
`script-src 'self' 'unsafe-inline' 'unsafe-eval' blob: data: ${resourceDomains}`.trim(),
|
|
69
|
-
`style-src 'self' 'unsafe-inline' blob: data: ${resourceDomains}`.trim(),
|
|
70
|
-
`img-src 'self' data: blob: ${resourceDomains}`.trim(),
|
|
71
|
-
`font-src 'self' data: blob: ${resourceDomains}`.trim(),
|
|
72
|
-
`media-src 'self' data: blob: ${resourceDomains}`.trim(),
|
|
73
|
-
`connect-src 'self' ${connectDomains}`.trim(),
|
|
74
|
-
`worker-src 'self' blob: ${resourceDomains}`.trim(),
|
|
75
|
-
frameDomains.length > 0
|
|
76
|
-
? `frame-src ${frameDomains}`
|
|
77
|
-
: "frame-src 'none'",
|
|
78
|
-
"object-src 'none'",
|
|
79
|
-
baseUriDomains.length > 0
|
|
80
|
-
? `base-uri ${baseUriDomains}`
|
|
81
|
-
: "base-uri 'none'",
|
|
82
|
-
];
|
|
83
|
-
return directives.join('; ');
|
|
84
|
-
}
|
|
85
|
-
/**
|
|
86
|
-
* The self-contained sandbox.html body. Inlines the message-relay JS
|
|
87
|
-
* from `examples/basic-host/src/sandbox.ts` so we ship one file and
|
|
88
|
-
* don't need a build step. The script is a verbatim port that:
|
|
89
|
-
*
|
|
90
|
-
* 1. Asserts iframe isolation (throws if it can reach `window.top`).
|
|
91
|
-
* 2. Creates an inner iframe sandboxed with `allow-scripts
|
|
92
|
-
* allow-same-origin allow-forms`.
|
|
93
|
-
* 3. Intercepts `ui/notifications/sandbox-resource-ready` from the
|
|
94
|
-
* parent to write the app HTML into the inner iframe.
|
|
95
|
-
* 4. Relays every other postMessage bidirectionally between parent
|
|
96
|
-
* and inner.
|
|
97
|
-
* 5. Posts `ui/notifications/sandbox-proxy-ready` once attached.
|
|
98
|
-
*/
|
|
99
|
-
const SANDBOX_HTML = `<!doctype html>
|
|
100
|
-
<html>
|
|
101
|
-
<head>
|
|
102
|
-
<meta charset="utf-8" />
|
|
103
|
-
<meta name="color-scheme" content="light dark">
|
|
104
|
-
<title>ggui sandbox proxy</title>
|
|
105
|
-
<style>
|
|
106
|
-
html, body { margin: 0; height: 100vh; width: 100vw; background-color: transparent; }
|
|
107
|
-
body { display: flex; flex-direction: column; }
|
|
108
|
-
* { box-sizing: border-box; }
|
|
109
|
-
iframe {
|
|
110
|
-
background-color: transparent;
|
|
111
|
-
border: 0px none transparent;
|
|
112
|
-
padding: 0px;
|
|
113
|
-
overflow: hidden;
|
|
114
|
-
flex-grow: 1;
|
|
115
|
-
color-scheme: inherit;
|
|
116
|
-
}
|
|
117
|
-
</style>
|
|
118
|
-
</head>
|
|
119
|
-
<body>
|
|
120
|
-
<script>
|
|
121
|
-
(function(){
|
|
122
|
-
'use strict';
|
|
123
|
-
if (window.self === window.top) {
|
|
124
|
-
throw new Error('This file is only to be used in an iframe sandbox.');
|
|
125
|
-
}
|
|
126
|
-
if (!document.referrer) {
|
|
127
|
-
throw new Error('No referrer, cannot validate embedding site.');
|
|
128
|
-
}
|
|
129
|
-
var EXPECTED_HOST_ORIGIN = new URL(document.referrer).origin;
|
|
130
|
-
var OWN_ORIGIN = new URL(window.location.href).origin;
|
|
131
|
-
// Security self-test: top access MUST throw (sandbox attribute strips same-origin).
|
|
132
|
-
try {
|
|
133
|
-
window.top.alert('If you see this, the sandbox is not setup securely.');
|
|
134
|
-
throw 'FAIL';
|
|
135
|
-
} catch (e) {
|
|
136
|
-
if (e === 'FAIL') {
|
|
137
|
-
throw new Error('The sandbox is not setup securely.');
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
// Inner iframe — the untrusted app HTML lands here.
|
|
141
|
-
var inner = document.createElement('iframe');
|
|
142
|
-
inner.style = 'width:100%; height:100%; border:none;';
|
|
143
|
-
inner.setAttribute('sandbox', 'allow-scripts allow-same-origin allow-forms');
|
|
144
|
-
document.body.appendChild(inner);
|
|
145
|
-
var RESOURCE_READY = 'ui/notifications/sandbox-resource-ready';
|
|
146
|
-
var PROXY_READY = 'ui/notifications/sandbox-proxy-ready';
|
|
147
|
-
window.addEventListener('message', function(event) {
|
|
148
|
-
if (event.source === window.parent) {
|
|
149
|
-
if (event.origin !== EXPECTED_HOST_ORIGIN) {
|
|
150
|
-
console.error('[Sandbox] Rejecting parent message from unexpected origin:', event.origin);
|
|
151
|
-
return;
|
|
152
|
-
}
|
|
153
|
-
if (event.data && event.data.method === RESOURCE_READY) {
|
|
154
|
-
var params = event.data.params || {};
|
|
155
|
-
var html = params.html;
|
|
156
|
-
var sandboxAttr = params.sandbox;
|
|
157
|
-
if (typeof sandboxAttr === 'string') {
|
|
158
|
-
inner.setAttribute('sandbox', sandboxAttr);
|
|
159
|
-
}
|
|
160
|
-
if (typeof html === 'string') {
|
|
161
|
-
var doc = inner.contentDocument || (inner.contentWindow && inner.contentWindow.document);
|
|
162
|
-
if (doc) {
|
|
163
|
-
doc.open();
|
|
164
|
-
doc.write(html);
|
|
165
|
-
doc.close();
|
|
166
|
-
} else {
|
|
167
|
-
inner.srcdoc = html;
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
} else {
|
|
171
|
-
if (inner && inner.contentWindow) {
|
|
172
|
-
inner.contentWindow.postMessage(event.data, '*');
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
} else if (event.source === inner.contentWindow) {
|
|
176
|
-
if (event.origin !== OWN_ORIGIN) {
|
|
177
|
-
console.error('[Sandbox] Rejecting inner message from unexpected origin:', event.origin);
|
|
178
|
-
return;
|
|
179
|
-
}
|
|
180
|
-
window.parent.postMessage(event.data, EXPECTED_HOST_ORIGIN);
|
|
181
|
-
}
|
|
182
|
-
});
|
|
183
|
-
window.parent.postMessage({
|
|
184
|
-
jsonrpc: '2.0',
|
|
185
|
-
method: PROXY_READY,
|
|
186
|
-
params: {},
|
|
187
|
-
}, EXPECTED_HOST_ORIGIN);
|
|
188
|
-
})();
|
|
189
|
-
</script>
|
|
190
|
-
</body>
|
|
191
|
-
</html>`;
|
|
192
|
-
/**
|
|
193
|
-
* Start an HTTP server serving `sandbox.html` for use as
|
|
194
|
-
* `<AppRenderer>`'s `sandbox.url`. The server has exactly two
|
|
195
|
-
* behaviors:
|
|
196
|
-
*
|
|
197
|
-
* - `GET /` and `GET /sandbox.html` → sandbox HTML + CSP header
|
|
198
|
-
* derived from `?csp=<urlencoded-json>` (mirrors upstream
|
|
199
|
-
* basic-host serve.ts).
|
|
200
|
-
* - Anything else → 404.
|
|
201
|
-
*
|
|
202
|
-
* Returns immediately once the server is listening; the handle's
|
|
203
|
-
* `close()` releases the port.
|
|
204
|
-
*/
|
|
205
|
-
export function startSandboxProxyServer(options) {
|
|
206
|
-
const host = options.host ?? '127.0.0.1';
|
|
207
|
-
const server = createServer((req, res) => {
|
|
208
|
-
// Resolve URL relative to a synthetic base so we can use
|
|
209
|
-
// URL parsing (req.url is path+query only).
|
|
210
|
-
const url = new URL(req.url ?? '/', 'http://placeholder');
|
|
211
|
-
if (req.method === 'GET' &&
|
|
212
|
-
(url.pathname === '/' || url.pathname === '/sandbox.html')) {
|
|
213
|
-
let csp;
|
|
214
|
-
const cspParam = url.searchParams.get('csp');
|
|
215
|
-
if (cspParam !== null) {
|
|
216
|
-
try {
|
|
217
|
-
const parsed = JSON.parse(cspParam);
|
|
218
|
-
if (parsed !== null && typeof parsed === 'object') {
|
|
219
|
-
csp = parsed;
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
catch {
|
|
223
|
-
// Ignore malformed CSP — fall through to the default
|
|
224
|
-
// (`'self' 'unsafe-inline'`) header. Visible in the
|
|
225
|
-
// browser's console as a CSP violation if the app loads
|
|
226
|
-
// external resources.
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
res.setHeader('Content-Security-Policy', buildCspHeader(csp));
|
|
230
|
-
res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate');
|
|
231
|
-
res.setHeader('Pragma', 'no-cache');
|
|
232
|
-
res.setHeader('Expires', '0');
|
|
233
|
-
res.setHeader('Content-Type', 'text/html; charset=utf-8');
|
|
234
|
-
// CORS: AppRenderer fetches the sandbox HTML cross-origin
|
|
235
|
-
// (parent vs. sandbox-proxy origin); the browser still
|
|
236
|
-
// navigates the iframe even without explicit CORS, but the
|
|
237
|
-
// header is harmless and unblocks any future fetch() probes.
|
|
238
|
-
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
239
|
-
res.statusCode = 200;
|
|
240
|
-
res.end(SANDBOX_HTML);
|
|
241
|
-
return;
|
|
242
|
-
}
|
|
243
|
-
res.statusCode = 404;
|
|
244
|
-
res.setHeader('Content-Type', 'text/plain; charset=utf-8');
|
|
245
|
-
res.end('sandbox proxy: only GET /sandbox.html is served here\n');
|
|
246
|
-
});
|
|
247
|
-
return new Promise((resolve, reject) => {
|
|
248
|
-
server.once('error', reject);
|
|
249
|
-
server.listen(options.port, host, () => {
|
|
250
|
-
const addr = server.address();
|
|
251
|
-
if (addr === null || typeof addr === 'string') {
|
|
252
|
-
reject(new Error(`startSandboxProxyServer: address() returned ${String(addr)}; expected AddressInfo`));
|
|
253
|
-
return;
|
|
254
|
-
}
|
|
255
|
-
const url = `http://${host}:${addr.port}/sandbox.html`;
|
|
256
|
-
resolve({
|
|
257
|
-
url,
|
|
258
|
-
port: addr.port,
|
|
259
|
-
close: () => new Promise((resolveClose, rejectClose) => {
|
|
260
|
-
server.close((err) => {
|
|
261
|
-
if (err)
|
|
262
|
-
rejectClose(err);
|
|
263
|
-
else
|
|
264
|
-
resolveClose();
|
|
265
|
-
});
|
|
266
|
-
}),
|
|
267
|
-
});
|
|
268
|
-
});
|
|
269
|
-
});
|
|
270
|
-
}
|