wensity 0.1.11 → 0.1.12

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 +164 -40
  2. package/dist/index.js +366 -61
  3. package/package.json +24 -12
  4. package/LICENSE +0 -21
package/README.md CHANGED
@@ -1,63 +1,187 @@
1
1
  # Wensity CLI
2
2
 
3
- Install open-code components and UI primitives from the Wensity registry into your project. There is no `@wensity/ui` runtime dependency and no theme provider to wire up.
3
+ The command line tool for [Wensity](https://ui.wensity.com). Install React
4
+ components, UI primitives, and shareable Tailwind CSS presets into your project
5
+ as source code you own.
6
+
7
+ There is no `@wensity/ui` runtime dependency and no theme provider to wire up.
8
+ Everything the CLI installs lands in your repository as plain files you can read,
9
+ edit, and commit.
10
+
11
+ [![npm](https://img.shields.io/npm/v/wensity)](https://www.npmjs.com/package/wensity)
12
+ [![license](https://img.shields.io/npm/l/wensity)](./LICENSE)
13
+
14
+ [Docs](https://ui.wensity.com/docs/cli) ·
15
+ [Components](https://ui.wensity.com/components) ·
16
+ [Preset studio](https://ui.wensity.com/create-preset) ·
17
+ [Registry](https://github.com/wensity/registry) ·
18
+ [wensity.com](https://wensity.com)
19
+
20
+ ## Quick start
4
21
 
5
22
  ```bash
6
- # Initialise (defaults: Tabler icons, medium radius)
23
+ # New project, with an app scaffold
24
+ npx wensity@latest init --template next
25
+
26
+ # Existing project
7
27
  npx wensity@latest init
8
28
 
9
- # Or apply a shareable preset from https://ui.wensity.com/create-preset
29
+ # Add a component
30
+ npx wensity@latest add button
31
+ npx wensity@latest add liquid-multimodal-input
32
+ ```
33
+
34
+ Requires Node.js 20 or later. Works with npm, pnpm, yarn, and bun. The CLI
35
+ picks your package manager from the client that invoked it, falling back to the
36
+ lockfile in your project root, and installs component dependencies with it.
37
+
38
+ ## Presets
39
+
40
+ A preset is a shareable code that captures a full visual style: color palette,
41
+ border radius, fonts, and control colors. Build one visually in the
42
+ [preset studio](https://ui.wensity.com/create-preset), copy the `wsty1` code,
43
+ and apply it.
44
+
45
+ ```bash
46
+ # New project, styled from the start
10
47
  npx wensity@latest init --preset <wsty1-code>
11
48
  npx wensity@latest init --preset <wsty1-code> --template next
12
49
 
13
- # Existing project — style + CSS only
50
+ # Existing project, style and CSS only
14
51
  npx wensity@latest apply --preset <wsty1-code>
15
52
 
16
- # Inspect a code before applying
53
+ # See what a code contains before applying it
17
54
  npx wensity@latest preset inspect <wsty1-code>
18
-
19
- # Install a primitive or component
20
- npx wensity@latest add button
21
- npx wensity@latest add liquid-multimodal-input
55
+ npx wensity@latest preset inspect <wsty1-code> --json
22
56
  ```
23
57
 
24
- ## Presets (primitives)
58
+ Applying a preset writes two things:
59
+
60
+ 1. Style settings into `wensity.json` (icon library, radius, fonts, control
61
+ colors).
62
+ 2. A delimited `--primitive-*` CSS block in your Tailwind CSS file.
25
63
 
26
- Generate a preset in the [Create preset studio](https://ui.wensity.com/create-preset) (guide: [`/docs/create-preset`](https://ui.wensity.com/docs/create-preset)). Then run `init --preset` (new project) or `apply --preset` (existing). Settings land in `wensity.json` (`style.iconLibrary`, radius, fonts, control-solid colors) and a delimited `--primitive-*` CSS block in your Tailwind CSS file.
64
+ Later `add` and `update` runs read that style automatically, so newly installed
65
+ components match what is already there. `apply` rewrites the CSS block in place
66
+ and never touches your component source.
27
67
 
28
- Later `wensity add <primitive>` / `wensity update <primitive>` reads that style automatically:
68
+ Plain `init` with no `--preset` refuses to overwrite an existing `wensity.json`
69
+ unless you pass `--force`. To restyle an existing project, prefer
70
+ `apply --preset`.
29
71
 
30
- - Colors, radius, and fonts re-apply into the CSS block
31
- - Tabler ↔ Lucide icon imports transform at install time for **UI primitives only**
32
- - Mandatory dependency swap installs exactly one icon package when icons are used
72
+ ## Icon libraries
33
73
 
34
- Plain `init` (no `--preset`) refuses to overwrite an existing `wensity.json` unless you pass `--force`. Prefer `apply --preset` for style-only updates; `init --preset` also merges `style` (+ CSS) without rewriting installed component source.
74
+ Wensity primitives are authored against [Tabler](https://tabler.io/icons). If
75
+ your project uses [Lucide](https://lucide.dev), set that in your preset or
76
+ `wensity.json` and the CLI rewrites icon imports at install time for UI
77
+ primitives. It installs exactly one icon package, whichever you chose.
78
+
79
+ This transform applies to UI primitives only. Larger composed components ship
80
+ with their icons as authored.
35
81
 
36
82
  ## Commands
37
83
 
38
- | Command | Purpose |
39
- |---|---|
40
- | `wensity init [--preset <code>] [--template next\|vite\|astro\|react-router]` | Create / merge project config |
41
- | `wensity apply --preset <code>` | Apply style + CSS to an existing project |
42
- | `wensity preset inspect <code>` | Decode a preset code |
43
- | `wensity login` | Save a dashboard API token for Pro |
44
- | `wensity logout` | Clear credentials |
45
- | `wensity whoami` | Show current user / plan |
46
- | `wensity list` | List registry entries |
47
- | `wensity add <slug>` | Install source (skips existing files) |
48
- | `wensity update <slug>` | Re-download and overwrite local source |
49
- | `wensity version` | Print `0.1.9` |
50
-
51
- Premium components and blocks require an API token from the Wensity dashboard. Tokens are stored at `~/.config/wensity/credentials.json` with `0600` permissions.
52
-
53
- ## CLI vs shadcn
54
-
55
- | | Wensity CLI | Public shadcn registry (`@wensity/*`) |
56
- |---|---|---|
57
- | Stored presets (`wensity.json` style) | Yes | No |
58
- | Lucide delivery | Yes (CLI transform) | No — Tabler-canonical payloads |
84
+ | Command | What it does |
85
+ | --- | --- |
86
+ | `wensity init` | Create `wensity.json` and local helper files. Optional `--preset` applies style and CSS, optional `--template` scaffolds an app |
87
+ | `wensity apply --preset <code>` | Apply a preset to an existing project, style and CSS only |
88
+ | `wensity preset inspect <code>` | Decode and print a preset code, add `--json` for machine output |
89
+ | `wensity list` | List registry components and blocks |
90
+ | `wensity add <slug...>` | Download component source into your project, skipping files that already exist |
91
+ | `wensity update <slug...>` | Re-download components and overwrite the local copies |
92
+ | `wensity login` | Save a Wensity API token for Pro component access |
93
+ | `wensity logout` | Remove saved credentials |
94
+ | `wensity whoami` | Verify the saved token and print the account and plan |
95
+ | `wensity version` | Print the CLI version |
96
+
97
+ Useful flags:
98
+
99
+ | Flag | Effect |
100
+ | --- | --- |
101
+ | `--template next\|vite\|astro\|react-router` | Scaffold a new app during `init` |
102
+ | `--type component\|block` | Narrow `list`, `add`, and `update` |
103
+ | `--api-url <url>` | Point at a different Wensity origin, defaults to `https://ui.wensity.com` |
104
+ | `--force` | Allow `init` to overwrite an existing `wensity.json` |
105
+ | `--json` | Machine readable output where supported |
106
+
107
+ ## Authentication
108
+
109
+ Free components need no account. Wensity Pro components and blocks require an
110
+ API token from the [dashboard](https://ui.wensity.com/dashboard).
111
+
112
+ ```bash
113
+ npx wensity@latest login
114
+ npx wensity@latest whoami
115
+ ```
116
+
117
+ The token is written to `~/.config/wensity/credentials.json` with `0600`
118
+ permissions. The location honours `XDG_CONFIG_HOME`, and can be overridden with
119
+ `WENSITY_CONFIG_HOME` or `WENSITY_CREDENTIALS_FILE`. Access is checked server
120
+ side on every request, so the token is the only secret involved and it never
121
+ leaves your machine except as a bearer header to the Wensity API.
122
+
123
+ Run `wensity logout` to delete it.
124
+
125
+ ## Wensity CLI or the shadcn CLI
126
+
127
+ Free Wensity components are published to a public shadcn registry, so you can
128
+ install them with either tool. Use whichever fits your project.
129
+
130
+ | | Wensity CLI | shadcn CLI with `@wensity/*` |
131
+ | --- | --- | --- |
59
132
  | Full editable source | Yes | Yes |
60
- | `@wensity/ui` runtime | Never | Never |
61
- | Premium `/components` | Token + license | Not exported |
133
+ | `@wensity/ui` runtime dependency | Never | Never |
134
+ | Stored presets and `wensity.json` style | Yes | No |
135
+ | Lucide delivery | Yes, via icon transform | No, payloads are Tabler canonical |
136
+ | Project scaffolding (`--template`) | Yes | No |
137
+ | Wensity Pro components | Yes, with a token | Not published |
138
+
139
+ The public registry lives at [wensity/registry](https://github.com/wensity/registry):
140
+
141
+ ```bash
142
+ npx shadcn@latest add @wensity/button
143
+ ```
144
+
145
+ ## Configuration
146
+
147
+ `wensity.json` sits at your project root:
148
+
149
+ ```json
150
+ {
151
+ "$schema": "https://ui.wensity.com/schema/wensity.json",
152
+ "version": 1,
153
+ "registry": "https://ui.wensity.com/api/registry",
154
+ "components": "src/components/wensity",
155
+ "utils": "src/lib/utils",
156
+ "tailwind": {
157
+ "config": "tailwind.config.ts",
158
+ "css": "src/app/globals.css"
159
+ },
160
+ "aliases": {
161
+ "components": "@/components/wensity",
162
+ "utils": "@/lib/utils"
163
+ },
164
+ "style": {
165
+ "iconLibrary": "tabler",
166
+ "radius": "medium"
167
+ }
168
+ }
169
+ ```
170
+
171
+ `init` also records a `style` block with fonts and control colors, which is
172
+ what `--preset` overwrites. Paths and aliases are read on every `add` and
173
+ `update`, so you can relocate the components directory and the CLI follows.
174
+
175
+ ## Source and issues
176
+
177
+ The CLI is open source at [wensity/cli](https://github.com/wensity/cli), MIT
178
+ licensed. Issues and pull requests are welcome there.
179
+
180
+ For a bug in a component's source rather than the CLI itself, open an issue on
181
+ [wensity/registry](https://github.com/wensity/registry) instead.
182
+
183
+ ## License
184
+
185
+ MIT. See [LICENSE](./LICENSE).
62
186
 
63
- Docs: [CLI reference](https://ui.wensity.com/docs/cli) · [Create preset](https://ui.wensity.com/docs/create-preset) · [Primitives](https://ui.wensity.com/docs/primitives)
187
+ Copyright (c) 2026 Wensity Private Limited.
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 mkdir6, readFile as readFile6, writeFile as writeFile6 } from "fs/promises";
4
+ import { mkdir as mkdir6, readFile as readFile6, stat, writeFile as writeFile6 } from "fs/promises";
5
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.11";
9
+ var CLI_VERSION = "0.1.12";
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";
@@ -25,8 +25,8 @@ Usage:
25
25
  wensity logout
26
26
  wensity whoami [--json]
27
27
  wensity list [--type component|block] [options]
28
- wensity add <slug> [--type component|block] [options]
29
- wensity update <slug> [--type component|block] [options]
28
+ wensity add <slug...> [--type component|block] [options]
29
+ wensity update <slug...> [--type component|block] [options]
30
30
  wensity version
31
31
 
32
32
  Commands:
@@ -37,8 +37,8 @@ Commands:
37
37
  logout Remove saved CLI credentials
38
38
  whoami Verify the saved token and print the authenticated account
39
39
  list List registry components or blocks
40
- add Download a component or block source file into this project
41
- update Re-download a component or block and overwrite the local copy
40
+ add Download one or more component/block source files into this project
41
+ update Re-download one or more components/blocks and overwrite the local copies
42
42
  version Print the CLI version
43
43
 
44
44
  Options:
@@ -49,6 +49,7 @@ Options:
49
49
  --preset <code> Apply a shareable Wensity style preset (wsty1.\u2026); merges style when config exists
50
50
  --preset-file <path> Read a preset code or JSON payload from a file
51
51
  --template <name> Scaffold a new app before init: next, vite, astro, react-router
52
+ --package-manager <pm> Force npm, pnpm, yarn, or bun instead of auto-detection
52
53
  --force Overwrite generated files when adding or initializing
53
54
  --no-install Write files without running the package manager
54
55
  --dry-run Print planned writes and installs without changing files
@@ -63,7 +64,7 @@ Preset notes:
63
64
  Plain init refuses an existing wensity.json unless --force
64
65
  init --preset merges style + primitive CSS; does not rewrite installed source
65
66
  init --template writes a minimal app scaffold in the current directory before Wensity files
66
- add/update read style.iconLibrary for UI primitives only (Tabler|Lucide)
67
+ add/update accept several slugs at once and read style.iconLibrary for UI primitives only (Tabler|Lucide)
67
68
  Invalid/expired/unsupported codes exit non-zero with an actionable message
68
69
  `;
69
70
 
@@ -126,7 +127,16 @@ function hasFlag(args, name) {
126
127
  return args.options.get(name) === true;
127
128
  }
128
129
  function expectsValue(name) {
129
- return ["api-url", "registry", "token", "type", "preset", "preset-file", "template"].includes(name);
130
+ return [
131
+ "api-url",
132
+ "registry",
133
+ "token",
134
+ "type",
135
+ "preset",
136
+ "preset-file",
137
+ "template",
138
+ "package-manager"
139
+ ].includes(name);
130
140
  }
131
141
  function normalizeOptionName(name) {
132
142
  if (name === "dry") return "dry-run";
@@ -2521,6 +2531,91 @@ function resolvedTokensToCssVars(tokens, mode) {
2521
2531
  [RESOLVED_TOKEN_CSS_VARS.fontMono]: tokens.fontMono
2522
2532
  };
2523
2533
  }
2534
+ function resolvedNamedBaseSemanticCssVars(baseColor, mode) {
2535
+ if (baseColor === "inherit") return {};
2536
+ const seeds = BASE_COLOR_PALETTE[baseColor]?.[mode];
2537
+ if (!seeds) return {};
2538
+ if (mode === "light") {
2539
+ return {
2540
+ // Soft family wash over white — the main light-mode tell (slate vs stone).
2541
+ "--background": `color-mix(in oklab, ${seeds.surfaceHover} 62%, white)`,
2542
+ "--foreground": `color-mix(in oklab, ${seeds.textSecondary} 38%, black)`,
2543
+ "--surface": seeds.surfaceElevated,
2544
+ "--surface-muted": seeds.surfaceHover,
2545
+ "--muted": seeds.surfaceHover,
2546
+ "--muted-foreground": seeds.textSecondary,
2547
+ "--border": seeds.borderSubtle,
2548
+ "--border-strong": `color-mix(in oklab, ${seeds.textSecondary} 30%, transparent)`
2549
+ };
2550
+ }
2551
+ return {
2552
+ // Canvas sits under elevated cards (darker than surfaceElevated).
2553
+ "--background": `color-mix(in oklab, ${seeds.surfaceElevated} 58%, black)`,
2554
+ "--foreground": `color-mix(in oklab, ${seeds.textSecondary} 22%, white)`,
2555
+ "--surface": seeds.surfaceElevated,
2556
+ "--surface-muted": seeds.surfaceHover,
2557
+ "--muted": seeds.surfaceHover,
2558
+ "--muted-foreground": seeds.textSecondary,
2559
+ "--border": seeds.borderSubtle,
2560
+ "--border-strong": `color-mix(in oklab, ${seeds.textSecondary} 38%, transparent)`
2561
+ };
2562
+ }
2563
+ var INHERIT_BASE_SEMANTIC_CSS_VARS = {
2564
+ light: {
2565
+ "--background": "#fafafa",
2566
+ "--foreground": "#0a0a0a",
2567
+ "--surface": "#ffffff",
2568
+ "--surface-muted": "#f4f4f5",
2569
+ "--muted": "#f4f4f5",
2570
+ "--muted-foreground": "#52525b",
2571
+ "--border": "rgba(10, 10, 10, 0.08)",
2572
+ "--border-strong": "rgba(10, 10, 10, 0.16)"
2573
+ },
2574
+ dark: {
2575
+ "--background": "#0a0a0b",
2576
+ "--foreground": "#f5f5f6",
2577
+ "--surface": "#111113",
2578
+ "--surface-muted": "#18181b",
2579
+ "--muted": "#1c1c1f",
2580
+ "--muted-foreground": "#a1a1aa",
2581
+ "--border": "rgba(255, 255, 255, 0.08)",
2582
+ "--border-strong": "rgba(255, 255, 255, 0.14)"
2583
+ }
2584
+ };
2585
+ function exportedBaseSemanticCssVars(baseColor, mode) {
2586
+ const named = resolvedNamedBaseSemanticCssVars(baseColor, mode);
2587
+ return Object.keys(named).length > 0 ? named : { ...INHERIT_BASE_SEMANTIC_CSS_VARS[mode] };
2588
+ }
2589
+ var BASE_SEMANTIC_THEME_UTILITIES = {
2590
+ "--color-background": "var(--background)",
2591
+ "--color-foreground": "var(--foreground)",
2592
+ "--color-surface": "var(--surface)",
2593
+ "--color-surface-muted": "var(--surface-muted)",
2594
+ "--color-muted": "var(--muted)",
2595
+ "--color-muted-foreground": "var(--muted-foreground)",
2596
+ "--color-border": "var(--border)",
2597
+ "--color-border-strong": "var(--border-strong)"
2598
+ };
2599
+ var BASE_CSS_START = "/* \u2500\u2500 Wensity Base Tokens \u2014 generated by the Wensity CLI.\n Edit these values to retheme your app; re-running `wensity init --preset`\n replaces this block. \u2500\u2500 */";
2600
+ var BASE_CSS_END = "/* \u2500\u2500 End Wensity Base Tokens \u2500\u2500 */";
2601
+ function formatBaseDeclarations(entries, indent = " ") {
2602
+ return Object.entries(entries).map(([name, value]) => `${indent}${name}: ${value};`).join("\n");
2603
+ }
2604
+ function buildBaseCssBlock(baseColor = "inherit") {
2605
+ return [
2606
+ BASE_CSS_START,
2607
+ "@theme inline {",
2608
+ formatBaseDeclarations(BASE_SEMANTIC_THEME_UTILITIES),
2609
+ "}",
2610
+ ":root {",
2611
+ formatBaseDeclarations(exportedBaseSemanticCssVars(baseColor, "light")),
2612
+ "}",
2613
+ ".dark {",
2614
+ formatBaseDeclarations(exportedBaseSemanticCssVars(baseColor, "dark")),
2615
+ "}",
2616
+ BASE_CSS_END
2617
+ ].join("\n");
2618
+ }
2524
2619
 
2525
2620
  // src/paths.ts
2526
2621
  import { dirname, isAbsolute, relative, resolve, sep } from "path";
@@ -2608,6 +2703,12 @@ function resolveTokensForStyle(style) {
2608
2703
  }
2609
2704
  return resolveStyleConfig(normalizeStyleConfig(style));
2610
2705
  }
2706
+ function baseColorFromStyle(style) {
2707
+ if (isCanonicalStyleShape(style)) {
2708
+ return normalizeStyleConfigCanonical(style).baseColor;
2709
+ }
2710
+ return "inherit";
2711
+ }
2611
2712
  function curatedFontsFromStyle(style) {
2612
2713
  if (isCanonicalStyleShape(style)) {
2613
2714
  const normalized = normalizeStyleConfigCanonical(style);
@@ -3475,15 +3576,34 @@ function rewriteStrokeProps(content, localBindings) {
3475
3576
  }
3476
3577
 
3477
3578
  // src/package-manager.ts
3478
- import { existsSync as existsSync2, readFileSync } from "fs";
3579
+ import { existsSync as existsSync2, readFileSync, writeFileSync } from "fs";
3479
3580
  import { spawnSync } from "child_process";
3480
3581
  import { join as join4 } from "path";
3481
- function detectPackageManager(projectRoot) {
3582
+ var PACKAGE_MANAGERS = ["npm", "pnpm", "yarn", "bun"];
3583
+ function parsePackageManager(value) {
3584
+ if (!value) return void 0;
3585
+ const normalized = value.trim().toLowerCase();
3586
+ if (isPackageManager(normalized)) return normalized;
3587
+ throw new CliError(
3588
+ `Unsupported package manager "${value}". Use one of: ${PACKAGE_MANAGERS.join(", ")}.`
3589
+ );
3590
+ }
3591
+ function packageManagerFromUserAgent(env = process.env) {
3592
+ const agent = env.npm_config_user_agent;
3593
+ if (!agent) return void 0;
3594
+ const first = agent.trim().split(/\s+/)[0] ?? "";
3595
+ const name = first.split("/")[0]?.toLowerCase();
3596
+ return name && isPackageManager(name) ? name : void 0;
3597
+ }
3598
+ function detectPackageManager(projectRoot, env = process.env) {
3482
3599
  if (existsSync2(join4(projectRoot, "pnpm-lock.yaml"))) return "pnpm";
3483
3600
  if (existsSync2(join4(projectRoot, "bun.lockb")) || existsSync2(join4(projectRoot, "bun.lock"))) return "bun";
3484
3601
  if (existsSync2(join4(projectRoot, "yarn.lock"))) return "yarn";
3485
3602
  if (existsSync2(join4(projectRoot, "package-lock.json"))) return "npm";
3486
- return "npm";
3603
+ return packageManagerFromUserAgent(env) ?? "npm";
3604
+ }
3605
+ function isPackageManager(value) {
3606
+ return PACKAGE_MANAGERS.includes(value);
3487
3607
  }
3488
3608
  function missingDependencies(projectRoot, dependencies) {
3489
3609
  const installed = readInstalledPackageNames(projectRoot);
@@ -3503,15 +3623,34 @@ function readInstalledPackageNames(projectRoot) {
3503
3623
  })
3504
3624
  );
3505
3625
  }
3506
- function runInstall(projectRoot, dependencies) {
3626
+ var YARN_CONFIG_FILE = ".yarnrc.yml";
3627
+ function prepareProjectPackageManager(projectRoot, options = {}) {
3628
+ if (resolvePackageManager(projectRoot, options) !== "yarn") return [];
3629
+ const configPath = join4(projectRoot, YARN_CONFIG_FILE);
3630
+ if (existsSync2(configPath)) return [];
3631
+ if (!options.dryRun) writeFileSync(configPath, "nodeLinker: node-modules\n", "utf8");
3632
+ return [YARN_CONFIG_FILE];
3633
+ }
3634
+ function runProjectInstall(projectRoot, options = {}) {
3635
+ const packageJsonPath = join4(projectRoot, "package.json");
3636
+ if (!existsSync2(packageJsonPath)) return;
3637
+ const packageManager = resolvePackageManager(projectRoot, options);
3638
+ runPackageManager(projectRoot, packageManager, ["install"]);
3639
+ }
3640
+ function resolvePackageManager(projectRoot, options = {}) {
3641
+ return options.packageManager ?? detectPackageManager(projectRoot, options.env);
3642
+ }
3643
+ function runInstall(projectRoot, dependencies, options = {}) {
3507
3644
  if (dependencies.length === 0) return;
3508
3645
  const packageJsonPath = join4(projectRoot, "package.json");
3509
3646
  if (!existsSync2(packageJsonPath)) {
3510
3647
  warn(`No package.json found. Install these dependencies manually: ${dependencies.join(" ")}`);
3511
3648
  return;
3512
3649
  }
3513
- const packageManager = detectPackageManager(projectRoot);
3514
- const args = installArgs(packageManager, dependencies);
3650
+ const packageManager = resolvePackageManager(projectRoot, options);
3651
+ runPackageManager(projectRoot, packageManager, installArgs(packageManager, dependencies));
3652
+ }
3653
+ function runPackageManager(projectRoot, packageManager, args) {
3515
3654
  const result = spawnSync(packageManager, args, {
3516
3655
  cwd: projectRoot,
3517
3656
  stdio: "inherit",
@@ -3775,6 +3914,12 @@ async function commitPresetInitWrites(input) {
3775
3914
  // src/project-templates.ts
3776
3915
  import { mkdir as mkdir4, readFile as readFile4, writeFile as writeFile4 } from "fs/promises";
3777
3916
  import { dirname as dirname5, join as join5 } from "path";
3917
+ function templateCss() {
3918
+ return `@import "tailwindcss";
3919
+
3920
+ ${buildBaseCssBlock()}
3921
+ `;
3922
+ }
3778
3923
  var PROJECT_TEMPLATE_IDS = ["next", "vite", "astro", "react-router"];
3779
3924
  function parseProjectTemplate(value) {
3780
3925
  if (!value) return void 0;
@@ -3886,11 +4031,14 @@ var templateDefinitions = {
3886
4031
  start: "next start"
3887
4032
  },
3888
4033
  dependencies: {
4034
+ "clsx": "^2.1.1",
3889
4035
  "next": "^16.2.4",
3890
4036
  "react": "^19.2.4",
3891
- "react-dom": "^19.2.4"
4037
+ "react-dom": "^19.2.4",
4038
+ "tailwind-merge": "^3.3.1"
3892
4039
  },
3893
4040
  devDependencies: {
4041
+ "@tailwindcss/postcss": "^4.0.0",
3894
4042
  "@types/node": "^22.10.2",
3895
4043
  "@types/react": "^19.0.0",
3896
4044
  "@types/react-dom": "^19.0.0",
@@ -3899,7 +4047,18 @@ var templateDefinitions = {
3899
4047
  }
3900
4048
  })
3901
4049
  },
3902
- { path: "src/app/globals.css", content: '@import "tailwindcss";\n' },
4050
+ {
4051
+ path: "postcss.config.mjs",
4052
+ content: `const config = {
4053
+ plugins: {
4054
+ "@tailwindcss/postcss": {},
4055
+ },
4056
+ };
4057
+
4058
+ export default config;
4059
+ `
4060
+ },
4061
+ { path: "src/app/globals.css", content: templateCss() },
3903
4062
  {
3904
4063
  path: "src/app/layout.tsx",
3905
4064
  content: `import type { ReactNode } from "react";
@@ -3961,11 +4120,14 @@ export default function RootLayout({ children }: { children: ReactNode }) {
3961
4120
  preview: "vite preview"
3962
4121
  },
3963
4122
  dependencies: {
4123
+ "clsx": "^2.1.1",
3964
4124
  "vite": "^7.3.0",
3965
4125
  "react": "^19.2.4",
3966
- "react-dom": "^19.2.4"
4126
+ "react-dom": "^19.2.4",
4127
+ "tailwind-merge": "^3.3.1"
3967
4128
  },
3968
4129
  devDependencies: {
4130
+ "@tailwindcss/vite": "^4.0.0",
3969
4131
  "@vitejs/plugin-react": "^5.1.2",
3970
4132
  "@types/react": "^19.0.0",
3971
4133
  "@types/react-dom": "^19.0.0",
@@ -3975,7 +4137,7 @@ export default function RootLayout({ children }: { children: ReactNode }) {
3975
4137
  })
3976
4138
  },
3977
4139
  { path: "index.html", content: '<div id="root"></div><script type="module" src="/src/main.tsx"></script>\n' },
3978
- { path: "src/index.css", content: '@import "tailwindcss";\n' },
4140
+ { path: "src/index.css", content: templateCss() },
3979
4141
  {
3980
4142
  path: "src/main.tsx",
3981
4143
  content: `import { createRoot } from "react-dom/client";
@@ -3990,9 +4152,10 @@ createRoot(document.getElementById("root")!).render(
3990
4152
  path: "vite.config.ts",
3991
4153
  content: `import { defineConfig } from "vite";
3992
4154
  import react from "@vitejs/plugin-react";
4155
+ import tailwindcss from "@tailwindcss/vite";
3993
4156
 
3994
4157
  export default defineConfig({
3995
- plugins: [react()],
4158
+ plugins: [react(), tailwindcss()],
3996
4159
  resolve: {
3997
4160
  alias: {
3998
4161
  "@": new URL("./src", import.meta.url).pathname,
@@ -4019,23 +4182,30 @@ export default defineConfig({
4019
4182
  dependencies: {
4020
4183
  "@astrojs/react": "^4.4.0",
4021
4184
  "astro": "^5.16.0",
4185
+ "clsx": "^2.1.1",
4022
4186
  "react": "^19.2.4",
4023
- "react-dom": "^19.2.4"
4187
+ "react-dom": "^19.2.4",
4188
+ "tailwind-merge": "^3.3.1"
4024
4189
  },
4025
4190
  devDependencies: {
4191
+ "@tailwindcss/vite": "^4.0.0",
4026
4192
  "tailwindcss": "^4.0.0",
4027
4193
  "typescript": "^5.7.2"
4028
4194
  }
4029
4195
  })
4030
4196
  },
4031
- { path: "src/styles/global.css", content: '@import "tailwindcss";\n' },
4197
+ { path: "src/styles/global.css", content: templateCss() },
4032
4198
  {
4033
4199
  path: "astro.config.mjs",
4034
4200
  content: `import { defineConfig } from "astro/config";
4035
4201
  import react from "@astrojs/react";
4202
+ import tailwindcss from "@tailwindcss/vite";
4036
4203
 
4037
4204
  export default defineConfig({
4038
4205
  integrations: [react()],
4206
+ vite: {
4207
+ plugins: [tailwindcss()],
4208
+ },
4039
4209
  });
4040
4210
  `
4041
4211
  },
@@ -4064,12 +4234,15 @@ import "../styles/global.css";
4064
4234
  preview: "vite preview"
4065
4235
  },
4066
4236
  dependencies: {
4237
+ "clsx": "^2.1.1",
4067
4238
  "vite": "^7.3.0",
4068
4239
  "react": "^19.2.4",
4069
4240
  "react-dom": "^19.2.4",
4070
- "react-router": "^7.12.0"
4241
+ "react-router": "^7.12.0",
4242
+ "tailwind-merge": "^3.3.1"
4071
4243
  },
4072
4244
  devDependencies: {
4245
+ "@tailwindcss/vite": "^4.0.0",
4073
4246
  "@vitejs/plugin-react": "^5.1.2",
4074
4247
  "@types/react": "^19.0.0",
4075
4248
  "@types/react-dom": "^19.0.0",
@@ -4079,7 +4252,7 @@ import "../styles/global.css";
4079
4252
  })
4080
4253
  },
4081
4254
  { path: "index.html", content: '<div id="root"></div><script type="module" src="/src/main.tsx"></script>\n' },
4082
- { path: "src/app/globals.css", content: '@import "tailwindcss";\n' },
4255
+ { path: "src/app/globals.css", content: templateCss() },
4083
4256
  {
4084
4257
  path: "src/main.tsx",
4085
4258
  content: `import { createRoot } from "react-dom/client";
@@ -4099,9 +4272,10 @@ createRoot(document.getElementById("root")!).render(
4099
4272
  path: "vite.config.ts",
4100
4273
  content: `import { defineConfig } from "vite";
4101
4274
  import react from "@vitejs/plugin-react";
4275
+ import tailwindcss from "@tailwindcss/vite";
4102
4276
 
4103
4277
  export default defineConfig({
4104
- plugins: [react()],
4278
+ plugins: [react(), tailwindcss()],
4105
4279
  resolve: {
4106
4280
  alias: {
4107
4281
  "@": new URL("./src", import.meta.url).pathname,
@@ -4363,6 +4537,8 @@ var ALWAYS_PRESENT_PACKAGES = /* @__PURE__ */ new Set(["react", "react-dom", "ne
4363
4537
  var OVERLAY_HELPER_PATH = "components/wensity/overlay.tsx";
4364
4538
  var PRIMITIVE_CSS_START_MARKER = "/* \u2500\u2500 Wensity Primitive Tokens";
4365
4539
  var PRIMITIVE_CSS_END_MARKER = "/* \u2500\u2500 End Wensity Primitive Tokens \u2500\u2500 */";
4540
+ var BASE_CSS_START_MARKER = "/* \u2500\u2500 Wensity Base Tokens";
4541
+ var BASE_CSS_END_MARKER = "/* \u2500\u2500 End Wensity Base Tokens \u2500\u2500 */";
4366
4542
  async function prepareInstall(projectRoot, config, payload, options = {}) {
4367
4543
  const fallbackFileName = componentFileName(payload.slug);
4368
4544
  const files = [];
@@ -4453,7 +4629,8 @@ async function planPrimitiveCssFile(projectRoot, config, payload) {
4453
4629
  if (!cssRelative) return void 0;
4454
4630
  const absolutePath = resolveInside(projectRoot, cssRelative);
4455
4631
  const existing = await fileExists(absolutePath) ? await readFile5(absolutePath, "utf8") : "";
4456
- const withTokens = upsertPrimitiveCssBlock(existing, primitiveCss);
4632
+ const withBase = upsertBaseCssBlock(existing, buildBaseCssBlock(baseColorFromStyle(style)));
4633
+ const withTokens = upsertPrimitiveCssBlock(withBase, primitiveCss);
4457
4634
  const fonts = curatedFontsFromStyle(style);
4458
4635
  const fontPlan = planFontInstall(fonts.headingFont, fonts.bodyFont);
4459
4636
  const next = upsertFontImportBlock(withTokens, fontPlan.block);
@@ -4466,19 +4643,44 @@ async function planPrimitiveCssFile(projectRoot, config, payload) {
4466
4643
  };
4467
4644
  }
4468
4645
  function upsertPrimitiveCssBlock(existingCss, block) {
4646
+ return upsertDelimitedBlock(existingCss, block, {
4647
+ startMarker: PRIMITIVE_CSS_START_MARKER,
4648
+ endMarker: PRIMITIVE_CSS_END_MARKER,
4649
+ label: "Wensity Primitive Tokens"
4650
+ });
4651
+ }
4652
+ function upsertBaseCssBlock(existingCss, block) {
4653
+ return upsertDelimitedBlock(existingCss, block, {
4654
+ startMarker: BASE_CSS_START_MARKER,
4655
+ endMarker: BASE_CSS_END_MARKER,
4656
+ label: "Wensity Base Tokens",
4657
+ insertBeforeMarker: PRIMITIVE_CSS_START_MARKER
4658
+ });
4659
+ }
4660
+ function upsertDelimitedBlock(existingCss, block, options) {
4469
4661
  const normalizedBlock = block.trim();
4470
- const start = existingCss.indexOf(PRIMITIVE_CSS_START_MARKER);
4471
- const end = existingCss.indexOf(PRIMITIVE_CSS_END_MARKER);
4662
+ const start = existingCss.indexOf(options.startMarker);
4663
+ const end = existingCss.indexOf(options.endMarker);
4472
4664
  if (start !== -1 && end !== -1 && end > start) {
4473
4665
  const before = existingCss.slice(0, start).replace(/\s*$/, "\n\n");
4474
- const after = existingCss.slice(end + PRIMITIVE_CSS_END_MARKER.length).replace(/^\s*/, "\n");
4666
+ const after = existingCss.slice(end + options.endMarker.length).replace(/^\s*/, "\n");
4475
4667
  return `${before}${normalizedBlock}${after}`.replace(/\n{3,}/g, "\n\n");
4476
4668
  }
4477
4669
  if (start !== -1 || end !== -1) {
4478
4670
  throw new CliError(
4479
- "Configured Tailwind CSS has a partial Wensity Primitive Tokens block. Remove the broken markers and re-run install."
4671
+ `Configured Tailwind CSS has a partial ${options.label} block. Remove the broken markers and re-run install.`
4480
4672
  );
4481
4673
  }
4674
+ if (options.insertBeforeMarker) {
4675
+ const anchor = existingCss.indexOf(options.insertBeforeMarker);
4676
+ if (anchor !== -1) {
4677
+ const before = existingCss.slice(0, anchor).replace(/\s*$/, "\n\n");
4678
+ const after = existingCss.slice(anchor);
4679
+ return `${before}${normalizedBlock}
4680
+
4681
+ ${after}`.replace(/\n{3,}/g, "\n\n");
4682
+ }
4683
+ }
4482
4684
  const trimmed = existingCss.replace(/\s*$/, "");
4483
4685
  if (!trimmed) return `${normalizedBlock}
4484
4686
  `;
@@ -4722,6 +4924,7 @@ async function initCommand(context, parsed) {
4722
4924
  const template = templateId ? getProjectTemplateDefinition(templateId) : void 0;
4723
4925
  const hasPreset = Boolean(presetCode || presetFile);
4724
4926
  const dependencies = ["clsx", "tailwind-merge"];
4927
+ const installOptions = installOptionsFrom(context, parsed);
4725
4928
  if (hasPreset) {
4726
4929
  await initWithPreset(context, {
4727
4930
  dryRun,
@@ -4731,11 +4934,13 @@ async function initCommand(context, parsed) {
4731
4934
  presetCode,
4732
4935
  presetFile,
4733
4936
  dependencies,
4734
- template
4937
+ template,
4938
+ installOptions
4735
4939
  });
4736
4940
  return;
4737
4941
  }
4738
4942
  const templateFiles = template ? await scaffoldProjectTemplate(context.cwd, template, { dryRun, force }) : [];
4943
+ const packageManagerFiles = template ? prepareProjectPackageManager(context.cwd, { ...installOptions, dryRun }) : [];
4739
4944
  const config = template ? applyTemplateToConfig(createDefaultConfig(context.cwd, apiUrl), template) : createDefaultConfig(context.cwd, apiUrl);
4740
4945
  const configPath = join7(context.cwd, CONFIG_FILE_NAME);
4741
4946
  const utilityPath = resolveConfiguredFilePath(context.cwd, config.utils);
@@ -4743,7 +4948,12 @@ async function initCommand(context, parsed) {
4743
4948
  if (dryRun) {
4744
4949
  printTitle("Project initialization preview");
4745
4950
  print(action("Would write:"));
4746
- print(formatList([...formatTemplateFiles(templateFiles), CONFIG_FILE_NAME, utilityRelativePath]));
4951
+ print(formatList([
4952
+ ...formatTemplateFiles(templateFiles),
4953
+ ...packageManagerFiles,
4954
+ CONFIG_FILE_NAME,
4955
+ utilityRelativePath
4956
+ ]));
4747
4957
  if (template) print(action(`Template: ${template.label}`));
4748
4958
  if (install) print(action(`Would install: ${dependencies.join(" ")}`));
4749
4959
  return;
@@ -4753,14 +4963,24 @@ async function initCommand(context, parsed) {
4753
4963
  await writeTextFile(utilityPath, cnUtilityTemplate());
4754
4964
  }
4755
4965
  if (install) {
4756
- runInstall(context.cwd, missingDependencies(context.cwd, dependencies));
4757
- }
4966
+ if (template) runProjectInstall(context.cwd, installOptions);
4967
+ runInstall(context.cwd, missingDependencies(context.cwd, dependencies), installOptions);
4968
+ }
4969
+ await assertInitPostconditions(context.cwd, [
4970
+ ...templateFiles,
4971
+ ...packageManagerFiles,
4972
+ CONFIG_FILE_NAME,
4973
+ utilityRelativePath
4974
+ ]);
4758
4975
  printTitle("Project initialized");
4759
4976
  print(panel("Created", [
4977
+ ...formatTemplateFiles(templateFiles).map((file) => success(file)),
4978
+ ...packageManagerFiles.map((file) => success(file)),
4760
4979
  success(CONFIG_FILE_NAME),
4761
4980
  success(utilityRelativePath),
4762
4981
  success(`registry: ${apiUrl}`)
4763
4982
  ]));
4983
+ print(action(`Location: ${context.cwd}`));
4764
4984
  print(nextSteps([
4765
4985
  "Browse available components: npx wensity list",
4766
4986
  "Browse premium blocks: npx wensity list --type block",
@@ -4778,6 +4998,10 @@ async function initWithPreset(context, options) {
4778
4998
  dryRun: options.dryRun,
4779
4999
  force: options.force
4780
5000
  }) : [];
5001
+ const packageManagerFiles = options.template ? prepareProjectPackageManager(context.cwd, {
5002
+ ...options.installOptions,
5003
+ dryRun: options.dryRun
5004
+ }) : [];
4781
5005
  let config;
4782
5006
  if (configExists && !options.force) {
4783
5007
  if (options.template) {
@@ -4801,7 +5025,12 @@ async function initWithPreset(context, options) {
4801
5025
  if (options.dryRun) {
4802
5026
  printTitle("Project initialization preview (preset)");
4803
5027
  print(action("Would write:"));
4804
- const planned = [...formatTemplateFiles(templateFiles), CONFIG_FILE_NAME, utilityRelativePath];
5028
+ const planned = [
5029
+ ...formatTemplateFiles(templateFiles),
5030
+ ...packageManagerFiles,
5031
+ CONFIG_FILE_NAME,
5032
+ utilityRelativePath
5033
+ ];
4805
5034
  if (prepared.cssChanged) planned.push(toPosixPath(prepared.cssRelative));
4806
5035
  print(formatList(planned));
4807
5036
  if (options.template) print(action(`Template: ${options.template.label}`));
@@ -4832,11 +5061,25 @@ async function initWithPreset(context, options) {
4832
5061
  }
4833
5062
  });
4834
5063
  if (options.install) {
4835
- runInstall(context.cwd, missingDependencies(context.cwd, installDependencies));
5064
+ if (options.template) runProjectInstall(context.cwd, options.installOptions);
5065
+ runInstall(
5066
+ context.cwd,
5067
+ missingDependencies(context.cwd, installDependencies),
5068
+ options.installOptions
5069
+ );
4836
5070
  }
5071
+ await assertInitPostconditions(context.cwd, [
5072
+ ...templateFiles,
5073
+ ...packageManagerFiles,
5074
+ CONFIG_FILE_NAME,
5075
+ utilityRelativePath,
5076
+ prepared.cssRelative
5077
+ ]);
4837
5078
  printTitle("Project initialized");
4838
5079
  const created = [
4839
5080
  ...options.template ? [success(`template: ${options.template.label}`)] : [],
5081
+ ...formatTemplateFiles(templateFiles).map((file) => success(file)),
5082
+ ...packageManagerFiles.map((file) => success(file)),
4840
5083
  success(CONFIG_FILE_NAME),
4841
5084
  success(utilityRelativePath),
4842
5085
  success(`registry: ${options.apiUrl}`),
@@ -4851,6 +5094,7 @@ async function initWithPreset(context, options) {
4851
5094
  created.push(success(`fonts: ${prepared.fontPlan.packages.join(", ")}`));
4852
5095
  }
4853
5096
  print(panel("Created", created));
5097
+ print(action(`Location: ${context.cwd}`));
4854
5098
  if (prepared.unusedFontWarning) warn(prepared.unusedFontWarning);
4855
5099
  print(nextSteps([
4856
5100
  "Browse available components: npx wensity list",
@@ -4915,7 +5159,11 @@ async function applyCommand(context, parsed) {
4915
5159
  }
4916
5160
  });
4917
5161
  if (install && installDependencies.length > 0) {
4918
- runInstall(loaded.projectRoot, missingDependencies(loaded.projectRoot, installDependencies));
5162
+ runInstall(
5163
+ loaded.projectRoot,
5164
+ missingDependencies(loaded.projectRoot, installDependencies),
5165
+ installOptionsFrom(context, parsed)
5166
+ );
4919
5167
  }
4920
5168
  printTitle("Preset applied");
4921
5169
  const applied = [
@@ -4965,7 +5213,8 @@ async function preparePresetStyleApplication(projectRoot, config, style, operati
4965
5213
  }
4966
5214
  const cssAbsolute = resolveInside(projectRoot, cssRelative);
4967
5215
  const existingCss = await fileExists(cssAbsolute) ? await readFile6(cssAbsolute, "utf8") : "";
4968
- const withTokens = upsertPrimitiveCssBlock(existingCss, primitiveCss);
5216
+ const withBase = upsertBaseCssBlock(existingCss, buildBaseCssBlock(baseColorFromStyle(style)));
5217
+ const withTokens = upsertPrimitiveCssBlock(withBase, primitiveCss);
4969
5218
  const nextCss = upsertFontImportBlock(withTokens, fontPlan.block);
4970
5219
  const cssChanged = nextCss !== existingCss;
4971
5220
  const installedPackages = readInstalledPackageNames(projectRoot);
@@ -5095,13 +5344,13 @@ function formatBlockGroup(blocks) {
5095
5344
  }
5096
5345
  async function addCommand(context, parsed, positionals, updateMode) {
5097
5346
  const resourceType = getResourceType(parsed);
5098
- const slug = positionals[0];
5099
- if (!slug) {
5100
- throw new CliError(
5101
- `Missing ${resourceType} slug.
5347
+ const command = updateMode ? "update" : "add";
5348
+ const usage = `Usage: wensity ${command} <slug...>${resourceType === "block" ? " --type block" : ""}`;
5349
+ const slugs = dedupe(positionals);
5350
+ if (slugs.length === 0) {
5351
+ throw new CliError(`Missing ${resourceType} slug.
5102
5352
 
5103
- Usage: wensity ${updateMode ? "update" : "add"} <slug>${resourceType === "block" ? " --type block" : ""}`
5104
- );
5353
+ ${usage}`);
5105
5354
  }
5106
5355
  const apiUrl = getStringOption(parsed, "api-url") ?? context.env.WENSITY_API_URL;
5107
5356
  const registryOverride = getStringOption(parsed, "registry");
@@ -5109,40 +5358,67 @@ Usage: wensity ${updateMode ? "update" : "add"} <slug>${resourceType === "block"
5109
5358
  const credentials = await readCredentials(context.env);
5110
5359
  const registry = registryUrlForType(loaded.config.registry, resourceType, registryOverride);
5111
5360
  const force = updateMode || hasFlag(parsed, "force");
5112
- const payload = resourceType === "block" ? await fetchBlockPayload({ slug, registry, credentials }) : await fetchComponentPayload({ slug, registry, credentials });
5113
- const prepared = await prepareInstall(loaded.projectRoot, loaded.config, payload, {
5114
- overwriteExistingDependencyFiles: force
5115
- });
5116
5361
  const dryRun = hasFlag(parsed, "dry-run");
5117
5362
  const install = !hasFlag(parsed, "no-install");
5118
- const plannedInstallFiles = prepared.cssFile ? [...prepared.files, prepared.cssFile] : prepared.files;
5119
- await assertNoConflicts(plannedInstallFiles, force || dryRun);
5120
- const plannedFiles = summarizeFiles(loaded.projectRoot, plannedInstallFiles);
5121
- const missing = missingDependencies(loaded.projectRoot, prepared.dependencies);
5363
+ const payloads = [];
5364
+ for (const slug of slugs) {
5365
+ payloads.push(
5366
+ resourceType === "block" ? await fetchBlockPayload({ slug, registry, credentials }) : await fetchComponentPayload({ slug, registry, credentials })
5367
+ );
5368
+ }
5122
5369
  const style = normalizeProjectStyle(loaded.config.style);
5123
5370
  const fonts = curatedFontsFromStyle(style);
5124
5371
  const installedPackages = readInstalledPackageNames(loaded.projectRoot);
5125
- const oppositeIconWarning = installedPackages && prepared.iconDependencySwap ? warnOppositeIconPackageMessage(style.iconLibrary, installedPackages) : void 0;
5126
5372
  const unusedFontWarning = installedPackages ? warnUnusedFontPackageMessage(fonts.headingFont, fonts.bodyFont, installedPackages) : void 0;
5373
+ const writtenFiles = [];
5374
+ const cssTargets = [];
5375
+ const dependencies = [];
5376
+ const installPlans = [];
5377
+ let iconDependencySwapped = false;
5378
+ for (const payload of payloads) {
5379
+ const prepared = await prepareInstall(loaded.projectRoot, loaded.config, payload, {
5380
+ overwriteExistingDependencyFiles: force
5381
+ });
5382
+ const plannedInstallFiles = prepared.cssFile ? [...prepared.files, prepared.cssFile] : prepared.files;
5383
+ installPlans.push(prepared);
5384
+ writtenFiles.push(...summarizeFiles(loaded.projectRoot, plannedInstallFiles));
5385
+ if (prepared.cssFile) cssTargets.push(prepared.cssFile.relativePath);
5386
+ dependencies.push(...prepared.dependencies);
5387
+ if (prepared.iconDependencySwap) iconDependencySwapped = true;
5388
+ }
5389
+ const allPlannedInstallFiles = installPlans.flatMap(
5390
+ (prepared) => prepared.cssFile ? [...prepared.files, prepared.cssFile] : prepared.files
5391
+ );
5392
+ await assertNoConflicts(allPlannedInstallFiles, force || dryRun);
5393
+ const plannedFiles = dedupe(writtenFiles);
5394
+ const missing = missingDependencies(loaded.projectRoot, dedupe(dependencies));
5395
+ const oppositeIconWarning = installedPackages && iconDependencySwapped ? warnOppositeIconPackageMessage(style.iconLibrary, installedPackages) : void 0;
5127
5396
  if (dryRun) {
5128
5397
  printTitle(updateMode ? "Update preview" : "Install preview");
5129
5398
  print(action("Would write:"));
5130
5399
  print(formatList(plannedFiles));
5131
- if (prepared.cssFile) {
5132
- print(action(`Would update CSS tokens in: ${prepared.cssFile.relativePath}`));
5400
+ for (const target of dedupe(cssTargets)) {
5401
+ print(action(`Would update CSS tokens in: ${target}`));
5133
5402
  }
5134
5403
  if (install && missing.length > 0) print(action(`Would install: ${missing.join(" ")}`));
5135
5404
  if (oppositeIconWarning) warn(oppositeIconWarning);
5136
5405
  if (unusedFontWarning) warn(unusedFontWarning);
5137
5406
  return;
5138
5407
  }
5139
- await writePlannedFiles(plannedInstallFiles);
5140
- if (install) {
5141
- runInstall(loaded.projectRoot, missing);
5408
+ for (const prepared of installPlans) {
5409
+ await writePlannedFiles(
5410
+ prepared.cssFile ? [...prepared.files, prepared.cssFile] : prepared.files
5411
+ );
5142
5412
  }
5143
- printTitle(updateMode ? "Component updated" : "Component installed");
5144
- print(panel(payload.name, [
5145
- success(`${updateMode ? "Updated" : "Added"} ${payload.slug}`),
5413
+ if (install) {
5414
+ runInstall(loaded.projectRoot, missing, installOptionsFrom(context, parsed));
5415
+ }
5416
+ const noun = payloads.length === 1 ? "Component" : "Components";
5417
+ printTitle(updateMode ? `${noun} updated` : `${noun} installed`);
5418
+ print(panel(payloads.map((payload) => payload.name).join(", "), [
5419
+ ...payloads.map(
5420
+ (payload) => success(`${updateMode ? "Updated" : "Added"} ${payload.slug}`)
5421
+ ),
5146
5422
  ...plannedFiles.map((file) => success(file))
5147
5423
  ]));
5148
5424
  if (oppositeIconWarning) warn(oppositeIconWarning);
@@ -5156,10 +5432,13 @@ Usage: wensity ${updateMode ? "update" : "add"} <slug>${resourceType === "block"
5156
5432
  return;
5157
5433
  }
5158
5434
  print(nextSteps([
5159
- "Import the installed file from your Wensity components folder",
5435
+ payloads.length === 1 ? "Import the installed file from your Wensity components folder" : "Import the installed files from your Wensity components folder",
5160
5436
  resourceType === "block" ? "Customize the block copy, colors, and sections" : "Customize props and styling for your app"
5161
5437
  ]));
5162
5438
  }
5439
+ function dedupe(values) {
5440
+ return [...new Set(values)];
5441
+ }
5163
5442
  async function fetchComponentPayload(input) {
5164
5443
  const metadata = await withSpinner(
5165
5444
  `Finding ${input.slug}`,
@@ -5272,6 +5551,32 @@ async function writeTextFile(path, content) {
5272
5551
  await mkdir6(dirname7(path), { recursive: true });
5273
5552
  await writeFile6(path, content, "utf8");
5274
5553
  }
5554
+ function installOptionsFrom(context, parsed) {
5555
+ return {
5556
+ packageManager: parsePackageManager(getStringOption(parsed, "package-manager")),
5557
+ env: context.env
5558
+ };
5559
+ }
5560
+ async function assertInitPostconditions(projectRoot, relativePaths) {
5561
+ const missing = [];
5562
+ for (const relativePath of new Set(relativePaths)) {
5563
+ const absolute = join7(projectRoot, relativePath);
5564
+ let size = 0;
5565
+ try {
5566
+ size = (await stat(absolute)).size;
5567
+ } catch {
5568
+ missing.push(relativePath);
5569
+ continue;
5570
+ }
5571
+ if (size === 0) missing.push(relativePath);
5572
+ }
5573
+ if (missing.length > 0) {
5574
+ throw new CliError(
5575
+ `Initialization did not complete. These files are missing or empty in ${projectRoot}:
5576
+ ` + missing.map((file) => ` - ${toPosixPath(file)}`).join("\n")
5577
+ );
5578
+ }
5579
+ }
5275
5580
 
5276
5581
  // src/index.ts
5277
5582
  run({
package/package.json CHANGED
@@ -1,11 +1,12 @@
1
1
  {
2
2
  "name": "wensity",
3
- "version": "0.1.11",
4
- "description": "Wensity component registry CLI",
3
+ "version": "0.1.12",
4
+ "description": "Install React components, UI primitives, and shareable Tailwind CSS presets into your Next.js project as source code you own.",
5
5
  "license": "MIT",
6
6
  "author": "Wensity <hey@wensity.com> (https://wensity.com)",
7
- "homepage": "https://ui.wensity.com",
7
+ "homepage": "https://ui.wensity.com/docs/cli",
8
8
  "bugs": {
9
+ "url": "https://github.com/wensity/cli/issues",
9
10
  "email": "hey@wensity.com"
10
11
  },
11
12
  "keywords": [
@@ -13,10 +14,17 @@
13
14
  "react",
14
15
  "nextjs",
15
16
  "tailwindcss",
17
+ "tailwind-v4",
16
18
  "components",
17
19
  "ui",
20
+ "ui-components",
18
21
  "design-system",
19
- "shadcn"
22
+ "design-tokens",
23
+ "theme",
24
+ "shadcn",
25
+ "shadcn-ui",
26
+ "base-ui",
27
+ "cli"
20
28
  ],
21
29
  "type": "module",
22
30
  "bin": {
@@ -29,16 +37,22 @@
29
37
  "dist",
30
38
  "README.md"
31
39
  ],
40
+ "scripts": {
41
+ "build": "tsup --config tsup.config.ts",
42
+ "typecheck": "tsc --noEmit",
43
+ "test": "vitest run --config vitest.config.ts",
44
+ "lint": "echo 'no lint'"
45
+ },
32
46
  "engines": {
33
47
  "node": ">=20.0.0"
34
48
  },
35
49
  "devDependencies": {
36
50
  "@types/node": "^22.10.2",
51
+ "@wensity/preset": "workspace:*",
37
52
  "lucide-react": "^1.24.0",
38
53
  "tsup": "^8.3.5",
39
54
  "typescript": "^5.7.2",
40
- "vitest": "^4.1.5",
41
- "@wensity/preset": "0.1.0"
55
+ "vitest": "^4.1.5"
42
56
  },
43
57
  "publishConfig": {
44
58
  "access": "public"
@@ -49,10 +63,8 @@
49
63
  "ora": "^9.4.0",
50
64
  "picocolors": "^1.1.1"
51
65
  },
52
- "scripts": {
53
- "build": "tsup --config tsup.config.ts",
54
- "typecheck": "tsc --noEmit",
55
- "test": "vitest run --config vitest.config.ts",
56
- "lint": "echo 'no lint'"
66
+ "repository": {
67
+ "type": "git",
68
+ "url": "git+https://github.com/wensity/cli.git"
57
69
  }
58
- }
70
+ }
package/LICENSE DELETED
@@ -1,21 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 Wensity Private Limited
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.