eskill 1.0.16 → 1.0.18
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 +40 -14
- package/lib/installer.js +95 -1
- package/package.json +1 -1
package/cli.js
CHANGED
|
@@ -25,13 +25,18 @@ program
|
|
|
25
25
|
.option('-l, --link', '使用符号链接而非复制', false)
|
|
26
26
|
.option('-f, --force', '强制覆盖已存在的技能', false)
|
|
27
27
|
.action(async (url, options) => {
|
|
28
|
-
const spinner = ora('正在安装技能...').start();
|
|
29
|
-
|
|
30
28
|
try {
|
|
31
|
-
await installFromGitUrl(url, options);
|
|
32
|
-
|
|
29
|
+
const result = await installFromGitUrl(url, options);
|
|
30
|
+
|
|
31
|
+
// 如果用户取消安装,正常退出
|
|
32
|
+
if (result && result.cancelled) {
|
|
33
|
+
process.exit(0);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// 其他情况显示成功消息
|
|
37
|
+
console.log('\n✓ 技能安装成功');
|
|
33
38
|
} catch (error) {
|
|
34
|
-
|
|
39
|
+
console.error(`\n❌ 安装失败: ${error.message}`);
|
|
35
40
|
process.exit(1);
|
|
36
41
|
}
|
|
37
42
|
});
|
|
@@ -130,15 +135,36 @@ program
|
|
|
130
135
|
|
|
131
136
|
console.log(formatSkillList(skills));
|
|
132
137
|
|
|
133
|
-
//
|
|
134
|
-
if (total > 0) {
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
console.log(
|
|
141
|
-
console.log(
|
|
138
|
+
// 显示分页信息和提示
|
|
139
|
+
if (total > 0 && skills.length > 0) {
|
|
140
|
+
const currentPage = pagination.page || parseInt(options.page);
|
|
141
|
+
const totalPages = pagination.totalPages || Math.ceil(total / skills.length);
|
|
142
|
+
const limit = pagination.limit || parseInt(options.limit);
|
|
143
|
+
|
|
144
|
+
// 分页信息
|
|
145
|
+
console.log(`┌─ 搜索结果 ──────────────────────────────────────────`);
|
|
146
|
+
console.log(`│ 总计: ${total} 个技能 | 当前: 第 ${currentPage}/${totalPages} 页 | 每页: ${limit} 个`);
|
|
147
|
+
console.log(`└────────────────────────────────────────────────────\n`);
|
|
148
|
+
|
|
149
|
+
// 安装提示(使用第一个结果作为示例)
|
|
150
|
+
const firstSkill = skills[0];
|
|
151
|
+
const skillName = firstSkill.name || firstSkill.title || 'skill-name';
|
|
152
|
+
const author = firstSkill.author || firstSkill.owner || 'author';
|
|
153
|
+
console.log(`💡 安装技能:`);
|
|
154
|
+
console.log(` eskill install ${skillName}@${author}\n`);
|
|
155
|
+
|
|
156
|
+
// 翻页提示
|
|
157
|
+
if (totalPages > 1) {
|
|
158
|
+
console.log(`📖 翻页:`);
|
|
159
|
+
if (currentPage < totalPages) {
|
|
160
|
+
console.log(` eskill search ${query} --page ${currentPage + 1}`);
|
|
161
|
+
console.log(` eskill search ${query} -p ${currentPage + 1} # 简写`);
|
|
162
|
+
}
|
|
163
|
+
if (currentPage > 1) {
|
|
164
|
+
console.log(` eskill search ${query} --page ${currentPage - 1} # 上一页`);
|
|
165
|
+
}
|
|
166
|
+
console.log(``);
|
|
167
|
+
}
|
|
142
168
|
}
|
|
143
169
|
} catch (error) {
|
|
144
170
|
spinner.fail(`搜索失败: ${error.message}`);
|
package/lib/installer.js
CHANGED
|
@@ -5,6 +5,8 @@ import { tmpdir } from 'os';
|
|
|
5
5
|
import { fileURLToPath } from 'url';
|
|
6
6
|
import { parseGitUrl } from './git-url-parser.js';
|
|
7
7
|
import { AGENTS, expandHomePath } from './agent-config.js';
|
|
8
|
+
import { searchSkills, formatSkillList } from './search.js';
|
|
9
|
+
import readline from 'readline';
|
|
8
10
|
|
|
9
11
|
/**
|
|
10
12
|
* 获取 eskill 包的安装目录
|
|
@@ -48,14 +50,106 @@ function getSkillMeta(skillPath) {
|
|
|
48
50
|
}
|
|
49
51
|
}
|
|
50
52
|
|
|
53
|
+
/**
|
|
54
|
+
* 检测并处理 name@author 格式
|
|
55
|
+
* 返回解析后的 GitHub URL,如果不是该格式则返回原 URL
|
|
56
|
+
*/
|
|
57
|
+
async function handleSkillAtAuthorFormat(input) {
|
|
58
|
+
// 检测是否为 name@author 格式
|
|
59
|
+
const match = input.match(/^([^@]+)@([^@]+)$/);
|
|
60
|
+
|
|
61
|
+
if (!match) {
|
|
62
|
+
// 不是该格式,直接返回原 URL
|
|
63
|
+
return input;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const [, skillName, author] = match;
|
|
67
|
+
console.log(`\n检测到技能名称格式: ${skillName}@${author}`);
|
|
68
|
+
console.log(`正在搜索 "${skillName}"...\n`);
|
|
69
|
+
|
|
70
|
+
try {
|
|
71
|
+
// 搜索技能
|
|
72
|
+
const result = await searchSkills(skillName, { limit: 100, sortBy: 'stars' });
|
|
73
|
+
|
|
74
|
+
if (result.success === false) {
|
|
75
|
+
throw new Error(result.error?.message || '搜索失败');
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const skills = result.data?.skills || result.data || result.skills || result.results || [];
|
|
79
|
+
|
|
80
|
+
// 筛选出匹配作者的技能
|
|
81
|
+
const matchedSkills = skills.filter(skill => {
|
|
82
|
+
const skillAuthor = skill.author || skill.owner || '';
|
|
83
|
+
return skillAuthor.toLowerCase() === author.toLowerCase();
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
if (matchedSkills.length === 0) {
|
|
87
|
+
// 没有找到匹配的作者
|
|
88
|
+
console.log(`❌ 未找到作者 "${author}" 的技能 "${skillName}"\n`);
|
|
89
|
+
console.log('搜索结果如下:\n');
|
|
90
|
+
console.log(formatSkillList(skills));
|
|
91
|
+
throw new Error(`未找到匹配的技能`);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// 找到匹配的技能
|
|
95
|
+
const matchedSkill = matchedSkills[0];
|
|
96
|
+
const githubUrl = matchedSkill.githubUrl || matchedSkill.github_url || matchedSkill.url;
|
|
97
|
+
|
|
98
|
+
if (!githubUrl) {
|
|
99
|
+
throw new Error('技能信息中没有 GitHub URL');
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
console.log(`\n✓ 找到匹配的技能:`);
|
|
103
|
+
console.log(` 名称: ${skillName}@${author}`);
|
|
104
|
+
console.log(` Stars: ⭐ ${matchedSkill.stars || 'N/A'}`);
|
|
105
|
+
console.log(` GitHub: ${githubUrl}\n`);
|
|
106
|
+
|
|
107
|
+
return githubUrl;
|
|
108
|
+
} catch (error) {
|
|
109
|
+
if (error.message === '未找到匹配的技能') {
|
|
110
|
+
throw error;
|
|
111
|
+
}
|
|
112
|
+
throw new Error(`搜索失败: ${error.message}`);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* 等待用户确认
|
|
118
|
+
*/
|
|
119
|
+
function confirmAction(message) {
|
|
120
|
+
return new Promise((resolve) => {
|
|
121
|
+
const rl = readline.createInterface({
|
|
122
|
+
input: process.stdin,
|
|
123
|
+
output: process.stdout
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
rl.question(`${message} (y/N): `, (answer) => {
|
|
127
|
+
rl.close();
|
|
128
|
+
resolve(answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes');
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
|
|
51
133
|
/**
|
|
52
134
|
* 从 Git URL 安装技能
|
|
53
135
|
*/
|
|
54
136
|
export async function installFromGitUrl(gitUrl, options = {}) {
|
|
55
137
|
const { agent = 'claude', link = false, force = false } = options;
|
|
56
138
|
|
|
139
|
+
// 检测并处理 name@author 格式
|
|
140
|
+
let actualUrl = await handleSkillAtAuthorFormat(gitUrl);
|
|
141
|
+
|
|
142
|
+
// 如果 URL 被转换了(即原来是 name@author 格式),需要确认
|
|
143
|
+
if (actualUrl !== gitUrl) {
|
|
144
|
+
const confirmed = await confirmAction('是否安装此技能?');
|
|
145
|
+
if (!confirmed) {
|
|
146
|
+
console.log('\n已取消安装');
|
|
147
|
+
return { success: false, cancelled: true };
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
57
151
|
// 解析 Git URL
|
|
58
|
-
const parsed = parseGitUrl(
|
|
152
|
+
const parsed = parseGitUrl(actualUrl);
|
|
59
153
|
console.log(`平台: ${parsed.platform}`);
|
|
60
154
|
console.log(`仓库: ${parsed.owner}/${parsed.repo}`);
|
|
61
155
|
console.log(`分支: ${parsed.branch}`);
|