eskill 1.0.21 → 1.0.25
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 +81 -6
- package/lib/installer.js +334 -34
- 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 } from './lib/installer.js';
|
|
4
|
+
import { installFromGitUrl, listSkills, removeSkill, 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.
|
|
16
|
+
.version('1.0.25');
|
|
17
17
|
|
|
18
18
|
// 安装命令
|
|
19
19
|
program
|
|
@@ -59,7 +59,13 @@ program
|
|
|
59
59
|
console.log(`\n已安装的技能:\n`);
|
|
60
60
|
skills.forEach(skill => {
|
|
61
61
|
const displayName = skill.author ? `${skill.name}@${skill.author}` : skill.name;
|
|
62
|
-
|
|
62
|
+
const versionInfo = skill.version || (skill.commitHash ? `#${skill.commitHash.substring(0, 7)}` : '');
|
|
63
|
+
const localMark = !skill.gitUrl ? ' [本地]' : '';
|
|
64
|
+
|
|
65
|
+
console.log(` • ${displayName}${localMark}`);
|
|
66
|
+
if (versionInfo) {
|
|
67
|
+
console.log(` 版本: ${versionInfo}`);
|
|
68
|
+
}
|
|
63
69
|
});
|
|
64
70
|
console.log(`\n总计: ${skills.length} 个技能\n`);
|
|
65
71
|
} catch (error) {
|
|
@@ -85,6 +91,38 @@ program
|
|
|
85
91
|
}
|
|
86
92
|
});
|
|
87
93
|
|
|
94
|
+
// 更新命令
|
|
95
|
+
program
|
|
96
|
+
.command('update')
|
|
97
|
+
.description('更新技能')
|
|
98
|
+
.argument('[name]', '技能名称(不指定则更新所有)')
|
|
99
|
+
.option('-f, --force', '强制更新(即使版本相同)', false)
|
|
100
|
+
.action(async (name, options) => {
|
|
101
|
+
try {
|
|
102
|
+
if (name) {
|
|
103
|
+
// 更新单个技能
|
|
104
|
+
console.log(`\n检查技能: ${name}`);
|
|
105
|
+
const result = await updateSkill(name, options);
|
|
106
|
+
|
|
107
|
+
if (result.local) {
|
|
108
|
+
console.log('\n⚠️ 本地技能无法自动更新,请手动更新\n');
|
|
109
|
+
} else if (result.updated === false) {
|
|
110
|
+
console.log(`\n✓ 已是最新版本 (${result.currentVersion})\n`);
|
|
111
|
+
} else if (result.updated) {
|
|
112
|
+
console.log(`\n✓ 更新成功:`);
|
|
113
|
+
console.log(` 旧版本: ${result.currentVersion}`);
|
|
114
|
+
console.log(` 新版本: ${result.newVersion}\n`);
|
|
115
|
+
}
|
|
116
|
+
} else {
|
|
117
|
+
// 更新所有技能
|
|
118
|
+
await updateAllSkills(options);
|
|
119
|
+
}
|
|
120
|
+
} catch (error) {
|
|
121
|
+
console.error(`\n❌ 更新失败: ${error.message}\n`);
|
|
122
|
+
process.exit(1);
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
|
|
88
126
|
// 搜索命令
|
|
89
127
|
program
|
|
90
128
|
.command('search')
|
|
@@ -122,9 +160,9 @@ program
|
|
|
122
160
|
}
|
|
123
161
|
|
|
124
162
|
// 从 API 返回的数据结构中提取技能和分页信息
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
163
|
+
let skills = result.data?.skills || result.data || result.skills || result.results || [];
|
|
164
|
+
let pagination = result.data?.pagination || result.pagination || {};
|
|
165
|
+
let total = pagination.total || result.total || skills.length;
|
|
128
166
|
|
|
129
167
|
// 确保 skills 是数组
|
|
130
168
|
if (!Array.isArray(skills)) {
|
|
@@ -133,6 +171,43 @@ program
|
|
|
133
171
|
process.exit(1);
|
|
134
172
|
}
|
|
135
173
|
|
|
174
|
+
// 智能搜索:如果没有结果且是 name@author 格式
|
|
175
|
+
if (skills.length === 0) {
|
|
176
|
+
const match = query.match(/^([^@]+)@([^@]+)$/);
|
|
177
|
+
if (match) {
|
|
178
|
+
const [, skillName, author] = match;
|
|
179
|
+
console.log(`\n💡 未找到 "${query}",尝试按技能名 "${skillName}" 和作者 "${author}" 搜索...\n`);
|
|
180
|
+
|
|
181
|
+
// 重新搜索技能名
|
|
182
|
+
const newResult = await searchSkills(skillName, {
|
|
183
|
+
page: 1,
|
|
184
|
+
limit: 100,
|
|
185
|
+
sortBy: options.sort,
|
|
186
|
+
ai: options.ai
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
const allSkills = newResult.data?.skills || newResult.data || newResult.skills || newResult.results || [];
|
|
190
|
+
|
|
191
|
+
// 筛选匹配作者的技能(不区分大小写)
|
|
192
|
+
skills = allSkills.filter(skill => {
|
|
193
|
+
const skillAuthor = (skill.author || skill.owner || '').toLowerCase();
|
|
194
|
+
return skillAuthor === author.toLowerCase();
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
total = skills.length;
|
|
198
|
+
|
|
199
|
+
if (skills.length > 0) {
|
|
200
|
+
console.log(`✓ 找到 ${total} 个匹配的技能\n`);
|
|
201
|
+
} else {
|
|
202
|
+
console.log(`❌ 仍然未找到匹配的技能\n`);
|
|
203
|
+
console.log('提示:');
|
|
204
|
+
console.log(` - 尝试只搜索技能名: eskill search ${skillName}`);
|
|
205
|
+
console.log(` - 尝试只搜索作者: eskill search ${author}\n`);
|
|
206
|
+
process.exit(0);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
136
211
|
console.log(formatSkillList(skills));
|
|
137
212
|
|
|
138
213
|
// 显示分页信息和提示
|
package/lib/installer.js
CHANGED
|
@@ -8,6 +8,45 @@ import { AGENTS, expandHomePath } from './agent-config.js';
|
|
|
8
8
|
import { searchSkills, formatSkillList } from './search.js';
|
|
9
9
|
import readline from 'readline';
|
|
10
10
|
|
|
11
|
+
/**
|
|
12
|
+
* 获取 Git 仓库的当前 commit hash
|
|
13
|
+
*/
|
|
14
|
+
function getGitCommitHash(repoPath) {
|
|
15
|
+
try {
|
|
16
|
+
return execSync('git rev-parse HEAD', { cwd: repoPath, encoding: 'utf-8' }).trim();
|
|
17
|
+
} catch (error) {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* 获取 Git 仓库的当前版本(tag 或 branch)
|
|
24
|
+
*/
|
|
25
|
+
function getGitVersion(repoPath) {
|
|
26
|
+
try {
|
|
27
|
+
// 尝试获取最新的 tag
|
|
28
|
+
const tag = execSync('git describe --tags --abbrev=0 2>/dev/null || echo ""', {
|
|
29
|
+
cwd: repoPath,
|
|
30
|
+
encoding: 'utf-8',
|
|
31
|
+
shell: '/bin/bash'
|
|
32
|
+
}).trim();
|
|
33
|
+
|
|
34
|
+
if (tag) {
|
|
35
|
+
return tag;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// 如果没有 tag,返回 branch
|
|
39
|
+
const branch = execSync('git rev-parse --abbrev-ref HEAD', {
|
|
40
|
+
cwd: repoPath,
|
|
41
|
+
encoding: 'utf-8'
|
|
42
|
+
}).trim();
|
|
43
|
+
|
|
44
|
+
return branch;
|
|
45
|
+
} catch (error) {
|
|
46
|
+
return 'unknown';
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
11
50
|
/**
|
|
12
51
|
* 获取 eskill 包的安装目录
|
|
13
52
|
* 技能安装在这里,卸载时会自动删除
|
|
@@ -79,29 +118,73 @@ function migrateOldSkills() {
|
|
|
79
118
|
}
|
|
80
119
|
|
|
81
120
|
/**
|
|
82
|
-
*
|
|
121
|
+
* 读取所有技能元数据
|
|
83
122
|
*/
|
|
84
|
-
function
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
}
|
|
123
|
+
function getAllSkillsMeta() {
|
|
124
|
+
const skillDir = getSkillsStorageDir();
|
|
125
|
+
const metaPath = join(skillDir, '.eskill-meta.json');
|
|
88
126
|
|
|
89
|
-
/**
|
|
90
|
-
* 读取技能元数据
|
|
91
|
-
*/
|
|
92
|
-
function getSkillMeta(skillPath) {
|
|
93
|
-
const metaPath = join(skillPath, '.eskill-meta.json');
|
|
94
127
|
if (!existsSync(metaPath)) {
|
|
95
|
-
return
|
|
128
|
+
return [];
|
|
96
129
|
}
|
|
130
|
+
|
|
97
131
|
try {
|
|
98
132
|
const content = readFileSync(metaPath, 'utf-8');
|
|
99
133
|
return JSON.parse(content);
|
|
100
134
|
} catch (error) {
|
|
101
|
-
return
|
|
135
|
+
return [];
|
|
102
136
|
}
|
|
103
137
|
}
|
|
104
138
|
|
|
139
|
+
/**
|
|
140
|
+
* 保存所有技能元数据
|
|
141
|
+
*/
|
|
142
|
+
function saveAllSkillsMeta(metaArray) {
|
|
143
|
+
const skillDir = getSkillsStorageDir();
|
|
144
|
+
|
|
145
|
+
// 确保目录存在
|
|
146
|
+
if (!existsSync(skillDir)) {
|
|
147
|
+
mkdirSync(skillDir, { recursive: true });
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const metaPath = join(skillDir, '.eskill-meta.json');
|
|
151
|
+
writeFileSync(metaPath, JSON.stringify(metaArray, null, 2));
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* 保存单个技能元数据
|
|
156
|
+
*/
|
|
157
|
+
function saveSkillMeta(skillName, meta) {
|
|
158
|
+
const allMeta = getAllSkillsMeta();
|
|
159
|
+
|
|
160
|
+
// 查找并更新或添加
|
|
161
|
+
const index = allMeta.findIndex(m => m.name === skillName);
|
|
162
|
+
if (index >= 0) {
|
|
163
|
+
allMeta[index] = { ...allMeta[index], ...meta };
|
|
164
|
+
} else {
|
|
165
|
+
allMeta.push(meta);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
saveAllSkillsMeta(allMeta);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* 读取单个技能元数据
|
|
173
|
+
*/
|
|
174
|
+
function getSkillMeta(skillName) {
|
|
175
|
+
const allMeta = getAllSkillsMeta();
|
|
176
|
+
return allMeta.find(m => m.name === skillName) || null;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* 删除技能元数据
|
|
181
|
+
*/
|
|
182
|
+
function removeSkillMeta(skillName) {
|
|
183
|
+
const allMeta = getAllSkillsMeta();
|
|
184
|
+
const newMeta = allMeta.filter(m => m.name !== skillName);
|
|
185
|
+
saveAllSkillsMeta(newMeta);
|
|
186
|
+
}
|
|
187
|
+
|
|
105
188
|
/**
|
|
106
189
|
* 检测并处理 name@author 格式
|
|
107
190
|
* 返回解析后的 GitHub URL,如果不是该格式则返回原 URL
|
|
@@ -117,7 +200,7 @@ async function handleSkillAtAuthorFormat(input) {
|
|
|
117
200
|
|
|
118
201
|
const [, skillName, author] = match;
|
|
119
202
|
console.log(`\n检测到技能名称格式: ${skillName}@${author}`);
|
|
120
|
-
console.log(`正在搜索 "${skillName}"...\n`);
|
|
203
|
+
console.log(`正在搜索 "${skillName}" 和作者 "${author}"...\n`);
|
|
121
204
|
|
|
122
205
|
try {
|
|
123
206
|
// 搜索技能
|
|
@@ -129,17 +212,28 @@ async function handleSkillAtAuthorFormat(input) {
|
|
|
129
212
|
|
|
130
213
|
const skills = result.data?.skills || result.data || result.skills || result.results || [];
|
|
131
214
|
|
|
132
|
-
//
|
|
215
|
+
// 筛选出匹配作者的技能(不区分大小写)
|
|
133
216
|
const matchedSkills = skills.filter(skill => {
|
|
134
|
-
const skillAuthor = skill.author || skill.owner || '';
|
|
135
|
-
return skillAuthor
|
|
217
|
+
const skillAuthor = (skill.author || skill.owner || '').toLowerCase();
|
|
218
|
+
return skillAuthor === author.toLowerCase();
|
|
136
219
|
});
|
|
137
220
|
|
|
138
221
|
if (matchedSkills.length === 0) {
|
|
139
222
|
// 没有找到匹配的作者
|
|
140
223
|
console.log(`❌ 未找到作者 "${author}" 的技能 "${skillName}"\n`);
|
|
141
|
-
|
|
142
|
-
|
|
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
|
+
|
|
143
237
|
throw new Error(`未找到匹配的技能`);
|
|
144
238
|
}
|
|
145
239
|
|
|
@@ -151,7 +245,7 @@ async function handleSkillAtAuthorFormat(input) {
|
|
|
151
245
|
throw new Error('技能信息中没有 GitHub URL');
|
|
152
246
|
}
|
|
153
247
|
|
|
154
|
-
console.log(
|
|
248
|
+
console.log(`✓ 找到匹配的技能:`);
|
|
155
249
|
console.log(` 名称: ${skillName}@${author}`);
|
|
156
250
|
console.log(` Stars: ⭐ ${matchedSkill.stars || 'N/A'}`);
|
|
157
251
|
console.log(` GitHub: ${githubUrl}\n`);
|
|
@@ -275,15 +369,22 @@ export async function installFromGitUrl(gitUrl, options = {}) {
|
|
|
275
369
|
console.log(` 存储位置: ~/.eskill/skills/`);
|
|
276
370
|
console.log(` 说明: 技能永久保存,更新 eskill 不会丢失`);
|
|
277
371
|
|
|
278
|
-
//
|
|
279
|
-
|
|
372
|
+
// 获取 git 版本信息
|
|
373
|
+
const commitHash = getGitCommitHash(sourcePath);
|
|
374
|
+
const version = getGitVersion(sourcePath);
|
|
375
|
+
|
|
376
|
+
// 保存技能元数据(作者、Git URL、版本等)
|
|
377
|
+
saveSkillMeta(skillName, {
|
|
280
378
|
name: skillName,
|
|
281
379
|
author: parsed.owner,
|
|
282
380
|
gitUrl: gitUrl,
|
|
283
381
|
platform: parsed.platform,
|
|
284
382
|
repo: parsed.repo,
|
|
285
383
|
branch: parsed.branch,
|
|
286
|
-
|
|
384
|
+
commitHash,
|
|
385
|
+
version,
|
|
386
|
+
installedAt: new Date().toISOString(),
|
|
387
|
+
updatedAt: new Date().toISOString()
|
|
287
388
|
});
|
|
288
389
|
|
|
289
390
|
return { success: true, path: targetPath };
|
|
@@ -310,18 +411,25 @@ export function listSkills(agent = 'claude') {
|
|
|
310
411
|
}
|
|
311
412
|
|
|
312
413
|
// 读取目录中的技能
|
|
313
|
-
|
|
314
|
-
.filter(dirent => dirent.isDirectory())
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
414
|
+
const skillDirs = readdirSync(skillDir, { withFileTypes: true })
|
|
415
|
+
.filter(dirent => dirent.isDirectory());
|
|
416
|
+
|
|
417
|
+
// 读取所有元数据
|
|
418
|
+
const allMeta = getAllSkillsMeta();
|
|
419
|
+
|
|
420
|
+
return skillDirs.map(dirent => {
|
|
421
|
+
const skillPath = join(skillDir, dirent.name);
|
|
422
|
+
const meta = allMeta.find(m => m.name === dirent.name);
|
|
423
|
+
return {
|
|
424
|
+
name: dirent.name,
|
|
425
|
+
path: skillPath,
|
|
426
|
+
author: meta?.author || null,
|
|
427
|
+
version: meta?.version || null,
|
|
428
|
+
commitHash: meta?.commitHash || null,
|
|
429
|
+
installedAt: meta?.installedAt || null,
|
|
430
|
+
gitUrl: meta?.gitUrl || null
|
|
431
|
+
};
|
|
432
|
+
});
|
|
325
433
|
}
|
|
326
434
|
|
|
327
435
|
/**
|
|
@@ -336,6 +444,7 @@ export function removeSkill(skillName, agent = 'claude') {
|
|
|
336
444
|
}
|
|
337
445
|
|
|
338
446
|
rmSync(targetPath, { recursive: true, force: true });
|
|
447
|
+
removeSkillMeta(skillName);
|
|
339
448
|
console.log(`✓ 已删除技能: ${skillName}`);
|
|
340
449
|
}
|
|
341
450
|
|
|
@@ -385,3 +494,194 @@ export function cleanupAll() {
|
|
|
385
494
|
export function getSkillsDir() {
|
|
386
495
|
return getSkillsStorageDir();
|
|
387
496
|
}
|
|
497
|
+
|
|
498
|
+
/**
|
|
499
|
+
* 更新单个技能
|
|
500
|
+
*/
|
|
501
|
+
export async function updateSkill(skillName, options = {}) {
|
|
502
|
+
const { force = false } = options;
|
|
503
|
+
const skillDir = getSkillsStorageDir();
|
|
504
|
+
const targetPath = join(skillDir, skillName);
|
|
505
|
+
|
|
506
|
+
// 检查技能是否存在
|
|
507
|
+
if (!existsSync(targetPath)) {
|
|
508
|
+
throw new Error(`技能不存在: ${skillName}`);
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
// 读取元数据
|
|
512
|
+
const meta = getSkillMeta(skillName);
|
|
513
|
+
|
|
514
|
+
// 如果没有 gitUrl,说明是本地技能,无法更新
|
|
515
|
+
if (!meta || !meta.gitUrl) {
|
|
516
|
+
return { success: false, local: true, skill: skillName };
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
const currentVersion = meta.version || meta.commitHash?.substring(0, 7) || 'unknown';
|
|
520
|
+
|
|
521
|
+
// 重新安装技能(使用内部的 installFromGitUrl,但不打印)
|
|
522
|
+
try {
|
|
523
|
+
const parsed = parseGitUrl(meta.gitUrl);
|
|
524
|
+
|
|
525
|
+
// 确定技能名称
|
|
526
|
+
const skillNameFromUrl = parsed.path ? basename(parsed.path) : parsed.repo;
|
|
527
|
+
|
|
528
|
+
// 创建临时目录
|
|
529
|
+
const tempDir = join(tmpdir(), `eskill-update-${Date.now()}`);
|
|
530
|
+
|
|
531
|
+
try {
|
|
532
|
+
// 克隆仓库(静默模式)
|
|
533
|
+
const cloneUrl = parsed.cloneUrl;
|
|
534
|
+
execSync(
|
|
535
|
+
`git clone --depth 1 --branch ${parsed.branch} --single-branch "${cloneUrl}" "${tempDir}"`,
|
|
536
|
+
{ stdio: 'inherit' }
|
|
537
|
+
);
|
|
538
|
+
|
|
539
|
+
// 确定源路径
|
|
540
|
+
const sourcePath = parsed.path ? join(tempDir, parsed.path) : tempDir;
|
|
541
|
+
|
|
542
|
+
// 验证源路径存在
|
|
543
|
+
if (!existsSync(sourcePath)) {
|
|
544
|
+
throw new Error(`路径不存在: ${sourcePath}`);
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
// 检查 SKILL.md 是否存在
|
|
548
|
+
const skillManifestPath = join(sourcePath, 'SKILL.md');
|
|
549
|
+
if (!existsSync(skillManifestPath)) {
|
|
550
|
+
throw new Error(`无效的技能包:未找到 SKILL.md 文件`);
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
// 获取新版本信息
|
|
554
|
+
const newCommitHash = getGitCommitHash(sourcePath);
|
|
555
|
+
const newVersion = getGitVersion(sourcePath);
|
|
556
|
+
const newVersionDisplay = newVersion || newCommitHash?.substring(0, 7) || 'unknown';
|
|
557
|
+
|
|
558
|
+
// 检查是否有更新
|
|
559
|
+
if (!force && newCommitHash === meta.commitHash) {
|
|
560
|
+
// 删除临时目录
|
|
561
|
+
rmSync(tempDir, { recursive: true, force: true });
|
|
562
|
+
return { success: true, skill: skillName, updated: false, currentVersion };
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
// 删除旧技能
|
|
566
|
+
rmSync(targetPath, { recursive: true, force: true });
|
|
567
|
+
|
|
568
|
+
// 复制新技能
|
|
569
|
+
if (process.platform === 'win32') {
|
|
570
|
+
execSync(`xcopy "${sourcePath}" "${targetPath}" /E /I /H /Y`, { stdio: 'inherit' });
|
|
571
|
+
} else {
|
|
572
|
+
execSync(`cp -r "${sourcePath}" "${targetPath}"`, { stdio: 'inherit' });
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
// 更新元数据
|
|
576
|
+
saveSkillMeta(skillName, {
|
|
577
|
+
...meta,
|
|
578
|
+
commitHash: newCommitHash,
|
|
579
|
+
version: newVersion,
|
|
580
|
+
updatedAt: new Date().toISOString()
|
|
581
|
+
});
|
|
582
|
+
|
|
583
|
+
return {
|
|
584
|
+
success: true,
|
|
585
|
+
skill: skillName,
|
|
586
|
+
updated: true,
|
|
587
|
+
currentVersion,
|
|
588
|
+
newVersion: newVersionDisplay
|
|
589
|
+
};
|
|
590
|
+
} finally {
|
|
591
|
+
// 清理临时目录
|
|
592
|
+
if (existsSync(tempDir)) {
|
|
593
|
+
rmSync(tempDir, { recursive: true, force: true });
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
} catch (error) {
|
|
597
|
+
throw new Error(`更新失败: ${error.message}`);
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
/**
|
|
602
|
+
* 更新所有技能
|
|
603
|
+
*/
|
|
604
|
+
export async function updateAllSkills(options = {}) {
|
|
605
|
+
const { force = false } = options;
|
|
606
|
+
|
|
607
|
+
// 自动迁移旧位置的技能
|
|
608
|
+
migrateOldSkills();
|
|
609
|
+
|
|
610
|
+
const skillDir = getSkillsStorageDir();
|
|
611
|
+
|
|
612
|
+
if (!existsSync(skillDir)) {
|
|
613
|
+
console.log('未安装任何技能');
|
|
614
|
+
return [];
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
// 读取所有技能
|
|
618
|
+
const skills = readdirSync(skillDir, { withFileTypes: true })
|
|
619
|
+
.filter(dirent => dirent.isDirectory())
|
|
620
|
+
.map(dirent => {
|
|
621
|
+
const skillPath = join(skillDir, dirent.name);
|
|
622
|
+
const meta = getSkillMeta(dirent.name);
|
|
623
|
+
return {
|
|
624
|
+
name: dirent.name,
|
|
625
|
+
path: skillPath,
|
|
626
|
+
meta
|
|
627
|
+
};
|
|
628
|
+
});
|
|
629
|
+
|
|
630
|
+
if (skills.length === 0) {
|
|
631
|
+
console.log('未安装任何技能');
|
|
632
|
+
return [];
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
console.log(`\n检查 ${skills.length} 个技能的更新...\n`);
|
|
636
|
+
|
|
637
|
+
const results = {
|
|
638
|
+
updated: [],
|
|
639
|
+
skipped: [],
|
|
640
|
+
failed: []
|
|
641
|
+
};
|
|
642
|
+
|
|
643
|
+
for (const skill of skills) {
|
|
644
|
+
try {
|
|
645
|
+
// 如果没有 gitUrl,跳过
|
|
646
|
+
if (!skill.meta || !skill.meta.gitUrl) {
|
|
647
|
+
console.log(`⊘ ${skill.name} - 本地技能`);
|
|
648
|
+
results.skipped.push({ name: skill.name, reason: 'local' });
|
|
649
|
+
continue;
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
// 更新技能
|
|
653
|
+
const result = await updateSkill(skill.name, { force });
|
|
654
|
+
|
|
655
|
+
if (result.success && result.updated) {
|
|
656
|
+
console.log(`✓ ${skill.name} - ${result.currentVersion} → ${result.newVersion}`);
|
|
657
|
+
results.updated.push({ name: skill.name, oldVersion: result.currentVersion, newVersion: result.newVersion });
|
|
658
|
+
} else if (result.success && !result.updated) {
|
|
659
|
+
console.log(`• ${skill.name} - 已是最新`);
|
|
660
|
+
results.skipped.push({ name: skill.name, reason: 'latest' });
|
|
661
|
+
} else if (result.local) {
|
|
662
|
+
results.skipped.push({ name: skill.name, reason: 'local' });
|
|
663
|
+
}
|
|
664
|
+
} catch (error) {
|
|
665
|
+
console.log(`✗ ${skill.name} - 失败: ${error.message}`);
|
|
666
|
+
results.failed.push({ name: skill.name, error: error.message });
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
// 显示总结
|
|
671
|
+
console.log(`\n${'─'.repeat(50)}`);
|
|
672
|
+
console.log(`更新完成:`);
|
|
673
|
+
console.log(` ✓ 成功: ${results.updated.length}`);
|
|
674
|
+
console.log(` ⊘ 跳过: ${results.skipped.length}`);
|
|
675
|
+
console.log(` ✗ 失败: ${results.failed.length}`);
|
|
676
|
+
console.log(`${'─'.repeat(50)}\n`);
|
|
677
|
+
|
|
678
|
+
if (results.failed.length > 0) {
|
|
679
|
+
console.log('失败的技能:');
|
|
680
|
+
results.failed.forEach(({ name, error }) => {
|
|
681
|
+
console.log(` - ${name}: ${error}`);
|
|
682
|
+
});
|
|
683
|
+
console.log('');
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
return results;
|
|
687
|
+
}
|