eskill 1.0.23 → 1.0.27
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 +48 -7
- package/lib/installer.js +102 -73
- 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.0.27');
|
|
17
17
|
|
|
18
18
|
// 安装命令
|
|
19
19
|
program
|
|
@@ -95,11 +95,15 @@ program
|
|
|
95
95
|
program
|
|
96
96
|
.command('update')
|
|
97
97
|
.description('更新技能')
|
|
98
|
-
.argument('[name]', '
|
|
98
|
+
.argument('[name]', '技能名称(不指定或 "all" 则更新所有)')
|
|
99
99
|
.option('-f, --force', '强制更新(即使版本相同)', false)
|
|
100
100
|
.action(async (name, options) => {
|
|
101
101
|
try {
|
|
102
|
-
|
|
102
|
+
// 支持显式使用 "all" 参数
|
|
103
|
+
if (name === 'all' || name === 'All' || name === 'ALL') {
|
|
104
|
+
// 更新所有技能
|
|
105
|
+
await updateAllSkills(options);
|
|
106
|
+
} else if (name) {
|
|
103
107
|
// 更新单个技能
|
|
104
108
|
console.log(`\n检查技能: ${name}`);
|
|
105
109
|
const result = await updateSkill(name, options);
|
|
@@ -114,7 +118,7 @@ program
|
|
|
114
118
|
console.log(` 新版本: ${result.newVersion}\n`);
|
|
115
119
|
}
|
|
116
120
|
} else {
|
|
117
|
-
//
|
|
121
|
+
// 更新所有技能(不指定参数时)
|
|
118
122
|
await updateAllSkills(options);
|
|
119
123
|
}
|
|
120
124
|
} catch (error) {
|
|
@@ -160,9 +164,9 @@ program
|
|
|
160
164
|
}
|
|
161
165
|
|
|
162
166
|
// 从 API 返回的数据结构中提取技能和分页信息
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
167
|
+
let skills = result.data?.skills || result.data || result.skills || result.results || [];
|
|
168
|
+
let pagination = result.data?.pagination || result.pagination || {};
|
|
169
|
+
let total = pagination.total || result.total || skills.length;
|
|
166
170
|
|
|
167
171
|
// 确保 skills 是数组
|
|
168
172
|
if (!Array.isArray(skills)) {
|
|
@@ -171,6 +175,43 @@ program
|
|
|
171
175
|
process.exit(1);
|
|
172
176
|
}
|
|
173
177
|
|
|
178
|
+
// 智能搜索:如果没有结果且是 name@author 格式
|
|
179
|
+
if (skills.length === 0) {
|
|
180
|
+
const match = query.match(/^([^@]+)@([^@]+)$/);
|
|
181
|
+
if (match) {
|
|
182
|
+
const [, skillName, author] = match;
|
|
183
|
+
console.log(`\n💡 未找到 "${query}",尝试按技能名 "${skillName}" 和作者 "${author}" 搜索...\n`);
|
|
184
|
+
|
|
185
|
+
// 重新搜索技能名
|
|
186
|
+
const newResult = await searchSkills(skillName, {
|
|
187
|
+
page: 1,
|
|
188
|
+
limit: 100,
|
|
189
|
+
sortBy: options.sort,
|
|
190
|
+
ai: options.ai
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
const allSkills = newResult.data?.skills || newResult.data || newResult.skills || newResult.results || [];
|
|
194
|
+
|
|
195
|
+
// 筛选匹配作者的技能(不区分大小写)
|
|
196
|
+
skills = allSkills.filter(skill => {
|
|
197
|
+
const skillAuthor = (skill.author || skill.owner || '').toLowerCase();
|
|
198
|
+
return skillAuthor === author.toLowerCase();
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
total = skills.length;
|
|
202
|
+
|
|
203
|
+
if (skills.length > 0) {
|
|
204
|
+
console.log(`✓ 找到 ${total} 个匹配的技能\n`);
|
|
205
|
+
} else {
|
|
206
|
+
console.log(`❌ 仍然未找到匹配的技能\n`);
|
|
207
|
+
console.log('提示:');
|
|
208
|
+
console.log(` - 尝试只搜索技能名: eskill search ${skillName}`);
|
|
209
|
+
console.log(` - 尝试只搜索作者: eskill search ${author}\n`);
|
|
210
|
+
process.exit(0);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
174
215
|
console.log(formatSkillList(skills));
|
|
175
216
|
|
|
176
217
|
// 显示分页信息和提示
|
package/lib/installer.js
CHANGED
|
@@ -200,7 +200,7 @@ async function handleSkillAtAuthorFormat(input) {
|
|
|
200
200
|
|
|
201
201
|
const [, skillName, author] = match;
|
|
202
202
|
console.log(`\n检测到技能名称格式: ${skillName}@${author}`);
|
|
203
|
-
console.log(`正在搜索 "${skillName}"...\n`);
|
|
203
|
+
console.log(`正在搜索 "${skillName}" 和作者 "${author}"...\n`);
|
|
204
204
|
|
|
205
205
|
try {
|
|
206
206
|
// 搜索技能
|
|
@@ -212,17 +212,28 @@ async function handleSkillAtAuthorFormat(input) {
|
|
|
212
212
|
|
|
213
213
|
const skills = result.data?.skills || result.data || result.skills || result.results || [];
|
|
214
214
|
|
|
215
|
-
//
|
|
215
|
+
// 筛选出匹配作者的技能(不区分大小写)
|
|
216
216
|
const matchedSkills = skills.filter(skill => {
|
|
217
|
-
const skillAuthor = skill.author || skill.owner || '';
|
|
218
|
-
return skillAuthor
|
|
217
|
+
const skillAuthor = (skill.author || skill.owner || '').toLowerCase();
|
|
218
|
+
return skillAuthor === author.toLowerCase();
|
|
219
219
|
});
|
|
220
220
|
|
|
221
221
|
if (matchedSkills.length === 0) {
|
|
222
222
|
// 没有找到匹配的作者
|
|
223
223
|
console.log(`❌ 未找到作者 "${author}" 的技能 "${skillName}"\n`);
|
|
224
|
-
|
|
225
|
-
|
|
224
|
+
|
|
225
|
+
// 显示找到的同名技能(如果有)
|
|
226
|
+
if (skills.length > 0) {
|
|
227
|
+
console.log(`找到 ${skills.length} 个名为 "${skillName}" 的技能,但作者不匹配:\n`);
|
|
228
|
+
console.log(formatSkillList(skills));
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// 给出建议
|
|
232
|
+
console.log(`💡 建议:`);
|
|
233
|
+
console.log(` - 尝试搜索其他作者: eskill search ${skillName}`);
|
|
234
|
+
console.log(` - 尝试搜索该作者的其他技能: eskill search ${author}`);
|
|
235
|
+
console.log(` - 或直接使用 GitHub URL 安装\n`);
|
|
236
|
+
|
|
226
237
|
throw new Error(`未找到匹配的技能`);
|
|
227
238
|
}
|
|
228
239
|
|
|
@@ -234,7 +245,7 @@ async function handleSkillAtAuthorFormat(input) {
|
|
|
234
245
|
throw new Error('技能信息中没有 GitHub URL');
|
|
235
246
|
}
|
|
236
247
|
|
|
237
|
-
console.log(
|
|
248
|
+
console.log(`✓ 找到匹配的技能:`);
|
|
238
249
|
console.log(` 名称: ${skillName}@${author}`);
|
|
239
250
|
console.log(` Stars: ⭐ ${matchedSkill.stars || 'N/A'}`);
|
|
240
251
|
console.log(` GitHub: ${githubUrl}\n`);
|
|
@@ -484,6 +495,23 @@ export function getSkillsDir() {
|
|
|
484
495
|
return getSkillsStorageDir();
|
|
485
496
|
}
|
|
486
497
|
|
|
498
|
+
/**
|
|
499
|
+
* 获取远程仓库的 commit hash(不克隆)
|
|
500
|
+
*/
|
|
501
|
+
function getRemoteCommitHash(gitUrl, branch) {
|
|
502
|
+
try {
|
|
503
|
+
const result = execSync(
|
|
504
|
+
`git ls-remote "${gitUrl}" refs/heads/${branch}`,
|
|
505
|
+
{ encoding: 'utf-8' }
|
|
506
|
+
);
|
|
507
|
+
// 输出格式: "commitHash\trefs/heads/branch"
|
|
508
|
+
const match = result.trim().split('\t');
|
|
509
|
+
return match[0] || null;
|
|
510
|
+
} catch (error) {
|
|
511
|
+
return null;
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
|
|
487
515
|
/**
|
|
488
516
|
* 更新单个技能
|
|
489
517
|
*/
|
|
@@ -507,83 +535,84 @@ export async function updateSkill(skillName, options = {}) {
|
|
|
507
535
|
|
|
508
536
|
const currentVersion = meta.version || meta.commitHash?.substring(0, 7) || 'unknown';
|
|
509
537
|
|
|
510
|
-
//
|
|
511
|
-
|
|
512
|
-
const parsed = parseGitUrl(meta.gitUrl);
|
|
538
|
+
// 解析 Git URL
|
|
539
|
+
const parsed = parseGitUrl(meta.gitUrl);
|
|
513
540
|
|
|
514
|
-
|
|
515
|
-
|
|
541
|
+
// 先检查远程 commit hash(不下载)
|
|
542
|
+
console.log(` 检查远程版本...`);
|
|
543
|
+
const remoteCommitHash = getRemoteCommitHash(parsed.cloneUrl, parsed.branch);
|
|
516
544
|
|
|
517
|
-
|
|
518
|
-
|
|
545
|
+
if (!remoteCommitHash) {
|
|
546
|
+
throw new Error('无法获取远程版本信息');
|
|
547
|
+
}
|
|
519
548
|
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
`git clone --depth 1 --branch ${parsed.branch} --single-branch "${cloneUrl}" "${tempDir}"`,
|
|
525
|
-
{ stdio: 'inherit' }
|
|
526
|
-
);
|
|
527
|
-
|
|
528
|
-
// 确定源路径
|
|
529
|
-
const sourcePath = parsed.path ? join(tempDir, parsed.path) : tempDir;
|
|
530
|
-
|
|
531
|
-
// 验证源路径存在
|
|
532
|
-
if (!existsSync(sourcePath)) {
|
|
533
|
-
throw new Error(`路径不存在: ${sourcePath}`);
|
|
534
|
-
}
|
|
549
|
+
// 检查是否有更新
|
|
550
|
+
if (!force && remoteCommitHash === meta.commitHash) {
|
|
551
|
+
return { success: true, skill: skillName, updated: false, currentVersion };
|
|
552
|
+
}
|
|
535
553
|
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
if (!existsSync(skillManifestPath)) {
|
|
539
|
-
throw new Error(`无效的技能包:未找到 SKILL.md 文件`);
|
|
540
|
-
}
|
|
554
|
+
// 有更新或强制更新,才下载代码
|
|
555
|
+
console.log(` 发现更新,正在下载...`);
|
|
541
556
|
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
const newVersion = getGitVersion(sourcePath);
|
|
545
|
-
const newVersionDisplay = newVersion || newCommitHash?.substring(0, 7) || 'unknown';
|
|
557
|
+
// 创建临时目录
|
|
558
|
+
const tempDir = join(tmpdir(), `eskill-update-${Date.now()}`);
|
|
546
559
|
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
560
|
+
try {
|
|
561
|
+
// 克隆仓库(静默模式)
|
|
562
|
+
execSync(
|
|
563
|
+
`git clone --depth 1 --branch ${parsed.branch} --single-branch "${parsed.cloneUrl}" "${tempDir}"`,
|
|
564
|
+
{ stdio: 'inherit' }
|
|
565
|
+
);
|
|
553
566
|
|
|
554
|
-
|
|
555
|
-
|
|
567
|
+
// 确定源路径
|
|
568
|
+
const sourcePath = parsed.path ? join(tempDir, parsed.path) : tempDir;
|
|
556
569
|
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
execSync(`cp -r "${sourcePath}" "${targetPath}"`, { stdio: 'inherit' });
|
|
562
|
-
}
|
|
570
|
+
// 验证源路径存在
|
|
571
|
+
if (!existsSync(sourcePath)) {
|
|
572
|
+
throw new Error(`路径不存在: ${sourcePath}`);
|
|
573
|
+
}
|
|
563
574
|
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
updatedAt: new Date().toISOString()
|
|
570
|
-
});
|
|
575
|
+
// 检查 SKILL.md 是否存在
|
|
576
|
+
const skillManifestPath = join(sourcePath, 'SKILL.md');
|
|
577
|
+
if (!existsSync(skillManifestPath)) {
|
|
578
|
+
throw new Error(`无效的技能包:未找到 SKILL.md 文件`);
|
|
579
|
+
}
|
|
571
580
|
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
581
|
+
// 获取新版本信息
|
|
582
|
+
const newCommitHash = getGitCommitHash(sourcePath);
|
|
583
|
+
const newVersion = getGitVersion(sourcePath);
|
|
584
|
+
const newVersionDisplay = newVersion || newCommitHash?.substring(0, 7) || 'unknown';
|
|
585
|
+
|
|
586
|
+
// 删除旧技能
|
|
587
|
+
rmSync(targetPath, { recursive: true, force: true });
|
|
588
|
+
|
|
589
|
+
// 复制新技能
|
|
590
|
+
if (process.platform === 'win32') {
|
|
591
|
+
execSync(`xcopy "${sourcePath}" "${targetPath}" /E /I /H /Y`, { stdio: 'inherit' });
|
|
592
|
+
} else {
|
|
593
|
+
execSync(`cp -r "${sourcePath}" "${targetPath}"`, { stdio: 'inherit' });
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
// 更新元数据
|
|
597
|
+
saveSkillMeta(skillName, {
|
|
598
|
+
...meta,
|
|
599
|
+
commitHash: newCommitHash,
|
|
600
|
+
version: newVersion,
|
|
601
|
+
updatedAt: new Date().toISOString()
|
|
602
|
+
});
|
|
603
|
+
|
|
604
|
+
return {
|
|
605
|
+
success: true,
|
|
606
|
+
skill: skillName,
|
|
607
|
+
updated: true,
|
|
608
|
+
currentVersion,
|
|
609
|
+
newVersion: newVersionDisplay
|
|
610
|
+
};
|
|
611
|
+
} finally {
|
|
612
|
+
// 清理临时目录
|
|
613
|
+
if (existsSync(tempDir)) {
|
|
614
|
+
rmSync(tempDir, { recursive: true, force: true });
|
|
584
615
|
}
|
|
585
|
-
} catch (error) {
|
|
586
|
-
throw new Error(`更新失败: ${error.message}`);
|
|
587
616
|
}
|
|
588
617
|
}
|
|
589
618
|
|