eskill 1.0.31 → 1.1.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.
- package/cli.js +1 -1
- package/lib/installer.js +127 -8
- 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
|
|
16
|
+
.version('1.1.0')
|
|
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
|
|
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
|
-
|
|
240
|
-
|
|
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,53 @@ 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
|
+
console.log(`\n⚠️ 本地已存在技能: ${skillName}`);
|
|
373
|
+
console.log(` 位置: ${targetPath}\n`);
|
|
374
|
+
|
|
375
|
+
const overwrite = await confirmAction('是否强制覆盖已存在的技能?');
|
|
376
|
+
if (!overwrite) {
|
|
377
|
+
console.log('\n已取消安装\n');
|
|
378
|
+
return { success: false, cancelled: true };
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
console.log(`删除本地已存在的技能: ${targetPath}`);
|
|
382
|
+
rmSync(targetPath, { recursive: true, force: true });
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// 复制技能
|
|
386
|
+
console.log(`从全局仓库复制技能: ${skillName}`);
|
|
387
|
+
console.log(` 源: ${sourcePath}`);
|
|
388
|
+
console.log(` 目标: ${targetPath}\n`);
|
|
389
|
+
|
|
390
|
+
execSync(`cp -r "${sourcePath}" "${targetPath}"`, { stdio: 'inherit' });
|
|
391
|
+
|
|
392
|
+
// 复制元数据
|
|
393
|
+
const globalMeta = getSkillMeta(skillName, true);
|
|
394
|
+
if (globalMeta) {
|
|
395
|
+
saveSkillMeta(skillName, globalMeta, false);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
console.log(`\n✓ 技能已从全局仓库复制到本地`);
|
|
399
|
+
console.log(` 位置: ${targetPath}`);
|
|
400
|
+
console.log(` 说明: 本地技能与全局仓库保持独立\n`);
|
|
401
|
+
|
|
402
|
+
return { success: true, path: targetPath, fromGlobal: true };
|
|
403
|
+
}
|
|
404
|
+
|
|
299
405
|
/**
|
|
300
406
|
* 等待用户确认
|
|
301
407
|
*/
|
|
@@ -320,7 +426,13 @@ export async function installFromGitUrl(gitUrl, options = {}) {
|
|
|
320
426
|
const { agent = 'claude', link = false, force = false, global = false } = options;
|
|
321
427
|
|
|
322
428
|
// 检测并处理 name@author 格式
|
|
323
|
-
let actualUrl = await handleSkillAtAuthorFormat(gitUrl);
|
|
429
|
+
let actualUrl = await handleSkillAtAuthorFormat(gitUrl, global);
|
|
430
|
+
|
|
431
|
+
// 如果是从全局复制
|
|
432
|
+
if (actualUrl.startsWith('GLOBAL:')) {
|
|
433
|
+
const skillName = actualUrl.replace('GLOBAL:', '');
|
|
434
|
+
return await copyFromGlobal(skillName, options);
|
|
435
|
+
}
|
|
324
436
|
|
|
325
437
|
// 解析 Git URL
|
|
326
438
|
const parsed = parseGitUrl(actualUrl);
|
|
@@ -357,10 +469,17 @@ export async function installFromGitUrl(gitUrl, options = {}) {
|
|
|
357
469
|
mkdirSync(skillsDir, { recursive: true });
|
|
358
470
|
}
|
|
359
471
|
|
|
360
|
-
//
|
|
472
|
+
// 检查是否已存在同名技能
|
|
361
473
|
if (existsSync(targetPath)) {
|
|
362
474
|
if (!force) {
|
|
363
|
-
|
|
475
|
+
console.log(`\n⚠️ 技能已存在: ${skillName}`);
|
|
476
|
+
console.log(` 位置: ${targetPath}\n`);
|
|
477
|
+
|
|
478
|
+
const overwrite = await confirmAction('是否强制覆盖已存在的技能?');
|
|
479
|
+
if (!overwrite) {
|
|
480
|
+
console.log('\n已取消安装\n');
|
|
481
|
+
return { success: false, cancelled: true };
|
|
482
|
+
}
|
|
364
483
|
}
|
|
365
484
|
console.log(`删除已存在的技能: ${targetPath}`);
|
|
366
485
|
rmSync(targetPath, { recursive: true, force: true });
|