skill-market-cli 1.0.1 → 1.1.0
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/examples/test-skill.yaml +78 -0
- package/package.json +3 -3
- package/src/api/client.js +6 -2
- package/src/auth/oauth.js +161 -113
- package/src/auth/token-store.js +1 -1
- package/src/commands/login.js +4 -5
- package/src/commands/logout.js +5 -5
- package/src/commands/run-example.js +1 -56
- package/src/commands/update.js +36 -7
- package/src/commands/upload.js +229 -105
- package/src/config/server-modes.js +43 -0
- package/src/index.js +42 -13
- package/src/lib/run-example-collect.js +44 -0
- package/src/lib/skill-upload-helpers.js +77 -0
- package/src/skills/skill-market-upload/SKILL.md +53 -0
package/src/commands/update.js
CHANGED
|
@@ -28,6 +28,7 @@ async function update(skillId, options) {
|
|
|
28
28
|
let description = options.description || existingSkill.purpose;
|
|
29
29
|
let tags = options.tags ? options.tags.split(',').map(t => t.trim()) : existingSkill.tags;
|
|
30
30
|
let usageExamples = existingSkill.usageExamples || [];
|
|
31
|
+
let model = options.model || existingSkill.model;
|
|
31
32
|
|
|
32
33
|
// 如果提供了文件,读取新的 SKILL.md
|
|
33
34
|
if (options.file) {
|
|
@@ -79,15 +80,43 @@ async function update(skillId, options) {
|
|
|
79
80
|
return;
|
|
80
81
|
}
|
|
81
82
|
|
|
82
|
-
//
|
|
83
|
-
console.log(chalk.blue('\n
|
|
83
|
+
// 执行更新(走后端 /skill/ai/update:全字段非空、tags 与 usageExamples 不可为空数组)
|
|
84
|
+
console.log(chalk.blue('\n正在更新…\n'));
|
|
85
|
+
|
|
86
|
+
const tagsFinal =
|
|
87
|
+
Array.isArray(tags) && tags.length > 0
|
|
88
|
+
? tags.map((t) => String(t).trim()).filter(Boolean)
|
|
89
|
+
: ['general'];
|
|
90
|
+
const modelFinal =
|
|
91
|
+
model && String(model).trim() ? String(model).trim() : 'deepseek-chat';
|
|
92
|
+
const rootUrlFinal =
|
|
93
|
+
existingSkill.rootUrl && String(existingSkill.rootUrl).trim()
|
|
94
|
+
? String(existingSkill.rootUrl).trim()
|
|
95
|
+
: 'https://example.com/SKILL.md';
|
|
96
|
+
|
|
97
|
+
if (!usageExamples || usageExamples.length === 0) {
|
|
98
|
+
console.error(
|
|
99
|
+
chalk.red(
|
|
100
|
+
'当前 Skill 没有用法示例(usageExamples),AI 渠道更新要求至少一条示例。请先在网页中补充后再试。'
|
|
101
|
+
)
|
|
102
|
+
);
|
|
103
|
+
process.exit(1);
|
|
104
|
+
}
|
|
105
|
+
const examplesOk = usageExamples.every(
|
|
106
|
+
(ex) => ex && String(ex.prompt || '').trim()
|
|
107
|
+
);
|
|
108
|
+
if (!examplesOk) {
|
|
109
|
+
console.error(chalk.red('存在空的用法示例 prompt,请修正后再试。'));
|
|
110
|
+
process.exit(1);
|
|
111
|
+
}
|
|
84
112
|
|
|
85
113
|
const data = {
|
|
86
|
-
name,
|
|
87
|
-
purpose: description,
|
|
88
|
-
rootUrl:
|
|
89
|
-
tags,
|
|
90
|
-
usageExamples
|
|
114
|
+
name: String(name).trim(),
|
|
115
|
+
purpose: String(description).trim(),
|
|
116
|
+
rootUrl: rootUrlFinal,
|
|
117
|
+
tags: tagsFinal,
|
|
118
|
+
usageExamples,
|
|
119
|
+
model: modelFinal
|
|
91
120
|
};
|
|
92
121
|
|
|
93
122
|
const response = await apiClient.updateSkill(skillId, data);
|
package/src/commands/upload.js
CHANGED
|
@@ -2,174 +2,298 @@ const fs = require('fs-extra');
|
|
|
2
2
|
const path = require('path');
|
|
3
3
|
const chalk = require('chalk');
|
|
4
4
|
const inquirer = require('inquirer');
|
|
5
|
-
const YAML = require('yaml');
|
|
6
5
|
const { isLoggedIn } = require('../auth/token-store');
|
|
7
6
|
const apiClient = require('../api/client');
|
|
7
|
+
const { runExampleAndCollect } = require('../lib/run-example-collect');
|
|
8
|
+
const {
|
|
9
|
+
parseSkillMarkdown,
|
|
10
|
+
loadDotSkillExamples,
|
|
11
|
+
promptOnlyExamples
|
|
12
|
+
} = require('../lib/skill-upload-helpers');
|
|
8
13
|
|
|
9
|
-
|
|
14
|
+
/**
|
|
15
|
+
* 交互补全:名称、描述、标签、模型、rootUrl、用户案例 + 可选运行采集轨迹
|
|
16
|
+
*/
|
|
17
|
+
async function upload(skillPath, options = {}) {
|
|
10
18
|
if (!isLoggedIn()) {
|
|
11
|
-
console.error(chalk.red('
|
|
19
|
+
console.error(chalk.red('请先登录:skill-market-cli login\n'));
|
|
12
20
|
process.exit(1);
|
|
13
21
|
}
|
|
14
22
|
|
|
15
|
-
// 检查路径
|
|
16
23
|
if (!fs.existsSync(skillPath)) {
|
|
17
|
-
console.error(chalk.red(
|
|
24
|
+
console.error(chalk.red(`路径不存在:${skillPath}`));
|
|
18
25
|
process.exit(1);
|
|
19
26
|
}
|
|
20
27
|
|
|
21
|
-
// 确定 SKILL.md 文件路径
|
|
22
28
|
let skillFilePath;
|
|
23
29
|
const stats = fs.statSync(skillPath);
|
|
24
|
-
|
|
25
30
|
if (stats.isDirectory()) {
|
|
26
31
|
skillFilePath = path.join(skillPath, 'SKILL.md');
|
|
27
32
|
if (!fs.existsSync(skillFilePath)) {
|
|
28
|
-
console.error(chalk.red(
|
|
33
|
+
console.error(chalk.red(`目录中未找到 SKILL.md:${skillPath}`));
|
|
29
34
|
process.exit(1);
|
|
30
35
|
}
|
|
31
36
|
} else {
|
|
32
37
|
skillFilePath = skillPath;
|
|
33
38
|
}
|
|
34
39
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
40
|
+
const skillDir = path.dirname(skillFilePath);
|
|
41
|
+
console.log(chalk.gray(`读取:${skillFilePath}\n`));
|
|
42
|
+
|
|
38
43
|
const skillContent = fs.readFileSync(skillFilePath, 'utf-8');
|
|
39
|
-
const { frontmatter, examples } =
|
|
44
|
+
const { frontmatter, examples: examplesFromMd } = parseSkillMarkdown(skillContent);
|
|
40
45
|
|
|
41
|
-
// 收集信息
|
|
42
46
|
let name = options.name || frontmatter?.name;
|
|
43
|
-
let description =
|
|
44
|
-
|
|
47
|
+
let description =
|
|
48
|
+
options.description || frontmatter?.purpose || frontmatter?.description;
|
|
49
|
+
let tags = options.tags
|
|
50
|
+
? options.tags.split(',').map((t) => t.trim())
|
|
51
|
+
: frontmatter?.tags || [];
|
|
45
52
|
let model = options.model || frontmatter?.model;
|
|
46
53
|
let rootUrl = frontmatter?.rootUrl;
|
|
47
54
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
if (!
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
55
|
+
const fromJson = loadDotSkillExamples(skillDir);
|
|
56
|
+
let usageExamples = fromJson;
|
|
57
|
+
|
|
58
|
+
if (!usageExamples || usageExamples.length === 0) {
|
|
59
|
+
const raw = promptOnlyExamples(examplesFromMd);
|
|
60
|
+
if (raw.length > 0) {
|
|
61
|
+
usageExamples = raw.map((ex) => ({
|
|
62
|
+
prompt: ex.prompt,
|
|
63
|
+
aiResponses: ex.aiResponses || [],
|
|
64
|
+
model: ex.model || model || ''
|
|
65
|
+
}));
|
|
66
|
+
}
|
|
58
67
|
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
68
|
+
|
|
69
|
+
const needName = !name || !String(name).trim();
|
|
70
|
+
const needDesc = !description || !String(description).trim();
|
|
71
|
+
if (needName || needDesc) {
|
|
72
|
+
const answers = await inquirer.prompt(
|
|
73
|
+
[
|
|
74
|
+
needName && {
|
|
75
|
+
type: 'input',
|
|
76
|
+
name: 'name',
|
|
77
|
+
message: 'Skill 名称(必填):',
|
|
78
|
+
validate: (input) => (input && String(input).trim() ? true : '不能为空')
|
|
79
|
+
},
|
|
80
|
+
needDesc && {
|
|
81
|
+
type: 'input',
|
|
82
|
+
name: 'description',
|
|
83
|
+
message: '用途 / 描述(必填):',
|
|
84
|
+
validate: (input) => (input && String(input).trim() ? true : '不能为空')
|
|
85
|
+
}
|
|
86
|
+
].filter(Boolean)
|
|
87
|
+
);
|
|
88
|
+
if (answers.name) name = answers.name;
|
|
89
|
+
if (answers.description) description = answers.description;
|
|
67
90
|
}
|
|
68
91
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
92
|
+
// 模型默认值(用于采集与提交)
|
|
93
|
+
if (!model || !String(model).trim()) {
|
|
94
|
+
const { m } = await inquirer.prompt([
|
|
95
|
+
{
|
|
96
|
+
type: 'input',
|
|
97
|
+
name: 'm',
|
|
98
|
+
message: '推荐模型(用于案例采集与提交,建议与线上一致):',
|
|
99
|
+
default: 'deepseek-chat'
|
|
100
|
+
}
|
|
101
|
+
]);
|
|
102
|
+
model = m || 'deepseek-chat';
|
|
73
103
|
}
|
|
74
104
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
105
|
+
const modelFinal = String(model).trim();
|
|
106
|
+
|
|
107
|
+
// 必须有至少一条「用户案例」且含轨迹:若无则交互式收集
|
|
108
|
+
usageExamples = await ensureUsageExamplesWithTrace({
|
|
109
|
+
initial: usageExamples,
|
|
110
|
+
model: modelFinal
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
if (!tags || tags.length === 0) {
|
|
114
|
+
const { tagStr } = await inquirer.prompt([
|
|
115
|
+
{
|
|
116
|
+
type: 'input',
|
|
117
|
+
name: 'tagStr',
|
|
118
|
+
message: '标签(逗号分隔,至少一个):',
|
|
119
|
+
default: 'general',
|
|
120
|
+
validate: (input) =>
|
|
121
|
+
input && String(input).trim() ? true : '至少填写一个标签'
|
|
122
|
+
}
|
|
123
|
+
]);
|
|
124
|
+
tags = tagStr.split(',').map((t) => t.trim()).filter(Boolean);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (!rootUrl || !String(rootUrl).trim()) {
|
|
128
|
+
const { ru } = await inquirer.prompt([
|
|
129
|
+
{
|
|
130
|
+
type: 'input',
|
|
131
|
+
name: 'ru',
|
|
132
|
+
message: 'SKILL 根资源 URL(可填 GitHub raw 或本地文件):',
|
|
133
|
+
default: `file://${path.resolve(skillFilePath)}`
|
|
134
|
+
}
|
|
135
|
+
]);
|
|
136
|
+
rootUrl = ru || `file://${path.resolve(skillFilePath)}`;
|
|
90
137
|
}
|
|
91
138
|
|
|
92
|
-
|
|
93
|
-
console.log(chalk.
|
|
94
|
-
console.log(
|
|
95
|
-
console.log(
|
|
96
|
-
console.log(
|
|
97
|
-
console.log(`
|
|
98
|
-
console.log(
|
|
99
|
-
console.log();
|
|
139
|
+
console.log(chalk.gray('\n--- 上传摘要 ---'));
|
|
140
|
+
console.log(`名称:${chalk.bold(name)}`);
|
|
141
|
+
console.log(`描述:${description}`);
|
|
142
|
+
console.log(`标签:${tags.join(', ')}`);
|
|
143
|
+
console.log(`模型:${modelFinal}`);
|
|
144
|
+
console.log(`rootUrl:${rootUrl}`);
|
|
145
|
+
console.log(`案例条数:${usageExamples.length}(每条含 prompt + 轨迹)`);
|
|
146
|
+
console.log('');
|
|
100
147
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
148
|
+
let confirm = options.yes === true;
|
|
149
|
+
if (!confirm) {
|
|
150
|
+
const ans = await inquirer.prompt([
|
|
151
|
+
{
|
|
152
|
+
type: 'confirm',
|
|
153
|
+
name: 'confirm',
|
|
154
|
+
message: '确认提交到 Skill Market?',
|
|
155
|
+
default: true
|
|
156
|
+
}
|
|
157
|
+
]);
|
|
158
|
+
confirm = ans.confirm;
|
|
159
|
+
}
|
|
107
160
|
|
|
108
161
|
if (!confirm) {
|
|
109
|
-
console.log(chalk.yellow('
|
|
162
|
+
console.log(chalk.yellow('已取消上传。\n'));
|
|
110
163
|
return;
|
|
111
164
|
}
|
|
112
165
|
|
|
113
|
-
//
|
|
166
|
+
// 写入 .skill-examples.json 便于复查与再次上传
|
|
167
|
+
try {
|
|
168
|
+
const outPath = path.join(skillDir, '.skill-examples.json');
|
|
169
|
+
fs.writeJsonSync(
|
|
170
|
+
outPath,
|
|
171
|
+
{
|
|
172
|
+
model: modelFinal,
|
|
173
|
+
examples: usageExamples.map((e) => ({
|
|
174
|
+
prompt: e.prompt,
|
|
175
|
+
aiResponses: e.aiResponses,
|
|
176
|
+
model: e.model || modelFinal
|
|
177
|
+
}))
|
|
178
|
+
},
|
|
179
|
+
{ spaces: 2 }
|
|
180
|
+
);
|
|
181
|
+
console.log(chalk.gray(`已保存本地示例与轨迹:${outPath}`));
|
|
182
|
+
} catch {
|
|
183
|
+
// ignore
|
|
184
|
+
}
|
|
185
|
+
|
|
114
186
|
try {
|
|
115
|
-
console.log(chalk.
|
|
187
|
+
console.log(chalk.gray('\n正在上传…\n'));
|
|
116
188
|
|
|
189
|
+
const tagsFinal = tags.map((t) => String(t).trim()).filter(Boolean);
|
|
117
190
|
const data = {
|
|
118
|
-
name,
|
|
119
|
-
purpose: description,
|
|
120
|
-
rootUrl: rootUrl
|
|
121
|
-
tags,
|
|
122
|
-
usageExamples
|
|
123
|
-
model
|
|
191
|
+
name: String(name).trim(),
|
|
192
|
+
purpose: String(description).trim(),
|
|
193
|
+
rootUrl: String(rootUrl).trim(),
|
|
194
|
+
tags: tagsFinal,
|
|
195
|
+
usageExamples,
|
|
196
|
+
model: modelFinal
|
|
124
197
|
};
|
|
125
198
|
|
|
126
199
|
const response = await apiClient.uploadSkill(data);
|
|
127
200
|
|
|
128
201
|
if (response.code === 200) {
|
|
129
|
-
console.log(chalk.green('
|
|
130
|
-
console.log(chalk.
|
|
131
|
-
console.log();
|
|
202
|
+
console.log(chalk.green('上传成功'));
|
|
203
|
+
console.log(chalk.gray(`Skill ID:${response.data.id}`));
|
|
204
|
+
console.log('');
|
|
132
205
|
} else {
|
|
133
|
-
console.error(chalk.red('
|
|
206
|
+
console.error(chalk.red('上传失败:'), response.data || '未知错误');
|
|
134
207
|
process.exit(1);
|
|
135
208
|
}
|
|
136
209
|
} catch (error) {
|
|
137
|
-
console.error(chalk.red('
|
|
210
|
+
console.error(chalk.red('上传出错:'), error.message);
|
|
138
211
|
process.exit(1);
|
|
139
212
|
}
|
|
140
213
|
}
|
|
141
214
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
const
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
215
|
+
/**
|
|
216
|
+
* 保证至少一条案例;若仅有 prompt 无轨迹,则询问是否运行采集
|
|
217
|
+
*/
|
|
218
|
+
async function ensureUsageExamplesWithTrace({ initial, model }) {
|
|
219
|
+
let list = Array.isArray(initial) ? [...initial] : [];
|
|
220
|
+
|
|
221
|
+
const hasTrace = (ex) =>
|
|
222
|
+
ex &&
|
|
223
|
+
ex.prompt &&
|
|
224
|
+
String(ex.prompt).trim() &&
|
|
225
|
+
Array.isArray(ex.aiResponses) &&
|
|
226
|
+
ex.aiResponses.length > 0;
|
|
227
|
+
|
|
228
|
+
const valid = list.filter((ex) => ex && String(ex.prompt || '').trim());
|
|
229
|
+
const allHaveTrace = valid.length > 0 && valid.every(hasTrace);
|
|
230
|
+
|
|
231
|
+
if (allHaveTrace) {
|
|
232
|
+
return valid.map((ex) => ({
|
|
233
|
+
prompt: String(ex.prompt).trim(),
|
|
234
|
+
aiResponses: ex.aiResponses,
|
|
235
|
+
model: ex.model || model
|
|
236
|
+
}));
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
if (valid.length > 0 && !allHaveTrace) {
|
|
240
|
+
console.log(chalk.gray('检测到 SKILL.md 中已有案例文本,但缺少轨迹。将逐条运行采集。\n'));
|
|
241
|
+
const out = [];
|
|
242
|
+
for (const ex of valid) {
|
|
243
|
+
const trace = await runExampleAndCollect(ex.prompt, model);
|
|
244
|
+
out.push({
|
|
245
|
+
prompt: String(ex.prompt).trim(),
|
|
246
|
+
aiResponses: trace,
|
|
247
|
+
model
|
|
248
|
+
});
|
|
154
249
|
}
|
|
250
|
+
return out;
|
|
155
251
|
}
|
|
156
252
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
253
|
+
console.log(
|
|
254
|
+
chalk.yellow(
|
|
255
|
+
'上传至 Skill Market 需要至少一条「用户案例」及对应采集轨迹(thinking / toolcall / message)。\n'
|
|
256
|
+
)
|
|
257
|
+
);
|
|
258
|
+
|
|
259
|
+
const collected = [];
|
|
260
|
+
let addMore = true;
|
|
261
|
+
while (addMore) {
|
|
262
|
+
const { promptText } = await inquirer.prompt([
|
|
263
|
+
{
|
|
264
|
+
type: 'input',
|
|
265
|
+
name: 'promptText',
|
|
266
|
+
message: '请输入一条用户测试案例(终端可多行请用 \\n 分段,或分多次添加):',
|
|
267
|
+
validate: (input) =>
|
|
268
|
+
input && String(input).trim() ? true : '案例内容不能为空'
|
|
269
|
+
}
|
|
270
|
+
]);
|
|
271
|
+
|
|
272
|
+
const aiResponses = await runExampleAndCollect(promptText.trim(), model);
|
|
273
|
+
|
|
274
|
+
collected.push({
|
|
275
|
+
prompt: promptText.trim(),
|
|
276
|
+
aiResponses,
|
|
277
|
+
model
|
|
169
278
|
});
|
|
279
|
+
|
|
280
|
+
const { again } = await inquirer.prompt([
|
|
281
|
+
{
|
|
282
|
+
type: 'confirm',
|
|
283
|
+
name: 'again',
|
|
284
|
+
message: '是否再添加一条用户案例?',
|
|
285
|
+
default: false
|
|
286
|
+
}
|
|
287
|
+
]);
|
|
288
|
+
addMore = again;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
if (collected.length === 0) {
|
|
292
|
+
console.error(chalk.red('未提供任何用户案例,无法上传。'));
|
|
293
|
+
process.exit(1);
|
|
170
294
|
}
|
|
171
295
|
|
|
172
|
-
return
|
|
296
|
+
return collected;
|
|
173
297
|
}
|
|
174
298
|
|
|
175
299
|
module.exports = upload;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
const { setServerConfig, getConfig, saveConfig } = require('../auth/token-store');
|
|
2
|
+
|
|
3
|
+
const SERVER_MODES = {
|
|
4
|
+
production: {
|
|
5
|
+
baseURL: 'https://kirigaya.cn',
|
|
6
|
+
apiBase: 'https://kirigaya.cn/api'
|
|
7
|
+
},
|
|
8
|
+
development: {
|
|
9
|
+
baseURL: 'http://localhost:8080',
|
|
10
|
+
apiBase: 'http://localhost:8080/api'
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
function normalizeMode(raw) {
|
|
15
|
+
if (raw == null || raw === '') return 'production';
|
|
16
|
+
const m = String(raw).toLowerCase().trim();
|
|
17
|
+
if (m === 'production' || m === 'prod') return 'production';
|
|
18
|
+
if (m === 'development' || m === 'dev') return 'development';
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function applyServerMode(modeRaw) {
|
|
23
|
+
const mode = normalizeMode(modeRaw);
|
|
24
|
+
if (!mode) {
|
|
25
|
+
throw new Error(`无效的 --mode:「${modeRaw}」。请使用 production 或 development。`);
|
|
26
|
+
}
|
|
27
|
+
setServerConfig(SERVER_MODES[mode]);
|
|
28
|
+
const config = getConfig();
|
|
29
|
+
config.mode = mode;
|
|
30
|
+
saveConfig(config);
|
|
31
|
+
return mode;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function getServerModesHelp() {
|
|
35
|
+
return 'production (https://kirigaya.cn) | development (http://localhost:8080)';
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
module.exports = {
|
|
39
|
+
SERVER_MODES,
|
|
40
|
+
normalizeMode,
|
|
41
|
+
applyServerMode,
|
|
42
|
+
getServerModesHelp
|
|
43
|
+
};
|
package/src/index.js
CHANGED
|
@@ -10,33 +10,61 @@ const update = require('./commands/update');
|
|
|
10
10
|
const remove = require('./commands/delete');
|
|
11
11
|
const runExample = require('./commands/run-example');
|
|
12
12
|
const { getConfig } = require('./auth/token-store');
|
|
13
|
+
const { applyServerMode, getServerModesHelp } = require('./config/server-modes');
|
|
14
|
+
const apiClient = require('./api/client');
|
|
13
15
|
|
|
14
16
|
const program = new Command();
|
|
15
17
|
|
|
16
18
|
program
|
|
17
19
|
.name('skill-market-cli')
|
|
18
|
-
.description(
|
|
20
|
+
.description(`Skill Market 命令行:管理技能与登录
|
|
21
|
+
|
|
22
|
+
全局网络(对所有子命令生效):
|
|
23
|
+
--mode <环境> ${getServerModesHelp()}
|
|
24
|
+
未指定时优先使用 ~/.skill-market-cli/config.json 中保存的环境(登录后写入),否则为 production。
|
|
25
|
+
|
|
26
|
+
示例:
|
|
27
|
+
skill-market-cli --mode development login
|
|
28
|
+
skill-market-cli login --mode development
|
|
29
|
+
node bin/skill-market-cli.js list --mode development`)
|
|
19
30
|
.version(pkg.version, '-v, --version')
|
|
20
31
|
.option('-c, --config <path>', 'config file path')
|
|
21
|
-
.
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
32
|
+
.option('--mode <environment>', getServerModesHelp());
|
|
33
|
+
|
|
34
|
+
program.hook('preAction', () => {
|
|
35
|
+
const opts = program.opts();
|
|
36
|
+
const config = getConfig();
|
|
37
|
+
const effectiveMode =
|
|
38
|
+
opts.mode !== undefined && opts.mode !== null && opts.mode !== ''
|
|
39
|
+
? opts.mode
|
|
40
|
+
: (config.mode || 'production');
|
|
41
|
+
try {
|
|
42
|
+
applyServerMode(effectiveMode);
|
|
43
|
+
apiClient.reinit();
|
|
44
|
+
} catch (e) {
|
|
45
|
+
console.error(chalk.red(e.message));
|
|
46
|
+
process.exit(1);
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
program.hook('preAction', (thisCommand) => {
|
|
51
|
+
const config = getConfig();
|
|
52
|
+
if (config.user && thisCommand.args[0] !== 'login') {
|
|
53
|
+
console.log(chalk.gray(`已登录:${config.user.name}`));
|
|
54
|
+
}
|
|
55
|
+
});
|
|
28
56
|
|
|
29
57
|
// Login command
|
|
30
58
|
program
|
|
31
59
|
.command('login')
|
|
32
|
-
.description('
|
|
33
|
-
.option('--no-open', '
|
|
60
|
+
.description('浏览器 OAuth 登录(使用全局 --mode 指向的环境)')
|
|
61
|
+
.option('--no-open', '不自动打开浏览器,仅打印授权链接')
|
|
34
62
|
.action(login);
|
|
35
63
|
|
|
36
64
|
// Logout command
|
|
37
65
|
program
|
|
38
66
|
.command('logout')
|
|
39
|
-
.description('
|
|
67
|
+
.description('清除本地登录状态并尝试撤销服务端令牌')
|
|
40
68
|
.action(logout);
|
|
41
69
|
|
|
42
70
|
// List command
|
|
@@ -54,11 +82,12 @@ program
|
|
|
54
82
|
program
|
|
55
83
|
.command('upload <path>')
|
|
56
84
|
.alias('up')
|
|
57
|
-
.description('
|
|
85
|
+
.description('上传 Skill(交互补全字段;用户案例 + 自动采集轨迹后提交 AI 渠道)')
|
|
58
86
|
.option('-n, --name <name>', 'Skill name')
|
|
59
87
|
.option('-d, --description <desc>', 'Skill description/purpose')
|
|
60
88
|
.option('-t, --tags <tags>', 'Tags (comma separated)')
|
|
61
89
|
.option('-m, --model <model>', 'Recommended model')
|
|
90
|
+
.option('-y, --yes', '非交互:跳过最终确认,适合脚本/CI')
|
|
62
91
|
.action(upload);
|
|
63
92
|
|
|
64
93
|
// Update command
|
|
@@ -100,7 +129,7 @@ program
|
|
|
100
129
|
if (fs.existsSync(guidePath)) {
|
|
101
130
|
console.log(fs.readFileSync(guidePath, 'utf-8'));
|
|
102
131
|
} else {
|
|
103
|
-
console.log(chalk.yellow('
|
|
132
|
+
console.log(chalk.yellow('未找到本地指南,请访问:https://kirigaya.cn/ktools/skillmanager'));
|
|
104
133
|
}
|
|
105
134
|
});
|
|
106
135
|
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
const chalk = require('chalk');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 对用户 prompt 执行一次「示例运行」,采集 AI 侧轨迹(thinking / toolcall / message)。
|
|
5
|
+
* 生产环境可替换为真实模型 API;当前为可预测的模拟数据,便于联调。
|
|
6
|
+
*/
|
|
7
|
+
async function runExampleAndCollect(prompt, model) {
|
|
8
|
+
const modelLabel = model && String(model).trim() ? String(model).trim() : 'default';
|
|
9
|
+
|
|
10
|
+
console.log(chalk.gray('\n正在运行用户案例以采集轨迹与输出…'));
|
|
11
|
+
console.log(chalk.gray(`推荐模型:${modelLabel}`));
|
|
12
|
+
|
|
13
|
+
const simulatedResponses = [
|
|
14
|
+
{
|
|
15
|
+
type: 'thinking',
|
|
16
|
+
content: `分析用户请求:「${prompt}」。将拆解步骤并调用合适工具完成目标。`
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
type: 'toolcall',
|
|
20
|
+
toolName: 'read_file',
|
|
21
|
+
toolInput: { path: './README.md' }
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
type: 'toolcall',
|
|
25
|
+
toolName: 'write_file',
|
|
26
|
+
toolInput: {
|
|
27
|
+
path: './output.txt',
|
|
28
|
+
content: `已处理:${prompt}`
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
type: 'message',
|
|
33
|
+
content: `已根据你的请求完成处理:「${prompt}」。\n\n步骤摘要:\n1. 理解需求\n2. 读取上下文\n3. 生成结果\n\n以上为演示轨迹,上传时将一并提交。`
|
|
34
|
+
}
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
await new Promise((resolve) => setTimeout(resolve, 400));
|
|
38
|
+
|
|
39
|
+
console.log(chalk.gray(`已采集 ${simulatedResponses.length} 条轨迹节点(thinking / toolcall / message)。`));
|
|
40
|
+
|
|
41
|
+
return simulatedResponses;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
module.exports = { runExampleAndCollect };
|