wensity 0.1.6 → 0.1.8

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 +3 -3
  2. package/dist/index.js +1286 -264
  3. package/package.json +10 -10
  4. package/LICENSE +0 -21
package/dist/index.js CHANGED
@@ -4,10 +4,160 @@
4
4
  import { mkdir as mkdir5, readFile as readFile5, writeFile as writeFile5 } from "fs/promises";
5
5
  import { dirname as dirname6, join as join6 } from "path";
6
6
 
7
+ // src/constants.ts
8
+ var CLI_NAME = "wensity";
9
+ var CLI_VERSION = "0.1.8";
10
+ var CONFIG_FILE_NAME = "wensity.json";
11
+ var DEFAULT_API_ORIGIN = "https://ui.wensity.com";
12
+ var DEFAULT_REGISTRY_PATH = "/api/registry";
13
+ var DEFAULT_COMPONENTS_DIR = "src/components/wensity";
14
+ var DEFAULT_UTILS_PATH = "src/lib/utils";
15
+ var DEFAULT_COMPONENTS_ALIAS = "@/components/wensity";
16
+ var DEFAULT_UTILS_ALIAS = "@/lib/utils";
17
+ var INIT_PRIMITIVE_CSS_SLUG = "button";
18
+ var HELP_TEXT = `Wensity CLI ${CLI_VERSION}
19
+
20
+ Usage:
21
+ wensity init [--preset <code>] [--preset-file <path>] [options]
22
+ wensity login [options]
23
+ wensity logout
24
+ wensity whoami [--json]
25
+ wensity list [--type component|block] [options]
26
+ wensity add <slug> [--type component|block] [options]
27
+ wensity update <slug> [--type component|block] [options]
28
+ wensity version
29
+
30
+ Commands:
31
+ init Create wensity.json and local helper files (optional --preset applies style + CSS)
32
+ login Save a Wensity API token for premium component access
33
+ logout Remove saved CLI credentials
34
+ whoami Verify the saved token and print the authenticated account
35
+ list List registry components or blocks
36
+ add Download a component or block source file into this project
37
+ update Re-download a component or block and overwrite the local copy
38
+ version Print the CLI version
39
+
40
+ Options:
41
+ --api-url <url> Override the Wensity app origin, defaults to ${DEFAULT_API_ORIGIN}
42
+ --registry <url> Override the registry endpoint
43
+ --type <type> Select component or block, defaults to component
44
+ --token <token> Provide a token non-interactively for login
45
+ --preset <code> Apply a shareable Wensity style preset (wsty1.\u2026); merges style when config exists
46
+ --preset-file <path> Read a preset code or JSON payload from a file
47
+ --force Overwrite generated files when adding or initializing
48
+ --no-install Write files without running the package manager
49
+ --dry-run Print planned writes and installs without changing files
50
+ --json Print machine-readable output where supported
51
+ --help Show help
52
+ --version Print the CLI version
53
+
54
+ Preset notes:
55
+ Generate codes at ${DEFAULT_API_ORIGIN}/docs/primitives/setup
56
+ Plain init refuses an existing wensity.json unless --force
57
+ init --preset merges style + primitive CSS; does not rewrite installed source
58
+ add/update read style.iconLibrary for UI primitives only (Tabler|Lucide)
59
+ Invalid/expired/unsupported codes exit non-zero with an actionable message
60
+ `;
61
+
62
+ // src/errors.ts
63
+ var CliError = class extends Error {
64
+ exitCode;
65
+ constructor(message, exitCode = 1) {
66
+ super(message);
67
+ this.name = "CliError";
68
+ this.exitCode = exitCode;
69
+ }
70
+ };
71
+ function isNodeError(error) {
72
+ return error instanceof Error && "code" in error;
73
+ }
74
+
75
+ // src/args.ts
76
+ function parseArgs(argv) {
77
+ const positionals = [];
78
+ const options = /* @__PURE__ */ new Map();
79
+ for (let index = 0; index < argv.length; index += 1) {
80
+ const token = argv[index];
81
+ if (!token.startsWith("--")) {
82
+ positionals.push(token);
83
+ continue;
84
+ }
85
+ if (token === "--") {
86
+ positionals.push(...argv.slice(index + 1));
87
+ break;
88
+ }
89
+ const inlineValueIndex = token.indexOf("=");
90
+ const rawName = inlineValueIndex > -1 ? token.slice(2, inlineValueIndex) : token.slice(2);
91
+ const name = normalizeOptionName(rawName);
92
+ if (!name) {
93
+ throw new CliError(`Invalid option: ${token}`);
94
+ }
95
+ if (inlineValueIndex > -1) {
96
+ options.set(name, token.slice(inlineValueIndex + 1));
97
+ continue;
98
+ }
99
+ const nextToken = argv[index + 1];
100
+ if (nextToken && !nextToken.startsWith("--") && expectsValue(name)) {
101
+ options.set(name, nextToken);
102
+ index += 1;
103
+ continue;
104
+ }
105
+ options.set(name, true);
106
+ }
107
+ return { positionals, options };
108
+ }
109
+ function getStringOption(args, name) {
110
+ const value = args.options.get(name);
111
+ if (value === void 0) return void 0;
112
+ if (typeof value === "boolean") {
113
+ throw new CliError(`--${name} expects a value.`);
114
+ }
115
+ return value;
116
+ }
117
+ function hasFlag(args, name) {
118
+ return args.options.get(name) === true;
119
+ }
120
+ function expectsValue(name) {
121
+ return ["api-url", "registry", "token", "type", "preset", "preset-file"].includes(name);
122
+ }
123
+ function normalizeOptionName(name) {
124
+ if (name === "dry") return "dry-run";
125
+ return name;
126
+ }
127
+
128
+ // src/config.ts
129
+ import { existsSync } from "fs";
130
+ import { access, mkdir, readFile, writeFile } from "fs/promises";
131
+ import { dirname as dirname2, join as join2, relative as relative2 } from "path";
132
+
7
133
  // ../wensity-preset/src/constants.ts
8
134
  var ICON_LIBRARIES = ["tabler", "lucide"];
9
135
  var RADIUS_PRESETS = ["none", "small", "medium", "large"];
10
136
  var BUILTIN_FONT_IDS = ["site", "system-sans", "system-mono"];
137
+ var BASE_COLOR_IDS = [
138
+ "inherit",
139
+ "neutral",
140
+ "zinc",
141
+ "slate",
142
+ "stone",
143
+ "taupe"
144
+ ];
145
+ var THEME_COLOR_IDS = [
146
+ "ink",
147
+ "red",
148
+ "blue",
149
+ "violet",
150
+ "green",
151
+ "orange"
152
+ ];
153
+ var CHART_PALETTE_IDS = [
154
+ "theme",
155
+ "warm",
156
+ "cool",
157
+ "mixed"
158
+ ];
159
+ var HEADING_FONT_IDS = ["system", "playfair"];
160
+ var BODY_FONT_IDS = ["system", "inter"];
11
161
  var RADIUS_PRESET_CSS = {
12
162
  none: "0px",
13
163
  small: "0.5rem",
@@ -27,12 +177,6 @@ var CONTROL_SOLID_COLOR_KEYS = [
27
177
  "controlSolidActive",
28
178
  "controlSolidForeground"
29
179
  ];
30
- var CONTROL_SOLID_CSS_VARS = {
31
- controlSolid: "--primitive-control-solid",
32
- controlSolidHover: "--primitive-control-solid-hover",
33
- controlSolidActive: "--primitive-control-solid-active",
34
- controlSolidForeground: "--primitive-control-solid-foreground"
35
- };
36
180
  var STYLE_OBJECT_KEYS = [
37
181
  "iconLibrary",
38
182
  "radius",
@@ -40,6 +184,16 @@ var STYLE_OBJECT_KEYS = [
40
184
  "colors",
41
185
  "presetCode"
42
186
  ];
187
+ var STYLE_OBJECT_KEYS_CANONICAL = [
188
+ "baseColor",
189
+ "themeColor",
190
+ "chartPalette",
191
+ "headingFont",
192
+ "bodyFont",
193
+ "iconLibrary",
194
+ "radius",
195
+ "presetCode"
196
+ ];
43
197
  var FONT_ROLE_KEYS = ["sans", "display", "mono"];
44
198
  var COLOR_MODE_KEYS = ["light", "dark"];
45
199
  var HEX_COLOR_PATTERN = /^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/;
@@ -62,6 +216,21 @@ function isFontRoleValue(value) {
62
216
  if (isBuiltinFontId(value)) return true;
63
217
  return value.startsWith(CUSTOM_FONT_PREFIX) && value.length > CUSTOM_FONT_PREFIX.length;
64
218
  }
219
+ function isBaseColorId(value) {
220
+ return BASE_COLOR_IDS.includes(value);
221
+ }
222
+ function isThemeColorId(value) {
223
+ return THEME_COLOR_IDS.includes(value);
224
+ }
225
+ function isChartPaletteId(value) {
226
+ return CHART_PALETTE_IDS.includes(value);
227
+ }
228
+ function isHeadingFontId(value) {
229
+ return HEADING_FONT_IDS.includes(value);
230
+ }
231
+ function isBodyFontId(value) {
232
+ return BODY_FONT_IDS.includes(value);
233
+ }
65
234
 
66
235
  // ../wensity-preset/src/defaults.ts
67
236
  var DEFAULT_STYLE_FONTS = {
@@ -101,6 +270,26 @@ function createDefaultStyleConfig() {
101
270
  }
102
271
  };
103
272
  }
273
+ var DEFAULT_STYLE_CONFIG_CANONICAL = {
274
+ baseColor: "inherit",
275
+ themeColor: "ink",
276
+ chartPalette: "theme",
277
+ headingFont: "system",
278
+ bodyFont: "system",
279
+ iconLibrary: "tabler",
280
+ radius: "medium"
281
+ };
282
+ function createDefaultStyleConfigCanonical() {
283
+ return {
284
+ baseColor: DEFAULT_STYLE_CONFIG_CANONICAL.baseColor,
285
+ themeColor: DEFAULT_STYLE_CONFIG_CANONICAL.themeColor,
286
+ chartPalette: DEFAULT_STYLE_CONFIG_CANONICAL.chartPalette,
287
+ headingFont: DEFAULT_STYLE_CONFIG_CANONICAL.headingFont,
288
+ bodyFont: DEFAULT_STYLE_CONFIG_CANONICAL.bodyFont,
289
+ iconLibrary: DEFAULT_STYLE_CONFIG_CANONICAL.iconLibrary,
290
+ radius: DEFAULT_STYLE_CONFIG_CANONICAL.radius
291
+ };
292
+ }
104
293
 
105
294
  // ../wensity-preset/src/schema.ts
106
295
  var fontRoleSchema = {
@@ -123,66 +312,53 @@ var controlSolidColorsSchema = {
123
312
  ])
124
313
  )
125
314
  };
