wensity 0.1.7 → 0.1.9

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 (4) hide show
  1. package/README.md +5 -4
  2. package/dist/index.js +1626 -167
  3. package/package.json +10 -10
  4. package/LICENSE +0 -21
package/dist/index.js CHANGED
@@ -1,12 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/commands.ts
4
- import { mkdir as mkdir5, readFile as readFile5, writeFile as writeFile5 } from "fs/promises";
5
- import { dirname as dirname6, join as join6 } from "path";
4
+ import { mkdir as mkdir6, readFile as readFile6, writeFile as writeFile6 } from "fs/promises";
5
+ import { dirname as dirname7, join as join7 } from "path";
6
6
 
7
7
  // src/constants.ts
8
8
  var CLI_NAME = "wensity";
9
- var CLI_VERSION = "0.1.7";
9
+ var CLI_VERSION = "0.1.9";
10
10
  var CONFIG_FILE_NAME = "wensity.json";
11
11
  var DEFAULT_API_ORIGIN = "https://ui.wensity.com";
12
12
  var DEFAULT_REGISTRY_PATH = "/api/registry";
@@ -18,7 +18,9 @@ var INIT_PRIMITIVE_CSS_SLUG = "button";
18
18
  var HELP_TEXT = `Wensity CLI ${CLI_VERSION}
19
19
 
20
20
  Usage:
21
- wensity init [--preset <code>] [--preset-file <path>] [options]
21
+ wensity init [--preset <code>] [--preset-file <path>] [--template next|vite|astro|react-router] [options]
22
+ wensity apply --preset <code> [options]
23
+ wensity preset inspect <code> [--json]
22
24
  wensity login [options]
23
25
  wensity logout
24
26
  wensity whoami [--json]
@@ -28,7 +30,9 @@ Usage:
28
30
  wensity version
29
31
 
30
32
  Commands:
31
- init Create wensity.json and local helper files (optional --preset applies style + CSS)
33
+ init Create wensity.json and local helper files (optional --preset applies style + CSS; optional --template scaffolds an app)
34
+ apply Apply a preset to an existing Wensity project
35
+ preset Inspect or work with shareable Wensity style presets
32
36
  login Save a Wensity API token for premium component access
33
37
  logout Remove saved CLI credentials
34
38
  whoami Verify the saved token and print the authenticated account
@@ -44,6 +48,7 @@ Options:
44
48
  --token <token> Provide a token non-interactively for login
45
49
  --preset <code> Apply a shareable Wensity style preset (wsty1.\u2026); merges style when config exists
46
50
  --preset-file <path> Read a preset code or JSON payload from a file
51
+ --template <name> Scaffold a new app before init: next, vite, astro, react-router
47
52
  --force Overwrite generated files when adding or initializing
48
53
  --no-install Write files without running the package manager
49
54
  --dry-run Print planned writes and installs without changing files
@@ -53,8 +58,11 @@ Options:
53
58
 
54
59
  Preset notes:
55
60
  Generate codes at ${DEFAULT_API_ORIGIN}/docs/primitives/setup
61
+ preset inspect decodes the token, font packages, and resolved primitive tokens
62
+ apply --preset updates existing style + primitive CSS without scaffolding helper files
56
63
  Plain init refuses an existing wensity.json unless --force
57
64
  init --preset merges style + primitive CSS; does not rewrite installed source
65
+ init --template writes a minimal app scaffold in the current directory before Wensity files
58
66
  add/update read style.iconLibrary for UI primitives only (Tabler|Lucide)
59
67
  Invalid/expired/unsupported codes exit non-zero with an actionable message
60
68
  `;
@@ -118,7 +126,7 @@ function hasFlag(args, name) {
118
126
  return args.options.get(name) === true;
119
127
  }
120
128
  function expectsValue(name) {
121
- return ["api-url", "registry", "token", "type", "preset", "preset-file"].includes(name);
129
+ return ["api-url", "registry", "token", "type", "preset", "preset-file", "template"].includes(name);
122
130
  }
