eskill 1.2.0 → 1.2.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/cli.js +21 -4
- package/lib/installer.js +150 -17
- package/package.json +1 -1
package/cli.js
CHANGED
|
@@ -13,7 +13,7 @@ const program = new Command();
|
|
|
13
13
|
program
|
|
14
14
|
.name('eskill')
|
|
15
15
|
.description('Unified AI Agent Skills Management - Install skills from Git URLs')
|
|
16
|
-
.version('1.2.
|
|
16
|
+
.version('1.2.2')
|
|
17
17
|
.option('-g, --global', '使用全局技能目录(~/.eskill/skills/),否则使用当前目录(./.claude/skills/)', false);
|
|
18
18
|
|
|
19
19
|
// 安装命令
|
|
@@ -67,17 +67,34 @@ program
|
|
|
67
67
|
|
|
68
68
|
const location = global ? '~/.eskill/skills/' : './.claude/skills/';
|
|
69
69
|
console.log(`\n已安装的技能 (${location}):\n`);
|
|
70
|
+
|
|
71
|
+
// 来源类型显示映射
|
|
72
|
+
const sourceLabels = {
|
|
73
|
+
'symlink': '🔗 软链',
|
|
74
|
+
'copied-from-global': '📋 复制',
|
|
75
|
+
'github': '📥 GitHub',
|
|
76
|
+
'local': '👤 本地',
|
|
77
|
+
'unknown': '❓ 未知'
|
|
78
|
+
};
|
|
79
|
+
|
|
70
80
|
skills.forEach(skill => {
|
|
71
81
|
const displayName = skill.author ? `${skill.name}@${skill.author}` : skill.name;
|
|
72
82
|
const versionInfo = skill.version || (skill.commitHash ? `#${skill.commitHash.substring(0, 7)}` : '');
|
|
73
|
-
const
|
|
83
|
+
const sourceLabel = sourceLabels[skill.source] || sourceLabels['unknown'];
|
|
74
84
|
|
|
75
|
-
console.log(` • ${displayName}${
|
|
85
|
+
console.log(` • ${displayName} [${sourceLabel}]`);
|
|
76
86
|
if (versionInfo) {
|
|
77
87
|
console.log(` 版本: ${versionInfo}`);
|
|
78
88
|
}
|
|
79
89
|
});
|
|
80
90
|
console.log(`\n总计: ${skills.length} 个技能\n`);
|
|
91
|
+
|
|
92
|
+
// 显示来源说明
|
|
93
|
+
console.log('来源说明:');
|
|
94
|
+
console.log(' 🔗 软链 - 指向全局仓库的软链接');
|
|
95
|
+
console.log(' 📋 复制 - 从全局仓库复制到本地');
|
|
96
|
+
console.log(' 📥 GitHub - 从 GitHub 直接下载');
|
|
97
|
+
console.log(' 👤 本地 - 用户手动创建\n');
|
|
81
98
|
} catch (error) {
|
|
82
99
|
console.error(`错误: ${error.message}`);
|
|
83
100
|
process.exit(1);
|
|
@@ -109,7 +126,7 @@ program
|
|
|
109
126
|
program
|
|
110
127
|
.command('link')
|
|
111
128
|
.description('将全局技能软链接到本地')
|
|
112
|
-
.argument('<name>', '
|
|
129
|
+
.argument('<name>', '技能名称(支持 name@author 格式)')
|
|
113
130
|
.action(async (name) => {
|
|
114
131
|
try {
|
|
115
132
|
const result = await linkSkill(name);
|
package/lib/installer.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { execSync } from 'child_process';
|
|
2
|
-
import { existsSync, mkdirSync, symlinkSync, rmSync, readdirSync, writeFileSync, readFileSync, copyFileSync } from 'fs';
|
|
2
|
+
import { existsSync, mkdirSync, symlinkSync, rmSync, readdirSync, writeFileSync, readFileSync, copyFileSync, lstatSync } from 'fs';
|
|
3
3
|
import { join, basename, dirname } from 'path';
|
|
4
4
|
import { tmpdir, homedir } from 'os';
|
|
5
5
|
import { fileURLToPath } from 'url';
|
|
@@ -398,6 +398,21 @@ async function copyFromGlobal(skillName, options = {}) {
|
|
|
398
398
|
const sourcePath = join(globalSkillsDir, skillName);
|
|
399
399
|
const targetPath = join(localSkillsDir, skillName);
|
|
400
400
|
|
|
401
|
+
// 检查本地技能目录是否存在
|
|
402
|
+
if (!existsSync(localSkillsDir)) {
|
|
403
|
+
console.log(`\n📁 本地技能目录不存在`);
|
|
404
|
+
console.log(` 需要创建: ${localSkillsDir}\n`);
|
|
405
|
+
|
|
406
|
+
const createDir = await confirmAction('是否创建本地技能目录?');
|
|
407
|
+
if (!createDir) {
|
|
408
|
+
console.log('\n已取消安装\n');
|
|
409
|
+
return { success: false, cancelled: true };
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
mkdirSync(localSkillsDir, { recursive: true });
|
|
413
|
+
console.log(`✓ 已创建目录: ${localSkillsDir}\n`);
|
|
414
|
+
}
|
|
415
|
+
|
|
401
416
|
// 检查本地是否已存在同名技能
|
|
402
417
|
if (existsSync(targetPath)) {
|
|
403
418
|
if (!force) {
|
|
@@ -421,10 +436,23 @@ async function copyFromGlobal(skillName, options = {}) {
|
|
|
421
436
|
|
|
422
437
|
execSync(`cp -r "${sourcePath}" "${targetPath}"`, { stdio: 'inherit' });
|
|
423
438
|
|
|
424
|
-
//
|
|
439
|
+
// 复制元数据,并标记来源
|
|
425
440
|
const globalMeta = getSkillMeta(skillName, true);
|
|
426
441
|
if (globalMeta) {
|
|
427
|
-
saveSkillMeta(skillName,
|
|
442
|
+
saveSkillMeta(skillName, {
|
|
443
|
+
...globalMeta,
|
|
444
|
+
source: 'copied-from-global',
|
|
445
|
+
installedAt: new Date().toISOString(),
|
|
446
|
+
updatedAt: new Date().toISOString()
|
|
447
|
+
}, false);
|
|
448
|
+
} else {
|
|
449
|
+
// 如果全局没有元数据,创建基础元数据
|
|
450
|
+
saveSkillMeta(skillName, {
|
|
451
|
+
name: skillName,
|
|
452
|
+
source: 'copied-from-global',
|
|
453
|
+
installedAt: new Date().toISOString(),
|
|
454
|
+
updatedAt: new Date().toISOString()
|
|
455
|
+
}, false);
|
|
428
456
|
}
|
|
429
457
|
|
|
430
458
|
console.log(`\n✓ 技能已从全局仓库复制到本地`);
|
|
@@ -504,9 +532,19 @@ export async function installFromGitUrl(gitUrl, options = {}) {
|
|
|
504
532
|
console.log(`路径: ${parsed.path}`);
|
|
505
533
|
}
|
|
506
534
|
|
|
507
|
-
//
|
|
535
|
+
// 检查技能目录是否存在
|
|
508
536
|
if (!existsSync(skillsDir)) {
|
|
537
|
+
console.log(`\n📁 技能目录不存在`);
|
|
538
|
+
console.log(` 需要创建: ${skillsDir}\n`);
|
|
539
|
+
|
|
540
|
+
const createDir = await confirmAction('是否创建技能目录?');
|
|
541
|
+
if (!createDir) {
|
|
542
|
+
console.log('\n已取消安装\n');
|
|
543
|
+
return { success: false, cancelled: true };
|
|
544
|
+
}
|
|
545
|
+
|
|
509
546
|
mkdirSync(skillsDir, { recursive: true });
|
|
547
|
+
console.log(`✓ 已创建目录: ${skillsDir}\n`);
|
|
510
548
|
}
|
|
511
549
|
|
|
512
550
|
// 检查是否已存在同名技能
|
|
@@ -585,6 +623,7 @@ export async function installFromGitUrl(gitUrl, options = {}) {
|
|
|
585
623
|
branch: parsed.branch,
|
|
586
624
|
commitHash,
|
|
587
625
|
version,
|
|
626
|
+
source: global ? 'github' : 'github',
|
|
588
627
|
installedAt: new Date().toISOString(),
|
|
589
628
|
updatedAt: new Date().toISOString()
|
|
590
629
|
}, global);
|
|
@@ -614,22 +653,71 @@ export function listSkills(agent = 'claude', global = false) {
|
|
|
614
653
|
|
|
615
654
|
// 读取目录中的技能
|
|
616
655
|
const skillDirs = readdirSync(skillDir, { withFileTypes: true })
|
|
617
|
-
.filter(dirent => dirent.isDirectory());
|
|
656
|
+
.filter(dirent => dirent.isDirectory() || dirent.isSymbolicLink());
|
|
618
657
|
|
|
619
658
|
// 读取所有元数据
|
|
620
659
|
const allMeta = getAllSkillsMeta(global);
|
|
621
660
|
|
|
661
|
+
// 获取全局技能目录(用于检测是否从全局复制)
|
|
662
|
+
const globalSkillsDir = getSkillsDir(true);
|
|
663
|
+
const globalSkillsExists = existsSync(globalSkillsDir);
|
|
664
|
+
const globalSkillNames = globalSkillsExists
|
|
665
|
+
? readdirSync(globalSkillsDir, { withFileTypes: true })
|
|
666
|
+
.filter(dirent => dirent.isDirectory())
|
|
667
|
+
.map(dirent => dirent.name)
|
|
668
|
+
: [];
|
|
669
|
+
|
|
622
670
|
return skillDirs.map(dirent => {
|
|
623
|
-
const
|
|
624
|
-
const
|
|
671
|
+
const skillName = dirent.name;
|
|
672
|
+
const skillPath = join(skillDir, skillName);
|
|
673
|
+
const meta = allMeta.find(m => m.name === skillName);
|
|
674
|
+
|
|
675
|
+
// 优先使用元数据中的 source 字段
|
|
676
|
+
let source = meta?.source || null;
|
|
677
|
+
let isSymlink = false;
|
|
678
|
+
|
|
679
|
+
// 如果元数据中没有 source,则进行检测
|
|
680
|
+
if (!source) {
|
|
681
|
+
try {
|
|
682
|
+
const stat = lstatSync(skillPath);
|
|
683
|
+
isSymlink = stat.isSymbolicLink();
|
|
684
|
+
|
|
685
|
+
if (isSymlink) {
|
|
686
|
+
source = 'symlink';
|
|
687
|
+
} else if (meta?.gitUrl) {
|
|
688
|
+
// 有 gitUrl,判断是否从全局复制
|
|
689
|
+
if (!global && globalSkillNames.includes(skillName)) {
|
|
690
|
+
source = 'copied-from-global';
|
|
691
|
+
} else {
|
|
692
|
+
source = 'github';
|
|
693
|
+
}
|
|
694
|
+
} else {
|
|
695
|
+
// 没有 gitUrl,是用户自己创建的
|
|
696
|
+
source = 'local';
|
|
697
|
+
}
|
|
698
|
+
} catch (error) {
|
|
699
|
+
// 检测失败,默认为 local
|
|
700
|
+
source = 'local';
|
|
701
|
+
}
|
|
702
|
+
} else {
|
|
703
|
+
// 从元数据中读取是否为符号链接
|
|
704
|
+
try {
|
|
705
|
+
isSymlink = lstatSync(skillPath).isSymbolicLink();
|
|
706
|
+
} catch (error) {
|
|
707
|
+
isSymlink = false;
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
|
|
625
711
|
return {
|
|
626
|
-
name:
|
|
712
|
+
name: skillName,
|
|
627
713
|
path: skillPath,
|
|
628
714
|
author: meta?.author || null,
|
|
629
715
|
version: meta?.version || null,
|
|
630
716
|
commitHash: meta?.commitHash || null,
|
|
631
717
|
installedAt: meta?.installedAt || null,
|
|
632
|
-
gitUrl: meta?.gitUrl || null
|
|
718
|
+
gitUrl: meta?.gitUrl || null,
|
|
719
|
+
source: source,
|
|
720
|
+
isSymlink: isSymlink
|
|
633
721
|
};
|
|
634
722
|
});
|
|
635
723
|
}
|
|
@@ -654,30 +742,47 @@ export function removeSkill(skillName, agent = 'claude', global = false) {
|
|
|
654
742
|
* 链接全局技能到本地
|
|
655
743
|
*/
|
|
656
744
|
export async function linkSkill(skillName) {
|
|
745
|
+
// 解析 skill-name@author 格式,提取实际技能名
|
|
746
|
+
let actualSkillName = skillName;
|
|
747
|
+
const match = skillName.match(/^([^@]+)@([^@]+)$/);
|
|
748
|
+
if (match) {
|
|
749
|
+
actualSkillName = match[1];
|
|
750
|
+
}
|
|
751
|
+
|
|
657
752
|
const globalSkillsDir = getSkillsDir(true);
|
|
658
753
|
const localSkillsDir = getSkillsDir(false);
|
|
659
754
|
|
|
660
|
-
const globalSkillPath = join(globalSkillsDir,
|
|
661
|
-
const localSkillPath = join(localSkillsDir,
|
|
755
|
+
const globalSkillPath = join(globalSkillsDir, actualSkillName);
|
|
756
|
+
const localSkillPath = join(localSkillsDir, actualSkillName);
|
|
662
757
|
|
|
663
758
|
// 检查全局是否存在该技能
|
|
664
759
|
if (!existsSync(globalSkillPath)) {
|
|
665
|
-
console.log(`\n❌ 全局仓库中不存在技能: ${
|
|
666
|
-
console.log(` 位置: ~/.eskill/skills/${
|
|
760
|
+
console.log(`\n❌ 全局仓库中不存在技能: ${actualSkillName}`);
|
|
761
|
+
console.log(` 位置: ~/.eskill/skills/${actualSkillName}\n`);
|
|
667
762
|
console.log(`💡 提示:`);
|
|
668
763
|
console.log(` - 使用 "eskill install -g ${skillName}" 先安装到全局仓库`);
|
|
669
764
|
console.log(` - 或使用 "eskill install" 直接安装到本地\n`);
|
|
670
|
-
throw new Error(`全局仓库中不存在技能: ${
|
|
765
|
+
throw new Error(`全局仓库中不存在技能: ${actualSkillName}`);
|
|
671
766
|
}
|
|
672
767
|
|
|
673
|
-
//
|
|
768
|
+
// 检查本地技能目录是否存在
|
|
674
769
|
if (!existsSync(localSkillsDir)) {
|
|
770
|
+
console.log(`\n📁 本地技能目录不存在`);
|
|
771
|
+
console.log(` 需要创建: ${localSkillsDir}\n`);
|
|
772
|
+
|
|
773
|
+
const createDir = await confirmAction('是否创建本地技能目录?');
|
|
774
|
+
if (!createDir) {
|
|
775
|
+
console.log('\n已取消链接\n');
|
|
776
|
+
return { success: false, cancelled: true };
|
|
777
|
+
}
|
|
778
|
+
|
|
675
779
|
mkdirSync(localSkillsDir, { recursive: true });
|
|
780
|
+
console.log(`✓ 已创建目录: ${localSkillsDir}\n`);
|
|
676
781
|
}
|
|
677
782
|
|
|
678
783
|
// 检查本地是否已存在同名技能
|
|
679
784
|
if (existsSync(localSkillPath)) {
|
|
680
|
-
console.log(`\n⚠️ 本地已存在同名技能: ${
|
|
785
|
+
console.log(`\n⚠️ 本地已存在同名技能: ${actualSkillName}`);
|
|
681
786
|
console.log(` 位置: ${localSkillPath}\n`);
|
|
682
787
|
|
|
683
788
|
const overwrite = await confirmAction('是否删除本地技能并创建软链接?');
|
|
@@ -699,11 +804,30 @@ export async function linkSkill(skillName) {
|
|
|
699
804
|
symlinkSync(globalSkillPath, localSkillPath, 'dir');
|
|
700
805
|
|
|
701
806
|
console.log(`✓ 软链接创建成功`);
|
|
702
|
-
console.log(` 技能: ${
|
|
807
|
+
console.log(` 技能: ${actualSkillName}`);
|
|
703
808
|
console.log(` 本地路径: ${localSkillPath}`);
|
|
704
809
|
console.log(` 全局路径: ${globalSkillPath}`);
|
|
705
810
|
console.log(` 说明: 本地技能指向全局仓库,全局更新会自动同步\n`);
|
|
706
811
|
|
|
812
|
+
// 保存元数据,标记来源为软链接
|
|
813
|
+
const globalMeta = getSkillMeta(actualSkillName, true);
|
|
814
|
+
if (globalMeta) {
|
|
815
|
+
saveSkillMeta(actualSkillName, {
|
|
816
|
+
...globalMeta,
|
|
817
|
+
source: 'symlink',
|
|
818
|
+
installedAt: new Date().toISOString(),
|
|
819
|
+
updatedAt: new Date().toISOString()
|
|
820
|
+
}, false);
|
|
821
|
+
} else {
|
|
822
|
+
// 如果全局没有元数据,创建基础元数据
|
|
823
|
+
saveSkillMeta(actualSkillName, {
|
|
824
|
+
name: actualSkillName,
|
|
825
|
+
source: 'symlink',
|
|
826
|
+
installedAt: new Date().toISOString(),
|
|
827
|
+
updatedAt: new Date().toISOString()
|
|
828
|
+
}, false);
|
|
829
|
+
}
|
|
830
|
+
|
|
707
831
|
return { success: true, path: localSkillPath };
|
|
708
832
|
} catch (error) {
|
|
709
833
|
throw new Error(`创建软链接失败: ${error.message}`);
|
|
@@ -728,6 +852,15 @@ export function cleanupAllSkills(global = false) {
|
|
|
728
852
|
console.error(`清理失败: ${error.message}`);
|
|
729
853
|
throw error;
|
|
730
854
|
}
|
|
855
|
+
|
|
856
|
+
// 清空元数据文件
|
|
857
|
+
try {
|
|
858
|
+
saveAllSkillsMeta([], global);
|
|
859
|
+
const metaLocation = global ? '~/.eskill/skills/.eskill-meta.json' : './.eskill-meta.json';
|
|
860
|
+
console.log(`✓ 已清空元数据: ${metaLocation}`);
|
|
861
|
+
} catch (error) {
|
|
862
|
+
console.error(`清空元数据失败: ${error.message}`);
|
|
863
|
+
}
|
|
731
864
|
}
|
|
732
865
|
|
|
733
866
|
/**
|