eskill 1.2.0 → 1.2.1
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 +20 -3
- package/lib/installer.js +127 -10
- 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.1')
|
|
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);
|
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
|
}
|
|
@@ -670,9 +758,19 @@ export async function linkSkill(skillName) {
|
|
|
670
758
|
throw new Error(`全局仓库中不存在技能: ${skillName}`);
|
|
671
759
|
}
|
|
672
760
|
|
|
673
|
-
//
|
|
761
|
+
// 检查本地技能目录是否存在
|
|
674
762
|
if (!existsSync(localSkillsDir)) {
|
|
763
|
+
console.log(`\n📁 本地技能目录不存在`);
|
|
764
|
+
console.log(` 需要创建: ${localSkillsDir}\n`);
|
|
765
|
+
|
|
766
|
+
const createDir = await confirmAction('是否创建本地技能目录?');
|
|
767
|
+
if (!createDir) {
|
|
768
|
+
console.log('\n已取消链接\n');
|
|
769
|
+
return { success: false, cancelled: true };
|
|
770
|
+
}
|
|
771
|
+
|
|
675
772
|
mkdirSync(localSkillsDir, { recursive: true });
|
|
773
|
+
console.log(`✓ 已创建目录: ${localSkillsDir}\n`);
|
|
676
774
|
}
|
|
677
775
|
|
|
678
776
|
// 检查本地是否已存在同名技能
|
|
@@ -704,6 +802,25 @@ export async function linkSkill(skillName) {
|
|
|
704
802
|
console.log(` 全局路径: ${globalSkillPath}`);
|
|
705
803
|
console.log(` 说明: 本地技能指向全局仓库,全局更新会自动同步\n`);
|
|
706
804
|
|
|
805
|
+
// 保存元数据,标记来源为软链接
|
|
806
|
+
const globalMeta = getSkillMeta(skillName, true);
|
|
807
|
+
if (globalMeta) {
|
|
808
|
+
saveSkillMeta(skillName, {
|
|
809
|
+
...globalMeta,
|
|
810
|
+
source: 'symlink',
|
|
811
|
+
installedAt: new Date().toISOString(),
|
|
812
|
+
updatedAt: new Date().toISOString()
|
|
813
|
+
}, false);
|
|
814
|
+
} else {
|
|
815
|
+
// 如果全局没有元数据,创建基础元数据
|
|
816
|
+
saveSkillMeta(skillName, {
|
|
817
|
+
name: skillName,
|
|
818
|
+
source: 'symlink',
|
|
819
|
+
installedAt: new Date().toISOString(),
|
|
820
|
+
updatedAt: new Date().toISOString()
|
|
821
|
+
}, false);
|
|
822
|
+
}
|
|
823
|
+
|
|
707
824
|
return { success: true, path: localSkillPath };
|
|
708
825
|
} catch (error) {
|
|
709
826
|
throw new Error(`创建软链接失败: ${error.message}`);
|