126
- var WENSITY_CONFIG_SCHEMA = {
127
- $schema: "https://json-schema.org/draft/2020-12/schema",
128
- $id: "https://ui.wensity.com/schema/wensity.json",
129
- title: "Wensity CLI configuration",
315
+ var legacyStyleSchema = {
130
316
  type: "object",
131
317
  additionalProperties: false,
132
318
  properties: {
133
- $schema: { type: "string" },
134
- version: { type: "integer", const: 1 },
135
- registry: { type: "string", format: "uri" },
136
- components: { type: "string", minLength: 1 },
137
- utils: { type: "string", minLength: 1 },
138
- tailwind: {
319
+ iconLibrary: { type: "string", enum: [...ICON_LIBRARIES] },
320
+ radius: { type: "string", enum: [...RADIUS_PRESETS] },
321
+ fonts: {
139
322
  type: "object",
140
323
  additionalProperties: false,
141
324
  properties: {
142
- config: { type: "string" },
143
- css: { type: "string" }
325
+ sans: fontRoleSchema,
326
+ display: fontRoleSchema,
327
+ mono: fontRoleSchema
144
328
  }
145
329
  },
146
- aliases: {
330
+ colors: {
147
331
  type: "object",
148
332
  additionalProperties: false,
149
333
  properties: {
150
- components: { type: "string", minLength: 1 },
151
- utils: { type: "string", minLength: 1 }
334
+ light: controlSolidColorsSchema,
335
+ dark: controlSolidColorsSchema
152
336
  }
153
337
  },
154
- style: {
155
- type: "object",
156
- additionalProperties: false,
157
- properties: {
158
- iconLibrary: { type: "string", enum: [...ICON_LIBRARIES] },
159
- radius: { type: "string", enum: [...RADIUS_PRESETS] },
160
- fonts: {
161
- type: "object",
162
- additionalProperties: false,
163
- properties: {
164
- sans: fontRoleSchema,
165
- display: fontRoleSchema,
166
- mono: fontRoleSchema
167
- }
168
- },
169
- colors: {
170
- type: "object",
171
- additionalProperties: false,
172
- properties: {
173
- light: controlSolidColorsSchema,
174
- dark: controlSolidColorsSchema
175
- }
176
- },
177
- presetCode: {
178
- type: "string",
179
- minLength: 1,
180
- maxLength: PRESET_CODEC_MAX_CODE_LENGTH
181
- }
182
- }
338
+ presetCode: {
339
+ type: "string",
340
+ minLength: 1,
341
+ maxLength: PRESET_CODEC_MAX_CODE_LENGTH
183
342
  }
184
- },
185
- required: ["registry", "components", "utils", "aliases"]
343
+ }
344
+ };
345
+ var canonicalStyleSchema = {
346
+ type: "object",
347
+ additionalProperties: false,
348
+ properties: {
349
+ baseColor: { type: "string", enum: [...BASE_COLOR_IDS] },
350
+ themeColor: { type: "string", enum: [...THEME_COLOR_IDS] },
351
+ chartPalette: { type: "string", enum: [...CHART_PALETTE_IDS] },
352
+ headingFont: { type: "string", enum: [...HEADING_FONT_IDS] },
353
+ bodyFont: { type: "string", enum: [...BODY_FONT_IDS] },
354
+ iconLibrary: { type: "string", enum: [...ICON_LIBRARIES] },
355
+ radius: { type: "string", enum: [...RADIUS_PRESETS] },
356
+ presetCode: {
357
+ type: "string",
358
+ minLength: 1,
359
+ maxLength: PRESET_CODEC_MAX_CODE_LENGTH
360
+ }
361
+ }
186
362
  };
187
363
 
188
364
  // ../wensity-preset/src/fonts.ts
@@ -420,6 +596,91 @@ function validateStyleConfigPartial(value) {
420
596
  }
421
597
  return result;
422
598
  }
599
+ function validateStyleConfigCanonicalPartial(value) {
600
+ if (value === void 0 || value === null) return {};
601
+ assertPlainObject(value, "style");
602
+ assertAllowedKeys(value, STYLE_OBJECT_KEYS_CANONICAL, "style");
603
+ const result = {};
604
+ if ("baseColor" in value) {
605
+ if (typeof value.baseColor !== "string" || !isBaseColorId(value.baseColor)) {
606
+ throw new PresetValidationError(
607
+ "Invalid style.baseColor. Expected inherit | neutral | zinc | slate | stone | taupe.",
608
+ "style.baseColor"
609
+ );
610
+ }
611
+ result.baseColor = value.baseColor;
612
+ }
613
+ if ("themeColor" in value) {
614
+ if (typeof value.themeColor !== "string" || !isThemeColorId(value.themeColor)) {
615
+ throw new PresetValidationError(
616
+ "Invalid style.themeColor. Expected ink | red | blue | violet | green | orange.",
617
+ "style.themeColor"
618
+ );
619
+ }
620
+ result.themeColor = value.themeColor;
621
+ }
622
+ if ("chartPalette" in value) {
623
+ if (typeof value.chartPalette !== "string" || !isChartPaletteId(value.chartPalette)) {
624
+ throw new PresetValidationError(
625
+ "Invalid style.chartPalette. Expected theme | warm | cool | mixed.",
626
+ "style.chartPalette"
627
+ );
628
+ }
629
+ result.chartPalette = value.chartPalette;
630
+ }
631
+ if ("headingFont" in value) {
632
+ if (typeof value.headingFont !== "string" || !isHeadingFontId(value.headingFont)) {
633
+ throw new PresetValidationError(
634
+ "Invalid style.headingFont. Expected system | playfair.",
635
+ "style.headingFont"
636
+ );
637
+ }
638
+ result.headingFont = value.headingFont;
639
+ }
640
+ if ("bodyFont" in value) {
641
+ if (typeof value.bodyFont !== "string" || !isBodyFontId(value.bodyFont)) {
642
+ throw new PresetValidationError(
643
+ "Invalid style.bodyFont. Expected system | inter.",
644
+ "style.bodyFont"
645
+ );
646
+ }
647
+ result.bodyFont = value.bodyFont;
648
+ }
649
+ if ("iconLibrary" in value) {
650
+ if (typeof value.iconLibrary !== "string" || !isIconLibrary(value.iconLibrary)) {
651
+ throw new PresetValidationError(
652
+ "Invalid style.iconLibrary. Expected tabler | lucide.",
653
+ "style.iconLibrary"
654
+ );
655
+ }
656
+ result.iconLibrary = value.iconLibrary;
657
+ }
658
+ if ("radius" in value) {
659
+ if (typeof value.radius !== "string" || !isRadiusPreset(value.radius)) {
660
+ throw new PresetValidationError(
661
+ "Invalid style.radius. Expected none | small | medium | large.",
662
+ "style.radius"
663
+ );
664
+ }
665
+ result.radius = value.radius;
666
+ }
667
+ if ("presetCode" in value) {
668
+ if (typeof value.presetCode !== "string" || value.presetCode.length === 0) {
669
+ throw new PresetValidationError(
670
+ "Invalid style.presetCode. Expected a non-empty string.",
671
+ "style.presetCode"
672
+ );
673
+ }
674
+ if (value.presetCode.length > PRESET_CODEC_MAX_CODE_LENGTH) {
675
+ throw new PresetValidationError(
676
+ `Invalid style.presetCode. Exceeds ${PRESET_CODEC_MAX_CODE_LENGTH} characters.`,
677
+ "style.presetCode"
678
+ );
679
+ }
680
+ result.presetCode = value.presetCode;
681
+ }
682
+ return result;
683
+ }
423
684
 
424
685
  // ../wensity-preset/src/normalize.ts
425
686
  function mergeControlSolid(base, partial) {
@@ -460,9 +721,36 @@ function normalizeStyleConfig(partial) {
460
721
  }
461
722
  return normalized;
462
723
  }
724
+ function normalizeStyleConfigCanonical(partial) {
725
+ const validated = validateStyleConfigCanonicalPartial(partial ?? void 0);
726
+ const defaults = createDefaultStyleConfigCanonical();
727
+ const normalized = {
728
+ baseColor: validated.baseColor ?? defaults.baseColor,
729
+ themeColor: validated.themeColor ?? defaults.themeColor,
730
+ chartPalette: validated.chartPalette ?? defaults.chartPalette,
731
+ headingFont: validated.headingFont ?? defaults.headingFont,
732
+ bodyFont: validated.bodyFont ?? defaults.bodyFont,
733
+ iconLibrary: validated.iconLibrary ?? defaults.iconLibrary,
734
+ radius: validated.radius ?? defaults.radius
735
+ };
736
+ if (validated.presetCode !== void 0) {
737
+ normalized.presetCode = validated.presetCode;
738
+ }
739
+ return normalized;
740
+ }
463
741
 
464
742
  // ../wensity-preset/src/codec.ts
465
743
  var CODEC_PAYLOAD_KEYS = ["v", "iconLibrary", "radius", "fonts", "colors"];
744
+ var CODEC_PAYLOAD_KEYS_CANONICAL = [
745
+ "v",
746
+ "baseColor",
747
+ "themeColor",
748
+ "chartPalette",
749
+ "headingFont",
750
+ "bodyFont",
751
+ "iconLibrary",
752
+ "radius"
753
+ ];
466
754
  function assertPlainObject2(value, path) {
467
755
  if (value === null || typeof value !== "object" || Array.isArray(value)) {
468
756
  throw new PresetValidationError(`Expected an object at ${path}.`, path);
@@ -481,6 +769,9 @@ function utf8ByteLength(text) {
481
769
  return new TextEncoder().encode(text).byteLength;
482
770
  }
483
771
  var BASE64URL_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
772
+ var COMPACT_CANONICAL_PREFIX = "c";
773
+ var COMPACT_CANONICAL_BODY_LENGTH = 3;
774
+ var COMPACT_CANONICAL_ENCODED_LENGTH = COMPACT_CANONICAL_PREFIX.length + COMPACT_CANONICAL_BODY_LENGTH;
484
775
  function toBase64Url(bytes) {
485
776
  let out = "";
486
777
  for (let i = 0; i < bytes.length; i += 3) {
@@ -520,6 +811,66 @@ function fromBase64Url(code) {
520
811
  }
521
812
  return outIndex === out.length ? out : out.subarray(0, outIndex);
522
813
  }
814
+ function indexOfPresetValue(values, value, path) {
815
+ const index = values.indexOf(value);
816
+ if (index < 0) {
817
+ throw new PresetValidationError(`Invalid compact preset value at ${path}.`, path);
818
+ }
819
+ return index;
820
+ }
821
+ function readCompactChar(char) {
822
+ if (!char) return -1;
823
+ return BASE64URL_ALPHABET.indexOf(char);
824
+ }
825
+ function encodeCompactCanonicalPayload(payload) {
826
+ let value = indexOfPresetValue(BASE_COLOR_IDS, payload.baseColor, "preset.baseColor");
827
+ value = value << 3 | indexOfPresetValue(THEME_COLOR_IDS, payload.themeColor, "preset.themeColor");
828
+ value = value << 2 | indexOfPresetValue(CHART_PALETTE_IDS, payload.chartPalette, "preset.chartPalette");
829
+ value = value << 1 | indexOfPresetValue(HEADING_FONT_IDS, payload.headingFont, "preset.headingFont");
830
+ value = value << 1 | indexOfPresetValue(BODY_FONT_IDS, payload.bodyFont, "preset.bodyFont");
831
+ value = value << 1 | indexOfPresetValue(ICON_LIBRARIES, payload.iconLibrary, "preset.iconLibrary");
832
+ value = value << 2 | indexOfPresetValue(RADIUS_PRESETS, payload.radius, "preset.radius");
833
+ return `${COMPACT_CANONICAL_PREFIX}${BASE64URL_ALPHABET[value >> 12 & 63]}${BASE64URL_ALPHABET[value >> 6 & 63]}${BASE64URL_ALPHABET[value & 63]}`;
834
+ }
835
+ function decodeCompactCanonicalPayload(encoded) {
836
+ if (encoded.length !== COMPACT_CANONICAL_ENCODED_LENGTH || !encoded.startsWith(COMPACT_CANONICAL_PREFIX)) {
837
+ throw new PresetValidationError("Compact preset code has an invalid length.", "preset");
838
+ }
839
+ const a = readCompactChar(encoded[1]);
840
+ const b = readCompactChar(encoded[2]);
841
+ const c = readCompactChar(encoded[3]);
842
+ if (a < 0 || b < 0 || c < 0) {
843
+ throw new PresetValidationError("Compact preset code is not valid base64url.", "preset");
844
+ }
845
+ let value = a << 12 | b << 6 | c;
846
+ const radius = RADIUS_PRESETS[value & 3];
847
+ value >>= 2;
848
+ const iconLibrary = ICON_LIBRARIES[value & 1];
849
+ value >>= 1;
850
+ const bodyFont = BODY_FONT_IDS[value & 1];
851
+ value >>= 1;
852
+ const headingFont = HEADING_FONT_IDS[value & 1];
853
+ value >>= 1;
854
+ const chartPalette = CHART_PALETTE_IDS[value & 3];
855
+ value >>= 2;
856
+ const themeColor = THEME_COLOR_IDS[value & 7];
857
+ value >>= 3;
858
+ const baseColor = BASE_COLOR_IDS[value & 7];
859
+ value >>= 3;
860
+ if (value !== 0 || !baseColor || !themeColor || !chartPalette || !headingFont || !bodyFont || !iconLibrary || !radius) {
861
+ throw new PresetValidationError("Compact preset code contains an out-of-range value.", "preset");
862
+ }
863
+ return {
864
+ v: 2,
865
+ baseColor,
866
+ themeColor,
867
+ chartPalette,
868
+ headingFont,
869
+ bodyFont,
870
+ iconLibrary,
871
+ radius
872
+ };
873
+ }
523
874
  function stableStringifyCodecPayload(payload) {
524
875
  const parts = [
525
876
  `"v":${payload.v}`,
@@ -675,7 +1026,104 @@ function pickDefinedColors(partial) {
675
1026
  }
676
1027
  return Object.keys(out).length > 0 ? out : void 0;
677
1028
  }
678
- function decodePreset(code) {
1029
+ var CODEC_CANONICAL_REQUIRED_FIELDS = [
1030
+ "baseColor",
1031
+ "themeColor",
1032
+ "chartPalette",
1033
+ "headingFont",
1034
+ "bodyFont",
1035
+ "iconLibrary",
1036
+ "radius"
1037
+ ];
1038
+ function validateCodecPayloadCanonical(value) {
1039
+ assertPlainObject2(value, "preset");
1040
+ assertAllowedKeys2(value, CODEC_PAYLOAD_KEYS_CANONICAL, "preset");
1041
+ if (value.v !== 2) {
1042
+ throw new PresetValidationError(
1043
+ typeof value.v === "number" ? `Unsupported preset codec version ${value.v}. Expected v: 2.` : "Preset payload missing v: 2.",
1044
+ "preset.v"
1045
+ );
1046
+ }
1047
+ for (const key of CODEC_CANONICAL_REQUIRED_FIELDS) {
1048
+ if (!(key in value)) {
1049
+ throw new PresetValidationError(`Preset payload missing ${key}.`, `preset.${key}`);
1050
+ }
1051
+ }
1052
+ if (typeof value.baseColor !== "string" || !isBaseColorId(value.baseColor)) {
1053
+ throw new PresetValidationError(
1054
+ "Invalid preset.baseColor. Expected inherit | neutral | zinc | slate | stone | taupe.",
1055
+ "preset.baseColor"
1056
+ );
1057
+ }
1058
+ if (typeof value.themeColor !== "string" || !isThemeColorId(value.themeColor)) {
1059
+ throw new PresetValidationError(
1060
+ "Invalid preset.themeColor. Expected ink | red | blue | violet | green | orange.",
1061
+ "preset.themeColor"
1062
+ );
1063
+ }
1064
+ if (typeof value.chartPalette !== "string" || !isChartPaletteId(value.chartPalette)) {
1065
+ throw new PresetValidationError(
1066
+ "Invalid preset.chartPalette. Expected theme | warm | cool | mixed.",
1067
+ "preset.chartPalette"
1068
+ );
1069
+ }
1070
+ if (typeof value.headingFont !== "string" || !isHeadingFontId(value.headingFont)) {
1071
+ throw new PresetValidationError(
1072
+ "Invalid preset.headingFont. Expected system | playfair.",
1073
+ "preset.headingFont"
1074
+ );
1075
+ }
1076
+ if (typeof value.bodyFont !== "string" || !isBodyFontId(value.bodyFont)) {
1077
+ throw new PresetValidationError(
1078
+ "Invalid preset.bodyFont. Expected system | inter.",
1079
+ "preset.bodyFont"
1080
+ );
1081
+ }
1082
+ if (typeof value.iconLibrary !== "string" || !isIconLibrary(value.iconLibrary)) {
1083
+ throw new PresetValidationError(
1084
+ "Invalid preset.iconLibrary. Expected tabler | lucide.",
1085
+ "preset.iconLibrary"
1086
+ );
1087
+ }
1088
+ if (typeof value.radius !== "string" || !isRadiusPreset(value.radius)) {
1089
+ throw new PresetValidationError(
1090
+ "Invalid preset.radius. Expected none | small | medium | large.",
1091
+ "preset.radius"
1092
+ );
1093
+ }
1094
+ return {
1095
+ v: 2,
1096
+ baseColor: value.baseColor,
1097
+ themeColor: value.themeColor,
1098
+ chartPalette: value.chartPalette,
1099
+ headingFont: value.headingFont,
1100
+ bodyFont: value.bodyFont,
1101
+ iconLibrary: value.iconLibrary,
1102
+ radius: value.radius
1103
+ };
1104
+ }
1105
+ function encodePresetCanonical(style) {
1106
+ const normalized = normalizeStyleConfigCanonical(style);
1107
+ const payload = {
1108
+ v: 2,
1109
+ baseColor: normalized.baseColor,
1110
+ themeColor: normalized.themeColor,
1111
+ chartPalette: normalized.chartPalette,
1112
+ headingFont: normalized.headingFont,
1113
+ bodyFont: normalized.bodyFont,
1114
+ iconLibrary: normalized.iconLibrary,
1115
+ radius: normalized.radius
1116
+ };
1117
+ const code = `${PRESET_CODEC_PREFIX}.${encodeCompactCanonicalPayload(payload)}`;
1118
+ if (code.length > PRESET_CODEC_MAX_CODE_LENGTH) {
1119
+ throw new PresetValidationError(
1120
+ `Preset code exceeds ${PRESET_CODEC_MAX_CODE_LENGTH} characters.`,
1121
+ "preset"
1122
+ );
1123
+ }
1124
+ return code;
1125
+ }
1126
+ function decodePresetEnvelope(code) {
679
1127
  if (typeof code !== "string" || code.length === 0) {
680
1128
  throw new PresetValidationError("Preset code is required.", "preset");
681
1129
  }
@@ -696,6 +1144,9 @@ function decodePreset(code) {
696
1144
  if (!encoded) {
697
1145
  throw new PresetValidationError("Preset code payload is empty.", "preset");
698
1146
  }
1147
+ if (encoded.length === COMPACT_CANONICAL_ENCODED_LENGTH && encoded.startsWith(COMPACT_CANONICAL_PREFIX)) {
1148
+ return decodeCompactCanonicalPayload(encoded);
1149
+ }
699
1150
  if (encoded.includes("=") || /[^A-Za-z0-9_-]/.test(encoded)) {
700
1151
  throw new PresetValidationError("Preset code payload is not valid unpadded base64url.", "preset");
701
1152
  }
@@ -727,7 +1178,26 @@ function decodePreset(code) {
727
1178
  } catch {
728
1179
  throw new PresetValidationError("Preset payload is not valid JSON.", "preset");
729
1180
  }
730
- return validateCodecPayload(parsed);
1181
+ return parsed;
1182
+ }
1183
+ function formatUnsupportedPayloadVersion(v) {
1184
+ const label = v === void 0 ? "undefined" : String(v);
1185
+ return `Unsupported preset payload version ${label}. This preset may require a newer version of the Wensity CLI.`;
1186
+ }
1187
+ function decodeAnyPreset(code) {
1188
+ const parsed = decodePresetEnvelope(code);
1189
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
1190
+ throw new PresetValidationError("Expected an object at preset.", "preset");
1191
+ }
1192
+ assertNoForbiddenObjectKeys(parsed, "preset");
1193
+ const v = parsed.v;
1194
+ if (v === 1) {
1195
+ return validateCodecPayload(parsed);
1196
+ }
1197
+ if (v === 2) {
1198
+ return validateCodecPayloadCanonical(parsed);
1199
+ }
1200
+ throw new PresetValidationError(formatUnsupportedPayloadVersion(v), "preset.v");
731
1201
  }
732
1202
 
733
1203
  // ../wensity-preset/src/icon-map.ts
@@ -748,184 +1218,520 @@ var TABLER_TO_LUCIDE = {
748
1218
  IconCalendar: "Calendar",
749
1219
  IconCamera: "Camera",
750
1220
  IconCheck: "Check",
751
- IconChevronDown: "ChevronDown",
752
- IconChevronLeft: "ChevronLeft",
753
- IconChevronRight: "ChevronRight",
754
- IconChevronUp: "ChevronUp",
755
- IconChevronsLeft: "ChevronsLeft",
756
- IconChevronsRight: "ChevronsRight",
757
- IconCircleX: "CircleX",
758
- IconClock: "Clock",
759
- IconCornerDownLeft: "CornerDownLeft",
760
- IconDots: "Ellipsis",
761
- IconDownload: "Download",
762
- IconFile: "File",
763
- IconFileMusic: "FileAudio",
764
- IconFileText: "FileText",
765
- IconFileZip: "FileArchive",
766
- IconInfoCircle: "Info",
767
- IconLayoutSidebarLeftCollapse: "PanelLeftClose",
768
- IconLayoutSidebarLeftExpand: "PanelLeftOpen",
769
- IconMinus: "Minus",
770
- IconPhoto: "Image",
771
- IconPlus: "Plus",
772
- IconRefresh: "RefreshCw",
773
- IconSearch: "Search",
774
- IconSelector: "ChevronsUpDown",
775
- IconSlash: "Slash",
776
- IconUser: "User",
777
- IconVideo: "Video",
778
- IconX: "X"
779
- };
780
- var EXCLUSION_SET = new Set(ICON_TRANSFORM_EXCLUSIONS);
781
- function isIconTransformExclusion(name) {
782
- return EXCLUSION_SET.has(name);
783
- }
784
- function isMappedTablerIcon(name) {
785
- return Object.prototype.hasOwnProperty.call(TABLER_TO_LUCIDE, name);
786
- }
787
- function resolveLucideExport(tablerName) {
788
- if (isIconTransformExclusion(tablerName)) {
789
- throw new Error(
790
- `Icon "${tablerName}" is a Phase 0 exclusion and must not be transformed. Use internal SVG/CSS geometry instead.`
791
- );
792
- }
793
- if (!isMappedTablerIcon(tablerName)) {
794
- throw new Error(
795
- `No approved Lucide mapping for Tabler icon "${tablerName}". Add an audited map entry or convert it to internal geometry.`
796
- );
797
- }
798
- return TABLER_TO_LUCIDE[tablerName];
799
- }
800
- var LUCIDE_TO_TABLER = Object.fromEntries(
801
- Object.entries(TABLER_TO_LUCIDE).map(([tabler, lucide]) => [lucide, tabler])
802
- );
803
-
804
- // src/constants.ts
805
- var CLI_NAME = "wensity";
806
- var CLI_VERSION = "0.1.6";
807
- var CONFIG_FILE_NAME = "wensity.json";
808
- var DEFAULT_API_ORIGIN = "https://ui.wensity.com";
809
- var DEFAULT_REGISTRY_PATH = "/api/registry";
810
- var DEFAULT_COMPONENTS_DIR = "src/components/wensity";
811
- var DEFAULT_UTILS_PATH = "src/lib/utils";
812
- var DEFAULT_COMPONENTS_ALIAS = "@/components/wensity";
813
- var DEFAULT_UTILS_ALIAS = "@/lib/utils";
814
- var INIT_PRIMITIVE_CSS_SLUG = "button";
815
- var HELP_TEXT = `Wensity CLI ${CLI_VERSION}
816
-
817
- Usage:
818
- wensity init [--preset <code>] [--preset-file <path>] [options]
819
- wensity login [options]
820
- wensity logout
821
- wensity whoami [--json]
822
- wensity list [--type component|block] [options]
823
- wensity add <slug> [--type component|block] [options]
824
- wensity update <slug> [--type component|block] [options]
825
- wensity version
826
-
827
- Commands:
828
- init Create wensity.json and local helper files (optional --preset applies style + CSS)
829
- login Save a Wensity API token for premium component access
830
- logout Remove saved CLI credentials
831
- whoami Verify the saved token and print the authenticated account
832
- list List registry components or blocks
833
- add Download a component or block source file into this project
834
- update Re-download a component or block and overwrite the local copy
835
- version Print the CLI version
836
-
837
- Options:
838
- --api-url <url> Override the Wensity app origin, defaults to ${DEFAULT_API_ORIGIN}
839
- --registry <url> Override the registry endpoint
840
- --type <type> Select component or block, defaults to component
841
- --token <token> Provide a token non-interactively for login
842
- --preset <code> Apply a shareable Wensity style preset (wsty1.\u2026); merges style when config exists
843
- --preset-file <path> Read a preset code or JSON payload from a file
844
- --force Overwrite generated files when adding or initializing
845
- --no-install Write files without running the package manager
846
- --dry-run Print planned writes and installs without changing files
847
- --json Print machine-readable output where supported
848
- --help Show help
849
- --version Print the CLI version
850
-
851
- Preset notes:
852
- Generate codes at ${DEFAULT_API_ORIGIN}/docs/primitives/setup
853
- Plain init refuses an existing wensity.json unless --force
854
- init --preset merges style + primitive CSS; does not rewrite installed source
855
- add/update read style.iconLibrary for UI primitives only (Tabler|Lucide)
856
- Invalid/expired/unsupported codes exit non-zero with an actionable message
857
- `;
858
-
859
- // src/errors.ts
860
- var CliError = class extends Error {
861
- exitCode;
862
- constructor(message, exitCode = 1) {
863
- super(message);
864
- this.name = "CliError";
865
- this.exitCode = exitCode;
866
- }
1221
+ IconChevronDown: "ChevronDown",
1222
+ IconChevronLeft: "ChevronLeft",
1223
+ IconChevronRight: "ChevronRight",
1224
+ IconChevronUp: "ChevronUp",
1225
+ IconChevronsLeft: "ChevronsLeft",
1226
+ IconChevronsRight: "ChevronsRight",
1227
+ IconCircleX: "CircleX",
1228
+ IconClock: "Clock",
1229
+ IconCornerDownLeft: "CornerDownLeft",
1230
+ IconDots: "Ellipsis",
1231
+ IconDownload: "Download",
1232
+ IconFile: "File",
1233
+ IconFileMusic: "FileAudio",
1234
+ IconFileText: "FileText",
1235
+ IconFileZip: "FileArchive",
1236
+ IconInfoCircle: "Info",
1237
+ IconLayoutSidebarLeftCollapse: "PanelLeftClose",
1238
+ IconLayoutSidebarLeftExpand: "PanelLeftOpen",
1239
+ IconMinus: "Minus",
1240
+ IconPhoto: "Image",
1241
+ IconPlus: "Plus",
1242
+ IconRefresh: "RefreshCw",
1243
+ IconSearch: "Search",
1244
+ IconSelector: "ChevronsUpDown",
1245
+ IconSlash: "Slash",
1246
+ IconUser: "User",
1247
+ IconVideo: "Video",
1248
+ IconX: "X"
867
1249
  };
868
- function isNodeError(error) {
869
- return error instanceof Error && "code" in error;
1250
+ var EXCLUSION_SET = new Set(ICON_TRANSFORM_EXCLUSIONS);
1251
+ function isIconTransformExclusion(name) {
1252
+ return EXCLUSION_SET.has(name);
1253
+ }
1254
+ function isMappedTablerIcon(name) {
1255
+ return Object.prototype.hasOwnProperty.call(TABLER_TO_LUCIDE, name);
1256
+ }
1257
+ function resolveLucideExport(tablerName) {
1258
+ if (isIconTransformExclusion(tablerName)) {
1259
+ throw new Error(
1260
+ `Icon "${tablerName}" is a Phase 0 exclusion and must not be transformed. Use internal SVG/CSS geometry instead.`
1261
+ );
1262
+ }
1263
+ if (!isMappedTablerIcon(tablerName)) {
1264
+ throw new Error(
1265
+ `No approved Lucide mapping for Tabler icon "${tablerName}". Add an audited map entry or convert it to internal geometry.`
1266
+ );
1267
+ }
1268
+ return TABLER_TO_LUCIDE[tablerName];
870
1269
  }
1270
+ var LUCIDE_TO_TABLER = Object.fromEntries(
1271
+ Object.entries(TABLER_TO_LUCIDE).map(([tabler, lucide]) => [lucide, tabler])
1272
+ );
871
1273
 
872
- // src/args.ts
873
- function parseArgs(argv) {
874
- const positionals = [];
875
- const options = /* @__PURE__ */ new Map();
876
- for (let index = 0; index < argv.length; index += 1) {
877
- const token = argv[index];
878
- if (!token.startsWith("--")) {
879
- positionals.push(token);
880
- continue;
1274
+ // ../wensity-preset/src/palettes.ts
1275
+ var BASE_COLOR_PALETTE = {
1276
+ zinc: {
1277
+ light: {
1278
+ surfaceElevated: "#ffffff",
1279
+ surfaceOverlay: "#ffffff",
1280
+ foreground: "#09090b",
1281
+ border: "rgba(9, 9, 11, 0.08)",
1282
+ mutedForeground: "#71717a"
1283
+ },
1284
+ dark: {
1285
+ surfaceElevated: "#18181b",
1286
+ surfaceOverlay: "#27272a",
1287
+ foreground: "#fafafa",
1288
+ border: "rgba(250, 250, 250, 0.10)",
1289
+ mutedForeground: "#a1a1aa"
881
1290
  }
882
- if (token === "--") {
883
- positionals.push(...argv.slice(index + 1));
884
- break;
1291
+ },
1292
+ neutral: {
1293
+ light: {
1294
+ surfaceElevated: "#ffffff",
1295
+ surfaceOverlay: "#ffffff",
1296
+ foreground: "#171717",
1297
+ border: "rgba(23, 23, 23, 0.08)",
1298
+ mutedForeground: "#737373"
1299
+ },
1300
+ dark: {
1301
+ surfaceElevated: "#171717",
1302
+ surfaceOverlay: "#262626",
1303
+ foreground: "#fafafa",
1304
+ border: "rgba(250, 250, 250, 0.10)",
1305
+ mutedForeground: "#a3a3a3"
885
1306
  }
886
- const inlineValueIndex = token.indexOf("=");
887
- const rawName = inlineValueIndex > -1 ? token.slice(2, inlineValueIndex) : token.slice(2);
888
- const name = normalizeOptionName(rawName);
889
- if (!name) {
890
- throw new CliError(`Invalid option: ${token}`);
1307
+ },
1308
+ slate: {
1309
+ light: {
1310
+ surfaceElevated: "#ffffff",
1311
+ surfaceOverlay: "#ffffff",
1312
+ foreground: "#0f172a",
1313
+ border: "rgba(15, 23, 42, 0.08)",
1314
+ mutedForeground: "#64748b"
1315
+ },
1316
+ dark: {
1317
+ surfaceElevated: "#0f172a",
1318
+ surfaceOverlay: "#1e293b",
1319
+ foreground: "#f8fafc",
1320
+ border: "rgba(248, 250, 252, 0.10)",
1321
+ mutedForeground: "#94a3b8"
891
1322
  }
892
- if (inlineValueIndex > -1) {
893
- options.set(name, token.slice(inlineValueIndex + 1));
894
- continue;
1323
+ },
1324
+ stone: {
1325
+ light: {
1326
+ surfaceElevated: "#ffffff",
1327
+ surfaceOverlay: "#ffffff",
1328
+ foreground: "#1c1917",
1329
+ border: "rgba(28, 25, 23, 0.08)",
1330
+ mutedForeground: "#78716c"
1331
+ },
1332
+ dark: {
1333
+ surfaceElevated: "#1c1917",
1334
+ surfaceOverlay: "#292524",
1335
+ foreground: "#fafaf9",
1336
+ border: "rgba(250, 250, 249, 0.10)",
1337
+ mutedForeground: "#a8a29e"
895
1338
  }
896
- const nextToken = argv[index + 1];
897
- if (nextToken && !nextToken.startsWith("--") && expectsValue(name)) {
898
- options.set(name, nextToken);
899
- index += 1;
900
- continue;
1339
+ },
1340
+ taupe: {
1341
+ light: {
1342
+ surfaceElevated: "#faf8f5",
1343
+ surfaceOverlay: "#ffffff",
1344
+ foreground: "#3b342e",
1345
+ border: "rgba(59, 52, 46, 0.10)",
1346
+ mutedForeground: "#8a7f74"
1347
+ },
1348
+ dark: {
1349
+ surfaceElevated: "#1c1916",
1350
+ surfaceOverlay: "#2a2521",
1351
+ foreground: "#f5f0ea",
1352
+ border: "rgba(245, 240, 234, 0.10)",
1353
+ mutedForeground: "#a89f94"
901
1354
  }
902
- options.set(name, true);
903
1355
  }
904
- return { positionals, options };
1356
+ };
1357
+ var THEME_COLOR_PALETTE = {
1358
+ ink: {
1359
+ light: { ...DEFAULT_CONTROL_SOLID_LIGHT },
1360
+ dark: { ...DEFAULT_CONTROL_SOLID_DARK }
1361
+ },
1362
+ //
1363
+ // Named hues stay the same true, saturated color family in both modes —
1364
+ // dark mode gets one step deeper (never a washed-out pastel tint of the
1365
+ // light-mode color). This mirrors how shadcn's own theme presets work:
1366
+ // e.g. its "red" theme ships `oklch(0.505 … 27.518)` (~Tailwind red-700)
1367
+ // for light and `oklch(0.444 … 26.899)` (~red-800, darker not lighter)
1368
+ // for dark, with a near-white foreground in both. Hex stops below are
1369
+ // Tailwind v4's red/blue/violet/green/orange 600/700/800/900/50 values.
1370
+ red: {
1371
+ light: {
1372
+ controlSolid: "#c10007",
1373
+ // red-700
1374
+ controlSolidHover: "#e7000b",
1375
+ // red-600 — brighter on hover
1376
+ controlSolidActive: "#9f0712",
1377
+ // red-800 — darker on press
1378
+ controlSolidForeground: "#fef2f2"
1379
+ // red-50
1380
+ },
1381
+ dark: {
1382
+ controlSolid: "#9f0712",
1383
+ // red-800
1384
+ controlSolidHover: "#c10007",
1385
+ // red-700 — brighter on hover
1386
+ controlSolidActive: "#82181a",
1387
+ // red-900 — darker on press
1388
+ controlSolidForeground: "#fef2f2"
1389
+ // red-50
1390
+ }
1391
+ },
1392
+ blue: {
1393
+ light: {
1394
+ controlSolid: "#1447e6",
1395
+ // blue-700
1396
+ controlSolidHover: "#155dfc",
1397
+ // blue-600
1398
+ controlSolidActive: "#193cb8",
1399
+ // blue-800
1400
+ controlSolidForeground: "#eff6ff"
1401
+ // blue-50
1402
+ },
1403
+ dark: {
1404
+ controlSolid: "#193cb8",
1405
+ // blue-800
1406
+ controlSolidHover: "#1447e6",
1407
+ // blue-700
1408
+ controlSolidActive: "#1c398e",
1409
+ // blue-900
1410
+ controlSolidForeground: "#eff6ff"
1411
+ // blue-50
1412
+ }
1413
+ },
1414
+ violet: {
1415
+ light: {
1416
+ controlSolid: "#7008e7",
1417
+ // violet-700
1418
+ controlSolidHover: "#7f22fe",
1419
+ // violet-600
1420
+ controlSolidActive: "#5d0ec0",
1421
+ // violet-800
1422
+ controlSolidForeground: "#f5f3ff"
1423
+ // violet-50
1424
+ },
1425
+ dark: {
1426
+ controlSolid: "#5d0ec0",
1427
+ // violet-800
1428
+ controlSolidHover: "#7008e7",
1429
+ // violet-700
1430
+ controlSolidActive: "#4d179a",
1431
+ // violet-900
1432
+ controlSolidForeground: "#f5f3ff"
1433
+ // violet-50
1434
+ }
1435
+ },
1436
+ green: {
1437
+ light: {
1438
+ controlSolid: "#008236",
1439
+ // green-700
1440
+ controlSolidHover: "#00a63e",
1441
+ // green-600
1442
+ controlSolidActive: "#016630",
1443
+ // green-800
1444
+ controlSolidForeground: "#f0fdf4"
1445
+ // green-50
1446
+ },
1447
+ dark: {
1448
+ controlSolid: "#016630",
1449
+ // green-800
1450
+ controlSolidHover: "#008236",
1451
+ // green-700
1452
+ controlSolidActive: "#0d542b",
1453
+ // green-900
1454
+ controlSolidForeground: "#f0fdf4"
1455
+ // green-50
1456
+ }
1457
+ },
1458
+ orange: {
1459
+ light: {
1460
+ controlSolid: "#ca3500",
1461
+ // orange-700
1462
+ controlSolidHover: "#f54900",
1463
+ // orange-600
1464
+ controlSolidActive: "#9f2d00",
1465
+ // orange-800
1466
+ controlSolidForeground: "#fff7ed"
1467
+ // orange-50
1468
+ },
1469
+ dark: {
1470
+ controlSolid: "#9f2d00",
1471
+ // orange-800
1472
+ controlSolidHover: "#ca3500",
1473
+ // orange-700
1474
+ controlSolidActive: "#7e2a0c",
1475
+ // orange-900
1476
+ controlSolidForeground: "#fff7ed"
1477
+ // orange-50
1478
+ }
1479
+ }
1480
+ };
1481
+ var DEFAULT_CHART_STOPS = {
1482
+ light: [
1483
+ "#2a78d6",
1484
+ "#1baf7a",
1485
+ "#eda100",
1486
+ "#008300",
1487
+ "#4a3aa7",
1488
+ "#e34948"
1489
+ ],
1490
+ dark: [
1491
+ "#3987e5",
1492
+ "#199e70",
1493
+ "#c98500",
1494
+ "#008300",
1495
+ "#9085e9",
1496
+ "#e66767"
1497
+ ]
1498
+ };
1499
+ var CHART_PALETTES = {
1500
+ theme: {
1501
+ ink: DEFAULT_CHART_STOPS,
1502
+ red: {
1503
+ light: ["#e34948", "#f97316", "#eda100", "#db2777", "#b91c1c", "#9f1239"],
1504
+ dark: ["#e66767", "#fb923c", "#c98500", "#f472b6", "#f87171", "#fb7185"]
1505
+ },
1506
+ blue: {
1507
+ light: ["#2a78d6", "#0284c7", "#4a3aa7", "#0e7490", "#1d4ed8", "#0369a1"],
1508
+ dark: ["#3987e5", "#38bdf8", "#9085e9", "#22d3ee", "#60a5fa", "#7dd3fc"]
1509
+ },
1510
+ violet: {
1511
+ light: ["#4a3aa7", "#7c3aed", "#2a78d6", "#a21caf", "#6d28d9", "#db2777"],
1512
+ dark: ["#9085e9", "#a78bfa", "#3987e5", "#e879f9", "#c4b5fd", "#f472b6"]
1513
+ },
1514
+ green: {
1515
+ light: ["#1baf7a", "#008300", "#0d9488", "#2a78d6", "#047857", "#65a30d"],
1516
+ dark: ["#199e70", "#34d399", "#2dd4bf", "#3987e5", "#4ade80", "#a3e635"]
1517
+ },
1518
+ orange: {
1519
+ light: ["#eda100", "#f97316", "#e34948", "#ca8a04", "#c2410c", "#db2777"],
1520
+ dark: ["#c98500", "#fb923c", "#e66767", "#facc15", "#fdba74", "#f472b6"]
1521
+ }
1522
+ },
1523
+ warm: {
1524
+ light: ["#ea580c", "#d97706", "#e34948", "#db2777", "#ca8a04", "#c2410c"],
1525
+ dark: ["#fb923c", "#fbbf24", "#e66767", "#f472b6", "#facc15", "#fdba74"]
1526
+ },
1527
+ cool: {
1528
+ light: ["#2a78d6", "#0e7490", "#4a3aa7", "#0284c7", "#1d4ed8", "#0f766e"],
1529
+ dark: ["#3987e5", "#22d3ee", "#9085e9", "#38bdf8", "#60a5fa", "#2dd4bf"]
1530
+ },
1531
+ mixed: {
1532
+ light: ["#2a78d6", "#e34948", "#1baf7a", "#eda100", "#4a3aa7", "#db2777"],
1533
+ dark: ["#3987e5", "#e66767", "#199e70", "#c98500", "#9085e9", "#f472b6"]
1534
+ }
1535
+ };
1536
+
1537
+ // ../wensity-preset/src/curated-fonts.ts
1538
+ var HEADING_FONT_PACKAGES = {
1539
+ playfair: {
1540
+ packageName: "@fontsource/playfair-display",
1541
+ importSpecifier: "@fontsource/playfair-display",
1542
+ familyName: "Playfair Display"
1543
+ }
1544
+ };
1545
+ var BODY_FONT_PACKAGES = {
1546
+ inter: {
1547
+ packageName: "@fontsource/inter",
1548
+ importSpecifier: "@fontsource/inter",
1549
+ familyName: "Inter"
1550
+ }
1551
+ };
1552
+ var HEADING_FONT_CSS = {
1553
+ system: "var(--font-display)",
1554
+ playfair: "'Playfair Display', Georgia, 'Times New Roman', serif"
1555
+ };
1556
+ var BODY_FONT_CSS = {
1557
+ system: "var(--font-sans)",
1558
+ inter: "'Inter', ui-sans-serif, system-ui, sans-serif"
1559
+ };
1560
+
1561
+ // ../wensity-preset/src/resolve.ts
1562
+ var RESOLVED_TOKEN_CSS_VARS = {
1563
+ surfaceElevated: "--primitive-surface-elevated",
1564
+ surfaceOverlay: "--primitive-surface-overlay",
1565
+ surfaceHover: "--primitive-surface-hover",
1566
+ surfaceActive: "--primitive-surface-active",
1567
+ surfaceSelected: "--primitive-surface-selected",
1568
+ borderSubtle: "--primitive-border-subtle",
1569
+ ring: "--primitive-ring",
1570
+ textSecondary: "--primitive-text-secondary",
1571
+ textPlaceholder: "--primitive-text-placeholder",
1572
+ controlSolid: "--primitive-control-solid",
1573
+ controlSolidHover: "--primitive-control-solid-hover",
1574
+ controlSolidActive: "--primitive-control-solid-active",
1575
+ controlSolidForeground: "--primitive-control-solid-foreground",
1576
+ chart1: "--primitive-chart-1",
1577
+ chart2: "--primitive-chart-2",
1578
+ chart3: "--primitive-chart-3",
1579
+ chart4: "--primitive-chart-4",
1580
+ chart5: "--primitive-chart-5",
1581
+ chart6: "--primitive-chart-6",
1582
+ radius: "--primitive-radius",
1583
+ fontSans: "--primitive-font-sans",
1584
+ fontDisplay: "--primitive-font-display",
1585
+ fontMono: "--primitive-font-mono"
1586
+ };
1587
+ var INHERIT_BASE_EXPRESSIONS = {
1588
+ light: {
1589
+ surfaceElevated: "#ffffff",
1590
+ surfaceOverlay: "#ffffff",
1591
+ surfaceHover: "color-mix(in srgb, var(--foreground) 4%, transparent)",
1592
+ surfaceActive: "color-mix(in srgb, var(--foreground) 7%, transparent)",
1593
+ borderSubtle: "color-mix(in srgb, var(--border) 60%, transparent)",
1594
+ textSecondary: "color-mix(in srgb, var(--foreground) 72%, transparent)",
1595
+ textPlaceholder: "color-mix(in srgb, var(--muted-foreground) 85%, transparent)"
1596
+ },
1597
+ dark: {
1598
+ surfaceElevated: "#0f0f10",
1599
+ surfaceOverlay: "#141415",
1600
+ surfaceHover: "color-mix(in srgb, var(--foreground) 5%, transparent)",
1601
+ surfaceActive: "color-mix(in srgb, var(--foreground) 10%, transparent)",
1602
+ borderSubtle: "color-mix(in srgb, var(--border) 60%, transparent)",
1603
+ textSecondary: "color-mix(in srgb, var(--foreground) 72%, transparent)",
1604
+ textPlaceholder: "color-mix(in srgb, var(--muted-foreground) 85%, transparent)"
1605
+ }
1606
+ };
1607
+ var FIXED_RING = "color-mix(in srgb, var(--foreground) 45%, transparent)";
1608
+ var FIXED_SURFACE_SELECTED = {
1609
+ light: "color-mix(in srgb, var(--foreground) 6%, transparent)",
1610
+ dark: "color-mix(in srgb, var(--foreground) 8%, transparent)"
1611
+ };
1612
+ var COLORED_THEME_IDS = /* @__PURE__ */ new Set([
1613
+ "red",
1614
+ "blue",
1615
+ "violet",
1616
+ "green",
1617
+ "orange"
1618
+ ]);
1619
+ function colorMix(source, percent) {
1620
+ return `color-mix(in srgb, ${source} ${percent}%, transparent)`;
905
1621
  }
906
- function getStringOption(args, name) {
907
- const value = args.options.get(name);
908
- if (value === void 0) return void 0;
909
- if (typeof value === "boolean") {
910
- throw new CliError(`--${name} expects a value.`);
1622
+ function resolveControlSolid(partial, fallback) {
1623
+ const out = { ...fallback };
1624
+ for (const key of CONTROL_SOLID_COLOR_KEYS) {
1625
+ const value = partial?.[key];
1626
+ if (value !== void 0) out[key] = value;
911
1627
  }
912
- return value;
1628
+ return out;
913
1629
  }
914
- function hasFlag(args, name) {
915
- return args.options.get(name) === true;
1630
+ function chartStopsToModeTokens(stops) {
1631
+ return {
1632
+ chart1: stops[0],
1633
+ chart2: stops[1],
1634
+ chart3: stops[2],
1635
+ chart4: stops[3],
1636
+ chart5: stops[4],
1637
+ chart6: stops[5]
1638
+ };
916
1639
  }
917
- function expectsValue(name) {
918
- return ["api-url", "registry", "token", "type", "preset", "preset-file"].includes(name);
1640
+ function baseFromInherit(mode) {
1641
+ return { ...INHERIT_BASE_EXPRESSIONS[mode] };
919
1642
  }
920
- function normalizeOptionName(name) {
921
- if (name === "dry") return "dry-run";
922
- return name;
1643
+ function baseFromSeeds(seeds, mode) {
1644
+ const hoverPercent = mode === "light" ? 4 : 5;
1645
+ const activePercent = mode === "light" ? 7 : 10;
1646
+ return {
1647
+ surfaceElevated: seeds.surfaceElevated,
1648
+ surfaceOverlay: seeds.surfaceOverlay,
1649
+ surfaceHover: colorMix(seeds.foreground, hoverPercent),
1650
+ surfaceActive: colorMix(seeds.foreground, activePercent),
1651
+ borderSubtle: colorMix(seeds.border, 60),
1652
+ textSecondary: colorMix(seeds.foreground, 72),
1653
+ textPlaceholder: colorMix(seeds.mutedForeground, 85)
1654
+ };
1655
+ }
1656
+ function ringAndSelectedForInk(mode) {
1657
+ return {
1658
+ ring: FIXED_RING,
1659
+ surfaceSelected: FIXED_SURFACE_SELECTED[mode]
1660
+ };
1661
+ }
1662
+ function ringAndSelectedFromAccent(accent, mode) {
1663
+ return {
1664
+ ring: colorMix(accent, 45),
1665
+ surfaceSelected: colorMix(accent, mode === "light" ? 6 : 8)
1666
+ };
1667
+ }
1668
+ function buildModeTokens(args) {
1669
+ return {
1670
+ ...args.base,
1671
+ ...args.ringSelected,
1672
+ controlSolid: args.control.controlSolid,
1673
+ controlSolidHover: args.control.controlSolidHover,
1674
+ controlSolidActive: args.control.controlSolidActive,
1675
+ controlSolidForeground: args.control.controlSolidForeground,
1676
+ ...chartStopsToModeTokens(args.charts)
1677
+ };
1678
+ }
1679
+ function resolveStyleConfig(style) {
1680
+ const lightControl = resolveControlSolid(
1681
+ style.colors?.light,
1682
+ DEFAULT_CONTROL_SOLID_LIGHT
1683
+ );
1684
+ const darkControl = resolveControlSolid(
1685
+ style.colors?.dark,
1686
+ DEFAULT_CONTROL_SOLID_DARK
1687
+ );
1688
+ return {
1689
+ iconLibrary: style.iconLibrary,
1690
+ radius: RADIUS_PRESET_CSS[style.radius],
1691
+ fontSans: expandFontRole(style.fonts.sans, "sans"),
1692
+ fontDisplay: expandFontRole(style.fonts.display, "display"),
1693
+ fontMono: expandFontRole(style.fonts.mono, "mono"),
1694
+ light: buildModeTokens({
1695
+ base: baseFromInherit("light"),
1696
+ control: lightControl,
1697
+ ringSelected: ringAndSelectedForInk("light"),
1698
+ charts: DEFAULT_CHART_STOPS.light
1699
+ }),
1700
+ dark: buildModeTokens({
1701
+ base: baseFromInherit("dark"),
1702
+ control: darkControl,
1703
+ ringSelected: ringAndSelectedForInk("dark"),
1704
+ charts: DEFAULT_CHART_STOPS.dark
1705
+ })
1706
+ };
1707
+ }
1708
+ function resolveChartStops(chartPalette, themeColor, mode) {
1709
+ if (chartPalette === "theme") {
1710
+ return CHART_PALETTES.theme[themeColor][mode];
1711
+ }
1712
+ return CHART_PALETTES[chartPalette][mode];
1713
+ }
1714
+ function resolveStyleConfigCanonical(style) {
1715
+ const theme = THEME_COLOR_PALETTE[style.themeColor];
1716
+ const useAccentRing = COLORED_THEME_IDS.has(style.themeColor);
1717
+ const resolveMode = (mode) => {
1718
+ const base = style.baseColor === "inherit" ? baseFromInherit(mode) : baseFromSeeds(BASE_COLOR_PALETTE[style.baseColor][mode], mode);
1719
+ const control = theme[mode];
1720
+ const ringSelected = useAccentRing ? ringAndSelectedFromAccent(control.controlSolid, mode) : ringAndSelectedForInk(mode);
1721
+ const charts = resolveChartStops(style.chartPalette, style.themeColor, mode);
1722
+ return buildModeTokens({ base, control, ringSelected, charts });
1723
+ };
1724
+ return {
1725
+ iconLibrary: style.iconLibrary,
1726
+ radius: RADIUS_PRESET_CSS[style.radius],
1727
+ fontSans: BODY_FONT_CSS[style.bodyFont],
1728
+ fontDisplay: HEADING_FONT_CSS[style.headingFont],
1729
+ // Canonical model has no mono field — lock to today's system-mono expansion.
1730
+ fontMono: FONT_ID_EXPANSIONS["system-mono"],
1731
+ light: resolveMode("light"),
1732
+ dark: resolveMode("dark")
1733
+ };
923
1734
  }
924
-
925
- // src/config.ts
926
- import { existsSync } from "fs";
927
- import { access, mkdir, readFile, writeFile } from "fs/promises";
928
- import { dirname as dirname2, join as join2, relative as relative2 } from "path";
929
1735
 
930
1736
  // src/paths.ts
931
1737
  import { dirname, isAbsolute, relative, resolve, sep } from "path";
@@ -995,6 +1801,35 @@ function extensionlessPath(path) {
995
1801
  return path.replace(/\.(tsx|ts|jsx|js)$/, "");
996
1802
  }
997
1803
 
1804
+ // src/style-shape.ts
1805
+ function isCanonicalStyleShape(value) {
1806
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
1807
+ const record = value;
1808
+ return "baseColor" in record || "themeColor" in record || "chartPalette" in record || "headingFont" in record || "bodyFont" in record;
1809
+ }
1810
+ function normalizeProjectStyle(style) {
1811
+ if (isCanonicalStyleShape(style)) {
1812
+ return normalizeStyleConfigCanonical(style);
1813
+ }
1814
+ return normalizeStyleConfig(style);
1815
+ }
1816
+ function resolveTokensForStyle(style) {
1817
+ if (isCanonicalStyleShape(style)) {
1818
+ return resolveStyleConfigCanonical(normalizeStyleConfigCanonical(style));
1819
+ }
1820
+ return resolveStyleConfig(normalizeStyleConfig(style));
1821
+ }
1822
+ function curatedFontsFromStyle(style) {
1823
+ if (isCanonicalStyleShape(style)) {
1824
+ const normalized = normalizeStyleConfigCanonical(style);
1825
+ return {
1826
+ headingFont: normalized.headingFont,
1827
+ bodyFont: normalized.bodyFont
1828
+ };
1829
+ }
1830
+ return { headingFont: "system", bodyFont: "system" };
1831
+ }
1832
+
998
1833
  // src/config.ts
999
1834
  async function loadConfig(cwd, overrides = {}) {
1000
1835
  const foundPath = await findUp(cwd, CONFIG_FILE_NAME);
@@ -1128,7 +1963,8 @@ function normalizeConfig(config, overrides) {
1128
1963
  utils: config.aliases?.utils || DEFAULT_UTILS_ALIAS
1129
1964
  },
1130
1965
  // In-memory only: missing style fields resolve to defaults without rewriting disk.
1131
- style: normalizeStyleConfig(config.style)
1966
+ // Accepts either legacy or canonical style shapes.
1967
+ style: normalizeProjectStyle(config.style)
1132
1968
  };
1133
1969
  } catch (error) {
1134
1970
  if (error instanceof PresetValidationError) {
@@ -1257,6 +2093,122 @@ function requiredHome(env) {
1257
2093
  throw new CliError("HOME is not set, so the CLI cannot locate ~/.config/wensity.");
1258
2094
  }
1259
2095
 
2096
+ // src/font-install.ts
2097
+ var FONT_CSS_START_MARKER = "/* \u2500\u2500 Wensity Fonts";
2098
+ var FONT_CSS_END_MARKER = "/* \u2500\u2500 End Wensity Fonts \u2500\u2500 */";
2099
+ function planFontInstall(headingFont, bodyFont) {
2100
+ const packages = [];
2101
+ const importLines = [];
2102
+ const seenPackages = /* @__PURE__ */ new Set();
2103
+ const seenImports = /* @__PURE__ */ new Set();
2104
+ const pushFont = (meta) => {
2105
+ if (!meta) return;
2106
+ if (!seenPackages.has(meta.packageName)) {
2107
+ seenPackages.add(meta.packageName);
2108
+ packages.push(meta.packageName);
2109
+ }
2110
+ const line = `@import "${meta.importSpecifier}";`;
2111
+ if (!seenImports.has(line)) {
2112
+ seenImports.add(line);
2113
+ importLines.push(line);
2114
+ }
2115
+ };
2116
+ if (headingFont !== "system") {
2117
+ pushFont(HEADING_FONT_PACKAGES[headingFont]);
2118
+ }
2119
+ if (bodyFont !== "system") {
2120
+ pushFont(BODY_FONT_PACKAGES[bodyFont]);
2121
+ }
2122
+ packages.sort();
2123
+ if (importLines.length === 0) {
2124
+ return { packages: [], importLines: [], block: null };
2125
+ }
2126
+ const block = [
2127
+ `${FONT_CSS_START_MARKER} \u2500\u2500 */`,
2128
+ ...importLines,
2129
+ FONT_CSS_END_MARKER
2130
+ ].join("\n");
2131
+ return { packages, importLines, block };
2132
+ }
2133
+ function upsertFontImportBlock(existingCss, block) {
2134
+ const start = existingCss.indexOf(FONT_CSS_START_MARKER);
2135
+ const end = existingCss.indexOf(FONT_CSS_END_MARKER);
2136
+ if (start !== -1 || end !== -1) {
2137
+ if (start === -1 || end === -1 || end <= start) {
2138
+ throw new CliError(
2139
+ "Configured Tailwind CSS has a partial Wensity Fonts block. Remove the broken markers and re-run install."
2140
+ );
2141
+ }
2142
+ const before2 = existingCss.slice(0, start).replace(/\s*$/, "");
2143
+ const after2 = existingCss.slice(end + FONT_CSS_END_MARKER.length).replace(/^\s*/, "");
2144
+ if (!block) {
2145
+ const joined = [before2, after2].filter((part) => part.length > 0).join("\n\n");
2146
+ return joined ? `${joined.replace(/\n{3,}/g, "\n\n")}
2147
+ ` : "";
2148
+ }
2149
+ const normalizedBlock2 = block.trim();
2150
+ const prefix2 = before2 ? `${before2}
2151
+
2152
+ ` : "";
2153
+ const suffix2 = after2 ? `
2154
+
2155
+ ${after2}` : "\n";
2156
+ return `${prefix2}${normalizedBlock2}${suffix2}`.replace(/\n{3,}/g, "\n\n");
2157
+ }
2158
+ if (!block) return existingCss;
2159
+ const normalizedBlock = block.trim();
2160
+ const insertAt = findFontBlockInsertIndex(existingCss);
2161
+ const before = existingCss.slice(0, insertAt).replace(/\s*$/, "");
2162
+ const after = existingCss.slice(insertAt).replace(/^\s*/, "");
2163
+ const prefix = before ? `${before}
2164
+
2165
+ ` : "";
2166
+ const suffix = after ? `
2167
+
2168
+ ${after}` : "\n";
2169
+ return `${prefix}${normalizedBlock}${suffix}`.replace(/\n{3,}/g, "\n\n");
2170
+ }
2171
+ function warnUnusedFontPackageMessage(headingFont, bodyFont, installedPackages) {
2172
+ const needed = new Set(planFontInstall(headingFont, bodyFont).packages);
2173
+ const curated = [
2174
+ ...Object.values(HEADING_FONT_PACKAGES).map((entry) => entry.packageName),
2175
+ ...Object.values(BODY_FONT_PACKAGES).map((entry) => entry.packageName)
2176
+ ];
2177
+ const leftover = curated.filter(
2178
+ (pkg) => installedPackages.has(pkg) && !needed.has(pkg)
2179
+ );
2180
+ if (leftover.length === 0) return void 0;
2181
+ return `Project package.json still lists ${leftover.join(", ")} while curated fonts are set to heading="${headingFont}" body="${bodyFont}". Wensity removed the @import for unused fonts but will not uninstall ${leftover.join(", ")} (it may be used outside Wensity).`;
2182
+ }
2183
+ function findFontBlockInsertIndex(css) {
2184
+ let index = 0;
2185
+ const length = css.length;
2186
+ while (index < length) {
2187
+ while (index < length && /\s/.test(css[index])) index += 1;
2188
+ if (index >= length) break;
2189
+ if (css.startsWith("/*", index)) {
2190
+ const end = css.indexOf("*/", index + 2);
2191
+ if (end === -1) break;
2192
+ index = end + 2;
2193
+ continue;
2194
+ }
2195
+ if (css.startsWith("//", index)) {
2196
+ const end = css.indexOf("\n", index);
2197
+ if (end === -1) return length;
2198
+ index = end + 1;
2199
+ continue;
2200
+ }
2201
+ if (css.startsWith("@charset", index) || css.startsWith("@import", index)) {
2202
+ const end = css.indexOf(";", index);
2203
+ if (end === -1) break;
2204
+ index = end + 1;
2205
+ continue;
2206
+ }
2207
+ break;
2208
+ }
2209
+ return index;
2210
+ }
2211
+
1260
2212
  // src/output.ts
1261
2213
  import boxen from "boxen";
1262
2214
  import Table from "cli-table3";
@@ -1807,19 +2759,13 @@ async function resolvePresetStyle(input) {
1807
2759
  const trimmed = raw.trim();
1808
2760
  if (trimmed.startsWith("{")) {
1809
2761
  const parsed = JSON.parse(trimmed);
1810
- if (parsed && typeof parsed === "object" && "v" in parsed) {
1811
- const payload = validateCodecPayload(parsed);
1812
- const json = stableStringifyCodecPayload(payload);
1813
- code = `${PRESET_CODEC_PREFIX}.${Buffer.from(json, "utf8").toString("base64url")}`;
1814
- } else {
1815
- code = encodePreset(parsed);
1816
- }
2762
+ code = encodePresetFileJson(parsed);
1817
2763
  } else {
1818
2764
  code = trimmed;
1819
2765
  }
1820
2766
  } catch (error) {
1821
2767
  if (error instanceof PresetValidationError) {
1822
- throw new CliError(`Invalid preset file: ${error.message}`);
2768
+ throw toCliPresetError(error);
1823
2769
  }
1824
2770
  if (error instanceof SyntaxError) {
1825
2771
  throw new CliError(`Preset file contains invalid JSON: ${error.message}`);
@@ -1834,19 +2780,76 @@ async function resolvePresetStyle(input) {
1834
2780
  }
1835
2781
  try {
1836
2782
  const trimmed = code.trim();
1837
- const payload = decodePreset(trimmed);
1838
- const style = normalizeStyleConfig({
1839
- ...codecPayloadToStyleInput(payload),
1840
- presetCode: trimmed
1841
- });
2783
+ const payload = decodeAnyPreset(trimmed);
2784
+ const style = styleFromDecodedPayload(payload, trimmed);
1842
2785
  return { style, presetCode: trimmed };
1843
2786
  } catch (error) {
1844
2787
  if (error instanceof PresetValidationError) {
1845
- throw new CliError(`Invalid preset: ${error.message}`);
2788
+ throw toCliPresetError(error);
1846
2789
  }
1847
2790
  throw error;
1848
2791
  }
1849
2792
  }
2793
+ function encodePresetFileJson(parsed) {
2794
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
2795
+ throw new PresetValidationError("Expected an object at preset.", "preset");
2796
+ }
2797
+ if ("v" in parsed) {
2798
+ const v = parsed.v;
2799
+ if (v === 1) {
2800
+ const payload = validateCodecPayload(parsed);
2801
+ const json = stableStringifyCodecPayload(payload);
2802
+ return `${PRESET_CODEC_PREFIX}.${Buffer.from(json, "utf8").toString("base64url")}`;
2803
+ }
2804
+ if (v === 2) {
2805
+ const payload = validateCodecPayloadCanonical(parsed);
2806
+ return encodePresetCanonical({
2807
+ baseColor: payload.baseColor,
2808
+ themeColor: payload.themeColor,
2809
+ chartPalette: payload.chartPalette,
2810
+ headingFont: payload.headingFont,
2811
+ bodyFont: payload.bodyFont,
2812
+ iconLibrary: payload.iconLibrary,
2813
+ radius: payload.radius
2814
+ });
2815
+ }
2816
+ throw new PresetValidationError(
2817
+ `Unsupported preset payload version ${String(v)}. This preset may require a newer version of the Wensity CLI.`,
2818
+ "preset.v"
2819
+ );
2820
+ }
2821
+ if ("baseColor" in parsed || "themeColor" in parsed || "chartPalette" in parsed || "headingFont" in parsed || "bodyFont" in parsed) {
2822
+ return encodePresetCanonical(parsed);
2823
+ }
2824
+ return encodePreset(parsed);
2825
+ }
2826
+ function styleFromDecodedPayload(payload, presetCode) {
2827
+ if (payload.v === 1) {
2828
+ return normalizeStyleConfig({
2829
+ ...codecPayloadToStyleInput(payload),
2830
+ presetCode
2831
+ });
2832
+ }
2833
+ return normalizeStyleConfigCanonical({
2834
+ baseColor: payload.baseColor,
2835
+ themeColor: payload.themeColor,
2836
+ chartPalette: payload.chartPalette,
2837
+ headingFont: payload.headingFont,
2838
+ bodyFont: payload.bodyFont,
2839
+ iconLibrary: payload.iconLibrary,
2840
+ radius: payload.radius,
2841
+ presetCode
2842
+ });
2843
+ }
2844
+ function toCliPresetError(error) {
2845
+ const message = error.message;
2846
+ if (/Unsupported preset payload version/i.test(message)) {
2847
+ return new CliError(
2848
+ `Invalid preset: ${message} Upgrade the Wensity CLI (wensity@latest) and try again.`
2849
+ );
2850
+ }
2851
+ return new CliError(`Invalid preset: ${message}`);
2852
+ }
1850
2853
 
1851
2854
  // src/preset-init-writes.ts
1852
2855
  import { mkdir as mkdir3, writeFile as writeFile3 } from "fs/promises";
@@ -1878,34 +2881,29 @@ async function commitPresetInitWrites(input) {
1878
2881
  // src/style-css.ts
1879
2882
  var ROOT_SELECTOR = ":root";
1880
2883
  var DARK_SELECTOR = ".dark";
1881
- function applyStyleToPrimitiveCss(block, styleInput) {
1882
- const style = normalizeStyleConfig(styleInput);
2884
+ var SHARED_TOKEN_KEYS = ["radius", "fontSans", "fontDisplay", "fontMono"];
2885
+ var MODE_TOKEN_KEYS = Object.keys(RESOLVED_TOKEN_CSS_VARS).filter(
2886
+ (key) => !SHARED_TOKEN_KEYS.includes(key)
2887
+ );
2888
+ function applyResolvedTokensToPrimitiveCss(block, tokens) {
1883
2889
  let next = block;
1884
- const radiusValue = RADIUS_PRESET_CSS[style.radius];
1885
- next = replaceDeclInSelector(next, ROOT_SELECTOR, "--primitive-radius", radiusValue);
1886
- next = replaceDeclInSelector(next, DARK_SELECTOR, "--primitive-radius", radiusValue);
1887
- for (const role of FONT_ROLE_KEYS) {
1888
- const cssVar = `--primitive-font-${role}`;
1889
- const cssValue = expandFontRole(style.fonts[role], role);
1890
- next = replaceDeclInSelector(next, ROOT_SELECTOR, cssVar, cssValue);
1891
- next = replaceDeclInSelector(next, DARK_SELECTOR, cssVar, cssValue);
1892
- }
1893
- if (style.colors?.light) {
1894
- for (const key of CONTROL_SOLID_COLOR_KEYS) {
1895
- const value = style.colors.light[key];
1896
- if (value === void 0) continue;
1897
- next = replaceDeclInSelector(next, ROOT_SELECTOR, CONTROL_SOLID_CSS_VARS[key], value);
1898
- }
2890
+ for (const key of SHARED_TOKEN_KEYS) {
2891
+ const cssVar = RESOLVED_TOKEN_CSS_VARS[key];
2892
+ const value = tokens[key];
2893
+ next = replaceDeclInSelector(next, ROOT_SELECTOR, cssVar, value);
2894
+ next = replaceDeclInSelector(next, DARK_SELECTOR, cssVar, value);
1899
2895
  }
1900
- if (style.colors?.dark) {
1901
- for (const key of CONTROL_SOLID_COLOR_KEYS) {
1902
- const value = style.colors.dark[key];
1903
- if (value === void 0) continue;
1904
- next = replaceDeclInSelector(next, DARK_SELECTOR, CONTROL_SOLID_CSS_VARS[key], value);
1905
- }
2896
+ for (const key of MODE_TOKEN_KEYS) {
2897
+ const cssVar = RESOLVED_TOKEN_CSS_VARS[key];
2898
+ next = replaceDeclInSelector(next, ROOT_SELECTOR, cssVar, tokens.light[key]);
2899
+ next = replaceDeclInSelector(next, DARK_SELECTOR, cssVar, tokens.dark[key]);
1906
2900
  }
1907
2901
  return next;
1908
2902
  }
2903
+ function applyStyleToPrimitiveCss(block, styleInput) {
2904
+ const style = normalizeProjectStyle(styleInput);
2905
+ return applyResolvedTokensToPrimitiveCss(block, resolveTokensForStyle(style));
2906
+ }
1909
2907
  function replaceDeclInSelector(css, selector, property, value) {
1910
2908
  const block = findTopLevelRule(css, selector);
1911
2909
  if (!block) return css;
@@ -2131,7 +3129,7 @@ async function prepareInstall(projectRoot, config, payload, options = {}) {
2131
3129
  const files = [];
2132
3130
  const componentContents = [];
2133
3131
  const iconSwaps = [];
2134
- const style = normalizeStyleConfig(config.style);
3132
+ const style = normalizeProjectStyle(config.style);
2135
3133
  const isPrimitiveCategory = isPrimitivePayloadCategory(payload.category);
2136
3134
  const payloadHelperNeeds = normalizePayloadHelperNeeds(
2137
3135
  "helperNeeds" in payload ? payload.helperNeeds : void 0
@@ -2188,8 +3186,10 @@ async function prepareInstall(projectRoot, config, payload, options = {}) {
2188
3186
  }
2189
3187
  const iconDependencySwap = aggregateIconDependencySwaps(iconSwaps);
2190
3188
  const declaredAfterSwap = applyIconDependencySwap(payload.dependencies ?? [], iconDependencySwap);
3189
+ const fonts = curatedFontsFromStyle(style);
3190
+ const fontPlan = planFontInstall(fonts.headingFont, fonts.bodyFont);
2191
3191
  const dependencies = collectDependencies({
2192
- declared: declaredAfterSwap,
3192
+ declared: [...declaredAfterSwap, ...fontPlan.packages],
2193
3193
  source: componentContents.join("\n"),
2194
3194
  needsUtility: helperNeeds.utils
2195
3195
  });
@@ -2198,7 +3198,8 @@ async function prepareInstall(projectRoot, config, payload, options = {}) {
2198
3198
  files,
2199
3199
  dependencies,
2200
3200
  ...cssFile ? { cssFile } : {},
2201
- ...isPrimitiveCategory ? { iconDependencySwap } : {}
3201
+ ...isPrimitiveCategory ? { iconDependencySwap } : {},
3202
+ ...fontPlan.packages.length > 0 ? { fontPackages: fontPlan.packages } : {}
2202
3203
  };
2203
3204
  }
2204
3205
  async function planPrimitiveCssFile(projectRoot, config, payload) {
@@ -2207,13 +3208,16 @@ async function planPrimitiveCssFile(projectRoot, config, payload) {
2207
3208
  }
2208
3209
  let primitiveCss = payload.primitiveCss.trim();
2209
3210
  if (!primitiveCss) return void 0;
2210
- const style = normalizeStyleConfig(config.style);
3211
+ const style = normalizeProjectStyle(config.style);
2211
3212
  primitiveCss = applyStyleToPrimitiveCss(primitiveCss, style).trim();
2212
3213
  const cssRelative = config.tailwind?.css;
2213
3214
  if (!cssRelative) return void 0;
2214
3215
  const absolutePath = resolveInside(projectRoot, cssRelative);
2215
3216
  const existing = await fileExists(absolutePath) ? await readFile4(absolutePath, "utf8") : "";
2216
- const next = upsertPrimitiveCssBlock(existing, primitiveCss);
3217
+ const withTokens = upsertPrimitiveCssBlock(existing, primitiveCss);
3218
+ const fonts = curatedFontsFromStyle(style);
3219
+ const fontPlan = planFontInstall(fonts.headingFont, fonts.bodyFont);
3220
+ const next = upsertFontImportBlock(withTokens, fontPlan.block);
2217
3221
  if (next === existing) return void 0;
2218
3222
  return {
2219
3223
  absolutePath,
@@ -2489,6 +3493,9 @@ async function initWithPreset(context, options) {
2489
3493
  presetCode: options.presetCode,
2490
3494
  presetFile: options.presetFile
2491
3495
  });
3496
+ const fonts = curatedFontsFromStyle(style);
3497
+ const fontPlan = planFontInstall(fonts.headingFont, fonts.bodyFont);
3498
+ const installDependencies = [...options.dependencies, ...fontPlan.packages];
2492
3499
  const configPath = join6(context.cwd, CONFIG_FILE_NAME);
2493
3500
  const configExists = await fileExists(configPath);
2494
3501
  let config;
@@ -2531,8 +3538,11 @@ async function initWithPreset(context, options) {
2531
3538
  }
2532
3539
  const cssAbsolute = resolveInside(context.cwd, cssRelative);
2533
3540
  const existingCss = await fileExists(cssAbsolute) ? await readFile5(cssAbsolute, "utf8") : "";
2534
- const nextCss = upsertPrimitiveCssBlock(existingCss, primitiveCss);
3541
+ const withTokens = upsertPrimitiveCssBlock(existingCss, primitiveCss);
3542
+ const nextCss = upsertFontImportBlock(withTokens, fontPlan.block);
2535
3543
  const cssChanged = nextCss !== existingCss;
3544
+ const installedPackages = readInstalledPackageNames(context.cwd);
3545
+ const unusedFontWarning = installedPackages ? warnUnusedFontPackageMessage(fonts.headingFont, fonts.bodyFont, installedPackages) : void 0;
2536
3546
  if (options.dryRun) {
2537
3547
  printTitle("Project initialization preview (preset)");
2538
3548
  print(action("Would write:"));
@@ -2541,7 +3551,11 @@ async function initWithPreset(context, options) {
2541
3551
  print(formatList(planned));
2542
3552
  print(action(`Preset: ${presetCode.slice(0, 24)}\u2026`));
2543
3553
  print(action(`Style: icon=${style.iconLibrary} radius=${style.radius}`));
2544
- if (options.install) print(action(`Would install: ${options.dependencies.join(" ")}`));
3554
+ if (fontPlan.packages.length > 0) {
3555
+ print(action(`Fonts: ${fontPlan.packages.join(" ")}`));
3556
+ }
3557
+ if (options.install) print(action(`Would install: ${installDependencies.join(" ")}`));
3558
+ if (unusedFontWarning) warn(unusedFontWarning);
2545
3559
  return;
2546
3560
  }
2547
3561
  const shouldWriteUtility = options.force || !await fileExists(utilityPath);
@@ -2562,7 +3576,7 @@ async function initWithPreset(context, options) {
2562
3576
  }
2563
3577
  });
2564
3578
  if (options.install) {
2565
- runInstall(context.cwd, missingDependencies(context.cwd, options.dependencies));
3579
+ runInstall(context.cwd, missingDependencies(context.cwd, installDependencies));
2566
3580
  }
2567
3581
  printTitle("Project initialized");
2568
3582
  const created = [
@@ -2576,7 +3590,11 @@ async function initWithPreset(context, options) {
2576
3590
  } else {
2577
3591
  created.push(action(`CSS tokens unchanged: ${toPosixPath(cssRelative)}`));
2578
3592
  }
3593
+ if (fontPlan.packages.length > 0) {
3594
+ created.push(success(`fonts: ${fontPlan.packages.join(", ")}`));
3595
+ }
2579
3596
  print(panel("Created", created));
3597
+ if (unusedFontWarning) warn(unusedFontWarning);
2580
3598
  print(nextSteps([
2581
3599
  "Browse available components: npx wensity list",
2582
3600
  "Install a primitive: npx wensity add button"
@@ -2721,9 +3739,11 @@ Usage: wensity ${updateMode ? "update" : "add"} <slug>${resourceType === "block"
2721
3739
  await assertNoConflicts(plannedInstallFiles, force || dryRun);
2722
3740
  const plannedFiles = summarizeFiles(loaded.projectRoot, plannedInstallFiles);
2723
3741
  const missing = missingDependencies(loaded.projectRoot, prepared.dependencies);
2724
- const style = normalizeStyleConfig(loaded.config.style);
3742
+ const style = normalizeProjectStyle(loaded.config.style);
3743
+ const fonts = curatedFontsFromStyle(style);
2725
3744
  const installedPackages = readInstalledPackageNames(loaded.projectRoot);
2726
3745
  const oppositeIconWarning = installedPackages && prepared.iconDependencySwap ? warnOppositeIconPackageMessage(style.iconLibrary, installedPackages) : void 0;
3746
+ const unusedFontWarning = installedPackages ? warnUnusedFontPackageMessage(fonts.headingFont, fonts.bodyFont, installedPackages) : void 0;
2727
3747
  if (dryRun) {
2728
3748
  printTitle(updateMode ? "Update preview" : "Install preview");
2729
3749
  print(action("Would write:"));
@@ -2733,6 +3753,7 @@ Usage: wensity ${updateMode ? "update" : "add"} <slug>${resourceType === "block"
2733
3753
  }
2734
3754
  if (install && missing.length > 0) print(action(`Would install: ${missing.join(" ")}`));
2735
3755
  if (oppositeIconWarning) warn(oppositeIconWarning);
3756
+ if (unusedFontWarning) warn(unusedFontWarning);
2736
3757
  return;
2737
3758
  }
2738
3759
  await writePlannedFiles(plannedInstallFiles);
@@ -2745,6 +3766,7 @@ Usage: wensity ${updateMode ? "update" : "add"} <slug>${resourceType === "block"
2745
3766
  ...plannedFiles.map((file) => success(file))
2746
3767
  ]));
2747
3768
  if (oppositeIconWarning) warn(oppositeIconWarning);
3769
+ if (unusedFontWarning) warn(unusedFontWarning);
2748
3770
  if (!install && missing.length > 0) {
2749
3771
  warn(`Dependencies were not installed: ${missing.join(" ")}`);
2750
3772
  print(nextSteps([