eskill 1.2.3 → 1.3.0

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 +21 -2
  2. package/lib/installer.js +114 -5
  3. package/package.json +1 -1
package/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { Command } from 'commander';
3
3
  import ora from 'ora';
4
- import { installFromGitUrl, listSkills, removeSkill, linkSkill, cleanupAllSkills, cleanupAll, updateSkill, updateAllSkills } from './lib/installer.js';
4
+ import { installFromGitUrl, listSkills, removeSkill, linkSkill, uploadSkill, cleanupAllSkills, cleanupAll, updateSkill, updateAllSkills } from './lib/installer.js';
5
5
  import { AGENTS, getDefaultAgent } from './lib/agent-config.js';
6
6
  import { bashCompletionScript, zshCompletionScript, listSkillsForCompletion } from './lib/completion.js';
7
7
  import { searchSkills, formatSkillList } from './lib/search.js';
@@ -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.3')
16
+ .version('1.3.0')
17
17
  .option('-g, --global', '使用全局技能目录(~/.eskill/skills/),否则使用当前目录(./.claude/skills/)', false);
18
18
 
19
19
  // 安装命令
@@ -141,6 +141,25 @@ program
141
141
  }
142
142
  });
143
143
 
144
+ // 上传命令
145
+ program
146
+ .command('upload')
147
+ .description('上传本地技能到全局仓库')
148
+ .argument('<name>', '技能名称(支持 name@author 格式)')
149
+ .action(async (name) => {
150
+ try {
151
+ const result = await uploadSkill(name);
152
+
153
+ // 如果用户取消上传,正常退出
154
+ if (result && result.cancelled) {
155
+ process.exit(0);
156
+ }
157
+ } catch (error) {
158
+ console.error(`\n❌ 上传失败: ${error.message}`);
159
+ process.exit(1);
160
+ }
161
+ });
162
+
144
163
  // 更新命令
145
164
  program
146
165
  .command('update')
package/lib/installer.js CHANGED
@@ -434,7 +434,12 @@ async function copyFromGlobal(skillName, options = {}) {
434
434
  console.log(` 源: ${sourcePath}`);
435
435
  console.log(` 目标: ${targetPath}\n`);
436
436
 
437
- execSync(`cp -r "${sourcePath}" "${targetPath}"`, { stdio: 'inherit' });
437
+ // 使用 -L 选项解引用符号链接,复制实际文件内容
438
+ if (process.platform === 'win32') {
439
+ execSync(`xcopy "${sourcePath}" "${targetPath}" /E /I /H /Y`, { stdio: 'inherit' });
440
+ } else {
441
+ execSync(`cp -rL "${sourcePath}" "${targetPath}"`, { stdio: 'inherit' });
442
+ }
438
443
 
439
444
  // 复制元数据,并标记来源
440
445
  const globalMeta = getSkillMeta(skillName, true);
@@ -597,12 +602,13 @@ export async function installFromGitUrl(gitUrl, options = {}) {
597
602
  console.log(`\n创建符号链接: ${sourcePath} -> ${targetPath}`);
598
603
  symlinkSync(sourcePath, targetPath, 'dir');
599
604
  } else {
600
- // 复制文件(Windows 使用 xcopy,Linux/Mac 使用 cp -r)
605
+ // 复制文件(使用 -L 选项跟随符号链接,复制实际文件而非链接本身)
601
606
  console.log(`\n复制文件到: ${targetPath}`);
602
607
  if (process.platform === 'win32') {
603
608
  execSync(`xcopy "${sourcePath}" "${targetPath}" /E /I /H /Y`, { stdio: 'inherit' });
604
609
  } else {
605
- execSync(`cp -r "${sourcePath}" "${targetPath}"`, { stdio: 'inherit' });
610
+ // 使用 -L 选项解引用符号链接,复制实际文件内容
611
+ execSync(`cp -rL "${sourcePath}" "${targetPath}"`, { stdio: 'inherit' });
606
612
  }
607
613
  }
608
614
 
@@ -836,6 +842,109 @@ export async function linkSkill(skillName) {
836
842
  }
837
843
  }
838
844
 
