@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/dist/theme.js ADDED
@@ -0,0 +1,366 @@
1
+ /**
2
+ * `ggui.json#theme` → plain DTCG JSON document.
3
+ *
4
+ * The theme file is a plain JSON document matching the Design Tokens
5
+ * Community Group (DTCG) spec. It is one of the open file-format
6
+ * surfaces in the ggui manifest model, alongside
7
+ * `ggui.primitives.json` and `ggui.ui.json`.
8
+ *
9
+ * **Shape parity with the internal `DtcgTheme`.** This v1 schema is
10
+ * deliberately the same shape as `@ggui-ai/design`'s canonical
11
+ * {@link DtcgTheme} — `color`/`font`/`spacing`/`shape`/`motion`/
12
+ * `canvas`/`accessibility`/`zIndex` — so an authored `theme.json`
13
+ * uses the same vocabulary the curated registry themes (light/dark/
14
+ * premium-*) use. Previously this schema was Tailwind-style flat
15
+ * (`typography`/`radius`/`shadow` at root); the v1 schema now mirrors
16
+ * the internal shape to remove the divergence.
17
+ *
18
+ * **External-tool compatibility.** External tooling (Figma Tokens,
19
+ * Style Dictionary, Tokens Studio) commonly emits only a subset of
20
+ * the canonical shape. Only the original required groups (`color`,
21
+ * `font`, `spacing`, `shape`) stay required; `motion`, `canvas`,
22
+ * `accessibility`, `zIndex`, and the new DTCG metadata fields
23
+ * (`$name`, `$description`, `$metadata`) are all OPTIONAL so tools
24
+ * that only emit colors + dimensions still parse cleanly.
25
+ *
26
+ * **BREAKING for old `theme.json` files.** Authors who hand-wrote a
27
+ * `theme.json` against the pre-rc schema must rename:
28
+ *
29
+ * - `typography.fontFamily` → `font.family`
30
+ * - `typography.fontSize` → `font.size`
31
+ * - `typography.fontWeight` → `font.weight`
32
+ * - `typography.lineHeight` → `font.lineHeight`
33
+ * - top-level `radius` → `shape.radius`
34
+ * - top-level `shadow` → `shape.shadow`
35
+ * - top-level `duration` → `motion.duration`
36
+ * - top-level `transition` → `motion.transition`
37
+ *
38
+ * Plus: under `font.family`, `sans` is the only required sub-token
39
+ * (was previously freeform); other family slots (`mono`, …) are
40
+ * optional. The font-family record itself stays open so additional
41
+ * named families parse without a schema change.
42
+ *
43
+ * **Ownership boundary:**
44
+ *
45
+ * - *Where* the theme lives → `ggui.json#theme` (pointer string).
46
+ * - *What* the theme file looks like → this module's schema.
47
+ * - *How* DTCG tokens become CSS variables → `@ggui-ai/design`'s
48
+ * `generateCssVariables` (stays over there; this schema does not
49
+ * emit). The duck-typed walker accepts any DTCG-shaped tree, so
50
+ * the new nested groups (`font.size`, `shape.radius`, …) walk
51
+ * into `--ggui-font-size-md`/`--ggui-shape-radius-md` CSS vars
52
+ * without code changes.
53
+ * - *Built-in default* when `theme` is absent → `@ggui-ai/design`'s
54
+ * shipped `lightTheme`.
55
+ *
56
+ * **Extending rules:**
57
+ *
58
+ * 1. Additive only within `schema: '1'`. New optional fields must
59
+ * default to no-op behaviour so older tooling ignores them.
60
+ * 2. Framework-neutral + host-neutral. No vendor names in enum
61
+ * values, no build-pipeline fields. (`canvas` IS hosting-
62
+ * vendor vocabulary; it is opt-in via the optional field.)
63
+ * 3. Root and per-group objects are strict — unknown keys fail
64
+ * parse. Same discipline as `ggui.json`, `ggui.ui.json`, and
65
+ * `ggui.primitives.json`.
66
+ *
67
+ * **Strictness tradeoff:**
68
+ *
69
+ * Individual DTCG token leaves are validated with a discriminated
70
+ * union over `$type` (color / dimension / fontFamily / fontWeight /
71
+ * shadow / duration / cubicBezier / transition / number). Each
72
+ * variant validates its `$value` shape. `$description` is accepted
73
+ * everywhere as a free-form docstring. Token leaves under newer
74
+ * groups (`canvas`, `accessibility`, `zIndex`) accept a slightly
75
+ * wider token vocabulary because external tools sometimes encode
76
+ * scalar/array values with permissive `$type` strings (`string`,
77
+ * `array`, …); the schema accepts those without enumerating every
78
+ * possible spelling.
79
+ */
80
+ import { z } from 'zod';
81
+ // ─── Token leaves ────────────────────────────────────────────────────
82
+ /** A CSS color string. `$value` is not structurally validated (hex,
83
+ * rgba, color-mix, named, etc. are all legal DTCG colors). */
84
+ const ColorToken = z.strictObject({
85
+ $type: z.literal('color'),
86
+ $value: z.string().min(1),
87
+ $description: z.string().optional(),
88
+ });
89
+ /** A CSS dimension string (`"16px"`, `"1rem"`, `"0.5em"`, …). */
90
+ const DimensionToken = z.strictObject({
91
+ $type: z.literal('dimension'),
92
+ $value: z.string().min(1),
93
+ $description: z.string().optional(),
94
+ });
95
+ /** Font family stack. Array-form is canonical (matches how
96
+ * `generateCssVariables` emits `"Inter", "system-ui"`). String-form
97
+ * also accepted for single-family leaves. */
98
+ const FontFamilyToken = z.strictObject({
99
+ $type: z.literal('fontFamily'),
100
+ $value: z.union([z.string().min(1), z.array(z.string().min(1)).min(1)]),
101
+ $description: z.string().optional(),
102
+ });
103
+ /** Font weight — DTCG allows numeric weight (100..900) or keyword
104
+ * aliases; the design package's emitter handles both. */
105
+ const FontWeightToken = z.strictObject({
106
+ $type: z.literal('fontWeight'),
107
+ $value: z.union([z.number().int().min(1).max(1000), z.string().min(1)]),
108
+ $description: z.string().optional(),
109
+ });
110
+ /** A CSS duration (`"200ms"`, `"0.3s"`). */
111
+ const DurationToken = z.strictObject({
112
+ $type: z.literal('duration'),
113
+ $value: z.string().min(1),
114
+ $description: z.string().optional(),
115
+ });
116
+ /** DTCG cubic-bezier easing token. `$value` is a CSS timing function
117
+ * string (`"cubic-bezier(0.4, 0, 0.2, 1)"`, keyword aliases like
118
+ * `"ease-out"`, etc.). */
119
+ const CubicBezierToken = z.strictObject({
120
+ $type: z.literal('cubicBezier'),
121
+ $value: z.string().min(1),
122
+ $description: z.string().optional(),
123
+ });
124
+ /** Structured shadow value — DTCG spec composite shape. */
125
+ const ShadowValue = z.strictObject({
126
+ offsetX: z.string().min(1),
127
+ offsetY: z.string().min(1),
128
+ blur: z.string().min(1),
129
+ spread: z.string().min(1),
130
+ color: z.string().min(1),
131
+ });
132
+ /** Shadow token — accepts either the structured DTCG composite or a
133
+ * raw CSS box-shadow string (the curated registry themes ship the
134
+ * string form, e.g. `"0 1px 2px 0 rgba(0, 0, 0, 0.05)"`). */
135
+ const ShadowToken = z.strictObject({
136
+ $type: z.literal('shadow'),
137
+ $value: z.union([z.string().min(1), ShadowValue]),
138
+ $description: z.string().optional(),
139
+ });
140
+ /** Structured transition value. String-form also accepted because
141
+ * DTCG spec itself allows a single `property duration timing` line. */
142
+ const TransitionValue = z.strictObject({
143
+ duration: z.string().min(1),
144
+ timingFunction: z.string().min(1),
145
+ property: z.string().min(1).optional(),
146
+ });
147
+ const TransitionToken = z.strictObject({
148
+ $type: z.literal('transition'),
149
+ $value: z.union([z.string().min(1), TransitionValue]),
150
+ $description: z.string().optional(),
151
+ });
152
+ const NumberToken = z.strictObject({
153
+ $type: z.literal('number'),
154
+ $value: z.number(),
155
+ $description: z.string().optional(),
156
+ });
157
+ /** Line-height token — DTCG allows unit-less number or dimension. */
158
+ const LineHeightToken = z.union([
159
+ z.strictObject({
160
+ $type: z.literal('number'),
161
+ $value: z.number(),
162
+ $description: z.string().optional(),
163
+ }),
164
+ z.strictObject({
165
+ $type: z.literal('dimension'),
166
+ $value: z.string().min(1),
167
+ $description: z.string().optional(),
168
+ }),
169
+ ]);
170
+ /**
171
+ * Permissive token leaf for the newer optional groups (`canvas`,
172
+ * `motion.keyframes`, `zIndex`). DTCG hasn't standardised every
173
+ * `$type` value the curated registry themes carry — for example
174
+ * canvas mode uses `$type: 'string'`, canvas colors uses
175
+ * `$type: 'array'`, easing uses `$type: 'cubicBezier'`. Rather than
176
+ * enumerate every spelling, this leaf accepts any `$type` string and
177
+ * any JSON-serializable `$value`. Strict per-leaf shape stays in
178
+ * force for the original token types via the leaves above.
179
+ */
180
+ const PermissiveToken = z.strictObject({
181
+ $type: z.string().min(1),
182
+ $value: z.unknown(),
183
+ $description: z.string().optional(),
184
+ });
185
+ // ─── Groups ──────────────────────────────────────────────────────────
186
+ /**
187
+ * A "palette" group — a record of color tokens keyed by scale step
188
+ * (`50`, `100`, `200`, …). DTCG allows freeform keys so the record
189
+ * stays open; keys are not enforced at schema level.
190
+ */
191
+ const ColorPalette = z.record(z.string(), ColorToken);
192
+ /**
193
+ * Color group — two-tier: `{palette}` records for scales (primary /
194
+ * neutral / semantic scales) plus single-token semantic roles
195
+ * (surface / onSurface / outline / …) at the top level.
196
+ *
197
+ * Kept open (`z.record` not `z.strictObject`) so authored themes
198
+ * can add brand-specific palettes (`accent`, `brand-green`, …) and
199
+ * semantic roles without a schema change. `color` itself is
200
+ * required but its internal shape is author-extensible.
201
+ *
202
+ * Each entry can be EITHER a singleton `ColorToken` (e.g. a Material
203
+ * role like `surface`) OR a `ColorPalette` (a scale like `primary`'s
204
+ * 50-900 stops). Tools that emit only singleton colors (no scales)
205
+ * stay parseable.
206
+ */
207
+ const ColorGroup = z.record(z.string(), z.union([ColorToken, ColorPalette]));
208
+ const SpacingGroup = z.record(z.string(), DimensionToken);
209
+ /**
210
+ * Font group — `family` / `size` / `weight` / `lineHeight` records.
211
+ * Renamed from `typography` (previously: `typography.fontFamily`,
212
+ * `typography.fontSize`, …) to mirror the internal `DtcgTheme`
213
+ * shape's `font.family` / `font.size` / `font.weight` /
214
+ * `font.lineHeight`.
215
+ *
216
+ * Inside `family`, `sans` is the only required slot — the canonical
217
+ * theme always carries a sans-serif default. `mono` and any other
218
+ * family slot (e.g. `serif`, `display`) are optional. The record
219
+ * stays open so authors can declare additional named families.
220
+ */
221
+ const FontFamilyGroup = z.record(z.string(), FontFamilyToken).and(z.object({
222
+ sans: FontFamilyToken,
223
+ }));
224
+ const FontGroup = z.strictObject({
225
+ family: FontFamilyGroup,
226
+ size: z.record(z.string(), DimensionToken),
227
+ weight: z.record(z.string(), FontWeightToken),
228
+ lineHeight: z.record(z.string(), LineHeightToken),
229
+ });
230
+ /**
231
+ * Shape group — `radius` + `shadow`. Moved here from the previous
232
+ * top-level `radius` and `shadow` fields to mirror the internal
233
+ * `DtcgTheme.shape` group.
234
+ */
235
+ const ShapeGroup = z.strictObject({
236
+ radius: z.record(z.string(), DimensionToken),
237
+ shadow: z.record(z.string(), ShadowToken),
238
+ });
239
+ /**
240
+ * Motion group — `duration` / `easing` / `transition` / `keyframes`.
241
+ * Moved here from the previous top-level `duration` and `transition`
242
+ * fields. `easing` and `keyframes` are optional because external
243
+ * tools (Figma Tokens, Style Dictionary) don't always emit them.
244
+ */
245
+ const MotionGroup = z.strictObject({
246
+ duration: z.record(z.string(), DurationToken),
247
+ easing: z.record(z.string(), CubicBezierToken).optional(),
248
+ transition: z.record(z.string(), TransitionToken),
249
+ keyframes: z.record(z.string(), PermissiveToken).optional(),
250
+ });
251
+ /**
252
+ * Canvas group — GenerativeCanvas background configuration.
253
+ * Optional at the document root (external tools won't emit this).
254
+ * Token leaves use `PermissiveToken` because the canonical shape
255
+ * mixes `$type: 'string'` (mode), `$type: 'number'` (speed),
256
+ * `$type: 'array'` (colors), `$type: 'color'` (background).
257
+ */
258
+ const CanvasGroup = z.strictObject({
259
+ mode: PermissiveToken,
260
+ speed: PermissiveToken,
261
+ colors: PermissiveToken,
262
+ background: PermissiveToken,
263
+ });
264
+ const AccessibilityGroup = z.strictObject({
265
+ focusRing: z
266
+ .strictObject({
267
+ color: ColorToken,
268
+ width: DimensionToken,
269
+ offset: DimensionToken,
270
+ })
271
+ .optional(),
272
+ reducedMotion: z
273
+ .strictObject({
274
+ duration: DurationToken,
275
+ })
276
+ .optional(),
277
+ highContrast: z
278
+ .strictObject({
279
+ borderWidth: DimensionToken,
280
+ textColor: ColorToken,
281
+ backgroundColor: ColorToken,
282
+ linkColor: ColorToken,
283
+ })
284
+ .optional(),
285
+ });
286
+ const ZIndexGroup = z.record(z.string(), NumberToken);
287
+ /**
288
+ * Optional `$metadata` bag — mirrors the internal
289
+ * `DtcgTheme.$metadata` shape so registry themes can round-trip
290
+ * through the file format without information loss.
291
+ */
292
+ const MetadataGroup = z.strictObject({
293
+ font: z.string().min(1).optional(),
294
+ fontUrl: z.string().min(1).optional(),
295
+ philosophy: z.string().min(1).optional(),
296
+ });
297
+ // ─── Root document ───────────────────────────────────────────────────
298
+ /**
299
+ * Plain DTCG theme document v1. Required groups (`color`, `font`,
300
+ * `spacing`, `shape`) map 1:1 to `@ggui-ai/design`'s
301
+ * `generateCssVariables` walker targets — anything less would mean
302
+ * a partial CSS output worse than falling back to the shipped
303
+ * default. Optional groups (`motion`, `canvas`, `accessibility`,
304
+ * `zIndex`) are the well-known DTCG additives; present → emitted,
305
+ * absent → the design system's built-in fallbacks apply.
306
+ *
307
+ * `$name`, `$description`, and `$metadata` are accepted as optional
308
+ * DTCG-standard metadata. They round-trip cleanly to/from the
309
+ * internal `DtcgTheme` shape but external tools don't always emit
310
+ * them.
311
+ *
312
+ * Strict root: unknown top-level keys fail parse. `$schema`,
313
+ * `$version`, `$name`, `$description`, `$metadata` are all
314
+ * allow-listed (DTCG metadata); anything else is a typo. Additive
315
+ * slices add new fields here under `schema: '1'`.
316
+ */
317
+ export const ThemeDocumentV1 = z.strictObject({
318
+ /** Optional DTCG spec URL. Not validated — the spec hasn't frozen
319
+ * a canonical URL and hand-authored themes often omit it. */
320
+ $schema: z.string().min(1).optional(),
321
+ /** Optional author-declared version. Not validated — string-form. */
322
+ $version: z.string().min(1).optional(),
323
+ /** Optional human-readable theme name. DTCG metadata; mirrors
324
+ * `DtcgTheme.$name`. */
325
+ $name: z.string().min(1).optional(),
326
+ /** Optional human-readable theme description. DTCG metadata;
327
+ * mirrors `DtcgTheme.$description`. */
328
+ $description: z.string().min(1).optional(),
329
+ /** Optional metadata bag — font fallback, hosting-philosophy notes,
330
+ * etc. Mirrors `DtcgTheme.$metadata`. */
331
+ $metadata: MetadataGroup.optional(),
332
+ /** Color tokens — palettes + semantic roles. Required. */
333
+ color: ColorGroup,
334
+ /** Spacing scale. Required. */
335
+ spacing: SpacingGroup,
336
+ /** Font tokens — `family` / `size` / `weight` / `lineHeight`. Required. */
337
+ font: FontGroup,
338
+ /** Shape tokens — `radius` + `shadow`. Required. */
339
+ shape: ShapeGroup,
340
+ /** Motion tokens — `duration` + `transition` (required sub-records),
341
+ * `easing` + `keyframes` (optional sub-records). Optional. */
342
+ motion: MotionGroup.optional(),
343
+ /** Canvas configuration for the GenerativeCanvas background.
344
+ * Optional — external tools won't emit this. */
345
+ canvas: CanvasGroup.optional(),
346
+ /** Accessibility tokens (focus ring, reduced motion, high contrast).
347
+ * Optional. */
348
+ accessibility: AccessibilityGroup.optional(),
349
+ /** Z-index scale. Optional. */
350
+ zIndex: ZIndexGroup.optional(),
351
+ });
352
+ /**
353
+ * Parse a raw JSON value into a validated {@link ThemeDocument}.
354
+ * Throws a `ZodError` with human-readable issues on invalid input.
355
+ */
356
+ export function parseThemeDocument(raw) {
357
+ return ThemeDocumentV1.parse(raw);
358
+ }
359
+ /**
360
+ * Safe-parse variant — returns a discriminated `z.safeParse` result.
361
+ * Prefer this inside CLI tooling where you want to render the issue
362
+ * list without try/catch.
363
+ */
364
+ export function safeParseThemeDocument(raw) {
365
+ return ThemeDocumentV1.safeParse(raw);
366
+ }
@@ -0,0 +1,95 @@
1
+ /**
2
+ * `ggui.ui.json` v1 — per-UI manifest, colocated with source.
3
+ *
4
+ * One of three file-format surfaces in the ggui manifest model (see
5
+ * `schema.ts` header for the full context). Each authored UI ships
6
+ * its own `ggui.ui.json` next to its TSX source:
7
+ *
8
+ * ```
9
+ * ui/weather-card/
10
+ * ├── ggui.ui.json ← this file
11
+ * └── index.tsx
12
+ * ```
13
+ *
14
+ * The root `ggui.json`'s `blueprints.include` globs index these
15
+ * files at OSS-server boot. The `id` field is the stable identifier
16
+ * that crosses source boundaries — it's how publish, pull, conflict
17
+ * detection, and agent references address a UI independent of path
18
+ * or display name.
19
+ *
20
+ * **Identity rules:**
21
+ *
22
+ * 1. `id` is **required**. Zero-config isn't allowed here — a UI
23
+ * without an id can't be published, pulled, or referenced.
24
+ * 2. `id` is **stable across content edits**. Do NOT derive it from
25
+ * `contentHash` (which changes on every edit) or the display
26
+ * name (which is user-editable prose).
27
+ * 3. `id` is **machine-oriented**. Lowercase-ish, URL-safe. Not a
28
+ * UUID requirement — any stable-ish identifier works — but it
29
+ * must be grep-friendly, not a human display name.
30
+ * 4. `id` is **not path-derived**. Renaming or moving a UI file
31
+ * MUST NOT invalidate references. The id follows the UI.
32
+ *
33
+ * `contentHash` stays as a separate, derived field. It versions the
34
+ * compiled artifact and drives conflict detection on publish. It is
35
+ * NOT the identity.
36
+ */
37
+ import { z } from 'zod';
38
+ import type { DataContract } from '@ggui-ai/protocol';
39
+ /**
40
+ * Per-UI manifest schema. Strict-object for v1 — unknown fields
41
+ * fail parse. Additive fields land as coordinated schema changes.
42
+ */
43
+ export declare const UiManifestV1: z.ZodObject<{
44
+ /** Stable identity. See identity rules in module docstring. */
45
+ id: z.ZodString;
46
+ /** Human-readable name (e.g., "Weather Card", "Kanban Board"). */
47
+ name: z.ZodString;
48
+ /** One-line description of what this UI does. */
49
+ description: z.ZodOptional<z.ZodString>;
50
+ /** Data contract: props, actions, streams, wired tools, client tools. */
51
+ contract: z.ZodCustom<DataContract, DataContract>;
52
+ /** Category for browsing/filtering (e.g., "dashboard", "form", "data-viz"). */
53
+ category: z.ZodOptional<z.ZodString>;
54
+ /** Phrases that help blueprint-first matching find this UI. */
55
+ matchPatterns: z.ZodOptional<z.ZodArray<z.ZodString>>;
56
+ /** MCP tool names this UI is designed for. Used for discovery-layer matching. */
57
+ mcpTools: z.ZodOptional<z.ZodArray<z.ZodString>>;
58
+ /** MCP server name this UI is optimized for (e.g., "github", "stripe"). */
59
+ mcpServer: z.ZodOptional<z.ZodString>;
60
+ /** UI class: sandboxed (portable) or fullstack (requires client bundle). */
61
+ uiClass: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<"sandboxed">, z.ZodLiteral<"fullstack">]>>;
62
+ /** Content hash of the compiled ESM output — versions the artifact.
63
+ * Derived, not authored. NOT the identity (see `id`). */
64
+ contentHash: z.ZodOptional<z.ZodString>;
65
+ /** Parent content hash (for exported → modified → re-registered lineage). */
66
+ parentHash: z.ZodOptional<z.ZodString>;
67
+ /** Source entry point relative to project root (e.g., "src/component.tsx"). */
68
+ entryPoint: z.ZodOptional<z.ZodString>;
69
+ }, z.core.$strict>;
70
+ /** Static TypeScript type derived from the v1 schema. */
71
+ export type UiManifestV1 = z.infer<typeof UiManifestV1>;
72
+ /**
73
+ * Canonical type alias — `UiManifest` is the term used in protocol
74
+ * docs, code, and plan docs. `UiManifestV1` is the version-pinned
75
+ * name. Both resolve to the same shape; consumers may import either.
76
+ */
77
+ export type UiManifest = UiManifestV1;
78
+ /**
79
+ * Canonical filename for the per-UI manifest. Always this name,
80
+ * always colocated with the UI's source. Exported so tooling uses
81
+ * the constant instead of hard-coding the string.
82
+ */
83
+ export declare const GGUI_UI_JSON_FILENAME = "ggui.ui.json";
84
+ /**
85
+ * Parse a raw JSON value into a validated {@link UiManifest}.
86
+ * Throws a `ZodError` with human-readable issues on invalid input.
87
+ */
88
+ export declare function parseUiManifest(raw: unknown): UiManifest;
89
+ /**
90
+ * Safe-parse variant — returns a discriminated `z.safeParse` result.
91
+ * Prefer this inside CLI tooling where you want to render the issue
92
+ * list without try/catch.
93
+ */
94
+ export declare function safeParseUiManifest(raw: unknown): ReturnType<typeof UiManifestV1.safeParse>;
95
+ //# sourceMappingURL=ui-manifest.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ui-manifest.d.ts","sourceRoot":"","sources":["../src/ui-manifest.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AA4BtD;;;GAGG;AACH,eAAO,MAAM,YAAY;IACvB,+DAA+D;;IAG/D,kEAAkE;;IAGlE,iDAAiD;;IAGjD,yEAAyE;;IAGzE,+EAA+E;;IAG/E,+DAA+D;;IAK/D,iFAAiF;;IAGjF,2EAA2E;;IAK3E,4EAA4E;;IAG5E;6DACyD;;IAGzD,6EAA6E;;IAG7E,+EAA+E;;kBAE/E,CAAC;AAEH,yDAAyD;AACzD,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,YAAY,CAAC,CAAC;AAExD;;;;GAIG;AACH,MAAM,MAAM,UAAU,GAAG,YAAY,CAAC;AAEtC;;;;GAIG;AACH,eAAO,MAAM,qBAAqB,iBAAiB,CAAC;AAEpD;;;GAGG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,OAAO,GAAG,UAAU,CAExD;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CACjC,GAAG,EAAE,OAAO,GACX,UAAU,CAAC,OAAO,YAAY,CAAC,SAAS,CAAC,CAE3C"}
@@ -0,0 +1,114 @@
1
+ /**
2
+ * `ggui.ui.json` v1 — per-UI manifest, colocated with source.
3
+ *
4
+ * One of three file-format surfaces in the ggui manifest model (see
5
+ * `schema.ts` header for the full context). Each authored UI ships
6
+ * its own `ggui.ui.json` next to its TSX source:
7
+ *
8
+ * ```
9
+ * ui/weather-card/
10
+ * ├── ggui.ui.json ← this file
11
+ * └── index.tsx
12
+ * ```
13
+ *
14
+ * The root `ggui.json`'s `blueprints.include` globs index these
15
+ * files at OSS-server boot. The `id` field is the stable identifier
16
+ * that crosses source boundaries — it's how publish, pull, conflict
17
+ * detection, and agent references address a UI independent of path
18
+ * or display name.
19
+ *
20
+ * **Identity rules:**
21
+ *
22
+ * 1. `id` is **required**. Zero-config isn't allowed here — a UI
23
+ * without an id can't be published, pulled, or referenced.
24
+ * 2. `id` is **stable across content edits**. Do NOT derive it from
25
+ * `contentHash` (which changes on every edit) or the display
26
+ * name (which is user-editable prose).
27
+ * 3. `id` is **machine-oriented**. Lowercase-ish, URL-safe. Not a
28
+ * UUID requirement — any stable-ish identifier works — but it
29
+ * must be grep-friendly, not a human display name.
30
+ * 4. `id` is **not path-derived**. Renaming or moving a UI file
31
+ * MUST NOT invalidate references. The id follows the UI.
32
+ *
33
+ * `contentHash` stays as a separate, derived field. It versions the
34
+ * compiled artifact and drives conflict detection on publish. It is
35
+ * NOT the identity.
36
+ */
37
+ import { z } from 'zod';
38
+ /**
39
+ * Stable UI identity — lowercase-ish, URL-safe, ≤128 chars. Accepts
40
+ * letters, digits, hyphens, underscores, dots, and colons (for
41
+ * scoping like `company:weather-card`). Deliberately permissive
42
+ * enough to admit common conventions (uuids, nanoids, slug-like ids)
43
+ * without forcing one shape.
44
+ *
45
+ * Rejects whitespace, slashes, and unprintables so file paths can
46
+ * safely embed the id without escaping.
47
+ */
48
+ const UiIdSchema = z
49
+ .string()
50
+ .min(1)
51
+ .max(128)
52
+ .regex(/^[A-Za-z0-9][A-Za-z0-9._:-]*$/, {
53
+ message: 'ggui.ui.json.id must be 1-128 chars of A-Za-z0-9 plus . _ : -, starting with an alphanumeric.',
54
+ });
55
+ /**
56
+ * UI class — matches `UiClass` from `@ggui-ai/protocol` (vocabulary
57
+ * owned there so the `classifyUi()` runtime classifier can ship
58
+ * without a reverse dep on this package).
59
+ */
60
+ const UiClassSchema = z.union([z.literal('sandboxed'), z.literal('fullstack')]);
61
+ /**
62
+ * Per-UI manifest schema. Strict-object for v1 — unknown fields
63
+ * fail parse. Additive fields land as coordinated schema changes.
64
+ */
65
+ export const UiManifestV1 = z.strictObject({
66
+ /** Stable identity. See identity rules in module docstring. */
67
+ id: UiIdSchema,
68
+ /** Human-readable name (e.g., "Weather Card", "Kanban Board"). */
69
+ name: z.string().min(1).max(120),
70
+ /** One-line description of what this UI does. */
71
+ description: z.string().max(500).optional(),
72
+ /** Data contract: props, actions, streams, wired tools, client tools. */
73
+ contract: z.custom((v) => typeof v === 'object' && v !== null),
74
+ /** Category for browsing/filtering (e.g., "dashboard", "form", "data-viz"). */
75
+ category: z.string().max(60).optional(),
76
+ /** Phrases that help blueprint-first matching find this UI. */
77
+ matchPatterns: z.array(z.string().min(1)).optional(),
78
+ // ── MCP Pairing (optional) ─────────────────────────────────────────
79
+ /** MCP tool names this UI is designed for. Used for discovery-layer matching. */
80
+ mcpTools: z.array(z.string().min(1)).optional(),
81
+ /** MCP server name this UI is optimized for (e.g., "github", "stripe"). */
82
+ mcpServer: z.string().min(1).optional(),
83
+ // ── Classification & Lineage ───────────────────────────────────────
84
+ /** UI class: sandboxed (portable) or fullstack (requires client bundle). */
85
+ uiClass: UiClassSchema.optional(),
86
+ /** Content hash of the compiled ESM output — versions the artifact.
87
+ * Derived, not authored. NOT the identity (see `id`). */
88
+ contentHash: z.string().min(1).optional(),
89
+ /** Parent content hash (for exported → modified → re-registered lineage). */
90
+ parentHash: z.string().min(1).optional(),
91
+ /** Source entry point relative to project root (e.g., "src/component.tsx"). */
92
+ entryPoint: z.string().min(1).optional(),
93
+ });
94
+ /**
95
+ * Canonical filename for the per-UI manifest. Always this name,
96
+ * always colocated with the UI's source. Exported so tooling uses
97
+ * the constant instead of hard-coding the string.
98
+ */
99
+ export const GGUI_UI_JSON_FILENAME = 'ggui.ui.json';
100
+ /**
101
+ * Parse a raw JSON value into a validated {@link UiManifest}.
102
+ * Throws a `ZodError` with human-readable issues on invalid input.
103
+ */
104
+ export function parseUiManifest(raw) {
105
+ return UiManifestV1.parse(raw);
106
+ }
107
+ /**
108
+ * Safe-parse variant — returns a discriminated `z.safeParse` result.
109
+ * Prefer this inside CLI tooling where you want to render the issue
110
+ * list without try/catch.
111
+ */
112
+ export function safeParseUiManifest(raw) {
113
+ return UiManifestV1.safeParse(raw);
114
+ }
package/package.json ADDED
@@ -0,0 +1,67 @@
1
+ {
2
+ "name": "@ggui-ai/project-config",
3
+ "version": "0.1.0-rc.1",
4
+ "description": "Schema and loader for `ggui.json` — the portable app/agent manifest for the ggui protocol. Vendor-neutral. Consumed by the `ggui` CLI and any host that reconstitutes an agent from the manifest.",
5
+ "keywords": [
6
+ "ggui",
7
+ "manifest",
8
+ "schema",
9
+ "config",
10
+ "mcp",
11
+ "zod"
12
+ ],
13
+ "license": "Apache-2.0",
14
+ "type": "module",
15
+ "main": "dist/index.js",
16
+ "types": "dist/index.d.ts",
17
+ "files": [
18
+ "dist",
19
+ "README.md"
20
+ ],
21
+ "exports": {
22
+ ".": {
23
+ "types": "./dist/index.d.ts",
24
+ "import": "./dist/index.js",
25
+ "default": "./dist/index.js"
26
+ },
27
+ "./node": {
28
+ "types": "./dist/node.d.ts",
29
+ "import": "./dist/node.js",
30
+ "default": "./dist/node.js"
31
+ }
32
+ },
33
+ "dependencies": {
34
+ "tinyglobby": "^0.2.16",
35
+ "zod": "^4.3.6",
36
+ "@ggui-ai/protocol": "0.1.0-rc.1",
37
+ "@ggui-ai/design": "0.1.0-rc.1"
38
+ },
39
+ "devDependencies": {
40
+ "@types/node": "^24.0.0",
41
+ "typescript": "^5.0.0",
42
+ "vitest": "^3.0.0"
43
+ },
44
+ "repository": {
45
+ "type": "git",
46
+ "url": "https://github.com/ggui-ai/ggui.git",
47
+ "directory": "packages/project-config"
48
+ },
49
+ "homepage": "https://github.com/ggui-ai/ggui/tree/main/packages/project-config",
50
+ "bugs": {
51
+ "url": "https://github.com/ggui-ai/ggui/issues"
52
+ },
53
+ "publishConfig": {
54
+ "access": "public"
55
+ },
56
+ "engines": {
57
+ "node": ">=20.0.0"
58
+ },
59
+ "author": "ggui contributors <hello@ggui.ai>",
60
+ "scripts": {
61
+ "build": "tsc -p tsconfig.build.json",
62
+ "dev": "tsc --watch",
63
+ "typecheck": "tsc --noEmit",
64
+ "test": "vitest run",
65
+ "test:watch": "vitest"
66
+ }
67
+ }