@zvoove/unity-ui 2.22.1 → 2.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,1916 +1,32 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  /**
4
- * Unity UI Agent Skills Generator
4
+ * @deprecated Use `npx unity-ui skills` instead.
5
5
  *
6
- * Generates individual SKILL.md files for each Unity UI component
7
- * following the Agent Skills standard (https://agentskills.io).
8
- *
9
- * Run from a project that has @zvoove/unity-ui installed:
10
- *
11
- * npx unity-ui-skills → outputs to .claude/skills/
12
- * npx unity-ui-skills --output .cursor → outputs to .cursor/skills/
13
- * npx unity-ui-skills --output ./my-skills → outputs to ./my-skills/
14
- *
15
- * Skills enable progressive disclosure — AI agents scan skill metadata
16
- * initially but load full content only when relevant to the current task.
6
+ * This shim exists for backward compatibility only.
17
7
  */
18
- import fs from 'fs';
19
- import path from 'path';
20
- import { fileURLToPath } from 'url';
21
-
22
- const __filename = fileURLToPath(import.meta.url);
23
- const __dirname = path.dirname(__filename);
24
-
25
- // ─── Resolve llms.txt from the installed package ────────────────────────────
26
-
27
- function findLlmsTxt() {
28
- // 1. Co-located with this bin (inside the published package)
29
- const fromBin = path.resolve(__dirname, '..', 'dist', 'llms.txt');
30
- if (fs.existsSync(fromBin)) return fromBin;
31
-
32
- // 2. Fallback: llms.txt at the package root (dev / pre-build)
33
- const fromRoot = path.resolve(__dirname, '..', 'llms.txt');
34
- if (fs.existsSync(fromRoot)) return fromRoot;
35
-
36
- return null;
37
- }
38
-
39
- // ─── CLI args ───────────────────────────────────────────────────────────────
40
-
41
- function parseArgs(argv) {
42
- const args = argv.slice(2);
43
- let outputBase = '.claude';
44
-
45
- for (let i = 0; i < args.length; i++) {
46
- if ((args[i] === '--output' || args[i] === '-o') && args[i + 1]) {
47
- outputBase = args[i + 1];
48
- i++;
49
- }
50
- if (args[i] === '--help' || args[i] === '-h') {
51
- console.log(`
52
- Unity UI — Agent Skills Generator
53
-
54
- Usage:
55
- npx unity-ui-skills [options]
56
-
57
- Options:
58
- -o, --output <dir> Base directory for skills output (default: .claude)
59
- Skills will be written to <dir>/skills/unity-ui-*/SKILL.md
60
- -h, --help Show this help message
61
-
62
- Examples:
63
- npx unity-ui-skills # → .claude/skills/
64
- npx unity-ui-skills --output .cursor # → .cursor/skills/
65
- npx unity-ui-skills --output ./ai-skills # → ./ai-skills/skills/
66
- `);
67
- process.exit(0);
68
- }
69
- }
70
-
71
- return { outputBase };
72
- }
73
-
74
- // ─── Name normalization ─────────────────────────────────────────────────────
75
- // Maps llms.txt header names → clean skill identifiers.
76
- const NAME_MAP = {
77
- 'Avatar & AvatarGroup': 'Avatar',
78
- 'Radio (RadioGroup + RadioButton)': 'Radio',
79
- 'Segment (SegmentGroup + SegmentButton)': 'Segment',
80
- 'Common Icons': '__SKIP__',
81
- 'File Type Icons': '__SKIP__',
82
- };
83
-
84
- // ─── shadcn/ui equivalence map ──────────────────────────────────────────────
85
- const SHADCN_MAP = {
86
- Accordion: {
87
- name: 'Accordion',
88
- url: 'https://ui.shadcn.com/docs/components/accordion',
89
- },
90
- ActionCard: null,
91
- Avatar: {
92
- name: 'Avatar',
93
- url: 'https://ui.shadcn.com/docs/components/avatar',
94
- },
95
- Badge: { name: 'Badge', url: 'https://ui.shadcn.com/docs/components/badge' },
96
- Breadcrumbs: {
97
- name: 'Breadcrumb',
98
- url: 'https://ui.shadcn.com/docs/components/breadcrumb',
99
- },
100
- Button: {
101
- name: 'Button',
102
- url: 'https://ui.shadcn.com/docs/components/button',
103
- },
104
- Card: { name: 'Card', url: 'https://ui.shadcn.com/docs/components/card' },
105
- ChatBubble: null,
106
- Checkbox: {
107
- name: 'Checkbox',
108
- url: 'https://ui.shadcn.com/docs/components/checkbox',
109
- },
110
- Chip: {
111
- name: 'Badge',
112
- url: 'https://ui.shadcn.com/docs/components/badge',
113
- note: 'shadcn Badge covers simple labels; Unity UI Chip adds interactivity (filter, input, suggestion types).',
114
- },
115
- CodeBlock: null,
116
- ConfirmationCard: {
117
- name: 'AlertDialog',
118
- url: 'https://ui.shadcn.com/docs/components/alert-dialog',
119
- note: 'AlertDialog is the closest equivalent for confirmation flows, but ConfirmationCard is an inline card, not a modal.',
120
- },
121
- ContentBlock: null,
122
- DatePicker: {
123
- name: 'DatePicker',
124
- url: 'https://ui.shadcn.com/docs/components/date-picker',
125
- },
126
- Dialog: {
127
- name: 'Dialog',
128
- url: 'https://ui.shadcn.com/docs/components/dialog',
129
- },
130
- Divider: {
131
- name: 'Separator',
132
- url: 'https://ui.shadcn.com/docs/components/separator',
133
- },
134
- Expandable: {
135
- name: 'Collapsible',
136
- url: 'https://ui.shadcn.com/docs/components/collapsible',
137
- },
138
- FormLabel: {
139
- name: 'Label',
140
- url: 'https://ui.shadcn.com/docs/components/label',
141
- },
142
- Grid: null,
143
- Icon: null,
144
- InfoBox: {
145
- name: 'Alert',
146
- url: 'https://ui.shadcn.com/docs/components/alert',
147
- },
148
- MessageActions: null,
149
- Pagination: {
150
- name: 'Pagination',
151
- url: 'https://ui.shadcn.com/docs/components/pagination',
152
- },
153
- PopUpMenu: {
154
- name: 'DropdownMenu',
155
- url: 'https://ui.shadcn.com/docs/components/dropdown-menu',
156
- note: 'Also comparable to ContextMenu when using trigger="right-click".',
157
- },
158
- ProgressIndicator: {
159
- name: 'Progress',
160
- url: 'https://ui.shadcn.com/docs/components/progress',
161
- },
162
- Radio: {
163
- name: 'RadioGroup',
164
- url: 'https://ui.shadcn.com/docs/components/radio-group',
165
- },
166
- ScoreCard: null,
167
- Segment: {
168
- name: 'ToggleGroup',
169
- url: 'https://ui.shadcn.com/docs/components/toggle-group',
170
- },
171
- Select: {
172
- name: 'Select',
173
- url: 'https://ui.shadcn.com/docs/components/select',
174
- note: 'When searchable=true, comparable to Combobox.',
175
- },
176
- Sheet: { name: 'Sheet', url: 'https://ui.shadcn.com/docs/components/sheet' },
177
- SideNavigation: {
178
- name: 'Sidebar',
179
- url: 'https://ui.shadcn.com/docs/components/sidebar',
180
- },
181
- Skeleton: {
182
- name: 'Skeleton',
183
- url: 'https://ui.shadcn.com/docs/components/skeleton',
184
- },
185
- Snackbar: {
186
- name: 'Sonner (Toast)',
187
- url: 'https://ui.shadcn.com/docs/components/sonner',
188
- },
189
- Stack: null,
190
- Switch: {
191
- name: 'Switch',
192
- url: 'https://ui.shadcn.com/docs/components/switch',
193
- },
194
- Table: {
195
- name: 'Table / DataTable',
196
- url: 'https://ui.shadcn.com/docs/components/data-table',
197
- },
198
- Tabs: { name: 'Tabs', url: 'https://ui.shadcn.com/docs/components/tabs' },
199
- Tag: {
200
- name: 'Badge',
201
- url: 'https://ui.shadcn.com/docs/components/badge',
202
- note: 'shadcn Badge is the closest match. Unity UI Tag offers more color and tone variants.',
203
- },
204
- TextField: {
205
- name: 'Input',
206
- url: 'https://ui.shadcn.com/docs/components/input',
207
- },
208
- Textarea: {
209
- name: 'Textarea',
210
- url: 'https://ui.shadcn.com/docs/components/textarea',
211
- },
212
- Tooltip: {
213
- name: 'Tooltip',
214
- url: 'https://ui.shadcn.com/docs/components/tooltip',
215
- },
216
- TopBar: null,
217
- Typography: null,
218
- Uploader: null,
219
- useBreakpoint: null,
220
- useClickOutside: null,
221
- };
222
-
223
- // ─── MUI equivalence map ─────────────────────────────────────────────────────
224
- const MUI_BASE = 'https://mui.com/material-ui/react-';
225
- const MUI_X_BASE = 'https://mui.com/x/react-';
226
-
227
- const MUI_MAP = {
228
- Accordion: { name: 'Accordion', url: `${MUI_BASE}accordion/` },
229
- ActionCard: null,
230
- Avatar: { name: 'Avatar', url: `${MUI_BASE}avatar/` },
231
- Badge: { name: 'Badge', url: `${MUI_BASE}badge/` },
232
- Breadcrumbs: { name: 'Breadcrumbs', url: `${MUI_BASE}breadcrumbs/` },
233
- Button: { name: 'Button', url: `${MUI_BASE}button/` },
234
- Card: { name: 'Card', url: `${MUI_BASE}card/` },
235
- ChatBubble: null,
236
- Checkbox: { name: 'Checkbox', url: `${MUI_BASE}checkbox/` },
237
- Chip: { name: 'Chip', url: `${MUI_BASE}chip/` },
238
- CodeBlock: null,
239
- ConfirmationCard: {
240
- name: 'Dialog',
241
- url: `${MUI_BASE}dialog/`,
242
- note: 'Use a Dialog with confirmation actions. ConfirmationCard is an inline card, not a modal.',
243
- },
244
- ContentBlock: null,
245
- DatePicker: {
246
- name: 'DatePicker',
247
- url: `${MUI_X_BASE}date-picker/`,
248
- note: 'MUI X component — requires @mui/x-date-pickers.',
249
- },
250
- Dialog: { name: 'Dialog', url: `${MUI_BASE}dialog/` },
251
- Divider: { name: 'Divider', url: `${MUI_BASE}divider/` },
252
- Expandable: { name: 'Collapse', url: `${MUI_BASE}collapse/` },
253
- FormLabel: { name: 'FormLabel', url: `${MUI_BASE}text-field/` },
254
- Grid: { name: 'Grid', url: `${MUI_BASE}grid/` },
255
- Icon: { name: 'SvgIcon', url: `${MUI_BASE}icons/` },
256
- InfoBox: { name: 'Alert', url: `${MUI_BASE}alert/` },
257
- MessageActions: null,
258
- Pagination: { name: 'Pagination', url: `${MUI_BASE}pagination/` },
259
- PopUpMenu: { name: 'Menu', url: `${MUI_BASE}menu/` },
260
- ProgressIndicator: {
261
- name: 'LinearProgress / CircularProgress',
262
- url: `${MUI_BASE}progress/`,
263
- },
264
- Radio: { name: 'RadioGroup', url: `${MUI_BASE}radio-button/` },
265
- ScoreCard: null,
266
- Segment: {
267
- name: 'ToggleButtonGroup',
268
- url: `${MUI_BASE}toggle-button/`,
269
- },
270
- Select: { name: 'Select', url: `${MUI_BASE}select/` },
271
- Sheet: {
272
- name: 'Drawer',
273
- url: `${MUI_BASE}drawer/`,
274
- note: 'Use a temporary or persistent Drawer.',
275
- },
276
- SideNavigation: {
277
- name: 'Drawer (persistent)',
278
- url: `${MUI_BASE}drawer/`,
279
- },
280
- Skeleton: { name: 'Skeleton', url: `${MUI_BASE}skeleton/` },
281
- Snackbar: { name: 'Snackbar', url: `${MUI_BASE}snackbar/` },
282
- Stack: { name: 'Stack', url: `${MUI_BASE}stack/` },
283
- Switch: { name: 'Switch', url: `${MUI_BASE}switch/` },
284
- Table: {
285
- name: 'Table / DataGrid',
286
- url: `${MUI_BASE}table/`,
287
- note: 'For advanced features use MUI X DataGrid (@mui/x-data-grid).',
288
- },
289
- Tabs: { name: 'Tabs', url: `${MUI_BASE}tabs/` },
290
- Tag: {
291
- name: 'Chip',
292
- url: `${MUI_BASE}chip/`,
293
- note: 'MUI Chip covers static labels; Unity UI Tag is display-only with more color/tone variants.',
294
- },
295
- TextField: { name: 'TextField', url: `${MUI_BASE}text-field/` },
296
- Textarea: {
297
- name: 'TextField (multiline)',
298
- url: `${MUI_BASE}text-field/`,
299
- note: 'Use TextField with multiline and rows props.',
300
- },
301
- Tooltip: { name: 'Tooltip', url: `${MUI_BASE}tooltip/` },
302
- TopBar: { name: 'AppBar', url: `${MUI_BASE}app-bar/` },
303
- Typography: { name: 'Typography', url: `${MUI_BASE}typography/` },
304
- Uploader: null,
305
- useBreakpoint: {
306
- name: 'useMediaQuery',
307
- url: 'https://mui.com/material-ui/react-use-media-query/',
308
- },
309
- useClickOutside: {
310
- name: 'ClickAwayListener',
311
- url: `${MUI_BASE}click-away-listener/`,
312
- },
313
- };
314
-
315
- // ─── Storybook documentation links ───────────────────────────────────────────
316
- // Maps component name → Storybook title path (as defined in *.stories.tsx).
317
- // URL: https://main--67c03f013fea08bb2f926e5f.chromatic.com/?path=/docs/<path>--docs
318
- const STORYBOOK_BASE =
319
- 'https://main--67c03f013fea08bb2f926e5f.chromatic.com/?path=/docs/';
320
-
321
- const STORYBOOK_MAP = {
322
- Accordion: 'Components/Accordion',
323
- ActionCard: 'Chat/ActionCard',
324
- Avatar: 'Components/Avatar/Avatar',
325
- Badge: 'Components/Badge',
326
- Breadcrumbs: 'Navigation/Breadcrumbs',
327
- Button: 'Components/Buttons/Button',
328
- Card: 'Components/Card',
329
- ChatBubble: 'Chat/ChatBubble',
330
- Checkbox: 'Forms/Checkbox',
331
- Chip: 'Components/Chip',
332
- CodeBlock: 'Components/CodeBlock',
333
- ConfirmationCard: 'Components/ConfirmationCard',
334
- ContentBlock: 'Components/ContentBlock',
335
- DatePicker: 'Forms/DatePicker',
336
- Dialog: 'Components/Dialog',
337
- Divider: 'Components/Divider',
338
- Expandable: 'Components/Expandable',
339
- FormLabel: 'Forms/FormLabel',
340
- Grid: 'Layout/Grid',
341
- Icon: 'Components/Icon',
342
- InfoBox: 'Components/InfoBox',
343
- MessageActions: 'Chat/MessageActions',
344
- Pagination: 'Components/Pagination',
345
- PopUpMenu: 'Components/PopUpMenu',
346
- ProgressIndicator: 'Components/ProgressIndicator',
347
- Radio: 'Forms/Radio/RadioGroup',
348
- ScoreCard: 'Components/ScoreCard',
349
- Segment: 'Components/Buttons/Segment/SegmentGroup',
350
- Select: 'Forms/Select',
351
- Sheet: 'Components/Sheet',
352
- SideNavigation: 'Navigation/SideNavigation',
353
- Skeleton: 'Components/Skeleton',
354
- Snackbar: 'Components/Snackbar',
355
- Stack: 'Layout/Stack',
356
- Switch: 'Forms/Switch',
357
- Table: 'Components/Table',
358
- Tabs: 'Navigation/Tabs',
359
- Tag: 'Components/Tag',
360
- TextField: 'Forms/TextField',
361
- Textarea: 'Forms/Textarea',
362
- Tooltip: 'Components/Tooltip',
363
- TopBar: 'Components/TopBar',
364
- Typography: 'Components/Typography',
365
- Uploader: 'Forms/Uploader',
366
- };
367
-
368
- function storybookUrl(name) {
369
- const title = STORYBOOK_MAP[name];
370
- if (!title) return null;
371
- const slug = title.toLowerCase().replaceAll('/', '-');
372
- return `${STORYBOOK_BASE}${slug}--docs`;
373
- }
374
-
375
- // ─── Component categories ───────────────────────────────────────────────────
376
- const CATEGORIES = {
377
- Stack: 'layout',
378
- Grid: 'layout',
379
- Card: 'layout',
380
- Divider: 'layout',
381
- Expandable: 'layout',
382
- ContentBlock: 'layout',
383
- Typography: 'typography',
384
- TextField: 'form',
385
- Textarea: 'form',
386
- Select: 'form',
387
- Checkbox: 'form',
388
- Radio: 'form',
389
- Switch: 'form',
390
- DatePicker: 'form',
391
- Uploader: 'form',
392
- FormLabel: 'form',
393
- Button: 'action',
394
- Segment: 'action',
395
- PopUpMenu: 'action',
396
- Table: 'data-display',
397
- Tag: 'data-display',
398
- Chip: 'data-display',
399
- Avatar: 'data-display',
400
- Badge: 'data-display',
401
- Icon: 'data-display',
402
- Skeleton: 'data-display',
403
- ProgressIndicator: 'data-display',
404
- CodeBlock: 'data-display',
405
- Tabs: 'navigation',
406
- Breadcrumbs: 'navigation',
407
- SideNavigation: 'navigation',
408
- Pagination: 'navigation',
409
- TopBar: 'navigation',
410
- Dialog: 'feedback',
411
- Sheet: 'feedback',
412
- Snackbar: 'feedback',
413
- InfoBox: 'feedback',
414
- ConfirmationCard: 'feedback',
415
- Tooltip: 'feedback',
416
- Accordion: 'compound',
417
- ActionCard: 'compound',
418
- ChatBubble: 'compound',
419
- MessageActions: 'compound',
420
- ScoreCard: 'compound',
421
- useBreakpoint: 'hook',
422
- useClickOutside: 'hook',
423
- };
424
-
425
- // Category-specific rules appended to each skill
426
- const CATEGORY_RULES = {
427
- layout: [
428
- '- Use Stack and Grid for layout instead of raw div + CSS',
429
- '- Use the spacing scale for gap/padding/margin — never use arbitrary pixel values',
430
- ],
431
- form: [
432
- '- Never create custom form inputs when this component exists',
433
- '- Use responsive `density` prop when available for compact layouts',
434
- ],
435
- feedback: [
436
- '- Never create custom overlays or toast systems when this component exists',
437
- ],
438
- typography: [
439
- '- Use Typography for all text — never use raw `<p>`, `<h1>`, `<span>` etc.',
440
- ],
441
- hook: ['- Import hooks directly from `@zvoove/unity-ui`'],
442
- };
443
-
444
- // ─── Parse llms.txt ─────────────────────────────────────────────────────────
445
-
446
- function detectSection(line) {
447
- const sectionHeaders = {
448
- '## Setup': 'setup',
449
- '## Responsive Props': 'responsive',
450
- '## RULES FOR AI AGENTS': 'rules',
451
- '## SPACING SCALE': 'spacing',
452
- '## ICON NAMES': 'icons',
453
- '## STYLING': 'styling',
454
- '## CUSTOM COMPONENT': 'customComponent',
455
- };
456
-
457
- for (const [prefix, key] of Object.entries(sectionHeaders)) {
458
- if (line.startsWith(prefix)) return key;
459
- }
460
- if (line.startsWith('## EXAMPLE:')) return 'example';
461
- if (
462
- /^## [A-Z]+ COMPONENTS?$/.test(line) ||
463
- /^## (HOOKS|COMPOUND COMPONENTS|TYPOGRAPHY)$/.test(line)
464
- ) {
465
- return 'category-header';
466
- }
467
- return null;
468
- }
469
-
470
- function mergeComponent(components, buckets, name, lines) {
471
- if (!name || lines.length === 0) return;
472
- const normalizedName = NAME_MAP[name] ?? name;
473
-
474
- if (normalizedName === '__SKIP__') {
475
- buckets.icons.push('', `### ${name}`, ...lines);
476
- return;
477
- }
478
- const text = lines.join('\n').trim();
479
- components[normalizedName] = components[normalizedName]
480
- ? components[normalizedName] + '\n\n' + text
481
- : text;
482
- }
483
-
484
- function parseLlmsTxt(content) {
485
- const components = {};
486
- const buckets = {
487
- setup: [],
488
- responsive: [],
489
- rules: [],
490
- spacing: [],
491
- styling: [],
492
- customComponent: [],
493
- icons: [],
494
- example: [],
495
- };
496
-
497
- let activeSection = null;
498
- let currentComponent = null;
499
- let currentLines = [];
500
-
501
- function flush() {
502
- mergeComponent(components, buckets, currentComponent, currentLines);
503
- currentComponent = null;
504
- currentLines = [];
505
- }
506
-
507
- function handleSectionHeader(line) {
508
- flush();
509
- const section = detectSection(line);
510
- activeSection = section === 'category-header' ? null : section;
511
- }
512
-
513
- function handleComponentHeader(line) {
514
- // In prose sections, ### sub-headings are content, not component names
515
- if (activeSection === 'styling' || activeSection === 'customComponent') {
516
- handleContentLine(line);
517
- return;
518
- }
519
- flush();
520
- currentComponent = line.slice(4).trim();
521
- if (activeSection !== 'icons') activeSection = null;
522
- }
523
-
524
- function handleContentLine(line) {
525
- if (currentComponent) {
526
- currentLines.push(line);
527
- return;
528
- }
529
- if (activeSection && buckets[activeSection]) {
530
- buckets[activeSection].push(line);
531
- }
532
- }
533
-
534
- for (const line of content.split('\n')) {
535
- if (line === '---') continue;
536
- if (line.startsWith('## ')) handleSectionHeader(line);
537
- else if (line.startsWith('### ')) handleComponentHeader(line);
538
- else handleContentLine(line);
539
- }
540
-
541
- flush();
542
-
543
- const trimBucket = (key) => buckets[key].join('\n').trim();
544
- return {
545
- setup: trimBucket('setup'),
546
- responsive: trimBucket('responsive'),
547
- rules: trimBucket('rules'),
548
- spacing: trimBucket('spacing'),
549
- styling: trimBucket('styling'),
550
- customComponent: trimBucket('customComponent'),
551
- icons: trimBucket('icons'),
552
- example: trimBucket('example'),
553
- components,
554
- };
555
- }
556
-
557
- // ─── Skill generators ───────────────────────────────────────────────────────
558
-
559
- function kebabCase(str) {
560
- return str
561
- .replace(/([a-z])([A-Z])/g, '$1-$2')
562
- .toLowerCase()
563
- .replace(/[^a-z0-9]+/g, '-')
564
- .replace(/^-|-$/g, '');
565
- }
566
-
567
- function generateComponentSkill(name, content) {
568
- const shadcn = SHADCN_MAP[name];
569
- const category = CATEGORIES[name] || 'component';
570
- const slug = kebabCase(name);
571
- const docsUrl = storybookUrl(name);
572
-
573
- let description = `${name} component from Unity UI (@zvoove/unity-ui). Category: ${category}.`;
574
- if (shadcn) {
575
- description += ` Comparable to shadcn/ui ${shadcn.name}.`;
576
- }
577
- description += String.raw`\nTRIGGER when: user needs ${name} in a project using @zvoove/unity-ui.\nDO NOT read dist/ or source files — all API docs are in this skill.`;
578
-
579
- const frontmatter = [
580
- '---',
581
- `name: unity-ui-${slug}`,
582
- `description: "${description}"`,
583
- `category: ${category}`,
584
- ];
585
- if (shadcn) frontmatter.push(`shadcn_equivalent: ${shadcn.name}`);
586
- if (docsUrl) frontmatter.push(`docs: ${docsUrl}`);
587
- frontmatter.push('---');
588
-
589
- const body = [`# Unity UI — ${name}\n`];
590
-
591
- const links = [];
592
- if (docsUrl) links.push(`[Storybook docs](${docsUrl})`);
593
- if (shadcn) links.push(`[shadcn/ui ${shadcn.name}](${shadcn.url})`);
594
- if (links.length > 0) {
595
- body.push(`> ${links.join(' | ')}`);
596
- if (shadcn?.note) body.push(`> ${shadcn.note}`);
597
- body.push('');
598
- }
599
-
600
- body.push(content);
601
- body.push('\n## Rules\n');
602
- body.push('- Always import from `@zvoove/unity-ui`');
603
- body.push('- Never recreate components that exist in this library');
604
-
605
- const extraRules = CATEGORY_RULES[category] || [];
606
- body.push(...extraRules);
607
- body.push(
608
- '- Use responsive props where applicable (single value or breakpoint object)'
609
- );
610
-
611
- return frontmatter.join('\n') + '\n\n' + body.join('\n') + '\n';
612
- }
613
-
614
- function generateSetupSkill(parsed) {
615
- const frontmatter = [
616
- '---',
617
- 'name: unity-ui-setup',
618
- 'description: "Setup and installation for Unity UI (@zvoove/unity-ui). React component library with 40+ components, Tailwind CSS 4, dark mode, responsive props."',
619
- 'category: setup',
620
- '---',
621
- ].join('\n');
622
-
623
- return (
624
- frontmatter +
625
- `\n\n# Unity UI — Setup & Overview
626
-
627
- > Unity UI (\`@zvoove/unity-ui\`) is a React component library with 40+ accessible, themeable components built with TypeScript and Tailwind CSS 4.
628
-
629
- ${parsed.setup}
630
-
631
- ## Responsive Props
632
-
633
- ${parsed.responsive}
634
-
635
- ## Spacing Scale
636
-
637
- ${parsed.spacing}
638
-
639
- ## Example: Contact Form
640
-
641
- ${parsed.example}
642
-
643
- ## Rules
644
-
645
- ${parsed.rules}
646
- `
647
- );
648
- }
649
-
650
- function generateIconsSkill(parsed) {
651
- const frontmatter = [
652
- '---',
653
- 'name: unity-ui-icons',
654
- 'description: "Icon names and usage for Unity UI (@zvoove/unity-ui). Semantic icon names based on Phosphor Icons. Use with <Icon name=\\"icon-name\\" /> component."',
655
- 'category: data-display',
656
- '---',
657
- ].join('\n');
658
-
659
- return (
660
- frontmatter +
661
- `\n\n# Unity UI — Icons
662
-
663
- > Unity UI uses its own semantic icon names (not raw Phosphor icon names). Pass them via \`<Icon name="icon-name" />\`.
664
-
665
- ${parsed.icons}
666
-
667
- ## Rules
668
-
669
- - Always import Icon from \`@zvoove/unity-ui\`
670
- - Use semantic icon name strings — never import Phosphor icon components directly
671
- - Use \`getIconForFileExtension(extension)\` to resolve file extensions to icon names automatically
672
- `
673
- );
674
- }
675
-
676
- function generateIndexSkill(componentNames) {
677
- const shadcnRows = componentNames
678
- .filter((n) => SHADCN_MAP[n])
679
- .map(
680
- (n) =>
681
- `| ${n} | [${SHADCN_MAP[n].name}](${SHADCN_MAP[n].url}) | \`unity-ui-${kebabCase(n)}\` |`
682
- );
683
-
684
- const muiRows = componentNames
685
- .filter((n) => MUI_MAP[n])
686
- .map(
687
- (n) =>
688
- `| ${n} | [${MUI_MAP[n].name}](${MUI_MAP[n].url}) | \`unity-ui-${kebabCase(n)}\` |`
689
- );
690
-
691
- const uniqueRows = componentNames
692
- .filter((n) => !SHADCN_MAP[n] && !MUI_MAP[n])
693
- .map((n) => `| ${n} | \`unity-ui-${kebabCase(n)}\` |`);
694
-
695
- const frontmatter = [
696
- '---',
697
- 'name: unity-ui-index',
698
- 'description: "Component index for Unity UI (@zvoove/unity-ui). Maps all 40+ components to their shadcn/ui and MUI equivalents. Use this to find the right component or migrate from another library."',
699
- 'category: index',
700
- '---',
701
- ].join('\n');
702
-
703
- return (
704
- frontmatter +
705
- `\n\n# Unity UI — Component Index
706
-
707
- > Use this index to find the right Unity UI component. Each component has its own skill with full documentation.
708
-
709
- ## shadcn/ui → Unity UI
710
-
711
- | Unity UI | shadcn/ui | Skill |
712
- |----------|-----------|-------|
713
- ${shadcnRows.join('\n')}
714
-
715
- ## MUI → Unity UI
716
-
717
- | Unity UI | MUI | Skill |
718
- |----------|-----|-------|
719
- ${muiRows.join('\n')}
720
-
721
- ## Unity UI–Only Components
722
-
723
- | Unity UI | Skill |
724
- |----------|-------|
725
- ${uniqueRows.map((r) => r.replace(' | — ', '')).join('\n')}
726
-
727
- ## How to Use Skills
728
-
729
- Each skill is a standalone file with full props, usage examples, and rules for one component.
730
- AI agents should load the relevant skill when they need to use a specific component.
731
- `
732
- );
733
- }
734
-
735
- function generateStylingSkill(parsed) {
736
- const description = [
737
- 'Styling guide for Unity UI (@zvoove/unity-ui). Tailwind CSS v4 + design tokens + tailwind-variants.',
738
- 'TRIGGER when: user needs to style a custom element, asks about CSS/styling, or produces inline styles / arbitrary Tailwind values in a @zvoove/unity-ui project.',
739
- 'DO NOT use inline styles, arbitrary Tailwind values, or raw CSS — use design tokens and tv() instead.',
740
- ].join(String.raw`\n`);
741
-
742
- const frontmatter = [
743
- '---',
744
- 'name: unity-ui-styling',
745
- `description: "${description}"`,
746
- 'category: styling',
747
- '---',
748
- ].join('\n');
749
-
750
- return (
751
- frontmatter + `\n\n# Unity UI — Styling Guide\n\n` + parsed.styling + '\n'
752
- );
753
- }
754
-
755
- function generateCustomComponentSkill(parsed) {
756
- const description = [
757
- 'Guide for creating a custom component in a project using @zvoove/unity-ui, following the same conventions as the design system.',
758
- 'TRIGGER when: user needs to create a custom component that does not exist in @zvoove/unity-ui, following Unity UI patterns.',
759
- 'DO NOT TRIGGER when: an existing Unity UI component already covers the use case — use that instead.',
760
- ].join(String.raw`\n`);
761
-
762
- const frontmatter = [
763
- '---',
764
- 'name: unity-ui-custom-component',
765
- `description: "${description}"`,
766
- 'category: authoring',
767
- '---',
768
- ].join('\n');
769
-
770
- return (
771
- frontmatter +
772
- `\n\n# Unity UI — Creating a Custom Component\n\n` +
773
- parsed.customComponent +
774
- '\n'
775
- );
776
- }
777
-
778
- function generateFormsSkill() {
779
- const description = [
780
- 'Form composition guide for @zvoove/unity-ui with react-hook-form and zod.',
781
- 'TRIGGER when: user is building a form, adding validation, wiring inputs to a form library, or asking how to use Unity UI inputs with react-hook-form.',
782
- 'DO NOT use register() — Unity UI inputs are controlled components; always use Controller.',
783
- ].join(String.raw`\n`);
784
-
785
- const frontmatter = [
786
- '---',
787
- 'name: unity-ui-forms',
788
- `description: "${description}"`,
789
- 'category: forms',
790
- '---',
791
- ].join('\n');
792
-
793
- return (
794
- frontmatter +
795
- `\n\n# Unity UI — Form Composition
796
-
797
- > Unity UI inputs are **controlled components**. Use \`Controller\` from react-hook-form — never \`register()\`.
798
-
799
- ## Installing
800
-
801
- \`\`\`bash
802
- npm install react-hook-form zod @hookform/resolvers
803
- \`\`\`
804
-
805
- ## Basic Pattern
806
-
807
- \`\`\`tsx
808
- import { useForm, Controller } from 'react-hook-form';
809
- import { zodResolver } from '@hookform/resolvers/zod';
810
- import { z } from 'zod';
811
- import { Button, Select, Stack, TextField } from '@zvoove/unity-ui';
812
-
813
- const schema = z.object({
814
- email: z.string().email('Ungültige E-Mail-Adresse'),
815
- role: z.string().min(1, 'Pflichtfeld'),
816
- });
817
-
818
- type FormValues = z.infer<typeof schema>;
819
-
820
- export function ExampleForm() {
821
- const { control, handleSubmit } = useForm<FormValues>({
822
- resolver: zodResolver(schema),
823
- defaultValues: { email: '', role: '' },
824
- });
825
-
826
- return (
827
- <form onSubmit={handleSubmit((data) => console.log(data))}>
828
- <Stack direction="column" gap="md">
829
- <Controller
830
- name="email"
831
- control={control}
832
- render={({ field, fieldState }) => (
833
- <TextField
834
- {...field}
835
- label="E-Mail"
836
- error={!!fieldState.error}
837
- errorMessage={fieldState.error?.message}
838
- />
839
- )}
840
- />
841
- <Controller
842
- name="role"
843
- control={control}
844
- render={({ field, fieldState }) => (
845
- <Select
846
- {...field}
847
- label="Rolle"
848
- options={[
849
- { value: 'admin', label: 'Administrator' },
850
- { value: 'user', label: 'Benutzer' },
851
- ]}
852
- error={!!fieldState.error}
853
- errorMessage={fieldState.error?.message}
854
- />
855
- )}
856
- />
857
- <Stack direction="row" gap="sm" justify="flex-end">
858
- <Button type="submit" variant="filled">Speichern</Button>
859
- </Stack>
860
- </Stack>
861
- </form>
862
- );
863
- }
864
- \`\`\`
865
-
866
- ## Input Binding Reference
867
-
868
- | Component | Binding pattern | Notes |
869
- |-----------|----------------|-------|
870
- | TextField | \`{...field}\` | value + onChange + onBlur + name |
871
- | Textarea | \`{...field}\` | same as TextField |
872
- | Select | \`{...field}\` | value + onChange |
873
- | Checkbox | \`checked={field.value}\` | use \`checked\`, not \`value\` |
874
- | Switch | \`checked={field.value}\` | use \`checked\`, not \`value\` |
875
- | DatePicker | \`value={field.value} onChange={field.onChange}\` | pass date value directly |
876
- | Radio (RadioGroup) | \`value={field.value} onChange={field.onChange}\` | — |
877
-
878
- ## Checkbox / Switch Pattern
879
-
880
- \`\`\`tsx
881
- <Controller
882
- name="acceptTerms"
883
- control={control}
884
- render={({ field }) => (
885
- <Checkbox
886
- label="Ich akzeptiere die Nutzungsbedingungen"
887
- checked={field.value ?? false}
888
- onChange={(e) => field.onChange(e.target.checked)}
889
- />
890
- )}
891
- />
892
- \`\`\`
893
-
894
- ## DatePicker Pattern
895
-
896
- \`\`\`tsx
897
- <Controller
898
- name="birthDate"
899
- control={control}
900
- render={({ field, fieldState }) => (
901
- <DatePicker
902
- label="Geburtsdatum"
903
- value={field.value}
904
- onChange={field.onChange}
905
- error={!!fieldState.error}
906
- errorMessage={fieldState.error?.message}
907
- />
908
- )}
909
- />
910
- \`\`\`
911
-
912
- ## Complete Login Form
913
-
914
- \`\`\`tsx
915
- import { useForm, Controller } from 'react-hook-form';
916
- import { zodResolver } from '@hookform/resolvers/zod';
917
- import { z } from 'zod';
918
- import { Button, Card, Checkbox, Stack, TextField, Typography } from '@zvoove/unity-ui';
919
-
920
- const loginSchema = z.object({
921
- email: z.string().email('Ungültige E-Mail-Adresse'),
922
- password: z.string().min(8, 'Mindestens 8 Zeichen erforderlich'),
923
- rememberMe: z.boolean().optional(),
924
- });
925
-
926
- type LoginValues = z.infer<typeof loginSchema>;
927
-
928
- export function LoginForm() {
929
- const {
930
- control,
931
- handleSubmit,
932
- formState: { isSubmitting },
933
- } = useForm<LoginValues>({
934
- resolver: zodResolver(loginSchema),
935
- defaultValues: { email: '', password: '', rememberMe: false },
936
- });
937
-
938
- const onSubmit = async (data: LoginValues) => {
939
- await login(data);
940
- };
941
-
942
- return (
943
- <Card padding="xl">
944
- <form onSubmit={handleSubmit(onSubmit)}>
945
- <Stack direction="column" gap="md">
946
- <Typography variant="title-large">Anmelden</Typography>
947
-
948
- <Controller
949
- name="email"
950
- control={control}
951
- render={({ field, fieldState }) => (
952
- <TextField
953
- {...field}
954
- label="E-Mail"
955
- type="email"
956
- error={!!fieldState.error}
957
- errorMessage={fieldState.error?.message}
958
- />
959
- )}
960
- />
961
-
962
- <Controller
963
- name="password"
964
- control={control}
965
- render={({ field, fieldState }) => (
966
- <TextField
967
- {...field}
968
- label="Passwort"
969
- type="password"
970
- error={!!fieldState.error}
971
- errorMessage={fieldState.error?.message}
972
- />
973
- )}
974
- />
975
-
976
- <Controller
977
- name="rememberMe"
978
- control={control}
979
- render={({ field }) => (
980
- <Checkbox
981
- label="Angemeldet bleiben"
982
- checked={field.value ?? false}
983
- onChange={(e) => field.onChange(e.target.checked)}
984
- />
985
- )}
986
- />
987
-
988
- <Stack direction="row" justify="flex-end">
989
- <Button type="submit" variant="filled" disabled={isSubmitting}>
990
- {isSubmitting ? 'Wird angemeldet…' : 'Anmelden'}
991
- </Button>
992
- </Stack>
993
- </Stack>
994
- </form>
995
- </Card>
996
- );
997
- }
998
- \`\`\`
999
-
1000
- ## Rules
1001
-
1002
- - NEVER use \`register()\` — Unity UI inputs are controlled components; always use \`Controller\`
1003
- - ALWAYS use \`zodResolver\` from \`@hookform/resolvers/zod\` for schema validation
1004
- - Checkbox and Switch require \`checked={field.value}\` — not \`value\`
1005
- - Display errors with \`error={!!fieldState.error}\` + \`errorMessage={fieldState.error?.message}\`
1006
- - Use \`Stack\` for form layout — never raw \`<div>\` with flexbox CSS
1007
- - Default texts, labels, and validation messages must be in German
1008
- `
1009
- );
1010
- }
1011
-
1012
- function generateLayoutsSkill() {
1013
- const description = [
1014
- 'Common app layout patterns for @zvoove/unity-ui: app shells, dashboards, settings pages, master/detail.',
1015
- 'TRIGGER when: user is building a page layout, app shell, or multi-section UI in a @zvoove/unity-ui project.',
1016
- 'DO NOT use raw div + CSS for layout — always use Stack, Grid, TopBar, SideNavigation.',
1017
- ].join(String.raw`\n`);
1018
-
1019
- const frontmatter = [
1020
- '---',
1021
- 'name: unity-ui-layouts',
1022
- `description: "${description}"`,
1023
- 'category: layout',
1024
- '---',
1025
- ].join('\n');
1026
-
1027
- return (
1028
- frontmatter +
1029
- `\n\n# Unity UI — Layout Patterns
1030
-
1031
- > Use \`Stack\` and \`Grid\` for all layout. Never use raw \`<div style={{ display: 'flex' }}>\`.
1032
-
1033
- ## App Shell (TopBar + SideNavigation)
1034
-
1035
- \`\`\`tsx
1036
- import { Avatar, SideNavigation, Stack, TopBar } from '@zvoove/unity-ui';
1037
-
1038
- export function AppShell({ children }: { children: React.ReactNode }) {
1039
- const [activeId, setActiveId] = useState('dashboard');
1040
-
1041
- return (
1042
- <Stack direction="column" style={{ height: '100vh' }}>
1043
- <TopBar
1044
- title="Meine App"
1045
- actions={<Avatar initials="AB" />}
1046
- />
1047
- <Stack direction="row" style={{ flex: 1, overflow: 'hidden' }}>
1048
- <SideNavigation
1049
- items={[
1050
- { id: 'dashboard', label: 'Dashboard', icon: 'House' },
1051
- { id: 'users', label: 'Benutzer', icon: 'Users' },
1052
- { id: 'settings', label: 'Einstellungen', icon: 'Gear' },
1053
- ]}
1054
- activeId={activeId}
1055
- onSelect={setActiveId}
1056
- />
1057
- <Stack
1058
- direction="column"
1059
- padding="xl"
1060
- style={{ flex: 1, overflow: 'auto' }}
1061
- >
1062
- {children}
1063
- </Stack>
1064
- </Stack>
1065
- </Stack>
1066
- );
1067
- }
1068
- \`\`\`
1069
-
1070
- ## Dashboard Layout
1071
-
1072
- \`\`\`tsx
1073
- <Stack direction="column" gap="lg" padding="xl">
1074
- <Typography variant="headline-small">Dashboard</Typography>
1075
-
1076
- {/* KPI summary row */}
1077
- <Grid columns={{ mobile: 1, tablet: 2, desktop: 4 }} gap="md">
1078
- {kpis.map((kpi) => (
1079
- <Card key={kpi.id} padding="md">
1080
- <Stack direction="column" gap="xs">
1081
- <Typography variant="label-medium">{kpi.label}</Typography>
1082
- <Typography variant="display-small">{kpi.value}</Typography>
1083
- </Stack>
1084
- </Card>
1085
- ))}
1086
- </Grid>
8
+ import { runSkills } from './commands/skills.mjs';
1087
9
 
1088
- {/* Main area: 2/3 table + 1/3 sidebar */}
1089
- <Grid columns={{ mobile: 1, laptop: 3 }} gap="lg">
1090
- <Stack style={{ gridColumn: 'span 2' }}>
1091
- <Card padding="md">
1092
- <Table columns={columns} rows={rows} />
1093
- </Card>
1094
- </Stack>
1095
- <Stack direction="column" gap="md">
1096
- <Card padding="md">
1097
- <Typography variant="title-medium">Aktivität</Typography>
1098
- {/* activity list */}
1099
- </Card>
1100
- </Stack>
1101
- </Grid>
1102
- </Stack>
1103
- \`\`\`
1104
-
1105
- ## Settings Page (Two-Column)
1106
-
1107
- \`\`\`tsx
1108
- <Stack direction="column" gap="xl" padding="xl">
1109
- <Typography variant="headline-small">Einstellungen</Typography>
1110
-
1111
- <Stack direction={{ mobile: 'column', tablet: 'row' }} gap="xl">
1112
- {/* Left: section label */}
1113
- <Stack direction="column" gap="xs" style={{ minWidth: 200 }}>
1114
- <Typography variant="title-medium">Profil</Typography>
1115
- <Typography variant="body-medium">Persönliche Informationen.</Typography>
1116
- </Stack>
1117
-
1118
- {/* Right: fields */}
1119
- <Card padding="lg" style={{ flex: 1 }}>
1120
- <Stack direction="column" gap="md">
1121
- <Stack direction={{ mobile: 'column', tablet: 'row' }} gap="md">
1122
- <TextField label="Vorname" value={firstName} onChange={setFirstName} />
1123
- <TextField label="Nachname" value={lastName} onChange={setLastName} />
1124
- </Stack>
1125
- <TextField label="E-Mail" value={email} onChange={setEmail} />
1126
- <Switch
1127
- label="E-Mail-Benachrichtigungen aktivieren"
1128
- checked={notifications}
1129
- onChange={(e) => setNotifications(e.target.checked)}
1130
- />
1131
- </Stack>
1132
- </Card>
1133
- </Stack>
1134
-
1135
- {/* Sticky action bar */}
1136
- <Stack direction="row" gap="sm" justify="flex-end">
1137
- <Button variant="outlined">Abbrechen</Button>
1138
- <Button variant="filled">Speichern</Button>
1139
- </Stack>
1140
- </Stack>
1141
- \`\`\`
1142
-
1143
- ## Master / Detail (List + Detail)
1144
-
1145
- \`\`\`tsx
1146
- <Stack
1147
- direction={{ mobile: 'column', tablet: 'row' }}
1148
- gap="md"
1149
- padding="xl"
1150
- style={{ height: '100%' }}
1151
- >
1152
- {/* List panel */}
1153
- <Stack direction="column" gap="sm" style={{ width: 300, flexShrink: 0 }}>
1154
- {items.map((item) => (
1155
- <Card
1156
- key={item.id}
1157
- padding="md"
1158
- onClick={() => setSelected(item.id)}
1159
- >
1160
- <Typography variant="body-medium">{item.title}</Typography>
1161
- </Card>
1162
- ))}
1163
- </Stack>
1164
-
1165
- {/* Detail panel */}
1166
- <Stack direction="column" gap="md" style={{ flex: 1 }}>
1167
- {selected ? (
1168
- <Card padding="lg">{/* detail content */}</Card>
1169
- ) : (
1170
- <Stack direction="column" align="center" justify="center" style={{ height: '100%' }}>
1171
- <Typography variant="body-medium">Wählen Sie einen Eintrag aus.</Typography>
1172
- </Stack>
1173
- )}
1174
- </Stack>
1175
- </Stack>
1176
- \`\`\`
1177
-
1178
- ## Centered Auth / Empty State
1179
-
1180
- \`\`\`tsx
1181
- <Stack
1182
- direction="column"
1183
- align="center"
1184
- justify="center"
1185
- style={{ minHeight: '100vh' }}
1186
- gap="lg"
1187
- >
1188
- <Card padding="xl" style={{ width: '100%', maxWidth: 400 }}>
1189
- <Stack direction="column" gap="md">
1190
- <Typography variant="headline-small">Anmelden</Typography>
1191
- {/* form fields */}
1192
- </Stack>
1193
- </Card>
1194
- </Stack>
1195
- \`\`\`
1196
-
1197
- ## Card Grid (Content Listing)
1198
-
1199
- \`\`\`tsx
1200
- <Stack direction="column" gap="lg" padding="xl">
1201
- <Stack direction="row" justify="space-between" align="center">
1202
- <Typography variant="headline-small">Projekte</Typography>
1203
- <Button variant="filled" icon="Plus">Neues Projekt</Button>
1204
- </Stack>
1205
-
1206
- <Grid columns={{ mobile: 1, tablet: 2, desktop: 3 }} gap="md">
1207
- {projects.map((project) => (
1208
- <Card key={project.id} padding="md">
1209
- <Stack direction="column" gap="sm">
1210
- <Typography variant="title-medium">{project.name}</Typography>
1211
- <Typography variant="body-small">{project.description}</Typography>
1212
- <Stack direction="row" gap="xs" justify="flex-end">
1213
- <Button variant="ghost" size="sm">Bearbeiten</Button>
1214
- </Stack>
1215
- </Stack>
1216
- </Card>
1217
- ))}
1218
- </Grid>
1219
- </Stack>
1220
- \`\`\`
1221
-
1222
- ## Rules
1223
-
1224
- - Use \`Stack\` and \`Grid\` for ALL layout — never \`<div style={{ display: 'flex' }}>\`
1225
- - Use \`TopBar\` + \`SideNavigation\` for app shells — never custom nav bars
1226
- - Use \`Card\` for content containers — never styled divs
1227
- - Use spacing tokens for gap/padding — never arbitrary pixel values (\`gap-lg\` not \`gap-5\`)
1228
- - Always make layouts responsive with \`ResponsiveType\` props
1229
- - SideNavigation handles its own responsive collapse — do not wrap it in conditional rendering
1230
- `
1231
- );
1232
- }
1233
-
1234
- function generateDarkModeSkill() {
1235
- const description = [
1236
- 'Dark mode setup and theme toggling for @zvoove/unity-ui projects.',
1237
- 'TRIGGER when: user wants to add dark mode, a theme toggle, or asks how Unity UI dark mode works.',
1238
- 'DO NOT use prefers-color-scheme media queries or className=\\"dark\\" — Unity UI uses data-theme=\\"dark\\".',
1239
- ].join(String.raw`\n`);
1240
-
1241
- const frontmatter = [
1242
- '---',
1243
- 'name: unity-ui-dark-mode',
1244
- `description: "${description}"`,
1245
- 'category: theming',
1246
- '---',
1247
- ].join('\n');
1248
-
1249
- return (
1250
- frontmatter +
1251
- `\n\n# Unity UI — Dark Mode
1252
-
1253
- > Dark mode is activated by \`data-theme="dark"\` on any ancestor element.
1254
- > All semantic design tokens adapt automatically — no per-component changes needed.
1255
-
1256
- ## How It Works
1257
-
1258
- \`\`\`html
1259
- <!-- Light mode (default) -->
1260
- <div>...</div>
1261
-
1262
- <!-- Dark mode — applies to this element and all descendants -->
1263
- <div data-theme="dark">...</div>
1264
- \`\`\`
1265
-
1266
- Unity UI uses Tailwind's **selector strategy** (\`[data-theme="dark"]\`), not the class or media-query strategy.
1267
- Configure Tailwind accordingly if you need \`dark:\` in custom components:
1268
-
1269
- \`\`\`ts
1270
- // tailwind.config.ts
1271
- export default {
1272
- darkMode: ['selector', '[data-theme="dark"]'],
1273
- };
1274
- \`\`\`
1275
-
1276
- ## React ThemeProvider
1277
-
1278
- \`\`\`tsx
1279
- import { createContext, useContext, useEffect, useState } from 'react';
1280
-
1281
- type Theme = 'light' | 'dark';
1282
-
1283
- const ThemeContext = createContext<{
1284
- theme: Theme;
1285
- toggleTheme: () => void;
1286
- }>({ theme: 'light', toggleTheme: () => {} });
1287
-
1288
- export function ThemeProvider({ children }: { children: React.ReactNode }) {
1289
- const [theme, setTheme] = useState<Theme>(() => {
1290
- const stored = localStorage.getItem('theme') as Theme | null;
1291
- if (stored) return stored;
1292
- return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
1293
- });
1294
-
1295
- // Sync with OS preference changes (only when no stored preference)
1296
- useEffect(() => {
1297
- const mq = window.matchMedia('(prefers-color-scheme: dark)');
1298
- const handler = (e: MediaQueryListEvent) => {
1299
- if (!localStorage.getItem('theme')) {
1300
- setTheme(e.matches ? 'dark' : 'light');
1301
- }
1302
- };
1303
- mq.addEventListener('change', handler);
1304
- return () => mq.removeEventListener('change', handler);
1305
- }, []);
1306
-
1307
- const toggleTheme = () => {
1308
- setTheme((t) => {
1309
- const next = t === 'light' ? 'dark' : 'light';
1310
- localStorage.setItem('theme', next);
1311
- return next;
1312
- });
1313
- };
1314
-
1315
- return (
1316
- <ThemeContext.Provider value={{ theme, toggleTheme }}>
1317
- <div data-theme={theme} style={{ minHeight: '100vh' }}>
1318
- {children}
1319
- </div>
1320
- </ThemeContext.Provider>
1321
- );
1322
- }
1323
-
1324
- export const useTheme = () => useContext(ThemeContext);
1325
- \`\`\`
1326
-
1327
- Wrap the app root:
1328
-
1329
- \`\`\`tsx
1330
- // main.tsx
1331
- <ThemeProvider>
1332
- <App />
1333
- </ThemeProvider>
1334
- \`\`\`
1335
-
1336
- ## Theme Toggle Button
1337
-
1338
- \`\`\`tsx
1339
- import { Button } from '@zvoove/unity-ui';
1340
- import { useTheme } from './ThemeProvider';
1341
-
1342
- export function ThemeToggle() {
1343
- const { theme, toggleTheme } = useTheme();
1344
-
1345
- return (
1346
- <Button
1347
- variant="ghost"
1348
- icon={theme === 'dark' ? 'Sun' : 'Moon'}
1349
- onClick={toggleTheme}
1350
- aria-label={
1351
- theme === 'dark'
1352
- ? 'Zum hellen Modus wechseln'
1353
- : 'Zum dunklen Modus wechseln'
1354
- }
1355
- />
1356
- );
1357
- }
1358
- \`\`\`
1359
-
1360
- Place it in \`TopBar\`'s \`actions\` prop:
1361
-
1362
- \`\`\`tsx
1363
- <TopBar title="Meine App" actions={<ThemeToggle />} />
1364
- \`\`\`
1365
-
1366
- ## Token Behaviour in Dark Mode
1367
-
1368
- Semantic tokens adapt automatically — no \`dark:\` prefix needed for these:
1369
-
1370
- | Token | Light | Dark |
1371
- |-------|-------|------|
1372
- | \`bg-surface\` | white / light gray | dark gray |
1373
- | \`bg-primary\` | brand color | lighter brand color |
1374
- | \`text-on-surface\` | near-black | near-white |
1375
- | \`text-on-surface-variant\` | gray | lighter gray |
1376
- | \`border-outline\` | gray border | darker border |
1377
- | \`bg-background\` | page background | dark page background |
1378
-
1379
- Only use \`dark:\` for values **outside** the token system (custom hex colors, images, etc.).
1380
-
1381
- ## Storybook Dark Mode
1382
-
1383
- \`\`\`tsx
1384
- // In a story file
1385
- export const DarkMode: Story = {
1386
- decorators: [
1387
- (Story) => (
1388
- <div data-theme="dark" style={{ padding: '1rem', background: 'var(--color-background)' }}>
1389
- <Story />
1390
- </div>
1391
- ),
1392
- ],
1393
- };
1394
- \`\`\`
1395
-
1396
- Or use the Storybook backgrounds addon — set the decorator on the global level in \`preview.ts\`.
1397
-
1398
- ## Rules
1399
-
1400
- - NEVER use \`prefers-color-scheme\` CSS to apply dark mode — use \`data-theme="dark"\`
1401
- - NEVER use \`className="dark"\` — Unity UI uses the selector strategy, not the class strategy
1402
- - Semantic tokens adapt automatically — only add \`dark:\` for custom non-token values
1403
- - Persist theme in \`localStorage\` to avoid a flash-of-wrong-theme on reload
1404
- - Do not apply \`data-theme\` to \`<html>\` or \`<body>\` unless your React root is there
1405
- `
1406
- );
1407
- }
1408
-
1409
- function generateMigrateShadcnSkill(componentNames) {
1410
- const description = [
1411
- 'Step-by-step guide for migrating a shadcn/ui codebase to @zvoove/unity-ui.',
1412
- 'TRIGGER when: user wants to migrate from shadcn/ui to Unity UI, or is converting shadcn/ui components.',
1413
- 'DO NOT keep shadcn/ui imports alongside Unity UI — replace fully.',
1414
- ].join(String.raw`\n`);
1415
-
1416
- const frontmatter = [
1417
- '---',
1418
- 'name: unity-ui-migrate-shadcn',
1419
- `description: "${description}"`,
1420
- 'category: migration',
1421
- '---',
1422
- ].join('\n');
1423
-
1424
- const componentRows = componentNames
1425
- .filter((n) => SHADCN_MAP[n])
1426
- .map((n) => {
1427
- const s = SHADCN_MAP[n];
1428
- const note = s.note ? ` _(${s.note})_` : '';
1429
- return `| [${s.name}](${s.url}) | \`<${n} />\` | \`unity-ui-${kebabCase(n)}\`${note} |`;
1430
- });
1431
-
1432
- const noEquivalent = componentNames
1433
- .filter((n) => !SHADCN_MAP[n])
1434
- .map(
1435
- (n) => `- **${n}** — load skill \`unity-ui-${kebabCase(n)}\` for usage`
1436
- )
1437
- .join('\n');
1438
-
1439
- return (
1440
- frontmatter +
1441
- `\n\n# Migrating from shadcn/ui to Unity UI
1442
-
1443
- ## Step-by-Step Process
1444
-
1445
- 1. **Install Unity UI** — see \`unity-ui-setup\` skill
1446
- 2. **Run a global find** for \`from "@/components/ui/\` and \`from "~/components/ui/\` — those are shadcn/ui imports
1447
- 3. **Replace each component** using the table below — load the \`unity-ui-<component>\` skill for full props
1448
- 4. **Replace styling** — remove Tailwind class-heavy JSX and use Unity UI props instead
1449
- 5. **Remove shadcn/ui** — delete the \`components/ui/\` folder and uninstall \`class-variance-authority\`, \`cmdk\`, \`@radix-ui\` etc.
1450
-
1451
- ## Component Map
1452
-
1453
- | shadcn/ui | Unity UI | Skill |
1454
- |-----------|----------|-------|
1455
- ${componentRows.join('\n')}
1456
-
1457
- ## Unity UI–Only Components (no shadcn/ui equivalent)
1458
-
1459
- ${noEquivalent}
1460
-
1461
- ## Common Prop Differences
1462
-
1463
- | Pattern | shadcn/ui | Unity UI |
1464
- |---------|-----------|----------|
1465
- | Text input | \`<Input />\` | \`<TextField label="..." />\` |
1466
- | Validation error | custom error \`<p>\` | \`error={true} errorMessage="..."\` |
1467
- | Button icon | wrap with \`<Icon />\` | \`icon="IconName"\` prop |
1468
- | Loading state | manual spinner | \`loading={true}\` prop (Button) |
1469
- | Form field wrapper | \`<FormField>\` + \`<FormControl>\` | direct Controller render |
1470
- | Toast | \`toast("msg")\` from sonner | \`addSnackbar("msg")\` from useSnackbar |
1471
- | Sheet position | \`side="right"\` | \`anchor="right"\` |
1472
-
1473
- ## Removing shadcn/ui
1474
-
1475
- \`\`\`bash
1476
- # Remove shadcn/ui dependencies
1477
- npm uninstall @radix-ui/react-* class-variance-authority cmdk lucide-react
1478
-
1479
- # Delete the generated component folder
1480
- rm -rf src/components/ui
1481
- \`\`\`
1482
-
1483
- ## Rules
1484
-
1485
- - NEVER keep shadcn/ui and Unity UI components side-by-side — migrate fully
1486
- - Replace all \`cn()\` usage in component files with \`twMerge()\` from tailwind-merge
1487
- - shadcn/ui uses \`className\` extensively — Unity UI uses props (variant, size, etc.); remove the class overrides
1488
- - Import everything from \`@zvoove/unity-ui\`, never from local \`components/ui/\`
1489
- `
1490
- );
1491
- }
1492
-
1493
- function generateMigrateMuiSkill(componentNames) {
1494
- const description = [
1495
- 'Step-by-step guide for migrating a Material UI (MUI) codebase to @zvoove/unity-ui.',
1496
- 'TRIGGER when: user wants to migrate from MUI (@mui/material) to Unity UI, or is converting MUI components.',
1497
- 'DO NOT keep MUI imports alongside Unity UI — replace fully.',
1498
- ].join(String.raw`\n`);
1499
-
1500
- const frontmatter = [
1501
- '---',
1502
- 'name: unity-ui-migrate-mui',
1503
- `description: "${description}"`,
1504
- 'category: migration',
1505
- '---',
1506
- ].join('\n');
1507
-
1508
- const componentRows = componentNames
1509
- .filter((n) => MUI_MAP[n])
1510
- .map((n) => {
1511
- const m = MUI_MAP[n];
1512
- const note = m.note ? ` _(${m.note})_` : '';
1513
- return `| [${m.name}](${m.url}) | \`<${n} />\` | \`unity-ui-${kebabCase(n)}\`${note} |`;
1514
- });
1515
-
1516
- const noEquivalent = componentNames
1517
- .filter((n) => !MUI_MAP[n])
1518
- .map(
1519
- (n) => `- **${n}** — load skill \`unity-ui-${kebabCase(n)}\` for usage`
1520
- )
1521
- .join('\n');
1522
-
1523
- return (
1524
- frontmatter +
1525
- `\n\n# Migrating from MUI to Unity UI
1526
-
1527
- ## Step-by-Step Process
1528
-
1529
- 1. **Install Unity UI** — see \`unity-ui-setup\` skill
1530
- 2. **Run a global find** for \`from "@mui/material\` and \`from "@mui/x-\` — those are MUI imports
1531
- 3. **Replace each component** using the table below — load the \`unity-ui-<component>\` skill for full props
1532
- 4. **Replace the sx prop** — MUI's \`sx\` system does not exist in Unity UI; use Tailwind tokens instead (load \`unity-ui-styling\`)
1533
- 5. **Remove ThemeProvider** — Unity UI uses \`data-theme="dark"\` for dark mode; load \`unity-ui-dark-mode\`
1534
- 6. **Uninstall MUI** after all components are replaced
1535
-
1536
- ## Component Map
1537
-
1538
- | MUI | Unity UI | Skill |
1539
- |-----|----------|-------|
1540
- ${componentRows.join('\n')}
1541
-
1542
- ## Unity UI–Only Components (no MUI equivalent)
1543
-
1544
- ${noEquivalent}
1545
-
1546
- ## Common Pattern Differences
1547
-
1548
- | Pattern | MUI | Unity UI |
1549
- |---------|-----|----------|
1550
- | Theming | \`<ThemeProvider theme={theme}>\` | \`data-theme="dark"\` attribute |
1551
- | Dark mode | \`createTheme({ palette: { mode: 'dark' } })\` | \`data-theme="dark"\` on container |
1552
- | Inline styles | \`sx={{ mt: 2, color: 'primary.main' }}\` | Tailwind tokens: \`className="mt-md text-primary"\` |
1553
- | Custom style prop | \`sx={{ ... }}\` | \`className\` with token classes |
1554
- | Text field | \`<TextField variant="outlined" label="..." />\` | \`<TextField label="..." />\` |
1555
- | Grid v2 | \`<Grid container spacing={2}>\` | \`<Grid columns={3} gap="md">\` |
1556
- | Stack spacing | \`<Stack spacing={2}>\` | \`<Stack gap="md">\` |
1557
- | Icons | \`import EditIcon from '@mui/icons-material/Edit'\` | \`<Icon name="Pencil" />\` |
1558
- | Button loading | \`<LoadingButton loading>\` | \`<Button loading>\` |
1559
- | Alert | \`<Alert severity="error">\` | \`<InfoBox variant="error">\` |
1560
- | Snackbar | \`<Snackbar>...</Snackbar>\` (imperative) | \`addSnackbar()\` from \`useSnackbar()\` |
1561
-
1562
- ## Replacing the sx Prop
1563
-
1564
- MUI's \`sx\` prop maps CSS properties to theme values. Replace with Tailwind design tokens:
1565
-
1566
- \`\`\`tsx
1567
- // MUI
1568
- <Box sx={{ mt: 2, mb: 3, color: 'text.secondary', bgcolor: 'background.paper' }}>
1569
-
1570
- // Unity UI
1571
- <Stack direction="column" gap="md" className="text-on-surface-variant bg-surface">
1572
- \`\`\`
1573
-
1574
- See the \`unity-ui-styling\` skill for the full token reference.
1575
-
1576
- ## Removing MUI
1577
-
1578
- \`\`\`bash
1579
- npm uninstall @mui/material @mui/x-date-pickers @mui/icons-material @emotion/react @emotion/styled
1580
- \`\`\`
1581
-
1582
- ## Rules
1583
-
1584
- - NEVER keep MUI and Unity UI components side-by-side — migrate fully
1585
- - The \`sx\` prop does not exist in Unity UI — use \`className\` with token classes or component props
1586
- - MUI icons do not exist in Unity UI — use \`<Icon name="..." />\` (load \`unity-ui-icons\` skill for names)
1587
- - Remove \`ThemeProvider\` and \`CssBaseline\` from MUI; Unity UI uses CSS custom properties from \`theme.css\`
1588
- - Import everything from \`@zvoove/unity-ui\`
1589
- `
1590
- );
1591
- }
1592
-
1593
- function generateFigmaCodeConnectSkill() {
1594
- const description = [
1595
- 'Figma Code Connect setup for @zvoove/unity-ui — links Figma components to Unity UI React components.',
1596
- 'TRIGGER when: user wants to set up Figma Code Connect, link design components to code, or improve design-to-code accuracy in a Unity UI project.',
1597
- 'DO NOT use this for general Figma-to-code generation — Code Connect is for mapping design components to existing code components.',
1598
- ].join(String.raw`\n`);
1599
-
1600
- const frontmatter = [
1601
- '---',
1602
- 'name: unity-ui-figma-code-connect',
1603
- `description: "${description}"`,
1604
- 'category: tooling',
1605
- '---',
1606
- ].join('\n');
1607
-
1608
- return (
1609
- frontmatter +
1610
- `\n\n# Unity UI — Figma Code Connect
1611
-
1612
- > Code Connect links Figma components to Unity UI React components so that when a designer shares a Figma URL, the AI knows exactly which component and props to use.
1613
-
1614
- ## Install
1615
-
1616
- \`\`\`bash
1617
- npm install --save-dev @figma/code-connect
1618
- \`\`\`
1619
-
1620
- ## Configuration
1621
-
1622
- Create \`figma.config.json\` at the project root:
1623
-
1624
- \`\`\`json
1625
- {
1626
- "codeConnect": {
1627
- "parser": "react",
1628
- "include": ["src/**/*.figma.tsx"],
1629
- "importPaths": {
1630
- "@zvoove/unity-ui": "@zvoove/unity-ui"
1631
- }
1632
- }
1633
- }
1634
- \`\`\`
1635
-
1636
- ## How to Get the Figma Node ID
1637
-
1638
- 1. Open the Unity UI Figma design file
1639
- 2. Select the component you want to connect
1640
- 3. Copy the URL — it contains \`node-id=<nodeId>\`
1641
- 4. Convert \`-\` to \`:\` in the node ID (e.g. \`123-456\` → \`123:456\`)
1642
-
1643
- ## Mapping File Structure
1644
-
1645
- Create a \`.figma.tsx\` file next to each component (or in a \`figma/\` folder):
1646
-
1647
- \`\`\`tsx
1648
- // src/figma/Button.figma.tsx
1649
- import figma from '@figma/code-connect';
1650
- import { Button } from '@zvoove/unity-ui';
1651
-
1652
- figma.connect(
1653
- Button,
1654
- 'https://www.figma.com/design/<FILE_KEY>/<FILE_NAME>?node-id=<NODE_ID>',
1655
- {
1656
- props: {
1657
- variant: figma.enum('Variant', {
1658
- filled: 'filled',
1659
- outlined: 'outlined',
1660
- ghost: 'ghost',
1661
- text: 'text',
1662
- }),
1663
- size: figma.enum('Size', {
1664
- sm: 'sm',
1665
- md: 'md',
1666
- lg: 'lg',
1667
- }),
1668
- disabled: figma.boolean('Disabled'),
1669
- label: figma.string('Label'),
1670
- },
1671
- example: ({ variant, size, disabled, label }) => (
1672
- <Button variant={variant} size={size} disabled={disabled}>
1673
- {label}
1674
- </Button>
1675
- ),
1676
- }
1677
- );
1678
- \`\`\`
1679
-
1680
- ## Example: TextField
1681
-
1682
- \`\`\`tsx
1683
- // src/figma/TextField.figma.tsx
1684
- import figma from '@figma/code-connect';
1685
- import { TextField } from '@zvoove/unity-ui';
1686
-
1687
- figma.connect(
1688
- TextField,
1689
- 'https://www.figma.com/design/<FILE_KEY>/<FILE_NAME>?node-id=<NODE_ID>',
1690
- {
1691
- props: {
1692
- label: figma.string('Label'),
1693
- placeholder: figma.string('Placeholder'),
1694
- error: figma.boolean('Error'),
1695
- disabled: figma.boolean('Disabled'),
1696
- },
1697
- example: ({ label, placeholder, error, disabled }) => (
1698
- <TextField
1699
- label={label}
1700
- placeholder={placeholder}
1701
- error={error}
1702
- disabled={disabled}
1703
- />
1704
- ),
1705
- }
10
+ console.warn(
11
+ '\x1b[33m⚠️ "unity-ui-skills" is deprecated. Use "npx unity-ui skills" instead.\x1b[0m\n'
1706
12
  );
1707
- \`\`\`
1708
-
1709
- ## Publish to Figma
1710
-
1711
- \`\`\`bash
1712
- # Preview what will be published (dry run)
1713
- npx figma connect publish --dry-run --token <FIGMA_ACCESS_TOKEN>
1714
-
1715
- # Publish
1716
- npx figma connect publish --token <FIGMA_ACCESS_TOKEN>
1717
- \`\`\`
1718
-
1719
- Store the token in \`.env.local\` (never commit it):
1720
-
1721
- \`\`\`bash
1722
- FIGMA_ACCESS_TOKEN=figd_xxxxxxxx
1723
- \`\`\`
1724
-
1725
- \`\`\`bash
1726
- npx figma connect publish --token $FIGMA_ACCESS_TOKEN
1727
- \`\`\`
1728
-
1729
- ## Figma Prop Types Reference
1730
-
1731
- | Figma layer type | Code Connect helper | Example |
1732
- |-----------------|---------------------|---------|
1733
- | Variant property | \`figma.enum()\` | \`figma.enum('Variant', { filled: 'filled' })\` |
1734
- | Boolean property | \`figma.boolean()\` | \`figma.boolean('Disabled')\` |
1735
- | Text layer | \`figma.string()\` | \`figma.string('Label')\` |
1736
- | Instance swap | \`figma.instance()\` | \`figma.instance('Icon')\` |
1737
- | Nested instance | \`figma.nestedProps()\` | \`figma.nestedProps('Slot', { ... })\` |
1738
-
1739
- ## Rules
1740
-
1741
- - Create one \`.figma.tsx\` file per component
1742
- - The Figma URL must point to the **main component** (not an instance or frame)
1743
- - Prop names in \`figma.enum()\` must match Figma's exact property names (case-sensitive)
1744
- - Never commit Figma access tokens — use environment variables
1745
- - After updating, republish with \`npx figma connect publish\`
1746
- `
1747
- );
1748
- }
1749
-
1750
- function generateUmbrellaSkill(componentNames) {
1751
- const description = [
1752
- 'Unity UI (@zvoove/unity-ui) component library router. Use this as the entry point when building UI with this package.',
1753
- 'TRIGGER when: user wants to build UI, use a component, or migrate/convert a UI from any source (Lovable, Figma Make, shadcn/ui, MUI, Ant Design, etc.) into this design system.',
1754
- 'DO NOT TRIGGER when: @zvoove/unity-ui is not installed in the project.',
1755
- ].join(String.raw`\n`);
1756
-
1757
- const componentList = componentNames
1758
- .map((n) => `- ${n} → load skill \`unity-ui-${kebabCase(n)}\``)
1759
- .join('\n');
1760
-
1761
- const frontmatter = [
1762
- '---',
1763
- 'name: unity-ui',
1764
- `description: "${description}"`,
1765
- 'category: routing',
1766
- '---',
1767
- ].join('\n');
1768
-
1769
- return (
1770
- frontmatter +
1771
- `\n\n# Unity UI — Component Router
1772
-
1773
- > **@zvoove/unity-ui** is installed in this project. Use the skills below — do NOT read \`dist/\`, \`node_modules/\`, or source files for API documentation.
1774
-
1775
- ## How to Use
1776
-
1777
- 1. Find the component you need in the directory below
1778
- 2. Load its \`unity-ui-<component>\` skill for full props, examples, and rules
1779
- 3. Import exclusively from \`@zvoove/unity-ui\` — never from dist paths
1780
13
 
1781
- ## Component Directory
1782
-
1783
- ${componentList}
1784
-
1785
- ## Rules
1786
-
1787
- - NEVER read \`dist/\` or \`node_modules/@zvoove/unity-ui/\` for type/API information — skills have everything
1788
- - NEVER create custom components for functionality already provided by this library
1789
- - Always import from \`@zvoove/unity-ui\`
1790
- - If unsure which component to use, load \`unity-ui-index\` for the full shadcn/ui equivalence table
1791
-
1792
- ## Migrating from Other Libraries
1793
-
1794
- If the project contains components from shadcn/ui, MUI, Ant Design, Lovable, Figma Make, or any other source:
1795
- - **Replace them with Unity UI equivalents** — do not keep mixed component libraries
1796
- - Load \`unity-ui-index\` to find the equivalent Unity UI component
1797
- - Remove the replaced library's imports and dependencies once converted
1798
- - Prefer Unity UI over any other component library, regardless of what already exists in the codebase
1799
- `
1800
- );
1801
- }
1802
-
1803
- // ─── Write skills to disk ───────────────────────────────────────────────────
1804
-
1805
- function writeSkill(outputDir, slug, content) {
1806
- const skillDir = path.join(outputDir, slug);
1807
- fs.mkdirSync(skillDir, { recursive: true });
1808
- fs.writeFileSync(path.join(skillDir, 'SKILL.md'), content, 'utf8');
1809
- }
1810
-
1811
- // ─── Main ───────────────────────────────────────────────────────────────────
1812
-
1813
- function main() {
1814
- const { outputBase } = parseArgs(process.argv);
1815
-
1816
- const llmsPath = findLlmsTxt();
1817
- if (!llmsPath) {
1818
- console.error(
1819
- 'Could not find llms.txt. Make sure @zvoove/unity-ui is installed and built.'
1820
- );
1821
- process.exit(1);
1822
- }
1823
-
1824
- const projectRoot = process.cwd();
1825
- const outputDir = path.resolve(projectRoot, outputBase, 'skills');
1826
- const content = fs.readFileSync(llmsPath, 'utf8');
1827
- const parsed = parseLlmsTxt(content);
1828
-
1829
- // Clean previous unity-ui skills (leave other skills untouched)
1830
- if (fs.existsSync(outputDir)) {
1831
- for (const entry of fs.readdirSync(outputDir)) {
1832
- if (entry.startsWith('unity-ui-')) {
1833
- fs.rmSync(path.join(outputDir, entry), { recursive: true });
1834
- }
1835
- }
14
+ // Parse legacy args
15
+ const args = process.argv.slice(2);
16
+ let output = '.claude';
17
+ for (let i = 0; i < args.length; i++) {
18
+ if ((args[i] === '--output' || args[i] === '-o') && args[i + 1]) {
19
+ output = args[i + 1];
20
+ i++;
1836
21
  }
22
+ if (args[i] === '--help' || args[i] === '-h') {
23
+ console.log(`Usage: npx unity-ui skills [options]
1837
24
 
1838
- const componentNames = Object.keys(parsed.components).sort();
1839
- let count = 0;
1840
-
1841
- // Component skills
1842
- for (const [name, componentContent] of Object.entries(parsed.components)) {
1843
- writeSkill(
1844
- outputDir,
1845
- `unity-ui-${kebabCase(name)}`,
1846
- generateComponentSkill(name, componentContent)
1847
- );
1848
- count++;
25
+ This command has moved. Please use:
26
+ npx unity-ui skills [-o <dir>]
27
+ `);
28
+ process.exit(0);
1849
29
  }
1850
-
1851
- // Meta skills
1852
- writeSkill(outputDir, 'unity-ui', generateUmbrellaSkill(componentNames));
1853
- writeSkill(outputDir, 'unity-ui-setup', generateSetupSkill(parsed));
1854
- writeSkill(outputDir, 'unity-ui-styling', generateStylingSkill(parsed));
1855
- writeSkill(
1856
- outputDir,
1857
- 'unity-ui-custom-component',
1858
- generateCustomComponentSkill(parsed)
1859
- );
1860
- writeSkill(outputDir, 'unity-ui-icons', generateIconsSkill(parsed));
1861
- writeSkill(outputDir, 'unity-ui-index', generateIndexSkill(componentNames));
1862
- writeSkill(outputDir, 'unity-ui-forms', generateFormsSkill());
1863
- writeSkill(outputDir, 'unity-ui-layouts', generateLayoutsSkill());
1864
- writeSkill(outputDir, 'unity-ui-dark-mode', generateDarkModeSkill());
1865
- writeSkill(
1866
- outputDir,
1867
- 'unity-ui-migrate-shadcn',
1868
- generateMigrateShadcnSkill(componentNames)
1869
- );
1870
- writeSkill(
1871
- outputDir,
1872
- 'unity-ui-migrate-mui',
1873
- generateMigrateMuiSkill(componentNames)
1874
- );
1875
- writeSkill(
1876
- outputDir,
1877
- 'unity-ui-figma-code-connect',
1878
- generateFigmaCodeConnectSkill()
1879
- );
1880
- count += 12;
1881
-
1882
- const relOutput = path.relative(projectRoot, outputDir);
1883
- console.log(`\n Unity UI — Agent Skills Generator\n`);
1884
- console.log(` ✅ Generated ${count} skills in ${relOutput}/\n`);
1885
- console.log(` ${componentNames.length} component skills`);
1886
- console.log(` 1 umbrella routing skill (entry point + migration rules)`);
1887
- console.log(` 1 setup skill (installation, spacing, responsive props)`);
1888
- console.log(
1889
- ` 1 styling skill (Tailwind v4 tokens, tv(), no inline styles)`
1890
- );
1891
- console.log(
1892
- ` 1 custom component skill (patterns for creating new components)`
1893
- );
1894
- console.log(` 1 icons skill (all icon names)`);
1895
- console.log(` 1 index skill (shadcn/ui + MUI mapping tables)`);
1896
- console.log(
1897
- ` 1 forms skill (react-hook-form + zod + Controller pattern)`
1898
- );
1899
- console.log(
1900
- ` 1 layouts skill (app shells, dashboards, settings, master/detail)`
1901
- );
1902
- console.log(
1903
- ` 1 dark mode skill (data-theme, ThemeProvider, token behaviour)`
1904
- );
1905
- console.log(
1906
- ` 1 migrate-shadcn skill (step-by-step shadcn/ui → Unity UI)`
1907
- );
1908
- console.log(` 1 migrate-mui skill (step-by-step MUI → Unity UI)`);
1909
- console.log(
1910
- ` 1 figma-code-connect skill (Code Connect setup and mappings)\n`
1911
- );
1912
- console.log(` Your AI agent can now discover and use Unity UI components.`);
1913
- console.log(` Skills are loaded on-demand — only relevant ones are read.\n`);
1914
30
  }
1915
31
 
1916
- main();
32
+ runSkills({ output });