845
+ /**
846
+ * 上传本地技能到全局仓库
847
+ */
848
+ export async function uploadSkill(skillName) {
849
+ // 解析 skill-name@author 格式,提取实际技能名
850
+ let actualSkillName = skillName;
851
+ const match = skillName.match(/^([^@]+)@([^@]+)$/);
852
+ if (match) {
853
+ actualSkillName = match[1];
854
+ }
855
+
856
+ const localSkillsDir = getSkillsDir(false);
857
+ const globalSkillsDir = getSkillsDir(true);
858
+
859
+ const localSkillPath = join(localSkillsDir, actualSkillName);
860
+ const globalSkillPath = join(globalSkillsDir, actualSkillName);
861
+
862
+ // 检查本地是否存在该技能
863
+ if (!existsSync(localSkillPath)) {
864
+ console.log(`\n❌ 本地不存在技能: ${actualSkillName}`);
865
+ console.log(` 位置: ${localSkillPath}\n`);
866
+ console.log(`💡 提示:`);
867
+ console.log(` - 使用 "eskill list" 查看本地已安装的技能`);
868
+ console.log(` - 使用 "eskill install" 先安装技能到本地\n`);
869
+ throw new Error(`本地不存在技能: ${actualSkillName}`);
870
+ }
871
+
872
+ // 检查是否为符号链接
873
+ try {
874
+ const stat = lstatSync(localSkillPath);
875
+ if (stat.isSymbolicLink()) {
876
+ console.log(`\n❌ 无法上传软链接技能: ${actualSkillName}`);
877
+ console.log(` 软链接指向全局仓库,无法上传\n`);
878
+ console.log(`💡 提示:`);
879
+ console.log(` - 软链接技能已经存在于全局仓库`);
880
+ console.log(` - 如需更新全局技能,请直接在全局仓库中使用 eskill update\n`);
881
+ throw new Error(`无法上传软链接技能`);
882
+ }
883
+ } catch (error) {
884
+ console.error(`检测技能类型失败: ${error.message}`);
885
+ throw error;
886
+ }
887
+
888
+ // 确保全局技能目录存在
889
+ if (!existsSync(globalSkillsDir)) {
890
+ mkdirSync(globalSkillsDir, { recursive: true });
891
+ }
892
+
893
+ // 检查全局是否已存在同名技能
894
+ if (existsSync(globalSkillPath)) {
895
+ console.log(`\n⚠️ 全局仓库已存在同名技能: ${actualSkillName}`);
896
+ console.log(` 位置: ${globalSkillPath}\n`);
897
+
898
+ const overwrite = await confirmAction('是否覆盖全局仓库中的技能?');
899
+ if (!overwrite) {
900
+ console.log('\n已取消上传\n');
901
+ return { success: false, cancelled: true };
902
+ }
903
+
904
+ console.log(`删除全局仓库中已存在的技能: ${globalSkillPath}`);
905
+ rmSync(globalSkillPath, { recursive: true, force: true });
906
+ }
907
+
908
+ // 复制技能到全局仓库
909
+ console.log(`\n上传技能到全局仓库: ${actualSkillName}`);
910
+ console.log(` 源: ${localSkillPath}`);
911
+ console.log(` 目标: ${globalSkillPath}\n`);
912
+
913
+ // 使用 -L 选项解引用符号链接
914
+ if (process.platform === 'win32') {
915
+ execSync(`xcopy "${localSkillPath}" "${globalSkillPath}" /E /I /H /Y`, { stdio: 'inherit' });
916
+ } else {
917
+ execSync(`cp -rL "${localSkillPath}" "${globalSkillPath}"`, { stdio: 'inherit' });
918
+ }
919
+
920
+ // 读取本地元数据
921
+ const localMeta = getSkillMeta(actualSkillName, false);
922
+
923
+ // 保存/更新全局元数据
924
+ if (localMeta) {
925
+ saveSkillMeta(actualSkillName, {
926
+ ...localMeta,
927
+ source: 'github', // 上传到全局后,来源标记为 github
928
+ updatedAt: new Date().toISOString()
929
+ }, true);
930
+ } else {
931
+ // 如果本地没有元数据,创建基础元数据
932
+ saveSkillMeta(actualSkillName, {
933
+ name: actualSkillName,
934
+ source: 'github',
935
+ installedAt: new Date().toISOString(),
936
+ updatedAt: new Date().toISOString()
937
+ }, true);
938
+ }
939
+
940
+ console.log(`\n✓ 技能已上传到全局仓库`);
941
+ console.log(` 技能: ${actualSkillName}`);
942
+ console.log(` 全局位置: ${globalSkillPath}`);
943
+ console.log(` 说明: 技现已可在全局仓库中使用\n`);
944
+
945
+ return { success: true, path: globalSkillPath };
946
+ }
947
+
839
948
  /**
840
949
  * 清理所有技能(用于卸载)
841
950
  */
@@ -982,11 +1091,11 @@ export async function updateSkill(skillName, options = {}) {
982
1091
  // 删除旧技能
983
1092
  rmSync(targetPath, { recursive: true, force: true });
984
1093
 
985
- // 复制新技能
1094
+ // 复制新技能(使用 -L 选项解引用符号链接)
986
1095
  if (process.platform === 'win32') {
987
1096
  execSync(`xcopy "${sourcePath}" "${targetPath}" /E /I /H /Y`, { stdio: 'inherit' });
988
1097
  } else {
989
- execSync(`cp -r "${sourcePath}" "${targetPath}"`, { stdio: 'inherit' });
1098
+ execSync(`cp -rL "${sourcePath}" "${targetPath}"`, { stdio: 'inherit' });
990
1099
  }
991
1100
 
992
1101
  // 更新元数据
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eskill",
3
- "version": "1.2.3",
3
+ "version": "1.3.0",
4
4
  "description": "Unified AI Agent Skills Management - Install skills from Git URLs",
5
5
  "main": "index.js",
6
6
  "type": "module",