@zvoove/unity-ui 2.22.1 → 2.23.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -29,29 +29,9 @@ The `unity-ui` is available as a public package on [npmjs](https://www.npmjs.com
29
29
  npm i @zvoove/unity-ui
30
30
  ```
31
31
 
32
- ### GitHub Packages registry (legacy)
32
+ > **Note:** The GitHub Packages registry (`@zvoove/unity-ui`) is deprecated and should no longer be used. Use the public npm registry (`@zvoove/unity-ui`) instead.
33
33
 
34
- > **Note:** We are transitioning to the public npm registry (`@zvoove/unity-ui`). The GitHub Packages registry (`@zvoove-org/unity-ui`) will be discontinued in the future. We recommend migrating to the npm registry as soon as possible.
35
-
36
- The `unity-ui` is also available on [our private package registry on GitHub](https://github.com/orgs/zvoove-org/packages?repo_name=unity-ui). To install from GitHub Packages, you'll need to [setup a personal access token](https://github.com/settings/tokens) with the `read:packages` permission.
37
-
38
- Configure the token for `npm`: `npm config set //npm.pkg.github.com/:_authToken $TOKEN`.
39
-
40
- Then create a `.npmrc` file in the root of your project:
41
-
42
- ```shell
43
- @zvoove-org:registry=https://npm.pkg.github.com/zvoove-org
44
- ```
45
-
46
- Then install:
47
-
48
- ```shell
49
- npm i @zvoove-org/unity-ui
50
- ```
51
-
52
- ---
53
-
54
- `@zvoove/unity-ui` (or `@zvoove-org/unity-ui`) has required peer dependencies for `react` and `react-dom`. If your project doesn't have them already, make sure you have installed using at least **v18**
34
+ `@zvoove/unity-ui` has required peer dependencies for `react` and `react-dom`. If your project doesn't have them already, make sure you have installed using at least **v18**
55
35
 
56
36
  ## Styles
57
37
 
@@ -62,8 +42,8 @@ In case you want to install `tailwindcss` in your project and have access to the
62
42
  ```css
63
43
  /* index.css or main.css etc... */
64
44
 
65
- @import '@zvoove-org/unity-ui/theme.css';
66
- @import '@zvoove-org/unity-ui/unity-ui.css';
45
+ @import '@zvoove/unity-ui/theme.css';
46
+ @import '@zvoove/unity-ui/unity-ui.css';
67
47
  ```
68
48
 
69
49
  The `theme.css` file contains all the design tokens used in the project, so you can use them in your styles.
@@ -71,7 +51,7 @@ The `theme.css` file contains all the design tokens used in the project, so you
71
51
  After that you can use the components in your project and you can also use the design tokens in your styles. (Check our [Design Tokens](https://main--67c03f013fea08bb2f926e5f.chromatic.com/?path=/docs/design-tokens--docs) section for more information)
72
52
 
73
53
  ```jsx
74
- import { Button } from '@zvoove-org/unity-ui';
54
+ import { Button } from '@zvoove/unity-ui';
75
55
 
76
56
  const App = () => {
77
57
  return (
@@ -89,7 +69,7 @@ export default App;
89
69
  The `unity-ui` has a dark mode that can be enabled by adding the `dark` data attribute to the root element of your application.
90
70
 
91
71
  ```jsx
92
- import { Button } from '@zvoove-org/unity-ui';
72
+ import { Button } from '@zvoove/unity-ui';
93
73
 
94
74
  const App = () => {
95
75
  return (
@@ -113,62 +93,14 @@ export default Layout;
113
93
  you can also force a specific component to be in dark mode by adding the `dark` data attribute to the component.
114
94
 
115
95
  ```jsx
116
- import { Button } from '@zvoove-org/unity-ui';
96
+ import { Button } from '@zvoove/unity-ui';
117
97
 
118
98
  <Button data-theme="dark">Click me</Button>;
119
99
  ```
120
100
 
121
101
  ## Fonts
122
102
 
123
- Our project uses the `Source Sans` font family available at [google fonts](https://fonts.google.com/specimen/Source+Sans+3), you can include it in your project by adding the following lines to your `<head>` tag:
124
-
125
- ```html
126
- <link rel="preconnect" href="https://fonts.googleapis.com" />
127
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
128
- <link
129
- href="https://fonts.googleapis.com/css2?family=Source+Sans+3:ital,wght@0,200..900;1,200..900&display=swap"
130
- rel="stylesheet"
131
- />
132
- ```
133
-
134
- ## Fonts in NextJS
135
-
136
- For `NextJS` you can load the fonts using `next/font` like this:
137
-
138
- ```jsx
139
- import './globals.css';
140
- import { Source_Sans_3 } from 'next/font/google';
141
-
142
- const sourceSans3 = Source_Sans_3({
143
- subsets: ['latin'],
144
- variable: '--font-source-sans-3',
145
- });
146
-
147
- export default async function RootLayout({
148
- children,
149
- }: Readonly<{
150
- children: React.ReactNode;
151
- }>) {
152
- return (
153
- <html lang="de-DE" className={sourceSans3.variable}>
154
- <body data-theme="light">{children}</body>
155
- </html>
156
- );
157
- }
158
- ```
159
-
160
- And then you update your `globals.css` with:
161
-
162
- ```css
163
- @import '@zvoove-org/unity-ui/theme.css';
164
- @import '@zvoove-org/unity-ui/unity-ui.css';
165
-
166
- @layer base {
167
- html {
168
- font-family: var(--font-source-sans-3);
169
- }
170
- }
171
- ```
103
+ Our project uses the `Source Sans 3` font family. The font is bundled with the library via `@font-face` declarations in `theme.css`, loaded from our CDN. No additional setup is required — importing the theme CSS is enough.
172
104
 
173
105
  ## Installing (Development)
174
106
 
package/bin/cli.mjs ADDED
@@ -0,0 +1,49 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Unity UI — Unified CLI
5
+ *
6
+ * Usage:
7
+ * npx unity-ui init Initialize unity-ui.config.mjs
8
+ * npx unity-ui skills [-o .claude] Generate Agent Skills
9
+ * npx unity-ui create <ComponentName> Scaffold a new component
10
+ * npx unity-ui rules [-o .] Generate AI rules (.cursorrules + CLAUDE.md)
11
+ * npx unity-ui --help Show all commands
12
+ */
13
+ import { runCreate } from './commands/create.mjs';
14
+ import { runInit } from './commands/init.mjs';
15
+ import { runRules } from './commands/rules.mjs';
16
+ import { runSkills } from './commands/skills.mjs';
17
+ import { Command } from 'commander';
18
+
19
+ const program = new Command();
20
+
21
+ program
22
+ .name('unity-ui')
23
+ .description('Unity UI Design System CLI')
24
+ .version('1.0.0');
25
+
26
+ program
27
+ .command('init')
28
+ .description('Initialize unity-ui.config.mjs with project settings')
29
+ .action(() => runInit());
30
+
31
+ program
32
+ .command('skills')
33
+ .description('Generate Agent Skills files from llms.txt')
34
+ .option('-o, --output <dir>', 'Base directory for skills output')
35
+ .action((opts) => runSkills(opts));
36
+
37
+ program
38
+ .command('create')
39
+ .description('Scaffold a new component')
40
+ .argument('<name>', 'Component name (PascalCase)')
41
+ .action((name) => runCreate(name));
42
+
43
+ program
44
+ .command('rules')
45
+ .description('Generate AI rules files (.cursorrules + CLAUDE.md)')
46
+ .option('-o, --output <dir>', 'Output directory')
47
+ .action((opts) => runRules(opts));
48
+
49
+ program.parse();
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Unity UI — Config Loader
3
+ *
4
+ * Looks for unity-ui.config.mjs in process.cwd().
5
+ * If found, merges with defaults. If not, returns defaults.
6
+ */
7
+ import fs from 'fs';
8
+ import path from 'path';
9
+ import { pathToFileURL } from 'url';
10
+
11
+ export const DEFAULTS = {
12
+ components: {
13
+ directory: 'src/components',
14
+ indexFile: 'src/index.ts',
15
+ },
16
+ ai: {
17
+ skills: {
18
+ output: '.claude',
19
+ },
20
+ rules: {
21
+ output: '.',
22
+ targets: ['cursorrules', 'claude'],
23
+ },
24
+ },
25
+ };
26
+
27
+ function deepMerge(target, source) {
28
+ const result = { ...target };
29
+ for (const key of Object.keys(source)) {
30
+ if (
31
+ source[key] &&
32
+ typeof source[key] === 'object' &&
33
+ !Array.isArray(source[key]) &&
34
+ target[key] &&
35
+ typeof target[key] === 'object' &&
36
+ !Array.isArray(target[key])
37
+ ) {
38
+ result[key] = deepMerge(target[key], source[key]);
39
+ } else {
40
+ result[key] = source[key];
41
+ }
42
+ }
43
+ return result;
44
+ }
45
+
46
+ /**
47
+ * Loads unity-ui.config.mjs from process.cwd() and merges with defaults.
48
+ * Returns defaults if no config file is found.
49
+ */
50
+ export async function loadConfig() {
51
+ const configPath = path.resolve(process.cwd(), 'unity-ui.config.mjs');
52
+
53
+ if (!fs.existsSync(configPath)) {
54
+ return DEFAULTS;
55
+ }
56
+
57
+ try {
58
+ const configUrl = pathToFileURL(configPath).href;
59
+ const mod = await import(configUrl);
60
+ const userConfig = mod.default || {};
61
+ return deepMerge(DEFAULTS, userConfig);
62
+ } catch (err) {
63
+ console.warn(
64
+ `\x1b[33m⚠️ Failed to load unity-ui.config.mjs: ${err.message}\x1b[0m`
65
+ );
66
+ return DEFAULTS;
67
+ }
68
+ }
@@ -0,0 +1,163 @@
1
+ /**
2
+ * Unity UI — Component Scaffolder (core logic)
3
+ *
4
+ * Creates a new component folder with all required files
5
+ * from templates.
6
+ */
7
+ import { loadConfig } from './config.mjs';
8
+ import { execSync } from 'child_process';
9
+ import fs from 'fs';
10
+ import path from 'path';
11
+ import { fileURLToPath } from 'url';
12
+
13
+ const __filename = fileURLToPath(import.meta.url);
14
+ const __dirname = path.dirname(__filename);
15
+
16
+ const lowerFirstLetter = (str) => {
17
+ if (!str) return '';
18
+ return str.charAt(0).toLowerCase() + str.slice(1);
19
+ };
20
+
21
+ const checkComponentName = (componentName) => {
22
+ const componentNameRegex = /^[A-Z][a-zA-Z]*$/;
23
+
24
+ if (!componentNameRegex.test(componentName)) {
25
+ console.error(
26
+ '❌ Component name must start with an uppercase letter and contain only letters'
27
+ );
28
+ return false;
29
+ }
30
+
31
+ if (componentName.includes(' ')) {
32
+ console.error('❌ Component name must not contain spaces');
33
+ return false;
34
+ }
35
+
36
+ return true;
37
+ };
38
+
39
+ export async function runCreate(componentName) {
40
+ if (!checkComponentName(componentName)) {
41
+ process.exit(1);
42
+ }
43
+
44
+ const config = await loadConfig();
45
+
46
+ const componentDir = path.join(
47
+ process.cwd(),
48
+ config.components.directory,
49
+ componentName
50
+ );
51
+
52
+ const templatesDir = path.resolve(__dirname, '..', 'templates');
53
+
54
+ const templateMap = {
55
+ [`{name}.tsx`]: 'component.tsx',
56
+ [`{name}.styled.ts`]: 'styled.ts',
57
+ [`{name}.stories.tsx`]: 'stories.tsx',
58
+ [`{name}.test.tsx`]: 'test.tsx',
59
+ [`{name}.types.ts`]: 'types.ts',
60
+ [`{name}.mdx`]: 'doc.mdx',
61
+ [`index.ts`]: 'index.ts',
62
+ };
63
+
64
+ // Load template files
65
+ const loadTemplate = (templateFile, name) => {
66
+ const filePath = path.join(templatesDir, templateFile);
67
+
68
+ if (!fs.existsSync(filePath)) {
69
+ console.error(`❌ Template file not found: \x1b[33m${filePath}\x1b[0m`);
70
+ return;
71
+ }
72
+
73
+ const content = fs.readFileSync(filePath, 'utf8');
74
+
75
+ return content
76
+ .replace(/__COMPONENT_NAME__/g, name)
77
+ .replace(/__STYLES__FUNCTION__/g, lowerFirstLetter(name));
78
+ };
79
+
80
+ // Create component folder
81
+ if (!fs.existsSync(componentDir)) {
82
+ fs.mkdirSync(componentDir);
83
+ console.log(`✅ Created folder: \x1b[34m${componentName}\x1b[0m`);
84
+ } else {
85
+ console.log(`⚠️ Folder \x1b[33m${componentName}\x1b[0m already exists`);
86
+ }
87
+
88
+ // Create files
89
+ Object.entries(templateMap).forEach(([fileNamePattern, templateFile]) => {
90
+ const fileName = fileNamePattern.replace('{name}', componentName);
91
+ const fullPath = path.join(componentDir, fileName);
92
+
93
+ if (!fs.existsSync(fullPath)) {
94
+ const content = loadTemplate(templateFile, componentName);
95
+ fs.writeFileSync(fullPath, content, 'utf8');
96
+ console.log(`✅ Created file: \x1b[32m${fileName}\x1b[0m`);
97
+ } else {
98
+ console.log(`⚠️ File \x1b[33m${fileName}\x1b[0m already exists`);
99
+ }
100
+ });
101
+
102
+ const indexFile = path.join(process.cwd(), config.components.indexFile);
103
+ const exportLine = `export * from './components/${componentName}';`;
104
+
105
+ // Add export to index.ts
106
+ if (fs.existsSync(indexFile)) {
107
+ const content = fs.readFileSync(indexFile, 'utf8');
108
+ const lines = content.split('\n').filter((line) => line.trim());
109
+
110
+ // Separate theme import from component exports
111
+ const themeImport = lines.find((line) =>
112
+ line.includes("import './theme.css'")
113
+ );
114
+ const componentExports = lines.filter(
115
+ (line) => line !== themeImport && line.startsWith('export * from')
116
+ );
117
+
118
+ if (!componentExports.includes(exportLine)) {
119
+ // Add new export and sort alphabetically
120
+ componentExports.push(exportLine);
121
+ const sortedExports = componentExports.sort((a, b) => {
122
+ const getComponentName = (line) => {
123
+ const match = line.match(/from '\.\/components\/([^']+)'/);
124
+ return match ? match[1] : '';
125
+ };
126
+ return getComponentName(a).localeCompare(getComponentName(b));
127
+ });
128
+
129
+ const finalContent =
130
+ [themeImport, '', ...sortedExports].filter(Boolean).join('\n') + '\n';
131
+
132
+ const finalContentWithEmptyLine = finalContent.replace(
133
+ /(import '\.\/theme\.css';\n)(\n*)/,
134
+ '$1\n'
135
+ );
136
+
137
+ fs.writeFileSync(indexFile, finalContentWithEmptyLine, 'utf8');
138
+ console.log(
139
+ `✅ Added export to \x1b[34m./src/index.ts\x1b[0m (sorted alphabetically)`
140
+ );
141
+ } else {
142
+ console.log(`⚠️ Export already exists in \x1b[33m./src/index.ts\x1b[0m`);
143
+ }
144
+ } else {
145
+ console.log(`❌ \x1b[33m./src/index.ts\x1b[0m does not exist.`);
146
+ }
147
+
148
+ console.log(
149
+ `🎉 Component \x1b[32m<${componentName} />\x1b[0m structure created successfully!`
150
+ );
151
+
152
+ // Format the newly created component folder
153
+ try {
154
+ execSync(
155
+ `npx prettier --write "${componentDir}" --ignore-glob "**/*.md" --ignore-glob "**/*.mdx"`,
156
+ { stdio: 'inherit' }
157
+ );
158
+
159
+ console.log(`✅ Prettier formatted: \x1b[32m<${componentName} />\x1b[0m`);
160
+ } catch (err) {
161
+ console.error(`❌ Prettier formatting failed:`, err);
162
+ }
163
+ }
@@ -0,0 +1,158 @@
1
+ /**
2
+ * Unity UI — Init Command
3
+ *
4
+ * Interactive questionnaire that generates unity-ui.config.mjs.
5
+ */
6
+ import { DEFAULTS } from './config.mjs';
7
+ import fs from 'fs';
8
+ import path from 'path';
9
+ import readline from 'readline';
10
+
11
+ function createRL() {
12
+ return readline.createInterface({
13
+ input: process.stdin,
14
+ output: process.stdout,
15
+ });
16
+ }
17
+
18
+ function ask(rl, question, defaultValue) {
19
+ return new Promise((resolve) => {
20
+ rl.question(
21
+ ` \x1b[36m?\x1b[0m ${question} \x1b[90m(${defaultValue})\x1b[0m `,
22
+ (answer) => {
23
+ resolve(answer.trim() || defaultValue);
24
+ }
25
+ );
26
+ });
27
+ }
28
+
29
+ function askChoice(rl, question, options, defaultValue) {
30
+ const optionsList = options.map((o) => o.label).join(' / ');
31
+ return new Promise((resolve) => {
32
+ rl.question(
33
+ ` \x1b[36m?\x1b[0m ${question} \x1b[90m(${defaultValue})\x1b[0m [${optionsList}] `,
34
+ (answer) => {
35
+ const trimmed = answer.trim().toLowerCase();
36
+ const match = options.find(
37
+ (o) => o.label.toLowerCase() === trimmed || o.alias === trimmed
38
+ );
39
+ if (match) {
40
+ resolve(match.value);
41
+ } else {
42
+ // Default
43
+ const defaultOption = options.find(
44
+ (o) =>
45
+ o.label.toLowerCase() === defaultValue.toLowerCase() ||
46
+ o.alias === defaultValue.toLowerCase()
47
+ );
48
+ resolve(defaultOption ? defaultOption.value : options[0].value);
49
+ }
50
+ }
51
+ );
52
+ });
53
+ }
54
+
55
+ function generateConfigContent(config) {
56
+ const targets = config.ai.rules.targets;
57
+ const targetsStr = targets.map((t) => `'${t}'`).join(', ');
58
+
59
+ return `// unity-ui.config.mjs — generated by \`npx unity-ui init\`
60
+ import { defineConfig } from '@zvoove/unity-ui/config';
61
+
62
+ export default defineConfig({
63
+ components: {
64
+ directory: '${config.components.directory}',
65
+ indexFile: '${config.components.indexFile}',
66
+ },
67
+ ai: {
68
+ skills: {
69
+ output: '${config.ai.skills.output}',
70
+ },
71
+ rules: {
72
+ output: '${config.ai.rules.output}',
73
+ targets: [${targetsStr}],
74
+ },
75
+ },
76
+ });
77
+ `;
78
+ }
79
+
80
+ export async function runInit() {
81
+ const configPath = path.resolve(process.cwd(), 'unity-ui.config.mjs');
82
+
83
+ if (fs.existsSync(configPath)) {
84
+ console.log(
85
+ `\x1b[33m⚠️ unity-ui.config.mjs already exists. Delete it first to re-initialize.\x1b[0m`
86
+ );
87
+ return;
88
+ }
89
+
90
+ console.log(`\n \x1b[1m🔧 Unity UI — Project Setup\x1b[0m\n`);
91
+
92
+ const rl = createRL();
93
+
94
+ const componentsDir = await ask(
95
+ rl,
96
+ 'Where do your components live?',
97
+ DEFAULTS.components.directory
98
+ );
99
+
100
+ const indexFile = await ask(
101
+ rl,
102
+ 'Where is your main index/barrel file?',
103
+ DEFAULTS.components.indexFile
104
+ );
105
+
106
+ const rulesTargets = await askChoice(
107
+ rl,
108
+ 'Generate AI rules for which tools?',
109
+ [
110
+ { label: 'Both', alias: 'b', value: ['cursorrules', 'claude'] },
111
+ { label: 'Claude', alias: 'c', value: ['claude'] },
112
+ { label: 'Cursor', alias: 'u', value: ['cursorrules'] },
113
+ { label: 'None', alias: 'n', value: [] },
114
+ ],
115
+ 'Both'
116
+ );
117
+
118
+ const skillsOutput = await ask(
119
+ rl,
120
+ 'Where should Agent Skills be generated?',
121
+ DEFAULTS.ai.skills.output
122
+ );
123
+
124
+ const rulesOutput = await ask(
125
+ rl,
126
+ 'Where should rules files be generated?',
127
+ DEFAULTS.ai.rules.output
128
+ );
129
+
130
+ rl.close();
131
+
132
+ const config = {
133
+ components: {
134
+ directory: componentsDir,
135
+ indexFile: indexFile,
136
+ },
137
+ ai: {
138
+ skills: { output: skillsOutput },
139
+ rules: { output: rulesOutput, targets: rulesTargets },
140
+ },
141
+ };
142
+
143
+ const content = generateConfigContent(config);
144
+ fs.writeFileSync(configPath, content, 'utf8');
145
+
146
+ console.log(`\n \x1b[32m✅ Created unity-ui.config.mjs\x1b[0m\n`);
147
+ console.log(` Components: ${config.components.directory}/`);
148
+ console.log(` Index file: ${config.components.indexFile}`);
149
+ console.log(` Skills dir: ${config.ai.skills.output}/skills/`);
150
+ if (config.ai.rules.targets.length > 0) {
151
+ console.log(
152
+ ` Rules: ${config.ai.rules.targets.join(', ')} → ${config.ai.rules.output}/`
153
+ );
154
+ } else {
155
+ console.log(` Rules: disabled`);
156
+ }
157
+ console.log('');
158
+ }
@@ -0,0 +1,100 @@
1
+ /**
2
+ * Unity UI — AI Rules Generator
3
+ *
4
+ * Generates .cursorrules and CLAUDE.md files for consuming projects.
5
+ * These files help AI coding assistants understand and correctly use
6
+ * Unity UI components.
7
+ */
8
+ import { loadConfig } from './config.mjs';
9
+ import { findLlmsTxt } from './skills.mjs';
10
+ import fs from 'fs';
11
+ import path from 'path';
12
+
13
+ function generateRulesContent(llmsContent) {
14
+ return `# Unity UI — AI Rules
15
+
16
+ > Auto-generated by \`npx unity-ui rules\`. Do not edit manually.
17
+ > Source: @zvoove/unity-ui llms.txt
18
+
19
+ ## General Rules
20
+
21
+ - Always import components from \`@zvoove/unity-ui\`
22
+ - Never recreate components that exist in this library
23
+ - Use responsive props where applicable (single value or breakpoint object)
24
+ - Use design tokens from theme.css — never hardcode colors, spacing, or shadows
25
+ - Use \`tv()\` from tailwind-variants for component styling — never inline conditional classes
26
+ - Dark mode uses \`data-theme="dark"\` attribute, not \`className="dark"\`
27
+ - Default/placeholder texts should be in German
28
+ - Font (Source Sans 3) is bundled via \`@font-face\` in theme.css — no extra setup needed
29
+
30
+ ## Setup
31
+
32
+ \`\`\`bash
33
+ npm install @zvoove/unity-ui
34
+ \`\`\`
35
+
36
+ \`\`\`css
37
+ @import '@zvoove/unity-ui/theme.css';
38
+ @import '@zvoove/unity-ui/unity-ui.css';
39
+ \`\`\`
40
+
41
+ ## Responsive Props
42
+
43
+ Breakpoints: minimum (0px), mobile (320px), tablet (768px), laptop (1024px), desktop (1440px).
44
+
45
+ \`\`\`tsx
46
+ <Button size="md" />
47
+ <Button size={{ mobile: 'sm', tablet: 'md', desktop: 'lg' }} />
48
+ \`\`\`
49
+
50
+ ## Component Reference
51
+
52
+ ${llmsContent}
53
+ `;
54
+ }
55
+
56
+ export async function runRules({ output: outputDir } = {}) {
57
+ const config = await loadConfig();
58
+ const resolvedDir = path.resolve(
59
+ process.cwd(),
60
+ outputDir || config.ai.rules.output
61
+ );
62
+ const targets = config.ai.rules.targets;
63
+
64
+ const llmsPath = findLlmsTxt();
65
+ if (!llmsPath) {
66
+ console.error(
67
+ 'Could not find llms.txt. Make sure @zvoove/unity-ui is installed and built.'
68
+ );
69
+ process.exit(1);
70
+ }
71
+
72
+ const llmsContent = fs.readFileSync(llmsPath, 'utf8');
73
+ const rulesContent = generateRulesContent(llmsContent);
74
+
75
+ fs.mkdirSync(resolvedDir, { recursive: true });
76
+
77
+ if (targets.includes('cursorrules')) {
78
+ const cursorRulesPath = path.join(resolvedDir, '.cursorrules');
79
+ fs.writeFileSync(cursorRulesPath, rulesContent, 'utf8');
80
+ console.log(
81
+ `✅ Generated ${path.relative(process.cwd(), cursorRulesPath)}`
82
+ );
83
+ }
84
+
85
+ if (targets.includes('claude')) {
86
+ const claudeMdPath = path.join(resolvedDir, 'CLAUDE.md');
87
+ fs.writeFileSync(claudeMdPath, rulesContent, 'utf8');
88
+ console.log(`✅ Generated ${path.relative(process.cwd(), claudeMdPath)}`);
89
+ }
90
+
91
+ if (targets.length > 0) {
92
+ console.log(
93
+ `\n Your AI assistant now knows how to use Unity UI components.\n`
94
+ );
95
+ } else {
96
+ console.log(
97
+ `\n No rule targets configured. Set targets in unity-ui.config.mjs.\n`
98
+ );
99
+ }
100
+ }