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.
@@ -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📤 Updating...\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: existingSkill.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);
@@ -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
- async function upload(skillPath, options) {
14
+ /**
15
+ * 交互补全:名称、描述、标签、模型、rootUrl、用户案例 + 可选运行采集轨迹
16
+ */
17
+ async function upload(skillPath, options = {}) {
10
18
  if (!isLoggedIn()) {
11
- console.error(chalk.red('❌ Please login first: skill-market-cli login\n'));
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(`❌ Path not found: ${skillPath}`));
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(`❌ SKILL.md not found in directory: ${skillPath}`));
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
- console.log(chalk.blue('📖 Reading SKILL.md...\n'));
36
-
37
- // 读取并解析 SKILL.md
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 } = parseSkillFile(skillContent);
44
+ const { frontmatter, examples: examplesFromMd } = parseSkillMarkdown(skillContent);
40
45
 
41
- // 收集信息
42
46
  let name = options.name || frontmatter?.name;
43
- let description = options.description || frontmatter?.purpose || frontmatter?.description;
44
- let tags = options.tags ? options.tags.split(',').map(t => t.trim()) : (frontmatter?.tags || []);
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
- const questions = [];
50
-
51
- if (!name) {
52
- questions.push({
53
- type: 'input',
54
- name: 'name',
55
- message: 'Skill name:',
56
- validate: input => input.length > 0 || 'Name is required'
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
- if (!description) {
61
- questions.push({
62
- type: 'input',
63
- name: 'description',
64
- message: 'Purpose/Description:',
65
- validate: input => input.length > 0 || 'Description is required'
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
- if (questions.length > 0) {
70
- const answers = await inquirer.prompt(questions);
71
- name = name || answers.name;
72
- description = description || answers.description;
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
- // 如果没有 examples,询问是否运行案例采集
76
- let usageExamples = examples;
77
- if (!usageExamples || usageExamples.length === 0) {
78
- const { runExamples } = await inquirer.prompt([{
79
- type: 'confirm',
80
- name: 'runExamples',
81
- message: 'No usage examples found. Do you want to run example collection now?',
82
- default: true
83
- }]);
84
-
85
- if (runExamples) {
86
- console.log(chalk.gray('\nPlease run: skill-market-cli run-example ' + skillPath));
87
- console.log(chalk.gray('Then upload again with the collected examples.\n'));
88
- return;
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.gray('\n--- Upload Summary ---'));
94
- console.log(`Name: ${chalk.bold(name)}`);
95
- console.log(`Description: ${description}`);
96
- console.log(`Tags: ${tags.join(', ') || 'none'}`);
97
- console.log(`Model: ${model || 'not specified'}`);
98
- console.log(`Examples: ${usageExamples?.length || 0}`);
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
- const { confirm } = await inquirer.prompt([{
102
- type: 'confirm',
103
- name: 'confirm',
104
- message: 'Upload this skill?',
105
- default: true
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('Upload cancelled.\n'));
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.blue('\n📤 Uploading...\n'));
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: 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('✅ Skill uploaded successfully!'));
130
- console.log(chalk.cyan(`📝 Skill ID: ${response.data.id}`));
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('❌ Upload failed:'), response.data || 'Unknown error');
206
+ console.error(chalk.red('上传失败:'), response.data || '未知错误');
134
207
  process.exit(1);
135
208
  }
136
209
  } catch (error) {
137
- console.error(chalk.red('❌ Upload error:'), error.message);
210
+ console.error(chalk.red('上传出错:'), error.message);
138
211
  process.exit(1);
139
212
  }
140
213
  }
141
214
 
142
- // 解析 SKILL.md 文件
143
- function parseSkillFile(content) {
144
- let frontmatter = null;
145
- let examples = [];
146
-
147
- // 解析 front matter
148
- const frontmatterMatch = content.match(/^---\s*\n([\s\S]*?)\n---\s*(?:\n|$)/);
149
- if (frontmatterMatch) {
150
- try {
151
- frontmatter = YAML.parse(frontmatterMatch[1]);
152
- } catch (e) {
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
- // 解析 usage examples(如果文件中有)
158
- const examplesMatch = content.match(/## Usage Examples?\s*\n([\s\S]*?)(?=##|$)/i);
159
- if (examplesMatch) {
160
- // 简单解析示例 - 可以根据实际格式改进
161
- const exampleText = examplesMatch[1];
162
- const exampleBlocks = exampleText.split(/\n\n+/).filter(b => b.trim());
163
-
164
- examples = exampleBlocks.map(block => {
165
- const lines = block.split('\n').filter(l => l.trim());
166
- return {
167
- prompt: lines.join('\n')
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 { frontmatter, examples };
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('CLI tool for managing skills on Skill Market')
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
- .hook('preAction', (thisCommand) => {
22
- // 显示欢迎信息
23
- const config = getConfig();
24
- if (config.user && thisCommand.args[0] !== 'login') {
25
- console.log(chalk.gray(`Logged in as: ${config.user.name}`));
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('Login to Skill Market')
33
- .option('--no-open', 'Do not open browser automatically')
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('Logout from Skill Market')
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('Upload a new skill')
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('Guide not found. Please visit https://kirigaya.cn/ktools/skillmanager'));
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 };