@motion-proto/live-tokens 0.73.0 → 0.74.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.
Files changed (73) hide show
  1. package/.claude/skills/live-tokens-build-page/SKILL.md +2 -2
  2. package/.claude/skills/live-tokens-check-compliance/SKILL.md +2 -2
  3. package/.claude/skills/live-tokens-create-component/SKILL.md +4 -4
  4. package/.claude/skills/live-tokens-create-theme/SKILL.md +80 -0
  5. package/.claude/skills/live-tokens-create-theme/references/design-directions.md +94 -0
  6. package/.claude/skills/live-tokens-set-colors/SKILL.md +140 -0
  7. package/.claude/skills/live-tokens-set-colors/references/color-anchors.md +80 -0
  8. package/.claude/skills/{live-tokens-adjust-geometry → live-tokens-set-geometry}/SKILL.md +12 -7
  9. package/.claude/skills/live-tokens-set-geometry/references/geometry-anchors.md +61 -0
  10. package/.claude/skills/{live-tokens-pair-fonts → live-tokens-set-type}/SKILL.md +18 -15
  11. package/.claude/skills/live-tokens-set-type/references/type-anchors.md +60 -0
  12. package/CHANGELOG.md +91 -0
  13. package/README.md +24 -15
  14. package/bin/check-page.mjs +3 -3
  15. package/bin/cli.mjs +92 -55
  16. package/bin/lib/liveState.mjs +110 -0
  17. package/bin/save-theme.mjs +177 -0
  18. package/bin/set-colors.mjs +191 -0
  19. package/bin/{adjust.mjs → set-geometry.mjs} +18 -50
  20. package/bin/{set-fonts.mjs → set-type.mjs} +21 -55
  21. package/dist-plugin/{chunk-RIXO2E55.js → chunk-7VRTBGJT.js} +1 -1
  22. package/dist-plugin/{chunk-YLCOIGQC.js → chunk-V3YF6CGT.js} +56 -2
  23. package/dist-plugin/index.cjs +58 -3
  24. package/dist-plugin/index.js +11 -11
  25. package/dist-plugin/migrateData/index.cjs +56 -1
  26. package/dist-plugin/migrateData/index.js +2 -2
  27. package/dist-plugin/{generateColorsAndType → setColors}/index.cjs +1109 -1071
  28. package/dist-plugin/{generateColorsAndType → setColors}/index.d.cts +33 -24
  29. package/dist-plugin/{generateColorsAndType → setColors}/index.d.ts +33 -24
  30. package/dist-plugin/{generateColorsAndType → setColors}/index.js +45 -63
  31. package/dist-plugin/{adjust → setGeometry}/index.cjs +60 -5
  32. package/dist-plugin/{adjust → setGeometry}/index.d.cts +1 -1
  33. package/dist-plugin/{adjust → setGeometry}/index.d.ts +1 -1
  34. package/dist-plugin/{adjust → setGeometry}/index.js +1 -1
  35. package/dist-plugin/{fontPairing → setType}/index.cjs +4 -4
  36. package/dist-plugin/{fontPairing → setType}/index.d.cts +1 -1
  37. package/dist-plugin/{fontPairing → setType}/index.d.ts +1 -1
  38. package/dist-plugin/{themeTypes-DSV3Zisf.d.cts → themeTypes-BxRtuN5V.d.cts} +1 -1
  39. package/dist-plugin/{themeTypes-DSV3Zisf.d.ts → themeTypes-BxRtuN5V.d.ts} +1 -1
  40. package/package.json +2 -1
  41. package/src/editor/core/themes/{generateColorsAndType.ts → buildColors.ts} +82 -96
  42. package/src/editor/core/themes/migrations/2026-09-03-drop-legacy-component-keys.ts +62 -0
  43. package/src/editor/core/themes/migrations/index.ts +2 -0
  44. package/src/editor/docs/content/themes-workflow.md +2 -2
  45. package/src/editor/docs/content.generated.ts +1 -1
  46. package/src/editor/overlay/LiveEditorOverlay.svelte +519 -28
  47. package/src/editor/skill-atlas/SkillAtlas.svelte +2 -2
  48. package/src/editor/skill-atlas/skillSources.generated.ts +19 -15
  49. package/src/editor/skill-atlas/skillTrees.ts +785 -632
  50. package/src/live-tokens/data/colors-and-type/autumn.json +1 -37
  51. package/src/live-tokens/data/colors-and-type/default.json +1 -37
  52. package/src/live-tokens/data/colors-and-type/halloween.json +1 -37
  53. package/src/live-tokens/data/colors-and-type/midnight-study.json +1 -37
  54. package/src/live-tokens/data/colors-and-type/ocean.json +1 -37
  55. package/src/live-tokens/data/colors-and-type/royal-velvet.json +1 -37
  56. package/src/live-tokens/data/colors-and-type/sketchy.json +1 -37
  57. package/src/live-tokens/data/colors-and-type/spring-meadow.json +1 -37
  58. package/src/live-tokens/data/colors-and-type/sunset.json +1 -37
  59. package/src/live-tokens/data/themes/autumn.json +1 -37
  60. package/src/live-tokens/data/themes/halloween.json +1 -37
  61. package/src/live-tokens/data/themes/midnight-study.json +1 -37
  62. package/src/live-tokens/data/themes/ocean.json +1 -37
  63. package/src/live-tokens/data/themes/royal-velvet.json +1 -37
  64. package/src/live-tokens/data/themes/sketchy.json +1 -37
  65. package/src/live-tokens/data/themes/spring-meadow.json +1 -37
  66. package/src/live-tokens/data/themes/sunset.json +1 -37
  67. package/src/live-tokens/data/tokens.generated.css +0 -36
  68. package/.claude/skills/live-tokens-generate-theme/SKILL.md +0 -156
  69. package/.claude/skills/live-tokens-generate-theme/references/mood-vocabulary.md +0 -43
  70. package/.claude/skills/live-tokens-generate-theme/references/named-themes.md +0 -18
  71. package/.claude/skills/live-tokens-generate-theme/references/style-vocabulary.md +0 -35
  72. package/bin/generate-theme.mjs +0 -260
  73. /package/dist-plugin/{fontPairing → setType}/index.js +0 -0
@@ -1,24 +1,25 @@
1
1
  ---
