devskill 2.0.2 → 2.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -1
- package/README.vn.md +9 -1
- package/package.json +1 -1
- package/scripts/cli.ts +182 -27
package/README.md
CHANGED
|
@@ -48,10 +48,17 @@ npx skills add vuluu2k/skills --skill='pinia-options'
|
|
|
48
48
|
```
|
|
49
49
|
|
|
50
50
|
### Options 3: Interactive CLI
|
|
51
|
-
We've also built an elegant, interactive CLI for advanced management
|
|
51
|
+
We've also built an elegant, interactive CLI for advanced management:
|
|
52
52
|
|
|
53
53
|
```bash
|
|
54
|
+
# Open interactive menu
|
|
55
|
+
npx devskill
|
|
56
|
+
|
|
57
|
+
# Option 1: Install entire collections of skills
|
|
54
58
|
npx devskill install
|
|
59
|
+
|
|
60
|
+
# Option 2: Add specific individual skills (use Space to select)
|
|
61
|
+
npx devskill add
|
|
55
62
|
```
|
|
56
63
|
|
|
57
64
|
---
|
package/README.vn.md
CHANGED
|
@@ -27,9 +27,17 @@ Bằng cách cung cấp cho AI các tài liệu `SKILL.md` chuyên dụng, bạn
|
|
|
27
27
|
|
|
28
28
|
## 📦 Cài đặt & Bắt đầu nhanh
|
|
29
29
|
|
|
30
|
-
Quên việc copy thủ công đi. Chúng tôi đã xây dựng một CLI tương tác thanh lịch để đưa kiến thức trực
|
|
30
|
+
Quên việc copy thủ công đi. Chúng tôi đã xây dựng một CLI tương tác thanh lịch để đưa kiến thức trực tiếp vào repo của bạn.
|
|
31
|
+
|
|
31
32
|
```bash
|
|
33
|
+
# Mở menu tương tác chính
|
|
34
|
+
npx devskill
|
|
35
|
+
|
|
36
|
+
# Tuỳ chọn 1: Cài đặt toàn bộ một nhóm (collection) skills
|
|
32
37
|
npx devskill install
|
|
38
|
+
|
|
39
|
+
# Tuỳ chọn 2: Thêm các skills đơn lẻ (chọn bằng phím Space)
|
|
40
|
+
npx devskill add
|
|
33
41
|
```
|
|
34
42
|
|
|
35
43
|
> **Mẹo:** Bạn cũng có thể cài đặt mọi skill trên toàn hệ thống bằng công cụ chính thức:
|
package/package.json
CHANGED
package/scripts/cli.ts
CHANGED
|
@@ -97,9 +97,9 @@ async function initSubmodules(skipPrompt = false) {
|
|
|
97
97
|
const shouldRemove = skipPrompt
|
|
98
98
|
? true
|
|
99
99
|
: await p.confirm({
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
100
|
+
message: 'Remove these extra submodules?',
|
|
101
|
+
initialValue: true,
|
|
102
|
+
})
|
|
103
103
|
|
|
104
104
|
if (p.isCancel(shouldRemove)) {
|
|
105
105
|
p.cancel('Cancelled')
|
|
@@ -130,14 +130,14 @@ async function initSubmodules(skipPrompt = false) {
|
|
|
130
130
|
const selected = skipPrompt
|
|
131
131
|
? newProjects
|
|
132
132
|
: await p.multiselect({
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
133
|
+
message: 'Select projects to initialize',
|
|
134
|
+
options: newProjects.map(project => ({
|
|
135
|
+
value: project,
|
|
136
|
+
label: `${project.name} (${project.type})`,
|
|
137
|
+
hint: project.url,
|
|
138
|
+
})),
|
|
139
|
+
initialValues: newProjects,
|
|
140
|
+
})
|
|
141
141
|
|
|
142
142
|
if (p.isCancel(selected)) {
|
|
143
143
|
p.cancel('Cancelled')
|
|
@@ -332,9 +332,9 @@ async function cleanup(skipPrompt = false) {
|
|
|
332
332
|
const shouldRemove = skipPrompt
|
|
333
333
|
? true
|
|
334
334
|
: await p.confirm({
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
335
|
+
message: 'Remove these extra skills?',
|
|
336
|
+
initialValue: false,
|
|
337
|
+
})
|
|
338
338
|
|
|
339
339
|
if (p.isCancel(shouldRemove)) {
|
|
340
340
|
p.cancel('Cancelled')
|
|
@@ -366,7 +366,7 @@ async function cleanup(skipPrompt = false) {
|
|
|
366
366
|
|
|
367
367
|
async function installSkills() {
|
|
368
368
|
const spinner = p.spinner()
|
|
369
|
-
|
|
369
|
+
|
|
370
370
|
const allCollections: Record<string, string[]> = { ...collections }
|
|
371
371
|
for (const [vendorName, config] of Object.entries(vendors)) {
|
|
372
372
|
const vendorConfig = config as VendorConfig
|
|
@@ -379,7 +379,7 @@ async function installSkills() {
|
|
|
379
379
|
}
|
|
380
380
|
|
|
381
381
|
const selectedCollectionNames = await p.multiselect({
|
|
382
|
-
message: 'Select collections to install',
|
|
382
|
+
message: 'Select collections to install (Space to select, Enter to confirm)',
|
|
383
383
|
options: Object.keys(allCollections).map(name => ({
|
|
384
384
|
value: name,
|
|
385
385
|
label: name,
|
|
@@ -412,21 +412,45 @@ async function installSkills() {
|
|
|
412
412
|
return
|
|
413
413
|
}
|
|
414
414
|
|
|
415
|
-
const
|
|
416
|
-
message: '
|
|
417
|
-
|
|
418
|
-
|
|
415
|
+
const toolChoice = await p.select({
|
|
416
|
+
message: 'Which AI tool are you installing these skills for?',
|
|
417
|
+
options: [
|
|
418
|
+
{ value: 'skills', label: 'Generic (skills/)', hint: 'Default skills directory' },
|
|
419
|
+
{ value: '.cursor/skills', label: 'Cursor (.cursor/skills)' },
|
|
420
|
+
{ value: '.windsurf/skills', label: 'Windsurf (.windsurf/skills)' },
|
|
421
|
+
{ value: '.claude/skills', label: 'Claude Desktop (.claude/skills)' },
|
|
422
|
+
{ value: '.claudecode/skills', label: 'Claude Code (.claudecode/skills)' },
|
|
423
|
+
{ value: '.agents/skills', label: 'Antigravity (.agents/skills)' },
|
|
424
|
+
{ value: '.vscode/skills', label: 'VSCode / Copilot (.vscode/skills)' },
|
|
425
|
+
{ value: 'custom', label: 'Custom path...' }
|
|
426
|
+
]
|
|
419
427
|
})
|
|
420
428
|
|
|
421
|
-
if (p.isCancel(
|
|
429
|
+
if (p.isCancel(toolChoice)) {
|
|
422
430
|
p.cancel('Cancelled')
|
|
423
431
|
return
|
|
424
432
|
}
|
|
425
433
|
|
|
434
|
+
let skillsDirName = toolChoice as string
|
|
435
|
+
|
|
436
|
+
if (toolChoice === 'custom') {
|
|
437
|
+
const customDir = await p.text({
|
|
438
|
+
message: 'Enter custom skills directory path:',
|
|
439
|
+
initialValue: 'skills',
|
|
440
|
+
placeholder: 'skills'
|
|
441
|
+
})
|
|
442
|
+
|
|
443
|
+
if (p.isCancel(customDir)) {
|
|
444
|
+
p.cancel('Cancelled')
|
|
445
|
+
return
|
|
446
|
+
}
|
|
447
|
+
skillsDirName = customDir as string
|
|
448
|
+
}
|
|
449
|
+
|
|
426
450
|
const selectedSkills = Array.from(new Set(
|
|
427
451
|
(selectedCollectionNames as string[]).flatMap(name => allCollections[name])
|
|
428
452
|
))
|
|
429
|
-
const targetDir = join(targetProject as string, skillsDirName
|
|
453
|
+
const targetDir = join(targetProject as string, skillsDirName)
|
|
430
454
|
|
|
431
455
|
if (!existsSync(targetDir)) {
|
|
432
456
|
mkdirSync(targetDir, { recursive: true })
|
|
@@ -446,9 +470,129 @@ async function installSkills() {
|
|
|
446
470
|
if (existsSync(destPath)) {
|
|
447
471
|
rmSync(destPath, { recursive: true })
|
|
448
472
|
}
|
|
449
|
-
|
|
473
|
+
|
|
450
474
|
mkdirSync(destPath, { recursive: true })
|
|
451
|
-
|
|
475
|
+
|
|
476
|
+
const files = readdirSync(sourcePath, { recursive: true, withFileTypes: true })
|
|
477
|
+
for (const file of files) {
|
|
478
|
+
if (file.isFile()) {
|
|
479
|
+
const fullPath = join(file.parentPath, file.name)
|
|
480
|
+
const relativePath = fullPath.replace(sourcePath, '')
|
|
481
|
+
const dp = join(destPath, relativePath)
|
|
482
|
+
const dd = dirname(dp)
|
|
483
|
+
if (!existsSync(dd)) {
|
|
484
|
+
mkdirSync(dd, { recursive: true })
|
|
485
|
+
}
|
|
486
|
+
cpSync(fullPath, dp)
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
successCount++;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
spinner.stop(`Installed ${successCount}/${selectedSkills.length} skills to target project`)
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
async function installSpecificSkills() {
|
|
496
|
+
const spinner = p.spinner()
|
|
497
|
+
|
|
498
|
+
const allSkills = getExistingSkillNames()
|
|
499
|
+
|
|
500
|
+
if (allSkills.length === 0) {
|
|
501
|
+
p.log.warn('No skills found in skills directory. Try running init or sync first.')
|
|
502
|
+
return
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
const selectedSkills = await p.multiselect({
|
|
506
|
+
message: 'Select specific skills to install (Space to select, Enter to confirm)',
|
|
507
|
+
options: allSkills.map(name => ({
|
|
508
|
+
value: name,
|
|
509
|
+
label: name
|
|
510
|
+
}))
|
|
511
|
+
})
|
|
512
|
+
|
|
513
|
+
if (p.isCancel(selectedSkills)) {
|
|
514
|
+
p.cancel('Cancelled')
|
|
515
|
+
return
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
if (selectedSkills.length === 0) {
|
|
519
|
+
p.log.warn('No skills selected')
|
|
520
|
+
return
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
const targetProject = await p.text({
|
|
524
|
+
message: 'Enter target project directory path (relative or absolute)',
|
|
525
|
+
initialValue: process.cwd(),
|
|
526
|
+
placeholder: process.cwd(),
|
|
527
|
+
validate: (value) => {
|
|
528
|
+
if (!value) return 'Path is required'
|
|
529
|
+
if (!existsSync(value)) return 'Directory does not exist'
|
|
530
|
+
}
|
|
531
|
+
})
|
|
532
|
+
|
|
533
|
+
if (p.isCancel(targetProject)) {
|
|
534
|
+
p.cancel('Cancelled')
|
|
535
|
+
return
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
const toolChoice = await p.select({
|
|
539
|
+
message: 'Which AI tool are you installing these skills for?',
|
|
540
|
+
options: [
|
|
541
|
+
{ value: 'skills', label: 'Generic (skills/)', hint: 'Default skills directory' },
|
|
542
|
+
{ value: '.cursor/skills', label: 'Cursor (.cursor/skills)' },
|
|
543
|
+
{ value: '.windsurf/rules', label: 'Windsurf (.windsurf/rules)' },
|
|
544
|
+
{ value: '.claude/skills', label: 'Claude Desktop (.claude/skills)' },
|
|
545
|
+
{ value: '.claudecode/skills', label: 'Claude Code (.claudecode/skills)' },
|
|
546
|
+
{ value: '.agents/skills', label: 'Antigravity (.agents/skills)' },
|
|
547
|
+
{ value: '.vscode/skills', label: 'VSCode / Copilot (.vscode/skills)' },
|
|
548
|
+
{ value: 'custom', label: 'Custom path...' }
|
|
549
|
+
]
|
|
550
|
+
})
|
|
551
|
+
|
|
552
|
+
if (p.isCancel(toolChoice)) {
|
|
553
|
+
p.cancel('Cancelled')
|
|
554
|
+
return
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
let skillsDirName = toolChoice as string
|
|
558
|
+
|
|
559
|
+
if (toolChoice === 'custom') {
|
|
560
|
+
const customDir = await p.text({
|
|
561
|
+
message: 'Enter custom skills directory path:',
|
|
562
|
+
initialValue: 'skills',
|
|
563
|
+
placeholder: 'skills'
|
|
564
|
+
})
|
|
565
|
+
|
|
566
|
+
if (p.isCancel(customDir)) {
|
|
567
|
+
p.cancel('Cancelled')
|
|
568
|
+
return
|
|
569
|
+
}
|
|
570
|
+
skillsDirName = customDir as string
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
const targetDir = join(targetProject as string, skillsDirName)
|
|
574
|
+
|
|
575
|
+
if (!existsSync(targetDir)) {
|
|
576
|
+
mkdirSync(targetDir, { recursive: true })
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
spinner.start(`Installing ${selectedSkills.length} skills to ${targetDir}...`)
|
|
580
|
+
|
|
581
|
+
let successCount = 0;
|
|
582
|
+
for (const skill of selectedSkills as string[]) {
|
|
583
|
+
const sourcePath = join(root, 'skills', skill)
|
|
584
|
+
if (!existsSync(sourcePath)) {
|
|
585
|
+
p.log.warn(`Skill not found: ${skill}`)
|
|
586
|
+
continue
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
const destPath = join(targetDir, skill)
|
|
590
|
+
if (existsSync(destPath)) {
|
|
591
|
+
rmSync(destPath, { recursive: true })
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
mkdirSync(destPath, { recursive: true })
|
|
595
|
+
|
|
452
596
|
const files = readdirSync(sourcePath, { recursive: true, withFileTypes: true })
|
|
453
597
|
for (const file of files) {
|
|
454
598
|
if (file.isFile()) {
|
|
@@ -502,15 +646,22 @@ async function main() {
|
|
|
502
646
|
}
|
|
503
647
|
|
|
504
648
|
if (command === 'install') {
|
|
505
|
-
p.intro('Skills Manager - Install')
|
|
649
|
+
p.intro('Skills Manager - Install Groups')
|
|
506
650
|
await installSkills()
|
|
507
651
|
p.outro('Done')
|
|
508
652
|
return
|
|
509
653
|
}
|
|
510
654
|
|
|
655
|
+
if (command === 'add') {
|
|
656
|
+
p.intro('Skills Manager - Add Specific Skills')
|
|
657
|
+
await installSpecificSkills()
|
|
658
|
+
p.outro('Done')
|
|
659
|
+
return
|
|
660
|
+
}
|
|
661
|
+
|
|
511
662
|
if (skipPrompt) {
|
|
512
663
|
p.log.error('Command required when using -y flag')
|
|
513
|
-
p.log.info('Available commands: install, init, sync, check, cleanup')
|
|
664
|
+
p.log.info('Available commands: install, add, init, sync, check, cleanup')
|
|
514
665
|
process.exit(1)
|
|
515
666
|
}
|
|
516
667
|
|
|
@@ -519,7 +670,8 @@ async function main() {
|
|
|
519
670
|
const action = await p.select({
|
|
520
671
|
message: 'What would you like to do?',
|
|
521
672
|
options: [
|
|
522
|
-
{ value: 'install', label: 'Install collections', hint: 'Copy skill collections to a local project' },
|
|
673
|
+
{ value: 'install', label: 'Install collections', hint: 'Copy entire skill collections to a local project' },
|
|
674
|
+
{ value: 'add', label: 'Add specific skills', hint: 'Choose individual skills to add to your project' },
|
|
523
675
|
{ value: 'sync', label: 'Sync submodules', hint: 'Pull latest and sync Type 2 skills' },
|
|
524
676
|
{ value: 'init', label: 'Init submodules', hint: 'Add new submodules from meta.ts' },
|
|
525
677
|
{ value: 'check', label: 'Check updates', hint: 'See available updates' },
|
|
@@ -536,6 +688,9 @@ async function main() {
|
|
|
536
688
|
case 'install':
|
|
537
689
|
await installSkills()
|
|
538
690
|
break
|
|
691
|
+
case 'add':
|
|
692
|
+
await installSpecificSkills()
|
|
693
|
+
break
|
|
539
694
|
case 'init':
|
|
540
695
|
await initSubmodules()
|
|
541
696
|
break
|