eskill 1.0.27 → 1.0.29
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 +33 -10
- package/lib/installer.js +84 -47
- 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.29')
|
|
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);
|
|
@@ -99,14 +113,18 @@ program
|
|
|
99
113
|
.option('-f, --force', '强制更新(即使版本相同)', false)
|
|
100
114
|
.action(async (name, options) => {
|
|
101
115
|
try {
|
|
116
|
+
// 获取全局选项
|
|
117
|
+
const globalOptions = program.opts();
|
|
118
|
+
const fullOptions = { ...options, global: globalOptions.global };
|
|
119
|
+
|
|
102
120
|
// 支持显式使用 "all" 参数
|
|
103
121
|
if (name === 'all' || name === 'All' || name === 'ALL') {
|
|
104
122
|
// 更新所有技能
|
|
105
|
-
await updateAllSkills(
|
|
123
|
+
await updateAllSkills(fullOptions);
|
|
106
124
|
} else if (name) {
|
|
107
125
|
// 更新单个技能
|
|
108
126
|
console.log(`\n检查技能: ${name}`);
|
|
109
|
-
const result = await updateSkill(name,
|
|
127
|
+
const result = await updateSkill(name, fullOptions);
|
|
110
128
|
|
|
111
129
|
if (result.local) {
|
|
112
130
|
console.log('\n⚠️ 本地技能无法自动更新,请手动更新\n');
|
|
@@ -119,7 +137,7 @@ program
|
|
|
119
137
|
}
|
|
120
138
|
} else {
|
|
121
139
|
// 更新所有技能(不指定参数时)
|
|
122
|
-
await updateAllSkills(
|
|
140
|
+
await updateAllSkills(fullOptions);
|
|
123
141
|
}
|
|
124
142
|
} catch (error) {
|
|
125
143
|
console.error(`\n❌ 更新失败: ${error.message}\n`);
|
|
@@ -289,6 +307,10 @@ program
|
|
|
289
307
|
.option('-a, --all', '清理所有数据(包括配置和技能)', false)
|
|
290
308
|
.action(async (options) => {
|
|
291
309
|
try {
|
|
310
|
+
// 获取全局选项
|
|
311
|
+
const globalOptions = program.opts();
|
|
312
|
+
const global = globalOptions.global;
|
|
313
|
+
|
|
292
314
|
if (options.all) {
|
|
293
315
|
console.log('\n⚠️ 警告:这将删除所有 eskill 数据(包括 API Key 配置和已安装的技能)');
|
|
294
316
|
|
|
@@ -306,8 +328,9 @@ program
|
|
|
306
328
|
}
|
|
307
329
|
});
|
|
308
330
|
} else {
|
|
309
|
-
|
|
310
|
-
|
|
331
|
+
const location = global ? '~/.eskill/skills/' : './.claude/skills/';
|
|
332
|
+
console.log(`\n清理 ${location} 中的所有技能...\n`);
|
|
333
|
+
cleanupAllSkills(global);
|
|
311
334
|
}
|
|
312
335
|
} catch (error) {
|
|
313
336
|
console.error(`\n❌ 清理失败: ${error.message}\n`);
|
package/lib/installer.js
CHANGED
|
@@ -58,21 +58,43 @@ function getEskillPackageDir() {
|
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
/**
|
|
61
|
-
*
|
|
61
|
+
* 获取技能目录
|
|
62
|
+
* @param {boolean} global - 是否使用全局目录
|
|
62
63
|
*/
|
|
63
|
-
function
|
|
64
|
-
|
|
64
|
+
function getSkillsDir(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
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* 获取技能存储目录(用于元数据)
|
|
76
|
+
* @param {boolean} global - 是否使用全局目录
|
|
77
|
+
*/
|
|
78
|
+
function getSkillsStorageDir(global = false) {
|
|
79
|
+
if (global) {
|
|
80
|
+
// 全局模式:使用 ~/.eskill/skills/
|
|
81
|
+
return join(homedir(), '.eskill', 'skills');
|
|
82
|
+
} else {
|
|
83
|
+
// 本地模式:使用当前目录(项目根目录)
|
|
84
|
+
return process.cwd();
|
|
85
|
+
}
|
|
65
86
|
}
|
|
66
87
|
|
|
67
88
|
/**
|
|
68
89
|
* 迁移旧位置的技能到新位置
|
|
90
|
+
* @param {boolean} global - 是否全局模式
|
|
69
91
|
*/
|
|
70
|
-
function migrateOldSkills() {
|
|
92
|
+
function migrateOldSkills(global = false) {
|
|
71
93
|
const __filename = fileURLToPath(import.meta.url);
|
|
72
94
|
const __dirname = dirname(__filename);
|
|
73
95
|
const pkgDir = dirname(__dirname);
|
|
74
96
|
const oldDir = join(pkgDir, 'skills-storage');
|
|
75
|
-
const newDir = getSkillsStorageDir();
|
|
97
|
+
const newDir = getSkillsStorageDir(global);
|
|
76
98
|
|
|
77
99
|
// 如果旧位置不存在,无需迁移
|
|
78
100
|
if (!existsSync(oldDir)) {
|
|
@@ -84,6 +106,11 @@ function migrateOldSkills() {
|
|
|
84
106
|
return;
|
|
85
107
|
}
|
|
86
108
|
|
|
109
|
+
// 只在全局模式下迁移
|
|
110
|
+
if (!global) {
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
|
|
87
114
|
try {
|
|
88
115
|
// 创建新目录
|
|
89
116
|
mkdirSync(newDir, { recursive: true });
|
|
@@ -119,9 +146,10 @@ function migrateOldSkills() {
|
|
|
119
146
|
|
|
120
147
|
/**
|
|
121
148
|
* 读取所有技能元数据
|
|
149
|
+
* @param {boolean} global - 是否全局模式
|
|
122
150
|
*/
|
|
123
|
-
function getAllSkillsMeta() {
|
|
124
|
-
const skillDir = getSkillsStorageDir();
|
|
151
|
+
function getAllSkillsMeta(global = false) {
|
|
152
|
+
const skillDir = getSkillsStorageDir(global);
|
|
125
153
|
const metaPath = join(skillDir, '.eskill-meta.json');
|
|
126
154
|
|
|
127
155
|
if (!existsSync(metaPath)) {
|
|
@@ -138,9 +166,11 @@ function getAllSkillsMeta() {
|
|
|
138
166
|
|
|
139
167
|
/**
|
|
140
168
|
* 保存所有技能元数据
|
|
169
|
+
* @param {metaArray} metaArray - 元数据数组
|
|
170
|
+
* @param {boolean} global - 是否全局模式
|
|
141
171
|
*/
|
|
142
|
-
function saveAllSkillsMeta(metaArray) {
|
|
143
|
-
const skillDir = getSkillsStorageDir();
|
|
172
|
+
function saveAllSkillsMeta(metaArray, global = false) {
|
|
173
|
+
const skillDir = getSkillsStorageDir(global);
|
|
144
174
|
|
|
145
175
|
// 确保目录存在
|
|
146
176
|
if (!existsSync(skillDir)) {
|
|
@@ -153,9 +183,12 @@ function saveAllSkillsMeta(metaArray) {
|
|
|
153
183
|
|
|
154
184
|
/**
|
|
155
185
|
* 保存单个技能元数据
|
|
186
|
+
* @param {string} skillName - 技能名称
|
|
187
|
+
* @param {object} meta - 元数据
|
|
188
|
+
* @param {boolean} global - 是否全局模式
|
|
156
189
|
*/
|
|
157
|
-
function saveSkillMeta(skillName, meta) {
|
|
158
|
-
const allMeta = getAllSkillsMeta();
|
|
190
|
+
function saveSkillMeta(skillName, meta, global = false) {
|
|
191
|
+
const allMeta = getAllSkillsMeta(global);
|
|
159
192
|
|
|
160
193
|
// 查找并更新或添加
|
|
161
194
|
const index = allMeta.findIndex(m => m.name === skillName);
|
|
@@ -165,24 +198,28 @@ function saveSkillMeta(skillName, meta) {
|
|
|
165
198
|
allMeta.push(meta);
|
|
166
199
|
}
|
|
167
200
|
|
|
168
|
-
saveAllSkillsMeta(allMeta);
|
|
201
|
+
saveAllSkillsMeta(allMeta, global);
|
|
169
202
|
}
|
|
170
203
|
|
|
171
204
|
/**
|
|
172
205
|
* 读取单个技能元数据
|
|
206
|
+
* @param {string} skillName - 技能名称
|
|
207
|
+
* @param {boolean} global - 是否全局模式
|
|
173
208
|
*/
|
|
174
|
-
function getSkillMeta(skillName) {
|
|
175
|
-
const allMeta = getAllSkillsMeta();
|
|
209
|
+
function getSkillMeta(skillName, global = false) {
|
|
210
|
+
const allMeta = getAllSkillsMeta(global);
|
|
176
211
|
return allMeta.find(m => m.name === skillName) || null;
|
|
177
212
|
}
|
|
178
213
|
|
|
179
214
|
/**
|
|
180
215
|
* 删除技能元数据
|
|
216
|
+
* @param {string} skillName - 技能名称
|
|
217
|
+
* @param {boolean} global - 是否全局模式
|
|
181
218
|
*/
|
|
182
|
-
function removeSkillMeta(skillName) {
|
|
183
|
-
const allMeta = getAllSkillsMeta();
|
|
219
|
+
function removeSkillMeta(skillName, global = false) {
|
|
220
|
+
const allMeta = getAllSkillsMeta(global);
|
|
184
221
|
const newMeta = allMeta.filter(m => m.name !== skillName);
|
|
185
|
-
saveAllSkillsMeta(newMeta);
|
|
222
|
+
saveAllSkillsMeta(newMeta, global);
|
|
186
223
|
}
|
|
187
224
|
|
|
188
225
|
/**
|
|
@@ -280,7 +317,7 @@ function confirmAction(message) {
|
|
|
280
317
|
* 从 Git URL 安装技能
|
|
281
318
|
*/
|
|
282
319
|
export async function installFromGitUrl(gitUrl, options = {}) {
|
|
283
|
-
const { agent = 'claude', link = false, force = false } = options;
|
|
320
|
+
const { agent = 'claude', link = false, force = false, global = false } = options;
|
|
284
321
|
|
|
285
322
|
// 检测并处理 name@author 格式
|
|
286
323
|
let actualUrl = await handleSkillAtAuthorFormat(gitUrl);
|
|
@@ -303,17 +340,17 @@ export async function installFromGitUrl(gitUrl, options = {}) {
|
|
|
303
340
|
console.log(`路径: ${parsed.path}`);
|
|
304
341
|
}
|
|
305
342
|
|
|
306
|
-
//
|
|
307
|
-
const
|
|
343
|
+
// 获取技能目录(技能文件存放位置)
|
|
344
|
+
const skillsDir = getSkillsDir(global);
|
|
308
345
|
|
|
309
346
|
// 确保技能目录存在
|
|
310
|
-
if (!existsSync(
|
|
311
|
-
mkdirSync(
|
|
347
|
+
if (!existsSync(skillsDir)) {
|
|
348
|
+
mkdirSync(skillsDir, { recursive: true });
|
|
312
349
|
}
|
|
313
350
|
|
|
314
351
|
// 确定技能名称
|
|
315
352
|
const skillName = parsed.path ? basename(parsed.path) : parsed.repo;
|
|
316
|
-
const targetPath = join(
|
|
353
|
+
const targetPath = join(skillsDir, skillName);
|
|
317
354
|
|
|
318
355
|
// 检查是否已存在
|
|
319
356
|
if (existsSync(targetPath)) {
|
|
@@ -366,8 +403,9 @@ export async function installFromGitUrl(gitUrl, options = {}) {
|
|
|
366
403
|
}
|
|
367
404
|
|
|
368
405
|
console.log(`\n✓ 技能已安装到: ${targetPath}`);
|
|
369
|
-
|
|
370
|
-
console.log(`
|
|
406
|
+
const metaLocation = global ? '~/.eskill/skills/.eskill-meta.json' : './.eskill-meta.json';
|
|
407
|
+
console.log(` 元数据位置: ${metaLocation}`);
|
|
408
|
+
console.log(` 说明: 技能永久保存,可提交到 Git`);
|
|
371
409
|
|
|
372
410
|
// 获取 git 版本信息
|
|
373
411
|
const commitHash = getGitCommitHash(sourcePath);
|
|
@@ -385,7 +423,7 @@ export async function installFromGitUrl(gitUrl, options = {}) {
|
|
|
385
423
|
version,
|
|
386
424
|
installedAt: new Date().toISOString(),
|
|
387
425
|
updatedAt: new Date().toISOString()
|
|
388
|
-
});
|
|
426
|
+
}, global);
|
|
389
427
|
|
|
390
428
|
return { success: true, path: targetPath };
|
|
391
429
|
} finally {
|
|
@@ -400,11 +438,11 @@ export async function installFromGitUrl(gitUrl, options = {}) {
|
|
|
400
438
|
/**
|
|
401
439
|
* 列出已安装的技能
|
|
402
440
|
*/
|
|
403
|
-
export function listSkills(agent = 'claude') {
|
|
441
|
+
export function listSkills(agent = 'claude', global = false) {
|
|
404
442
|
// 自动迁移旧位置的技能
|
|
405
|
-
migrateOldSkills();
|
|
443
|
+
migrateOldSkills(global);
|
|
406
444
|
|
|
407
|
-
const skillDir =
|
|
445
|
+
const skillDir = getSkillsDir(global);
|
|
408
446
|
|
|
409
447
|
if (!existsSync(skillDir)) {
|
|
410
448
|
return [];
|
|
@@ -415,7 +453,7 @@ export function listSkills(agent = 'claude') {
|
|
|
415
453
|
.filter(dirent => dirent.isDirectory());
|
|
416
454
|
|
|
417
455
|
// 读取所有元数据
|
|
418
|
-
const allMeta = getAllSkillsMeta();
|
|
456
|
+
const allMeta = getAllSkillsMeta(global);
|
|
419
457
|
|
|
420
458
|
return skillDirs.map(dirent => {
|
|
421
459
|
const skillPath = join(skillDir, dirent.name);
|
|
@@ -435,8 +473,8 @@ export function listSkills(agent = 'claude') {
|
|
|
435
473
|
/**
|
|
436
474
|
* 删除技能
|
|
437
475
|
*/
|
|
438
|
-
export function removeSkill(skillName, agent = 'claude') {
|
|
439
|
-
const skillDir =
|
|
476
|
+
export function removeSkill(skillName, agent = 'claude', global = false) {
|
|
477
|
+
const skillDir = getSkillsDir(global);
|
|
440
478
|
const targetPath = join(skillDir, skillName);
|
|
441
479
|
|
|
442
480
|
if (!existsSync(targetPath)) {
|
|
@@ -444,15 +482,15 @@ export function removeSkill(skillName, agent = 'claude') {
|
|
|
444
482
|
}
|
|
445
483
|
|
|
446
484
|
rmSync(targetPath, { recursive: true, force: true });
|
|
447
|
-
removeSkillMeta(skillName);
|
|
485
|
+
removeSkillMeta(skillName, global);
|
|
448
486
|
console.log(`✓ 已删除技能: ${skillName}`);
|
|
449
487
|
}
|
|
450
488
|
|
|
451
489
|
/**
|
|
452
490
|
* 清理所有技能(用于卸载)
|
|
453
491
|
*/
|
|
454
|
-
export function cleanupAllSkills() {
|
|
455
|
-
const skillDir =
|
|
492
|
+
export function cleanupAllSkills(global = false) {
|
|
493
|
+
const skillDir = getSkillsDir(global);
|
|
456
494
|
|
|
457
495
|
if (!existsSync(skillDir)) {
|
|
458
496
|
console.log('没有需要清理的技能');
|
|
@@ -490,10 +528,9 @@ export function cleanupAll() {
|
|
|
490
528
|
|
|
491
529
|
/**
|
|
492
530
|
* 获取技能存储目录路径
|
|
531
|
+
* 导出内部的 getSkillsDir 函数
|
|
493
532
|
*/
|
|
494
|
-
export
|
|
495
|
-
return getSkillsStorageDir();
|
|
496
|
-
}
|
|
533
|
+
export { getSkillsDir };
|
|
497
534
|
|
|
498
535
|
/**
|
|
499
536
|
* 获取远程仓库的 commit hash(不克隆)
|
|
@@ -516,8 +553,8 @@ function getRemoteCommitHash(gitUrl, branch) {
|
|
|
516
553
|
* 更新单个技能
|
|
517
554
|
*/
|
|
518
555
|
export async function updateSkill(skillName, options = {}) {
|
|
519
|
-
const { force = false } = options;
|
|
520
|
-
const skillDir =
|
|
556
|
+
const { force = false, global = false } = options;
|
|
557
|
+
const skillDir = getSkillsDir(global);
|
|
521
558
|
const targetPath = join(skillDir, skillName);
|
|
522
559
|
|
|
523
560
|
// 检查技能是否存在
|
|
@@ -526,7 +563,7 @@ export async function updateSkill(skillName, options = {}) {
|
|
|
526
563
|
}
|
|
527
564
|
|
|
528
565
|
// 读取元数据
|
|
529
|
-
const meta = getSkillMeta(skillName);
|
|
566
|
+
const meta = getSkillMeta(skillName, global);
|
|
530
567
|
|
|
531
568
|
// 如果没有 gitUrl,说明是本地技能,无法更新
|
|
532
569
|
if (!meta || !meta.gitUrl) {
|
|
@@ -599,7 +636,7 @@ export async function updateSkill(skillName, options = {}) {
|
|
|
599
636
|
commitHash: newCommitHash,
|
|
600
637
|
version: newVersion,
|
|
601
638
|
updatedAt: new Date().toISOString()
|
|
602
|
-
});
|
|
639
|
+
}, global);
|
|
603
640
|
|
|
604
641
|
return {
|
|
605
642
|
success: true,
|
|
@@ -620,12 +657,12 @@ export async function updateSkill(skillName, options = {}) {
|
|
|
620
657
|
* 更新所有技能
|
|
621
658
|
*/
|
|
622
659
|
export async function updateAllSkills(options = {}) {
|
|
623
|
-
const { force = false } = options;
|
|
660
|
+
const { force = false, global = false } = options;
|
|
624
661
|
|
|
625
662
|
// 自动迁移旧位置的技能
|
|
626
|
-
migrateOldSkills();
|
|
663
|
+
migrateOldSkills(global);
|
|
627
664
|
|
|
628
|
-
const skillDir =
|
|
665
|
+
const skillDir = getSkillsDir(global);
|
|
629
666
|
|
|
630
667
|
if (!existsSync(skillDir)) {
|
|
631
668
|
console.log('未安装任何技能');
|
|
@@ -637,7 +674,7 @@ export async function updateAllSkills(options = {}) {
|
|
|
637
674
|
.filter(dirent => dirent.isDirectory())
|
|
638
675
|
.map(dirent => {
|
|
639
676
|
const skillPath = join(skillDir, dirent.name);
|
|
640
|
-
const meta = getSkillMeta(dirent.name);
|
|
677
|
+
const meta = getSkillMeta(dirent.name, global);
|
|
641
678
|
return {
|
|
642
679
|
name: dirent.name,
|
|
643
680
|
path: skillPath,
|
|
@@ -668,7 +705,7 @@ export async function updateAllSkills(options = {}) {
|
|
|
668
705
|
}
|
|
669
706
|
|
|
670
707
|
// 更新技能
|
|
671
|
-
const result = await updateSkill(skill.name, { force });
|
|
708
|
+
const result = await updateSkill(skill.name, { force, global });
|
|
672
709
|
|
|
673
710
|
if (result.success && result.updated) {
|
|
674
711
|
console.log(`✓ ${skill.name} - ${result.currentVersion} → ${result.newVersion}`);
|