2
- name: live-tokens-pair-fonts
3
- description: Choose and apply a Google Fonts pairing for a live-tokens theme, binding families to the shipped --font-* stacks. Use whenever the user asks to pair fonts, pick a typeface, change or set the fonts, or describes type by voice: what font should the headings use, make the type more editorial, friendlier, more technical, more elegant, a serif for headings, a display font for this theme, less generic type, match the fonts to the theme. Also invoked by live-tokens-generate-theme for the type half of a whole look. Changes type only, never color. Not for a single token (use the editor) or for color (see live-tokens-generate-theme).
2
+ name: live-tokens-set-type
3
+ description: Choose and apply a Google Fonts pairing for a live-tokens theme, binding families to the shipped --font-* stacks. Use whenever the user asks to pair fonts, pick a typeface, change or set the fonts, or describes type by voice: what font should the headings use, make the type more editorial, friendlier, more technical, more elegant, a serif for headings, a display font for this theme, less generic type, match the fonts to the theme. Also invoked by live-tokens-create-theme, which supplies the type intent for a whole look. Changes type only, never color. Not for a single token (use the editor) or for a whole look (see live-tokens-create-theme).
4
4
  ---
5
5
 
6
- # Pairing fonts for a theme
6
+ # Setting a theme's fonts
7
7
 
8
8
  You choose the families; the CLI verifies each against Google Fonts, builds the URL from the weights the family actually has, and writes the result into the unsaved buffer. Never hand-author font JSON and never edit the data tree directly. Google Fonts is the pool because it is freely licensable and loads by URL; other sources go in by hand through the editor's Project fonts section.
9
9
 
10
10
  ## Workflow
11
11
 
12
- 1. Choose the pairing with the framework below and write a brief to `scratch/font-brief.json`.
13
- 2. Run `npx live-tokens set-fonts scratch/font-brief.json`. It prints each stack that moved, each family's real weights and URL, and the weights your typography tokens ask for that the family lacks.
12
+ 1. Read the type intent. When it names an anchor (a feeling, an idiom, or a genre), read `references/type-anchors.md` for that entry; it overrides the Voice table below. Choose the pairing with the framework here and write the pairing file to `scratch/font-pairing.json`.
13
+ 2. Run `npx live-tokens set-type scratch/font-pairing.json`. It prints each stack that moved, each family's real weights and URL, and the weights your typography tokens ask for that the family lacks.
14
14
  3. Read the report. A weight gap is a quality note: name it and offer an alternative only if it matters (a body face without 400, 700, or italic matters; a display face without 300 does not). A family not on Google Fonts fails the run; fix the spelling and re-run.
15
- 4. Tell the user to reload the editor page before saving. A running editor holds its own copy of the buffer this CLI just wrote and never re-reads it, so a Save without a reload writes the stale copy back and the pairing vanishes with a success report still on screen. After the reload the type is on the page, and unsaved until they save the open theme.
15
+ 4. Report back in a line: the two families, the form model behind each, and any weight gap worth naming.
16
+ 5. Tell the user to reload the editor page before saving. A running editor holds its own copy of the buffer this CLI just wrote and never re-reads it, so a Save without a reload writes the stale copy back and the pairing vanishes with a success report still on screen. After the reload the type is on the page, and unsaved until they save the open theme.
16
17
 
17
18
  State your reasoning when you propose the pairing: each face's form model and the matrix verdict, in one sentence, so the user can argue with the argument rather than only the result.
18
19
 
19
20
  Flags: `--dry-run` reports without writing. `--no-verify` skips the network and requires an explicit URL per family; use it only offline with a URL in hand.
20
21
 
21
- ## The brief
22
+ ## The pairing file
22
23
 
