gbs-add-block 2.0.1 → 2.0.2
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/.gbs/skills/gbs-components/SKILL.md +132 -0
- package/.gbs/skills/gbs-components/references/accessibility.md +102 -0
- package/.gbs/skills/gbs-components/references/data-display.md +185 -0
- package/.gbs/skills/gbs-components/references/data-grid.md +117 -0
- package/.gbs/skills/gbs-components/references/forms.md +202 -0
- package/.gbs/skills/gbs-components/references/install.md +190 -0
- package/.gbs/skills/gbs-components/references/overlays.md +209 -0
- package/.gbs/skills/gbs-components/references/pickers.md +83 -0
- package/.gbs/skills/gbs-components/references/styling.md +180 -0
- package/.gbs/skills/gbs-components/references/toaster.md +58 -0
- package/README.md +42 -19
- package/index.cjs +1019 -665
- package/package.json +3 -2
- package/source/beta-components/data-grid/README.md +4 -3
package/index.cjs
CHANGED
|
@@ -1,665 +1,1019 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
const fs = require("fs-extra");
|
|
4
|
-
const path = require("path");
|
|
5
|
-
const crypto = require("crypto");
|
|
6
|
-
const yargs = require("yargs/yargs");
|
|
7
|
-
const { hideBin } = require("yargs/helpers");
|
|
8
|
-
const readline = require("readline");
|
|
9
|
-
|
|
10
|
-
// Configuration
|
|
11
|
-
const CONFIG = {
|
|
12
|
-
components: [
|
|
13
|
-
"Card",
|
|
14
|
-
"Select",
|
|
15
|
-
"SideBar",
|
|
16
|
-
"MultiSelect",
|
|
17
|
-
"Button",
|
|
18
|
-
"DatePicker",
|
|
19
|
-
"Checkbox",
|
|
20
|
-
"DarkMode",
|
|
21
|
-
"Dialog",
|
|
22
|
-
"Input",
|
|
23
|
-
"Modal",
|
|
24
|
-
"Spinner",
|
|
25
|
-
"Toast",
|
|
26
|
-
"Tabs",
|
|
27
|
-
"Uploader",
|
|
28
|
-
"FormRenderer",
|
|
29
|
-
"MaterialInput",
|
|
30
|
-
"TextArea",
|
|
31
|
-
"ContextMenu",
|
|
32
|
-
"Navbar",
|
|
33
|
-
"DataGrid",
|
|
34
|
-
"BreadCrumb",
|
|
35
|
-
"Bargraph",
|
|
36
|
-
"UsePaginatedData",
|
|
37
|
-
"UseUploader",
|
|
38
|
-
],
|
|
39
|
-
betaComponents: [
|
|
40
|
-
"DataGrid",
|
|
41
|
-
"Combobox",
|
|
42
|
-
"DatePicker",
|
|
43
|
-
"Toaster",
|
|
44
|
-
"FileUploader",
|
|
45
|
-
"Dialog",
|
|
46
|
-
"Input",
|
|
47
|
-
"Modal",
|
|
48
|
-
"Textarea",
|
|
49
|
-
"Button",
|
|
50
|
-
"Breadcrumb",
|
|
51
|
-
"Checkbox",
|
|
52
|
-
"Tabs",
|
|
53
|
-
"Spinner",
|
|
54
|
-
"Menu",
|
|
55
|
-
"Tooltip",
|
|
56
|
-
"Popover",
|
|
57
|
-
"Card",
|
|
58
|
-
"Skeleton",
|
|
59
|
-
"NumberInput",
|
|
60
|
-
"RadioGroup",
|
|
61
|
-
"Switch",
|
|
62
|
-
"Accordion",
|
|
63
|
-
"Alert",
|
|
64
|
-
"Avatar",
|
|
65
|
-
"Badge",
|
|
66
|
-
"Progress",
|
|
67
|
-
],
|
|
68
|
-
// Beta folder names that are not simply the lowercased component name.
|
|
69
|
-
betaFolders: {
|
|
70
|
-
DataGrid: "data-grid",
|
|
71
|
-
DatePicker: "date-picker",
|
|
72
|
-
FileUploader: "file-uploader",
|
|
73
|
-
NumberInput: "number-input",
|
|
74
|
-
RadioGroup: "radio-group",
|
|
75
|
-
},
|
|
76
|
-
// Define component dependencies
|
|
77
|
-
dependencies: {
|
|
78
|
-
FormRenderer: ["Select", "MultiSelect", "Input", "DatePicker"],
|
|
79
|
-
},
|
|
80
|
-
docs: "https://gramprokit.vercel.app/",
|
|
81
|
-
};
|
|
82
|
-
|
|
83
|
-
const
|
|
84
|
-
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
*
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
const
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
const
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
const
|
|
151
|
-
const
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
const
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
);
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
);
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
const
|
|
342
|
-
const
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
const
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
const
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
};
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
};
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
.
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
const
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
})
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require("fs-extra");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
const crypto = require("crypto");
|
|
6
|
+
const yargs = require("yargs/yargs");
|
|
7
|
+
const { hideBin } = require("yargs/helpers");
|
|
8
|
+
const readline = require("readline");
|
|
9
|
+
|
|
10
|
+
// Configuration
|
|
11
|
+
const CONFIG = {
|
|
12
|
+
components: [
|
|
13
|
+
"Card",
|
|
14
|
+
"Select",
|
|
15
|
+
"SideBar",
|
|
16
|
+
"MultiSelect",
|
|
17
|
+
"Button",
|
|
18
|
+
"DatePicker",
|
|
19
|
+
"Checkbox",
|
|
20
|
+
"DarkMode",
|
|
21
|
+
"Dialog",
|
|
22
|
+
"Input",
|
|
23
|
+
"Modal",
|
|
24
|
+
"Spinner",
|
|
25
|
+
"Toast",
|
|
26
|
+
"Tabs",
|
|
27
|
+
"Uploader",
|
|
28
|
+
"FormRenderer",
|
|
29
|
+
"MaterialInput",
|
|
30
|
+
"TextArea",
|
|
31
|
+
"ContextMenu",
|
|
32
|
+
"Navbar",
|
|
33
|
+
"DataGrid",
|
|
34
|
+
"BreadCrumb",
|
|
35
|
+
"Bargraph",
|
|
36
|
+
"UsePaginatedData",
|
|
37
|
+
"UseUploader",
|
|
38
|
+
],
|
|
39
|
+
betaComponents: [
|
|
40
|
+
"DataGrid",
|
|
41
|
+
"Combobox",
|
|
42
|
+
"DatePicker",
|
|
43
|
+
"Toaster",
|
|
44
|
+
"FileUploader",
|
|
45
|
+
"Dialog",
|
|
46
|
+
"Input",
|
|
47
|
+
"Modal",
|
|
48
|
+
"Textarea",
|
|
49
|
+
"Button",
|
|
50
|
+
"Breadcrumb",
|
|
51
|
+
"Checkbox",
|
|
52
|
+
"Tabs",
|
|
53
|
+
"Spinner",
|
|
54
|
+
"Menu",
|
|
55
|
+
"Tooltip",
|
|
56
|
+
"Popover",
|
|
57
|
+
"Card",
|
|
58
|
+
"Skeleton",
|
|
59
|
+
"NumberInput",
|
|
60
|
+
"RadioGroup",
|
|
61
|
+
"Switch",
|
|
62
|
+
"Accordion",
|
|
63
|
+
"Alert",
|
|
64
|
+
"Avatar",
|
|
65
|
+
"Badge",
|
|
66
|
+
"Progress",
|
|
67
|
+
],
|
|
68
|
+
// Beta folder names that are not simply the lowercased component name.
|
|
69
|
+
betaFolders: {
|
|
70
|
+
DataGrid: "data-grid",
|
|
71
|
+
DatePicker: "date-picker",
|
|
72
|
+
FileUploader: "file-uploader",
|
|
73
|
+
NumberInput: "number-input",
|
|
74
|
+
RadioGroup: "radio-group",
|
|
75
|
+
},
|
|
76
|
+
// Define component dependencies
|
|
77
|
+
dependencies: {
|
|
78
|
+
FormRenderer: ["Select", "MultiSelect", "Input", "DatePicker"],
|
|
79
|
+
},
|
|
80
|
+
docs: "https://gramprokit.vercel.app/",
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const { version: PACKAGE_VERSION } = require("./package.json");
|
|
84
|
+
|
|
85
|
+
const SOURCE_PATH = path.join(__dirname, "source", "components");
|
|
86
|
+
const BETA_SOURCE_PATH = path.join(__dirname, "source", "beta-components");
|
|
87
|
+
const DEFAULT_DEST_PATH = path.join(process.cwd(), "component-lib");
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* The agent skill. It teaches a coding agent how to use these components, so it
|
|
91
|
+
* belongs at the root of the project being worked on, not in component-lib.
|
|
92
|
+
*/
|
|
93
|
+
const SKILL_SOURCE_PATH = path.join(__dirname, ".gbs");
|
|
94
|
+
const SKILL_DEST_DIR = ".gbs";
|
|
95
|
+
const SKILL_NAME = "gbs-components";
|
|
96
|
+
|
|
97
|
+
/** Where SKILL.md and references/ live inside the package. */
|
|
98
|
+
const SKILL_SRC_DIR = path.join(
|
|
99
|
+
SKILL_SOURCE_PATH,
|
|
100
|
+
"skills",
|
|
101
|
+
SKILL_NAME,
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Agents we can write a copy of the skill for. Each looks in its own place and
|
|
106
|
+
* none of them reads `.gbs`, which stays the canonical copy either way.
|
|
107
|
+
*/
|
|
108
|
+
const SKILL_TARGETS = ["claude", "codex", "antigravity"];
|
|
109
|
+
|
|
110
|
+
/** Codex reads one always-on file the project usually maintains by hand, so we
|
|
111
|
+
* own a marked block inside it and never touch a line outside. */
|
|
112
|
+
const AGENTS_FILE = "AGENTS.md";
|
|
113
|
+
const BLOCK_START = "<!-- gbs-add-block:start -->";
|
|
114
|
+
const BLOCK_END = "<!-- gbs-add-block:end -->";
|
|
115
|
+
|
|
116
|
+
/** Newline, named so long template literals stay readable. */
|
|
117
|
+
const NL = String.fromCharCode(10);
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Every beta component imports its shared helpers from `../../shared`, so this
|
|
121
|
+
* folder is installed alongside whatever the user picked. It is not a component
|
|
122
|
+
* and never appears in the component list.
|
|
123
|
+
*/
|
|
124
|
+
const SHARED_DIR = "shared";
|
|
125
|
+
const MANIFEST_FILE = ".install-manifest.json";
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Tests belong to this repo, not to the projects we install into: they import
|
|
129
|
+
* vitest, which the project has no reason to have, and the shared folder's
|
|
130
|
+
* boundary tests would run against whatever else is in the user's
|
|
131
|
+
* component-lib.
|
|
132
|
+
*/
|
|
133
|
+
const TESTS_DIR = "__tests__";
|
|
134
|
+
const isTestPath = (filePath) => filePath.split(/[\\/]/).includes(TESTS_DIR);
|
|
135
|
+
|
|
136
|
+
const normalizeComponent = (component, availableComponents) =>
|
|
137
|
+
availableComponents.find(
|
|
138
|
+
(available) => available.toLowerCase() === component.toLowerCase(),
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
const getAvailableComponents = (beta = false) =>
|
|
142
|
+
beta ? CONFIG.betaComponents : CONFIG.components;
|
|
143
|
+
|
|
144
|
+
const getSourcePath = (beta = false) => (beta ? BETA_SOURCE_PATH : SOURCE_PATH);
|
|
145
|
+
|
|
146
|
+
/** The folder a component lives in, which is not always its lowercased name. */
|
|
147
|
+
const folderFor = (component, beta = false) =>
|
|
148
|
+
(beta && CONFIG.betaFolders[component]) || component.toLowerCase();
|
|
149
|
+
|
|
150
|
+
const copyCommonFiles = async (destPath) => {
|
|
151
|
+
const commonFiles = [
|
|
152
|
+
{ src: ["..", "utils.ts"], dest: "utils.ts" },
|
|
153
|
+
{ src: ["..", "globalStyle.ts"], dest: "globalStyle.ts" },
|
|
154
|
+
{ src: ["..", "theme.ts"], dest: "theme.ts" },
|
|
155
|
+
{ src: ["..", "icon"], dest: "icon" },
|
|
156
|
+
];
|
|
157
|
+
|
|
158
|
+
for (const file of commonFiles) {
|
|
159
|
+
const src = path.join(SOURCE_PATH, ...file.src);
|
|
160
|
+
const dest = path.join(destPath, file.dest);
|
|
161
|
+
await fs.copy(src, dest, { overwrite: true });
|
|
162
|
+
console.log(`✓ ${file.dest} copied successfully`);
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
/* ------------------------------------------------------------ shared folder */
|
|
167
|
+
|
|
168
|
+
const sha256 = (filePath) =>
|
|
169
|
+
crypto.createHash("sha256").update(fs.readFileSync(filePath)).digest("hex");
|
|
170
|
+
|
|
171
|
+
/** Every file under a directory, relative to it, with "/" separators. */
|
|
172
|
+
const listFiles = (dir, prefix = "") =>
|
|
173
|
+
fs
|
|
174
|
+
.readdirSync(path.join(dir, prefix), { withFileTypes: true })
|
|
175
|
+
.flatMap((entry) => {
|
|
176
|
+
const relative = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
177
|
+
if (entry.name === MANIFEST_FILE || entry.name === TESTS_DIR) return [];
|
|
178
|
+
return entry.isDirectory() ? listFiles(dir, relative) : [relative];
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
/** -1, 0 or 1, comparing dotted numeric versions such as "1.2.0". */
|
|
182
|
+
const compareVersions = (a, b) => {
|
|
183
|
+
const left = String(a).split(".").map(Number);
|
|
184
|
+
const right = String(b).split(".").map(Number);
|
|
185
|
+
for (let i = 0; i < Math.max(left.length, right.length); i += 1) {
|
|
186
|
+
const diff = (left[i] || 0) - (right[i] || 0);
|
|
187
|
+
if (diff !== 0) return diff > 0 ? 1 : -1;
|
|
188
|
+
}
|
|
189
|
+
return 0;
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
const readJson = (filePath, fallback = null) => {
|
|
193
|
+
try {
|
|
194
|
+
return JSON.parse(fs.readFileSync(filePath, "utf8"));
|
|
195
|
+
} catch {
|
|
196
|
+
return fallback;
|
|
197
|
+
}
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
/** Files the manifest says we wrote, whose contents have since changed. */
|
|
201
|
+
const locallyEdited = (dest, manifest) =>
|
|
202
|
+
!manifest
|
|
203
|
+
? []
|
|
204
|
+
: Object.keys(manifest.files).filter((file) => {
|
|
205
|
+
const target = path.join(dest, file);
|
|
206
|
+
return fs.existsSync(target) && manifest.files[file] !== sha256(target);
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Installs or updates `shared/`.
|
|
211
|
+
*
|
|
212
|
+
* Because components are copied into a project one at a time, a project can end
|
|
213
|
+
* up with components built against different versions of this folder. The rules
|
|
214
|
+
* are that `shared/` only ever moves forward, and that a file the user has
|
|
215
|
+
* edited since we wrote it is never replaced without `--force`.
|
|
216
|
+
*/
|
|
217
|
+
const installShared = async (destPath, { force = false } = {}) => {
|
|
218
|
+
const src = path.join(BETA_SOURCE_PATH, SHARED_DIR);
|
|
219
|
+
const dest = path.join(destPath, SHARED_DIR);
|
|
220
|
+
|
|
221
|
+
if (!fs.existsSync(src)) {
|
|
222
|
+
throw new Error(
|
|
223
|
+
`The shared folder is missing from ${BETA_SOURCE_PATH}. Beta components cannot be installed without it.`,
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const srcVersion =
|
|
228
|
+
(readJson(path.join(src, "version.json"), {}) || {}).version || "0.0.0";
|
|
229
|
+
const manifest = readJson(path.join(dest, MANIFEST_FILE));
|
|
230
|
+
const installedVersion = manifest ? manifest.version : null;
|
|
231
|
+
const edited = locallyEdited(dest, manifest);
|
|
232
|
+
|
|
233
|
+
if (installedVersion && !force) {
|
|
234
|
+
const order = compareVersions(installedVersion, srcVersion);
|
|
235
|
+
|
|
236
|
+
if (order > 0) {
|
|
237
|
+
console.error(
|
|
238
|
+
`\n✗ This project has shared v${installedVersion}, newer than the v${srcVersion} in this installer.`,
|
|
239
|
+
);
|
|
240
|
+
console.error(
|
|
241
|
+
" Installing it would downgrade the folder and break the components already here.",
|
|
242
|
+
);
|
|
243
|
+
console.error(
|
|
244
|
+
" Update the CLI, or re-run with --force to overwrite it anyway.",
|
|
245
|
+
);
|
|
246
|
+
process.exit(1);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if (order === 0) {
|
|
250
|
+
console.log(
|
|
251
|
+
edited.length > 0
|
|
252
|
+
? `✓ shared v${installedVersion} is already installed (keeping your ${edited.length} edited file(s))`
|
|
253
|
+
: `✓ shared v${installedVersion} is already installed`,
|
|
254
|
+
);
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
if (edited.length > 0 && !force) {
|
|
260
|
+
console.error(
|
|
261
|
+
`\n✗ These files in component-lib/${SHARED_DIR} have local changes:`,
|
|
262
|
+
);
|
|
263
|
+
edited.forEach((file) => console.error(` - ${file}`));
|
|
264
|
+
console.error(
|
|
265
|
+
"\n Updating shared/ would replace them. Re-run with --force to do that, or move",
|
|
266
|
+
);
|
|
267
|
+
console.error(
|
|
268
|
+
" your changes into your own module first: components are yours to edit,",
|
|
269
|
+
);
|
|
270
|
+
console.error(" shared/ is replaced on every update.");
|
|
271
|
+
process.exit(1);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
const files = listFiles(src);
|
|
275
|
+
for (const file of files) {
|
|
276
|
+
await fs.copy(path.join(src, file), path.join(dest, file), {
|
|
277
|
+
overwrite: true,
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Remove what an older version left behind, but only files we installed and
|
|
282
|
+
// the user has not since changed.
|
|
283
|
+
if (manifest) {
|
|
284
|
+
const removed = Object.keys(manifest.files).filter(
|
|
285
|
+
(file) => !files.includes(file),
|
|
286
|
+
);
|
|
287
|
+
for (const file of removed) {
|
|
288
|
+
const target = path.join(dest, file);
|
|
289
|
+
if (
|
|
290
|
+
fs.existsSync(target) &&
|
|
291
|
+
(force || manifest.files[file] === sha256(target))
|
|
292
|
+
) {
|
|
293
|
+
await fs.remove(target);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
fs.writeJsonSync(
|
|
299
|
+
path.join(dest, MANIFEST_FILE),
|
|
300
|
+
{
|
|
301
|
+
version: srcVersion,
|
|
302
|
+
installedAt: new Date().toISOString(),
|
|
303
|
+
files: Object.fromEntries(
|
|
304
|
+
files.map((file) => [file, sha256(path.join(src, file))]),
|
|
305
|
+
),
|
|
306
|
+
},
|
|
307
|
+
{ spaces: 2 },
|
|
308
|
+
);
|
|
309
|
+
|
|
310
|
+
console.log(
|
|
311
|
+
installedVersion
|
|
312
|
+
? `✓ shared updated ${installedVersion} → ${srcVersion}`
|
|
313
|
+
: `✓ shared v${srcVersion} installed`,
|
|
314
|
+
);
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
/* --------------------------------------------------------------- the skill */
|
|
318
|
+
|
|
319
|
+
/** Posix-style path, so manifest keys read the same on every platform. */
|
|
320
|
+
const rel = (...parts) => parts.join("/");
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Removes directories a removal left empty, up to but not including `root`.
|
|
324
|
+
*
|
|
325
|
+
* Only ever removes an empty directory, so a `.claude` folder that also holds
|
|
326
|
+
* the project's own settings survives having our skill taken out of it.
|
|
327
|
+
*/
|
|
328
|
+
const pruneEmptyDirs = async (root, from) => {
|
|
329
|
+
const stop = path.resolve(root);
|
|
330
|
+
let dir = path.dirname(path.resolve(root, from));
|
|
331
|
+
|
|
332
|
+
while (dir !== stop && dir.startsWith(stop)) {
|
|
333
|
+
if (!fs.existsSync(dir) || fs.readdirSync(dir).length > 0) return;
|
|
334
|
+
await fs.remove(dir);
|
|
335
|
+
dir = path.dirname(dir);
|
|
336
|
+
}
|
|
337
|
+
};
|
|
338
|
+
|
|
339
|
+
/** Splits `---` front matter from the body of a markdown document. */
|
|
340
|
+
const parseSkillDoc = (file) => {
|
|
341
|
+
const text = fs.readFileSync(file, "utf8").split("\r\n").join(NL);
|
|
342
|
+
const opener = "---" + NL;
|
|
343
|
+
if (!text.startsWith(opener)) return { frontMatter: "", body: text };
|
|
344
|
+
|
|
345
|
+
const marker = NL + "---" + NL;
|
|
346
|
+
const close = text.indexOf(marker, opener.length);
|
|
347
|
+
if (close === -1) return { frontMatter: "", body: text };
|
|
348
|
+
|
|
349
|
+
return {
|
|
350
|
+
frontMatter: text.slice(opener.length, close),
|
|
351
|
+
body: text.slice(close + marker.length),
|
|
352
|
+
};
|
|
353
|
+
};
|
|
354
|
+
|
|
355
|
+
/** One `key: value` line out of front matter we wrote ourselves. */
|
|
356
|
+
const frontMatterField = (frontMatter, key) => {
|
|
357
|
+
const prefix = key + ":";
|
|
358
|
+
const line = frontMatter.split(NL).find((item) => item.startsWith(prefix));
|
|
359
|
+
return line ? line.slice(prefix.length).trim() : "";
|
|
360
|
+
};
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* Claude Code: a full skill folder, references included, since it reads files
|
|
364
|
+
* bundled beside SKILL.md. Only the front matter changes — it spells the glob
|
|
365
|
+
* list `paths` where the GBS agent spells it `autoAttach`.
|
|
366
|
+
*/
|
|
367
|
+
const writeClaudeSkill = async (root) => {
|
|
368
|
+
const base = rel(".claude", "skills", SKILL_NAME);
|
|
369
|
+
const written = [];
|
|
370
|
+
|
|
371
|
+
for (const file of listFiles(SKILL_SRC_DIR)) {
|
|
372
|
+
const from = path.join(SKILL_SRC_DIR, file);
|
|
373
|
+
const to = path.join(root, base, file);
|
|
374
|
+
|
|
375
|
+
if (file === "SKILL.md") {
|
|
376
|
+
const { frontMatter, body } = parseSkillDoc(from);
|
|
377
|
+
const globs = frontMatterField(frontMatter, "autoAttach");
|
|
378
|
+
const lines = [
|
|
379
|
+
"---",
|
|
380
|
+
"name: " + (frontMatterField(frontMatter, "name") || SKILL_NAME),
|
|
381
|
+
"description: " + frontMatterField(frontMatter, "description"),
|
|
382
|
+
];
|
|
383
|
+
// Claude Code spells the glob list `paths`; the GBS agent spells it `autoAttach`.
|
|
384
|
+
if (globs) lines.push("paths: " + globs);
|
|
385
|
+
lines.push("---");
|
|
386
|
+
|
|
387
|
+
// `body` keeps the blank line that followed the original front matter.
|
|
388
|
+
await fs.outputFile(to, lines.join(NL) + NL + body);
|
|
389
|
+
} else {
|
|
390
|
+
await fs.copy(from, to, { overwrite: true });
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
written.push(rel(base, file));
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
return written;
|
|
397
|
+
};
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
* Antigravity: one markdown rule, well inside its 12,000 character limit. Its
|
|
401
|
+
* glob activation is set on the rule in the IDE rather than in the file, so the
|
|
402
|
+
* globs are stated in the text for whoever configures it.
|
|
403
|
+
*/
|
|
404
|
+
const writeAntigravityRule = async (root) => {
|
|
405
|
+
const { frontMatter, body } = parseSkillDoc(
|
|
406
|
+
path.join(SKILL_SRC_DIR, "SKILL.md"),
|
|
407
|
+
);
|
|
408
|
+
const target = rel(".agents", "rules", SKILL_NAME + ".md");
|
|
409
|
+
|
|
410
|
+
const preamble = [
|
|
411
|
+
"> Applies to React UI files: " +
|
|
412
|
+
(frontMatterField(frontMatter, "autoAttach") || "src/**/*.tsx") +
|
|
413
|
+
".",
|
|
414
|
+
"> Set that as this rule's glob if you want it attached automatically.",
|
|
415
|
+
"> Deeper reference files live in " +
|
|
416
|
+
rel(SKILL_DEST_DIR, "skills", SKILL_NAME, "references") +
|
|
417
|
+
"/.",
|
|
418
|
+
"",
|
|
419
|
+
].join(NL);
|
|
420
|
+
|
|
421
|
+
await fs.outputFile(path.join(root, target), preamble + body);
|
|
422
|
+
return [target];
|
|
423
|
+
};
|
|
424
|
+
|
|
425
|
+
/**
|
|
426
|
+
* Codex: a pointer in AGENTS.md rather than the skill itself, because that file
|
|
427
|
+
* is always in context and the skill is 5 KB. Everything outside our markers is
|
|
428
|
+
* left exactly as it was.
|
|
429
|
+
*/
|
|
430
|
+
const writeAgentsPointer = async (root) => {
|
|
431
|
+
const file = path.join(root, AGENTS_FILE);
|
|
432
|
+
const skillPath = rel(SKILL_DEST_DIR, "skills", SKILL_NAME, "SKILL.md");
|
|
433
|
+
|
|
434
|
+
const block = [
|
|
435
|
+
BLOCK_START,
|
|
436
|
+
"",
|
|
437
|
+
"## GBS components",
|
|
438
|
+
"",
|
|
439
|
+
"This project uses the GBS headless component library. Before adding or",
|
|
440
|
+
"changing UI, read `" + skillPath + "` and follow it.",
|
|
441
|
+
"",
|
|
442
|
+
"It covers the import rule (components are copied into `component-lib/`, not",
|
|
443
|
+
"imported from a package), the change-handler name each component uses, and",
|
|
444
|
+
"the props that do not exist. Deeper detail is in the `references/` folder",
|
|
445
|
+
"beside it — open the one the task needs.",
|
|
446
|
+
"",
|
|
447
|
+
BLOCK_END,
|
|
448
|
+
].join(NL);
|
|
449
|
+
|
|
450
|
+
const existing = fs.existsSync(file) ? fs.readFileSync(file, "utf8") : "";
|
|
451
|
+
let next;
|
|
452
|
+
|
|
453
|
+
if (existing.includes(BLOCK_START) && existing.includes(BLOCK_END)) {
|
|
454
|
+
next =
|
|
455
|
+
existing.slice(0, existing.indexOf(BLOCK_START)) +
|
|
456
|
+
block +
|
|
457
|
+
existing.slice(existing.indexOf(BLOCK_END) + BLOCK_END.length);
|
|
458
|
+
} else if (existing.trim()) {
|
|
459
|
+
next = existing.replace(/\s+$/, "") + NL + NL + block + NL;
|
|
460
|
+
} else {
|
|
461
|
+
next = "# AGENTS.md" + NL + NL + block + NL;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
await fs.outputFile(file, next);
|
|
465
|
+
return existing.includes(BLOCK_START) ? "updated" : "added";
|
|
466
|
+
};
|
|
467
|
+
|
|
468
|
+
/**
|
|
469
|
+
* Installs or updates the skill at the project root.
|
|
470
|
+
*
|
|
471
|
+
* `.gbs/` is always written: it is the canonical copy and the one the GBS agent
|
|
472
|
+
* reads. The other agents each look somewhere else, so they get an adapter
|
|
473
|
+
* generated from the same source rather than a second copy to maintain.
|
|
474
|
+
*
|
|
475
|
+
* Everything we write is tracked by sha256 in one manifest, so a file the user
|
|
476
|
+
* has edited is never replaced without `--force`. AGENTS.md is the exception:
|
|
477
|
+
* the project owns that file, and we only rewrite our own marked block.
|
|
478
|
+
*/
|
|
479
|
+
const installSkill = async (
|
|
480
|
+
destRoot,
|
|
481
|
+
{ force = false, targets = SKILL_TARGETS } = {},
|
|
482
|
+
) => {
|
|
483
|
+
const src = SKILL_SOURCE_PATH;
|
|
484
|
+
|
|
485
|
+
if (!fs.existsSync(src)) {
|
|
486
|
+
throw new Error(
|
|
487
|
+
`The skill is missing from ${src}. Update the CLI and try again.`,
|
|
488
|
+
);
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
const manifestPath = path.join(destRoot, SKILL_DEST_DIR, MANIFEST_FILE);
|
|
492
|
+
const manifest = readJson(manifestPath);
|
|
493
|
+
const installedVersion = manifest ? manifest.version : null;
|
|
494
|
+
const edited = locallyEdited(destRoot, manifest);
|
|
495
|
+
|
|
496
|
+
if (edited.length > 0 && !force) {
|
|
497
|
+
console.error(`${NL}✗ These skill files have local changes:`);
|
|
498
|
+
edited.forEach((file) => console.error(` - ${file}`));
|
|
499
|
+
console.error(
|
|
500
|
+
NL +
|
|
501
|
+
" Updating the skill would replace them. Re-run with --force to do that,",
|
|
502
|
+
);
|
|
503
|
+
console.error(
|
|
504
|
+
" or move your own guidance into a separate skill folder first.",
|
|
505
|
+
);
|
|
506
|
+
process.exit(1);
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
// The canonical copy.
|
|
510
|
+
const written = [];
|
|
511
|
+
for (const file of listFiles(src)) {
|
|
512
|
+
await fs.copy(path.join(src, file), path.join(destRoot, SKILL_DEST_DIR, file), {
|
|
513
|
+
overwrite: true,
|
|
514
|
+
});
|
|
515
|
+
written.push(rel(SKILL_DEST_DIR, file));
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
// One adapter per agent that looks somewhere else.
|
|
519
|
+
if (targets.includes("claude")) {
|
|
520
|
+
written.push(...(await writeClaudeSkill(destRoot)));
|
|
521
|
+
}
|
|
522
|
+
if (targets.includes("antigravity")) {
|
|
523
|
+
written.push(...(await writeAntigravityRule(destRoot)));
|
|
524
|
+
}
|
|
525
|
+
const agents = targets.includes("codex")
|
|
526
|
+
? await writeAgentsPointer(destRoot)
|
|
527
|
+
: null;
|
|
528
|
+
|
|
529
|
+
// Drop what an older version left behind, but only files we installed and
|
|
530
|
+
// the user has not since changed.
|
|
531
|
+
if (manifest) {
|
|
532
|
+
const removed = Object.keys(manifest.files).filter(
|
|
533
|
+
(file) => !written.includes(file),
|
|
534
|
+
);
|
|
535
|
+
for (const file of removed) {
|
|
536
|
+
const target = path.join(destRoot, file);
|
|
537
|
+
if (
|
|
538
|
+
fs.existsSync(target) &&
|
|
539
|
+
(force || manifest.files[file] === sha256(target))
|
|
540
|
+
) {
|
|
541
|
+
await fs.remove(target);
|
|
542
|
+
await pruneEmptyDirs(destRoot, file);
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
fs.writeJsonSync(
|
|
548
|
+
manifestPath,
|
|
549
|
+
{
|
|
550
|
+
version: PACKAGE_VERSION,
|
|
551
|
+
installedAt: new Date().toISOString(),
|
|
552
|
+
targets,
|
|
553
|
+
// Hashed after writing, because the adapters are generated rather than copied.
|
|
554
|
+
files: Object.fromEntries(
|
|
555
|
+
written.map((file) => [file, sha256(path.join(destRoot, file))]),
|
|
556
|
+
),
|
|
557
|
+
},
|
|
558
|
+
{ spaces: 2 },
|
|
559
|
+
);
|
|
560
|
+
|
|
561
|
+
console.log(
|
|
562
|
+
installedVersion
|
|
563
|
+
? `✓ skill updated ${installedVersion} → ${PACKAGE_VERSION}`
|
|
564
|
+
: `✓ skill v${PACKAGE_VERSION} installed`,
|
|
565
|
+
);
|
|
566
|
+
console.log(` ${SKILL_DEST_DIR}/ (canonical, ${listFiles(src).length} files)`);
|
|
567
|
+
if (targets.includes("claude")) {
|
|
568
|
+
console.log(` .claude/skills/${SKILL_NAME}/ for Claude Code`);
|
|
569
|
+
}
|
|
570
|
+
if (targets.includes("antigravity")) {
|
|
571
|
+
console.log(` .agents/rules/${SKILL_NAME}.md for Antigravity`);
|
|
572
|
+
}
|
|
573
|
+
if (agents) {
|
|
574
|
+
console.log(` ${AGENTS_FILE} block ${agents}, for Codex`);
|
|
575
|
+
}
|
|
576
|
+
console.log(" Commit them so the whole team's agents pick this up.");
|
|
577
|
+
};
|
|
578
|
+
|
|
579
|
+
/** `--for claude,codex` picks the adapters; `none` writes only `.gbs/`. */
|
|
580
|
+
const parseSkillTargets = (value) => {
|
|
581
|
+
if (value === undefined || value === null || value === "") {
|
|
582
|
+
return SKILL_TARGETS;
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
const wanted = String(value)
|
|
586
|
+
.split(",")
|
|
587
|
+
.map((item) => item.trim().toLowerCase())
|
|
588
|
+
.filter(Boolean);
|
|
589
|
+
|
|
590
|
+
if (wanted.includes("none")) return [];
|
|
591
|
+
|
|
592
|
+
const unknown = wanted.filter((item) => !SKILL_TARGETS.includes(item));
|
|
593
|
+
if (unknown.length > 0) {
|
|
594
|
+
console.error(
|
|
595
|
+
`Unknown --for target(s): ${unknown.join(", ")}. Valid: ${SKILL_TARGETS.join(", ")}, none.`,
|
|
596
|
+
);
|
|
597
|
+
process.exit(1);
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
return wanted;
|
|
601
|
+
};
|
|
602
|
+
|
|
603
|
+
/** `-a skill` is accepted as well as `--skill`, since both read naturally. */
|
|
604
|
+
const isSkillWord = (value) =>
|
|
605
|
+
typeof value === "string" && value.trim().toLowerCase() === "skill";
|
|
606
|
+
|
|
607
|
+
/** The files a component needs beside it, which differ between the two sets. */
|
|
608
|
+
const copySupportFiles = async (destPath, beta, options) => {
|
|
609
|
+
if (beta) {
|
|
610
|
+
await installShared(destPath, options);
|
|
611
|
+
return;
|
|
612
|
+
}
|
|
613
|
+
// The legacy components' common files. Beta components use none of them.
|
|
614
|
+
if (!fs.existsSync(path.join(destPath, "utils.ts"))) {
|
|
615
|
+
await copyCommonFiles(destPath);
|
|
616
|
+
}
|
|
617
|
+
};
|
|
618
|
+
|
|
619
|
+
/* -------------------------------------------------------------- components */
|
|
620
|
+
|
|
621
|
+
const checkComponentExists = (component, destPath, beta = false) => {
|
|
622
|
+
const componentPath = path.join(destPath, folderFor(component, beta));
|
|
623
|
+
return fs.existsSync(componentPath);
|
|
624
|
+
};
|
|
625
|
+
|
|
626
|
+
const copyComponent = async (component, destPath, beta = false) => {
|
|
627
|
+
try {
|
|
628
|
+
const componentSrc = path.join(
|
|
629
|
+
getSourcePath(beta),
|
|
630
|
+
folderFor(component, beta),
|
|
631
|
+
);
|
|
632
|
+
const componentDest = path.join(destPath, folderFor(component, beta));
|
|
633
|
+
|
|
634
|
+
if (!fs.existsSync(componentSrc)) {
|
|
635
|
+
throw new Error(
|
|
636
|
+
`Component ${component} not found in ${beta ? "beta " : ""}source directory.`,
|
|
637
|
+
);
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
await fs.copy(componentSrc, componentDest, {
|
|
641
|
+
overwrite: true,
|
|
642
|
+
filter: (source) => !isTestPath(path.relative(componentSrc, source)),
|
|
643
|
+
});
|
|
644
|
+
console.log(
|
|
645
|
+
`✓ Component ${component} installed successfully ${
|
|
646
|
+
component === "Grid"
|
|
647
|
+
? "This Version of Grid will be deprecated soon. Please Install The New Data Grid Component"
|
|
648
|
+
: ""
|
|
649
|
+
}`,
|
|
650
|
+
);
|
|
651
|
+
} catch (error) {
|
|
652
|
+
console.error(`Error installing component ${component}:`, error.message);
|
|
653
|
+
process.exit(1);
|
|
654
|
+
}
|
|
655
|
+
};
|
|
656
|
+
|
|
657
|
+
const installComponentWithDependencies = async (
|
|
658
|
+
component,
|
|
659
|
+
destPath,
|
|
660
|
+
beta = false,
|
|
661
|
+
) => {
|
|
662
|
+
// Get dependencies for the component
|
|
663
|
+
const dependencies = CONFIG.dependencies[component] || [];
|
|
664
|
+
const componentsToInstall = new Set([component, ...dependencies]);
|
|
665
|
+
|
|
666
|
+
// Check which components need to be installed
|
|
667
|
+
const pendingInstalls = beta
|
|
668
|
+
? Array.from(componentsToInstall)
|
|
669
|
+
: Array.from(componentsToInstall).filter(
|
|
670
|
+
(comp) => !checkComponentExists(comp, destPath, beta),
|
|
671
|
+
);
|
|
672
|
+
|
|
673
|
+
if (pendingInstalls.length === 0) {
|
|
674
|
+
console.log(
|
|
675
|
+
`✓ ${component} and all its dependencies are already installed.`,
|
|
676
|
+
);
|
|
677
|
+
return;
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
// Install all pending components
|
|
681
|
+
for (const comp of pendingInstalls) {
|
|
682
|
+
await copyComponent(comp, destPath, beta);
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
if (dependencies.length > 0) {
|
|
686
|
+
console.log(`\nInstalled dependencies for ${component}:`);
|
|
687
|
+
dependencies.forEach((dep) => {
|
|
688
|
+
console.log(`- ${dep}`);
|
|
689
|
+
});
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
console.log(`\nFor documentation visit: ${CONFIG.docs}`);
|
|
693
|
+
};
|
|
694
|
+
|
|
695
|
+
const installMultipleComponents = async (
|
|
696
|
+
components,
|
|
697
|
+
destPath,
|
|
698
|
+
beta = false,
|
|
699
|
+
) => {
|
|
700
|
+
const allComponentsToInstall = new Set();
|
|
701
|
+
|
|
702
|
+
// Collect all components and their dependencies
|
|
703
|
+
components.forEach((component) => {
|
|
704
|
+
const dependencies = CONFIG.dependencies[component] || [];
|
|
705
|
+
allComponentsToInstall.add(component);
|
|
706
|
+
dependencies.forEach((dep) => allComponentsToInstall.add(dep));
|
|
707
|
+
});
|
|
708
|
+
|
|
709
|
+
// Filter out already installed components
|
|
710
|
+
const pendingInstalls = beta
|
|
711
|
+
? Array.from(allComponentsToInstall)
|
|
712
|
+
: Array.from(allComponentsToInstall).filter(
|
|
713
|
+
(comp) => !checkComponentExists(comp, destPath, beta),
|
|
714
|
+
);
|
|
715
|
+
|
|
716
|
+
if (pendingInstalls.length === 0) {
|
|
717
|
+
console.log(
|
|
718
|
+
"✓ All selected components and their dependencies are already installed.",
|
|
719
|
+
);
|
|
720
|
+
return;
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
console.log(`\nInstalling ${pendingInstalls.length} components...`);
|
|
724
|
+
|
|
725
|
+
// Install all pending components
|
|
726
|
+
for (const comp of pendingInstalls) {
|
|
727
|
+
await copyComponent(comp, destPath, beta);
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
// Show dependency information
|
|
731
|
+
const allDependencies = new Set();
|
|
732
|
+
components.forEach((component) => {
|
|
733
|
+
const dependencies = CONFIG.dependencies[component] || [];
|
|
734
|
+
dependencies.forEach((dep) => allDependencies.add(dep));
|
|
735
|
+
});
|
|
736
|
+
|
|
737
|
+
if (allDependencies.size > 0) {
|
|
738
|
+
console.log(`\nDependencies installed:`);
|
|
739
|
+
Array.from(allDependencies).forEach((dep) => {
|
|
740
|
+
console.log(`- ${dep}`);
|
|
741
|
+
});
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
console.log(`\nFor documentation visit: ${CONFIG.docs}`);
|
|
745
|
+
};
|
|
746
|
+
|
|
747
|
+
const interactiveComponentSelector = async (beta = false) => {
|
|
748
|
+
return new Promise((resolve) => {
|
|
749
|
+
const rl = readline.createInterface({
|
|
750
|
+
input: process.stdin,
|
|
751
|
+
output: process.stdout,
|
|
752
|
+
});
|
|
753
|
+
|
|
754
|
+
let selectedComponents = new Set();
|
|
755
|
+
let currentIndex = 0;
|
|
756
|
+
|
|
757
|
+
const renderMenu = () => {
|
|
758
|
+
console.clear();
|
|
759
|
+
console.log("🚀 Component Installer - Interactive Mode");
|
|
760
|
+
console.log(
|
|
761
|
+
"Use ↑/↓ arrow keys to navigate, SPACE to select/deselect, ENTER to install\n",
|
|
762
|
+
);
|
|
763
|
+
|
|
764
|
+
const availableComponents = getAvailableComponents(beta);
|
|
765
|
+
|
|
766
|
+
availableComponents.forEach((component, index) => {
|
|
767
|
+
const isSelected = selectedComponents.has(component);
|
|
768
|
+
const isCurrentIndex = index === currentIndex;
|
|
769
|
+
const deps = CONFIG.dependencies[component]
|
|
770
|
+
? ` (requires: ${CONFIG.dependencies[component].join(", ")})`
|
|
771
|
+
: "";
|
|
772
|
+
|
|
773
|
+
const prefix = isCurrentIndex ? ">" : " ";
|
|
774
|
+
const checkbox = isSelected ? "☑" : "☐";
|
|
775
|
+
const line = `${prefix} ${checkbox} ${component}${deps}`;
|
|
776
|
+
|
|
777
|
+
if (isCurrentIndex) {
|
|
778
|
+
console.log(`\x1b[36m${line}\x1b[0m`); // Cyan highlight
|
|
779
|
+
} else {
|
|
780
|
+
console.log(line);
|
|
781
|
+
}
|
|
782
|
+
});
|
|
783
|
+
|
|
784
|
+
console.log(`\nSelected: ${selectedComponents.size} components`);
|
|
785
|
+
console.log("Press ENTER to install selected components, or 'q' to quit");
|
|
786
|
+
};
|
|
787
|
+
|
|
788
|
+
const handleKeyPress = (key) => {
|
|
789
|
+
switch (key) {
|
|
790
|
+
case "[A": // Up arrow
|
|
791
|
+
currentIndex = Math.max(0, currentIndex - 1);
|
|
792
|
+
renderMenu();
|
|
793
|
+
break;
|
|
794
|
+
case "[B": // Down arrow
|
|
795
|
+
currentIndex = Math.min(
|
|
796
|
+
getAvailableComponents(beta).length - 1,
|
|
797
|
+
currentIndex + 1,
|
|
798
|
+
);
|
|
799
|
+
renderMenu();
|
|
800
|
+
break;
|
|
801
|
+
case " ": // Space bar
|
|
802
|
+
const component = getAvailableComponents(beta)[currentIndex];
|
|
803
|
+
if (selectedComponents.has(component)) {
|
|
804
|
+
selectedComponents.delete(component);
|
|
805
|
+
} else {
|
|
806
|
+
selectedComponents.add(component);
|
|
807
|
+
}
|
|
808
|
+
renderMenu();
|
|
809
|
+
break;
|
|
810
|
+
case "\r": // Enter
|
|
811
|
+
if (selectedComponents.size > 0) {
|
|
812
|
+
rl.close();
|
|
813
|
+
resolve(Array.from(selectedComponents));
|
|
814
|
+
}
|
|
815
|
+
break;
|
|
816
|
+
case "q":
|
|
817
|
+
rl.close();
|
|
818
|
+
resolve([]);
|
|
819
|
+
break;
|
|
820
|
+
}
|
|
821
|
+
};
|
|
822
|
+
|
|
823
|
+
// Enable raw mode to capture arrow keys
|
|
824
|
+
process.stdin.setRawMode(true);
|
|
825
|
+
process.stdin.resume();
|
|
826
|
+
process.stdin.setEncoding("utf8");
|
|
827
|
+
process.stdin.on("data", handleKeyPress);
|
|
828
|
+
|
|
829
|
+
renderMenu();
|
|
830
|
+
|
|
831
|
+
rl.on("close", () => {
|
|
832
|
+
process.stdin.setRawMode(false);
|
|
833
|
+
process.stdin.pause();
|
|
834
|
+
});
|
|
835
|
+
});
|
|
836
|
+
};
|
|
837
|
+
|
|
838
|
+
const parseMultipleComponents = (componentString) => {
|
|
839
|
+
return componentString
|
|
840
|
+
.split(",")
|
|
841
|
+
.map((comp) => comp.trim())
|
|
842
|
+
.filter((comp) => comp.length > 0);
|
|
843
|
+
};
|
|
844
|
+
|
|
845
|
+
const validateComponents = (components, beta = false) => {
|
|
846
|
+
const availableComponents = getAvailableComponents(beta);
|
|
847
|
+
const invalidComponents = components.filter(
|
|
848
|
+
(comp) => !availableComponents.includes(comp),
|
|
849
|
+
);
|
|
850
|
+
if (invalidComponents.length > 0) {
|
|
851
|
+
if (invalidComponents.some((comp) => comp.toLowerCase() === SHARED_DIR)) {
|
|
852
|
+
console.error(
|
|
853
|
+
"`shared` is not a component: it is installed automatically with any beta component.",
|
|
854
|
+
);
|
|
855
|
+
}
|
|
856
|
+
console.error(`Invalid components: ${invalidComponents.join(", ")}`);
|
|
857
|
+
console.log(`\nAvailable ${beta ? "beta " : ""}components:`);
|
|
858
|
+
availableComponents.forEach((comp) => console.log(`- ${comp}`));
|
|
859
|
+
if (!beta) {
|
|
860
|
+
console.log("\nRedesigned beta components (install with -beta):");
|
|
861
|
+
CONFIG.betaComponents.forEach((comp) => console.log(`- ${comp}`));
|
|
862
|
+
}
|
|
863
|
+
return false;
|
|
864
|
+
}
|
|
865
|
+
return true;
|
|
866
|
+
};
|
|
867
|
+
|
|
868
|
+
const main = async () => {
|
|
869
|
+
const args = hideBin(process.argv).map((arg) => {
|
|
870
|
+
if (arg === "-beta") return "--beta";
|
|
871
|
+
if (arg === "-skill") return "--skill";
|
|
872
|
+
return arg;
|
|
873
|
+
});
|
|
874
|
+
const argv = yargs(args)
|
|
875
|
+
.option("add", {
|
|
876
|
+
alias: "a",
|
|
877
|
+
describe:
|
|
878
|
+
"Component to install (single component or comma-separated list)",
|
|
879
|
+
type: "string",
|
|
880
|
+
})
|
|
881
|
+
.option("interactive", {
|
|
882
|
+
alias: "i",
|
|
883
|
+
describe: "Interactive component selection mode",
|
|
884
|
+
type: "boolean",
|
|
885
|
+
})
|
|
886
|
+
.option("list", {
|
|
887
|
+
alias: "l",
|
|
888
|
+
describe: "List available components",
|
|
889
|
+
type: "boolean",
|
|
890
|
+
})
|
|
891
|
+
.option("beta", {
|
|
892
|
+
describe: "Install redesigned beta components",
|
|
893
|
+
type: "boolean",
|
|
894
|
+
default: false,
|
|
895
|
+
})
|
|
896
|
+
.option("skill", {
|
|
897
|
+
describe:
|
|
898
|
+
"Install the GBS agent skill at the project root, for every supported agent",
|
|
899
|
+
type: "boolean",
|
|
900
|
+
default: false,
|
|
901
|
+
})
|
|
902
|
+
.option("for", {
|
|
903
|
+
describe:
|
|
904
|
+
"With --skill: which agents to write adapters for (claude, codex, antigravity, or none)",
|
|
905
|
+
type: "string",
|
|
906
|
+
})
|
|
907
|
+
.option("force", {
|
|
908
|
+
describe: "Replace files you have edited locally in shared/ or the skill",
|
|
909
|
+
type: "boolean",
|
|
910
|
+
default: false,
|
|
911
|
+
})
|
|
912
|
+
.example("$0 -a Button", "Install a single component")
|
|
913
|
+
.example("$0 -a Button,Card,Modal", "Install multiple components")
|
|
914
|
+
.example("$0 -a DataGrid -beta", "Install the redesigned beta DataGrid")
|
|
915
|
+
.example("$0 -a Combobox -beta", "Install the redesigned beta Combobox")
|
|
916
|
+
.example("$0 -skill", "Install the agent skill for every supported agent")
|
|
917
|
+
.example("$0 -skill --for claude", "Only the Claude Code copy")
|
|
918
|
+
.example("$0 -skill --for none", "Only .gbs/, no adapters")
|
|
919
|
+
.example("$0 -a DataGrid -beta -skill", "Install a component and the skill")
|
|
920
|
+
.example("$0 -i", "Interactive selection mode")
|
|
921
|
+
.help().argv;
|
|
922
|
+
|
|
923
|
+
// List components if requested
|
|
924
|
+
if (argv.list) {
|
|
925
|
+
const availableComponents = getAvailableComponents(argv.beta);
|
|
926
|
+
console.log(`\nAvailable ${argv.beta ? "beta " : ""}components:`);
|
|
927
|
+
availableComponents.forEach((comp) => {
|
|
928
|
+
const deps = CONFIG.dependencies[comp]
|
|
929
|
+
? ` (requires: ${CONFIG.dependencies[comp].join(", ")})`
|
|
930
|
+
: "";
|
|
931
|
+
console.log(`- ${comp}${deps}`);
|
|
932
|
+
});
|
|
933
|
+
if (argv.beta) {
|
|
934
|
+
console.log(
|
|
935
|
+
`\nEvery beta component also installs component-lib/${SHARED_DIR}, which they all import.`,
|
|
936
|
+
);
|
|
937
|
+
console.log(
|
|
938
|
+
`${NL}The agent skill installs separately, at the project root, for ${SKILL_TARGETS.join(", ")} and the GBS agent:${NL} npx gbs-add-block -skill`,
|
|
939
|
+
);
|
|
940
|
+
} else {
|
|
941
|
+
console.log("\nRedesigned beta components (install with -beta):");
|
|
942
|
+
CONFIG.betaComponents.forEach((comp) => console.log(`- ${comp}`));
|
|
943
|
+
}
|
|
944
|
+
return;
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
// Interactive mode
|
|
948
|
+
if (argv.interactive) {
|
|
949
|
+
console.log("Starting interactive component selector...\n");
|
|
950
|
+
const selectedComponents = await interactiveComponentSelector(argv.beta);
|
|
951
|
+
|
|
952
|
+
if (selectedComponents.length === 0) {
|
|
953
|
+
console.log("No components selected. Exiting...");
|
|
954
|
+
return;
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
// Create destination directory in project root
|
|
958
|
+
const destPath = DEFAULT_DEST_PATH;
|
|
959
|
+
await fs.ensureDir(destPath);
|
|
960
|
+
|
|
961
|
+
await copySupportFiles(destPath, argv.beta, { force: argv.force });
|
|
962
|
+
|
|
963
|
+
// Install selected components
|
|
964
|
+
await installMultipleComponents(selectedComponents, destPath, argv.beta);
|
|
965
|
+
return;
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
// The skill goes to the project root, not component-lib, so it is handled
|
|
969
|
+
// before anything reaches the component installer.
|
|
970
|
+
if (argv.skill || isSkillWord(argv.add)) {
|
|
971
|
+
await installSkill(process.cwd(), {
|
|
972
|
+
force: argv.force,
|
|
973
|
+
targets: parseSkillTargets(argv.for),
|
|
974
|
+
});
|
|
975
|
+
|
|
976
|
+
// `-a --skill` leaves `add` empty, and `-a skill` names no component.
|
|
977
|
+
if (!argv.add || isSkillWord(argv.add)) return;
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
if (!argv.add) {
|
|
981
|
+
console.error(
|
|
982
|
+
"Please specify a component to install using -a/--add, install the agent skill with --skill, use -i/--interactive for interactive mode, or -l/--list to see available components",
|
|
983
|
+
);
|
|
984
|
+
process.exit(1);
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
// Parse components (single or multiple)
|
|
988
|
+
const componentInput = argv.add;
|
|
989
|
+
const components = parseMultipleComponents(componentInput).map(
|
|
990
|
+
(component) =>
|
|
991
|
+
normalizeComponent(component, getAvailableComponents(argv.beta)) ||
|
|
992
|
+
component,
|
|
993
|
+
);
|
|
994
|
+
|
|
995
|
+
// Validate all components
|
|
996
|
+
if (!validateComponents(components, argv.beta)) {
|
|
997
|
+
process.exit(1);
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
// Create destination directory in project root
|
|
1001
|
+
const destPath = DEFAULT_DEST_PATH;
|
|
1002
|
+
await fs.ensureDir(destPath);
|
|
1003
|
+
|
|
1004
|
+
await copySupportFiles(destPath, argv.beta, { force: argv.force });
|
|
1005
|
+
|
|
1006
|
+
// Install components
|
|
1007
|
+
if (components.length === 1) {
|
|
1008
|
+
// Single component installation (existing behavior)
|
|
1009
|
+
await installComponentWithDependencies(components[0], destPath, argv.beta);
|
|
1010
|
+
} else {
|
|
1011
|
+
// Multiple components installation
|
|
1012
|
+
await installMultipleComponents(components, destPath, argv.beta);
|
|
1013
|
+
}
|
|
1014
|
+
};
|
|
1015
|
+
|
|
1016
|
+
main().catch((error) => {
|
|
1017
|
+
console.error("Error:", error.message);
|
|
1018
|
+
process.exit(1);
|
|
1019
|
+
});
|