eskill 1.0.25 → 1.0.28
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 +39 -12
- package/lib/installer.js +146 -105
- package/package.json +1 -1
package/cli.js
CHANGED
|
@@ -13,7 +13,8 @@ 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.28')
|
|
17
|
+
.option('-g, --global', '使用全局技能目录(~/.eskill/skills/),否则使用当前目录(./.claude/skills/)', false);
|
|
17
18
|
|
|
18
19
|
// 安装命令
|
|
19
20
|
program
|
|
@@ -26,7 +27,11 @@ program
|
|
|
26
27
|
.option('-f, --force', '强制覆盖已存在的技能', false)
|
|
27
28
|
.action(async (url, options) => {
|
|
28
29
|
try {
|
|
29
|
-
|
|
30
|
+
// 获取全局选项
|
|
31
|
+
const globalOptions = program.opts();
|
|
32
|
+
const fullOptions = { ...options, global: globalOptions.global };
|
|
33
|
+
|
|
34
|
+
const result = await installFromGitUrl(url, fullOptions);
|
|
30
35
|
|
|
31
36
|
// 如果用户取消安装,正常退出
|
|
32
37
|
if (result && result.cancelled) {
|
|
@@ -49,14 +54,19 @@ program
|
|
|
49
54
|
.option('-a, --agent <name>', '目标 agent', getDefaultAgent())
|
|
50
55
|
.action(async (options) => {
|
|
51
56
|
try {
|
|
52
|
-
|
|
57
|
+
// 获取全局选项
|
|
58
|
+
const globalOptions = program.opts();
|
|
59
|
+
const global = globalOptions.global;
|
|
60
|
+
|
|
61
|
+
const skills = await listSkills(options.agent, global);
|
|
53
62
|
|
|
54
63
|
if (skills.length === 0) {
|
|
55
64
|
console.log('未安装任何技能');
|
|
56
65
|
return;
|
|
57
66
|
}
|
|
58
67
|
|
|
59
|
-
|
|
68
|
+
const location = global ? '~/.eskill/skills/' : './.claude/skills/';
|
|
69
|
+
console.log(`\n已安装的技能 (${location}):\n`);
|
|
60
70
|
skills.forEach(skill => {
|
|
61
71
|
const displayName = skill.author ? `${skill.name}@${skill.author}` : skill.name;
|
|
62
72
|
const versionInfo = skill.version || (skill.commitHash ? `#${skill.commitHash.substring(0, 7)}` : '');
|
|
@@ -84,7 +94,11 @@ program
|
|
|
84
94
|
.option('-a, --agent <name>', '目标 agent', getDefaultAgent())
|
|
85
95
|
.action(async (name, options) => {
|
|
86
96
|
try {
|
|
87
|
-
|
|
97
|
+
// 获取全局选项
|
|
98
|
+
const globalOptions = program.opts();
|
|
99
|
+
const global = globalOptions.global;
|
|
100
|
+
|
|
101
|
+
removeSkill(name, options.agent, global);
|
|
88
102
|
} catch (error) {
|
|
89
103
|
console.error(`错误: ${error.message}`);
|
|
90
104
|
process.exit(1);
|
|
@@ -95,14 +109,22 @@ program
|
|
|
95
109
|
program
|
|
96
110
|
.command('update')
|
|
97
111
|
.description('更新技能')
|
|
98
|
-
.argument('[name]', '
|
|
112
|
+
.argument('[name]', '技能名称(不指定或 "all" 则更新所有)')
|
|
99
113
|
.option('-f, --force', '强制更新(即使版本相同)', false)
|
|
100
114
|
.action(async (name, options) => {
|
|
101
115
|
try {
|
|
102
|
-
|
|
116
|
+
// 获取全局选项
|
|
117
|
+
const globalOptions = program.opts();
|
|
118
|
+
const fullOptions = { ...options, global: globalOptions.global };
|
|
119
|
+
|
|
120
|
+
// 支持显式使用 "all" 参数
|
|
121
|
+
if (name === 'all' || name === 'All' || name === 'ALL') {
|
|
122
|
+
// 更新所有技能
|
|
123
|
+
await updateAllSkills(fullOptions);
|
|
124
|
+
} else if (name) {
|
|
103
125
|
// 更新单个技能
|
|
104
126
|
console.log(`\n检查技能: ${name}`);
|
|
105
|
-
const result = await updateSkill(name,
|
|
127
|
+
const result = await updateSkill(name, fullOptions);
|
|
106
128
|
|
|
107
129
|
if (result.local) {
|
|
108
130
|
console.log('\n⚠️ 本地技能无法自动更新,请手动更新\n');
|
|
@@ -114,8 +136,8 @@ program
|
|
|
114
136
|
console.log(` 新版本: ${result.newVersion}\n`);
|
|
115
137
|
}
|
|
116
138
|
} else {
|
|
117
|
-
//
|
|
118
|
-
await updateAllSkills(
|
|
139
|
+
// 更新所有技能(不指定参数时)
|
|
140
|
+
await updateAllSkills(fullOptions);
|
|
119
141
|
}
|
|
120
142
|
} catch (error) {
|
|
121
143
|
console.error(`\n❌ 更新失败: ${error.message}\n`);
|
|
@@ -285,6 +307,10 @@ program
|
|
|
285
307
|
.option('-a, --all', '清理所有数据(包括配置和技能)', false)
|
|
286
308
|
.action(async (options) => {
|
|
287
309
|
try {
|
|
310
|
+
// 获取全局选项
|
|
311
|
+
const globalOptions = program.opts();
|
|
312
|
+
const global = globalOptions.global;
|
|
313
|
+
|
|
288
314
|
if (options.all) {
|
|
289
315
|
console.log('\n⚠️ 警告:这将删除所有 eskill 数据(包括 API Key 配置和已安装的技能)');
|
|
290
316
|
|
|
@@ -302,8 +328,9 @@ program
|
|
|
302
328
|
}
|
|
303
329
|
});
|
|
304
330
|
} else {
|
|
305
|
-
|
|
306
|
-
|
|
331
|
+
const location = global ? '~/.eskill/skills/' : './.claude/skills/';
|
|
332
|
+
console.log(`\n清理 ${location} 中的所有技能...\n`);
|
|
333
|
+
cleanupAllSkills(global);
|
|
307
334
|
}
|
|
308
335
|
} catch (error) {
|
|
309
336
|
console.error(`\n❌ 清理失败: ${error.message}\n`);
|
package/lib/installer.js
CHANGED
|
@@ -58,21 +58,29 @@ function getEskillPackageDir() {
|
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
/**
|
|
61
|
-
*
|
|
61
|
+
* 获取技能存储目录
|
|
62
|
+
* @param {boolean} global - 是否使用全局目录(~/.eskill/skills/),否则使用当前目录(./.claude/skills/)
|
|
62
63
|
*/
|
|
63
|
-
function getSkillsStorageDir() {
|
|
64
|
-
|
|
64
|
+
function getSkillsStorageDir(global = false) {
|
|
65
|
+
if (global) {
|
|
66
|
+
// 全局模式:使用 ~/.eskill/skills/
|
|
67
|
+
return join(homedir(), '.eskill', 'skills');
|
|
68
|
+
} else {
|
|
69
|
+
// 本地模式:使用当前目录的 .claude/skills/
|
|
70
|
+
return join(process.cwd(), '.claude', 'skills');
|
|
71
|
+
}
|
|
65
72
|
}
|
|
66
73
|
|
|
67
74
|
/**
|
|
68
75
|
* 迁移旧位置的技能到新位置
|
|
76
|
+
* @param {boolean} global - 是否全局模式
|
|
69
77
|
*/
|
|
70
|
-
function migrateOldSkills() {
|
|
78
|
+
function migrateOldSkills(global = false) {
|
|
71
79
|
const __filename = fileURLToPath(import.meta.url);
|
|
72
80
|
const __dirname = dirname(__filename);
|
|
73
81
|
const pkgDir = dirname(__dirname);
|
|
74
82
|
const oldDir = join(pkgDir, 'skills-storage');
|
|
75
|
-
const newDir = getSkillsStorageDir();
|
|
83
|
+
const newDir = getSkillsStorageDir(global);
|
|
76
84
|
|
|
77
85
|
// 如果旧位置不存在,无需迁移
|
|
78
86
|
if (!existsSync(oldDir)) {
|
|
@@ -84,6 +92,11 @@ function migrateOldSkills() {
|
|
|
84
92
|
return;
|
|
85
93
|
}
|
|
86
94
|
|
|
95
|
+
// 只在全局模式下迁移
|
|
96
|
+
if (!global) {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
|
|
87
100
|
try {
|
|
88
101
|
// 创建新目录
|
|
89
102
|
mkdirSync(newDir, { recursive: true });
|
|
@@ -119,9 +132,10 @@ function migrateOldSkills() {
|
|
|
119
132
|
|
|
120
133
|
/**
|
|
121
134
|
* 读取所有技能元数据
|
|
135
|
+
* @param {boolean} global - 是否全局模式
|
|
122
136
|
*/
|
|
123
|
-
function getAllSkillsMeta() {
|
|
124
|
-
const skillDir = getSkillsStorageDir();
|
|
137
|
+
function getAllSkillsMeta(global = false) {
|
|
138
|
+
const skillDir = getSkillsStorageDir(global);
|
|
125
139
|
const metaPath = join(skillDir, '.eskill-meta.json');
|
|
126
140
|
|
|
127
141
|
if (!existsSync(metaPath)) {
|
|
@@ -138,9 +152,11 @@ function getAllSkillsMeta() {
|
|
|
138
152
|
|
|
139
153
|
/**
|
|
140
154
|
* 保存所有技能元数据
|
|
155
|
+
* @param {metaArray} metaArray - 元数据数组
|
|
156
|
+
* @param {boolean} global - 是否全局模式
|
|
141
157
|
*/
|
|
142
|
-
function saveAllSkillsMeta(metaArray) {
|
|
143
|
-
const skillDir = getSkillsStorageDir();
|
|
158
|
+
function saveAllSkillsMeta(metaArray, global = false) {
|
|
159
|
+
const skillDir = getSkillsStorageDir(global);
|
|
144
160
|
|
|
145
161
|
// 确保目录存在
|
|
146
162
|
if (!existsSync(skillDir)) {
|
|
@@ -153,9 +169,12 @@ function saveAllSkillsMeta(metaArray) {
|
|
|
153
169
|
|
|
154
170
|
/**
|
|
155
171
|
* 保存单个技能元数据
|
|
172
|
+
* @param {string} skillName - 技能名称
|
|
173
|
+
* @param {object} meta - 元数据
|
|
174
|
+
* @param {boolean} global - 是否全局模式
|
|
156
175
|
*/
|
|
157
|
-
function saveSkillMeta(skillName, meta) {
|
|
158
|
-
const allMeta = getAllSkillsMeta();
|
|
176
|
+
function saveSkillMeta(skillName, meta, global = false) {
|
|
177
|
+
const allMeta = getAllSkillsMeta(global);
|
|
159
178
|
|
|
160
179
|
// 查找并更新或添加
|
|
161
180
|
const index = allMeta.findIndex(m => m.name === skillName);
|
|
@@ -165,24 +184,28 @@ function saveSkillMeta(skillName, meta) {
|
|
|
165
184
|
allMeta.push(meta);
|
|
166
185
|
}
|
|
167
186
|
|
|
168
|
-
saveAllSkillsMeta(allMeta);
|
|
187
|
+
saveAllSkillsMeta(allMeta, global);
|
|
169
188
|
}
|
|
170
189
|
|
|
171
190
|
/**
|
|
172
191
|
* 读取单个技能元数据
|
|
192
|
+
* @param {string} skillName - 技能名称
|
|
193
|
+
* @param {boolean} global - 是否全局模式
|
|
173
194
|
*/
|
|
174
|
-
function getSkillMeta(skillName) {
|
|
175
|
-
const allMeta = getAllSkillsMeta();
|
|
195
|
+
function getSkillMeta(skillName, global = false) {
|
|
196
|
+
const allMeta = getAllSkillsMeta(global);
|
|
176
197
|
return allMeta.find(m => m.name === skillName) || null;
|
|
177
198
|
}
|
|
178
199
|
|
|
179
200
|
/**
|
|
180
201
|
* 删除技能元数据
|
|
202
|
+
* @param {string} skillName - 技能名称
|
|
203
|
+
* @param {boolean} global - 是否全局模式
|
|
181
204
|
*/
|
|
182
|
-
function removeSkillMeta(skillName) {
|
|
183
|
-
const allMeta = getAllSkillsMeta();
|
|
205
|
+
function removeSkillMeta(skillName, global = false) {
|
|
206
|
+
const allMeta = getAllSkillsMeta(global);
|
|
184
207
|
const newMeta = allMeta.filter(m => m.name !== skillName);
|
|
185
|
-
saveAllSkillsMeta(newMeta);
|
|
208
|
+
saveAllSkillsMeta(newMeta, global);
|
|
186
209
|
}
|
|
187
210
|
|
|
188
211
|
/**
|
|
@@ -280,7 +303,7 @@ function confirmAction(message) {
|
|
|
280
303
|
* 从 Git URL 安装技能
|
|
281
304
|
*/
|
|
282
305
|
export async function installFromGitUrl(gitUrl, options = {}) {
|
|
283
|
-
const { agent = 'claude', link = false, force = false } = options;
|
|
306
|
+
const { agent = 'claude', link = false, force = false, global = false } = options;
|
|
284
307
|
|
|
285
308
|
// 检测并处理 name@author 格式
|
|
286
309
|
let actualUrl = await handleSkillAtAuthorFormat(gitUrl);
|
|
@@ -304,7 +327,7 @@ export async function installFromGitUrl(gitUrl, options = {}) {
|
|
|
304
327
|
}
|
|
305
328
|
|
|
306
329
|
// 获取技能存储目录
|
|
307
|
-
const skillDir = getSkillsStorageDir();
|
|
330
|
+
const skillDir = getSkillsStorageDir(global);
|
|
308
331
|
|
|
309
332
|
// 确保技能目录存在
|
|
310
333
|
if (!existsSync(skillDir)) {
|
|
@@ -385,7 +408,7 @@ export async function installFromGitUrl(gitUrl, options = {}) {
|
|
|
385
408
|
version,
|
|
386
409
|
installedAt: new Date().toISOString(),
|
|
387
410
|
updatedAt: new Date().toISOString()
|
|
388
|
-
});
|
|
411
|
+
}, global);
|
|
389
412
|
|
|
390
413
|
return { success: true, path: targetPath };
|
|
391
414
|
} finally {
|
|
@@ -400,11 +423,11 @@ export async function installFromGitUrl(gitUrl, options = {}) {
|
|
|
400
423
|
/**
|
|
401
424
|
* 列出已安装的技能
|
|
402
425
|
*/
|
|
403
|
-
export function listSkills(agent = 'claude') {
|
|
426
|
+
export function listSkills(agent = 'claude', global = false) {
|
|
404
427
|
// 自动迁移旧位置的技能
|
|
405
|
-
migrateOldSkills();
|
|
428
|
+
migrateOldSkills(global);
|
|
406
429
|
|
|
407
|
-
const skillDir = getSkillsStorageDir();
|
|
430
|
+
const skillDir = getSkillsStorageDir(global);
|
|
408
431
|
|
|
409
432
|
if (!existsSync(skillDir)) {
|
|
410
433
|
return [];
|
|
@@ -415,7 +438,7 @@ export function listSkills(agent = 'claude') {
|
|
|
415
438
|
.filter(dirent => dirent.isDirectory());
|
|
416
439
|
|
|
417
440
|
// 读取所有元数据
|
|
418
|
-
const allMeta = getAllSkillsMeta();
|
|
441
|
+
const allMeta = getAllSkillsMeta(global);
|
|
419
442
|
|
|
420
443
|
return skillDirs.map(dirent => {
|
|
421
444
|
const skillPath = join(skillDir, dirent.name);
|
|
@@ -435,8 +458,8 @@ export function listSkills(agent = 'claude') {
|
|
|
435
458
|
/**
|
|
436
459
|
* 删除技能
|
|
437
460
|
*/
|
|
438
|
-
export function removeSkill(skillName, agent = 'claude') {
|
|
439
|
-
const skillDir = getSkillsStorageDir();
|
|
461
|
+
export function removeSkill(skillName, agent = 'claude', global = false) {
|
|
462
|
+
const skillDir = getSkillsStorageDir(global);
|
|
440
463
|
const targetPath = join(skillDir, skillName);
|
|
441
464
|
|
|
442
465
|
if (!existsSync(targetPath)) {
|
|
@@ -444,15 +467,15 @@ export function removeSkill(skillName, agent = 'claude') {
|
|
|
444
467
|
}
|
|
445
468
|
|
|
446
469
|
rmSync(targetPath, { recursive: true, force: true });
|
|
447
|
-
removeSkillMeta(skillName);
|
|
470
|
+
removeSkillMeta(skillName, global);
|
|
448
471
|
console.log(`✓ 已删除技能: ${skillName}`);
|
|
449
472
|
}
|
|
450
473
|
|
|
451
474
|
/**
|
|
452
475
|
* 清理所有技能(用于卸载)
|
|
453
476
|
*/
|
|
454
|
-
export function cleanupAllSkills() {
|
|
455
|
-
const skillDir = getSkillsStorageDir();
|
|
477
|
+
export function cleanupAllSkills(global = false) {
|
|
478
|
+
const skillDir = getSkillsStorageDir(global);
|
|
456
479
|
|
|
457
480
|
if (!existsSync(skillDir)) {
|
|
458
481
|
console.log('没有需要清理的技能');
|
|
@@ -491,16 +514,33 @@ export function cleanupAll() {
|
|
|
491
514
|
/**
|
|
492
515
|
* 获取技能存储目录路径
|
|
493
516
|
*/
|
|
494
|
-
export function getSkillsDir() {
|
|
495
|
-
return getSkillsStorageDir();
|
|
517
|
+
export function getSkillsDir(global = false) {
|
|
518
|
+
return getSkillsStorageDir(global);
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
/**
|
|
522
|
+
* 获取远程仓库的 commit hash(不克隆)
|
|
523
|
+
*/
|
|
524
|
+
function getRemoteCommitHash(gitUrl, branch) {
|
|
525
|
+
try {
|
|
526
|
+
const result = execSync(
|
|
527
|
+
`git ls-remote "${gitUrl}" refs/heads/${branch}`,
|
|
528
|
+
{ encoding: 'utf-8' }
|
|
529
|
+
);
|
|
530
|
+
// 输出格式: "commitHash\trefs/heads/branch"
|
|
531
|
+
const match = result.trim().split('\t');
|
|
532
|
+
return match[0] || null;
|
|
533
|
+
} catch (error) {
|
|
534
|
+
return null;
|
|
535
|
+
}
|
|
496
536
|
}
|
|
497
537
|
|
|
498
538
|
/**
|
|
499
539
|
* 更新单个技能
|
|
500
540
|
*/
|
|
501
541
|
export async function updateSkill(skillName, options = {}) {
|
|
502
|
-
const { force = false } = options;
|
|
503
|
-
const skillDir = getSkillsStorageDir();
|
|
542
|
+
const { force = false, global = false } = options;
|
|
543
|
+
const skillDir = getSkillsStorageDir(global);
|
|
504
544
|
const targetPath = join(skillDir, skillName);
|
|
505
545
|
|
|
506
546
|
// 检查技能是否存在
|
|
@@ -509,7 +549,7 @@ export async function updateSkill(skillName, options = {}) {
|
|
|
509
549
|
}
|
|
510
550
|
|
|
511
551
|
// 读取元数据
|
|
512
|
-
const meta = getSkillMeta(skillName);
|
|
552
|
+
const meta = getSkillMeta(skillName, global);
|
|
513
553
|
|
|
514
554
|
// 如果没有 gitUrl,说明是本地技能,无法更新
|
|
515
555
|
if (!meta || !meta.gitUrl) {
|
|
@@ -518,83 +558,84 @@ export async function updateSkill(skillName, options = {}) {
|
|
|
518
558
|
|
|
519
559
|
const currentVersion = meta.version || meta.commitHash?.substring(0, 7) || 'unknown';
|
|
520
560
|
|
|
521
|
-
//
|
|
522
|
-
|
|
523
|
-
const parsed = parseGitUrl(meta.gitUrl);
|
|
561
|
+
// 解析 Git URL
|
|
562
|
+
const parsed = parseGitUrl(meta.gitUrl);
|
|
524
563
|
|
|
525
|
-
|
|
526
|
-
|
|
564
|
+
// 先检查远程 commit hash(不下载)
|
|
565
|
+
console.log(` 检查远程版本...`);
|
|
566
|
+
const remoteCommitHash = getRemoteCommitHash(parsed.cloneUrl, parsed.branch);
|
|
527
567
|
|
|
528
|
-
|
|
529
|
-
|
|
568
|
+
if (!remoteCommitHash) {
|
|
569
|
+
throw new Error('无法获取远程版本信息');
|
|
570
|
+
}
|
|
530
571
|
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
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
|
-
}
|
|
572
|
+
// 检查是否有更新
|
|
573
|
+
if (!force && remoteCommitHash === meta.commitHash) {
|
|
574
|
+
return { success: true, skill: skillName, updated: false, currentVersion };
|
|
575
|
+
}
|
|
546
576
|
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
if (!existsSync(skillManifestPath)) {
|
|
550
|
-
throw new Error(`无效的技能包:未找到 SKILL.md 文件`);
|
|
551
|
-
}
|
|
577
|
+
// 有更新或强制更新,才下载代码
|
|
578
|
+
console.log(` 发现更新,正在下载...`);
|
|
552
579
|
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
const newVersion = getGitVersion(sourcePath);
|
|
556
|
-
const newVersionDisplay = newVersion || newCommitHash?.substring(0, 7) || 'unknown';
|
|
580
|
+
// 创建临时目录
|
|
581
|
+
const tempDir = join(tmpdir(), `eskill-update-${Date.now()}`);
|
|
557
582
|
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
583
|
+
try {
|
|
584
|
+
// 克隆仓库(静默模式)
|
|
585
|
+
execSync(
|
|
586
|
+
`git clone --depth 1 --branch ${parsed.branch} --single-branch "${parsed.cloneUrl}" "${tempDir}"`,
|
|
587
|
+
{ stdio: 'inherit' }
|
|
588
|
+
);
|
|
564
589
|
|
|
565
|
-
|
|
566
|
-
|
|
590
|
+
// 确定源路径
|
|
591
|
+
const sourcePath = parsed.path ? join(tempDir, parsed.path) : tempDir;
|
|
567
592
|
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
execSync(`cp -r "${sourcePath}" "${targetPath}"`, { stdio: 'inherit' });
|
|
573
|
-
}
|
|
593
|
+
// 验证源路径存在
|
|
594
|
+
if (!existsSync(sourcePath)) {
|
|
595
|
+
throw new Error(`路径不存在: ${sourcePath}`);
|
|
596
|
+
}
|
|
574
597
|
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
updatedAt: new Date().toISOString()
|
|
581
|
-
});
|
|
598
|
+
// 检查 SKILL.md 是否存在
|
|
599
|
+
const skillManifestPath = join(sourcePath, 'SKILL.md');
|
|
600
|
+
if (!existsSync(skillManifestPath)) {
|
|
601
|
+
throw new Error(`无效的技能包:未找到 SKILL.md 文件`);
|
|
602
|
+
}
|
|
582
603
|
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
604
|
+
// 获取新版本信息
|
|
605
|
+
const newCommitHash = getGitCommitHash(sourcePath);
|
|
606
|
+
const newVersion = getGitVersion(sourcePath);
|
|
607
|
+
const newVersionDisplay = newVersion || newCommitHash?.substring(0, 7) || 'unknown';
|
|
608
|
+
|
|
609
|
+
// 删除旧技能
|
|
610
|
+
rmSync(targetPath, { recursive: true, force: true });
|
|
611
|
+
|
|
612
|
+
// 复制新技能
|
|
613
|
+
if (process.platform === 'win32') {
|
|
614
|
+
execSync(`xcopy "${sourcePath}" "${targetPath}" /E /I /H /Y`, { stdio: 'inherit' });
|
|
615
|
+
} else {
|
|
616
|
+
execSync(`cp -r "${sourcePath}" "${targetPath}"`, { stdio: 'inherit' });
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
// 更新元数据
|
|
620
|
+
saveSkillMeta(skillName, {
|
|
621
|
+
...meta,
|
|
622
|
+
commitHash: newCommitHash,
|
|
623
|
+
version: newVersion,
|
|
624
|
+
updatedAt: new Date().toISOString()
|
|
625
|
+
}, global);
|
|
626
|
+
|
|
627
|
+
return {
|
|
628
|
+
success: true,
|
|
629
|
+
skill: skillName,
|
|
630
|
+
updated: true,
|
|
631
|
+
currentVersion,
|
|
632
|
+
newVersion: newVersionDisplay
|
|
633
|
+
};
|
|
634
|
+
} finally {
|
|
635
|
+
// 清理临时目录
|
|
636
|
+
if (existsSync(tempDir)) {
|
|
637
|
+
rmSync(tempDir, { recursive: true, force: true });
|
|
595
638
|
}
|
|
596
|
-
} catch (error) {
|
|
597
|
-
throw new Error(`更新失败: ${error.message}`);
|
|
598
639
|
}
|
|
599
640
|
}
|
|
600
641
|
|
|
@@ -602,12 +643,12 @@ export async function updateSkill(skillName, options = {}) {
|
|
|
602
643
|
* 更新所有技能
|
|
603
644
|
*/
|
|
604
645
|
export async function updateAllSkills(options = {}) {
|
|
605
|
-
const { force = false } = options;
|
|
646
|
+
const { force = false, global = false } = options;
|
|
606
647
|
|
|
607
648
|
// 自动迁移旧位置的技能
|
|
608
|
-
migrateOldSkills();
|
|
649
|
+
migrateOldSkills(global);
|
|
609
650
|
|
|
610
|
-
const skillDir = getSkillsStorageDir();
|
|
651
|
+
const skillDir = getSkillsStorageDir(global);
|
|
611
652
|
|
|
612
653
|
if (!existsSync(skillDir)) {
|
|
613
654
|
console.log('未安装任何技能');
|
|
@@ -619,7 +660,7 @@ export async function updateAllSkills(options = {}) {
|
|
|
619
660
|
.filter(dirent => dirent.isDirectory())
|
|
620
661
|
.map(dirent => {
|
|
621
662
|
const skillPath = join(skillDir, dirent.name);
|
|
622
|
-
const meta = getSkillMeta(dirent.name);
|
|
663
|
+
const meta = getSkillMeta(dirent.name, global);
|
|
623
664
|
return {
|
|
624
665
|
name: dirent.name,
|
|
625
666
|
path: skillPath,
|
|
@@ -650,7 +691,7 @@ export async function updateAllSkills(options = {}) {
|
|
|
650
691
|
}
|
|
651
692
|
|
|
652
693
|
// 更新技能
|
|
653
|
-
const result = await updateSkill(skill.name, { force });
|
|
694
|
+
const result = await updateSkill(skill.name, { force, global });
|
|
654
695
|
|
|
655
696
|
if (result.success && result.updated) {
|
|
656
697
|
console.log(`✓ ${skill.name} - ${result.currentVersion} → ${result.newVersion}`);
|