imtoagent 0.3.4 → 0.3.6

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.
Files changed (39) hide show
  1. package/README.md +97 -97
  2. package/bin/imtoagent-real +197 -153
  3. package/bin/imtoagent.cjs +13 -5
  4. package/index.ts +106 -106
  5. package/modules/agent/claude-adapter.ts +6 -6
  6. package/modules/agent/claude.ts +6 -6
  7. package/modules/agent/codex-adapter.ts +13 -13
  8. package/modules/agent/codex-exec-server.ts +11 -11
  9. package/modules/agent/codex.ts +29 -29
  10. package/modules/agent/opencode-adapter.ts +17 -17
  11. package/modules/agent/opencode.ts +10 -10
  12. package/modules/capabilities.ts +33 -33
  13. package/modules/cli/setup.ts +164 -164
  14. package/modules/core/config.ts +5 -5
  15. package/modules/core/error.ts +8 -8
  16. package/modules/core/runtime.ts +10 -10
  17. package/modules/core/session.ts +4 -4
  18. package/modules/core/stats.ts +14 -14
  19. package/modules/core/types.ts +7 -7
  20. package/modules/im/feishu.ts +56 -56
  21. package/modules/im/telegram.ts +23 -23
  22. package/modules/im/wechat.ts +54 -54
  23. package/modules/im/wecom.ts +50 -50
  24. package/modules/media/feishu-inbound-adapter.ts +4 -4
  25. package/modules/media/resolver.ts +11 -11
  26. package/modules/media/telegram-inbound-adapter.ts +8 -8
  27. package/modules/prompt-builder.ts +12 -12
  28. package/modules/proxy/anthropic-proxy.ts +31 -31
  29. package/modules/proxy/codex-proxy.ts +18 -18
  30. package/modules/utils/backend-check.ts +12 -12
  31. package/modules/utils/paths.ts +8 -8
  32. package/package.json +1 -1
  33. package/scripts/postinstall.cjs +10 -10
  34. package/scripts/postinstall.ts +13 -13
  35. package/templates/soul.template/identity.md +5 -5
  36. package/templates/soul.template/profile.md +7 -7
  37. package/templates/soul.template/rules.md +5 -5
  38. package/templates/soul.template/skills.md +2 -2
  39. package/templates/soul.template/workspace.md +3 -3
@@ -1,12 +1,12 @@
1
1
  // ================================================================
2
- // setup.ts — 交互式配置向导(v2
2
+ // setup.ts — Interactive Setup Wizard (v2)
3
3
  // ================================================================
4
- // 交互方式:
5
- // ↑↓ 空格切换选项
6
- // 回车确认选择
7
- // ESC — 返回上一步
4
+ // Interaction:
5
+ // ↑↓ or Spacenavigate options
6
+ // Enterconfirm selection
7
+ // ESC — go back
8
8
  //
9
- // 支持的 IM 平台:飞书 / Telegram / 企业微信 / 个人微信
9
+ // Supported IM platforms: Feishu / Telegram / WeCom / WeChat
10
10
  // ================================================================
11
11
 
12
12
  import * as fs from 'fs';
@@ -17,7 +17,7 @@ import { randomUUID } from 'crypto';
17
17
  import { checkAllBackends, formatBackendStatus } from '../utils/backend-check';
18
18
 
19
19
  // ================================================================
20
- // 键盘输入(raw mode
20
+ // Keyboard input (raw mode)
21
21
  // ================================================================
22
22
 