23
24
  ```json
24
25
  { "display": "Fraunces", "body": "Nunito Sans" }
@@ -28,7 +29,7 @@ Every slot is optional and an omitted slot is left exactly as it is. `display` i
28
29
 
29
30
  ## Choose the body face first
30
31
 
31
- The body face is the anchor. It carries most of the words, and text faces survive small sizes where display faces do not. Pick it against the brief, then pick the display face against it. A body face must have regular, bold, and italic; low to moderate stroke contrast; open apertures; and a large x-height. A face failing any of these is a display face whatever its name says. Single-weight families are fine for `display` and disqualifying for `body`.
32
+ The body face is the anchor. It carries most of the words, and text faces survive small sizes where display faces do not. Pick it against the type intent, then pick the display face against it. A body face must have regular, bold, and italic; low to moderate stroke contrast; open apertures; and a large x-height. A face failing any of these is a display face whatever its name says. Single-weight families are fine for `display` and disqualifying for `body`.
32
33
 
33
34
  The shipped text styles ask the display face for 600, across all four heading levels, and the body face for 400; prose markup adds 700 and italic for `strong` and `em`. Screen candidates against those four before running, so the report confirms a decision instead of reporting a surprise.
34
35
 
@@ -50,7 +51,7 @@ Many faces sit between columns. When one straddles, say so and lean on the voice
50
51
 
51
52
  ## Voice
52
53
 
53
- | Brief says | Type voice |
54
+ | The intent says | Type voice |
54
55
  |---|---|
55
56
  | editorial, literary, considered | dynamic serif display over a humanist sans body |
56
57
  | elegant, luxurious, formal | rational high-contrast serif display; keep the body quiet |
@@ -60,13 +61,15 @@ Many faces sit between columns. When one straddles, say so and lean on the voice
60
61
  | serious, institutional, trustworthy | rational sans body, rational serif display |
61
62
  | quiet, minimal, unbranded | one superfamily across both slots |
62
63
 
63
- Match the type to the same brief the color came from. A warm autumn palette under a cold geometric sans reads as two projects.
64
+ This table covers an intent that names no anchor. When the intent names one, `references/type-anchors.md` has the row and it wins.
65
+
66
+ Match the type to the same design direction the color came from. A warm autumn palette under a cold geometric sans reads as two projects.
64
67
 
65
68
  ## Shortcuts
66
69
 
67
- These find an adequate pairing fast and skip the reasoning; use them when the brief is vague or the type should stay quiet.
70
+ These find an adequate pairing fast and skip the reasoning; use them when the request is vague or the type should stay quiet.
68
71
 
69
- - **A superfamily.** Google Fonts families with both sans and serif siblings, among them Alegreya, Ancizar, IBM Plex, Inria, Merriweather, Noto, PT, Roboto, Source. The catalogue moves and this list does not, so treat it as a starting set: `set-fonts` verifies every family against the API and fails loudly on one that is gone.
72
+ - **A superfamily.** Google Fonts families with both sans and serif siblings, among them Alegreya, Ancizar, IBM Plex, Inria, Merriweather, Noto, PT, Roboto, Source. The catalogue moves and this list does not, so treat it as a starting set: `set-type` verifies every family against the API and fails loudly on one that is gone.
70
73
  - **One family across weights.**
71
74
  - **Same designer or foundry.**
72
75
  - **Serif display over sans body** when nothing else decides it.
@@ -75,16 +78,16 @@ These find an adequate pairing fast and skip the reasoning; use them when the br
75
78
 
76
79
  - **x-height parity.** Both faces are set from one size scale, so a small-x-height display face over a large-x-height body face gives a heading that looks weaker than its own body text. This is the one visual check that matters on screen; make it on the rendered page.
77
80
  - **Print faces at small sizes.** Delicate serifs and high stroke contrast turn to mud below 16px.
78
- - **Every family is a request.** Two is the target; three needs a reason.
81
+ - **Every family is a download.** Two is the target; three needs a reason.
79
82
  - **Sets of themes:** no two share a display face or a body face.
80
83
 
81
84
  ## Scope
82
85
 
83
- Type only. Color, component aliases, shape, and the type scale are untouched: `set-fonts` moves families between stacks and nothing else, writing only the unsaved colors-and-type buffer. Save the theme to keep it, Adopt to ship it. Adopt is also what rewrites `fonts.css`, which is how a build with no editor in it loads the family at all.
86
+ Type only. Color, component aliases, shape, and the type scale are untouched: `set-type` moves families between stacks and nothing else, writing only the font entries in the unsaved buffer. Save the theme to keep it, Adopt to ship it. Adopt is also what rewrites `fonts.css`, which is how a build with no editor in it loads the family at all.
84
87
 
85
88
  ## Verify
86
89
 
87
90
  - The CLI exits 0 and names each stack that moved, before and after.
88
91
  - Each URL reflects the family's real weights: a range for a variable family, an enumeration for a static one, a bare URL for a single-weight face.
89
92
  - The app shows the new type after a reload, and the editor's Fonts section lists both families with their fallbacks intact.
90
- - To revert, run the inverse brief, or load the open theme again to discard the buffer.
93
+ - To revert, run the inverse pairing file, or load the open theme again to discard the buffer.
@@ -0,0 +1,60 @@
1
+ # Type anchors: feelings, idioms, and genres
2
+
3
+ Read this when the type intent names one of these. An anchor overrides the
4
+ Voice table in SKILL.md, because it is tuned to the same direction the color
5
+ came from.
6
+
7
+ An entry names a form model and a role, never a family. Take the families from
8
+ the font matrix and the body-face rule in SKILL.md, and screen them against the
9
+ weights the shipped text styles ask for.
10
+
11
+ An occasion (Christmas, Autumn, Ocean) fixes color only. Take its type from the
12
+ feeling it implies: Autumn reads cozy, Halloween reads ominous, Spring reads
13
+ tender.
14
+
15
+ ## Feelings
16
+
17
+ | Anchor | Type voice |
18
+ |---|---|
19
+ | Joyful, exuberant, energetic | dynamic sans in both slots |
20
+ | Playful, whimsical | expressive display over a plain workhorse |
21
+ | Optimistic, hopeful | humanist sans, low contrast |
22
+ | Confident, bold | heavy rational display, plain body |
23
+ | Serene, tranquil | dynamic sans, one family |
24
+ | Tender, gentle, romantic | soft serif display over humanist sans, light weights |
25
+ | Cozy, comforting | dynamic serif over dynamic sans |
26
+ | Wistful, nostalgic, vintage, faded | rational serif display, quiet body |
27
+ | Earthy, grounded, natural | dynamic serif over humanist sans |
28
+ | Clinical, sterile, precise | one neo-grotesque, tightly set |
29
+ | Contemplative, focused | one quiet superfamily |
30
+ | Urgent, alarming | condensed grotesque, heavy |
31
+ | Tense, anxious | neo-grotesque, tightly set |
32
+ | Defiant, rebellious, loud | heavy display; a mono body works |
33
+ | Melancholy, moody, sad | rational serif display over quiet sans |
34
+ | Somber, grave, mournful | rational serif in both slots |
35
+ | Ominous, dramatic, haunted | heavy high-contrast display |
36
+ | Austere, severe, cold | one rational family |
37
+
38
+ ## Idioms, eras, and genres
39
+
40
+ | Anchor | Type voice |
41
+ |---|---|
42
+ | Swiss, International | one rational neo-grotesque across both slots |
43
+ | Bauhaus | geometric sans, heavy display |
44
+ | Mid-century modern | dynamic sans, or a geometric display over a dynamic body |
45
+ | Art deco, opulent, luxurious | rational high-contrast serif display, quiet body |
46
+ | Terminal, phosphor | mono in both slots |
47
+ | Cyberpunk, neon noir, futuristic | wide geometric display, neutral body |
48
+ | Vaporwave | wide retro display, serif welcome |
49
+ | Y2K, bubble | geometric sans, heavy display |
50
+ | Blueprint | mono, or a technical grotesque; a mono body works here and almost nowhere else |
51
+ | Scandinavian, hygge | dynamic sans on both sides |
52
+ | Japandi, wabi-sabi | rational serif display over a quiet humanist body |
53
+ | Cottagecore, botanical | dynamic serif display over humanist sans |
54
+ | Editorial, magazine | high-contrast serif display over a text serif or humanist sans |
55
+ | Newsprint, broadsheet | rational serif in both slots |
56
+ | Risograph, zine | expressive display over a plain workhorse |
57
+ | Corporate, professional, trustworthy | rational sans body with a rational serif or same-family display |
58
+ | Brutalist | neo-grotesque or mono at heavy weight |
59
+ | Memphis, postmodern | geometric display, plain body |
60
+ | Industrial, workshop, gritty | condensed grotesque display over a plain body |
package/CHANGELOG.md CHANGED
@@ -1,5 +1,96 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.74.0 — A theme is three decisions and one document
4
+
5
+ ### Changed
6
+
7
+ - **Breaking: the three theme CLI verbs are renamed, and there is no alias.**
8
+ `generate-theme` is now `set-colors`, `set-fonts` is now `set-type`, and
9
+ `adjust-geometry` is now `set-geometry`. Each verb names the dimension it
10
+ sets, and each matches the skill that runs it. A script or note that types
11
+ an old verb fails with "Unknown command"; there is no deprecation path,
12
+ because nothing is released against these names yet. `set-type` and
13
+ `set-geometry` keep their flags, their file formats, and their behaviour, and
14
+ the geometry ops file is now written to `scratch/geometry-ops.json`.
15
+
16
+ **`set-colors` writes the color buffer, and `save-theme` writes the theme.**
17
+ A theme is three decisions and one document. Each set verb now writes only
18
+ its own dimension into the unsaved buffers the app already renders:
19
+ `set-colors` joins `set-type` and `set-geometry` there, and no longer writes
20
+ or opens a theme. The new `save-theme <name>` composes the live state into
21
+ `themes/<slug>.json` and opens it, and with no unsaved edits it saves a copy
22
+ of the open theme under the new name. `--no-activate` saves without opening,
23
+ which is how a set of themes comes off one starting look. `--carry-from` is
24
+ gone: it existed because `set-colors` activated, and nothing activates now
25
+ until you save. The base color file no longer carries `name`; a name in it
26
+ is ignored with a notice. `live-tokens-create-theme` runs `save-theme` once,
27
+ after its three contributing skills. `init`, an undocumented alias for
28
+ `create` since 0.21.0, is gone: type `create`.
29
+
30
+ **36 dead keys left the override bag.** Themes carried the color, type, and
31
+ border-width siblings of keys an earlier migration dropped (Badge's `trait`
32
+ variant, SectionDivider's title and description slots, Dialog's variant and
33
+ state axes). Nothing read them, and every Adopt baked them into
34
+ `tokens.generated.css`. A colors-and-type migration drops them from your
35
+ themes on the next Save, the nine shipped themes are rewritten, and
36
+ `check:preset-themes` now refuses any shipped theme, `default` included,
37
+ whose override bag names a variable `tokens.css` does not declare.
38
+
39
+ Internal: the engine bundles are `dist-plugin/setColors`, `setType`, and
40
+ `setGeometry`, named for the verbs that load them. They have no `exports`
41
+ entry and no consumer imports them.
42
+
43
+ - **A theme is one design direction routed to three contributing skills.**
44
+ `live-tokens-generate-theme` did two jobs: it read the request and fixed a
45
+ whole look, and it executed the color layer itself. So two of a theme's three
46
+ dimensions were skills and the third was a section. It is now
47
+ **live-tokens-create-theme**, which reads the request once, states one design
48
+ direction, states a color, a type, and a geometry intent, routes each to
49
+ **live-tokens-set-colors**, **live-tokens-set-type**, and
50
+ **live-tokens-set-geometry**, and assembles their three reports. Its one CLI
51
+ is `save-theme`. Every contributing skill still works when invoked directly
52
+ with an intent and no direction behind it. This changes four `description` triggers:
53
+ a whole look reaches create-theme, and a request naming one dimension goes
54
+ straight to that dimension's skill, including a color-only refinement like
55
+ "warmer".
56
+
57
+ - **The anchor tables split by dimension.** A row in the old mood and style
58
+ vocabularies fixed color, type, and geometry at once, which meant the
59
+ coordinator read color mechanics and handed on the other two. create-theme
60
+ now holds `references/design-directions.md`, an index placing each anchor on
61
+ the valence, energy, and dominance axes with its one-line direction; each
62
+ contributing skill holds the column it executes, keyed on the same names.
63
+ `check:skills` gains an anchor-key parity gate, so a name that reaches only
64
+ some of the four files fails at commit.
65
+
66
+ - **The theme skills call the user's own words the request, not the brief.**
67
+ "Brief" named two things at once: the user's words, and the seed JSON at
68
+ `scratch/<slug>-brief.json`. The second sense is fixed in two filenames and
69
+ in the CLI's own `<brief.json>` usage string, so the first sense moves.
70
+ Layer 0 of the pipeline in `docs/terminology.md` is now the request; the
71
+ files stay where they are and the prose calls them the seed file and the
72
+ pairing file. This changes one `description` trigger, in
73
+ `live-tokens-generate-theme`, from "from a natural-language brief" to "from
74
+ a natural-language request". set-fonts loses its one clash with the new
75
+ term: every family is a download rather than a request. The atlas tagline
76
+ named the wrong layer and now credits the design direction, the one sentence
77
+ three skills actually read.
78
+
79
+ - **A palette's base color is called that, not its seed.** The word already
80
+ meant something else in this product: sketch mode displaces its strokes on
81
+ random seeds, and says so in copy a user reads. The theme document has always
82
+ called the value `baseColor`, so the skill and the CLI were the drift. The
83
+ generator's input key is now `baseColors` rather than `seeds`, and the file
84
+ the skill writes is `scratch/<slug>-base-colors.json`; the pairing file
85
+ `set-fonts` reads is `scratch/font-pairing.json`. Both CLI verbs report
86
+ against the new names, so a contrast failure now says "raise the Brand base
87
+ color lightness". Internally `buildColorsAndTypeFromSeeds` becomes
88
+ `buildColorsAndType`, dropping a "from" clause that was never true on the
89
+ type half, and `ColorsAndTypeBrief` becomes `ColorsAndTypeInput`; neither is
90
+ a public export. A kept base color file with
91
+ the old `seeds` key needs that one key renamed, and the skill rewrites the
92
+ file from scratch on every run anyway.
93
+
3
94
  ## 0.73.0 — The skill atlas ships from the package
4
95
 
5
96
  ### Added
package/README.md CHANGED
@@ -328,16 +328,17 @@ npx @motion-proto/live-tokens <command>
328
328
  | `report [--json]` | The project as facts: pending migrations, tokens each component reads, which page renders which component, and both checkers' findings by rule. Always exits 0. |
329
329
  | `check-component [id]` | Validate a component's runtime, editor, and registration against the authoring contract; with no id, every component authored under `src/system/components`. |
330
330
  | `check-page [paths...]` | Validate pages against the build-page contract: catalogue components and their props, theme tokens over literals, route wiring. |
331
- | `generate-theme <brief.json> [--no-activate] [--dry-run] [--carry-from <name>]` | Build a full theme from a 10-seed OKLCH brief, enforce AA contrast, write `themes/<slug>.json`, and open it. |
332
- | `adjust <ops.json> [--dry-run]` | Move radius, padding, gap, and border-width aliases along their token scales. |
333
- | `set-fonts <brief.json> [--dry-run] [--no-verify]` | Bind Google Fonts families to the theme's font stacks, verified against the API. |
331
+ | `set-colors <base-colors.json> [--dry-run]` | Build the theme's whole color identity from 10 OKLCH base colors, enforce AA contrast on the derived text tokens, and write the result to the unsaved colors-and-type buffer. |
332
+ | `set-type <pairing.json> [--dry-run] [--no-verify]` | Bind Google Fonts families to the theme's font stacks, verified against the API. |
333
+ | `set-geometry <ops.json> [--dry-run]` | Move radius, padding, gap, and border-width aliases along their token scales. |
334
+ | `save-theme <name> [--no-activate] [--dry-run]` | Compose the live state into `themes/<slug>.json` and open it, which clears the unsaved buffers. |
334
335
  | `migrate [--check] [--write] [--tokens <path>]` | Reconcile the project with the installed package: additive `tokens.css` migrations, the pre-0.48 data-tree move, and a report on source references to the routes that moved in 0.35.0. |
335
336
 
336
337
  Once installed in a project, the same commands are available as `npx live-tokens <command>`.
337
338
 
338
339
  ## Claude Code skills
339
340
 
340
- The package bundles eight Claude Code skills. They encode the conventions this README cannot carry in full: which component fits a need, how a page is wired, what a valid theme looks like in OKLCH, how two typefaces sit together, how geometry moves along the token scales, how a project is checked against all of that, and how an existing page or component is brought back into line. Each triggers from an ordinary request, so there are no slash commands to learn.
341
+ The package bundles nine Claude Code skills. They encode the conventions this README cannot carry in full: which component fits a need, how a page is wired, how one request becomes a whole look, what a valid theme looks like in OKLCH, how two typefaces sit together, how geometry moves along the token scales, how a project is checked against all of that, and how an existing page or component is brought back into line. Each triggers from an ordinary request, so there are no slash commands to learn.
341
342
 
342
343
  ### Install
343
344
 
@@ -363,33 +364,41 @@ Ask for a page, a route, or a screen: "build a pricing page", "add a /settings r
363
364
 
364
365
  The skill composes the page from shipped components, styles every value with `var(--token-*)` (no hex, no pixel literals, so editor changes repaint the page), places content on the column grid via `--columns-count`, `--columns-gutter`, and `--columns-max-width`, adds the route as a `lazy` import with a `source` so the overlay's "Page Source" button works, and imports `site.css` from the page rather than `main.ts` so page CSS stays out of the editor routes. It writes your page files and the route entry, and never touches the data tree.
365
366
 
366
- ### `live-tokens-generate-theme`
367
+ ### `live-tokens-create-theme`
367
368
 
368
- Ask for a look: "a dark, moody night theme", "a St Patrick's Day theme in green and gold", "warmer", "more contrast", "calmer".
369
+ Ask for a look: "a dark, moody night theme", "a St Patrick's Day theme in green and gold", "make it feel more serious".
369
370
 
370
- A theme is three decisions made from one brief: color, type, and geometry. The skill owns color and delegates the other two to `live-tokens-pair-fonts` and `live-tokens-adjust-geometry`, so the whole look comes from the same reading of the brief.
371
+ A theme is three decisions: color, type, and geometry. This skill reads the request once, states one design direction that fixes all three, and routes a color, a type, and a geometry intent to `live-tokens-set-colors`, `live-tokens-set-type`, and `live-tokens-set-geometry`. Each of them writes its dimension into the unsaved buffers the app already renders. The skill then runs `npx live-tokens save-theme "<name>"`, which turns those buffers into `themes/<slug>.json` and opens it, and assembles the three reports into one summary.
371
372
 
372
- For color it translates the brief into ten OKLCH seeds (Brand, Accent, Special, Canvas, Neutral, Alternate, Info, Success, Warning, Danger) plus a light or dark scheme, then runs `npx live-tokens generate-theme <brief.json>`. The CLI assembles the curves, enforces AA contrast on derived text tokens and auto-corrects where it can, writes `themes/<slug>.json`, opens it, and prints a contrast report. Exit 1 means the seeds themselves are unworkable, and each failure line names the seed to change.
373
+ It carries the anchor index: every feeling, idiom, era, genre, holiday, and season the skills know, placed on the valence, energy, and dominance axes with the direction each implies. An idiom sets constraints and a feeling moves dials inside them, so "cozy brutalist" reads the idiom first. Each contributing skill holds the mechanics for its own dimension, keyed on the same anchor names.
373
374
 
374
- Most of the skill is the judgment the generator cannot supply: a chroma budget scaled to how much screen area each palette covers, per-role lightness and hue bands for each scheme, gamut guardrails against impossible seeds, harmony modes, the optional canvas gradient, and a voice-to-shape table for the shape step. OKLCH anchors for named holidays and seasons live in a reference file the skill reads on demand.
375
+ A refinement that names one dimension goes straight to that sibling. This skill takes the ones that span dimensions.
375
376
 
376
- Color lands in the theme file; type and shape land in the unsaved buffers, and one Save keeps all three. `--dry-run` prints the report without writing; `--no-activate` writes without opening. Opening a theme never changes what your site ships; Adopt does. Regenerating replaces that theme's whole color state, including palette edits made in the editor since the last run, and carries the live buffers forward, so re-rolling color after setting fonts and shape keeps both.
377
+ ### `live-tokens-set-colors`
377
378
 
378
- ### `live-tokens-pair-fonts`
379
+ Ask for color: "a cooler palette", "warmer", "more contrast", "calmer", "make the ground darker".
380
+
381
+ The skill translates the color intent into ten OKLCH base colors (Brand, Accent, Special, Canvas, Neutral, Alternate, Info, Success, Warning, Danger) plus a light or dark scheme, then runs `npx live-tokens set-colors <base-colors.json>`. The CLI assembles the curves, enforces AA contrast on derived text tokens and auto-corrects where it can, writes the result to the unsaved colors-and-type buffer, and prints a contrast report. Exit 1 means the base colors themselves are unworkable, and each failure line names the base color to change.
382
+
383
+ Most of the skill is the judgment the generator cannot supply: a chroma budget scaled to how much screen area each palette covers, per-role lightness and hue bands for each scheme, three levels of canvas commitment, gamut guardrails against impossible base colors, harmony modes, and the optional canvas gradient. OKLCH anchors for every named feeling, idiom, and occasion live in a reference file the skill reads on demand.
384
+
385
+ Color is the one dimension every look fixes, so it runs first and never skips. `--dry-run` prints the report without writing. Saving the open theme in the editor, or running `save-theme`, keeps the result; Adopt ships it. A re-run replaces the buffer's whole color state, including palette edits made in the editor since the last run, and carries the type and geometry buffers forward.
386
+
387
+ ### `live-tokens-set-type`
379
388
 
380
389
  Ask for type: "pair some fonts for this theme", "what font should the headings use?", "make the type more editorial", "something friendlier", "a serif for headings".
381
390
 
382
- The skill chooses the families and runs `npx live-tokens set-fonts <brief.json>`, which binds each one to `--font-display`, `--font-sans`, `--font-serif`, or `--font-mono`. Every family is checked against the Google Fonts API before it is written, and the URL is built from the weights that family actually has: a range for a variable font, an enumeration for a static one, a bare URL for a single-weight display face. The report names the weights your typography tokens ask for and the family does not carry.
391
+ The skill chooses the families and runs `npx live-tokens set-type <pairing.json>`, which binds each one to `--font-display`, `--font-sans`, `--font-serif`, or `--font-mono`. Every family is checked against the Google Fonts API before it is written, and the URL is built from the weights that family actually has: a range for a variable font, an enumeration for a static one, a bare URL for a single-weight display face. The report names the weights your typography tokens ask for and the family does not carry.
383
392
 
384
- The judgment is the skill's half. It anchors on the body face, because that is most of the words on the page and text faces survive small sizes where display faces do not. It classifies both candidates by form model (dynamic, rational, geometric) and applies the font matrix: two faces sharing a skeleton under different surfaces pair reliably, two faces sharing a surface over different skeletons fight, and two faces far apart on both read as a decision. It also carries the screen test a body face has to pass, a voice table from brief to type, and the Google Fonts superfamilies for when the type should stay quiet.
393
+ The judgment is the skill's half. It anchors on the body face, because that is most of the words on the page and text faces survive small sizes where display faces do not. It classifies both candidates by form model (dynamic, rational, geometric) and applies the font matrix: two faces sharing a skeleton under different surfaces pair reliably, two faces sharing a surface over different skeletons fight, and two faces far apart on both read as a decision. It also carries the screen test a body face has to pass, a voice table from intent to type, a type anchor per named feeling and idiom, and the Google Fonts superfamilies for when the type should stay quiet.
385
394
 
386
395
  Scope: type only, and never color. Edits land in the colors-and-type `_working.json` buffer, so save the open theme to keep them. `--dry-run` reports without writing.
387
396
 
388
- ### `live-tokens-adjust-geometry`
397
+ ### `live-tokens-set-geometry`
389
398
 
390
399
  Ask for shape or space: "make the buttons pill shaped", "sharper corners on the cards", "space it out", "tighter", "thinner borders".
391
400
 
392
- The skill turns the phrase into ops (`kind` of `radius`, `padding`, `gap`, or `border-width`, with `shift: N` or `set: <token>`, optionally scoped to one component id), then runs `npx live-tokens adjust <ops.json>`. The CLI moves each matching alias along its ladder, reads the live config first so "a bit more" compounds, and prints every change and every skip.
401
+ The skill turns the phrase into ops (`kind` of `radius`, `padding`, `gap`, or `border-width`, with `shift: N` or `set: <token>`, optionally scoped to one component id), then runs `npx live-tokens set-geometry <ops.json>`. The CLI moves each matching alias along its ladder, reads the live config first so "a bit more" compounds, and prints every change and every skip.
393
402
 
394
403
  It also knows where these edits go wrong: controls run out of room long before containers do, so a global compaction is one step and anything deeper is aimed at named containers; a pill needs more horizontal padding than a square-cornered control, not less; and content insets stop at `--space-4`, below which a relative "tighter" reports as clamped instead of writing.
395
404
 
@@ -46,9 +46,9 @@ const DEEP_IMPORT_PATTERNS = [
46
46
  const TEXT_AXES = ['font-size', 'font-family', 'font-weight', 'line-height', 'letter-spacing'];
47
47
 
48
48
  // The geometry the theme owns: spacing, stroke, radius, and shadow all have a
49
- // token scale, and `adjust` moves them. Sizing (a hero's height, a column's
50
- // minimum width, a max content width) is layout, has no scale, and stays
51
- // literal.
49
+ // token scale, and `set-geometry` moves them. Sizing (a hero's height, a
50
+ // column's minimum width, a max content width) is layout, has no scale, and
51
+ // stays literal.
52
52
  const THEMED_GEOMETRY = /^(padding|margin|gap|row-gap|column-gap|border|outline|inset|top|right|bottom|left|box-shadow|text-shadow)(-|$)|-radius$/;
53
53
 
54
54
  // A local two-up or three-up is a layout. From four columns on, a hardcoded
package/bin/cli.mjs CHANGED
@@ -8,9 +8,10 @@
8
8
  // report The project as facts: tokens read, components used, findings by rule. Always exits 0.
9
9
  // check-component [id] Validate a component (or every authored one) against the create-component skill contract.
10
10
  // check-page [paths...] Validate pages against the build-page skill contract.
11
- // generate-theme <brief> Build a theme from a 10-seed OKLCH brief and open it.
12
- // adjust <ops.json> Apply radius/padding/gap/border-width ops to the open buffer.
13
- // set-fonts <brief.json> Bind Google Fonts families to the theme's font stacks.
11
+ // set-colors <colors> Build the color identity from 10 OKLCH base colors, into the open buffer.
12
+ // set-geometry <ops> Apply radius/padding/gap/border-width ops to the open buffer.
13
+ // set-type <pairing> Bind Google Fonts families to the theme's font stacks.
14
+ // save-theme <name> Compose the live state into themes/<slug>.json and open it.
14
15
  // migrate [...] Reconcile tokens.css, the data tree, and route references.
15
16
 
16
17
  import { cpSync, existsSync, mkdirSync, readdirSync, statSync, writeSync } from 'node:fs';
@@ -38,9 +39,10 @@ import {
38
39
  } from './migrate.mjs';
39
40
  import { runMigrateRoutes, formatRouteResult } from './migrate-routes.mjs';
40
41
  import { runCreate, formatCreateResult } from './create.mjs';
41
- import { runGenerateTheme, formatGenerateThemeResult } from './generate-theme.mjs';
42
- import { runAdjust, formatAdjustResult } from './adjust.mjs';
43
- import { runSetFonts, formatSetFontsResult } from './set-fonts.mjs';
42
+ import { runSetColors, formatSetColorsResult } from './set-colors.mjs';
43
+ import { runSetGeometry, formatSetGeometryResult } from './set-geometry.mjs';
44
+ import { runSetType, formatSetTypeResult } from './set-type.mjs';
45
+ import { runSaveTheme, formatSaveThemeResult } from './save-theme.mjs';
44
46
 
45
47
  const USAGE = `Usage: npx @motion-proto/live-tokens <command> [options]
