eskill 1.0.30 → 1.0.33

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 +1 -1
  2. package/lib/installer.js +131 -31
  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.0.30')
16
+ .version('1.0.33')
17
17
  .option('-g, --global', '使用全局技能目录(~/.eskill/skills/),否则使用当前目录(./.claude/skills/)', false);
18
18
 
19
19
  // 安装命令
package/lib/installer.js CHANGED
@@ -223,10 +223,48 @@ function removeSkillMeta(skillName, global = false) {
223
223
  }
224
224
 
225
225
  /**
226
- * 检测并处理 name@author 格式
227
- * 返回解析后的 GitHub URL,如果不是该格式则返回原 URL
226
+ * 检测并处理 name@author 格式或 GitHub URL
227
+ * 返回解析后的 GitHub URL 或特殊标记
228
228
  */
229
- async function handleSkillAtAuthorFormat(input) {
229
+ async function handleSkillAtAuthorFormat(input, global = false) {
230
+ // 检测是否为 GitHub URL
231
+ const isGitHubUrl = /^(https?:\/\/)?(github\.com|gitlab\.com)/i.test(input);
232
+
233
+ if (isGitHubUrl) {
234
+ // 是 GitHub URL,本地模式下先检查全局仓库
235
+ if (!global) {
236
+ // 解析 URL 获取技能名
237
+ let skillName = '';
238
+ try {
239
+ const parsed = parseGitUrl(input);
240
+ skillName = parsed.path ? basename(parsed.path) : parsed.repo;
241
+ } catch (error) {
242
+ // 解析失败,直接返回原 URL
243
+ return input;
244
+ }
245
+
246
+ console.log(`\n检测到 GitHub URL: ${input}`);
247
+ console.log(`正在全局仓库中查找 "${skillName}"...\n`);
248
+
249
+ const globalSkillsDir = getSkillsDir(true);
250
+ const globalSkillPath = join(globalSkillsDir, skillName);
251
+
252
+ if (existsSync(globalSkillPath)) {
253
+ // 全局仓库中找到,直接复制
254
+ console.log(`✓ 在全局仓库中找到技能: ${skillName}`);
255
+ console.log(` 位置: ~/.eskill/skills/${skillName}\n`);
256
+
257
+ // 返回特殊标记,表示需要从全局复制
258
+ return `GLOBAL:${skillName}`;
259
+ }
260
+
261
+ console.log(`⊘ 全局仓库中未找到,从 GitHub 下载...\n`);
262
+ }
263
+
264
+ // 直接返回原 URL
265
+ return input;
266
+ }
267
+
230
268
  // 检测是否为 name@author 格式
231
269
  const match = input.match(/^([^@]+)@([^@]+)$/);
232
270
 
@@ -236,8 +274,29 @@ async function handleSkillAtAuthorFormat(input) {
236
274
  }
237
275
 
238
276
  const [, skillName, author] = match;
239
- console.log(`\n检测到技能名称格式: ${skillName}@${author}`);
240
- console.log(`正在搜索 "${skillName}" 和作者 "${author}"...\n`);
277
+
278
+ // 本地模式:先检查全局仓库
279
+ if (!global) {
280
+ console.log(`\n检测到技能名称格式: ${skillName}@${author}`);
281
+ console.log(`正在全局仓库中查找 "${skillName}"...\n`);
282
+
283
+ const globalSkillsDir = getSkillsDir(true);
284
+ const globalSkillPath = join(globalSkillsDir, skillName);
285
+
286
+ if (existsSync(globalSkillPath)) {
287
+ // 全局仓库中找到,直接复制
288
+ console.log(`✓ 在全局仓库中找到技能: ${skillName}`);
289
+ console.log(` 位置: ~/.eskill/skills/${skillName}\n`);
290
+
291
+ // 返回特殊标记,表示需要从全局复制
292
+ return `GLOBAL:${skillName}`;
293
+ }
294
+
295
+ console.log(`⊘ 全局仓库中未找到,正在网络搜索...\n`);
296
+ } else {
297
+ console.log(`\n检测到技能名称格式: ${skillName}@${author}`);
298
+ console.log(`正在搜索 "${skillName}" 和作者 "${author}"...\n`);
299
+ }
241
300
 
242
301
  try {
243
302
  // 搜索技能
@@ -296,6 +355,46 @@ async function handleSkillAtAuthorFormat(input) {
296
355
  }
297
356
  }
298
357
 
358
+ /**
359
+ * 从全局仓库复制技能到本地
360
+ */
361
+ async function copyFromGlobal(skillName, options = {}) {
362
+ const { force = false } = options;
363
+ const globalSkillsDir = getSkillsDir(true);
364
+ const localSkillsDir = getSkillsDir(false);
365
+
366
+ const sourcePath = join(globalSkillsDir, skillName);
367
+ const targetPath = join(localSkillsDir, skillName);
368
+
369
+ // 检查本地是否已存在
370
+ if (existsSync(targetPath)) {
371
+ if (!force) {
372
+ throw new Error(`本地已存在技能: ${skillName}\n使用 --force 选项强制覆盖`);
373
+ }
374
+ console.log(`删除本地已存在的技能: ${targetPath}`);
375
+ rmSync(targetPath, { recursive: true, force: true });
376
+ }
377
+
378
+ // 复制技能
379
+ console.log(`从全局仓库复制技能: ${skillName}`);
380
+ console.log(` 源: ${sourcePath}`);
381
+ console.log(` 目标: ${targetPath}\n`);
382
+
383
+ execSync(`cp -r "${sourcePath}" "${targetPath}"`, { stdio: 'inherit' });
384
+
385
+ // 复制元数据
386
+ const globalMeta = getSkillMeta(skillName, true);
387
+ if (globalMeta) {
388
+ saveSkillMeta(skillName, globalMeta, false);
389
+ }
390
+
391
+ console.log(`\n✓ 技能已从全局仓库复制到本地`);
392
+ console.log(` 位置: ${targetPath}`);
393
+ console.log(` 说明: 本地技能与全局仓库保持独立\n`);
394
+
395
+ return { success: true, path: targetPath, fromGlobal: true };
396
+ }
397
+
299
398
  /**
300
399
  * 等待用户确认
301
400
  */
@@ -319,30 +418,38 @@ function confirmAction(message) {
319
418
  export async function installFromGitUrl(gitUrl, options = {}) {
320
419
  const { agent = 'claude', link = false, force = false, global = false } = options;
321
420
 
322
- // 本地模式:询问是否在当前目录安装
323
- if (!global) {
324
- const currentDir = process.cwd();
325
- const confirmed = await confirmAction(`是否在【${currentDir}】下安装此技能?`);
326
- if (!confirmed) {
327
- console.log('\n已取消安装\n');
328
- return { success: false, cancelled: true };
329
- }
330
- }
331
-
332
421
  // 检测并处理 name@author 格式
333
- let actualUrl = await handleSkillAtAuthorFormat(gitUrl);
334
-
335
- // 如果 URL 被转换了(即原来是 name@author 格式),需要确认
336
- if (actualUrl !== gitUrl) {
337
- const confirmed = await confirmAction('是否安装此技能?');
338
- if (!confirmed) {
339
- console.log('\n已取消安装');
340
- return { success: false, cancelled: true };
341
- }
422
+ let actualUrl = await handleSkillAtAuthorFormat(gitUrl, global);
423
+
424
+ // 如果是从全局复制
425
+ if (actualUrl.startsWith('GLOBAL:')) {
426
+ const skillName = actualUrl.replace('GLOBAL:', '');
427
+ return await copyFromGlobal(skillName, options);
342
428
  }
343
429
 
344
430
  // 解析 Git URL
345
431
  const parsed = parseGitUrl(actualUrl);
432
+ const skillsDir = getSkillsDir(global);
433
+
434
+ // 确定技能名称
435
+ const skillName = parsed.path ? basename(parsed.path) : parsed.repo;
436
+ const targetPath = join(skillsDir, skillName);
437
+
438
+ // 确认安装(带路径信息)
439
+ let confirmMessage = '';
440
+ if (!global) {
441
+ // 本地模式:显示当前目录
442
+ confirmMessage = `是否在【${process.cwd()}】下安装此技能?`;
443
+ } else {
444
+ // 全局模式:显示 skill 仓库
445
+ confirmMessage = `是否在【skill 仓库】中安装此技能?`;
446
+ }
447
+
448
+ const confirmed = await confirmAction(confirmMessage);
449
+ if (!confirmed) {
450
+ console.log('\n已取消安装\n');
451
+ return { success: false, cancelled: true };
452
+ }
346
453
  console.log(`平台: ${parsed.platform}`);
347
454
  console.log(`仓库: ${parsed.owner}/${parsed.repo}`);
348
455
  console.log(`分支: ${parsed.branch}`);
@@ -350,18 +457,11 @@ export async function installFromGitUrl(gitUrl, options = {}) {
350
457
  console.log(`路径: ${parsed.path}`);
351
458
  }
352
459
 
353
- // 获取技能目录(技能文件存放位置)
354
- const skillsDir = getSkillsDir(global);
355
-
356
460
  // 确保技能目录存在
357
461
  if (!existsSync(skillsDir)) {
358
462
  mkdirSync(skillsDir, { recursive: true });
359
463
  }
360
464
 
361
- // 确定技能名称
362
- const skillName = parsed.path ? basename(parsed.path) : parsed.repo;
363
- const targetPath = join(skillsDir, skillName);
364
-
365
465
  // 检查是否已存在
366
466
  if (existsSync(targetPath)) {
367
467
  if (!force) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eskill",
3
- "version": "1.0.30",
3
+ "version": "1.0.33",
4
4
  "description": "Unified AI Agent Skills Management - Install skills from Git URLs",
5
5
  "main": "index.js",
6
6
  "type": "module",