23
23
  const KEY = {
@@ -29,7 +29,7 @@ const KEY = {
29
29
  BACKSPACE: '\x7f',
30
30
  };
31
31
 
32
- /** 读取单个按键 */
32
+ /** Read a single keypress */
33
33
  function readKey(): Promise<string> {
34
34
  return new Promise((resolve) => {
35
35
  const onData = (data: Buffer) => {
@@ -43,22 +43,22 @@ function readKey(): Promise<string> {
43
43
  }
44
44
 
45
45
  // ================================================================
46
- // 菜单选择(↑↓/空格 切换,回车确认)
46
+ // Menu selection (↑↓/Space navigate, Enter confirm)
47
47
  // ================================================================
48
48
 
49
49
  async function selectMenu(title: string, options: string[]): Promise<number> {
50
50
  let idx = 0;
51
51
 
52
52
  function render() {
53
- // 清除之前的输出
54
- process.stdout.write('\x1B[0G'); // 回到行首
53
+ // Clear previous output
54
+ process.stdout.write('\x1B[0G'); // Return to line start
55
55
  options.forEach((opt, i) => {
56
56
  const prefix = i === idx ? '▸ ' : ' ';
57
57
  process.stdout.write(`\x1B[0G${prefix}${opt}\x1B[0K\n`);
58
58
  });
59
59
  }
60
60
 
61
- // 显示标题
61
+ // Show title
62
62
  console.log(title);
63
63
  render();
64
64
 
@@ -70,15 +70,15 @@ async function selectMenu(title: string, options: string[]): Promise<number> {
70
70
  const key = await readKey();
71
71
 
72
72
  if (key === KEY.UP || key === KEY.DOWN || key === KEY.SPACE) {
73
- // 移动光标
73
+ // Move cursor
74
74
  idx = (idx + (key === KEY.UP ? -1 : 1) + options.length) % options.length;
75
- // 重绘所有选项
76
- process.stdout.write(`\x1B[${options.length}A`); // 上移 N
75
+ // Redraw all options
76
+ process.stdout.write(`\x1B[${options.length}A`); // Move up N lines
77
77
  render();
78
78
  } else if (key === KEY.ENTER) {
79
79
  break;
80
80
  } else if (key === KEY.ESC) {
81
- return -1; // ESC = 返回上一步
81
+ return -1; // ESC = go back
82
82
  }
83
83
  }
84
84
  } finally {
@@ -91,7 +91,7 @@ async function selectMenu(title: string, options: string[]): Promise<number> {
91
91
  }
92
92
 
93
93
  // ================================================================
94
- // 文本输入(回车确认,ESC 返回 -1
94
+ // Text input (Enter confirm, ESC returns -1)
95
95
  // ================================================================
96
96
 
97
97
  async function promptText(label: string, defaultValue = ''): Promise<string> {
@@ -110,19 +110,19 @@ async function promptText(label: string, defaultValue = ''): Promise<string> {
110
110
  break;
111
111
  } else if (key === KEY.ESC) {
112
112
  process.stdout.write('\x1B[0K\n');
113
- return -1 as unknown as string; // 特殊返回值表示 ESC
113
+ return -1 as unknown as string; // Special return value for ESC
114
114
  } else if (key === KEY.BACKSPACE) {
115
115
  if (buf.length > 0) {
116
116
  buf.pop();
117
- process.stdout.write('\x1B[1D \x1B[1D'); // 退格删除
117
+ process.stdout.write('\x1B[1D \x1B[1D'); // Backspace delete
118
118
  }
119
119
  } else if (key === KEY.UP || key === KEY.DOWN) {
120
- // 忽略方向键
120
+ // Ignore arrow keys
121
121
  } else if (key === KEY.SPACE) {
122
122
  buf.push(' ');
123
123
  process.stdout.write(' ');
124
124
  } else if (key.length >= 1 && !key.startsWith('\x1b')) {
125
- // 普通字符 / 粘贴的多字符块(不含转义序列的文本)
125
+ // Normal characters / pasted multi-char blocks (text without escape sequences)
126
126
  buf.push(key);
127
127
  process.stdout.write(key);
128
128
  }
@@ -138,7 +138,7 @@ async function promptText(label: string, defaultValue = ''): Promise<string> {
138
138
  }
139
139
 
140
140
  // ================================================================
141
- // 确认(Y/N,回车确认,ESC 返回 -1
141
+ // Confirmation (Y/N, Enter confirm, ESC returns -1)
142
142
  // ================================================================
143
143
 
144
144
  async function confirm(label: string, defaultYes = true): Promise<boolean | -1> {
@@ -171,7 +171,7 @@ async function confirm(label: string, defaultYes = true): Promise<boolean | -1>
171
171
  }
172
172
 
173
173
  // ================================================================
174
- // 供应商预设
174
+ // Provider presets
175
175
  // ================================================================
176
176
 
177
177
  interface ProviderPreset {
@@ -179,12 +179,12 @@ interface ProviderPreset {
179
179
  baseUrl: string;
180
180
  format: 'openai' | 'anthropic';
181
181
  models: string[];
182
- hint?: string; // 额外说明
182
+ hint?: string; // Additional note
183
183
  }
184
184
 
185
185
  const PROVIDER_PRESETS: ProviderPreset[] = [
186
186
  {
187
- name: 'DashScope(阿里百炼)',
187
+ name: 'DashScope (Alibaba Bailian)',
188
188
  baseUrl: 'https://dashscope.aliyuncs.com/compatible-mode/v1',
189
189
  format: 'openai',
190
190
  models: ['qwen-max', 'qwen-plus', 'qwen-turbo'],
@@ -196,7 +196,7 @@ const PROVIDER_PRESETS: ProviderPreset[] = [
196
196
  models: ['deepseek-chat', 'deepseek-reasoner'],
197
197
  },
198
198
  {
199
- name: '智谱 AIZhipu',
199
+ name: 'Zhipu AI (Zhipu)',
200
200
  baseUrl: 'https://open.bigmodel.cn/api/paas/v4',
201
201
  format: 'openai',
202
202
  models: ['glm-4-plus', 'glm-4-flash', 'glm-4'],
@@ -208,13 +208,13 @@ const PROVIDER_PRESETS: ProviderPreset[] = [
208
208
  models: ['MiniMax-M2.5', 'MiniMax-M1'],
209
209
  },
210
210
  {
211
- name: '硅基流动(SiliconFlow',
211
+ name: 'SiliconFlow',
212
212
  baseUrl: 'https://api.siliconflow.cn/v1',
213
213
  format: 'openai',
214
214
  models: ['Qwen/Qwen2.5-72B-Instruct', 'deepseek-ai/DeepSeek-V3'],
215
215
  },
216
216
  {
217
- name: 'Moonshot(月之暗面)',
217
+ name: 'Moonshot (Moonshot AI)',
218
218
  baseUrl: 'https://api.moonshot.cn/v1',
219
219
  format: 'openai',
220
220
  models: ['moonshot-v1-8k', 'moonshot-v1-32k', 'moonshot-v1-128k'],
@@ -224,31 +224,31 @@ const PROVIDER_PRESETS: ProviderPreset[] = [
224
224
  baseUrl: 'https://api.openai.com/v1',
225
225
  format: 'openai',
226
226
  models: ['gpt-4o', 'gpt-4o-mini', 'o3', 'o4-mini'],
227
- hint: '需要代理才能访问',
227
+ hint: 'Proxy required to access',
228
228
  },
229
229
  {
230
230
  name: 'Anthropic',
231
231
  baseUrl: 'https://api.anthropic.com',
232
232
  format: 'anthropic',
233
233
  models: ['claude-sonnet-4-20250514', 'claude-haiku-4-20250514', 'claude-opus-4-20250514'],
234
- hint: '需要代理才能访问',
234
+ hint: 'Proxy required to access',
235
235
  },
236
236
  {
237
- name: 'GeminiGoogle',
237
+ name: 'Gemini (Google)',
238
238
  baseUrl: 'https://generativelanguage.googleapis.com/v1beta/openai',
239
239
  format: 'openai',
240
240
  models: ['gemini-2.5-pro', 'gemini-2.5-flash'],
241
- hint: '需要代理才能访问',
241
+ hint: 'Proxy required to access',
242
242
  },
243
243
  {
244
- name: 'xAIGrok',
244
+ name: 'xAI (Grok)',
245
245
  baseUrl: 'https://api.x.ai/v1',
246
246
  format: 'openai',
247
247
  models: ['grok-3', 'grok-3-mini'],
248
- hint: '需要代理才能访问',
248
+ hint: 'Requires proxy to access',
249
249
  },
250
250
  {
251
- name: 'Ollama(本地)',
251
+ name: 'Ollama (Local)',
252
252
  baseUrl: 'http://localhost:11434/v1',
253
253
  format: 'openai',
254
254
  models: ['qwen2.5', 'llama3.2', 'deepseek-r1'],
@@ -256,38 +256,38 @@ const PROVIDER_PRESETS: ProviderPreset[] = [
256
256
  ];
257
257
 
258
258
  // ================================================================
259
- // IM 平台配置定义
259
+ // IM platform configuration
260
260
  // ================================================================
261
261
 
262
262
  const IM_PLATFORMS = [
263
- { value: 'feishu', label: '飞书', desc: 'WebSocket 长连接' },
264
- { value: 'telegram', label: 'Telegram', desc: '长轮询' },
265
- { value: 'wecom', label: '企业微信', desc: '扫码绑定 + WebSocket' },
266
- { value: 'wechat', label: '个人微信', desc: 'iLink + QR 扫码' },
263
+ { value: 'feishu', label: 'Feishu', desc: 'WebSocket long-lived connection' },
264
+ { value: 'telegram', label: 'Telegram', desc: 'Long polling' },
265
+ { value: 'wecom', label: 'WeCom', desc: 'QR scan binding + WebSocket' },
266
+ { value: 'wechat', label: 'WeChat', desc: 'iLink + QR scan' },
267
267
  ];
268
268
 
269
- /** 每种 IM 需要的凭证字段 */
269
+ /** Credential fields required by each IM type */
270
270
  const IM_FIELDS: Record<string, { key: string; label: string; required: boolean }[]> = {
271
271
  feishu: [
272
- { key: 'appId', label: '飞书 App ID (cli_...)', required: true },
273
- { key: 'appSecret', label: '飞书 App Secret', required: true },
272
+ { key: 'appId', label: 'Feishu App ID (cli_...)', required: true },
273
+ { key: 'appSecret', label: 'Feishu App Secret', required: true },
274
274
  ],
275
275
  telegram: [
276
276
  { key: 'appId', label: 'Bot Token', required: true },
277
- { key: 'proxy', label: '代理地址(留空不使用)', required: false },
277
+ { key: 'proxy', label: 'Proxy URL (leave blank to skip)', required: false },
278
278
  ],
279
279
  wecom: [
280
- // 企业微信使用扫码绑定,无需预填凭证,启动时自动触发 QR 扫码
280
+ // WeCom uses QR scan binding, no pre-filled credentials needed, QR scan triggered automatically on startup
281
281
  ],
282
282
  wechat: [
283
- // 微信通过 iLink + QR 扫码认证,无需预填凭证
284
- { key: 'botId', label: 'iLink Bot ID(留空使用 QR 扫码)', required: false },
285
- { key: 'botToken', label: 'iLink Bot Token(留空使用 QR 扫码)', required: false },
283
+ // WeChat authenticates via iLink + QR scan, no pre-filled credentials needed
284
+ { key: 'botId', label: 'iLink Bot ID (leave blank for QR scan)', required: false },
285
+ { key: 'botToken', label: 'iLink Bot Token (leave blank for QR scan)', required: false },
286
286
  ],
287
287
  };
288
288
 
289
289
  // ================================================================
290
- // 主流程
290
+ // Main flow
291
291
  // ================================================================
292
292
 
293
293
  export async function runSetupWizard(): Promise<void> {
@@ -295,123 +295,123 @@ export async function runSetupWizard(): Promise<void> {
295
295
  const configPath = path.join(dataDir, 'config.json');
296
296
 
297
297
  console.log('\n╔══════════════════════════════════════════════╗');
298
- console.log('║ 🚀 imtoagent 配置向导 ║');
298
+ console.log('║ 🚀 imtoagent Setup Wizard ║');
299
299
  console.log('╚══════════════════════════════════════════════╝');
300
- console.log(`\n数据目录: ${dataDir}`);
301
- console.log(`操作提示: ↑↓/空格 切换 | 回车确认 | ESC 返回\n`);
300
+ console.log(`\nData directory: ${dataDir}`);
301
+ console.log(`Controls: ↑↓/Space navigate | Enter confirm | ESC go back\n`);
302
302
 
303
- // ===== Step 1: 检测已有配置 =====
303
+ // ===== Step 1: Detect existing configuration =====
304
304
  let existingConfig: any = null;
305
305
  let mergeMode = false;
306
306
 
307
307
  if (fs.existsSync(configPath)) {
308
- console.log('📋 检测到已有配置\n');
309
- const idx = await selectMenu('选择操作', ['覆盖现有配置', '合并(保留现有 Bot)', '退出']);
308
+ console.log('📋 Existing configuration detected\n');
309
+ const idx = await selectMenu('Choose action', ['Overwrite existing config', 'Merge (keep existing Bots)', 'Exit']);
310
310
  if (idx === -1) return; // ESC
311
- if (idx === 2) { console.log('👋 已取消'); process.exit(0); }
311
+ if (idx === 2) { console.log('👋 Cancelled'); process.exit(0); }
312
312
  if (idx === 1) mergeMode = true;
313
313
 
314
314
  try {
315
315
  existingConfig = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
316
316
  } catch {
317
- console.log('⚠️ 现有配置文件解析失败,将重新生成');
317
+ console.log('⚠️ Failed to parse existing config, will regenerate');
318
318
  existingConfig = null;
319
319
  mergeMode = false;
320
320
  }
321
321
  }
322
322
 
323
- // ===== Step 2: 检测后端 =====
324
- console.log('\n📌 检测后端 Agent...\n');
323
+ // ===== Step 2: Detect backends =====
324
+ console.log('\n📌 Detecting backend agents...\n');
325
325
  const backendStatus = checkAllBackends();
326
326
  console.log(formatBackendStatus(backendStatus));
327
327
 
328
328
  const installedBackends = backendStatus.filter(b => b.installed);
329
329
  if (installedBackends.length === 0) {
330
- console.log('\n⚠️ 未检测到任何后端 Agent。');
331
- console.log('推荐安装:');
330
+ console.log('\n⚠️ No backend agents detected.');
331
+ console.log('Recommended installs:');
332
332
  console.log(' npm install -g @anthropic-ai/claude-agent-sdk # Claude Code');
333
333
  console.log(' npm install -g @openai/codex # Codex');
334
334
  console.log(' npm install -g opencode # OpenCode');
335
- const r = await confirm('是否继续配置?(启动后发消息会报错,直到后端安装)', false);
336
- if (r === false || r === -1) { console.log('\n👋 安装后端后请重新运行 "imtoagent setup"'); process.exit(0); }
337
- console.log('\n⚠️ 已跳过,你可以稍后安装后端。\n');
335
+ const r = await confirm('Continue configuring? (Messaging will fail until backends are installed)', false);
336
+ if (r === false || r === -1) { console.log('\n👋 Run "imtoagent setup" again after installing backends'); process.exit(0); }
337
+ console.log('\n⚠️ Skipped, you can install backends later.\n');
338
338
  } else {
339
- console.log(`\n✅ 已安装: ${installedBackends.map(b => b.label).join(', ')}\n`);
339
+ console.log(`\n✅ Installed: ${installedBackends.map(b => b.label).join(', ')}\n`);
340
340
  }
341
341
 
342
- // ===== Step 3: 配置 Bot =====
343
- console.log('📌 Step 3: 配置 Bot\n');
342
+ // ===== Step 3: Configure Bots =====
343
+ console.log('📌 Step 3: Configure Bots\n');
344
344
 
345
345
  const bots: any[] = (mergeMode && existingConfig?.bots) ? [...existingConfig.bots] : [];
346
346
 
347
347
  let addingBots = true;
348
348
  while (addingBots) {
349
- console.log(`\n--- 添加新 Bot (已有 ${bots.length} ) ---\n`);
349
+ console.log(`\n--- Add New Bot (${bots.length} existing) ---\n`);
350
350
 
351
- // 3a: 选择 IM 平台
351
+ // 3a: Select IM platform
352
352
  const imLabels = IM_PLATFORMS.map(p => `${p.label} ${p.desc}`);
353
- const imIdx = await selectMenu('选择 IM 平台', imLabels);
353
+ const imIdx = await selectMenu('Select IM platform', imLabels);
354
354
  if (imIdx === -1) { if (bots.length === 0) return; break; } // ESC
355
355
  const imType = IM_PLATFORMS[imIdx].value;
356
356
 
357
- // 3b: 自动生成 Bot 名称,可自定义
357
+ // 3b: Auto-generate Bot name, customizable
358
358
  const defaultName = IM_PLATFORMS[imIdx].label + 'Bot';
359
- const nameInput = await promptText('Bot 名称', defaultName);
359
+ const nameInput = await promptText('Bot name', defaultName);
360
360
  if ((nameInput as any) === -1) { if (bots.length === 0) return; break; } // ESC
361
- const botName = nameInput || defaultName; // 留空用默认
361
+ const botName = nameInput || defaultName; // Use default if empty
362
362
 
363
- // 3c: 选择后端
363
+ // 3c: Select backend
364
364
  const backendLabels = backendStatus.map(b =>
365
- b.installed ? `${b.label} (v${b.version})` : `${b.label} ⚠️ 未安装`
365
+ b.installed ? `${b.label} (v${b.version})` : `${b.label} ⚠️ Not installed`
366
366
  );
367
- const backendIdx = await selectMenu('选择后端', backendLabels);
368
- if (backendIdx === -1) continue; // ESC 返回重新选 IM
367
+ const backendIdx = await selectMenu('Select backend', backendLabels);
368
+ if (backendIdx === -1) continue; // ESC go back to IM selection
369
369
  const backend = backendStatus[backendIdx].type;
370
370
  const isBackendInstalled = backendStatus[backendIdx].installed;
371
371
 
372
- // 后端未安装提示自动安装
372
+ // Backend not installed prompt for auto-install
373
373
  if (!isBackendInstalled) {
374
374
  const installCmd = backendStatus[backendIdx].installHint || '';
375
- console.log(`\n⚠️ ${backend} 未安装`);
375
+ console.log(`\n⚠️ ${backend} not installed`);
376
376
  console.log(` ${installCmd}\n`);
377
- const r = await confirm('是否现在自动安装?');
377
+ const r = await confirm('Auto-install now?');
378
378
  if (r === true) {
379
379
  const { installBackend } = await import('../utils/backend-check');
380
380
  const ok = await installBackend(backend as 'claude' | 'codex' | 'opencode');
381
381
  if (ok) {
382
- console.log(`✅ ${backend} 已安装\n`);
382
+ console.log(`✅ ${backend} installed\n`);
383
383
  } else {
384
- console.log(`⚠️ 安装失败,可稍后手动运行: ${installCmd}\n`);
385
- const r2 = await confirm('仍要继续配置此 Bot?');
384
+ console.log(`⚠️ Installation failed, run manually later: ${installCmd}\n`);
385
+ const r2 = await confirm('Continue configuring this Bot anyway?');
386
386
  if (r2 === false) continue;
387
387
  }
388
388
  } else if (r === -1) {
389
- continue; // ESC 返回
389
+ continue; // ESC go back
390
390
  } else {
391
- console.log('跳过安装\n');
391
+ console.log('Skipping installation\n');
392
392
  }
393
393
  }
394
394
 
395
- // 3d: 根据 IM 类型收集凭证
396
- console.log(`\n--- ${IM_PLATFORMS.find(p => p.value === imType)?.label} 凭证 ---`);
395
+ // 3d: Collect credentials based on IM type
396
+ console.log(`\n--- ${IM_PLATFORMS.find(p => p.value === imType)?.label} credentials ---`);
397
397
  const fields = IM_FIELDS[imType] || [];
398
398
  const credentials: Record<string, string> = {};
399
399
 
400
400
  for (const field of fields) {
401
- const val = await promptText(field.label + (field.required ? '' : '(可选)'));
401
+ const val = await promptText(field.label + (field.required ? '' : ' (optional)'));
402
402
  if ((val as any) === -1) { credentials._escaped = 'true'; break; } // ESC
403
403
  credentials[field.key] = val;
404
404
  }
405
- if (credentials._escaped) continue; // ESC 返回重新选后端
405
+ if (credentials._escaped) continue; // ESC go back and re-select backend
406
406
 
407
- // 3e: 工作目录
408
- const cwd = await promptText('工作目录', os.homedir());
407
+ // 3e: Working directory
408
+ const cwd = await promptText('Working directory', os.homedir());
409
409
  if ((cwd as any) === -1) continue;
410
410
 
411
- // 生成唯一 IDUUID,用于目录隔离,改名不影响)
411
+ // Generate unique ID (UUID, for directory isolation, renaming doesn't affect it)
412
412
  const botId = randomUUID();
413
413
 
414
- // 构建 Bot 配置(不同 IM 需要的字段不同)
414
+ // Build Bot configuration (different IM types need different fields)
415
415
  const bot: any = {
416
416
  id: botId,
417
417
  name: botName,
@@ -419,147 +419,147 @@ export async function runSetupWizard(): Promise<void> {
419
419
  cwd: cwd || os.homedir(),
420
420
  };
421
421
 
422
- // 飞书需要 appId + appSecret
422
+ // Feishu needs appId + appSecret
423
423
  if (imType === 'feishu') {
424
424
  bot.appId = credentials.appId || '';
425
425
  bot.appSecret = credentials.appSecret || '';
426
426
  }
427
- // Telegram 需要 appIdBot Token),可选 proxy
427
+ // Telegram needs appId (Bot Token), optional proxy
428
428
  else if (imType === 'telegram') {
429
429
  bot.appId = credentials.appId || '';
430
430
  if (credentials.proxy) bot.proxy = credentials.proxy;
431
431
  }
432
- // 企业微信:扫码绑定,无需预填凭证(可选 botId/secret
432
+ // WeCom: QR scan binding, no pre-filled credentials (optional botId/secret)
433
433
  else if (imType === 'wecom') {
434
434
  bot.im = 'wecom';
435
435
  if (credentials.botId) bot.botId = credentials.botId;
436
436
  if (credentials.secret) bot.secret = credentials.secret;
437
437
  }
438
- // 个人微信:可选 botId/botToken,留空则 QR 扫码
438
+ // WeChat: optional botId/botToken, QR scan if left blank
439
439
  else if (imType === 'wechat') {
440
440
  bot.im = 'wechat';
441
441
  if (credentials.botId) bot.botId = credentials.botId;
442
442
  if (credentials.botToken) bot.botToken = credentials.botToken;
443
443
  if (credentials.ilinkUserId) bot.ilinkUserId = credentials.ilinkUserId;
444
444
  }
445
- // 默认:非飞书平台加 im 字段
445
+ // Default: non-Feishu platforms add im field
446
446
  else {
447
447
  bot.im = imType;
448
448
  }
449
449
 
450
- // 检查重名
450
+ // Check for duplicate name
451
451
  const existingIdx = bots.findIndex(b => b.name === botName);
452
452
  if (existingIdx >= 0) {
453
453
  bots[existingIdx] = bot;
454
- console.log(`✅ 已替换: ${botName}`);
454
+ console.log(`✅ Replaced: ${botName}`);
455
455
  } else {
456
456
  bots.push(bot);
457
- console.log(`✅ 已添加: ${botName}`);
457
+ console.log(`✅ Added: ${botName}`);
458
458
  }
459
459
 
460
- // 是否继续添加
461
- const r = await confirm('继续添加其他 Bot?', true);
462
- if (r === -1) addingBots = false; // ESC = 不添加了,进入下一步
460
+ // Whether to continue adding
461
+ const r = await confirm('Add another Bot?', true);
462
+ if (r === -1) addingBots = false; // ESC = done adding, proceed to next step
463
463
  else addingBots = (r === true);
464
464
  }
465
465
 
466
466
  if (bots.length === 0) {
467
- console.log('\n⚠️ 未配置任何 Bot。');
468
- const r = await confirm('至少配置一个 Bot 吗?');
467
+ console.log('\n⚠️ No Bots configured.');
468
+ const r = await confirm('Configure at least one Bot?');
469
469
  if (r === true) return runSetupWizard();
470
- console.log('\n⚠️ 至少需要一个 Bot,配置已取消');
470
+ console.log('\n⚠️ At least one Bot required, configuration cancelled');
471
471
  return;
472
472
  }
473
473
 
474
- // ===== Step 4: 配置模型供应商 =====
475
- console.log('\n📌 Step 4: 配置模型供应商\n');
474
+ // ===== Step 4: Configure model providers =====
475
+ console.log('\n📌 Step 4: Configure model providers\n');
476
476
 
477
477
  const providers: Record<string, any> = {};
478
478
  if (mergeMode && existingConfig?.providers) {
479
479
  Object.assign(providers, existingConfig.providers);
480
- console.log(`✅ 已保留 ${Object.keys(providers).length} 个现有供应商\n`);
480
+ console.log(`✅ Kept ${Object.keys(providers).length} existing provider(s)\n`);
481
481
  }
482
482
 
483
- // Step 4 外层循环:确保至少有一个供应商(用户明确跳过时退出)
483
+ // Step 4 outer loop: ensure at least one provider (exit when user explicitly skips)
484
484
  let step4Loop = true;
485
485
  while (step4Loop) {
486
486
  let addingProviders = true;
487
487
  while (addingProviders) {
488
- console.log('--- 添加新供应商 ---\n');
488
+ console.log('--- Add new provider ---\n');
489
489
 
490
- // 选择预设 or 自定义
490
+ // Choose preset or custom
491
491
  const presetOptions = PROVIDER_PRESETS.map(p => {
492
492
  const tag = p.hint ? ` ${p.hint}` : '';
493
493
  return `${p.name}${tag}`;
494
494
  });
495
- presetOptions.push('自定义...');
495
+ presetOptions.push('Custom...');
496
496
 
497
- const presetIdx = await selectMenu('选择供应商', presetOptions);
497
+ const presetIdx = await selectMenu('Select provider', presetOptions);
498
498
  if (presetIdx === -1) { addingProviders = false; continue; }
499
499
 
500
500
  let provName: string, baseUrl: string, format: 'openai' | 'anthropic', models: string[];
501
501
 
502
502
  if (presetIdx < PROVIDER_PRESETS.length) {
503
- // 使用预设
503
+ // Use preset
504
504
  const preset = PROVIDER_PRESETS[presetIdx];
505
- provName = preset.name.split('')[0].trim().toLowerCase(); // 取简短名称
505
+ provName = preset.name.split('(')[0].trim().toLowerCase(); // Take short name
506
506
  baseUrl = preset.baseUrl;
507
507
  format = preset.format;
508
508
  models = [...preset.models];
509
509
 
510
- console.log(`\n✅ 预设已加载:`);
511
- console.log(` 名称: ${provName}`);
510
+ console.log(`\n✅ Preset loaded:`);
511
+ console.log(` Name: ${provName}`);
512
512
  console.log(` URL: ${preset.baseUrl}`);
513
- console.log(` 格式: ${preset.format}`);
514
- console.log(` 模型: ${preset.models.join(', ')}\n`);
513
+ console.log(` Format: ${preset.format}`);
514
+ console.log(` Models: ${preset.models.join(', ')}\n`);
515
515
 
516
- // 确认/修改简短名称
517
- const nameEdit = await promptText('供应商名称(留空确认)', provName);
516
+ // Confirm/edit short name
517
+ const nameEdit = await promptText('Provider name (leave blank to confirm)', provName);
518
518
  if ((nameEdit as any) === -1) continue;
519
519
  provName = nameEdit || provName;
520
520
 
521
- // 确认/修改 Base URL
521
+ // Confirm/edit Base URL
522
522
  const urlEdit = await promptText('Base URL', baseUrl);
523
523
  if ((urlEdit as any) === -1) continue;
524
524
  baseUrl = urlEdit || baseUrl;
525
525
 
526
- // 确认/修改模型列表
527
- const modelsEdit = await promptText('模型列表(逗号分隔)', models.join(', '));
526
+ // Confirm/edit model list
527
+ const modelsEdit = await promptText('Model list (comma-separated)', models.join(', '));
528
528
  if ((modelsEdit as any) === -1) continue;
529
529
  if (modelsEdit) models = modelsEdit.split(',').map(s => s.trim()).filter(Boolean);
530
530
 
531
531
  if (providers[provName]) {
532
- console.log(`⚠️ 供应商 "${provName}" 已存在,将覆盖\n`);
532
+ console.log(`⚠️ Provider "${provName}" already exists, will overwrite\n`);
533
533
  }
534
534
  } else {
535
- // 自定义
536
- provName = await promptText('供应商名称 ( deepseek, dashscope)');
535
+ // Custom
536
+ provName = await promptText('Provider name (e.g. deepseek, dashscope)');
537
537
  if ((provName as any) === -1) { addingProviders = false; continue; }
538
538
  if (!provName) { addingProviders = false; continue; }
539
539
  if (providers[provName]) {
540
- console.log(`⚠️ 供应商 "${provName}" 已存在,将覆盖\n`);
540
+ console.log(`⚠️ Provider "${provName}" already exists, will overwrite\n`);
541
541
  }
542
542
 
543
- baseUrl = await promptText('Base URL ( https://api.deepseek.com/v1)');
543
+ baseUrl = await promptText('Base URL (e.g. https://api.deepseek.com/v1)');
544
544
  if ((baseUrl as any) === -1) continue;
545
- const modelsStr = await promptText('模型列表 (逗号分隔)');
545
+ const modelsStr = await promptText('Model list (comma-separated)');
546
546
  if ((modelsStr as any) === -1) continue;
547
547
  models = (modelsStr || '').split(',').map(s => s.trim()).filter(Boolean);
548
548
 
549
- const formatIdx = await selectMenu('API 格式', ['openai', 'anthropic']);
549
+ const formatIdx = await selectMenu('API format', ['openai', 'anthropic']);
550
550
  if (formatIdx === -1) continue;
551
551
  format = ['openai', 'anthropic'][formatIdx];
552
552
  }
553
553
 
554
- // API Key(所有路径都需要)
554
+ // API Key (required for all providers)
555
555
  const apiKey = await promptText('API Key');
556
556
  if ((apiKey as any) === -1) continue;
557
557
  if (!apiKey) {
558
- console.log('⚠️ API Key 为空,当前供应商将暂时无法使用\n');
558
+ console.log('⚠️ API Key is empty, this provider will be temporarily unavailable\n');
559
559
  }
560
560
 
561
- // 价格(可选)
562
- const priceInput = await promptText('价格 (入/出 每百万 Token,如 0.55,2.19,留空跳过)');
561
+ // Pricing (optional)
562
+ const priceInput = await promptText('Pricing (in/out per million tokens, e.g. 0.55,2.19, leave blank to skip)');
563
563
  if ((priceInput as any) === -1) continue;
564
564
 
565
565
  const pricing: any = {};
@@ -573,24 +573,24 @@ export async function runSetupWizard(): Promise<void> {
573
573
  }
574
574
 
575
575
  providers[provName] = { baseUrl, apiKey, models, format, ...(Object.keys(pricing).length ? { pricing } : {}) };
576
- console.log(`✅ 已添加: ${provName}\n`);
576
+ console.log(`✅ Added: ${provName}\n`);
577
577
 
578
- const r = await confirm('继续添加供应商?', false);
578
+ const r = await confirm('Continue adding providers?', false);
579
579
  if (r === -1) addingProviders = false;
580
580
  else addingProviders = (r === true);
581
581
  }
582
582
 
583
583
  if (Object.keys(providers).length === 0) {
584
- console.log('\n⚠️ 未配置任何供应商。');
585
- const r = await confirm('至少配置一个供应商吗?');
586
- if (r === true) continue; // 重新进入 step4Loop
587
- if (r === -1) { console.log('\n⚠️ 已跳过,你可以稍后配置。\n'); }
584
+ console.log('\n⚠️ No providers configured.');
585
+ const r = await confirm('Configure at least one provider?');
586
+ if (r === true) continue; // Re-enter step4Loop
587
+ if (r === -1) { console.log('\n⚠️ Skipped, you can configure this later.\n'); }
588
588
  }
589
- step4Loop = false; // 有供应商或用户明确跳过
589
+ step4Loop = false; // Has providers or user explicitly skipped
590
590
  }
591
591
 
592
- // ===== Step 5: 选择默认模型 =====
593
- console.log('\n📌 Step 5: 选择默认模型\n');
592
+ // ===== Step 5: Select default model =====
593
+ console.log('\n📌 Step 5: Select default model\n');
594
594
 
595
595
  const allModels: string[] = [];
596
596
  for (const [provName, prov] of Object.entries(providers)) {
@@ -602,24 +602,24 @@ export async function runSetupWizard(): Promise<void> {
602
602
  let defaultModel = '';
603
603
  if (allModels.length > 0) {
604
604
  const existingDefault = existingConfig?.defaultModel || allModels[0];
605
- const val = await promptText('默认模型', existingDefault);
605
+ const val = await promptText('Default model', existingDefault);
606
606
  defaultModel = (val as any) === -1 ? existingDefault : (val || existingDefault);
607
607
  } else {
608
- defaultModel = await promptText('默认模型 (供应商/模型名)') || 'deepseek/deepseek-v4-pro';
608
+ defaultModel = await promptText('Default model (provider/model)') || 'deepseek/deepseek-v4-pro';
609
609
  if ((defaultModel as any) === -1) defaultModel = 'deepseek/deepseek-v4-pro';
610
610
  }
611
611
 
612
- // ===== Step 6: 生成灵魂文件 =====
613
- console.log('\n📌 Step 6: 生成灵魂文件\n');
612
+ // ===== Step 6: Generate soul files =====
613
+ console.log('\n📌 Step 6: Generate soul files\n');
614
614
 
615
615
  for (const bot of bots) {
616
616
  const botSoulDir = getSoulDir(getBotKey(bot));
617
617
  const templateSoulDir = path.join(getPkgDir(), 'templates', 'soul.template');
618
618
 
619
619
  if (fs.existsSync(botSoulDir)) {
620
- const r = await confirm(`已存在 ${bot.name} 的灵魂文件,重新生成?`, false);
620
+ const r = await confirm(`Soul files for ${bot.name} already exist, regenerate?`, false);
621
621
  if (r === -1 || r === false) {
622
- console.log(`⏭ 跳过: ${bot.name}`);
622
+ console.log(`⏭ Skipped: ${bot.name}`);
623
623
  continue;
624
624
  }
625
625
  }
@@ -641,11 +641,11 @@ export async function runSetupWizard(): Promise<void> {
641
641
  fs.writeFileSync(destPath, content);
642
642
  }
643
643
  }
644
- console.log(`✅ ${bot.name}: 灵魂文件 → ${botSoulDir}`);
644
+ console.log(`✅ ${bot.name}: soul files → ${botSoulDir}`);
645
645
  }
646
646
 
647
- // ===== Step 7: 写入配置文件 =====
648
- console.log('\n📌 Step 7: 写入配置文件\n');
647
+ // ===== Step 7: Write configuration files =====
648
+ console.log('\n📌 Step 7: Write configuration files\n');
649
649
 
650
650
  fs.mkdirSync(dataDir, { recursive: true });
651
651
 
@@ -700,22 +700,22 @@ export async function runSetupWizard(): Promise<void> {
700
700
 
701
701
  fs.mkdirSync(path.join(dataDir, 'sessions'), { recursive: true });
702
702
  fs.mkdirSync(path.join(dataDir, 'logs'), { recursive: true });
703
- console.log('✅ 子目录已创建 (sessions/, logs/)');
703
+ console.log('✅ Sub-directories created (sessions/, logs/)');
704
704
 
705
- // ===== 完成 =====
705
+ // ===== Done =====
706
706
  console.log('\n╔══════════════════════════════════════════════╗');
707
- console.log('║ ✅ 配置完成! ║');
707
+ console.log('║ ✅ Configuration complete! ║');
708
708
  console.log('╚══════════════════════════════════════════════╝\n');
709
709
  console.log(`Bot: ${bots.map(b => b.name).join(', ')}`);
710
- console.log(`默认模型: ${defaultModel}`);
711
- console.log(`供应商: ${Object.keys(providers).join(', ') || ''}`);
712
- console.log(`\n下一步:`);
713
- console.log(` imtoagent start 启动网关`);
714
- console.log(` imtoagent status 查看状态\n`);
710
+ console.log(`Default model: ${defaultModel}`);
711
+ console.log(`Providers: ${Object.keys(providers).join(', ') || 'None'}`);
712
+ console.log(`\nNext steps:`);
713
+ console.log(` imtoagent start Start the gateway`);
714
+ console.log(` imtoagent status Check status\n`);
715
715
  }
716
716
 
717
717
  // ================================================================
718
- // 工具函数
718
+ // Utility functions
719
719
  // ================================================================
720
720
 
721
721
  function buildDefaultAliases(defaultModel: string): Record<string, string> {