46
48
 
@@ -69,7 +71,7 @@ Commands:
69
71
  value a theme token. Checks every page under src/
70
72
  when given no paths.
71
73
 
72
- Both check commands accept:
74
+ check-component and check-page also accept:
73
75
  --json Machine-readable findings, for a skill to iterate
74
76
  against until the exit code is 0
75
77
  --strict Treat warnings as errors
@@ -77,31 +79,29 @@ Both check commands accept:
77
79
  (or set "checks": { "rules": {...} } in
78
80
  live-tokens.config.json; "checks": { "exclude":
79
81
  [...] } drops paths from discovery entirely)
80
- generate-theme <brief.json> [--no-activate] [--dry-run] [--carry-from <name>]
81
- Build a full theme from a 10-seed OKLCH brief
82
- (see the live-tokens-generate-theme skill),
83
- enforce AA contrast on derived text tokens, write
84
- themes/<slug>.json, and open it in the editor.
85
- Opening never changes what your site ships; Adopt
86
- in the editor does that.
87
- --no-activate writes the theme without opening it;
88
- --dry-run prints the contrast report without
89
- writing. Non-color content (gradients, fonts,
90
- component aliases) carries forward from the live
91
- look, or from theme <name> with --carry-from.
92
- adjust <ops.json> [--dry-run]
82
+ set-colors <base-colors.json> [--dry-run]
83
+ Build the theme's whole color identity from 10
84
+ OKLCH base colors (see the live-tokens-set-colors
85
+ skill) and enforce AA contrast on the derived text
86
+ tokens. Reads the live colors and type and writes
87
+ the result to the unsaved colors-and-type buffer,
88
+ so save the open theme in the editor or run
89
+ save-theme to keep it. Fonts and every override no
90
+ palette owns carry forward. --dry-run prints the
91
+ contrast report without writing.
92
+ set-geometry <ops.json> [--dry-run]
93
93
  Move radius, padding, gap, and border-width
