@ggui-ai/project-config 0.1.0-rc.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/LICENSE +201 -0
- package/README.md +66 -0
- package/dist/discovery.d.ts +74 -0
- package/dist/discovery.d.ts.map +1 -0
- package/dist/discovery.js +167 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +15 -0
- package/dist/mcp-mount-discovery.d.ts +82 -0
- package/dist/mcp-mount-discovery.d.ts.map +1 -0
- package/dist/mcp-mount-discovery.js +226 -0
- package/dist/node.d.ts +69 -0
- package/dist/node.d.ts.map +1 -0
- package/dist/node.js +157 -0
- package/dist/primitive-discovery.d.ts +95 -0
- package/dist/primitive-discovery.d.ts.map +1 -0
- package/dist/primitive-discovery.js +281 -0
- package/dist/primitives-manifest.d.ts +106 -0
- package/dist/primitives-manifest.d.ts.map +1 -0
- package/dist/primitives-manifest.js +131 -0
- package/dist/schema.d.ts +520 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +556 -0
- package/dist/theme-loader.d.ts +104 -0
- package/dist/theme-loader.d.ts.map +1 -0
- package/dist/theme-loader.js +263 -0
- package/dist/theme.d.ts +311 -0
- package/dist/theme.d.ts.map +1 -0
- package/dist/theme.js +366 -0
- package/dist/ui-manifest.d.ts +95 -0
- package/dist/ui-manifest.d.ts.map +1 -0
- package/dist/ui-manifest.js +114 -0
- package/package.json +67 -0
package/dist/schema.js
ADDED
|
@@ -0,0 +1,556 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `ggui.json` v1 — the OSS `ggui` server's runtime source of truth.
|
|
3
|
+
*
|
|
4
|
+
* When a developer runs `ggui serve`, the OSS server reads this file
|
|
5
|
+
* to discover everything it needs: app identity, protocol version,
|
|
6
|
+
* blueprint manifests, primitive packages, theme tokens, declared
|
|
7
|
+
* adapter intent. The schema is framework-neutral — it describes
|
|
8
|
+
* *the ggui app*, never the internal orchestration of any specific
|
|
9
|
+
* agent runtime. LLM choice, system prompt, framework-specific tool
|
|
10
|
+
* inventory belong in each framework's own config, not here.
|
|
11
|
+
*
|
|
12
|
+
* The manifest surface model:
|
|
13
|
+
*
|
|
14
|
+
* - `ggui.json` — this file. Open. Root manifest.
|
|
15
|
+
* - `ggui.ui.json` — one per authored UI, colocated with source.
|
|
16
|
+
* Indexed via `blueprints.include` globs.
|
|
17
|
+
*
|
|
18
|
+
* **Rules for extending:**
|
|
19
|
+
*
|
|
20
|
+
* 1. **Additive only within `schema: '1'`.** Optional fields with
|
|
21
|
+
* defaults are safe. New top-level fields MUST default to
|
|
22
|
+
* behavioural no-ops so older tooling can ignore them.
|
|
23
|
+
* 2. **Framework-neutral.** If a field is meaningful only to one
|
|
24
|
+
* agent framework (Claude SDK, Vercel AI SDK, LangGraph, ADK,
|
|
25
|
+
* CrewAI, Mastra, etc.), it does NOT belong here. Same manifest
|
|
26
|
+
* describes the same ggui app across all frameworks.
|
|
27
|
+
* 3. **Host-neutral.** Build / runtime / deploy / sizing fields live
|
|
28
|
+
* in a hosting-vendor overlay, never here.
|
|
29
|
+
* 4. **No vendor names in enum values.** `residency: 'acme-cloud'`
|
|
30
|
+
* is the exact mistake a previous draft made. If an enum needs
|
|
31
|
+
* to name a hosting provider, it belongs in that provider's
|
|
32
|
+
* overlay.
|
|
33
|
+
*
|
|
34
|
+
* **Strictness (locked 2026-04-18):**
|
|
35
|
+
*
|
|
36
|
+
* Root object is strict — unknown top-level keys cause parse to
|
|
37
|
+
* fail. This catches typos (`"blueprint"` singular, `"component"`
|
|
38
|
+
* for `"primitives"`, etc.) and prevents silent drift toward the
|
|
39
|
+
* agent-runtime-centric shape we explicitly rejected. Nested
|
|
40
|
+
* objects are also strict for v1; additive fields are a coordinated
|
|
41
|
+
* schema change, not a drive-by.
|
|
42
|
+
*/
|
|
43
|
+
import { z } from 'zod';
|
|
44
|
+
import { appPublicEnvSchema, strictGadgetDescriptorSchema, } from '@ggui-ai/protocol';
|
|
45
|
+
/**
|
|
46
|
+
* App slug — URL-safe identifier. 2–64 chars, lowercase
|
|
47
|
+
* alphanumeric + hyphens, no leading/trailing hyphen. Used as a
|
|
48
|
+
* namespace primitive by the ggui protocol for addressing.
|
|
49
|
+
*/
|
|
50
|
+
const SlugSchema = z
|
|
51
|
+
.string()
|
|
52
|
+
.regex(/^[a-z0-9][a-z0-9-]{0,62}[a-z0-9]$/, {
|
|
53
|
+
message: 'Slug must be lowercase alphanumeric with hyphens, 2-64 chars, not starting or ending with a hyphen.',
|
|
54
|
+
});
|
|
55
|
+
/**
|
|
56
|
+
* ggui protocol version. Two accepted shapes:
|
|
57
|
+
*
|
|
58
|
+
* - **Prelaunch draft-date** — `draft-YYYY-MM-DD` (e.g.,
|
|
59
|
+
* `draft-2026-04-19`). This is the canonical prelaunch form;
|
|
60
|
+
* `@ggui-ai/protocol#PROTOCOL_VERSION` currently ships this shape.
|
|
61
|
+
* - **Semver** — `major.minor`, `major.minor.patch`, with optional
|
|
62
|
+
* pre-release suffix (e.g., `1.0.0`, `2.0.0-alpha.1`). This becomes
|
|
63
|
+
* canonical once the protocol is frozen for launch (first release:
|
|
64
|
+
* `1.0.0`).
|
|
65
|
+
*
|
|
66
|
+
* The wire contract version this app speaks — distinct from the
|
|
67
|
+
* `schema` field which versions the file format itself.
|
|
68
|
+
*
|
|
69
|
+
* Future tightening: could import a known-versions list from
|
|
70
|
+
* `@ggui-ai/protocol` and validate membership. Deliberately left as
|
|
71
|
+
* pattern-only for v1 to avoid the cross-package dep until a real
|
|
72
|
+
* consumer benefits from the tighter check.
|
|
73
|
+
*/
|
|
74
|
+
const ProtocolVersionSchema = z
|
|
75
|
+
.string()
|
|
76
|
+
.regex(/^(draft-\d{4}-\d{2}-\d{2}|\d+\.\d+(?:\.\d+)?(?:-[0-9A-Za-z.-]+)?)$/, {
|
|
77
|
+
message: 'Protocol version must be `draft-YYYY-MM-DD` (prelaunch) or semver `major.minor[.patch][-pre-release]`.',
|
|
78
|
+
});
|
|
79
|
+
/**
|
|
80
|
+
* App identity — the minimal set of fields every ggui consumer
|
|
81
|
+
* needs to address this app. **Identity only.** No mode, no
|
|
82
|
+
* framework, no LLM config, no runtime policy.
|
|
83
|
+
*/
|
|
84
|
+
const AppSchema = z.strictObject({
|
|
85
|
+
slug: SlugSchema,
|
|
86
|
+
name: z.string().min(1).max(120),
|
|
87
|
+
/**
|
|
88
|
+
* Per-app gadget catalog. The CLI
|
|
89
|
+
* passes these through to `InMemoryAppMetadataStore` so the
|
|
90
|
+
* handshake / push / ops handlers can validate every
|
|
91
|
+
* `clientCapabilities.gadgets[*]` ref against the registered
|
|
92
|
+
* set's package identity + export names. Each entry is a gadget
|
|
93
|
+
* PACKAGE and MUST satisfy the strict
|
|
94
|
+
* `strictGadgetDescriptorSchema` from `@ggui-ai/protocol`
|
|
95
|
+
* (a non-empty `exports[]` array, each export carrying required
|
|
96
|
+
* description / usage / example).
|
|
97
|
+
*
|
|
98
|
+
* Omitting this field falls back to the protocol's
|
|
99
|
+
* `STDLIB_GADGETS` defaults (the 7 first-party hooks).
|
|
100
|
+
* Declared 3rd-party wrappers (Leaflet, Mapbox, Stripe, …) land
|
|
101
|
+
* here.
|
|
102
|
+
*/
|
|
103
|
+
gadgets: z
|
|
104
|
+
.array(strictGadgetDescriptorSchema)
|
|
105
|
+
.optional(),
|
|
106
|
+
/**
|
|
107
|
+
* Public env channel. Operator-stamped key→value map that
|
|
108
|
+
* the server projects (filtered to declared wrappers' `requires`)
|
|
109
|
+
* onto `globalThis.__ggui__.publicEnv` so wrapper hooks can read
|
|
110
|
+
* values via `getPublicEnv(key)`.
|
|
111
|
+
*
|
|
112
|
+
* Keys MUST match `GGUI_PUBLIC_APP_[A-Z0-9_]+` — the `GGUI_PUBLIC_USER_`
|
|
113
|
+
* namespace is reserved for a future per-user channel. Values are
|
|
114
|
+
* arbitrary strings; the server projects only keys whose values are
|
|
115
|
+
* declared in some registered wrapper's `requires`. Operators who
|
|
116
|
+
* declare a key without a value can use the empty string (the
|
|
117
|
+
* Mapbox SDK will surface "Invalid token" on first request rather
|
|
118
|
+
* than a missing-key error from the push gate).
|
|
119
|
+
*
|
|
120
|
+
* Omitting this field is equivalent to `{}` — wrappers without
|
|
121
|
+
* `requires` work unchanged; wrappers that declare `requires` will
|
|
122
|
+
* fail at push-gate validation if their required keys are missing.
|
|
123
|
+
*/
|
|
124
|
+
publicEnv: appPublicEnvSchema.optional(),
|
|
125
|
+
/**
|
|
126
|
+
* MCP-Apps presentation mode (canvas slice). `'canvas'` makes
|
|
127
|
+
* `ggui_new_session` mint a session-scoped iframe
|
|
128
|
+
* (`ui://ggui/session/<sessionId>`) and route subsequent pushes
|
|
129
|
+
* through the session WebSocket channel instead of returning a
|
|
130
|
+
* per-push `ui://ggui/render/<shortCode>` resource. `'inline'`
|
|
131
|
+
* (default when omitted) preserves the per-push iframe behavior
|
|
132
|
+
* that flagship MCP-Apps hosts already expect.
|
|
133
|
+
*/
|
|
134
|
+
defaultMcpAppsMode: z.enum(['inline', 'canvas']).optional(),
|
|
135
|
+
});
|
|
136
|
+
/**
|
|
137
|
+
* Operator identity — who's running this server. Surfaced on the
|
|
138
|
+
* public welcome page at `/` so visitors see "operated by X" instead
|
|
139
|
+
* of unbranded server chrome.
|
|
140
|
+
*
|
|
141
|
+
* **Privacy-first by default.** Every field is optional. Nothing is
|
|
142
|
+
* shown when nothing is set — no "anonymous" placeholder, no
|
|
143
|
+
* hostname-derived fallback. The visitor sees just server identity
|
|
144
|
+
* and public-surface affordances (login link, share-link hints).
|
|
145
|
+
*
|
|
146
|
+
* Distinct from `app` (which identifies the *server*) — `operator`
|
|
147
|
+
* identifies the *human/entity* running the server. A single
|
|
148
|
+
* operator can run multiple `app`s, but the welcome page always
|
|
149
|
+
* shows ONE operator block (one server = one ggui.json).
|
|
150
|
+
*
|
|
151
|
+
* **What this is NOT:**
|
|
152
|
+
* - Not auth (the admin token is its own credential)
|
|
153
|
+
* - Not telemetry (server doesn't phone home with operator info)
|
|
154
|
+
* - Not user/builder identity (those are MCP-pairing concerns)
|
|
155
|
+
*/
|
|
156
|
+
const OperatorSchema = z.strictObject({
|
|
157
|
+
/** Display name shown on the welcome page. e.g. "Wanseob Lim" or
|
|
158
|
+
* "Loqu Inc.". Free-form — no slug constraint. */
|
|
159
|
+
name: z.string().min(1).max(200).optional(),
|
|
160
|
+
/** Operator's homepage / brand URL. Linkified on the welcome page
|
|
161
|
+
* if set. Must be a valid http(s) URL. */
|
|
162
|
+
url: z
|
|
163
|
+
.url({ message: 'operator.url must be a valid http(s) URL' })
|
|
164
|
+
.optional(),
|
|
165
|
+
/** One-line description shown under the operator name. e.g.
|
|
166
|
+
* "personal ggui server — tinkering only" or "Loqu's hosted ggui
|
|
167
|
+
* for friends + family". Plain text, no markup. */
|
|
168
|
+
tagline: z.string().min(1).max(280).optional(),
|
|
169
|
+
/** mailto-eligible contact address. Linkified as `mailto:` on the
|
|
170
|
+
* welcome page if set. Operators uncomfortable putting this on a
|
|
171
|
+
* public page should leave it unset — `name` and `url` carry the
|
|
172
|
+
* "how to reach me" load just as well. */
|
|
173
|
+
contact: z
|
|
174
|
+
.email({ message: 'operator.contact must be a valid email' })
|
|
175
|
+
.optional(),
|
|
176
|
+
});
|
|
177
|
+
/**
|
|
178
|
+
* Blueprint discovery block. Points at per-UI `ggui.ui.json`
|
|
179
|
+
* manifests colocated with their TSX sources. The OSS server
|
|
180
|
+
* resolves the globs at boot and indexes the matched manifests.
|
|
181
|
+
*
|
|
182
|
+
* Glob-based by design — adding a new UI is a filesystem
|
|
183
|
+
* operation, not a `ggui.json` edit. If a project needs explicit
|
|
184
|
+
* per-manifest declarations (mixed source roots, selective
|
|
185
|
+
* registration), a `list` field can land later as an additive
|
|
186
|
+
* union; for v1 the blessed path is globs.
|
|
187
|
+
*/
|
|
188
|
+
const BlueprintsSchema = z.strictObject({
|
|
189
|
+
/** Glob patterns relative to the `ggui.json` directory. Each match
|
|
190
|
+
* should resolve to a `ggui.ui.json` file. */
|
|
191
|
+
include: z.array(z.string().min(1)).default([]),
|
|
192
|
+
});
|
|
193
|
+
/**
|
|
194
|
+
* Primitive discovery block. Tells the OSS server where to find
|
|
195
|
+
* the component primitives the UI generator can compose from.
|
|
196
|
+
*
|
|
197
|
+
* - `packages` — npm package specifiers (`@ggui-ai/design/primitives`,
|
|
198
|
+
* `@mycompany/ui`). The server resolves the package and
|
|
199
|
+
* enumerates its declared primitives. The exact package-level
|
|
200
|
+
* discovery convention (`package.json#ggui.primitives`,
|
|
201
|
+
* per-primitive manifests, etc.) is an open design question
|
|
202
|
+
* pending its own slice — the schema accepts the declaration
|
|
203
|
+
* shape regardless of which convention wins.
|
|
204
|
+
* - `local` — glob patterns resolving to per-primitive manifest
|
|
205
|
+
* files (`ggui.primitive.json`) for primitives authored inside
|
|
206
|
+
* the project. Symmetric with `blueprints.include`.
|
|
207
|
+
*/
|
|
208
|
+
const PrimitivesSchema = z.strictObject({
|
|
209
|
+
packages: z
|
|
210
|
+
.array(z.string().min(1))
|
|
211
|
+
.default(['@ggui-ai/design/primitives']),
|
|
212
|
+
local: z.array(z.string().min(1)).default([]),
|
|
213
|
+
});
|
|
214
|
+
/**
|
|
215
|
+
* Agent block — tells `ggui serve` where the user's agent lives.
|
|
216
|
+
*
|
|
217
|
+
* Only one field in v1: `entry`, a path (relative to the directory
|
|
218
|
+
* containing `ggui.json`) to the agent's entrypoint. The `ggui serve`
|
|
219
|
+
* command resolves this path, maps the extension to a spawn command
|
|
220
|
+
* (`.js/.mjs/.cjs` → `node <entry>`; `.ts/.tsx/.mts` →
|
|
221
|
+
* `node --import=tsx <entry>`), and supervises the subprocess
|
|
222
|
+
* alongside the MCP server.
|
|
223
|
+
*
|
|
224
|
+
* Namespace justification: the thing we're pointing at IS the agent
|
|
225
|
+
* (user code), not the "runtime" (the supervisor). `agent.*` has room
|
|
226
|
+
* to grow (`agent.env`, `agent.port` if/when real needs surface); none
|
|
227
|
+
* of that is in this slice. `app.agent` was rejected — `app.*` is
|
|
228
|
+
* identity, not execution config. `runtime.*` was rejected — muddles
|
|
229
|
+
* user-code vs supervisor.
|
|
230
|
+
*
|
|
231
|
+
* Deliberately not in v1: env vars, restart policies, port hints,
|
|
232
|
+
* arg arrays. Every one of those fields has a reasonable default
|
|
233
|
+
* (empty env / crash-and-exit / OS-assigned / no args) and can be
|
|
234
|
+
* added additively later under `schema: '1'`.
|
|
235
|
+
*
|
|
236
|
+
* **Block-level optional**: The entire `agent` block is optional.
|
|
237
|
+
* Absent means "run MCP-only" — `ggui serve` boots the MCP server
|
|
238
|
+
* without attaching an agent loop.
|
|
239
|
+
*/
|
|
240
|
+
const AgentSchema = z.strictObject({
|
|
241
|
+
/** Path to the agent's entry file, relative to the ggui.json
|
|
242
|
+
* directory. Supported extensions: .js, .mjs, .cjs, .ts, .tsx,
|
|
243
|
+
* .mts. Validation of extension happens at `ggui serve` time —
|
|
244
|
+
* the schema stays permissive so malformed entries surface with
|
|
245
|
+
* the CLI's error message (which can list supported extensions +
|
|
246
|
+
* remediation), not a zod issue trace. */
|
|
247
|
+
entry: z.string().min(1, 'agent.entry must not be empty'),
|
|
248
|
+
});
|
|
249
|
+
/**
|
|
250
|
+
* Theme color mode. The same identifier the design registry uses
|
|
251
|
+
* (`@ggui-ai/design`'s `ThemeMode`). Held here as a literal union
|
|
252
|
+
* so project-config can validate without a runtime dep on design —
|
|
253
|
+
* the two stay structurally identical (single source of truth is
|
|
254
|
+
* the JSON wire shape, not either type alias).
|
|
255
|
+
*/
|
|
256
|
+
const ThemeModeSchema = z.enum(['light', 'dark']);
|
|
257
|
+
/**
|
|
258
|
+
* Flat dot-path token overrides applied on top of a registered
|
|
259
|
+
* preset's tokens.
|
|
260
|
+
*
|
|
261
|
+
* Keys are dot-paths into the DTCG token tree (`color.primary.500`,
|
|
262
|
+
* `shape.radius.lg`, `font.family.sans`). Values replace the
|
|
263
|
+
* resolved leaf's `$value`. Unknown paths are silently ignored at
|
|
264
|
+
* load time — the console token editor mints valid keys, and an
|
|
265
|
+
* accidentally-stale override should not fail manifest parse.
|
|
266
|
+
*
|
|
267
|
+
* Why flat (not deep tree): the editor mutates leaves one at a
|
|
268
|
+
* time and serializes a diff; flat keys make the diff trivially
|
|
269
|
+
* computable and `JSON.stringify` round-trip stable. Authoring by
|
|
270
|
+
* hand is a side path.
|
|
271
|
+
*/
|
|
272
|
+
const ThemeOverridesSchema = z.record(z.string(), z.string());
|
|
273
|
+
/**
|
|
274
|
+
* `theme: { preset, mode?, overrides? }` — pick a registered preset
|
|
275
|
+
* from `@ggui-ai/design`'s registry. `preset` is the registry key
|
|
276
|
+
* (e.g. `'claudic'`, `'ggui'`, `'premium-zen'`). The loader
|
|
277
|
+
* returns an issue when the id is unregistered.
|
|
278
|
+
*/
|
|
279
|
+
const ThemePresetConfigSchema = z.strictObject({
|
|
280
|
+
preset: z.string().min(1),
|
|
281
|
+
mode: ThemeModeSchema.optional(),
|
|
282
|
+
overrides: ThemeOverridesSchema.optional(),
|
|
283
|
+
});
|
|
284
|
+
/**
|
|
285
|
+
* `theme: { file, mode? }` — load tokens from a project-relative
|
|
286
|
+
* DTCG JSON file. The `mode` is metadata only (the file's tokens
|
|
287
|
+
* ARE the resolved theme, not a switch); it propagates to consumers
|
|
288
|
+
* that need to vary based on color scheme (e.g. injecting
|
|
289
|
+
* `color-scheme: dark` on the document).
|
|
290
|
+
*/
|
|
291
|
+
const ThemeFileConfigSchema = z.strictObject({
|
|
292
|
+
file: z.string().min(1),
|
|
293
|
+
mode: ThemeModeSchema.optional(),
|
|
294
|
+
});
|
|
295
|
+
/**
|
|
296
|
+
* Discriminated theme selection. Three accepted shapes:
|
|
297
|
+
*
|
|
298
|
+
* 1. `string` — preset id shorthand (`theme: 'claudic'` ≡
|
|
299
|
+
* `theme: { preset: 'claudic' }`)
|
|
300
|
+
* 2. `{ preset, mode?, overrides? }` — registered preset with
|
|
301
|
+
* optional mode + override layer
|
|
302
|
+
* 3. `{ file, mode? }` — DTCG JSON file
|
|
303
|
+
*
|
|
304
|
+
* Strict objects on shapes 2 and 3 reject unknown keys to catch
|
|
305
|
+
* typos at parse (`{ presset: 'claudic' }` fails, not silently
|
|
306
|
+
* loads default).
|
|
307
|
+
*/
|
|
308
|
+
export const ThemeConfigSchema = z.union([
|
|
309
|
+
z.string().min(1),
|
|
310
|
+
ThemePresetConfigSchema,
|
|
311
|
+
ThemeFileConfigSchema,
|
|
312
|
+
]);
|
|
313
|
+
/**
|
|
314
|
+
* Storage block — opt-in persistence wiring for the OSS `ggui serve`
|
|
315
|
+
* runtime. Absent means **every storage surface stays in-memory** —
|
|
316
|
+
* the zero-config OSS default (state resets on process restart).
|
|
317
|
+
* Present means the operator has explicitly asked for the named
|
|
318
|
+
* adapters; no surface ever silently creates a file on disk without
|
|
319
|
+
* the operator declaring it here.
|
|
320
|
+
*
|
|
321
|
+
* ## Why `{sessions, vectors}` together
|
|
322
|
+
*
|
|
323
|
+
* Sessions + vectors are the two substantial persistence surfaces
|
|
324
|
+
* the OSS server honors today. Shipping them in the same block from
|
|
325
|
+
* v1 avoids a second schema churn when vectors or a future store
|
|
326
|
+
* (e.g. threads) lands — there's one `storage` shape and each known
|
|
327
|
+
* surface is an optional child. A future `storage.threads` or
|
|
328
|
+
* `storage.blueprints` slice adds a sibling field without reshaping
|
|
329
|
+
* the block.
|
|
330
|
+
*
|
|
331
|
+
* ## Why per-surface `driver`
|
|
332
|
+
*
|
|
333
|
+
* Explicit driver makes the choice visible at a glance — both to
|
|
334
|
+
* the operator reading the file and to future adapters that land
|
|
335
|
+
* against the same interfaces (`postgres`, `redis`, `libsql`, …).
|
|
336
|
+
* A flat `sessions: "./path.sqlite"` shape would bake sqlite into
|
|
337
|
+
* the schema and force a breaking redesign the first time another
|
|
338
|
+
* driver landed.
|
|
339
|
+
*
|
|
340
|
+
* ## Why per-surface `path` instead of a shared root
|
|
341
|
+
*
|
|
342
|
+
* Sessions + vectors can (and typically should) live in separate
|
|
343
|
+
* files: session data is small + hot-path; vector data is large +
|
|
344
|
+
* append-heavy. Coupling them into one file conflates two different
|
|
345
|
+
* IO profiles. Operators who want co-located storage can point both
|
|
346
|
+
* paths at the same file — but the schema doesn't force that.
|
|
347
|
+
*
|
|
348
|
+
* ## Deliberately out of scope (v1)
|
|
349
|
+
*
|
|
350
|
+
* - `driver: 'postgres' | 'redis' | …` — future additive unions
|
|
351
|
+
* once a reference adapter lands.
|
|
352
|
+
* - Connection pools, WAL pragma toggles, backup schedules — all
|
|
353
|
+
* real, all belong in an adapter-specific options block (not
|
|
354
|
+
* here) if they ever need to be surfaced.
|
|
355
|
+
* - `encryption` / `at-rest` settings — cross-cutting concern;
|
|
356
|
+
* not an OSS-v1 responsibility.
|
|
357
|
+
* - Secret expansion (`${VAR}` in paths) — operators can emit
|
|
358
|
+
* the manifest from templated input today; a schema-level
|
|
359
|
+
* token is a future additive.
|
|
360
|
+
*
|
|
361
|
+
* ## Strictness
|
|
362
|
+
*
|
|
363
|
+
* Root `storage` is strict — unknown surface keys (`sesions: …`,
|
|
364
|
+
* `vectros: …`, `blueprints: …`) fail parse. Per-surface shapes
|
|
365
|
+
* are discriminated on `driver`; each variant is strict so typos
|
|
366
|
+
* like `{driver: 'sqlite', paht: './x'}` fail immediately.
|
|
367
|
+
*/
|
|
368
|
+
const StorageMemorySchema = z.strictObject({
|
|
369
|
+
driver: z.literal('memory'),
|
|
370
|
+
});
|
|
371
|
+
const StorageSqliteSchema = z.strictObject({
|
|
372
|
+
driver: z.literal('sqlite'),
|
|
373
|
+
/** Filesystem path to the SQLite database file. Relative paths
|
|
374
|
+
* resolve from the ggui.json directory at adapter-instantiation
|
|
375
|
+
* time. Empty strings are rejected at parse. */
|
|
376
|
+
path: z.string().min(1, 'storage.<surface>.path must not be empty'),
|
|
377
|
+
});
|
|
378
|
+
/**
|
|
379
|
+
* Discriminated union over storage drivers. Add new drivers as
|
|
380
|
+
* additive siblings under the same discriminator — never widen an
|
|
381
|
+
* existing variant, since that would silently change what older
|
|
382
|
+
* manifests mean.
|
|
383
|
+
*/
|
|
384
|
+
const StorageSurfaceSchema = z.discriminatedUnion('driver', [
|
|
385
|
+
StorageMemorySchema,
|
|
386
|
+
StorageSqliteSchema,
|
|
387
|
+
]);
|
|
388
|
+
const StorageSchema = z.strictObject({
|
|
389
|
+
/** Session persistence (events + stack + identity). Absent =
|
|
390
|
+
* in-memory (sessions reset on restart). */
|
|
391
|
+
sessions: StorageSurfaceSchema.optional(),
|
|
392
|
+
/** Vector index persistence (RAG, blueprint embeddings). Absent =
|
|
393
|
+
* in-memory (index rebuilds on restart). */
|
|
394
|
+
vectors: StorageSurfaceSchema.optional(),
|
|
395
|
+
/** Persistent-chat thread + message persistence. Absent = in-memory
|
|
396
|
+
* (threads vanish on restart — correct for dev, unsafe for any
|
|
397
|
+
* deployment Portal's self-hosted flow talks to). Pair with
|
|
398
|
+
* `driver: 'sqlite'` + a `path` for durable local storage; the
|
|
399
|
+
* `@ggui-ai/mcp-server-core/sqlite#SqliteThreadStore` reference
|
|
400
|
+
* impl consumes the same file-per-surface layout the sessions/
|
|
401
|
+
* vectors drivers already use. */
|
|
402
|
+
threads: StorageSurfaceSchema.optional(),
|
|
403
|
+
});
|
|
404
|
+
/**
|
|
405
|
+
* The authoritative v1 schema. `z.infer<typeof GguiJsonV1>` yields
|
|
406
|
+
* the static TypeScript type. A valid document round-trips cleanly:
|
|
407
|
+
* parse → `JSON.stringify` → re-parse produces an equivalent value.
|
|
408
|
+
*
|
|
409
|
+
* **Required fields:** `schema`, `protocol`, `app`.
|
|
410
|
+
* **Optional fields with defaults:** `blueprints`, `primitives`,
|
|
411
|
+
* `adapters`. A zero-config `ggui.json` is just the three required
|
|
412
|
+
* fields — the OSS server boots with defaults for everything else.
|
|
413
|
+
* **Optional without default:** `theme` (absent = shipped default
|
|
414
|
+
* tokens), `agent` (absent = `ggui serve` runs MCP-only).
|
|
415
|
+
*
|
|
416
|
+
* **Strict root:** unknown top-level keys are rejected.
|
|
417
|
+
*/
|
|
418
|
+
export const GguiJsonV1 = z.strictObject({
|
|
419
|
+
/** File-format version — always `"1"` for v1. Distinct from
|
|
420
|
+
* `protocol` (which versions the wire contract the app speaks). */
|
|
421
|
+
schema: z
|
|
422
|
+
.literal('1')
|
|
423
|
+
.describe('File-format version. Always "1" for v1. Distinct from `protocol` (which versions the wire contract the app speaks).'),
|
|
424
|
+
/** ggui wire protocol version the app speaks. */
|
|
425
|
+
protocol: ProtocolVersionSchema.describe('ggui wire protocol version the app speaks. Pinned per file-format version.'),
|
|
426
|
+
/** App identity. */
|
|
427
|
+
app: AppSchema.describe('App identity — slug + display name. Slug is the stable machine identifier; name is human-facing.'),
|
|
428
|
+
/** Operator identity — who's running this server. Optional;
|
|
429
|
+
* surfaced on the public welcome page at `/` when set. Privacy-
|
|
430
|
+
* first: nothing renders when nothing is set, no "anonymous"
|
|
431
|
+
* placeholder. See {@link OperatorConfig}. */
|
|
432
|
+
operator: OperatorSchema.optional().describe('Operator identity surfaced on the public welcome page. All subfields optional; absent = no operator block rendered (privacy-first default).'),
|
|
433
|
+
/** Blueprint discovery. Defaults to no custom UIs declared. */
|
|
434
|
+
blueprints: BlueprintsSchema.default({ include: [] }).describe('Glob patterns the OSS server walks to discover authored UIs. Each match must point to a `ggui.ui.json` colocated with its TSX source. Empty default = no custom blueprints registered.'),
|
|
435
|
+
/** Primitive discovery. Defaults to the shipped
|
|
436
|
+
* `@ggui-ai/design/primitives` package + no local primitives. */
|
|
437
|
+
primitives: PrimitivesSchema.default({
|
|
438
|
+
packages: ['@ggui-ai/design/primitives'],
|
|
439
|
+
local: [],
|
|
440
|
+
}).describe('Where the server discovers UI primitives. `packages[]` lists npm packages shipping a `ggui.primitives.json` manifest; `local[]` lists project-relative manifest paths. Defaults to the shipped `@ggui-ai/design/primitives` package.'),
|
|
441
|
+
/**
|
|
442
|
+
* Theme selection. Three accepted shapes:
|
|
443
|
+
*
|
|
444
|
+
* - **Shorthand string** — `theme: "claudic"`. Picks a preset from
|
|
445
|
+
* the `@ggui-ai/design` registry. Default mode is `'light'`.
|
|
446
|
+
* - **Preset object** — `theme: { preset: "claudic", mode: "dark",
|
|
447
|
+
* overrides: { "color.primary.500": "#cc785c" } }`. Picks a
|
|
448
|
+
* registered preset, optional explicit mode, optional flat
|
|
449
|
+
* dot-path token overrides applied on top of the preset's
|
|
450
|
+
* tokens before CSS emission.
|
|
451
|
+
* - **File object** — `theme: { file: "./theme.json", mode: "dark" }`.
|
|
452
|
+
* Loads tokens from a project-relative DTCG JSON file. The
|
|
453
|
+
* `mode` is metadata only — the file's tokens are the resolved
|
|
454
|
+
* theme; mode propagates to consumers that vary based on it
|
|
455
|
+
* (e.g. `color-scheme: dark` injection).
|
|
456
|
+
*
|
|
457
|
+
* Absent (`theme: undefined`) means use the shipped default tokens
|
|
458
|
+
* from `@ggui-ai/design` (currently `lightTheme`).
|
|
459
|
+
*/
|
|
460
|
+
theme: ThemeConfigSchema.optional().describe('Theme selection. Accepts a preset id (string), a `{ preset, mode, overrides }` object, or a `{ file, mode }` object pointing at a DTCG JSON tokens file. Absent = shipped default tokens.'),
|
|
461
|
+
/** Agent-runtime config for `ggui serve`. Absent means the
|
|
462
|
+
* command boots MCP-only (no agent loop attached). */
|
|
463
|
+
agent: AgentSchema.optional().describe('Agent-runtime config for `ggui serve`. Absent = command boots MCP-only (no agent loop attached).'),
|
|
464
|
+
/** Explicit storage adapter declarations. Absent means every
|
|
465
|
+
* persistence surface stays in-memory (the zero-config OSS
|
|
466
|
+
* default). Present means the operator has opted in per surface
|
|
467
|
+
* — no surface silently creates a file on disk. See
|
|
468
|
+
* {@link StorageConfig}. */
|
|
469
|
+
storage: StorageSchema.optional().describe('Per-surface storage adapter declarations. Absent = every persistence surface (sessions, threads, vectors, kv) stays in-memory (zero-config OSS default). Present = opt-in per surface; no surface silently creates a file on disk.'),
|
|
470
|
+
/**
|
|
471
|
+
* Default plugin-registry URL for this app.
|
|
472
|
+
*
|
|
473
|
+
* When the user runs `ggui install <scope/name@version>` or `ggui
|
|
474
|
+
* publish` from this project, the CLI resolves the target registry
|
|
475
|
+
* via a three-layer chain (highest priority wins):
|
|
476
|
+
*
|
|
477
|
+
* 1. Explicit `--registry=<url>` flag passed to the CLI.
|
|
478
|
+
* 2. `GGUI_REGISTRY` environment variable.
|
|
479
|
+
* 3. This `registry` field in `ggui.json`.
|
|
480
|
+
* 4. (none → error) The CLI ships with NO hard-coded default;
|
|
481
|
+
* operators MUST pick a registry per app.
|
|
482
|
+
*
|
|
483
|
+
* Mirrors the npm `--registry` / `npm_config_registry` / `.npmrc`
|
|
484
|
+
* resolution pattern but anchored on `ggui.json` for the project-
|
|
485
|
+
* local layer (instead of a dotfile) — the same file that holds
|
|
486
|
+
* `app.gadgets` is the natural place to declare which
|
|
487
|
+
* marketplace those libraries come from.
|
|
488
|
+
*
|
|
489
|
+
* Validated as a URL at schema-parse time; the CLI does not
|
|
490
|
+
* second-guess scheme / host beyond URL-shape (HTTPS recommendation
|
|
491
|
+
* is enforced at install-time, not here, so dev fixtures pointing
|
|
492
|
+
* at `http://localhost:<port>` parse cleanly).
|
|
493
|
+
*
|
|
494
|
+
* Absent = no project-local default; the CLI falls through to env
|
|
495
|
+
* var or flag, and errors if neither is set.
|
|
496
|
+
*/
|
|
497
|
+
registry: z
|
|
498
|
+
.url({ message: 'registry must be a valid URL' })
|
|
499
|
+
.optional()
|
|
500
|
+
.describe('Default plugin-registry URL for `ggui install` / `ggui publish`. Three-layer resolution: `--registry` flag > `GGUI_REGISTRY` env > this field > error if unset. Absent = no project-local default.'),
|
|
501
|
+
/**
|
|
502
|
+
* Local MCP tool mounts — the operator-facing seam for declaring
|
|
503
|
+
* additional tool surfaces that aggregate onto `/mcp` alongside
|
|
504
|
+
* ggui-native tools (`ggui_push`, etc.). Each entry is a module
|
|
505
|
+
* specifier:
|
|
506
|
+
*
|
|
507
|
+
* - Relative paths (`./path/to/mount.js`, `../local/mcp.mjs`)
|
|
508
|
+
* resolve against the directory containing `ggui.json`. Must
|
|
509
|
+
* include the file extension — we do not guess `.js` vs
|
|
510
|
+
* `.mjs`. File must exist.
|
|
511
|
+
* - Bare npm specifiers (`@my-org/mcp-mount`, `some-pkg/entry`)
|
|
512
|
+
* resolve through Node's module resolver anchored at the
|
|
513
|
+
* project root.
|
|
514
|
+
*
|
|
515
|
+
* Each resolved module must export a `createGguiMcpMount` function
|
|
516
|
+
* (or a default export) returning `{ name: string; handlers:
|
|
517
|
+
* SharedHandler[] }` — i.e., an `McpServerMount` from
|
|
518
|
+
* `@ggui-ai/mcp-server`. The factory owns its own state (backing
|
|
519
|
+
* store, seed, etc.). Zero-argument today; arguments are a future
|
|
520
|
+
* additive (widen the entry to `string | { module, options? }`).
|
|
521
|
+
*
|
|
522
|
+
* Default: `[]` (no mounts). Collisions between mount tool names
|
|
523
|
+
* and ggui-native tool names (or between two mounts) are rejected
|
|
524
|
+
* at `createGguiServer` composition time, not silently — see
|
|
525
|
+
* `packages/mcp-server/src/mcp-mounts.ts::composeHandlersWithMounts`. */
|
|
526
|
+
mcpMounts: z
|
|
527
|
+
.array(z.string().min(1, 'mcpMounts entries must be non-empty module specifiers'))
|
|
528
|
+
.default([])
|
|
529
|
+
.describe('Local MCP tool mounts — each entry is a module specifier (relative path or bare npm specifier) exporting `createGguiMcpMount`. The factory returns `{name, handlers}` aggregated onto `/mcp` alongside ggui-native tools. Tool-name collisions are rejected at composition time.'),
|
|
530
|
+
});
|
|
531
|
+
/**
|
|
532
|
+
* Canonical filename — always at the project root, always this
|
|
533
|
+
* name. Exported so tooling uses the constant instead of
|
|
534
|
+
* hard-coding the string.
|
|
535
|
+
*/
|
|
536
|
+
export const GGUI_JSON_FILENAME = 'ggui.json';
|
|
537
|
+
/**
|
|
538
|
+
* Parse a raw JSON value into a validated {@link GguiJsonV1}.
|
|
539
|
+
* Throws a `ZodError` with human-readable issues on invalid input.
|
|
540
|
+
*
|
|
541
|
+
* Accepts any `unknown` — callers are expected to have already
|
|
542
|
+
* decoded the JSON (`JSON.parse(source)`). Applies defaults for
|
|
543
|
+
* absent optional fields.
|
|
544
|
+
*/
|
|
545
|
+
export function parseGguiJson(raw) {
|
|
546
|
+
return GguiJsonV1.parse(raw);
|
|
547
|
+
}
|
|
548
|
+
/**
|
|
549
|
+
* Safe-parse variant — returns a discriminated `z.safeParse` result
|
|
550
|
+
* (`{ success: true, data }` vs `{ success: false, error }`). Prefer
|
|
551
|
+
* this inside CLI tooling where you want to render the issue list
|
|
552
|
+
* without try/catch.
|
|
553
|
+
*/
|
|
554
|
+
export function safeParseGguiJson(raw) {
|
|
555
|
+
return GguiJsonV1.safeParse(raw);
|
|
556
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import type { DtcgTheme, ThemeMode } from '@ggui-ai/design/themes';
|
|
2
|
+
import type { GguiJsonV1, ThemeConfig } from './schema.js';
|
|
3
|
+
import { type ThemeDocument } from './theme.js';
|
|
4
|
+
/**
|
|
5
|
+
* One loaded theme — discriminated on `source`. Every variant carries
|
|
6
|
+
* the resolved `mode`, the parsed token document, and the pre-rendered
|
|
7
|
+
* `--ggui-*` CSS variable block.
|
|
8
|
+
*
|
|
9
|
+
* **`document` is shape-discriminated by `source`** (no casts, two
|
|
10
|
+
* structurally-similar but separately-validated underlying types):
|
|
11
|
+
*
|
|
12
|
+
* - `default` / `preset` → `DtcgTheme` (the canonical theme shape
|
|
13
|
+
* consumed by every registry-side theme — produces full
|
|
14
|
+
* `--ggui-*` CSS through `parseTheme`; every group required).
|
|
15
|
+
* - `file` → `ThemeDocument` (the strict-Zod schema for the open
|
|
16
|
+
* `ggui.json#theme` file format — same `color`/`font`/`spacing`/
|
|
17
|
+
* `shape`/`motion`/`canvas`/`accessibility`/`zIndex` vocabulary
|
|
18
|
+
* as `DtcgTheme`, but `motion`/`canvas`/`accessibility`/`zIndex`
|
|
19
|
+
* are optional so external tools that emit only a subset still
|
|
20
|
+
* parse).
|
|
21
|
+
*
|
|
22
|
+
* Consumers narrow on `source` if they ever need the shape. CSS-variable
|
|
23
|
+
* emission happens upstream into `cssVariables`, so most downstream code
|
|
24
|
+
* only reads that string.
|
|
25
|
+
*/
|
|
26
|
+
export type LoadedTheme = {
|
|
27
|
+
readonly source: 'default';
|
|
28
|
+
readonly mode: ThemeMode;
|
|
29
|
+
readonly document: DtcgTheme;
|
|
30
|
+
readonly cssVariables: string;
|
|
31
|
+
} | {
|
|
32
|
+
readonly source: 'preset';
|
|
33
|
+
/** Registry id (`'claudic'`, `'ggui'`, …). */
|
|
34
|
+
readonly preset: string;
|
|
35
|
+
readonly mode: ThemeMode;
|
|
36
|
+
/** Flat dot-path token overrides applied on top of the preset. */
|
|
37
|
+
readonly overrides?: Record<string, string>;
|
|
38
|
+
readonly document: DtcgTheme;
|
|
39
|
+
readonly cssVariables: string;
|
|
40
|
+
} | {
|
|
41
|
+
readonly source: 'file';
|
|
42
|
+
/** Absolute filesystem path of the loaded theme file. */
|
|
43
|
+
readonly path: string;
|
|
44
|
+
readonly mode: ThemeMode;
|
|
45
|
+
readonly document: ThemeDocument;
|
|
46
|
+
readonly cssVariables: string;
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
49
|
+
* An error surfaced during theme load. Shape matches the issue
|
|
50
|
+
* streams blueprint + primitive discovery produce so CLI renderers
|
|
51
|
+
* can treat all three identically.
|
|
52
|
+
*/
|
|
53
|
+
export interface ThemeLoadIssue {
|
|
54
|
+
/**
|
|
55
|
+
* Path of the offending theme source. For file sources this is
|
|
56
|
+
* the resolved absolute path; for preset sources it's the preset
|
|
57
|
+
* id (so the CLI line reads `theme: claudic — not registered`
|
|
58
|
+
* cleanly).
|
|
59
|
+
*/
|
|
60
|
+
path: string;
|
|
61
|
+
message: string;
|
|
62
|
+
cause?: unknown;
|
|
63
|
+
}
|
|
64
|
+
/** Result of {@link loadTheme}. One of two exhaustive branches. */
|
|
65
|
+
export type LoadThemeResult = {
|
|
66
|
+
readonly ok: true;
|
|
67
|
+
readonly theme: LoadedTheme;
|
|
68
|
+
} | {
|
|
69
|
+
readonly ok: false;
|
|
70
|
+
readonly issue: ThemeLoadIssue;
|
|
71
|
+
};
|
|
72
|
+
/**
|
|
73
|
+
* Options accepted by {@link loadTheme}. `projectRoot` is required —
|
|
74
|
+
* relative `manifest.theme.file` paths are resolved from the
|
|
75
|
+
* directory containing `ggui.json` (same rule `storage.*.path` uses).
|
|
76
|
+
*/
|
|
77
|
+
export interface LoadThemeOptions {
|
|
78
|
+
/** Absolute project root — the directory containing `ggui.json`. */
|
|
79
|
+
projectRoot: string;
|
|
80
|
+
/** Parsed `ggui.json`. Only the optional `theme` field is consumed. */
|
|
81
|
+
manifest: GguiJsonV1;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Load the theme declared in `ggui.json#theme`, or fall back to the
|
|
85
|
+
* shipped default when the field is absent.
|
|
86
|
+
*
|
|
87
|
+
* Non-throwing — single-issue failure mode returned as a tagged
|
|
88
|
+
* result. Throws only for programmer errors (non-absolute
|
|
89
|
+
* `projectRoot`), matching the other discovery helpers.
|
|
90
|
+
*/
|
|
91
|
+
export declare function loadTheme(options: LoadThemeOptions): LoadThemeResult;
|
|
92
|
+
/**
|
|
93
|
+
* Convenience wrapper for the "I have a `ggui.json` path" flow.
|
|
94
|
+
* Resolves the project root from the manifest path.
|
|
95
|
+
*/
|
|
96
|
+
export declare function loadThemeFromGguiJsonPath(manifestPath: string, manifest: GguiJsonV1): LoadThemeResult;
|
|
97
|
+
/**
|
|
98
|
+
* Re-exported for callers that need to accept any of the parse-time
|
|
99
|
+
* theme shapes (string shorthand, preset object, file object) and
|
|
100
|
+
* dispatch on the discriminator. `ThemeConfig` is the static
|
|
101
|
+
* counterpart of `ggui.json#theme`'s zod union.
|
|
102
|
+
*/
|
|
103
|
+
export type { ThemeConfig };
|
|
104
|
+
//# sourceMappingURL=theme-loader.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"theme-loader.d.ts","sourceRoot":"","sources":["../src/theme-loader.ts"],"names":[],"mappings":"AA4CA,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACnE,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC3D,OAAO,EAAsB,KAAK,aAAa,EAAE,MAAM,YAAY,CAAC;AAEpE;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,MAAM,WAAW,GACnB;IACE,QAAQ,CAAC,MAAM,EAAE,SAAS,CAAC;IAC3B,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB,QAAQ,CAAC,QAAQ,EAAE,SAAS,CAAC;IAC7B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;CAC/B,GACD;IACE,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC;IAC1B,8CAA8C;IAC9C,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB,kEAAkE;IAClE,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC5C,QAAQ,CAAC,QAAQ,EAAE,SAAS,CAAC;IAC7B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;CAC/B,GACD;IACE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,yDAAyD;IACzD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAC;IACjC,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;CAC/B,CAAC;AAEN;;;;GAIG;AACH,MAAM,WAAW,cAAc;IAC7B;;;;;OAKG;IACH,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,mEAAmE;AACnE,MAAM,MAAM,eAAe,GACvB;IACE,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;IAClB,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAC;CAC7B,GACD;IACE,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;IACnB,QAAQ,CAAC,KAAK,EAAE,cAAc,CAAC;CAChC,CAAC;AAEN;;;;GAIG;AACH,MAAM,WAAW,gBAAgB;IAC/B,oEAAoE;IACpE,WAAW,EAAE,MAAM,CAAC;IACpB,uEAAuE;IACvE,QAAQ,EAAE,UAAU,CAAC;CACtB;AAED;;;;;;;GAOG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,gBAAgB,GAAG,eAAe,CAwCpE;AAyMD;;;GAGG;AACH,wBAAgB,yBAAyB,CACvC,YAAY,EAAE,MAAM,EACpB,QAAQ,EAAE,UAAU,GACnB,eAAe,CAGjB;AAED;;;;;GAKG;AACH,YAAY,EAAE,WAAW,EAAE,CAAC"}
|