@getvitops/cli 0.9.0 → 2.0.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/CHANGELOG.md CHANGED
@@ -1,5 +1,445 @@
1
1
  # @getvitops/cli
2
2
 
3
+ ## 2.0.0
4
+
5
+ ### Minor Changes
6
+
7
+ - bf453b0: Anywhere the toolchain takes a config, that file may now be a `design-system.json` **or** the larger
8
+ site config that embeds one.
9
+
10
+ If you keep your tokens inside a `company.json` / `site.json`, you had to maintain a second file for
11
+ the tooling's sake — or duplicate your whole token set, because a site config must carry a complete
12
+ `designSystem`. Now you point the same option at the file you already have:
13
+
14
+ ```js
15
+ // astro.config.mjs
16
+ getvitops({ css: { input: 'company.json', format: 'css' } });
17
+ ```
18
+
19
+ ```
20
+ vitops generate --input company.json --format css --out dist
21
+ vitops lint --input company.json --src src
22
+ ```
23
+
24
+ The design system is taken from `designSystem.themes[theme]` with its `extends` chain resolved —
25
+ `defaultTheme`, else `default`, else whatever `--theme` / the `theme` option names. Nothing changes
26
+ for a plain `design-system.json`; the two are told apart by shape, not by filename, so you can call
27
+ the file anything.
28
+
29
+ **A site config also supplies the site-level facts generation reads**, so the path is declared once
30
+ rather than per option:
31
+ - `designSystem.defaultColorScheme: "system"` — emits the `prefers-color-scheme` block, which is
32
+ what makes `<color-scheme-toggle>`'s System position do anything.
33
+ - `legal.*.enabled` — renders the documents. `legal: {}` is now the whole declaration; `legal.input`
34
+ is optional.
35
+ - `icons.sprite` — builds `icons.svg`.
36
+ - `fonts` — `fonts: true` reads the families from it.
37
+
38
+ Each of those is already gated on a field in that config, so nothing new appears unless the config
39
+ asks for it. An explicit `site` option still wins when the two are genuinely different files.
40
+
41
+ Also:
42
+ - **`vitops validate` routes on the file's shape.** Pointed at a site config it used to report a
43
+ single `unrecognized_keys` for `designSystem` and nothing about the file's actual contents — a
44
+ wrong answer that reads like a right one. It now validates a site config as one, including the
45
+ cross-field integrity JSON Schema can't express, and checks every theme resolves to a complete
46
+ design system (`validateSite` only checked that the `extends` chain resolved, not what it resolved
47
+ to).
48
+ - **`generate()` gained `theme` and `siteEnv`.** The A/B variant for `siteEnv` is applied before the
49
+ theme is selected, so a variant can override tokens.
50
+ - **The theme editor's Save to source follows the design system into a site config.** Its patch is
51
+ design-system-relative, so the dev server merges into that subtree and writes only the surrounding
52
+ file whole. It locates the subtree in the raw on-disk object rather than the normalised one, so an
53
+ author who wrote either `designSystem` shorthand still gets their own keys edited.
54
+
55
+ - 04a51d8: Icons: one semantic vocabulary across icon sets, with the bundle derived from your source.
56
+
57
+ Name icons by meaning — `<Icon name="menu" />` resolves to `fa7-solid:bars`, `ph:list` or
58
+ `lucide:menu` depending on the set you configure, so swapping sets is a config edit rather than a
59
+ find-and-replace. A name containing `:` still passes through untouched, which is the escape hatch
60
+ for a set-specific glyph.
61
+
62
+ **New `icons` option on `getvitops()`.** Configures the sets once per site and, under
63
+ `output: 'server'`, derives astro-icon's `include` map by scanning your source — the list most
64
+ projects end up maintaining by hand. On a static build no `include` is passed at all, because
65
+ astro-icon is already zero-config there and a list could only drop a glyph the scan couldn't see.
66
+ Names you declare that don't resolve fail the build; names only the scan found just warn; names
67
+ computed at runtime are reported with file and line so you can declare them.
68
+
69
+ **New `<Icon />` component**, and a real fix with it: `Popover`, `Details` and `Drawer` imported
70
+ `astro-icon/components` at module scope, so the _optional_ peer was resolved whether or not an icon
71
+ ever rendered — hard-failing anyone who hadn't installed it. Engines now load dynamically. The
72
+ per-component `iconResolver` prop still works but is deprecated in favour of the integration option.
73
+
74
+ **New SVG sprite** (`icons.sprite: true` in your site config) for consumers that can't run an icon
75
+ integration — Bricks/WordPress, EmDash renderers, plain HTML. `<use href="…/icons.svg#ph--list">`,
76
+ no JavaScript and no icon-API request. Every semantic name also gets a set-independent `icon-<name>`
77
+ alias, so sprite markup survives an icon-set change. WordPress gets `vitops_icon()`, a
78
+ `[vitops_icon]` shortcode and a **Vitops → Icon** Bricks element.
79
+
80
+ **New `vitops icons`** command: reports which icons your source uses, which names couldn't be
81
+ resolved, and which are computed at runtime; `--sprite` builds the sprite, `--json` for CI.
82
+
83
+ **Renamed, and completed to all four directions.** Chevrons and arrows are the
84
+ one family whose _meaning is_ its direction, and that direction is physical, not
85
+ logical — a chevron in a details marker points down in every writing mode. So they
86
+ are named for where they point: `expand-more`/`expand-less` →
87
+ `chevron-down`/`chevron-up`, and `arrow-forward`/`arrow-back` →
88
+ `arrow-right`/`arrow-left`. `chevron-left`/`chevron-right` keep their names.
89
+ `arrow-up`/`arrow-down` are new, so both families now cover all four directions.
90
+ `lightning` is new.
91
+
92
+ If you passed any of the old names to `<Icon />`, `resolveIcon` or an `icons`
93
+ config, update them. They fail loudly rather than silently: an unresolvable
94
+ declared name throws, and `vitops icons` reports scanned ones.
95
+
96
+ **Fixed three Font Awesome mappings that named no real glyph** and so rendered an
97
+ empty box: `login`/`logout` were mapped to `login`/`logout` (Font Awesome calls
98
+ them `right-to-bracket`/`right-from-bracket`) and `backup` to `backup`
99
+ (`cloud-arrow-up`). Every value in all four UI sets is now checked against the
100
+ installed collection.
101
+
102
+ **Phosphor (`ph`) joins the semantic map**, with all 83 names verified against the real icon set.
103
+ Phosphor keeps every weight in one collection and varies the name (`list`, `list-bold`), unlike Font
104
+ Awesome's per-weight collections, so `resolveIcon` and `generateIconInclude` gained a `weight`
105
+ option for sets shaped that way.
106
+
107
+ Fixes: `site.icons` was a closed object, so any icon collection not in its hand-written key list was
108
+ silently dropped during validation — your config passed and the icons never bundled. It accepts any
109
+ collection name now.
110
+
111
+ - bf453b0: `vitops lint` now catches hand-written CSS that re-implements a framework primitive.
112
+
113
+ The existing linter finds classes that resolve to nothing — you named something and it isn't there.
114
+ This is the costlier inverse, where the code **works**: a centred container written by hand, a
115
+ two-column split behind a media query, a flex row. Nothing is broken, so nothing ever surfaces it,
116
+ and the design system quietly stops being where those decisions live.
117
+
118
+ Three rules to start, each requiring the combination that makes the intent unambiguous
119
+ (`margin-inline: auto` alone is not a centred track):
120
+
121
+ | Found | Suggested |
122
+ | ----------------------------------------------------------- | ------------------------------------------------------------------------- |
123
+ | `max-inline-size: var(--width-*)` + `margin-inline: auto` | `.centered`, widening a child with `breakout` / `spotlight` / `fullbleed` |
124
+ | `grid-template-columns: 1fr 2fr` inside a `min-width` query | `md-split-1-2` — or `@md:split-1-2` in the tailwind format |
125
+ | `display: flex` + `align-items: center` | `flex items-center`, or `.cluster-start` |
126
+
127
+ **Findings now carry a severity, and suggestions do not fail the command.** These are judgement
128
+ calls, and a reuse hint that broke CI on the day it shipped would be a worse defect than the drift it
129
+ reports. `--strict` promotes them for anyone who wants the ratchet. Everything the linter reported
130
+ before is an `error` and still exits 1.
131
+
132
+ `.css` files and `<style>` blocks are now read (the class linter only ever looked at markup). The
133
+ generated stylesheet is skipped by its `GENERATED … do not edit by hand` banner rather than by path —
134
+ the `css` format writes `styles.css` into `src/styles`, squarely inside what `--src` scans, and
135
+ linting it made the framework report `.split` as reinventing `.split`.
136
+
137
+ Two other gaps closed:
138
+ - **The format-spelling check ran in one direction only.** `md-split-1-2` in a tailwind project was
139
+ caught; `@md:split-1-2` in a css/bricks project was not, because variant stripping removed the
140
+ `@md:` regardless of format and left a bare class that matched nothing. The same silent no-op, in
141
+ the other direction, unreported. Stripping is format-aware now.
142
+ - **`grid-auto` and the whole `m-*` rhythm family were missing from the tailwind format entirely** —
143
+ not dropped via `TW_CLASH`, just never re-emitted when `layout.css` is skipped and a subset
144
+ re-emitted in its place. Unlike the `<bp>-` classes they have no Tailwind equivalent, and unlike a
145
+ misspelt class the linter cannot flag them, because they aren't anchored to your config. They emit
146
+ as `@utility` definitions now, keyed to the same `--rhythm-*` variables as the css format.
147
+
148
+ Run against this repo's own docs site, the rules found one real instance on the first pass; it is
149
+ fixed in the same change.
150
+
151
+ - bf453b0: Maskable favicons are now opaque, so they stop rendering as a logo in a black box.
152
+
153
+ `icon-mask.png` is declared `purpose: "maskable"` and `apple-touch-icon.png` is linked on every
154
+ page. Both sit a deliberately-inset logo on a larger canvas — that inset **is** the maskable safe
155
+ zone and was always correct — but the canvas was filled with `alpha: 0`. From any transparent
156
+ source that left 36% of `icon-mask.png` and 40% of `apple-touch-icon.png` transparent, on two files
157
+ whose entire contract is full bleed: the OS crops them to its own shape and composites the rest onto
158
+ whatever it likes, usually black. iOS discards alpha on the apple-touch-icon outright.
159
+
160
+ `backgroundColor` was already the obvious input for this and already plumbed — as far as the web
161
+ manifest, and no further. So the raster and the manifest disagreed about the very colour meant to
162
+ sit behind the icon.
163
+
164
+ Now:
165
+ - those two outputs are composited onto `backgroundColor`, defaulting to `#ffffff` — the same
166
+ default the manifest's `background_color` already used;
167
+ - `favicon.svg`, `icon-{16,32,192,512}.png` and `favicon.ico` keep the source's transparency, since
168
+ none of them is maskable;
169
+ - a transparent source with no `backgroundColor` set now warns, rather than silently inheriting
170
+ white — a dark logo would lose against it;
171
+ - `icon-192`/`icon-512` declare `purpose: "any"` explicitly. Omitting it was legal (unset means
172
+ "any"), but with a maskable present and nothing claiming "any", some launchers picked the maskable
173
+ — the one with the safe-zone inset — for slots that wanted a plain icon.
174
+
175
+ `vitops favicon` gained `--background <hex>`; it previously had no way to set this at all.
176
+ `getvitops({ favicon })` and the Vite plugin now forward the option they were already accepting.
177
+
178
+ ### Patch Changes
179
+
180
+ - bf453b0: Say plainly that `fonts` in `design-system.json` is stacks only and loads nothing.
181
+
182
+ The field's description was complete and self-consistent — "raw font stacks by name, emitted as
183
+ `--font-<name>` tokens" — and told you nothing about where the `@font-face` comes from. Following
184
+ it literally leads to installing a `@fontsource*` package and importing its CSS in a layout, which
185
+ renders correctly and silently gives up subsetting, preload, and the `size-adjust` /
186
+ `ascent-override` fallback metrics: a CLS regression that looks like a working setup.
187
+
188
+ The field is unchanged. What changed is that four surfaces now name the boundary and point at the
189
+ fix — declare the family in Astro's `fonts:` config and point the token at its `cssVariable`:
190
+ - the `fonts` description in the JSON Schema, and therefore `vitops docs authoring`
191
+ - `SKILL.md`, which previously never mentioned fonts at all
192
+ - the generated `tailwind.css` header and its `@theme` fonts comment
193
+ - the generated `type-tokens.css` header (css format)
194
+
195
+ - Updated dependencies [4756788]
196
+ - Updated dependencies [bf453b0]
197
+ - Updated dependencies [bf453b0]
198
+ - Updated dependencies [bf453b0]
199
+ - Updated dependencies [bf453b0]
200
+ - Updated dependencies [bf453b0]
201
+ - Updated dependencies [bf453b0]
202
+ - Updated dependencies [bf453b0]
203
+ - Updated dependencies [bf453b0]
204
+ - Updated dependencies [04a51d8]
205
+ - Updated dependencies [bf453b0]
206
+ - Updated dependencies [bf453b0]
207
+ - Updated dependencies [bf453b0]
208
+ - Updated dependencies [bf453b0]
209
+ - Updated dependencies [bf453b0]
210
+ - Updated dependencies [bf453b0]
211
+ - Updated dependencies [bf453b0]
212
+ - Updated dependencies [bf453b0]
213
+ - Updated dependencies [bf453b0]
214
+ - @getvitops/generator@2.0.0
215
+ - @getvitops/utils@2.0.0
216
+
217
+ ## 1.0.0
218
+
219
+ ### Major Changes
220
+
221
+ - bb92a14: **The colour system is rebuilt on a target-prefixed grammar and a shared lightness ladder.**
222
+ This is a breaking change to every colour token and utility class.
223
+
224
+ ## Why
225
+
226
+ Two axes shared one class namespace — functional _planes_ (`--<role>-bg-muted`) and
227
+ appearance-relative _stops_ (`--color-<role>-muted`) — arbitrated by a "plane wins" rule. The
228
+ result was not a scale. On the shipped palette, light mode:
229
+
230
+ | class | resolved to | step |
231
+ | ---------------------------------- | ------------ | --------------------------------------------- |
232
+ | `bg-ui-accent-x-muted` | stop | 100 |
233
+ | `bg-ui-accent-muted` | plane | 100 — identical, so `x-muted` was a dead rung |
234
+ | `bg-ui-accent` | plane | 50 — _lighter_ than both its "muted" rungs |
235
+ | `border-ui-accent-muted` / `-bold` | stop / plane | 300 / 300 — identical |
236
+ | `text-ui-accent-bold` | stop | 700 — _lighter_ than `text-…-muted` (800) |
237
+
238
+ Every family was non-monotonic, two had duplicate rungs, and `--color-<role>-muted` was
239
+ unreachable through any `bg-` class.
240
+
241
+ ## The new grammar
242
+
243
+ ```
244
+ --color-<target>-<role>[-<variant>] target ∈ bg | text | icon | border
245
+ ```
246
+
247
+ The target is **inside** the name, so `bg-danger-muted` and `text-danger-muted` are different
248
+ tokens and there is nothing left to arbitrate. **The class name is the token name minus
249
+ `--color-`** — one vocabulary instead of two.
250
+
251
+ Variants are ordinal (`xx-muted` < `x-muted` < `muted` < bare < `bold` < `x-bold`) and the
252
+ tables are sparse: only cells that hold their contrast target exist.
253
+
254
+ ## Role kinds
255
+
256
+ `colors.roles` values may now be `{ hue, kind }` as well as a bare hue string:
257
+
258
+ ```jsonc
259
+ "roles": {
260
+ "danger": "rust", // shorthand => chromatic
261
+ "surface": { "hue": "navy", "kind": "surface" }
262
+ }
263
+ ```
264
+
265
+ - **`surface`** — a page/panel colour: `bg-<role>` is the card, `bg-<role>-muted` the page
266
+ behind it, `bg-<role>-x-muted` a well, `bg-<role>-bold` the inverse surface. Full text scale.
267
+ - **`chromatic`** (default) — a signal colour: tints (`bg-<role>-x-muted`/`-muted`) and solids
268
+ (`bg-<role>-solid[-bold|-x-bold]`), with **no bare `bg-<role>`** — say how loud you mean.
269
+ `text-on-<role>` is the guaranteed foreground for the solid family.
270
+
271
+ ## Palette generation
272
+
273
+ Every ramp now sits on one **fixed lightness ladder** (50 → L 0.98 … 950 → L 0.21); only chroma
274
+ and hue vary. That is what makes a step mean the same lightness in every hue. Previously each
275
+ seed transposed the curve, so relative luminance at step 300 ranged from 0.253 to 0.384 across
276
+ the shipped palette — the same variant read differently depending on the role.
277
+
278
+ Authored colours are still reproduced **exactly**. A `seed`, an `anchors` entry or a `tones`
279
+ value is pinned verbatim at its nearest step; every other step takes the ladder. Snapping brand
280
+ colours to the ladder was measured and rejected: 11 of 18 real brand hexes moved beyond a
281
+ just-noticeable difference (worst ΔE-OK 0.050 — Facebook's `#1877F2` → `#0067e1`) and six left
282
+ sRGB gamut. Deviation is now bounded and local to pinned steps, and warns past ~0.03 L.
283
+
284
+ Two tones that claim the same step now **error** instead of one silently overwriting the other;
285
+ the record form (`tones: { "600": "…", "700": "…" }`) is how you resolve it.
286
+
287
+ ## New
288
+ - **`icon-<role>`** — a non-text tier, so a glyph may run more vivid than text. `icon` is now a
289
+ default utility family alongside `bg`/`text`/`border`.
290
+ - **`--color-border-focus`** — the focus-ring tone, taken from `ui-primary`'s solid.
291
+ - **Contrast is enforced at build time**, not only in tests: text ≥ APCA Lc 75 on its primary
292
+ background, ≥ 60 on secondary planes, icons and surface boundaries ≥ 45, both appearances.
293
+ A violation now fails `generate`. Chromatic text is checked against the _surface_ planes it
294
+ actually sits on, not only its own tints. `text-<role>-x-muted` (placeholder) and `-xx-muted`
295
+ (disabled) are explicitly exempt; nothing else is.
296
+
297
+ ## Migrating
298
+
299
+ Rename `--<role>-<suffix>` → `--color-<target>-<role>[-<variant>]`, and the same for classes:
300
+
301
+ | before | after |
302
+ | --------------------------------------- | ---------------------------------------------------------------- |
303
+ | `--<role>-bg` / `bg-<role>` (chromatic) | `--color-bg-<role>-x-muted` / `bg-<role>-x-muted` |
304
+ | `--<role>-bg-muted` | `--color-bg-<role>-muted` |
305
+ | `--<role>-solid` / `-solid-bold` | `--color-bg-<role>-solid` / `-solid-bold` |
306
+ | `--<role>-on-solid` / `text-on-<role>` | `--color-text-on-<role>` (class unchanged) |
307
+ | `--<role>-text` / `-text-muted` | `--color-text-<role>` / `-muted` |
308
+ | `--<role>-border` / `-border-bold` | `--color-border-<role>` / `-bold` |
309
+ | `--color-<role>-muted` (stop) | judge by use: `--color-bg-<role>-muted` or `--color-text-<role>` |
310
+
311
+ **`surface` background names rotate**, value-preserving: what was `--surface-bg` (the page) is
312
+ now `--color-bg-surface-muted`; what was `--surface-bg-bold` (raised) is now
313
+ `--color-bg-surface`. Elevation is expressed by which token you reach for — page `bg-muted`,
314
+ card `bg` — rather than a raised/sunken pair, which is what lets a future surfaces axis flatten
315
+ it without touching markup.
316
+
317
+ `vitops lint` reports role classes that no longer resolve, and now derives its suggestions from
318
+ what the generator actually emits rather than a hand-maintained list.
319
+
320
+ ### Minor Changes
321
+
322
+ - bb92a14: Add a fourth output format, `design`, that emits `DESIGN.md` — the agent-facing brief in
323
+ [google-labs-code/design.md](https://github.com/google-labs-code/design.md) format.
324
+
325
+ ```sh
326
+ vitops generate --format design --out . # DESIGN.md, and nothing else
327
+ vitops generate --format css,design # compose it with a stylesheet
328
+ ```
329
+
330
+ The file is YAML front matter carrying the tokens (`colors`, `typography`, `rounded`,
331
+ `spacing`, `components`, cross-referenced with `{group.token}`) followed by a prose body
332
+ carrying the rationale — colour model, fluid scales, layout vocabulary, elevation, shape
333
+ cascade, component tiers, do's and don'ts. Every section is rendered from your config, so
334
+ it cannot describe a system the other formats don't build. Point a coding agent, a Figma
335
+ import, or a designer at this one file when they don't have the toolchain; `vitops docs`
336
+ remains the richer reference for those who do.
337
+
338
+ It is emitted with `--out .` in mind: DESIGN.md conventionally lives at a repo root beside
339
+ `AGENTS.md`, not in a build directory.
340
+
341
+ Three things the spec cannot express, handled the same way every time and explained in the
342
+ emitted prose so the file is self-describing:
343
+ - **Fluid `clamp()` sizes** → the maximum (desktop) value, since a spec `Dimension` is a
344
+ bare number plus px/em/rem.
345
+ - **Dark mode** → light values only, with the automatic functional flip explained. Role
346
+ tokens are emitted as `{colors.<hue>-<step>}` references into the raw ramps rather than
347
+ flattened hexes, so the role → ramp lineage survives the export — flattening them is
348
+ exactly what breaks dark mode downstream.
349
+ - **A `50%` radius** → dropped from `rounded` (it is not a `Dimension`) and named in the
350
+ Shapes prose instead, so nothing is silently lost.
351
+
352
+ **New:** an optional `meta` key in `design-system.json` (`{ name, description }`) supplies
353
+ the brand name and the Overview paragraph. It affects no other format.
354
+
355
+ **New:** `StylesheetFormat` (`Exclude<Format, 'design'>`), exported from
356
+ `@getvitops/generator`. `@getvitops/astro`'s `css.format` now takes that narrower type —
357
+ `design` produces no stylesheet to inject, so passing it there is a type error rather than
358
+ a missing-file failure at build time. `vitops lint --format` is likewise restricted to the
359
+ three CSS formats. No change for anyone already passing `tailwind`, `css` or `bricks`.
360
+
361
+ - bb92a14: Generate legal documents from your site config
362
+
363
+ `vitops legal` renders a privacy policy, terms of service and cookie notice from a site
364
+ config, in markdown, HTML or EmDash Portable Text:
365
+
366
+ ```sh
367
+ vitops legal --out ./content # every enabled document, as markdown
368
+ vitops legal --doc privacy --format html # one document, as an HTML fragment
369
+ ```
370
+
371
+ The documents are **derived from your config**, not filled into a form. The analytics
372
+ provider they name is the one whose ID you set; the personal information they list is what
373
+ your configured forms actually collect; the countries they name come from the providers you
374
+ use. So a provider swap updates the policy on the next build, and the fix for a wrong policy
375
+ is a corrected config — hand-editing the output is overwritten.
376
+
377
+ Enable documents under `legal`, which gains the facts the prose asserts:
378
+
379
+ ```jsonc
380
+ {
381
+ "legal": {
382
+ "jurisdiction": "ca", // only 'ca' (PIPEDA) ships today
383
+ "privacyPolicy": {
384
+ "enabled": true,
385
+ "lastUpdated": "2026-08-01",
386
+ "retention": "24 months after our last contact with you",
387
+ // Third parties the config cannot imply. Analytics, Turnstile and your
388
+ // deploy platform are detected automatically — list only the rest.
389
+ "processors": [
390
+ {
391
+ "name": "Stripe",
392
+ "purpose": "payment processing",
393
+ "country": "the United States",
394
+ },
395
+ ],
396
+ },
397
+ "termsOfService": { "enabled": true },
398
+ "cookieConsent": {
399
+ "enabled": true,
400
+ "type": "opt-in",
401
+ "categories": ["Essential", "Analytics"],
402
+ },
403
+ },
404
+ }
405
+ ```
406
+
407
+ Delivery, by stack:
408
+ - **Any stack** — `vitops legal`. No integration code; prints to stdout without `--out`.
409
+ - **WordPress/Bricks** — `vitops generate --site <path>` also writes `dist/legal/*.html`, and
410
+ the theme loader now registers `[vitops_legal doc="privacy"]` to render one in a page. The
411
+ document updates on the next deploy with no action in WordPress.
412
+ - **Astro** — `getvitops({ legal: { input: 'site.json', out: 'src/content/legal' } })` writes
413
+ markdown into a content collection and re-renders when the site config changes. It needs a
414
+ `css` config (that is what registers the Vite plugin); without one, use the CLI.
415
+ - **EmDash** — `--format portable-text`, pasted into the admin.
416
+
417
+ Also new on the public API: `generateLegal()`, `renderMarkdown()`, `renderNodes()`,
418
+ `derivePolicyVars()`, `parseMarkdown()` / `toHtmlFragment()` / `toPortableText()`, and
419
+ `resolvePrivacyContact()`.
420
+
421
+ Two things to know before you publish anything this produces:
422
+ - **It is not legal advice.** Every document opens with a review banner saying so. The
423
+ bundled terms-of-service prose in particular is generic website boilerplate and
424
+ deliberately does not cover sales, refunds, subscriptions, accounts or user-generated
425
+ content — a site doing any of those needs clauses drafted for it.
426
+ - **It is only as true as your config.** A policy asserting things your site does not do is
427
+ worse than no policy. Check that the config describes reality before you ship the output.
428
+
429
+ `validateSite` now rejects a config that enables a privacy policy without a contact for
430
+ privacy requests or a `domains.canonical`, since both are interpolated into sentences that
431
+ would otherwise render blank.
432
+
433
+ ### Patch Changes
434
+
435
+ - Updated dependencies [bb92a14]
436
+ - Updated dependencies [bb92a14]
437
+ - Updated dependencies [bb92a14]
438
+ - Updated dependencies [bb92a14]
439
+ - Updated dependencies [eeb059f]
440
+ - @getvitops/generator@1.0.0
441
+ - @getvitops/utils@1.0.0
442
+
3
443
  ## 0.9.0
4
444
 
5
445
  ### Minor Changes
package/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # @getvitops/cli
2
2
 
3
- CLI for the Vitops design-system generator. Turn a `design-system.json` into Bricks, CSS, or
4
- Tailwind v4 output.
3
+ CLI for the Vitops design-system generator. Turn a `design-system.json` into Tailwind v4,
4
+ Bricks, `DESIGN.md`, or standalone CSS output.
5
5
 
6
6
  ```sh
7
7
  # scaffold a starter config (stamps a $schema for editor autocomplete)
@@ -17,14 +17,19 @@ npx vitops generate --format bricks,css --out dist
17
17
 
18
18
  ## Commands
19
19
 
20
- | command | does |
21
- | -------------------------------------------------- | --------------------------------------------- |
22
- | `vitops init [--out design-system.json] [--force]` | write a starter config with `$schema` |
23
- | `vitops validate <file>` | schema-check (non-zero exit on error) |
24
- | `vitops generate -i <file> -f <formats> -o <dir>` | generate `bricks` / `css` / `tailwind` output |
25
- | `vitops favicon -i <svg\|png> -o <dir>` | generate a favicon set from a source image |
26
- | `vitops agents [-o AGENTS.md] [--docs-dir <dir>]` | link the agent skill + AGENTS.md pointer |
27
- | `vitops docs [topic] [--all]` | print live reference docs to stdout |
20
+ | command | does |
21
+ | -------------------------------------------------- | ----------------------------------------------- |
22
+ | `vitops init [--out design-system.json] [--force]` | write a starter config with `$schema` |
23
+ | `vitops validate <file>` | schema-check (non-zero exit on error) |
24
+ | `vitops generate -i <file> -f <formats> -o <dir>` | `tailwind` / `bricks` / `design` / `css` output |
25
+ | `vitops favicon -i <svg\|png> -o <dir>` | generate a favicon set from a source image |
26
+ | `vitops agents [-o AGENTS.md] [--docs-dir <dir>]` | link the agent skill + AGENTS.md pointer |
27
+ | `vitops docs [topic] [--all]` | print live reference docs to stdout |
28
+
29
+ Every `--input` takes a `design-system.json` **or** the larger site config that embeds one
30
+ (`company.json` / `site.json`) — told apart by shape, so name the file whatever you like. Add
31
+ `--theme <name>` to build a `designSystem.themes` entry other than the default. `vitops validate`
32
+ routes the same way and checks a site config as a site config.
28
33
 
29
34
  ## Using the output
30
35
 
@@ -80,6 +85,19 @@ The CLI only writes files — how they reach WordPress is up to you:
80
85
  - **CI:** run `vitops generate --format bricks` in your pipeline and deploy the artifact the
81
86
  same way you deploy the rest of the theme.
82
87
 
88
+ **Agent brief (`DESIGN.md`).** `--format design` emits a single `DESIGN.md` and **no CSS** — the
89
+ portable brief in [design.md](https://github.com/google-labs-code/design.md) format (YAML token
90
+ front matter, then prose) for a coding agent or design tool that doesn't have the toolchain
91
+ installed. It conventionally lives at the repo root beside `AGENTS.md`:
92
+
93
+ ```sh
94
+ vitops generate --input design-system.json --format design --out .
95
+ ```
96
+
97
+ `--format` is comma-separated, but a run shares one `--out` — so keep the brief its own invocation
98
+ whenever your stylesheet goes somewhere other than the repo root. Regenerate it with your CSS; it's
99
+ derived from the same config, so it can't drift from what the browser gets.
100
+
83
101
  ## Teaching AI agents (`vitops agents` + `vitops docs`)
84
102
 
85
103
  This package **ships an agent skill** (`skill/SKILL.md`, the [Agent Skills](https://code.claude.com/docs/en/skills)