94
94
  aliases along their token scales (see the
95
- live-tokens-adjust-geometry skill). Reads each
95
+ live-tokens-set-geometry skill). Reads each
96
96
  component's live config and writes the result to
97
97
  that component's unsaved buffer, so save the open
98
98
  theme in the editor to keep it. --dry-run prints
99
99
  the report without writing.
100
- set-fonts <brief.json> [--dry-run] [--no-verify]
100
+ set-type <pairing.json> [--dry-run] [--no-verify]
101
101
  Bind Google Fonts families to --font-display,
102
102
  --font-sans, --font-serif, --font-mono and
103
103
  --font-editorial (see
104
- the live-tokens-pair-fonts skill). Each family is
104
+ the live-tokens-set-type skill). Each family is
105
105
  verified against the Google Fonts API and the URL
106
106
  is negotiated from the weights it actually has.
107
107
  Writes the result to the unsaved colors-and-type
@@ -109,6 +109,17 @@ Both check commands accept:
109
109
  keep it. --dry-run prints the report without
110
110
  writing; --no-verify skips the network and
111
111
  requires an explicit URL per family.
112
+ save-theme <name> [--no-activate] [--dry-run]
113
+ Compose the live state (the unsaved buffers, the
114
+ open theme under them, the shipped defaults under
115
+ that) into themes/<slug>.json and open it, which
116
+ clears the buffers. With no unsaved edits it saves
117
+ a copy of the open theme under the new name.
118
+ Opening never changes what your site ships; Adopt
119
+ in the editor does that. --no-activate writes the
120
+ theme and leaves the live state alone, so a set of
121
+ themes comes off one starting look; --dry-run
122
+ prints the report without writing.
112
123
  migrate [--check] [--write] [--tokens <path>]
