eskill 1.1.0 → 1.2.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 +110 -10
  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, cleanupAllSkills, cleanupAll, updateSkill, updateAllSkills } from './lib/installer.js';
4
+ import { installFromGitUrl, listSkills, removeSkill, linkSkill, 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.1.0')
16
+ .version('1.2.0')
17
17
  .option('-g, --global', '使用全局技能目录(~/.eskill/skills/),否则使用当前目录(./.claude/skills/)', false);
18
18
 
19
19
  // 安装命令
@@ -105,6 +105,25 @@ program
105
105
  }
106
106
  });
107
107
 
108
+ // 链接命令
109
+ program
110
+ .command('link')
111
+ .description('将全局技能软链接到本地')
112
+ .argument('<name>', '技能名称(不需要 @作者)')
113
+ .action(async (name) => {
114
+ try {
115
+ const result = await linkSkill(name);
116
+
117
+ // 如果用户取消链接,正常退出
118
+ if (result && result.cancelled) {
119
+ process.exit(0);
120
+ }
121
+ } catch (error) {
122
+ console.error(`\n❌ 链接失败: ${error.message}`);
123
+ process.exit(1);
124
+ }
125
+ });
126
+
108
127
  // 更新命令
109
128
  program
110
129
  .command('update')
package/lib/installer.js CHANGED
@@ -250,9 +250,25 @@ async function handleSkillAtAuthorFormat(input, global = false) {
250
250
  const globalSkillPath = join(globalSkillsDir, skillName);
251
251
 
252
252
  if (existsSync(globalSkillPath)) {
253
- // 全局仓库中找到,直接复制
253
+ // 全局仓库中找到,检查本地是否已存在
254
254
  console.log(`✓ 在全局仓库中找到技能: ${skillName}`);
255
- console.log(` 位置: ~/.eskill/skills/${skillName}\n`);
255
+ console.log(` 位置: ~/.eskill/skills/${skillName}`);
256
+
257
+ const localSkillsDir = getSkillsDir(false);
258
+ const localSkillPath = join(localSkillsDir, skillName);
259
+
260
+ if (existsSync(localSkillPath)) {
261
+ console.log(`\n⚠️ 本地已存在同名技能: ${skillName}`);
262
+ console.log(` 位置: ${localSkillPath}\n`);
263
+
264
+ const overwrite = await confirmAction('是否覆盖本地已存在的技能?');
265
+ if (!overwrite) {
266
+ console.log('\n已取消安装\n');
267
+ throw new Error('用户取消安装');
268
+ }
269
+ }
270
+
271
+ console.log();
256
272
 
257
273
  // 返回特殊标记,表示需要从全局复制
258
274
  return `GLOBAL:${skillName}`;
@@ -284,9 +300,25 @@ async function handleSkillAtAuthorFormat(input, global = false) {
284
300
  const globalSkillPath = join(globalSkillsDir, skillName);
285
301
 
286
302
  if (existsSync(globalSkillPath)) {
287
- // 全局仓库中找到,直接复制
303
+ // 全局仓库中找到,检查本地是否已存在
288
304
  console.log(`✓ 在全局仓库中找到技能: ${skillName}`);
289
- console.log(` 位置: ~/.eskill/skills/${skillName}\n`);
305
+ console.log(` 位置: ~/.eskill/skills/${skillName}`);
306
+
307
+ const localSkillsDir = getSkillsDir(false);
308
+ const localSkillPath = join(localSkillsDir, skillName);
309
+
310
+ if (existsSync(localSkillPath)) {
311
+ console.log(`\n⚠️ 本地已存在同名技能: ${skillName}`);
312
+ console.log(` 位置: ${localSkillPath}\n`);
313
+
314
+ const overwrite = await confirmAction('是否覆盖本地已存在的技能?');
315
+ if (!overwrite) {
316
+ console.log('\n已取消安装\n');
317
+ throw new Error('用户取消安装');
318
+ }
319
+ }
320
+
321
+ console.log();
290
322
 
291
323
  // 返回特殊标记,表示需要从全局复制
292
324
  return `GLOBAL:${skillName}`;
@@ -425,13 +457,21 @@ function confirmAction(message) {
425
457
  export async function installFromGitUrl(gitUrl, options = {}) {
426
458
  const { agent = 'claude', link = false, force = false, global = false } = options;
427
459
 
428
- // 检测并处理 name@author 格式
429
- let actualUrl = await handleSkillAtAuthorFormat(gitUrl, global);
460
+ try {
461
+ // 检测并处理 name@author 格式
462
+ let actualUrl = await handleSkillAtAuthorFormat(gitUrl, global);
430
463
 
431
- // 如果是从全局复制
432
- if (actualUrl.startsWith('GLOBAL:')) {
433
- const skillName = actualUrl.replace('GLOBAL:', '');
434
- return await copyFromGlobal(skillName, options);
464
+ // 如果是从全局复制
465
+ if (actualUrl.startsWith('GLOBAL:')) {
466
+ const skillName = actualUrl.replace('GLOBAL:', '');
467
+ return await copyFromGlobal(skillName, options);
468
+ }
469
+ } catch (error) {
470
+ // 用户取消安装
471
+ if (error.message === '用户取消安装') {
472
+ return { success: false, cancelled: true };
473
+ }
474
+ throw error;
435
475
  }
436
476
 
437
477
  // 解析 Git URL
@@ -610,6 +650,66 @@ export function removeSkill(skillName, agent = 'claude', global = false) {
610
650
  console.log(`✓ 已删除技能: ${skillName}`);
611
651
  }
612
652
 
653
+ /**
654
+ * 链接全局技能到本地
655
+ */
656
+ export async function linkSkill(skillName) {
657
+ const globalSkillsDir = getSkillsDir(true);
658
+ const localSkillsDir = getSkillsDir(false);
659
+
660
+ const globalSkillPath = join(globalSkillsDir, skillName);
661
+ const localSkillPath = join(localSkillsDir, skillName);
662
+
663
+ // 检查全局是否存在该技能
664
+ if (!existsSync(globalSkillPath)) {
665
+ console.log(`\n❌ 全局仓库中不存在技能: ${skillName}`);
666
+ console.log(` 位置: ~/.eskill/skills/${skillName}\n`);
667
+ console.log(`💡 提示:`);
668
+ console.log(` - 使用 "eskill install -g ${skillName}" 先安装到全局仓库`);
669
+ console.log(` - 或使用 "eskill install" 直接安装到本地\n`);
670
+ throw new Error(`全局仓库中不存在技能: ${skillName}`);
671
+ }
672
+
673
+ // 确保本地技能目录存在
674
+ if (!existsSync(localSkillsDir)) {
675
+ mkdirSync(localSkillsDir, { recursive: true });
676
+ }
677
+
678
+ // 检查本地是否已存在同名技能
679
+ if (existsSync(localSkillPath)) {
680
+ console.log(`\n⚠️ 本地已存在同名技能: ${skillName}`);
681
+ console.log(` 位置: ${localSkillPath}\n`);
682
+
683
+ const overwrite = await confirmAction('是否删除本地技能并创建软链接?');
684
+ if (!overwrite) {
685
+ console.log('\n已取消链接\n');
686
+ return { success: false, cancelled: true };
687
+ }
688
+
689
+ console.log(`删除本地已存在的技能: ${localSkillPath}`);
690
+ rmSync(localSkillPath, { recursive: true, force: true });
691
+ }
692
+
693
+ // 创建软链接
694
+ console.log(`\n创建软链接:`);
695
+ console.log(` 源: ${globalSkillPath}`);
696
+ console.log(` 目标: ${localSkillPath}\n`);
697
+
698
+ try {
699
+ symlinkSync(globalSkillPath, localSkillPath, 'dir');
700
+
701
+ console.log(`✓ 软链接创建成功`);
702
+ console.log(` 技能: ${skillName}`);
703
+ console.log(` 本地路径: ${localSkillPath}`);
704
+ console.log(` 全局路径: ${globalSkillPath}`);
705
+ console.log(` 说明: 本地技能指向全局仓库,全局更新会自动同步\n`);
706
+
707
+ return { success: true, path: localSkillPath };
708
+ } catch (error) {
709
+ throw new Error(`创建软链接失败: ${error.message}`);
710
+ }
711
+ }
712
+
613
713
  /**
614
714
  * 清理所有技能(用于卸载)
615
715
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eskill",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "description": "Unified AI Agent Skills Management - Install skills from Git URLs",
5
5
  "main": "index.js",
6
6
  "type": "module",