@zvoove/unity-ui 2.20.0 → 2.21.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.
- package/bin/generate-skills.mjs +683 -0
- package/dist/llms.txt +1185 -0
- package/dist/unity-ui.cjs.js +1 -1
- package/dist/unity-ui.css +1 -1
- package/dist/unity-ui.es.js +378 -378
- package/package.json +44 -34
|
@@ -0,0 +1,683 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Unity UI — Agent Skills Generator
|
|
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.
|
|
17
|
+
*/
|
|
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
|
+
// ─── Storybook documentation links ───────────────────────────────────────────
|
|
224
|
+
// Maps component name → Storybook title path (as defined in *.stories.tsx).
|
|
225
|
+
// URL: https://main--67c03f013fea08bb2f926e5f.chromatic.com/?path=/docs/<path>--docs
|
|
226
|
+
const STORYBOOK_BASE =
|
|
227
|
+
'https://main--67c03f013fea08bb2f926e5f.chromatic.com/?path=/docs/';
|
|
228
|
+
|
|
229
|
+
const STORYBOOK_MAP = {
|
|
230
|
+
Accordion: 'Components/Accordion',
|
|
231
|
+
ActionCard: 'Chat/ActionCard',
|
|
232
|
+
Avatar: 'Components/Avatar/Avatar',
|
|
233
|
+
Badge: 'Components/Badge',
|
|
234
|
+
Breadcrumbs: 'Navigation/Breadcrumbs',
|
|
235
|
+
Button: 'Components/Buttons/Button',
|
|
236
|
+
Card: 'Components/Card',
|
|
237
|
+
ChatBubble: 'Chat/ChatBubble',
|
|
238
|
+
Checkbox: 'Forms/Checkbox',
|
|
239
|
+
Chip: 'Components/Chip',
|
|
240
|
+
CodeBlock: 'Components/CodeBlock',
|
|
241
|
+
ConfirmationCard: 'Components/ConfirmationCard',
|
|
242
|
+
ContentBlock: 'Components/ContentBlock',
|
|
243
|
+
DatePicker: 'Forms/DatePicker',
|
|
244
|
+
Dialog: 'Components/Dialog',
|
|
245
|
+
Divider: 'Components/Divider',
|
|
246
|
+
Expandable: 'Components/Expandable',
|
|
247
|
+
FormLabel: 'Forms/FormLabel',
|
|
248
|
+
Grid: 'Layout/Grid',
|
|
249
|
+
Icon: 'Components/Icon',
|
|
250
|
+
InfoBox: 'Components/InfoBox',
|
|
251
|
+
MessageActions: 'Chat/MessageActions',
|
|
252
|
+
Pagination: 'Components/Pagination',
|
|
253
|
+
PopUpMenu: 'Components/PopUpMenu',
|
|
254
|
+
ProgressIndicator: 'Components/ProgressIndicator',
|
|
255
|
+
Radio: 'Forms/Radio/RadioGroup',
|
|
256
|
+
ScoreCard: 'Components/ScoreCard',
|
|
257
|
+
Segment: 'Components/Buttons/Segment/SegmentGroup',
|
|
258
|
+
Select: 'Forms/Select',
|
|
259
|
+
Sheet: 'Components/Sheet',
|
|
260
|
+
SideNavigation: 'Navigation/SideNavigation',
|
|
261
|
+
Skeleton: 'Components/Skeleton',
|
|
262
|
+
Snackbar: 'Components/Snackbar',
|
|
263
|
+
Stack: 'Layout/Stack',
|
|
264
|
+
Switch: 'Forms/Switch',
|
|
265
|
+
Table: 'Components/Table',
|
|
266
|
+
Tabs: 'Navigation/Tabs',
|
|
267
|
+
Tag: 'Components/Tag',
|
|
268
|
+
TextField: 'Forms/TextField',
|
|
269
|
+
Textarea: 'Forms/Textarea',
|
|
270
|
+
Tooltip: 'Components/Tooltip',
|
|
271
|
+
TopBar: 'Components/TopBar',
|
|
272
|
+
Typography: 'Components/Typography',
|
|
273
|
+
Uploader: 'Forms/Uploader',
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
function storybookUrl(name) {
|
|
277
|
+
const title = STORYBOOK_MAP[name];
|
|
278
|
+
if (!title) return null;
|
|
279
|
+
const slug = title.toLowerCase().replaceAll('/', '-');
|
|
280
|
+
return `${STORYBOOK_BASE}${slug}--docs`;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// ─── Component categories ───────────────────────────────────────────────────
|
|
284
|
+
const CATEGORIES = {
|
|
285
|
+
Stack: 'layout',
|
|
286
|
+
Grid: 'layout',
|
|
287
|
+
Card: 'layout',
|
|
288
|
+
Divider: 'layout',
|
|
289
|
+
Expandable: 'layout',
|
|
290
|
+
ContentBlock: 'layout',
|
|
291
|
+
Typography: 'typography',
|
|
292
|
+
TextField: 'form',
|
|
293
|
+
Textarea: 'form',
|
|
294
|
+
Select: 'form',
|
|
295
|
+
Checkbox: 'form',
|
|
296
|
+
Radio: 'form',
|
|
297
|
+
Switch: 'form',
|
|
298
|
+
DatePicker: 'form',
|
|
299
|
+
Uploader: 'form',
|
|
300
|
+
FormLabel: 'form',
|
|
301
|
+
Button: 'action',
|
|
302
|
+
Segment: 'action',
|
|
303
|
+
PopUpMenu: 'action',
|
|
304
|
+
Table: 'data-display',
|
|
305
|
+
Tag: 'data-display',
|
|
306
|
+
Chip: 'data-display',
|
|
307
|
+
Avatar: 'data-display',
|
|
308
|
+
Badge: 'data-display',
|
|
309
|
+
Icon: 'data-display',
|
|
310
|
+
Skeleton: 'data-display',
|
|
311
|
+
ProgressIndicator: 'data-display',
|
|
312
|
+
CodeBlock: 'data-display',
|
|
313
|
+
Tabs: 'navigation',
|
|
314
|
+
Breadcrumbs: 'navigation',
|
|
315
|
+
SideNavigation: 'navigation',
|
|
316
|
+
Pagination: 'navigation',
|
|
317
|
+
TopBar: 'navigation',
|
|
318
|
+
Dialog: 'feedback',
|
|
319
|
+
Sheet: 'feedback',
|
|
320
|
+
Snackbar: 'feedback',
|
|
321
|
+
InfoBox: 'feedback',
|
|
322
|
+
ConfirmationCard: 'feedback',
|
|
323
|
+
Tooltip: 'feedback',
|
|
324
|
+
Accordion: 'compound',
|
|
325
|
+
ActionCard: 'compound',
|
|
326
|
+
ChatBubble: 'compound',
|
|
327
|
+
MessageActions: 'compound',
|
|
328
|
+
ScoreCard: 'compound',
|
|
329
|
+
useBreakpoint: 'hook',
|
|
330
|
+
useClickOutside: 'hook',
|
|
331
|
+
};
|
|
332
|
+
|
|
333
|
+
// Category-specific rules appended to each skill
|
|
334
|
+
const CATEGORY_RULES = {
|
|
335
|
+
layout: [
|
|
336
|
+
'- Use Stack and Grid for layout instead of raw div + CSS',
|
|
337
|
+
'- Use the spacing scale for gap/padding/margin — never use arbitrary pixel values',
|
|
338
|
+
],
|
|
339
|
+
form: [
|
|
340
|
+
'- Never create custom form inputs when this component exists',
|
|
341
|
+
'- Use responsive `density` prop when available for compact layouts',
|
|
342
|
+
],
|
|
343
|
+
feedback: [
|
|
344
|
+
'- Never create custom overlays or toast systems when this component exists',
|
|
345
|
+
],
|
|
346
|
+
typography: [
|
|
347
|
+
'- Use Typography for all text — never use raw `<p>`, `<h1>`, `<span>` etc.',
|
|
348
|
+
],
|
|
349
|
+
hook: ['- Import hooks directly from `@zvoove/unity-ui`'],
|
|
350
|
+
};
|
|
351
|
+
|
|
352
|
+
// ─── Parse llms.txt ─────────────────────────────────────────────────────────
|
|
353
|
+
|
|
354
|
+
function detectSection(line) {
|
|
355
|
+
const sectionHeaders = {
|
|
356
|
+
'## Setup': 'setup',
|
|
357
|
+
'## Responsive Props': 'responsive',
|
|
358
|
+
'## RULES FOR AI AGENTS': 'rules',
|
|
359
|
+
'## SPACING SCALE': 'spacing',
|
|
360
|
+
'## ICON NAMES': 'icons',
|
|
361
|
+
};
|
|
362
|
+
|
|
363
|
+
for (const [prefix, key] of Object.entries(sectionHeaders)) {
|
|
364
|
+
if (line.startsWith(prefix)) return key;
|
|
365
|
+
}
|
|
366
|
+
if (line.startsWith('## EXAMPLE:')) return 'example';
|
|
367
|
+
if (
|
|
368
|
+
/^## [A-Z]+ COMPONENTS?$/.test(line) ||
|
|
369
|
+
/^## (HOOKS|COMPOUND COMPONENTS|TYPOGRAPHY)$/.test(line)
|
|
370
|
+
) {
|
|
371
|
+
return 'category-header';
|
|
372
|
+
}
|
|
373
|
+
return null;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
function mergeComponent(components, buckets, name, lines) {
|
|
377
|
+
if (!name || lines.length === 0) return;
|
|
378
|
+
const normalizedName = NAME_MAP[name] ?? name;
|
|
379
|
+
|
|
380
|
+
if (normalizedName === '__SKIP__') {
|
|
381
|
+
buckets.icons.push('', `### ${name}`, ...lines);
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
const text = lines.join('\n').trim();
|
|
385
|
+
components[normalizedName] = components[normalizedName]
|
|
386
|
+
? components[normalizedName] + '\n\n' + text
|
|
387
|
+
: text;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
function parseLlmsTxt(content) {
|
|
391
|
+
const components = {};
|
|
392
|
+
const buckets = {
|
|
393
|
+
setup: [],
|
|
394
|
+
responsive: [],
|
|
395
|
+
rules: [],
|
|
396
|
+
spacing: [],
|
|
397
|
+
icons: [],
|
|
398
|
+
example: [],
|
|
399
|
+
};
|
|
400
|
+
|
|
401
|
+
let activeSection = null;
|
|
402
|
+
let currentComponent = null;
|
|
403
|
+
let currentLines = [];
|
|
404
|
+
|
|
405
|
+
function flush() {
|
|
406
|
+
mergeComponent(components, buckets, currentComponent, currentLines);
|
|
407
|
+
currentComponent = null;
|
|
408
|
+
currentLines = [];
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
function handleSectionHeader(line) {
|
|
412
|
+
flush();
|
|
413
|
+
const section = detectSection(line);
|
|
414
|
+
activeSection = section === 'category-header' ? null : section;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
function handleComponentHeader(line) {
|
|
418
|
+
flush();
|
|
419
|
+
currentComponent = line.slice(4).trim();
|
|
420
|
+
if (activeSection !== 'icons') activeSection = null;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
function handleContentLine(line) {
|
|
424
|
+
if (currentComponent) {
|
|
425
|
+
currentLines.push(line);
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
if (activeSection && buckets[activeSection]) {
|
|
429
|
+
buckets[activeSection].push(line);
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
for (const line of content.split('\n')) {
|
|
434
|
+
if (line === '---') continue;
|
|
435
|
+
if (line.startsWith('## ')) handleSectionHeader(line);
|
|
436
|
+
else if (line.startsWith('### ')) handleComponentHeader(line);
|
|
437
|
+
else handleContentLine(line);
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
flush();
|
|
441
|
+
|
|
442
|
+
const trimBucket = (key) => buckets[key].join('\n').trim();
|
|
443
|
+
return {
|
|
444
|
+
setup: trimBucket('setup'),
|
|
445
|
+
responsive: trimBucket('responsive'),
|
|
446
|
+
rules: trimBucket('rules'),
|
|
447
|
+
spacing: trimBucket('spacing'),
|
|
448
|
+
icons: trimBucket('icons'),
|
|
449
|
+
example: trimBucket('example'),
|
|
450
|
+
components,
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
// ─── Skill generators ───────────────────────────────────────────────────────
|
|
455
|
+
|
|
456
|
+
function kebabCase(str) {
|
|
457
|
+
return str
|
|
458
|
+
.replace(/([a-z])([A-Z])/g, '$1-$2')
|
|
459
|
+
.toLowerCase()
|
|
460
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
461
|
+
.replace(/^-|-$/g, '');
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
function generateComponentSkill(name, content) {
|
|
465
|
+
const shadcn = SHADCN_MAP[name];
|
|
466
|
+
const category = CATEGORIES[name] || 'component';
|
|
467
|
+
const slug = kebabCase(name);
|
|
468
|
+
const docsUrl = storybookUrl(name);
|
|
469
|
+
|
|
470
|
+
let description = `${name} component from Unity UI (@zvoove/unity-ui). Category: ${category}.`;
|
|
471
|
+
if (shadcn) {
|
|
472
|
+
description += ` Comparable to shadcn/ui ${shadcn.name}.`;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
const frontmatter = [
|
|
476
|
+
'---',
|
|
477
|
+
`name: unity-ui-${slug}`,
|
|
478
|
+
`description: "${description}"`,
|
|
479
|
+
`category: ${category}`,
|
|
480
|
+
];
|
|
481
|
+
if (shadcn) frontmatter.push(`shadcn_equivalent: ${shadcn.name}`);
|
|
482
|
+
if (docsUrl) frontmatter.push(`docs: ${docsUrl}`);
|
|
483
|
+
frontmatter.push('---');
|
|
484
|
+
|
|
485
|
+
const body = [`# Unity UI — ${name}\n`];
|
|
486
|
+
|
|
487
|
+
const links = [];
|
|
488
|
+
if (docsUrl) links.push(`[Storybook docs](${docsUrl})`);
|
|
489
|
+
if (shadcn) links.push(`[shadcn/ui ${shadcn.name}](${shadcn.url})`);
|
|
490
|
+
if (links.length > 0) {
|
|
491
|
+
body.push(`> ${links.join(' | ')}`);
|
|
492
|
+
if (shadcn?.note) body.push(`> ${shadcn.note}`);
|
|
493
|
+
body.push('');
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
body.push(content);
|
|
497
|
+
body.push('\n## Rules\n');
|
|
498
|
+
body.push('- Always import from `@zvoove/unity-ui`');
|
|
499
|
+
body.push('- Never recreate components that exist in this library');
|
|
500
|
+
|
|
501
|
+
const extraRules = CATEGORY_RULES[category] || [];
|
|
502
|
+
body.push(...extraRules);
|
|
503
|
+
body.push(
|
|
504
|
+
'- Use responsive props where applicable (single value or breakpoint object)'
|
|
505
|
+
);
|
|
506
|
+
|
|
507
|
+
return frontmatter.join('\n') + '\n\n' + body.join('\n') + '\n';
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
function generateSetupSkill(parsed) {
|
|
511
|
+
const frontmatter = [
|
|
512
|
+
'---',
|
|
513
|
+
'name: unity-ui-setup',
|
|
514
|
+
'description: "Setup and installation for Unity UI (@zvoove/unity-ui). React component library with 40+ components, Tailwind CSS 4, dark mode, responsive props."',
|
|
515
|
+
'category: setup',
|
|
516
|
+
'---',
|
|
517
|
+
].join('\n');
|
|
518
|
+
|
|
519
|
+
return (
|
|
520
|
+
frontmatter +
|
|
521
|
+
`\n\n# Unity UI — Setup & Overview
|
|
522
|
+
|
|
523
|
+
> Unity UI (\`@zvoove/unity-ui\`) is a React component library with 40+ accessible, themeable components built with TypeScript and Tailwind CSS 4.
|
|
524
|
+
|
|
525
|
+
${parsed.setup}
|
|
526
|
+
|
|
527
|
+
## Responsive Props
|
|
528
|
+
|
|
529
|
+
${parsed.responsive}
|
|
530
|
+
|
|
531
|
+
## Spacing Scale
|
|
532
|
+
|
|
533
|
+
${parsed.spacing}
|
|
534
|
+
|
|
535
|
+
## Example: Contact Form
|
|
536
|
+
|
|
537
|
+
${parsed.example}
|
|
538
|
+
|
|
539
|
+
## Rules
|
|
540
|
+
|
|
541
|
+
${parsed.rules}
|
|
542
|
+
`
|
|
543
|
+
);
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
function generateIconsSkill(parsed) {
|
|
547
|
+
const frontmatter = [
|
|
548
|
+
'---',
|
|
549
|
+
'name: unity-ui-icons',
|
|
550
|
+
'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."',
|
|
551
|
+
'category: data-display',
|
|
552
|
+
'---',
|
|
553
|
+
].join('\n');
|
|
554
|
+
|
|
555
|
+
return (
|
|
556
|
+
frontmatter +
|
|
557
|
+
`\n\n# Unity UI — Icons
|
|
558
|
+
|
|
559
|
+
> Unity UI uses its own semantic icon names (not raw Phosphor icon names). Pass them via \`<Icon name="icon-name" />\`.
|
|
560
|
+
|
|
561
|
+
${parsed.icons}
|
|
562
|
+
|
|
563
|
+
## Rules
|
|
564
|
+
|
|
565
|
+
- Always import Icon from \`@zvoove/unity-ui\`
|
|
566
|
+
- Use semantic icon name strings — never import Phosphor icon components directly
|
|
567
|
+
- Use \`getIconForFileExtension(extension)\` to resolve file extensions to icon names automatically
|
|
568
|
+
`
|
|
569
|
+
);
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
function generateIndexSkill(componentNames) {
|
|
573
|
+
const shadcnRows = componentNames
|
|
574
|
+
.filter((n) => SHADCN_MAP[n])
|
|
575
|
+
.map(
|
|
576
|
+
(n) =>
|
|
577
|
+
`| ${n} | [${SHADCN_MAP[n].name}](${SHADCN_MAP[n].url}) | \`unity-ui-${kebabCase(n)}\` |`
|
|
578
|
+
);
|
|
579
|
+
|
|
580
|
+
const uniqueRows = componentNames
|
|
581
|
+
.filter((n) => !SHADCN_MAP[n])
|
|
582
|
+
.map((n) => `| ${n} | — | \`unity-ui-${kebabCase(n)}\` |`);
|
|
583
|
+
|
|
584
|
+
const frontmatter = [
|
|
585
|
+
'---',
|
|
586
|
+
'name: unity-ui-index',
|
|
587
|
+
'description: "Component index for Unity UI (@zvoove/unity-ui). Maps all 40+ components to their shadcn/ui equivalents. Use this to find the right component."',
|
|
588
|
+
'category: index',
|
|
589
|
+
'---',
|
|
590
|
+
].join('\n');
|
|
591
|
+
|
|
592
|
+
return (
|
|
593
|
+
frontmatter +
|
|
594
|
+
`\n\n# Unity UI — Component Index
|
|
595
|
+
|
|
596
|
+
> Use this index to find the right Unity UI component. Each component has its own skill with full documentation.
|
|
597
|
+
|
|
598
|
+
## Components with shadcn/ui Equivalents
|
|
599
|
+
|
|
600
|
+
| Unity UI | shadcn/ui | Skill |
|
|
601
|
+
|----------|-----------|-------|
|
|
602
|
+
${shadcnRows.join('\n')}
|
|
603
|
+
|
|
604
|
+
## Unity UI–Only Components
|
|
605
|
+
|
|
606
|
+
| Unity UI | shadcn/ui | Skill |
|
|
607
|
+
|----------|-----------|-------|
|
|
608
|
+
${uniqueRows.join('\n')}
|
|
609
|
+
|
|
610
|
+
## How to Use Skills
|
|
611
|
+
|
|
612
|
+
Each skill is a standalone file with full props, usage examples, and rules for one component.
|
|
613
|
+
AI agents should load the relevant skill when they need to use a specific component.
|
|
614
|
+
`
|
|
615
|
+
);
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
// ─── Write skills to disk ───────────────────────────────────────────────────
|
|
619
|
+
|
|
620
|
+
function writeSkill(outputDir, slug, content) {
|
|
621
|
+
const skillDir = path.join(outputDir, slug);
|
|
622
|
+
fs.mkdirSync(skillDir, { recursive: true });
|
|
623
|
+
fs.writeFileSync(path.join(skillDir, 'SKILL.md'), content, 'utf8');
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
// ─── Main ───────────────────────────────────────────────────────────────────
|
|
627
|
+
|
|
628
|
+
function main() {
|
|
629
|
+
const { outputBase } = parseArgs(process.argv);
|
|
630
|
+
|
|
631
|
+
const llmsPath = findLlmsTxt();
|
|
632
|
+
if (!llmsPath) {
|
|
633
|
+
console.error(
|
|
634
|
+
'Could not find llms.txt. Make sure @zvoove/unity-ui is installed and built.'
|
|
635
|
+
);
|
|
636
|
+
process.exit(1);
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
const projectRoot = process.cwd();
|
|
640
|
+
const outputDir = path.resolve(projectRoot, outputBase, 'skills');
|
|
641
|
+
const content = fs.readFileSync(llmsPath, 'utf8');
|
|
642
|
+
const parsed = parseLlmsTxt(content);
|
|
643
|
+
|
|
644
|
+
// Clean previous unity-ui skills (leave other skills untouched)
|
|
645
|
+
if (fs.existsSync(outputDir)) {
|
|
646
|
+
for (const entry of fs.readdirSync(outputDir)) {
|
|
647
|
+
if (entry.startsWith('unity-ui-')) {
|
|
648
|
+
fs.rmSync(path.join(outputDir, entry), { recursive: true });
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
const componentNames = Object.keys(parsed.components).sort();
|
|
654
|
+
let count = 0;
|
|
655
|
+
|
|
656
|
+
// Component skills
|
|
657
|
+
for (const [name, componentContent] of Object.entries(parsed.components)) {
|
|
658
|
+
writeSkill(
|
|
659
|
+
outputDir,
|
|
660
|
+
`unity-ui-${kebabCase(name)}`,
|
|
661
|
+
generateComponentSkill(name, componentContent)
|
|
662
|
+
);
|
|
663
|
+
count++;
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
// Meta skills
|
|
667
|
+
writeSkill(outputDir, 'unity-ui-setup', generateSetupSkill(parsed));
|
|
668
|
+
writeSkill(outputDir, 'unity-ui-icons', generateIconsSkill(parsed));
|
|
669
|
+
writeSkill(outputDir, 'unity-ui-index', generateIndexSkill(componentNames));
|
|
670
|
+
count += 3;
|
|
671
|
+
|
|
672
|
+
const relOutput = path.relative(projectRoot, outputDir);
|
|
673
|
+
console.log(`\n Unity UI — Agent Skills Generator\n`);
|
|
674
|
+
console.log(` ✅ Generated ${count} skills in ${relOutput}/\n`);
|
|
675
|
+
console.log(` ${componentNames.length} component skills`);
|
|
676
|
+
console.log(` 1 setup skill (installation, spacing, responsive props)`);
|
|
677
|
+
console.log(` 1 icons skill (all icon names)`);
|
|
678
|
+
console.log(` 1 index skill (shadcn/ui mapping table)\n`);
|
|
679
|
+
console.log(` Your AI agent can now discover and use Unity UI components.`);
|
|
680
|
+
console.log(` Skills are loaded on-demand — only relevant ones are read.\n`);
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
main();
|