113
124
  Reconcile your project with the installed package:
114
125
  applies additive tokens.css migrations, moves a
@@ -150,7 +161,7 @@ if (!command || command === '--help' || command === '-h') {
150
161
 
151
162
  const pkgRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..');
152
163
 
153
- if (command === 'create' || command === 'init') {
164
+ if (command === 'create') {
154
165
  const targetArg = rest.find((a) => !a.startsWith('-'));
155
166
  if (!targetArg) {
156
167
  fail(`Usage: npx @motion-proto/live-tokens create <project-directory>`);
@@ -241,72 +252,97 @@ if (command === 'check-page') {
241
252
  reportChecks('check-page', findings, checked, PAGE_RULES, opts);
242
253
  }
243
254
 
244
- if (command === 'generate-theme') {
245
- const briefPath = rest.find((a) => !a.startsWith('-'));
246
- if (!briefPath) {
247
- fail(`Usage: npx @motion-proto/live-tokens generate-theme <brief.json> [--no-activate] [--dry-run]`);
255
+ if (command === 'set-colors') {
256
+ const baseColorsPath = rest.find((a) => !a.startsWith('-'));
257
+ if (!baseColorsPath) {
258
+ fail(`Usage: npx @motion-proto/live-tokens set-colors <base-colors.json> [--dry-run]`);
259
+ }
260
+ if (rest.includes('--no-activate')) {
261
+ fail(
262
+ `set-colors has no --no-activate: it edits the open buffer, which is what the page already runs. ` +
263
+ `Nothing is activated until save-theme, which takes the flag.`,
264
+ );
265
+ }
266
+ if (rest.includes('--carry-from')) {
267
+ fail(
268
+ `set-colors has no --carry-from: it reads the live look and edits it in place, so a second theme ` +
269
+ `already starts from the first. Run save-theme --no-activate between themes.`,
270
+ );
248
271
  }
249
272
  try {
250
- const carryIdx = rest.indexOf('--carry-from');
251
- const carryFrom = carryIdx !== -1 ? rest[carryIdx + 1] : undefined;
252
- if (carryIdx !== -1 && !carryFrom) fail(`--carry-from requires a theme name`);
253
- const result = await runGenerateTheme({
254
- briefPath,
255
- activate: !rest.includes('--no-activate'),
273
+ const result = await runSetColors({
274
+ baseColorsPath,
256
275
  dryRun: rest.includes('--dry-run'),
257
- carryFrom,
258
276
  });
259
- console.log(formatGenerateThemeResult(result));
277
+ console.log(formatSetColorsResult(result));
260
278
  process.exit(result.report.failures.length === 0 ? 0 : 1);
261
279
  } catch (err) {
262
- fail(`generate-theme failed: ${err instanceof Error ? err.message : String(err)}`);
280
+ fail(`set-colors failed: ${err instanceof Error ? err.message : String(err)}`);
263
281
  }
264
282
  }
265
283
 
266
- if (command === 'adjust') {
284
+ if (command === 'set-geometry') {
267
285
  const opsPath = rest.find((a) => !a.startsWith('-'));
268
286
  if (!opsPath) {
269
- fail(`Usage: npx @motion-proto/live-tokens adjust <ops.json> [--dry-run]`);
287
+ fail(`Usage: npx @motion-proto/live-tokens set-geometry <ops.json> [--dry-run]`);
270
288
  }
271
289
  if (rest.includes('--no-activate')) {
272
290
  fail(
273
- `adjust has no --no-activate: it edits the open buffer, which is what the page already runs. ` +
291
+ `set-geometry has no --no-activate: it edits the open buffer, which is what the page already runs. ` +
274
292
  `Drop the flag and re-run.`,
275
293
  );
276
294
  }
277
295
  try {
278
- const result = await runAdjust({
296
+ const result = await runSetGeometry({
279
297
  opsPath,
280
298
  dryRun: rest.includes('--dry-run'),
281
299
  });
282
- console.log(formatAdjustResult(result));
300
+ console.log(formatSetGeometryResult(result));
283
301
  process.exit(0);
284
302
  } catch (err) {
285
- fail(`adjust failed: ${err instanceof Error ? err.message : String(err)}`);
303
+ fail(`set-geometry failed: ${err instanceof Error ? err.message : String(err)}`);
286
304
  }
287
305
  }
288
306
 
289
- if (command === 'set-fonts') {
290
- const briefPath = rest.find((a) => !a.startsWith('-'));
291
- if (!briefPath) {
292
- fail(`Usage: npx @motion-proto/live-tokens set-fonts <brief.json> [--dry-run] [--no-verify]`);
307
+ if (command === 'set-type') {
308
+ const pairingPath = rest.find((a) => !a.startsWith('-'));
309
+ if (!pairingPath) {
310
+ fail(`Usage: npx @motion-proto/live-tokens set-type <pairing.json> [--dry-run] [--no-verify]`);
293
311
  }
294
312
  if (rest.includes('--no-activate')) {
295
313
  fail(
296
- `set-fonts has no --no-activate: it edits the open buffer, which is what the page already runs. ` +
314
+ `set-type has no --no-activate: it edits the open buffer, which is what the page already runs. ` +
297
315
  `Drop the flag and re-run.`,
298
316
  );
299
317
  }
300
318
  try {
301
- const result = await runSetFonts({
302
- briefPath,
319
+ const result = await runSetType({
320
+ pairingPath,
303
321
  dryRun: rest.includes('--dry-run'),
304
322
  verify: !rest.includes('--no-verify'),
305
323
  });
306
- console.log(formatSetFontsResult(result));
324
+ console.log(formatSetTypeResult(result));
325
+ process.exit(0);
326
+ } catch (err) {
327
+ fail(`set-type failed: ${err instanceof Error ? err.message : String(err)}`);
328
+ }
329
+ }
330
+
331
+ if (command === 'save-theme') {
332
+ const name = rest.find((a) => !a.startsWith('-'));
333
+ if (!name) {
334
+ fail(`Usage: npx @motion-proto/live-tokens save-theme <name> [--no-activate] [--dry-run]`);
335
+ }
336
+ try {
337
+ const result = await runSaveTheme({
338
+ name,
339
+ activate: !rest.includes('--no-activate'),
340
+ dryRun: rest.includes('--dry-run'),
341
+ });
342
+ console.log(formatSaveThemeResult(result));
307
343
  process.exit(0);
308
344
  } catch (err) {
309
- fail(`set-fonts failed: ${err instanceof Error ? err.message : String(err)}`);
345
+ fail(`save-theme failed: ${err instanceof Error ? err.message : String(err)}`);
310
346
  }
311
347
  }
312
348
 
@@ -391,9 +427,10 @@ const SAMPLE_PROMPTS = {
391
427
  'live-tokens-build-page': 'build a pricing page using live-tokens components',
392
428
  'live-tokens-pick-component': "what's the difference between TabBar and SegmentedControl?",
393
429
  'live-tokens-create-component': 'author a new Toggle component for my live-tokens project',
394
- 'live-tokens-generate-theme': 'make me a bright and cheerful theme',
395
- 'live-tokens-adjust-geometry': 'make the buttons pill shaped',
396
- 'live-tokens-pair-fonts': 'pair some fonts for this theme',
430
+ 'live-tokens-create-theme': 'make me a bright and cheerful theme',
431
+ 'live-tokens-set-colors': 'give me a cooler palette, same fonts',
432
+ 'live-tokens-set-type': 'pair some fonts for this theme',
433
+ 'live-tokens-set-geometry': 'make the buttons pill shaped',
397
434
  'live-tokens-fix-findings': 'make check:design pass',
398
435
  'live-tokens-check-compliance': 'check this project against the design system',
399
436
  };