eskill 1.0.33 → 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 +128 -14
  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.0.33')
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}`;
@@ -366,10 +398,17 @@ async function copyFromGlobal(skillName, options = {}) {
366
398
  const sourcePath = join(globalSkillsDir, skillName);
367
399
  const targetPath = join(localSkillsDir, skillName);
368
400
 
369
- // 检查本地是否已存在
401
+ // 检查本地是否已存在同名技能
370
402
  if (existsSync(targetPath)) {
371
403
  if (!force) {
372
- throw new Error(`本地已存在技能: ${skillName}\n使用 --force 选项强制覆盖`);
404
+ console.log(`\n⚠️ 本地已存在技能: ${skillName}`);
405
+ console.log(` 位置: ${targetPath}\n`);
406
+
407
+ const overwrite = await confirmAction('是否强制覆盖已存在的技能?');
408
+ if (!overwrite) {
409
+ console.log('\n已取消安装\n');
410
+ return { success: false, cancelled: true };
411
+ }
373
412
  }
374
413
  console.log(`删除本地已存在的技能: ${targetPath}`);
375
414
  rmSync(targetPath, { recursive: true, force: true });
@@ -418,13 +457,21 @@ function confirmAction(message) {
418
457
  export async function installFromGitUrl(gitUrl, options = {}) {
419
458
  const { agent = 'claude', link = false, force = false, global = false } = options;
420
459
 
421
- // 检测并处理 name@author 格式
422
- let actualUrl = await handleSkillAtAuthorFormat(gitUrl, global);
460
+ try {
461
+ // 检测并处理 name@author 格式
462
+ let actualUrl = await handleSkillAtAuthorFormat(gitUrl, global);
423
463
 
424
- // 如果是从全局复制
425
- if (actualUrl.startsWith('GLOBAL:')) {
426
- const skillName = actualUrl.replace('GLOBAL:', '');
427
- 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;
428
475
  }
429
476
 
430
477
  // 解析 Git URL
@@ -462,10 +509,17 @@ export async function installFromGitUrl(gitUrl, options = {}) {
462
509
  mkdirSync(skillsDir, { recursive: true });
463
510
  }
464
511
 
465
- // 检查是否已存在
512
+ // 检查是否已存在同名技能
466
513
  if (existsSync(targetPath)) {
467
514
  if (!force) {
468
- throw new Error(`技能已存在: ${skillName}\n使用 --force 选项强制覆盖`);
515
+ console.log(`\n⚠️ 技能已存在: ${skillName}`);
516
+ console.log(` 位置: ${targetPath}\n`);
517
+
518
+ const overwrite = await confirmAction('是否强制覆盖已存在的技能?');
519
+ if (!overwrite) {
520
+ console.log('\n已取消安装\n');
521
+ return { success: false, cancelled: true };
522
+ }
469
523
  }
470
524
  console.log(`删除已存在的技能: ${targetPath}`);
471
525
  rmSync(targetPath, { recursive: true, force: true });
@@ -596,6 +650,66 @@ export function removeSkill(skillName, agent = 'claude', global = false) {
596
650
  console.log(`✓ 已删除技能: ${skillName}`);
597
651
  }
598
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
+
599
713
  /**
600
714
  * 清理所有技能(用于卸载)
601
715
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eskill",
3
- "version": "1.0.33",
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",