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.
Files changed (3) hide show
  1. package/cli.js +20 -3
  2. package/lib/installer.js +127 -10
  3. 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.0')
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 localMark = !skill.gitUrl ? ' [本地]' : '';
83
+ const sourceLabel = sourceLabels[skill.source] || sourceLabels['unknown'];
74
84
 
75
- console.log(` • ${displayName}${localMark}`);
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, globalMeta, false);
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 skillPath = join(skillDir, dirent.name);
624
- const meta = allMeta.find(m => m.name === dirent.name);
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: dirent.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}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eskill",
3
- "version": "1.2.0",
3
+ "version": "1.2.1",
4
4
  "description": "Unified AI Agent Skills Management - Install skills from Git URLs",
5
5
  "main": "index.js",
6
6
  "type": "module",