123
131
  function normalizeOptionName(name) {
124
132
  if (name === "dry") return "dry-run";
@@ -138,9 +146,14 @@ var BASE_COLOR_IDS = [
138
146
  "inherit",
139
147
  "neutral",
140
148
  "zinc",
149
+ "gray",
141
150
  "slate",
151
+ "cool-gray",
142
152
  "stone",
143
- "taupe"
153
+ "taupe",
154
+ "mauve",
155
+ "olive",
156
+ "mist"
144
157
  ];
145
158
  var THEME_COLOR_IDS = [
146
159
  "ink",
@@ -148,16 +161,68 @@ var THEME_COLOR_IDS = [
148
161
  "blue",
149
162
  "violet",
150
163
  "green",
151
- "orange"
164
+ "orange",
165
+ "amber",
166
+ "cyan",
167
+ "emerald",
168
+ "indigo",
169
+ "pink",
170
+ "purple",
171
+ "rose",
172
+ "sky",
173
+ "teal",
174
+ "yellow",
175
+ "fuchsia",
176
+ "lime"
152
177
  ];
153
178
  var CHART_PALETTE_IDS = [
154
179
  "theme",
155
180
  "warm",
156
181
  "cool",
157
- "mixed"
182
+ "mixed",
183
+ "neutral",
184
+ "zinc",
185
+ "stone",
186
+ "gray",
187
+ "amber",
188
+ "cyan",
189
+ "emerald",
190
+ "indigo",
191
+ "lime",
192
+ "pink",
193
+ "purple",
194
+ "rose",
195
+ "sky",
196
+ "teal",
197
+ "yellow",
198
+ "taupe"
199
+ ];
200
+ var HEADING_FONT_IDS = [
201
+ "system",
202
+ "playfair",
203
+ "instrument-serif",
204
+ "fraunces",
205
+ "space-grotesk",
206
+ "noto-serif",
207
+ "eb-garamond",
208
+ "lora",
209
+ "montserrat",
210
+ "manrope",
211
+ "inter"
212
+ ];
213
+ var BODY_FONT_IDS = [
214
+ "system",
215
+ "inter",
216
+ "geist",
217
+ "dm-sans",
218
+ "plus-jakarta-sans",
219
+ "figtree",
220
+ "noto-sans",
221
+ "public-sans",
222
+ "outfit",
223
+ "manrope",
224
+ "ibm-plex-sans"
158
225
  ];
159
- var HEADING_FONT_IDS = ["system", "playfair"];
160
- var BODY_FONT_IDS = ["system", "inter"];
161
226
  var RADIUS_PRESET_CSS = {
162
227
  none: "0px",
163
228
  small: "0.5rem",
@@ -274,8 +339,8 @@ var DEFAULT_STYLE_CONFIG_CANONICAL = {
274
339
  baseColor: "inherit",
275
340
  themeColor: "ink",
276
341
  chartPalette: "theme",
277
- headingFont: "system",
278
- bodyFont: "system",
342
+ headingFont: "inter",
343
+ bodyFont: "inter",
279
344
  iconLibrary: "tabler",
280
345
  radius: "medium"
281
346
  };
@@ -604,7 +669,7 @@ function validateStyleConfigCanonicalPartial(value) {
604
669
  if ("baseColor" in value) {
605
670
  if (typeof value.baseColor !== "string" || !isBaseColorId(value.baseColor)) {
606
671
  throw new PresetValidationError(
607
- "Invalid style.baseColor. Expected inherit | neutral | zinc | slate | stone | taupe.",
672
+ "Invalid style.baseColor. Expected inherit | neutral | zinc | gray | slate | cool-gray | stone | taupe.",
608
673
  "style.baseColor"
609
674
  );
610
675
  }
@@ -613,7 +678,7 @@ function validateStyleConfigCanonicalPartial(value) {
613
678
  if ("themeColor" in value) {
614
679
  if (typeof value.themeColor !== "string" || !isThemeColorId(value.themeColor)) {
615
680
  throw new PresetValidationError(
616
- "Invalid style.themeColor. Expected ink | red | blue | violet | green | orange.",
681
+ "Invalid style.themeColor. Expected a supported theme color id.",
617
682
  "style.themeColor"
618
683
  );
619
684
  }
@@ -622,7 +687,7 @@ function validateStyleConfigCanonicalPartial(value) {
622
687
  if ("chartPalette" in value) {
623
688
  if (typeof value.chartPalette !== "string" || !isChartPaletteId(value.chartPalette)) {
624
689
  throw new PresetValidationError(
625
- "Invalid style.chartPalette. Expected theme | warm | cool | mixed.",
690
+ "Invalid style.chartPalette. Expected a supported chart palette id.",
626
691
  "style.chartPalette"
627
692
  );
628
693
  }
@@ -631,7 +696,7 @@ function validateStyleConfigCanonicalPartial(value) {
631
696
  if ("headingFont" in value) {
632
697
  if (typeof value.headingFont !== "string" || !isHeadingFontId(value.headingFont)) {
633
698
  throw new PresetValidationError(
634
- "Invalid style.headingFont. Expected system | playfair.",
699
+ "Invalid style.headingFont. Expected a supported heading font id.",
635
700
  "style.headingFont"
636
701
  );
637
702
  }
@@ -640,7 +705,7 @@ function validateStyleConfigCanonicalPartial(value) {
640
705
  if ("bodyFont" in value) {
641
706
  if (typeof value.bodyFont !== "string" || !isBodyFontId(value.bodyFont)) {
642
707
  throw new PresetValidationError(
643
- "Invalid style.bodyFont. Expected system | inter.",
708
+ "Invalid style.bodyFont. Expected a supported body font id.",
644
709
  "style.bodyFont"
645
710
  );
646
711
  }
@@ -769,6 +834,29 @@ function utf8ByteLength(text) {
769
834
  return new TextEncoder().encode(text).byteLength;
770
835
  }
771
836
  var BASE64URL_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
837
+ var COMPACT_CANONICAL_LEGACY_PREFIX = "c";
838
+ var COMPACT_CANONICAL_PREFIX = "d";
839
+ var COMPACT_CANONICAL_EXPANDED_PREFIX = "e";
840
+ var COMPACT_CANONICAL_CHART_PREFIX = "f";
841
+ var COMPACT_CANONICAL_THEME_PREFIX = "g";
842
+ var COMPACT_CANONICAL_BASE_PREFIX = "h";
843
+ var COMPACT_CANONICAL_BODY_LENGTH = 3;
844
+ var COMPACT_CANONICAL_ENCODED_LENGTH = COMPACT_CANONICAL_PREFIX.length + COMPACT_CANONICAL_BODY_LENGTH;
845
+ var COMPACT_CANONICAL_EXPANDED_BODY_LENGTH = 4;
846
+ var COMPACT_CANONICAL_EXPANDED_ENCODED_LENGTH = COMPACT_CANONICAL_EXPANDED_PREFIX.length + COMPACT_CANONICAL_EXPANDED_BODY_LENGTH;
847
+ var COMPACT_CANONICAL_CHART_ENCODED_LENGTH = COMPACT_CANONICAL_CHART_PREFIX.length + COMPACT_CANONICAL_EXPANDED_BODY_LENGTH;
848
+ var COMPACT_CANONICAL_THEME_ENCODED_LENGTH = COMPACT_CANONICAL_THEME_PREFIX.length + COMPACT_CANONICAL_EXPANDED_BODY_LENGTH;
849
+ var COMPACT_CANONICAL_BASE_BODY_LENGTH = 5;
850
+ var COMPACT_CANONICAL_BASE_ENCODED_LENGTH = COMPACT_CANONICAL_BASE_PREFIX.length + COMPACT_CANONICAL_BASE_BODY_LENGTH;
851
+ var COMPACT_CANONICAL_LEGACY_BASE_MAX_INDEX = 7;
852
+ var COMPACT_CANONICAL_LEGACY_THEME_MAX_INDEX = 5;
853
+ var COMPACT_CANONICAL_LEGACY_CHART_MAX_INDEX = 3;
854
+ var COMPACT_CANONICAL_LEGACY_FONT_MAX_INDEX = 1;
855
+ var COMPACT_CANONICAL_FONT_MAX_INDEX = 7;
856
+ var COMPACT_CANONICAL_EXPANDED_FONT_MAX_INDEX = 15;
857
+ var COMPACT_CANONICAL_EXPANDED_CHART_MAX_INDEX = 31;
858
+ var COMPACT_CANONICAL_EXPANDED_THEME_MAX_INDEX = 31;
859
+ var COMPACT_CANONICAL_EXPANDED_BASE_MAX_INDEX = 31;
772
860
  function toBase64Url(bytes) {
773
861
  let out = "";
774
862
  for (let i = 0; i < bytes.length; i += 3) {
@@ -808,6 +896,242 @@ function fromBase64Url(code) {
808
896
  }
809
897
  return outIndex === out.length ? out : out.subarray(0, outIndex);
810
898
  }
899
+ function indexOfPresetValue(values, value, path) {
900
+ const index = values.indexOf(value);
901
+ if (index < 0) {
902
+ throw new PresetValidationError(`Invalid compact preset value at ${path}.`, path);
903
+ }
904
+ return index;
905
+ }
906
+ function readCompactChar(char) {
907
+ if (!char) return -1;
908
+ return BASE64URL_ALPHABET.indexOf(char);
909
+ }
910
+ function encodeCompactCanonicalPayload(payload) {
911
+ const baseColorIndex = indexOfPresetValue(BASE_COLOR_IDS, payload.baseColor, "preset.baseColor");
912
+ const themeColorIndex = indexOfPresetValue(
913
+ THEME_COLOR_IDS,
914
+ payload.themeColor,
915
+ "preset.themeColor"
916
+ );
917
+ const chartPaletteIndex = indexOfPresetValue(
918
+ CHART_PALETTE_IDS,
919
+ payload.chartPalette,
920
+ "preset.chartPalette"
921
+ );
922
+ const headingFontIndex = indexOfPresetValue(
923
+ HEADING_FONT_IDS,
924
+ payload.headingFont,
925
+ "preset.headingFont"
926
+ );
927
+ const bodyFontIndex = indexOfPresetValue(BODY_FONT_IDS, payload.bodyFont, "preset.bodyFont");
928
+ const iconLibraryIndex = indexOfPresetValue(
929
+ ICON_LIBRARIES,
930
+ payload.iconLibrary,
931
+ "preset.iconLibrary"
932
+ );
933
+ const radiusIndex = indexOfPresetValue(RADIUS_PRESETS, payload.radius, "preset.radius");
934
+ if (baseColorIndex > 7) {
935
+ if (baseColorIndex > COMPACT_CANONICAL_EXPANDED_BASE_MAX_INDEX) {
936
+ throw new PresetValidationError(
937
+ "Too many curated base colors for compact preset encoding.",
938
+ "preset.baseColor"
939
+ );
940
+ }
941
+ if (themeColorIndex > COMPACT_CANONICAL_EXPANDED_THEME_MAX_INDEX) {
942
+ throw new PresetValidationError(
943
+ "Too many curated theme colors for compact preset encoding.",
944
+ "preset.themeColor"
945
+ );
946
+ }
947
+ if (chartPaletteIndex > COMPACT_CANONICAL_EXPANDED_CHART_MAX_INDEX) {
948
+ throw new PresetValidationError(
949
+ "Too many curated chart palettes for compact preset encoding.",
950
+ "preset.chartPalette"
951
+ );
952
+ }
953
+ if (headingFontIndex > COMPACT_CANONICAL_EXPANDED_FONT_MAX_INDEX || bodyFontIndex > COMPACT_CANONICAL_EXPANDED_FONT_MAX_INDEX) {
954
+ throw new PresetValidationError(
955
+ "Too many curated fonts for compact preset encoding.",
956
+ "preset.headingFont"
957
+ );
958
+ }
959
+ let baseValue = baseColorIndex;
960
+ baseValue = baseValue << 5 | themeColorIndex;
961
+ baseValue = baseValue << 5 | chartPaletteIndex;
962
+ baseValue = baseValue << 4 | headingFontIndex;
963
+ baseValue = baseValue << 4 | bodyFontIndex;
964
+ baseValue = baseValue << 1 | iconLibraryIndex;
965
+ baseValue = baseValue << 2 | radiusIndex;
966
+ return encodeCompactCanonicalValue(
967
+ COMPACT_CANONICAL_BASE_PREFIX,
968
+ baseValue,
969
+ COMPACT_CANONICAL_BASE_BODY_LENGTH
970
+ );
971
+ }
972
+ if (themeColorIndex > 7) {
973
+ if (themeColorIndex > COMPACT_CANONICAL_EXPANDED_THEME_MAX_INDEX) {
974
+ throw new PresetValidationError(
975
+ "Too many curated theme colors for compact preset encoding.",
976
+ "preset.themeColor"
977
+ );
978
+ }
979
+ if (chartPaletteIndex > COMPACT_CANONICAL_EXPANDED_CHART_MAX_INDEX) {
980
+ throw new PresetValidationError(
981
+ "Too many curated chart palettes for compact preset encoding.",
982
+ "preset.chartPalette"
983
+ );
984
+ }
985
+ if (headingFontIndex > COMPACT_CANONICAL_EXPANDED_FONT_MAX_INDEX || bodyFontIndex > COMPACT_CANONICAL_EXPANDED_FONT_MAX_INDEX) {
986
+ throw new PresetValidationError(
987
+ "Too many curated fonts for compact preset encoding.",
988
+ "preset.headingFont"
989
+ );
990
+ }
991
+ let themeValue = baseColorIndex;
992
+ themeValue = themeValue << 5 | themeColorIndex;
993
+ themeValue = themeValue << 5 | chartPaletteIndex;
994
+ themeValue = themeValue << 4 | headingFontIndex;
995
+ themeValue = themeValue << 4 | bodyFontIndex;
996
+ themeValue = themeValue << 1 | iconLibraryIndex;
997
+ themeValue = themeValue << 2 | radiusIndex;
998
+ return encodeCompactCanonicalValue(
999
+ COMPACT_CANONICAL_THEME_PREFIX,
1000
+ themeValue,
1001
+ COMPACT_CANONICAL_EXPANDED_BODY_LENGTH
1002
+ );
1003
+ }
1004
+ if (baseColorIndex <= COMPACT_CANONICAL_LEGACY_BASE_MAX_INDEX && themeColorIndex <= COMPACT_CANONICAL_LEGACY_THEME_MAX_INDEX && chartPaletteIndex <= COMPACT_CANONICAL_LEGACY_CHART_MAX_INDEX && headingFontIndex <= COMPACT_CANONICAL_LEGACY_FONT_MAX_INDEX && bodyFontIndex <= COMPACT_CANONICAL_LEGACY_FONT_MAX_INDEX) {
1005
+ let legacyValue = baseColorIndex;
1006
+ legacyValue = legacyValue << 3 | themeColorIndex;
1007
+ legacyValue = legacyValue << 2 | chartPaletteIndex;
1008
+ legacyValue = legacyValue << 1 | headingFontIndex;
1009
+ legacyValue = legacyValue << 1 | bodyFontIndex;
1010
+ legacyValue = legacyValue << 1 | iconLibraryIndex;
1011
+ legacyValue = legacyValue << 2 | radiusIndex;
1012
+ return encodeCompactCanonicalValue(COMPACT_CANONICAL_LEGACY_PREFIX, legacyValue);
1013
+ }
1014
+ if (chartPaletteIndex > 3) {
1015
+ if (chartPaletteIndex > COMPACT_CANONICAL_EXPANDED_CHART_MAX_INDEX) {
1016
+ throw new PresetValidationError(
1017
+ "Too many curated chart palettes for compact preset encoding.",
1018
+ "preset.chartPalette"
1019
+ );
1020
+ }
1021
+ if (headingFontIndex > COMPACT_CANONICAL_EXPANDED_FONT_MAX_INDEX || bodyFontIndex > COMPACT_CANONICAL_EXPANDED_FONT_MAX_INDEX) {
1022
+ throw new PresetValidationError(
1023
+ "Too many curated fonts for compact preset encoding.",
1024
+ "preset.headingFont"
1025
+ );
1026
+ }
1027
+ let chartValue = baseColorIndex;
1028
+ chartValue = chartValue << 3 | themeColorIndex;
1029
+ chartValue = chartValue << 5 | chartPaletteIndex;
1030
+ chartValue = chartValue << 4 | headingFontIndex;
1031
+ chartValue = chartValue << 4 | bodyFontIndex;
1032
+ chartValue = chartValue << 1 | iconLibraryIndex;
1033
+ chartValue = chartValue << 2 | radiusIndex;
1034
+ return encodeCompactCanonicalValue(
1035
+ COMPACT_CANONICAL_CHART_PREFIX,
1036
+ chartValue,
1037
+ COMPACT_CANONICAL_EXPANDED_BODY_LENGTH
1038
+ );
1039
+ }
1040
+ if (headingFontIndex > COMPACT_CANONICAL_FONT_MAX_INDEX || bodyFontIndex > COMPACT_CANONICAL_FONT_MAX_INDEX) {
1041
+ if (headingFontIndex > COMPACT_CANONICAL_EXPANDED_FONT_MAX_INDEX || bodyFontIndex > COMPACT_CANONICAL_EXPANDED_FONT_MAX_INDEX) {
1042
+ throw new PresetValidationError(
1043
+ "Too many curated fonts for compact preset encoding.",
1044
+ "preset.headingFont"
1045
+ );
1046
+ }
1047
+ let expandedValue = baseColorIndex;
1048
+ expandedValue = expandedValue << 3 | themeColorIndex;
1049
+ expandedValue = expandedValue << 2 | chartPaletteIndex;
1050
+ expandedValue = expandedValue << 4 | headingFontIndex;
1051
+ expandedValue = expandedValue << 4 | bodyFontIndex;
1052
+ expandedValue = expandedValue << 1 | iconLibraryIndex;
1053
+ expandedValue = expandedValue << 2 | radiusIndex;
1054
+ return encodeCompactCanonicalValue(
1055
+ COMPACT_CANONICAL_EXPANDED_PREFIX,
1056
+ expandedValue,
1057
+ COMPACT_CANONICAL_EXPANDED_BODY_LENGTH
1058
+ );
1059
+ }
1060
+ let value = baseColorIndex;
1061
+ value = value << 3 | themeColorIndex;
1062
+ value = value << 2 | chartPaletteIndex;
1063
+ value = value << 3 | headingFontIndex;
1064
+ value = value << 3 | bodyFontIndex;
1065
+ value = value << 1 | iconLibraryIndex;
1066
+ value = value << 2 | radiusIndex;
1067
+ return encodeCompactCanonicalValue(
1068
+ COMPACT_CANONICAL_PREFIX,
1069
+ value,
1070
+ COMPACT_CANONICAL_BODY_LENGTH
1071
+ );
1072
+ }
1073
+ function encodeCompactCanonicalValue(prefix, value, bodyLength = 3) {
1074
+ let out = prefix;
1075
+ for (let shift = (bodyLength - 1) * 6; shift >= 0; shift -= 6) {
1076
+ out += BASE64URL_ALPHABET[value >> shift & 63];
1077
+ }
1078
+ return out;
1079
+ }
1080
+ function decodeCompactCanonicalPayload(encoded) {
1081
+ const isLegacyCompact = encoded.startsWith(COMPACT_CANONICAL_LEGACY_PREFIX);
1082
+ const isCurrentCompact = encoded.startsWith(COMPACT_CANONICAL_PREFIX);
1083
+ const isExpandedCompact = encoded.startsWith(COMPACT_CANONICAL_EXPANDED_PREFIX);
1084
+ const isChartCompact = encoded.startsWith(COMPACT_CANONICAL_CHART_PREFIX);
1085
+ const isThemeCompact = encoded.startsWith(COMPACT_CANONICAL_THEME_PREFIX);
1086
+ const isBaseCompact = encoded.startsWith(COMPACT_CANONICAL_BASE_PREFIX);
1087
+ const expectedLength = isExpandedCompact ? COMPACT_CANONICAL_EXPANDED_ENCODED_LENGTH : isChartCompact ? COMPACT_CANONICAL_CHART_ENCODED_LENGTH : isThemeCompact ? COMPACT_CANONICAL_THEME_ENCODED_LENGTH : isBaseCompact ? COMPACT_CANONICAL_BASE_ENCODED_LENGTH : COMPACT_CANONICAL_ENCODED_LENGTH;
1088
+ if (encoded.length !== expectedLength || !isCurrentCompact && !isLegacyCompact && !isExpandedCompact && !isChartCompact && !isThemeCompact && !isBaseCompact) {
1089
+ throw new PresetValidationError("Compact preset code has an invalid length.", "preset");
1090
+ }
1091
+ let value = 0;
1092
+ for (let i = 1; i < encoded.length; i += 1) {
1093
+ const char = readCompactChar(encoded[i]);
1094
+ if (char < 0) {
1095
+ throw new PresetValidationError("Compact preset code is not valid base64url.", "preset");
1096
+ }
1097
+ value = value << 6 | char;
1098
+ }
1099
+ const radius = RADIUS_PRESETS[value & 3];
1100
+ value >>= 2;
1101
+ const iconLibrary = ICON_LIBRARIES[value & 1];
1102
+ value >>= 1;
1103
+ const fontMask = isLegacyCompact ? 1 : isExpandedCompact || isChartCompact || isThemeCompact || isBaseCompact ? 15 : 7;
1104
+ const fontShift = isLegacyCompact ? 1 : isExpandedCompact || isChartCompact || isThemeCompact || isBaseCompact ? 4 : 3;
1105
+ const bodyFont = BODY_FONT_IDS[value & fontMask];
1106
+ value >>= fontShift;
1107
+ const headingFont = HEADING_FONT_IDS[value & fontMask];
1108
+ value >>= fontShift;
1109
+ const chartMask = isChartCompact || isThemeCompact || isBaseCompact ? 31 : 3;
1110
+ const chartShift = isChartCompact || isThemeCompact || isBaseCompact ? 5 : 2;
1111
+ const chartPalette = CHART_PALETTE_IDS[value & chartMask];
1112
+ value >>= chartShift;
1113
+ const themeMask = isThemeCompact || isBaseCompact ? 31 : 7;
1114
+ const themeShift = isThemeCompact || isBaseCompact ? 5 : 3;
1115
+ const themeColor = THEME_COLOR_IDS[value & themeMask];
1116
+ value >>= themeShift;
1117
+ const baseMask = isBaseCompact ? 31 : 7;
1118
+ const baseShift = isBaseCompact ? 5 : 3;
1119
+ const baseColor = BASE_COLOR_IDS[value & baseMask];
1120
+ value >>= baseShift;
1121
+ if (value !== 0 || !baseColor || !themeColor || !chartPalette || !headingFont || !bodyFont || !iconLibrary || !radius) {
1122
+ throw new PresetValidationError("Compact preset code contains an out-of-range value.", "preset");
1123
+ }
1124
+ return {
1125
+ v: 2,
1126
+ baseColor,
1127
+ themeColor,
1128
+ chartPalette,
1129
+ headingFont,
1130
+ bodyFont,
1131
+ iconLibrary,
1132
+ radius
1133
+ };
1134
+ }
811
1135
  function stableStringifyCodecPayload(payload) {
812
1136
  const parts = [
813
1137
  `"v":${payload.v}`,
@@ -994,25 +1318,25 @@ function validateCodecPayloadCanonical(value) {
994
1318
  }
995
1319
  if (typeof value.themeColor !== "string" || !isThemeColorId(value.themeColor)) {
996
1320
  throw new PresetValidationError(
997
- "Invalid preset.themeColor. Expected ink | red | blue | violet | green | orange.",
1321
+ "Invalid preset.themeColor. Expected a supported theme color id.",
998
1322
  "preset.themeColor"
999
1323
  );
1000
1324
  }
1001
1325
  if (typeof value.chartPalette !== "string" || !isChartPaletteId(value.chartPalette)) {
1002
1326
  throw new PresetValidationError(
1003
- "Invalid preset.chartPalette. Expected theme | warm | cool | mixed.",
1327
+ "Invalid preset.chartPalette. Expected a supported chart palette id.",
1004
1328
  "preset.chartPalette"
1005
1329
  );
1006
1330
  }
1007
1331
  if (typeof value.headingFont !== "string" || !isHeadingFontId(value.headingFont)) {
1008
1332
  throw new PresetValidationError(
1009
- "Invalid preset.headingFont. Expected system | playfair.",
1333
+ "Invalid preset.headingFont. Expected a supported heading font id.",
1010
1334
  "preset.headingFont"
1011
1335
  );
1012
1336
  }
1013
1337
  if (typeof value.bodyFont !== "string" || !isBodyFontId(value.bodyFont)) {
1014
1338
  throw new PresetValidationError(
1015
- "Invalid preset.bodyFont. Expected system | inter.",
1339
+ "Invalid preset.bodyFont. Expected a supported body font id.",
1016
1340
  "preset.bodyFont"
1017
1341
  );
1018
1342
  }
@@ -1039,18 +1363,6 @@ function validateCodecPayloadCanonical(value) {
1039
1363
  radius: value.radius
1040
1364
  };
1041
1365
  }
1042
- function stableStringifyCodecPayloadCanonical(payload) {
1043
- return `{${[
1044
- `"v":${payload.v}`,
1045
- `"baseColor":${JSON.stringify(payload.baseColor)}`,
1046
- `"themeColor":${JSON.stringify(payload.themeColor)}`,
1047
- `"chartPalette":${JSON.stringify(payload.chartPalette)}`,
1048
- `"headingFont":${JSON.stringify(payload.headingFont)}`,
1049
- `"bodyFont":${JSON.stringify(payload.bodyFont)}`,
1050
- `"iconLibrary":${JSON.stringify(payload.iconLibrary)}`,
1051
- `"radius":${JSON.stringify(payload.radius)}`
1052
- ].join(",")}}`;
1053
- }
1054
1366
  function encodePresetCanonical(style) {
1055
1367
  const normalized = normalizeStyleConfigCanonical(style);
1056
1368
  const payload = {
@@ -1063,15 +1375,7 @@ function encodePresetCanonical(style) {
1063
1375
  iconLibrary: normalized.iconLibrary,
1064
1376
  radius: normalized.radius
1065
1377
  };
1066
- const json = stableStringifyCodecPayloadCanonical(payload);
1067
- const bytes = new TextEncoder().encode(json);
1068
- if (bytes.byteLength > PRESET_CODEC_MAX_DECODED_JSON_BYTES) {
1069
- throw new PresetValidationError(
1070
- `Preset JSON exceeds ${PRESET_CODEC_MAX_DECODED_JSON_BYTES} bytes.`,
1071
- "preset"
1072
- );
1073
- }
1074
- const code = `${PRESET_CODEC_PREFIX}.${toBase64Url(bytes)}`;
1378
+ const code = `${PRESET_CODEC_PREFIX}.${encodeCompactCanonicalPayload(payload)}`;
1075
1379
  if (code.length > PRESET_CODEC_MAX_CODE_LENGTH) {
1076
1380
  throw new PresetValidationError(
1077
1381
  `Preset code exceeds ${PRESET_CODEC_MAX_CODE_LENGTH} characters.`,
@@ -1101,6 +1405,9 @@ function decodePresetEnvelope(code) {
1101
1405
  if (!encoded) {
1102
1406
  throw new PresetValidationError("Preset code payload is empty.", "preset");
1103
1407
  }
1408
+ if ((encoded.length === COMPACT_CANONICAL_ENCODED_LENGTH || encoded.length === COMPACT_CANONICAL_EXPANDED_ENCODED_LENGTH || encoded.length === COMPACT_CANONICAL_CHART_ENCODED_LENGTH || encoded.length === COMPACT_CANONICAL_THEME_ENCODED_LENGTH || encoded.length === COMPACT_CANONICAL_BASE_ENCODED_LENGTH) && (encoded.startsWith(COMPACT_CANONICAL_PREFIX) || encoded.startsWith(COMPACT_CANONICAL_LEGACY_PREFIX) || encoded.startsWith(COMPACT_CANONICAL_EXPANDED_PREFIX) || encoded.startsWith(COMPACT_CANONICAL_CHART_PREFIX) || encoded.startsWith(COMPACT_CANONICAL_THEME_PREFIX) || encoded.startsWith(COMPACT_CANONICAL_BASE_PREFIX))) {
1409
+ return decodeCompactCanonicalPayload(encoded);
1410
+ }
1104
1411
  if (encoded.includes("=") || /[^A-Za-z0-9_-]/.test(encoded)) {
1105
1412
  throw new PresetValidationError("Preset code payload is not valid unpadded base64url.", "preset");
1106
1413
  }
@@ -1229,8 +1536,8 @@ var LUCIDE_TO_TABLER = Object.fromEntries(
1229
1536
  var BASE_COLOR_PALETTE = {
1230
1537
  zinc: {
1231
1538
  light: {
1232
- surfaceElevated: "#ffffff",
1233
- surfaceOverlay: "#ffffff",
1539
+ surfaceElevated: "#fafafa",
1540
+ surfaceOverlay: "#f4f4f5",
1234
1541
  foreground: "#09090b",
1235
1542
  border: "rgba(9, 9, 11, 0.08)",
1236
1543
  mutedForeground: "#71717a"
@@ -1245,8 +1552,8 @@ var BASE_COLOR_PALETTE = {
1245
1552
  },
1246
1553
  neutral: {
1247
1554
  light: {
1248
- surfaceElevated: "#ffffff",
1249
- surfaceOverlay: "#ffffff",
1555
+ surfaceElevated: "#fafafa",
1556
+ surfaceOverlay: "#f5f5f5",
1250
1557
  foreground: "#171717",
1251
1558
  border: "rgba(23, 23, 23, 0.08)",
1252
1559
  mutedForeground: "#737373"
@@ -1259,10 +1566,26 @@ var BASE_COLOR_PALETTE = {
1259
1566
  mutedForeground: "#a3a3a3"
1260
1567
  }
1261
1568
  },
1569
+ gray: {
1570
+ light: {
1571
+ surfaceElevated: "#f9fafb",
1572
+ surfaceOverlay: "#f3f4f6",
1573
+ foreground: "#111827",
1574
+ border: "rgba(17, 24, 39, 0.08)",
1575
+ mutedForeground: "#6b7280"
1576
+ },
1577
+ dark: {
1578
+ surfaceElevated: "#1a1f2b",
1579
+ surfaceOverlay: "#262c3a",
1580
+ foreground: "#f9fafb",
1581
+ border: "rgba(249, 250, 251, 0.10)",
1582
+ mutedForeground: "#9ca3af"
1583
+ }
1584
+ },
1262
1585
  slate: {
1263
1586
  light: {
1264
- surfaceElevated: "#ffffff",
1265
- surfaceOverlay: "#ffffff",
1587
+ surfaceElevated: "#f8fafc",
1588
+ surfaceOverlay: "#f1f5f9",
1266
1589
  foreground: "#0f172a",
1267
1590
  border: "rgba(15, 23, 42, 0.08)",
1268
1591
  mutedForeground: "#64748b"
@@ -1275,10 +1598,26 @@ var BASE_COLOR_PALETTE = {
1275
1598
  mutedForeground: "#94a3b8"
1276
1599
  }
1277
1600
  },
1601
+ "cool-gray": {
1602
+ light: {
1603
+ surfaceElevated: "#f6fafb",
1604
+ surfaceOverlay: "#eaf3f4",
1605
+ foreground: "#13343b",
1606
+ border: "rgba(19, 52, 59, 0.08)",
1607
+ mutedForeground: "#5f7d84"
1608
+ },
1609
+ dark: {
1610
+ surfaceElevated: "#101c1f",
1611
+ surfaceOverlay: "#1b2d31",
1612
+ foreground: "#eef6f7",
1613
+ border: "rgba(238, 246, 247, 0.10)",
1614
+ mutedForeground: "#9db3b8"
1615
+ }
1616
+ },
1278
1617
  stone: {
1279
1618
  light: {
1280
- surfaceElevated: "#ffffff",
1281
- surfaceOverlay: "#ffffff",
1619
+ surfaceElevated: "#fafaf9",
1620
+ surfaceOverlay: "#f5f5f4",
1282
1621
  foreground: "#1c1917",
1283
1622
  border: "rgba(28, 25, 23, 0.08)",
1284
1623
  mutedForeground: "#78716c"
@@ -1294,7 +1633,7 @@ var BASE_COLOR_PALETTE = {
1294
1633
  taupe: {
1295
1634
  light: {
1296
1635
  surfaceElevated: "#faf8f5",
1297
- surfaceOverlay: "#ffffff",
1636
+ surfaceOverlay: "#f3ede6",
1298
1637
  foreground: "#3b342e",
1299
1638
  border: "rgba(59, 52, 46, 0.10)",
1300
1639
  mutedForeground: "#8a7f74"
@@ -1306,6 +1645,54 @@ var BASE_COLOR_PALETTE = {
1306
1645
  border: "rgba(245, 240, 234, 0.10)",
1307
1646
  mutedForeground: "#a89f94"
1308
1647
  }
1648
+ },
1649
+ mauve: {
1650
+ light: {
1651
+ surfaceElevated: "#fbf8fb",
1652
+ surfaceOverlay: "#f4eef5",
1653
+ foreground: "#34283a",
1654
+ border: "rgba(52, 40, 58, 0.10)",
1655
+ mutedForeground: "#837388"
1656
+ },
1657
+ dark: {
1658
+ surfaceElevated: "#1b151f",
1659
+ surfaceOverlay: "#2a2130",
1660
+ foreground: "#f5eef7",
1661
+ border: "rgba(245, 238, 247, 0.10)",
1662
+ mutedForeground: "#b3a5b8"
1663
+ }
1664
+ },
1665
+ olive: {
1666
+ light: {
1667
+ surfaceElevated: "#fafbf4",
1668
+ surfaceOverlay: "#f0f3e4",
1669
+ foreground: "#2d331f",
1670
+ border: "rgba(45, 51, 31, 0.10)",
1671
+ mutedForeground: "#747b61"
1672
+ },
1673
+ dark: {
1674
+ surfaceElevated: "#171a12",
1675
+ surfaceOverlay: "#24291b",
1676
+ foreground: "#f2f5e8",
1677
+ border: "rgba(242, 245, 232, 0.10)",
1678
+ mutedForeground: "#abb196"
1679
+ }
1680
+ },
1681
+ mist: {
1682
+ light: {
1683
+ surfaceElevated: "#f7fbfb",
1684
+ surfaceOverlay: "#edf5f5",
1685
+ foreground: "#253638",
1686
+ border: "rgba(37, 54, 56, 0.10)",
1687
+ mutedForeground: "#697d80"
1688
+ },
1689
+ dark: {
1690
+ surfaceElevated: "#111b1d",
1691
+ surfaceOverlay: "#1d2b2e",
1692
+ foreground: "#edf7f8",
1693
+ border: "rgba(237, 247, 248, 0.10)",
1694
+ mutedForeground: "#9fb5b8"
1695
+ }
1309
1696
  }
1310
1697
  };
1311
1698
  var THEME_COLOR_PALETTE = {
@@ -1313,74 +1700,290 @@ var THEME_COLOR_PALETTE = {
1313
1700
  light: { ...DEFAULT_CONTROL_SOLID_LIGHT },
1314
1701
  dark: { ...DEFAULT_CONTROL_SOLID_DARK }
1315
1702
  },
1703
+ //
1704
+ // Named hues stay the same true, saturated color family in both modes —
1705
+ // dark mode gets one step deeper (never a washed-out pastel tint of the
1706
+ // light-mode color). This mirrors how shadcn's own theme presets work:
1707
+ // e.g. its "red" theme ships `oklch(0.505 … 27.518)` (~Tailwind red-700)
1708
+ // for light and `oklch(0.444 … 26.899)` (~red-800, darker not lighter)
1709
+ // for dark, with a near-white foreground in both. Hex stops below are
1710
+ // Tailwind v4's red/blue/violet/green/orange 600/700/800/900/50 values.
1316
1711
  red: {
1317
1712
  light: {
1318
- controlSolid: "#b91c1c",
1319
- controlSolidHover: "#dc2626",
1320
- controlSolidActive: "#991b1b",
1321
- controlSolidForeground: "#ffffff"
1713
+ controlSolid: "#c10007",
1714
+ // red-700
1715
+ controlSolidHover: "#e7000b",
1716
+ // red-600 — brighter on hover
1717
+ controlSolidActive: "#9f0712",
1718
+ // red-800 — darker on press
1719
+ controlSolidForeground: "#fef2f2"
1720
+ // red-50
1322
1721
  },
1323
1722
  dark: {
1324
- controlSolid: "#f87171",
1325
- controlSolidHover: "#fca5a5",
1326
- controlSolidActive: "#ef4444",
1327
- controlSolidForeground: "#450a0a"
1723
+ controlSolid: "#9f0712",
1724
+ // red-800
1725
+ controlSolidHover: "#c10007",
1726
+ // red-700 — brighter on hover
1727
+ controlSolidActive: "#82181a",
1728
+ // red-900 — darker on press
1729
+ controlSolidForeground: "#fef2f2"
1730
+ // red-50
1328
1731
  }
1329
1732
  },
1330
1733
  blue: {
1331
1734
  light: {
1332
- controlSolid: "#1d4ed8",
1333
- controlSolidHover: "#2563eb",
1334
- controlSolidActive: "#1e40af",
1335
- controlSolidForeground: "#ffffff"
1735
+ controlSolid: "#1447e6",
1736
+ // blue-700
1737
+ controlSolidHover: "#155dfc",
1738
+ // blue-600
1739
+ controlSolidActive: "#193cb8",
1740
+ // blue-800
1741
+ controlSolidForeground: "#eff6ff"
1742
+ // blue-50
1336
1743
  },
1337
1744
  dark: {
1338
- controlSolid: "#60a5fa",
1339
- controlSolidHover: "#93c5fd",
1340
- controlSolidActive: "#3b82f6",
1341
- controlSolidForeground: "#172554"
1745
+ controlSolid: "#193cb8",
1746
+ // blue-800
1747
+ controlSolidHover: "#1447e6",
1748
+ // blue-700
1749
+ controlSolidActive: "#1c398e",
1750
+ // blue-900
1751
+ controlSolidForeground: "#eff6ff"
1752
+ // blue-50
1342
1753
  }
1343
1754
  },
1344
1755
  violet: {
1345
1756
  light: {
1346
- controlSolid: "#6d28d9",
1347
- controlSolidHover: "#7c3aed",
1348
- controlSolidActive: "#5b21b6",
1349
- controlSolidForeground: "#ffffff"
1757
+ controlSolid: "#7008e7",
1758
+ // violet-700
1759
+ controlSolidHover: "#7f22fe",
1760
+ // violet-600
1761
+ controlSolidActive: "#5d0ec0",
1762
+ // violet-800
1763
+ controlSolidForeground: "#f5f3ff"
1764
+ // violet-50
1350
1765
  },
1351
1766
  dark: {
1352
- controlSolid: "#a78bfa",
1353
- controlSolidHover: "#c4b5fd",
1354
- controlSolidActive: "#8b5cf6",
1355
- controlSolidForeground: "#2e1065"
1767
+ controlSolid: "#5d0ec0",
1768
+ // violet-800
1769
+ controlSolidHover: "#7008e7",
1770
+ // violet-700
1771
+ controlSolidActive: "#4d179a",
1772
+ // violet-900
1773
+ controlSolidForeground: "#f5f3ff"
1774
+ // violet-50
1356
1775
  }
1357
1776
  },
1358
1777
  green: {
1778
+ light: {
1779
+ controlSolid: "#008236",
1780
+ // green-700
1781
+ controlSolidHover: "#00a63e",
1782
+ // green-600
1783
+ controlSolidActive: "#016630",
1784
+ // green-800
1785
+ controlSolidForeground: "#f0fdf4"
1786
+ // green-50
1787
+ },
1788
+ dark: {
1789
+ controlSolid: "#016630",
1790
+ // green-800
1791
+ controlSolidHover: "#008236",
1792
+ // green-700
1793
+ controlSolidActive: "#0d542b",
1794
+ // green-900
1795
+ controlSolidForeground: "#f0fdf4"
1796
+ // green-50
1797
+ }
1798
+ },
1799
+ orange: {
1800
+ light: {
1801
+ controlSolid: "#ca3500",
1802
+ // orange-700
1803
+ controlSolidHover: "#f54900",
1804
+ // orange-600
1805
+ controlSolidActive: "#9f2d00",
1806
+ // orange-800
1807
+ controlSolidForeground: "#fff7ed"
1808
+ // orange-50
1809
+ },
1810
+ dark: {
1811
+ controlSolid: "#9f2d00",
1812
+ // orange-800
1813
+ controlSolidHover: "#ca3500",
1814
+ // orange-700
1815
+ controlSolidActive: "#7e2a0c",
1816
+ // orange-900
1817
+ controlSolidForeground: "#fff7ed"
1818
+ // orange-50
1819
+ }
1820
+ },
1821
+ amber: {
1822
+ light: {
1823
+ controlSolid: "#b45309",
1824
+ controlSolidHover: "#d97706",
1825
+ controlSolidActive: "#92400e",
1826
+ controlSolidForeground: "#fffbeb"
1827
+ },
1828
+ dark: {
1829
+ controlSolid: "#92400e",
1830
+ controlSolidHover: "#b45309",
1831
+ controlSolidActive: "#78350f",
1832
+ controlSolidForeground: "#fffbeb"
1833
+ }
1834
+ },
1835
+ cyan: {
1836
+ light: {
1837
+ controlSolid: "#0e7490",
1838
+ controlSolidHover: "#0891b2",
1839
+ controlSolidActive: "#155e75",
1840
+ controlSolidForeground: "#ecfeff"
1841
+ },
1842
+ dark: {
1843
+ controlSolid: "#155e75",
1844
+ controlSolidHover: "#0e7490",
1845
+ controlSolidActive: "#164e63",
1846
+ controlSolidForeground: "#ecfeff"
1847
+ }
1848
+ },
1849
+ emerald: {
1359
1850
  light: {
1360
1851
  controlSolid: "#047857",
1361
1852
  controlSolidHover: "#059669",
1362
1853
  controlSolidActive: "#065f46",
1363
- controlSolidForeground: "#ffffff"
1854
+ controlSolidForeground: "#ecfdf5"
1364
1855
  },
1365
1856
  dark: {
1366
- controlSolid: "#34d399",
1367
- controlSolidHover: "#6ee7b7",
1368
- controlSolidActive: "#10b981",
1369
- controlSolidForeground: "#022c22"
1857
+ controlSolid: "#065f46",
1858
+ controlSolidHover: "#047857",
1859
+ controlSolidActive: "#064e3b",
1860
+ controlSolidForeground: "#ecfdf5"
1370
1861
  }
1371
1862
  },
1372
- orange: {
1863
+ indigo: {
1864
+ light: {
1865
+ controlSolid: "#4338ca",
1866
+ controlSolidHover: "#4f46e5",
1867
+ controlSolidActive: "#3730a3",
1868
+ controlSolidForeground: "#eef2ff"
1869
+ },
1870
+ dark: {
1871
+ controlSolid: "#3730a3",
1872
+ controlSolidHover: "#4338ca",
1873
+ controlSolidActive: "#312e81",
1874
+ controlSolidForeground: "#eef2ff"
1875
+ }
1876
+ },
1877
+ pink: {
1373
1878
  light: {
1374
- controlSolid: "#c2410c",
1375
- controlSolidHover: "#ea580c",
1376
- controlSolidActive: "#9a3412",
1377
- controlSolidForeground: "#ffffff"
1879
+ controlSolid: "#be185d",
1880
+ controlSolidHover: "#db2777",
1881
+ controlSolidActive: "#9d174d",
1882
+ controlSolidForeground: "#fdf2f8"
1378
1883
  },
1379
1884
  dark: {
1380
- controlSolid: "#fb923c",
1381
- controlSolidHover: "#fdba74",
1382
- controlSolidActive: "#f97316",
1383
- controlSolidForeground: "#431407"
1885
+ controlSolid: "#9d174d",
1886
+ controlSolidHover: "#be185d",
1887
+ controlSolidActive: "#831843",
1888
+ controlSolidForeground: "#fdf2f8"
1889
+ }
1890
+ },
1891
+ purple: {
1892
+ light: {
1893
+ controlSolid: "#7e22ce",
1894
+ controlSolidHover: "#9333ea",
1895
+ controlSolidActive: "#6b21a8",
1896
+ controlSolidForeground: "#faf5ff"
1897
+ },
1898
+ dark: {
1899
+ controlSolid: "#6b21a8",
1900
+ controlSolidHover: "#7e22ce",
1901
+ controlSolidActive: "#581c87",
1902
+ controlSolidForeground: "#faf5ff"
1903
+ }
1904
+ },
1905
+ rose: {
1906
+ light: {
1907
+ controlSolid: "#be123c",
1908
+ controlSolidHover: "#e11d48",
1909
+ controlSolidActive: "#9f1239",
1910
+ controlSolidForeground: "#fff1f2"
1911
+ },
1912
+ dark: {
1913
+ controlSolid: "#9f1239",
1914
+ controlSolidHover: "#be123c",
1915
+ controlSolidActive: "#881337",
1916
+ controlSolidForeground: "#fff1f2"
1917
+ }
1918
+ },
1919
+ sky: {
1920
+ light: {
1921
+ controlSolid: "#0369a1",
1922
+ controlSolidHover: "#0284c7",
1923
+ controlSolidActive: "#075985",
1924
+ controlSolidForeground: "#f0f9ff"
1925
+ },
1926
+ dark: {
1927
+ controlSolid: "#075985",
1928
+ controlSolidHover: "#0369a1",
1929
+ controlSolidActive: "#0c4a6e",
1930
+ controlSolidForeground: "#f0f9ff"
1931
+ }
1932
+ },
1933
+ teal: {
1934
+ light: {
1935
+ controlSolid: "#0f766e",
1936
+ controlSolidHover: "#0d9488",
1937
+ controlSolidActive: "#115e59",
1938
+ controlSolidForeground: "#f0fdfa"
1939
+ },
1940
+ dark: {
1941
+ controlSolid: "#115e59",
1942
+ controlSolidHover: "#0f766e",
1943
+ controlSolidActive: "#134e4a",
1944
+ controlSolidForeground: "#f0fdfa"
1945
+ }
1946
+ },
1947
+ yellow: {
1948
+ light: {
1949
+ controlSolid: "#a16207",
1950
+ controlSolidHover: "#ca8a04",
1951
+ controlSolidActive: "#854d0e",
1952
+ controlSolidForeground: "#fefce8"
1953
+ },
1954
+ dark: {
1955
+ controlSolid: "#854d0e",
1956
+ controlSolidHover: "#a16207",
1957
+ controlSolidActive: "#713f12",
1958
+ controlSolidForeground: "#fefce8"
1959
+ }
1960
+ },
1961
+ fuchsia: {
1962
+ light: {
1963
+ controlSolid: "#a21caf",
1964
+ controlSolidHover: "#c026d3",
1965
+ controlSolidActive: "#86198f",
1966
+ controlSolidForeground: "#fdf4ff"
1967
+ },
1968
+ dark: {
1969
+ controlSolid: "#86198f",
1970
+ controlSolidHover: "#a21caf",
1971
+ controlSolidActive: "#701a75",
1972
+ controlSolidForeground: "#fdf4ff"
1973
+ }
1974
+ },
1975
+ lime: {
1976
+ light: {
1977
+ controlSolid: "#4d7c0f",
1978
+ controlSolidHover: "#65a30d",
1979
+ controlSolidActive: "#3f6212",
1980
+ controlSolidForeground: "#f7fee7"
1981
+ },
1982
+ dark: {
1983
+ controlSolid: "#3f6212",
1984
+ controlSolidHover: "#4d7c0f",
1985
+ controlSolidActive: "#365314",
1986
+ controlSolidForeground: "#f7fee7"
1384
1987
  }
1385
1988
  }
1386
1989
  };
@@ -1424,6 +2027,54 @@ var CHART_PALETTES = {
1424
2027
  orange: {
1425
2028
  light: ["#eda100", "#f97316", "#e34948", "#ca8a04", "#c2410c", "#db2777"],
1426
2029
  dark: ["#c98500", "#fb923c", "#e66767", "#facc15", "#fdba74", "#f472b6"]
2030
+ },
2031
+ amber: {
2032
+ light: ["#d97706", "#f59e0b", "#b45309", "#fbbf24", "#92400e", "#f97316"],
2033
+ dark: ["#fbbf24", "#f59e0b", "#fcd34d", "#d97706", "#fde68a", "#fb923c"]
2034
+ },
2035
+ cyan: {
2036
+ light: ["#0891b2", "#06b6d4", "#0e7490", "#22d3ee", "#155e75", "#0284c7"],
2037
+ dark: ["#22d3ee", "#67e8f9", "#06b6d4", "#38bdf8", "#a5f3fc", "#0891b2"]
2038
+ },
2039
+ emerald: {
2040
+ light: ["#059669", "#10b981", "#047857", "#34d399", "#065f46", "#0d9488"],
2041
+ dark: ["#34d399", "#6ee7b7", "#10b981", "#2dd4bf", "#a7f3d0", "#059669"]
2042
+ },
2043
+ indigo: {
2044
+ light: ["#4f46e5", "#6366f1", "#4338ca", "#818cf8", "#3730a3", "#7c3aed"],
2045
+ dark: ["#818cf8", "#a5b4fc", "#6366f1", "#a78bfa", "#c7d2fe", "#4f46e5"]
2046
+ },
2047
+ pink: {
2048
+ light: ["#db2777", "#ec4899", "#be185d", "#f472b6", "#9d174d", "#e11d48"],
2049
+ dark: ["#f472b6", "#f9a8d4", "#ec4899", "#fb7185", "#fbcfe8", "#db2777"]
2050
+ },
2051
+ purple: {
2052
+ light: ["#9333ea", "#a855f7", "#7e22ce", "#c084fc", "#6b21a8", "#7c3aed"],
2053
+ dark: ["#c084fc", "#d8b4fe", "#a855f7", "#a78bfa", "#e9d5ff", "#9333ea"]
2054
+ },
2055
+ rose: {
2056
+ light: ["#e11d48", "#f43f5e", "#be123c", "#fb7185", "#9f1239", "#db2777"],
2057
+ dark: ["#fb7185", "#fda4af", "#f43f5e", "#f472b6", "#ffe4e6", "#e11d48"]
2058
+ },
2059
+ sky: {
2060
+ light: ["#0284c7", "#0ea5e9", "#0369a1", "#38bdf8", "#075985", "#2563eb"],
2061
+ dark: ["#38bdf8", "#7dd3fc", "#0ea5e9", "#60a5fa", "#bae6fd", "#0284c7"]
2062
+ },
2063
+ teal: {
2064
+ light: ["#0d9488", "#14b8a6", "#0f766e", "#2dd4bf", "#115e59", "#0891b2"],
2065
+ dark: ["#2dd4bf", "#5eead4", "#14b8a6", "#22d3ee", "#99f6e4", "#0d9488"]
2066
+ },
2067
+ yellow: {
2068
+ light: ["#ca8a04", "#eab308", "#a16207", "#facc15", "#854d0e", "#f59e0b"],
2069
+ dark: ["#facc15", "#fde047", "#eab308", "#fbbf24", "#fef08a", "#ca8a04"]
2070
+ },
2071
+ fuchsia: {
2072
+ light: ["#c026d3", "#d946ef", "#a21caf", "#e879f9", "#86198f", "#db2777"],
2073
+ dark: ["#e879f9", "#f0abfc", "#d946ef", "#f472b6", "#fae8ff", "#c026d3"]
2074
+ },
2075
+ lime: {
2076
+ light: ["#65a30d", "#84cc16", "#4d7c0f", "#a3e635", "#3f6212", "#16a34a"],
2077
+ dark: ["#a3e635", "#bef264", "#84cc16", "#4ade80", "#d9f99d", "#65a30d"]
1427
2078
  }
1428
2079
  },
1429
2080
  warm: {
@@ -1437,6 +2088,70 @@ var CHART_PALETTES = {
1437
2088
  mixed: {
1438
2089
  light: ["#2a78d6", "#e34948", "#1baf7a", "#eda100", "#4a3aa7", "#db2777"],
1439
2090
  dark: ["#3987e5", "#e66767", "#199e70", "#c98500", "#9085e9", "#f472b6"]
2091
+ },
2092
+ neutral: {
2093
+ light: ["#404040", "#737373", "#a3a3a3", "#525252", "#262626", "#d4d4d4"],
2094
+ dark: ["#d4d4d4", "#a3a3a3", "#737373", "#e5e5e5", "#f5f5f5", "#525252"]
2095
+ },
2096
+ zinc: {
2097
+ light: ["#3f3f46", "#71717a", "#a1a1aa", "#52525b", "#27272a", "#d4d4d8"],
2098
+ dark: ["#d4d4d8", "#a1a1aa", "#71717a", "#e4e4e7", "#f4f4f5", "#52525b"]
2099
+ },
2100
+ stone: {
2101
+ light: ["#44403c", "#78716c", "#a8a29e", "#57534e", "#292524", "#d6d3d1"],
2102
+ dark: ["#d6d3d1", "#a8a29e", "#78716c", "#e7e5e4", "#fafaf9", "#57534e"]
2103
+ },
2104
+ gray: {
2105
+ light: ["#374151", "#6b7280", "#9ca3af", "#4b5563", "#1f2937", "#d1d5db"],
2106
+ dark: ["#d1d5db", "#9ca3af", "#6b7280", "#e5e7eb", "#f9fafb", "#4b5563"]
2107
+ },
2108
+ amber: {
2109
+ light: ["#d97706", "#f59e0b", "#b45309", "#fbbf24", "#92400e", "#f97316"],
2110
+ dark: ["#fbbf24", "#f59e0b", "#fcd34d", "#d97706", "#fde68a", "#fb923c"]
2111
+ },
2112
+ cyan: {
2113
+ light: ["#0891b2", "#06b6d4", "#0e7490", "#22d3ee", "#155e75", "#0284c7"],
2114
+ dark: ["#22d3ee", "#67e8f9", "#06b6d4", "#38bdf8", "#a5f3fc", "#0891b2"]
2115
+ },
2116
+ emerald: {
2117
+ light: ["#059669", "#10b981", "#047857", "#34d399", "#065f46", "#0d9488"],
2118
+ dark: ["#34d399", "#6ee7b7", "#10b981", "#2dd4bf", "#a7f3d0", "#059669"]
2119
+ },
2120
+ indigo: {
2121
+ light: ["#4f46e5", "#6366f1", "#4338ca", "#818cf8", "#3730a3", "#7c3aed"],
2122
+ dark: ["#818cf8", "#a5b4fc", "#6366f1", "#a78bfa", "#c7d2fe", "#4f46e5"]
2123
+ },
2124
+ lime: {
2125
+ light: ["#65a30d", "#84cc16", "#4d7c0f", "#a3e635", "#3f6212", "#16a34a"],
2126
+ dark: ["#a3e635", "#bef264", "#84cc16", "#4ade80", "#d9f99d", "#65a30d"]
2127
+ },
2128
+ pink: {
2129
+ light: ["#db2777", "#ec4899", "#be185d", "#f472b6", "#9d174d", "#e11d48"],
2130
+ dark: ["#f472b6", "#f9a8d4", "#ec4899", "#fb7185", "#fbcfe8", "#db2777"]
2131
+ },
2132
+ purple: {
2133
+ light: ["#9333ea", "#a855f7", "#7e22ce", "#c084fc", "#6b21a8", "#7c3aed"],
2134
+ dark: ["#c084fc", "#d8b4fe", "#a855f7", "#a78bfa", "#e9d5ff", "#9333ea"]
2135
+ },
2136
+ rose: {
2137
+ light: ["#e11d48", "#f43f5e", "#be123c", "#fb7185", "#9f1239", "#db2777"],
2138
+ dark: ["#fb7185", "#fda4af", "#f43f5e", "#f472b6", "#ffe4e6", "#e11d48"]
2139
+ },
2140
+ sky: {
2141
+ light: ["#0284c7", "#0ea5e9", "#0369a1", "#38bdf8", "#075985", "#2563eb"],
2142
+ dark: ["#38bdf8", "#7dd3fc", "#0ea5e9", "#60a5fa", "#bae6fd", "#0284c7"]
2143
+ },
2144
+ teal: {
2145
+ light: ["#0d9488", "#14b8a6", "#0f766e", "#2dd4bf", "#115e59", "#0891b2"],
2146
+ dark: ["#2dd4bf", "#5eead4", "#14b8a6", "#22d3ee", "#99f6e4", "#0d9488"]
2147
+ },
2148
+ yellow: {
2149
+ light: ["#ca8a04", "#eab308", "#a16207", "#facc15", "#854d0e", "#f59e0b"],
2150
+ dark: ["#facc15", "#fde047", "#eab308", "#fbbf24", "#fef08a", "#ca8a04"]
2151
+ },
2152
+ taupe: {
2153
+ light: ["#8a6f55", "#a78b71", "#6f5a46", "#c4aa8f", "#4f4034", "#d97706"],
2154
+ dark: ["#c4aa8f", "#dcc8b2", "#a78b71", "#f3ede6", "#8a6f55", "#fbbf24"]
1440
2155
  }
1441
2156
  };
1442
2157
 
@@ -1446,6 +2161,51 @@ var HEADING_FONT_PACKAGES = {
1446
2161
  packageName: "@fontsource/playfair-display",
1447
2162
  importSpecifier: "@fontsource/playfair-display",
1448
2163
  familyName: "Playfair Display"
2164
+ },
2165
+ "instrument-serif": {
2166
+ packageName: "@fontsource/instrument-serif",
2167
+ importSpecifier: "@fontsource/instrument-serif",
2168
+ familyName: "Instrument Serif"
2169
+ },
2170
+ fraunces: {
2171
+ packageName: "@fontsource/fraunces",
2172
+ importSpecifier: "@fontsource/fraunces",
2173
+ familyName: "Fraunces"
2174
+ },
2175
+ "space-grotesk": {
2176
+ packageName: "@fontsource/space-grotesk",
2177
+ importSpecifier: "@fontsource/space-grotesk",
2178
+ familyName: "Space Grotesk"
2179
+ },
2180
+ "noto-serif": {
2181
+ packageName: "@fontsource/noto-serif",
2182
+ importSpecifier: "@fontsource/noto-serif",
2183
+ familyName: "Noto Serif"
2184
+ },
2185
+ "eb-garamond": {
2186
+ packageName: "@fontsource/eb-garamond",
2187
+ importSpecifier: "@fontsource/eb-garamond",
2188
+ familyName: "EB Garamond"
2189
+ },
2190
+ lora: {
2191
+ packageName: "@fontsource/lora",
2192
+ importSpecifier: "@fontsource/lora",
2193
+ familyName: "Lora"
2194
+ },
2195
+ montserrat: {
2196
+ packageName: "@fontsource/montserrat",
2197
+ importSpecifier: "@fontsource/montserrat",
2198
+ familyName: "Montserrat"
2199
+ },
2200
+ manrope: {
2201
+ packageName: "@fontsource/manrope",
2202
+ importSpecifier: "@fontsource/manrope",
2203
+ familyName: "Manrope"
2204
+ },
2205
+ inter: {
2206
+ packageName: "@fontsource/inter",
2207
+ importSpecifier: "@fontsource/inter",
2208
+ familyName: "Inter"
1449
2209
  }
1450
2210
  };
1451
2211
  var BODY_FONT_PACKAGES = {
@@ -1453,15 +2213,78 @@ var BODY_FONT_PACKAGES = {
1453
2213
  packageName: "@fontsource/inter",
1454
2214
  importSpecifier: "@fontsource/inter",
1455
2215
  familyName: "Inter"
2216
+ },
2217
+ geist: {
2218
+ packageName: "@fontsource/geist",
2219
+ importSpecifier: "@fontsource/geist",
2220
+ familyName: "Geist"
2221
+ },
2222
+ "dm-sans": {
2223
+ packageName: "@fontsource/dm-sans",
2224
+ importSpecifier: "@fontsource/dm-sans",
2225
+ familyName: "DM Sans"
2226
+ },
2227
+ "plus-jakarta-sans": {
2228
+ packageName: "@fontsource/plus-jakarta-sans",
2229
+ importSpecifier: "@fontsource/plus-jakarta-sans",
2230
+ familyName: "Plus Jakarta Sans"
2231
+ },
2232
+ figtree: {
2233
+ packageName: "@fontsource/figtree",
2234
+ importSpecifier: "@fontsource/figtree",
2235
+ familyName: "Figtree"
2236
+ },
2237
+ "noto-sans": {
2238
+ packageName: "@fontsource/noto-sans",
2239
+ importSpecifier: "@fontsource/noto-sans",
2240
+ familyName: "Noto Sans"
2241
+ },
2242
+ "public-sans": {
2243
+ packageName: "@fontsource/public-sans",
2244
+ importSpecifier: "@fontsource/public-sans",
2245
+ familyName: "Public Sans"
2246
+ },
2247
+ outfit: {
2248
+ packageName: "@fontsource/outfit",
2249
+ importSpecifier: "@fontsource/outfit",
2250
+ familyName: "Outfit"
2251
+ },
2252
+ manrope: {
2253
+ packageName: "@fontsource/manrope",
2254
+ importSpecifier: "@fontsource/manrope",
2255
+ familyName: "Manrope"
2256
+ },
2257
+ "ibm-plex-sans": {
2258
+ packageName: "@fontsource/ibm-plex-sans",
2259
+ importSpecifier: "@fontsource/ibm-plex-sans",
2260
+ familyName: "IBM Plex Sans"
1456
2261
  }
1457
2262
  };
1458
2263
  var HEADING_FONT_CSS = {
1459
2264
  system: "var(--font-display)",
1460
- playfair: "'Playfair Display', Georgia, 'Times New Roman', serif"
2265
+ playfair: "'Playfair Display', Georgia, 'Times New Roman', serif",
2266
+ "instrument-serif": "'Instrument Serif', Georgia, 'Times New Roman', serif",
2267
+ fraunces: "'Fraunces', Georgia, 'Times New Roman', serif",
2268
+ "space-grotesk": "'Space Grotesk', ui-sans-serif, system-ui, sans-serif",
2269
+ "noto-serif": "'Noto Serif', Georgia, 'Times New Roman', serif",
2270
+ "eb-garamond": "'EB Garamond', Georgia, 'Times New Roman', serif",
2271
+ lora: "'Lora', Georgia, 'Times New Roman', serif",
2272
+ montserrat: "'Montserrat', ui-sans-serif, system-ui, sans-serif",
2273
+ manrope: "'Manrope', ui-sans-serif, system-ui, sans-serif",
2274
+ inter: "'Inter', ui-sans-serif, system-ui, sans-serif"
1461
2275
  };
1462
2276
  var BODY_FONT_CSS = {
1463
2277
  system: "var(--font-sans)",
1464
- inter: "'Inter', ui-sans-serif, system-ui, sans-serif"
2278
+ inter: "'Inter', ui-sans-serif, system-ui, sans-serif",
2279
+ geist: "'Geist', ui-sans-serif, system-ui, sans-serif",
2280
+ "dm-sans": "'DM Sans', ui-sans-serif, system-ui, sans-serif",
2281
+ "plus-jakarta-sans": "'Plus Jakarta Sans', ui-sans-serif, system-ui, sans-serif",
2282
+ figtree: "'Figtree', ui-sans-serif, system-ui, sans-serif",
2283
+ "noto-sans": "'Noto Sans', ui-sans-serif, system-ui, sans-serif",
2284
+ "public-sans": "'Public Sans', ui-sans-serif, system-ui, sans-serif",
2285
+ outfit: "'Outfit', ui-sans-serif, system-ui, sans-serif",
2286
+ manrope: "'Manrope', ui-sans-serif, system-ui, sans-serif",
2287
+ "ibm-plex-sans": "'IBM Plex Sans', ui-sans-serif, system-ui, sans-serif"
1465
2288
  };
1466
2289
 
1467
2290
  // ../wensity-preset/src/resolve.ts
@@ -1520,7 +2343,19 @@ var COLORED_THEME_IDS = /* @__PURE__ */ new Set([
1520
2343
  "blue",
1521
2344
  "violet",
1522
2345
  "green",
1523
- "orange"
2346
+ "orange",
2347
+ "amber",
2348
+ "cyan",
2349
+ "emerald",
2350
+ "indigo",
2351
+ "pink",
2352
+ "purple",
2353
+ "rose",
2354
+ "sky",
2355
+ "teal",
2356
+ "yellow",
2357
+ "fuchsia",
2358
+ "lime"
1524
2359
  ]);
1525
2360
  function colorMix(source, percent) {
1526
2361
  return `color-mix(in srgb, ${source} ${percent}%, transparent)`;
@@ -1638,6 +2473,34 @@ function resolveStyleConfigCanonical(style) {
1638
2473
  dark: resolveMode("dark")
1639
2474
  };
1640
2475
  }
2476
+ function resolvedTokensToCssVars(tokens, mode) {
2477
+ const modeTokens = tokens[mode];
2478
+ return {
2479
+ [RESOLVED_TOKEN_CSS_VARS.surfaceElevated]: modeTokens.surfaceElevated,
2480
+ [RESOLVED_TOKEN_CSS_VARS.surfaceOverlay]: modeTokens.surfaceOverlay,
2481
+ [RESOLVED_TOKEN_CSS_VARS.surfaceHover]: modeTokens.surfaceHover,
2482
+ [RESOLVED_TOKEN_CSS_VARS.surfaceActive]: modeTokens.surfaceActive,
2483
+ [RESOLVED_TOKEN_CSS_VARS.surfaceSelected]: modeTokens.surfaceSelected,
2484
+ [RESOLVED_TOKEN_CSS_VARS.borderSubtle]: modeTokens.borderSubtle,
2485
+ [RESOLVED_TOKEN_CSS_VARS.ring]: modeTokens.ring,
2486
+ [RESOLVED_TOKEN_CSS_VARS.textSecondary]: modeTokens.textSecondary,
2487
+ [RESOLVED_TOKEN_CSS_VARS.textPlaceholder]: modeTokens.textPlaceholder,
2488
+ [RESOLVED_TOKEN_CSS_VARS.controlSolid]: modeTokens.controlSolid,
2489
+ [RESOLVED_TOKEN_CSS_VARS.controlSolidHover]: modeTokens.controlSolidHover,
2490
+ [RESOLVED_TOKEN_CSS_VARS.controlSolidActive]: modeTokens.controlSolidActive,
2491
+ [RESOLVED_TOKEN_CSS_VARS.controlSolidForeground]: modeTokens.controlSolidForeground,
2492
+ [RESOLVED_TOKEN_CSS_VARS.chart1]: modeTokens.chart1,
2493
+ [RESOLVED_TOKEN_CSS_VARS.chart2]: modeTokens.chart2,
2494
+ [RESOLVED_TOKEN_CSS_VARS.chart3]: modeTokens.chart3,
2495
+ [RESOLVED_TOKEN_CSS_VARS.chart4]: modeTokens.chart4,
2496
+ [RESOLVED_TOKEN_CSS_VARS.chart5]: modeTokens.chart5,
2497
+ [RESOLVED_TOKEN_CSS_VARS.chart6]: modeTokens.chart6,
2498
+ [RESOLVED_TOKEN_CSS_VARS.radius]: tokens.radius,
2499
+ [RESOLVED_TOKEN_CSS_VARS.fontSans]: tokens.fontSans,
2500
+ [RESOLVED_TOKEN_CSS_VARS.fontDisplay]: tokens.fontDisplay,
2501
+ [RESOLVED_TOKEN_CSS_VARS.fontMono]: tokens.fontMono
2502
+ };
2503
+ }
1641
2504
 
1642
2505
  // src/paths.ts
1643
2506
  import { dirname, isAbsolute, relative, resolve, sep } from "path";
@@ -2608,13 +3471,13 @@ function missingDependencies(projectRoot, dependencies) {
2608
3471
  function readInstalledPackageNames(projectRoot) {
2609
3472
  const packageJsonPath = join4(projectRoot, "package.json");
2610
3473
  if (!existsSync2(packageJsonPath)) return void 0;
2611
- const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8"));
3474
+ const packageJson2 = JSON.parse(readFileSync(packageJsonPath, "utf8"));
2612
3475
  return new Set(
2613
3476
  Object.keys({
2614
- ...packageJson.dependencies,
2615
- ...packageJson.devDependencies,
2616
- ...packageJson.peerDependencies,
2617
- ...packageJson.optionalDependencies
3477
+ ...packageJson2.dependencies,
3478
+ ...packageJson2.devDependencies,
3479
+ ...packageJson2.peerDependencies,
3480
+ ...packageJson2.optionalDependencies
2618
3481
  })
2619
3482
  );
2620
3483
  }
@@ -2709,8 +3572,15 @@ function encodePresetFileJson(parsed) {
2709
3572
  }
2710
3573
  if (v === 2) {
2711
3574
  const payload = validateCodecPayloadCanonical(parsed);
2712
- const json = stableStringifyCodecPayloadCanonical(payload);
2713
- return `${PRESET_CODEC_PREFIX}.${Buffer.from(json, "utf8").toString("base64url")}`;
3575
+ return encodePresetCanonical({
3576
+ baseColor: payload.baseColor,
3577
+ themeColor: payload.themeColor,
3578
+ chartPalette: payload.chartPalette,
3579
+ headingFont: payload.headingFont,
3580
+ bodyFont: payload.bodyFont,
3581
+ iconLibrary: payload.iconLibrary,
3582
+ radius: payload.radius
3583
+ });
2714
3584
  }
2715
3585
  throw new PresetValidationError(
2716
3586
  `Unsupported preset payload version ${String(v)}. This preset may require a newer version of the Wensity CLI.`,
@@ -2744,12 +3614,115 @@ function toCliPresetError(error) {
2744
3614
  const message = error.message;
2745
3615
  if (/Unsupported preset payload version/i.test(message)) {
2746
3616
  return new CliError(
2747
- `Invalid preset: ${message} Upgrade the Wensity CLI (wensity@0.1.7 or newer) and try again.`
3617
+ `Invalid preset: ${message} Upgrade the Wensity CLI (wensity@latest) and try again.`
2748
3618
  );
2749
3619
  }
2750
3620
  return new CliError(`Invalid preset: ${message}`);
2751
3621
  }
2752
3622
 
3623
+ // src/preset-inspect.ts
3624
+ function createPresetInspectSummary(input) {
3625
+ const tokens = resolveTokensForStyle(input.style);
3626
+ const fontPlan = fontPlanForStyle(input.style);
3627
+ return {
3628
+ presetCode: input.presetCode,
3629
+ kind: isCanonicalStyleShape(input.style) ? "canonical" : "legacy",
3630
+ style: input.style,
3631
+ fontPackages: fontPlan.packages,
3632
+ fontImports: fontPlan.importLines,
3633
+ tokens: {
3634
+ radius: tokens.radius,
3635
+ fonts: {
3636
+ sans: tokens.fontSans,
3637
+ display: tokens.fontDisplay,
3638
+ mono: tokens.fontMono
3639
+ },
3640
+ light: summarizeMode(tokens, "light"),
3641
+ dark: summarizeMode(tokens, "dark")
3642
+ }
3643
+ };
3644
+ }
3645
+ function formatPresetInspectSummary(summary) {
3646
+ return [
3647
+ panel("Preset", [
3648
+ `Code: ${summary.presetCode}`,
3649
+ `Shape: ${summary.kind}`,
3650
+ `Icon library: ${summary.style.iconLibrary}`,
3651
+ `Radius: ${summary.style.radius} (${summary.tokens.radius})`
3652
+ ]),
3653
+ formatStylePanel(summary),
3654
+ formatFontPanel(summary),
3655
+ formatTokenPanel(summary)
3656
+ ].join("\n");
3657
+ }
3658
+ function fontPlanForStyle(style) {
3659
+ if (!isCanonicalStyleShape(style)) {
3660
+ return { packages: [], importLines: [], block: null };
3661
+ }
3662
+ return planFontInstall(style.headingFont, style.bodyFont);
3663
+ }
3664
+ function summarizeMode(tokens, mode) {
3665
+ const cssVars = resolvedTokensToCssVars(tokens, mode);
3666
+ return {
3667
+ controlSolid: cssVars["--primitive-control-solid"] ?? tokens[mode].controlSolid,
3668
+ ring: cssVars["--primitive-ring"] ?? tokens[mode].ring,
3669
+ surfaceSelected: cssVars["--primitive-surface-selected"] ?? tokens[mode].surfaceSelected,
3670
+ chart: [
3671
+ cssVars["--primitive-chart-1"] ?? tokens[mode].chart1,
3672
+ cssVars["--primitive-chart-2"] ?? tokens[mode].chart2,
3673
+ cssVars["--primitive-chart-3"] ?? tokens[mode].chart3,
3674
+ cssVars["--primitive-chart-4"] ?? tokens[mode].chart4,
3675
+ cssVars["--primitive-chart-5"] ?? tokens[mode].chart5,
3676
+ cssVars["--primitive-chart-6"] ?? tokens[mode].chart6
3677
+ ]
3678
+ };
3679
+ }
3680
+ function formatStylePanel(summary) {
3681
+ if (isCanonicalStyleShape(summary.style)) {
3682
+ return panel("Style", [
3683
+ `Base color: ${summary.style.baseColor}`,
3684
+ `Theme color: ${summary.style.themeColor}`,
3685
+ `Chart palette: ${summary.style.chartPalette}`,
3686
+ `Heading font: ${summary.style.headingFont}`,
3687
+ `Body font: ${summary.style.bodyFont}`
3688
+ ]);
3689
+ }
3690
+ return panel("Style", [
3691
+ "Legacy payload",
3692
+ `Font sans: ${summary.style.fonts.sans}`,
3693
+ `Font display: ${summary.style.fonts.display}`,
3694
+ `Font mono: ${summary.style.fonts.mono}`
3695
+ ]);
3696
+ }
3697
+ function formatFontPanel(summary) {
3698
+ const rows = [
3699
+ `--primitive-font-sans: ${summary.tokens.fonts.sans}`,
3700
+ `--primitive-font-display: ${summary.tokens.fonts.display}`,
3701
+ `--primitive-font-mono: ${summary.tokens.fonts.mono}`
3702
+ ];
3703
+ const install = summary.fontPackages.length ? [
3704
+ "",
3705
+ action(`Install packages: ${summary.fontPackages.join(" ")}`),
3706
+ action(`CSS imports: ${summary.fontImports.join(" ")}`)
3707
+ ].join("\n") : ["", action("No Fontsource packages required.")].join("\n");
3708
+ return `${panel("Fonts", rows)}${install}`;
3709
+ }
3710
+ function formatTokenPanel(summary) {
3711
+ return panel("Resolved primitive tokens", [
3712
+ "Light",
3713
+ ` control: ${summary.tokens.light.controlSolid}`,
3714
+ ` ring: ${summary.tokens.light.ring}`,
3715
+ ` selected: ${summary.tokens.light.surfaceSelected}`,
3716
+ ` chart: ${summary.tokens.light.chart.join(", ")}`,
3717
+ "",
3718
+ "Dark",
3719
+ ` control: ${summary.tokens.dark.controlSolid}`,
3720
+ ` ring: ${summary.tokens.dark.ring}`,
3721
+ ` selected: ${summary.tokens.dark.surfaceSelected}`,
3722
+ ` chart: ${summary.tokens.dark.chart.join(", ")}`
3723
+ ]);
3724
+ }
3725
+
2753
3726
  // src/preset-init-writes.ts
2754
3727
  import { mkdir as mkdir3, writeFile as writeFile3 } from "fs/promises";
2755
3728
  import { dirname as dirname4 } from "path";
@@ -2777,6 +3750,351 @@ async function commitPresetInitWrites(input) {
2777
3750
  }
2778
3751
  }
2779
3752
 
3753
+ // src/project-templates.ts
3754
+ import { mkdir as mkdir4, readFile as readFile4, writeFile as writeFile4 } from "fs/promises";
3755
+ import { dirname as dirname5, join as join5 } from "path";
3756
+ var PROJECT_TEMPLATE_IDS = ["next", "vite", "astro", "react-router"];
3757
+ function parseProjectTemplate(value) {
3758
+ if (!value) return void 0;
3759
+ const normalized = value.toLowerCase();
3760
+ if (normalized === "nextjs" || normalized === "next.js") return "next";
3761
+ if (normalized === "reactrouter" || normalized === "react-router") return "react-router";
3762
+ if (isProjectTemplateId(normalized)) return normalized;
3763
+ throw new CliError(
3764
+ `Unsupported template "${value}". Use one of: ${PROJECT_TEMPLATE_IDS.join(", ")}.`
3765
+ );
3766
+ }
3767
+ function getProjectTemplateDefinition(id) {
3768
+ return templateDefinitions[id];
3769
+ }
3770
+ function applyTemplateToConfig(config, template) {
3771
+ return {
3772
+ ...config,
3773
+ tailwind: {
3774
+ ...config.tailwind,
3775
+ css: template.cssPath
3776
+ }
3777
+ };
3778
+ }
3779
+ async function scaffoldProjectTemplate(projectRoot, template, options) {
3780
+ const planned = template.files.map((file) => file.path);
3781
+ if (!options.force) {
3782
+ const conflicts = [];
3783
+ for (const file of template.files) {
3784
+ if (await fileExists(join5(projectRoot, file.path))) {
3785
+ conflicts.push(file.path);
3786
+ }
3787
+ }
3788
+ if (conflicts.length > 0) {
3789
+ throw new CliError(
3790
+ `Template "${template.id}" would overwrite existing files: ${conflicts.join(", ")}. Use --force to overwrite.`
3791
+ );
3792
+ }
3793
+ }
3794
+ if (options.dryRun) return planned;
3795
+ for (const file of template.files) {
3796
+ await writeTemplateFile(projectRoot, file);
3797
+ }
3798
+ return planned;
3799
+ }
3800
+ async function writeTemplateFile(projectRoot, file) {
3801
+ const absolute = join5(projectRoot, file.path);
3802
+ await mkdir4(dirname5(absolute), { recursive: true });
3803
+ if (file.path === "package.json" && await fileExists(absolute)) {
3804
+ const existing = JSON.parse(await readFile4(absolute, "utf8"));
3805
+ const next = JSON.parse(file.content);
3806
+ await writeFile4(
3807
+ absolute,
3808
+ `${JSON.stringify(mergePackageJson(existing, next), null, 2)}
3809
+ `,
3810
+ "utf8"
3811
+ );
3812
+ return;
3813
+ }
3814
+ await writeFile4(absolute, file.content, "utf8");
3815
+ }
3816
+ function mergePackageJson(existing, next) {
3817
+ return {
3818
+ ...existing,
3819
+ ...next,
3820
+ scripts: {
3821
+ ...isRecord(existing.scripts) ? existing.scripts : {},
3822
+ ...isRecord(next.scripts) ? next.scripts : {}
3823
+ },
3824
+ dependencies: {
3825
+ ...isRecord(existing.dependencies) ? existing.dependencies : {},
3826
+ ...isRecord(next.dependencies) ? next.dependencies : {}
3827
+ },
3828
+ devDependencies: {
3829
+ ...isRecord(existing.devDependencies) ? existing.devDependencies : {},
3830
+ ...isRecord(next.devDependencies) ? next.devDependencies : {}
3831
+ }
3832
+ };
3833
+ }
3834
+ function isRecord(value) {
3835
+ return typeof value === "object" && value !== null && !Array.isArray(value);
3836
+ }
3837
+ function isProjectTemplateId(value) {
3838
+ return PROJECT_TEMPLATE_IDS.includes(value);
3839
+ }
3840
+ function packageJson(name, input) {
3841
+ return `${JSON.stringify({
3842
+ name,
3843
+ private: true,
3844
+ version: "0.1.0",
3845
+ type: "module",
3846
+ scripts: input.scripts,
3847
+ dependencies: input.dependencies,
3848
+ devDependencies: input.devDependencies ?? {}
3849
+ }, null, 2)}
3850
+ `;
3851
+ }
3852
+ var templateDefinitions = {
3853
+ next: {
3854
+ id: "next",
3855
+ label: "Next.js",
3856
+ cssPath: "src/app/globals.css",
3857
+ files: [
3858
+ {
3859
+ path: "package.json",
3860
+ content: packageJson("wensity-next-app", {
3861
+ scripts: {
3862
+ dev: "next dev",
3863
+ build: "next build",
3864
+ start: "next start"
3865
+ },
3866
+ dependencies: {
3867
+ "next": "^16.2.4",
3868
+ "react": "^19.2.4",
3869
+ "react-dom": "^19.2.4"
3870
+ },
3871
+ devDependencies: {
3872
+ "@types/node": "^22.10.2",
3873
+ "@types/react": "^19.0.0",
3874
+ "@types/react-dom": "^19.0.0",
3875
+ "tailwindcss": "^4.0.0",
3876
+ "typescript": "^5.7.2"
3877
+ }
3878
+ })
3879
+ },
3880
+ { path: "src/app/globals.css", content: '@import "tailwindcss";\n' },
3881
+ {
3882
+ path: "src/app/layout.tsx",
3883
+ content: `import type { ReactNode } from "react";
3884
+ import "./globals.css";
3885
+
3886
+ export default function RootLayout({ children }: { children: ReactNode }) {
3887
+ return (
3888
+ <html lang="en">
3889
+ <body>{children}</body>
3890
+ </html>
3891
+ );
3892
+ }
3893
+ `
3894
+ },
3895
+ {
3896
+ path: "src/app/page.tsx",
3897
+ content: `export default function Page() {
3898
+ return <main className="min-h-screen bg-background text-foreground" />;
3899
+ }
3900
+ `
3901
+ },
3902
+ {
3903
+ path: "tsconfig.json",
3904
+ content: `${JSON.stringify({
3905
+ compilerOptions: {
3906
+ target: "ES2017",
3907
+ lib: ["dom", "dom.iterable", "esnext"],
3908
+ allowJs: true,
3909
+ skipLibCheck: true,
3910
+ strict: true,
3911
+ noEmit: true,
3912
+ esModuleInterop: true,
3913
+ module: "esnext",
3914
+ moduleResolution: "bundler",
3915
+ resolveJsonModule: true,
3916
+ isolatedModules: true,
3917
+ jsx: "preserve",
3918
+ incremental: true,
3919
+ paths: { "@/*": ["./src/*"] }
3920
+ },
3921
+ include: ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
3922
+ exclude: ["node_modules"]
3923
+ }, null, 2)}
3924
+ `
3925
+ }
3926
+ ]
3927
+ },
3928
+ vite: {
3929
+ id: "vite",
3930
+ label: "Vite",
3931
+ cssPath: "src/index.css",
3932
+ files: [
3933
+ {
3934
+ path: "package.json",
3935
+ content: packageJson("wensity-vite-app", {
3936
+ scripts: {
3937
+ dev: "vite",
3938
+ build: "vite build",
3939
+ preview: "vite preview"
3940
+ },
3941
+ dependencies: {
3942
+ "vite": "^7.3.0",
3943
+ "react": "^19.2.4",
3944
+ "react-dom": "^19.2.4"
3945
+ },
3946
+ devDependencies: {
3947
+ "@vitejs/plugin-react": "^5.1.2",
3948
+ "@types/react": "^19.0.0",
3949
+ "@types/react-dom": "^19.0.0",
3950
+ "tailwindcss": "^4.0.0",
3951
+ "typescript": "^5.7.2"
3952
+ }
3953
+ })
3954
+ },
3955
+ { path: "index.html", content: '<div id="root"></div><script type="module" src="/src/main.tsx"></script>\n' },
3956
+ { path: "src/index.css", content: '@import "tailwindcss";\n' },
3957
+ {
3958
+ path: "src/main.tsx",
3959
+ content: `import { createRoot } from "react-dom/client";
3960
+ import "./index.css";
3961
+
3962
+ createRoot(document.getElementById("root")!).render(
3963
+ <main className="min-h-screen bg-background text-foreground" />,
3964
+ );
3965
+ `
3966
+ },
3967
+ {
3968
+ path: "vite.config.ts",
3969
+ content: `import { defineConfig } from "vite";
3970
+ import react from "@vitejs/plugin-react";
3971
+
3972
+ export default defineConfig({
3973
+ plugins: [react()],
3974
+ resolve: {
3975
+ alias: {
3976
+ "@": new URL("./src", import.meta.url).pathname,
3977
+ },
3978
+ },
3979
+ });
3980
+ `
3981
+ }
3982
+ ]
3983
+ },
3984
+ astro: {
3985
+ id: "astro",
3986
+ label: "Astro",
3987
+ cssPath: "src/styles/global.css",
3988
+ files: [
3989
+ {
3990
+ path: "package.json",
3991
+ content: packageJson("wensity-astro-app", {
3992
+ scripts: {
3993
+ dev: "astro dev",
3994
+ build: "astro build",
3995
+ preview: "astro preview"
3996
+ },
3997
+ dependencies: {
3998
+ "@astrojs/react": "^4.4.0",
3999
+ "astro": "^5.16.0",
4000
+ "react": "^19.2.4",
4001
+ "react-dom": "^19.2.4"
4002
+ },
4003
+ devDependencies: {
4004
+ "tailwindcss": "^4.0.0",
4005
+ "typescript": "^5.7.2"
4006
+ }
4007
+ })
4008
+ },
4009
+ { path: "src/styles/global.css", content: '@import "tailwindcss";\n' },
4010
+ {
4011
+ path: "astro.config.mjs",
4012
+ content: `import { defineConfig } from "astro/config";
4013
+ import react from "@astrojs/react";
4014
+
4015
+ export default defineConfig({
4016
+ integrations: [react()],
4017
+ });
4018
+ `
4019
+ },
4020
+ {
4021
+ path: "src/pages/index.astro",
4022
+ content: `---
4023
+ import "../styles/global.css";
4024
+ ---
4025
+
4026
+ <main class="min-h-screen bg-background text-foreground"></main>
4027
+ `
4028
+ }
4029
+ ]
4030
+ },
4031
+ "react-router": {
4032
+ id: "react-router",
4033
+ label: "React Router",
4034
+ cssPath: "src/app/globals.css",
4035
+ files: [
4036
+ {
4037
+ path: "package.json",
4038
+ content: packageJson("wensity-react-router-app", {
4039
+ scripts: {
4040
+ dev: "vite",
4041
+ build: "vite build",
4042
+ preview: "vite preview"
4043
+ },
4044
+ dependencies: {
4045
+ "vite": "^7.3.0",
4046
+ "react": "^19.2.4",
4047
+ "react-dom": "^19.2.4",
4048
+ "react-router": "^7.12.0"
4049
+ },
4050
+ devDependencies: {
4051
+ "@vitejs/plugin-react": "^5.1.2",
4052
+ "@types/react": "^19.0.0",
4053
+ "@types/react-dom": "^19.0.0",
4054
+ "tailwindcss": "^4.0.0",
4055
+ "typescript": "^5.7.2"
4056
+ }
4057
+ })
4058
+ },
4059
+ { path: "index.html", content: '<div id="root"></div><script type="module" src="/src/main.tsx"></script>\n' },
4060
+ { path: "src/app/globals.css", content: '@import "tailwindcss";\n' },
4061
+ {
4062
+ path: "src/main.tsx",
4063
+ content: `import { createRoot } from "react-dom/client";
4064
+ import { BrowserRouter, Route, Routes } from "react-router";
4065
+ import "./app/globals.css";
4066
+
4067
+ createRoot(document.getElementById("root")!).render(
4068
+ <BrowserRouter>
4069
+ <Routes>
4070
+ <Route path="/" element={<main className="min-h-screen bg-background text-foreground" />} />
4071
+ </Routes>
4072
+ </BrowserRouter>,
4073
+ );
4074
+ `
4075
+ },
4076
+ {
4077
+ path: "vite.config.ts",
4078
+ content: `import { defineConfig } from "vite";
4079
+ import react from "@vitejs/plugin-react";
4080
+
4081
+ export default defineConfig({
4082
+ plugins: [react()],
4083
+ resolve: {
4084
+ alias: {
4085
+ "@": new URL("./src", import.meta.url).pathname,
4086
+ },
4087
+ },
4088
+ });
4089
+ `
4090
+ }
4091
+ ]
4092
+ }
4093
+ };
4094
+ function formatTemplateFiles(files) {
4095
+ return files.map(toPosixPath);
4096
+ }
4097
+
2780
4098
  // src/style-css.ts
2781
4099
  var ROOT_SELECTOR = ":root";
2782
4100
  var DARK_SELECTOR = ".dark";
@@ -2862,8 +4180,8 @@ function escapeRegExp(value) {
2862
4180
  }
2863
4181
 
2864
4182
  // src/component-install.ts
2865
- import { mkdir as mkdir4, readFile as readFile4, writeFile as writeFile4 } from "fs/promises";
2866
- import { dirname as dirname5, join as join5, relative as relative3 } from "path";
4183
+ import { mkdir as mkdir5, readFile as readFile5, writeFile as writeFile5 } from "fs/promises";
4184
+ import { dirname as dirname6, join as join6, relative as relative3 } from "path";
2867
4185
 
2868
4186
  // src/templates.ts
2869
4187
  function cnUtilityTemplate() {
@@ -3071,7 +4389,7 @@ async function prepareInstall(projectRoot, config, payload, options = {}) {
3071
4389
  }
3072
4390
  }
3073
4391
  if (!hasGraphHelperFiles && helperNeeds.primitives) {
3074
- const primitivesPath = toPosixPath(join5(config.components, "primitives.tsx"));
4392
+ const primitivesPath = toPosixPath(join6(config.components, "primitives.tsx"));
3075
4393
  if (!await fileExists(resolveInside(projectRoot, primitivesPath))) {
3076
4394
  const primitives = primitivesTemplate(config.aliases.utils);
3077
4395
  files.push({
@@ -3112,7 +4430,7 @@ async function planPrimitiveCssFile(projectRoot, config, payload) {
3112
4430
  const cssRelative = config.tailwind?.css;
3113
4431
  if (!cssRelative) return void 0;
3114
4432
  const absolutePath = resolveInside(projectRoot, cssRelative);
3115
- const existing = await fileExists(absolutePath) ? await readFile4(absolutePath, "utf8") : "";
4433
+ const existing = await fileExists(absolutePath) ? await readFile5(absolutePath, "utf8") : "";
3116
4434
  const withTokens = upsertPrimitiveCssBlock(existing, primitiveCss);
3117
4435
  const fonts = curatedFontsFromStyle(style);
3118
4436
  const fontPlan = planFontInstall(fonts.headingFont, fonts.bodyFont);
@@ -3161,8 +4479,8 @@ ${conflicts.map((path) => ` - ${path}`).join("\n")}`);
3161
4479
  }
3162
4480
  async function writePlannedFiles(files) {
3163
4481
  for (const file of files) {
3164
- await mkdir4(dirname5(file.absolutePath), { recursive: true });
3165
- await writeFile4(file.absolutePath, file.content, "utf8");
4482
+ await mkdir5(dirname6(file.absolutePath), { recursive: true });
4483
+ await writeFile5(file.absolutePath, file.content, "utf8");
3166
4484
  }
3167
4485
  }
3168
4486
  function summarizeFiles(projectRoot, files) {
@@ -3187,13 +4505,13 @@ function getPayloadFileKind(payloadFile) {
3187
4505
  }
3188
4506
  function getPayloadOutputPath(payloadFile, fallbackFileName, config) {
3189
4507
  if (isUtilsHelperFile(payloadFile)) return withSourceExtension(config.utils, "ts");
3190
- if (isOverlayHelperFile(payloadFile)) return toPosixPath(join5(config.components, "overlay.tsx"));
3191
- if (isPrimitivesHelperFile(payloadFile)) return toPosixPath(join5(config.components, "primitives.tsx"));
4508
+ if (isOverlayHelperFile(payloadFile)) return toPosixPath(join6(config.components, "overlay.tsx"));
4509
+ if (isPrimitivesHelperFile(payloadFile)) return toPosixPath(join6(config.components, "primitives.tsx"));
3192
4510
  if (payloadFile.kind === "helper") {
3193
4511
  throw new CliError(`Unsupported registry helper file path: ${payloadFile.path}`);
3194
4512
  }
3195
4513
  const safePayloadPath = sanitizePayloadPath(payloadFile.path, fallbackFileName);
3196
- return toPosixPath(join5(config.components, safePayloadPath));
4514
+ return toPosixPath(join6(config.components, safePayloadPath));
3197
4515
  }
3198
4516
  function isGraphHelperFile(payloadFile) {
3199
4517
  return isUtilsHelperFile(payloadFile) || isOverlayHelperFile(payloadFile) || isPrimitivesHelperFile(payloadFile);
@@ -3290,7 +4608,7 @@ function normalizePackageName(specifier) {
3290
4608
  function withSourceExtension(path, extension) {
3291
4609
  const directory = parentDirectory(path);
3292
4610
  const fileName = extensionlessPath(path.split("/").at(-1) ?? path);
3293
- return toPosixPath(join5(directory === "." ? "" : directory, `${fileName}.${extension}`));
4611
+ return toPosixPath(join6(directory === "." ? "" : directory, `${fileName}.${extension}`));
3294
4612
  }
3295
4613
 
3296
4614
  // src/commands.ts
@@ -3309,6 +4627,12 @@ async function run(context) {
3309
4627
  case "init":
3310
4628
  await initCommand(context, parsed);
3311
4629
  return;
4630
+ case "apply":
4631
+ await applyCommand(context, parsed);
4632
+ return;
4633
+ case "preset":
4634
+ await presetCommand(context, parsed, positionals);
4635
+ return;
3312
4636
  case "login":
3313
4637
  await loginCommand(context, parsed);
3314
4638
  return;
@@ -3336,6 +4660,35 @@ async function run(context) {
3336
4660
  ${HELP_TEXT.trimEnd()}`);
3337
4661
  }
3338
4662
  }
4663
+ async function presetCommand(_context, parsed, positionals) {
4664
+ const [subcommand, positionalCode, ...extra] = positionals;
4665
+ if (subcommand !== "inspect") {
4666
+ throw new CliError(
4667
+ `Unknown preset command: ${subcommand ?? ""}
4668
+
4669
+ Usage: wensity preset inspect <code> [--json]`
4670
+ );
4671
+ }
4672
+ if (extra.length > 0) {
4673
+ throw new CliError("Too many arguments.\n\nUsage: wensity preset inspect <code> [--json]");
4674
+ }
4675
+ const optionCode = getStringOption(parsed, "preset");
4676
+ const presetFile = getStringOption(parsed, "preset-file");
4677
+ if (positionalCode && optionCode) {
4678
+ throw new CliError("Use either a positional preset code or --preset <code>, not both.");
4679
+ }
4680
+ const { style, presetCode } = await resolvePresetStyle({
4681
+ presetCode: optionCode ?? positionalCode,
4682
+ presetFile
4683
+ });
4684
+ const summary = createPresetInspectSummary({ presetCode, style });
4685
+ if (hasFlag(parsed, "json")) {
4686
+ printJson(summary);
4687
+ return;
4688
+ }
4689
+ printTitle("Preset inspection");
4690
+ print(formatPresetInspectSummary(summary));
4691
+ }
3339
4692
  async function initCommand(context, parsed) {
3340
4693
  const dryRun = hasFlag(parsed, "dry-run");
3341
4694
  const force = hasFlag(parsed, "force");
@@ -3343,6 +4696,8 @@ async function initCommand(context, parsed) {
3343
4696
  const apiUrl = getStringOption(parsed, "api-url") ?? context.env.WENSITY_API_URL ?? DEFAULT_API_ORIGIN;
3344
4697
  const presetCode = getStringOption(parsed, "preset");
3345
4698
  const presetFile = getStringOption(parsed, "preset-file");
4699
+ const templateId = parseProjectTemplate(getStringOption(parsed, "template"));
4700
+ const template = templateId ? getProjectTemplateDefinition(templateId) : void 0;
3346
4701
  const hasPreset = Boolean(presetCode || presetFile);
3347
4702
  const dependencies = ["clsx", "tailwind-merge"];
3348
4703
  if (hasPreset) {
@@ -3353,18 +4708,21 @@ async function initCommand(context, parsed) {
3353
4708
  apiUrl,
3354
4709
  presetCode,
3355
4710
  presetFile,
3356
- dependencies
4711
+ dependencies,
4712
+ template
3357
4713
  });
3358
4714
  return;
3359
4715
  }
3360
- const config = createDefaultConfig(context.cwd, apiUrl);
3361
- const configPath = join6(context.cwd, CONFIG_FILE_NAME);
4716
+ const templateFiles = template ? await scaffoldProjectTemplate(context.cwd, template, { dryRun, force }) : [];
4717
+ const config = template ? applyTemplateToConfig(createDefaultConfig(context.cwd, apiUrl), template) : createDefaultConfig(context.cwd, apiUrl);
4718
+ const configPath = join7(context.cwd, CONFIG_FILE_NAME);
3362
4719
  const utilityPath = resolveConfiguredFilePath(context.cwd, config.utils);
3363
4720
  const utilityRelativePath = toPosixPath(config.utils.endsWith(".ts") ? config.utils : `${config.utils}.ts`);
3364
4721
  if (dryRun) {
3365
4722
  printTitle("Project initialization preview");
3366
4723
  print(action("Would write:"));
3367
- print(formatList([CONFIG_FILE_NAME, utilityRelativePath]));
4724
+ print(formatList([...formatTemplateFiles(templateFiles), CONFIG_FILE_NAME, utilityRelativePath]));
4725
+ if (template) print(action(`Template: ${template.label}`));
3368
4726
  if (install) print(action(`Would install: ${dependencies.join(" ")}`));
3369
4727
  return;
3370
4728
  }
@@ -3392,13 +4750,17 @@ async function initWithPreset(context, options) {
3392
4750
  presetCode: options.presetCode,
3393
4751
  presetFile: options.presetFile
3394
4752
  });
3395
- const fonts = curatedFontsFromStyle(style);
3396
- const fontPlan = planFontInstall(fonts.headingFont, fonts.bodyFont);
3397
- const installDependencies = [...options.dependencies, ...fontPlan.packages];
3398
- const configPath = join6(context.cwd, CONFIG_FILE_NAME);
4753
+ const configPath = join7(context.cwd, CONFIG_FILE_NAME);
3399
4754
  const configExists = await fileExists(configPath);
4755
+ const templateFiles = options.template ? await scaffoldProjectTemplate(context.cwd, options.template, {
4756
+ dryRun: options.dryRun,
4757
+ force: options.force
4758
+ }) : [];
3400
4759
  let config;
3401
4760
  if (configExists && !options.force) {
4761
+ if (options.template) {
4762
+ throw new CliError(`${CONFIG_FILE_NAME} already exists. Omit --template for existing projects or use --force.`);
4763
+ }
3402
4764
  const loaded = await loadConfig(context.cwd, { apiUrl: options.apiUrl });
3403
4765
  config = {
3404
4766
  ...loaded.config,
@@ -3406,63 +4768,36 @@ async function initWithPreset(context, options) {
3406
4768
  };
3407
4769
  } else {
3408
4770
  config = {
3409
- ...createDefaultConfig(context.cwd, options.apiUrl),
4771
+ ...options.template ? applyTemplateToConfig(createDefaultConfig(context.cwd, options.apiUrl), options.template) : createDefaultConfig(context.cwd, options.apiUrl),
3410
4772
  style
3411
4773
  };
3412
4774
  }
3413
4775
  const utilityPath = resolveConfiguredFilePath(context.cwd, config.utils);
3414
4776
  const utilityRelativePath = toPosixPath(config.utils.endsWith(".ts") ? config.utils : `${config.utils}.ts`);
3415
- const registry = config.registry;
3416
- let primitiveCss;
3417
- try {
3418
- const payload = await withSpinner(
3419
- `Fetching primitive tokens (${INIT_PRIMITIVE_CSS_SLUG})`,
3420
- () => fetchComponentPayload({ slug: INIT_PRIMITIVE_CSS_SLUG, registry }),
3421
- "Fetched primitive tokens"
3422
- );
3423
- if (typeof payload.primitiveCss !== "string" || !payload.primitiveCss.trim()) {
3424
- throw new CliError(
3425
- `Pinned slug "${INIT_PRIMITIVE_CSS_SLUG}" did not return primitiveCss. Cannot apply preset styles.`
3426
- );
3427
- }
3428
- primitiveCss = applyStyleToPrimitiveCss(payload.primitiveCss, style).trim();
3429
- } catch (error) {
3430
- if (error instanceof CliError) throw error;
3431
- const message = error instanceof Error ? error.message : String(error);
3432
- throw new CliError(`Failed to fetch primitive CSS for preset init: ${message}`);
3433
- }
3434
- const cssRelative = config.tailwind?.css;
3435
- if (!cssRelative) {
3436
- throw new CliError("No Tailwind CSS path configured. Set tailwind.css in wensity.json.");
3437
- }
3438
- const cssAbsolute = resolveInside(context.cwd, cssRelative);
3439
- const existingCss = await fileExists(cssAbsolute) ? await readFile5(cssAbsolute, "utf8") : "";
3440
- const withTokens = upsertPrimitiveCssBlock(existingCss, primitiveCss);
3441
- const nextCss = upsertFontImportBlock(withTokens, fontPlan.block);
3442
- const cssChanged = nextCss !== existingCss;
3443
- const installedPackages = readInstalledPackageNames(context.cwd);
3444
- const unusedFontWarning = installedPackages ? warnUnusedFontPackageMessage(fonts.headingFont, fonts.bodyFont, installedPackages) : void 0;
4777
+ const prepared = await preparePresetStyleApplication(context.cwd, config, style, "preset init");
4778
+ const installDependencies = [...options.dependencies, ...prepared.fontPlan.packages];
3445
4779
  if (options.dryRun) {
3446
4780
  printTitle("Project initialization preview (preset)");
3447
4781
  print(action("Would write:"));
3448
- const planned = [CONFIG_FILE_NAME, utilityRelativePath];
3449
- if (cssChanged) planned.push(toPosixPath(cssRelative));
4782
+ const planned = [...formatTemplateFiles(templateFiles), CONFIG_FILE_NAME, utilityRelativePath];
4783
+ if (prepared.cssChanged) planned.push(toPosixPath(prepared.cssRelative));
3450
4784
  print(formatList(planned));
4785
+ if (options.template) print(action(`Template: ${options.template.label}`));
3451
4786
  print(action(`Preset: ${presetCode.slice(0, 24)}\u2026`));
3452
4787
  print(action(`Style: icon=${style.iconLibrary} radius=${style.radius}`));
3453
- if (fontPlan.packages.length > 0) {
3454
- print(action(`Fonts: ${fontPlan.packages.join(" ")}`));
4788
+ if (prepared.fontPlan.packages.length > 0) {
4789
+ print(action(`Fonts: ${prepared.fontPlan.packages.join(" ")}`));
3455
4790
  }
3456
4791
  if (options.install) print(action(`Would install: ${installDependencies.join(" ")}`));
3457
- if (unusedFontWarning) warn(unusedFontWarning);
4792
+ if (prepared.unusedFontWarning) warn(prepared.unusedFontWarning);
3458
4793
  return;
3459
4794
  }
3460
4795
  const shouldWriteUtility = options.force || !await fileExists(utilityPath);
3461
4796
  await commitPresetInitWrites({
3462
- cssAbsolute,
3463
- existingCss,
3464
- nextCss,
3465
- cssChanged,
4797
+ cssAbsolute: prepared.cssAbsolute,
4798
+ existingCss: prepared.existingCss,
4799
+ nextCss: prepared.nextCss,
4800
+ cssChanged: prepared.cssChanged,
3466
4801
  writeUtility: shouldWriteUtility,
3467
4802
  utilityAbsolutePath: utilityPath,
3468
4803
  utilityContent: cnUtilityTemplate(),
@@ -3479,26 +4814,150 @@ async function initWithPreset(context, options) {
3479
4814
  }
3480
4815
  printTitle("Project initialized");
3481
4816
  const created = [
4817
+ ...options.template ? [success(`template: ${options.template.label}`)] : [],
3482
4818
  success(CONFIG_FILE_NAME),
3483
4819
  success(utilityRelativePath),
3484
4820
  success(`registry: ${options.apiUrl}`),
3485
4821
  success(`preset applied (${style.iconLibrary}, radius=${style.radius})`)
3486
4822
  ];
3487
- if (cssChanged) {
3488
- created.push(success(`CSS tokens: ${toPosixPath(cssRelative)}`));
4823
+ if (prepared.cssChanged) {
4824
+ created.push(success(`CSS tokens: ${toPosixPath(prepared.cssRelative)}`));
3489
4825
  } else {
3490
- created.push(action(`CSS tokens unchanged: ${toPosixPath(cssRelative)}`));
4826
+ created.push(action(`CSS tokens unchanged: ${toPosixPath(prepared.cssRelative)}`));
3491
4827
  }
3492
- if (fontPlan.packages.length > 0) {
3493
- created.push(success(`fonts: ${fontPlan.packages.join(", ")}`));
4828
+ if (prepared.fontPlan.packages.length > 0) {
4829
+ created.push(success(`fonts: ${prepared.fontPlan.packages.join(", ")}`));
3494
4830
  }
3495
4831
  print(panel("Created", created));
3496
- if (unusedFontWarning) warn(unusedFontWarning);
4832
+ if (prepared.unusedFontWarning) warn(prepared.unusedFontWarning);
3497
4833
  print(nextSteps([
3498
4834
  "Browse available components: npx wensity list",
3499
4835
  "Install a primitive: npx wensity add button"
3500
4836
  ]));
3501
4837
  }
4838
+ async function applyCommand(context, parsed) {
4839
+ const dryRun = hasFlag(parsed, "dry-run");
4840
+ const install = !hasFlag(parsed, "no-install");
4841
+ const apiUrl = getStringOption(parsed, "api-url") ?? context.env.WENSITY_API_URL;
4842
+ const registryOverride = getStringOption(parsed, "registry");
4843
+ const presetCodeInput = getStringOption(parsed, "preset");
4844
+ const presetFile = getStringOption(parsed, "preset-file");
4845
+ if (!presetCodeInput && !presetFile) {
4846
+ throw new CliError("Missing preset. Pass --preset <code> or --preset-file <path>.");
4847
+ }
4848
+ const loaded = await loadConfig(context.cwd, { registry: registryOverride, apiUrl });
4849
+ if (!loaded.exists) {
4850
+ throw new CliError(`${CONFIG_FILE_NAME} not found. Run "wensity init" first, then "wensity apply --preset <code>".`);
4851
+ }
4852
+ const { style, presetCode } = await resolvePresetStyle({
4853
+ presetCode: presetCodeInput,
4854
+ presetFile
4855
+ });
4856
+ const prepared = await preparePresetStyleApplication(
4857
+ loaded.projectRoot,
4858
+ {
4859
+ ...loaded.config,
4860
+ style
4861
+ },
4862
+ style,
4863
+ "preset apply"
4864
+ );
4865
+ const installDependencies = prepared.fontPlan.packages;
4866
+ if (dryRun) {
4867
+ printTitle("Preset apply preview");
4868
+ print(action("Would write:"));
4869
+ const planned = [CONFIG_FILE_NAME];
4870
+ if (prepared.cssChanged) planned.push(toPosixPath(prepared.cssRelative));
4871
+ print(formatList(planned));
4872
+ print(action(`Preset: ${presetCode.slice(0, 24)}\u2026`));
4873
+ print(action(`Style: icon=${style.iconLibrary} radius=${style.radius}`));
4874
+ if (prepared.fontPlan.packages.length > 0) {
4875
+ print(action(`Fonts: ${prepared.fontPlan.packages.join(" ")}`));
4876
+ }
4877
+ if (install && installDependencies.length > 0) {
4878
+ print(action(`Would install: ${installDependencies.join(" ")}`));
4879
+ }
4880
+ if (prepared.unusedFontWarning) warn(prepared.unusedFontWarning);
4881
+ return;
4882
+ }
4883
+ await commitPresetInitWrites({
4884
+ cssAbsolute: prepared.cssAbsolute,
4885
+ existingCss: prepared.existingCss,
4886
+ nextCss: prepared.nextCss,
4887
+ cssChanged: prepared.cssChanged,
4888
+ writeUtility: false,
4889
+ utilityAbsolutePath: resolveConfiguredFilePath(loaded.projectRoot, loaded.config.utils),
4890
+ utilityContent: cnUtilityTemplate(),
4891
+ commitConfig: async () => {
4892
+ await mergeStyleIntoConfigFile(loaded.configPath, style);
4893
+ }
4894
+ });
4895
+ if (install && installDependencies.length > 0) {
4896
+ runInstall(loaded.projectRoot, missingDependencies(loaded.projectRoot, installDependencies));
4897
+ }
4898
+ printTitle("Preset applied");
4899
+ const applied = [
4900
+ success(CONFIG_FILE_NAME),
4901
+ success(`preset applied (${style.iconLibrary}, radius=${style.radius})`)
4902
+ ];
4903
+ if (prepared.cssChanged) {
4904
+ applied.push(success(`CSS tokens: ${toPosixPath(prepared.cssRelative)}`));
4905
+ } else {
4906
+ applied.push(action(`CSS tokens unchanged: ${toPosixPath(prepared.cssRelative)}`));
4907
+ }
4908
+ if (prepared.fontPlan.packages.length > 0) {
4909
+ applied.push(success(`fonts: ${prepared.fontPlan.packages.join(", ")}`));
4910
+ }
4911
+ print(panel("Updated", applied));
4912
+ if (prepared.unusedFontWarning) warn(prepared.unusedFontWarning);
4913
+ print(nextSteps([
4914
+ "Review your changed CSS tokens",
4915
+ "Run your app to verify the updated primitive theme"
4916
+ ]));
4917
+ }
4918
+ async function preparePresetStyleApplication(projectRoot, config, style, operation) {
4919
+ const fonts = curatedFontsFromStyle(style);
4920
+ const fontPlan = planFontInstall(fonts.headingFont, fonts.bodyFont);
4921
+ const registry = config.registry;
4922
+ let primitiveCss;
4923
+ try {
4924
+ const payload = await withSpinner(
4925
+ `Fetching primitive tokens (${INIT_PRIMITIVE_CSS_SLUG})`,
4926
+ () => fetchComponentPayload({ slug: INIT_PRIMITIVE_CSS_SLUG, registry }),
4927
+ "Fetched primitive tokens"
4928
+ );
4929
+ if (typeof payload.primitiveCss !== "string" || !payload.primitiveCss.trim()) {
4930
+ throw new CliError(
4931
+ `Pinned slug "${INIT_PRIMITIVE_CSS_SLUG}" did not return primitiveCss. Cannot apply preset styles.`
4932
+ );
4933
+ }
4934
+ primitiveCss = applyStyleToPrimitiveCss(payload.primitiveCss, style).trim();
4935
+ } catch (error) {
4936
+ if (error instanceof CliError) throw error;
4937
+ const message = error instanceof Error ? error.message : String(error);
4938
+ throw new CliError(`Failed to fetch primitive CSS for ${operation}: ${message}`);
4939
+ }
4940
+ const cssRelative = config.tailwind?.css;
4941
+ if (!cssRelative) {
4942
+ throw new CliError("No Tailwind CSS path configured. Set tailwind.css in wensity.json.");
4943
+ }
4944
+ const cssAbsolute = resolveInside(projectRoot, cssRelative);
4945
+ const existingCss = await fileExists(cssAbsolute) ? await readFile6(cssAbsolute, "utf8") : "";
4946
+ const withTokens = upsertPrimitiveCssBlock(existingCss, primitiveCss);
4947
+ const nextCss = upsertFontImportBlock(withTokens, fontPlan.block);
4948
+ const cssChanged = nextCss !== existingCss;
4949
+ const installedPackages = readInstalledPackageNames(projectRoot);
4950
+ const unusedFontWarning = installedPackages ? warnUnusedFontPackageMessage(fonts.headingFont, fonts.bodyFont, installedPackages) : void 0;
4951
+ return {
4952
+ fontPlan,
4953
+ cssRelative,
4954
+ cssAbsolute,
4955
+ existingCss,
4956
+ nextCss,
4957
+ cssChanged,
4958
+ unusedFontWarning
4959
+ };
4960
+ }
3502
4961
  async function loginCommand(context, parsed) {
3503
4962
  const apiUrl = getStringOption(parsed, "api-url") ?? context.env.WENSITY_API_URL ?? DEFAULT_API_ORIGIN;
3504
4963
  const apiBaseUrl = normalizeUrlOrigin(apiUrl);
@@ -3788,8 +5247,8 @@ function formatAccountPanel(whoami, savedPath) {
3788
5247
  return panel(isPro ? "Wensity Pro" : "Wensity Free", lines);
3789
5248
  }
3790
5249
  async function writeTextFile(path, content) {
3791
- await mkdir5(dirname6(path), { recursive: true });
3792
- await writeFile5(path, content, "utf8");
5250
+ await mkdir6(dirname7(path), { recursive: true });
5251
+ await writeFile6(path, content, "utf8");
3793
5252
  }
3794
5253
 
3795
5254
  // src/index.ts