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.
- package/README.md +97 -97
- package/bin/imtoagent-real +197 -153
- package/bin/imtoagent.cjs +13 -5
- package/index.ts +106 -106
- package/modules/agent/claude-adapter.ts +6 -6
- package/modules/agent/claude.ts +6 -6
- package/modules/agent/codex-adapter.ts +13 -13
- package/modules/agent/codex-exec-server.ts +11 -11
- package/modules/agent/codex.ts +29 -29
- package/modules/agent/opencode-adapter.ts +17 -17
- package/modules/agent/opencode.ts +10 -10
- package/modules/capabilities.ts +33 -33
- package/modules/cli/setup.ts +164 -164
- package/modules/core/config.ts +5 -5
- package/modules/core/error.ts +8 -8
- package/modules/core/runtime.ts +10 -10
- package/modules/core/session.ts +4 -4
- package/modules/core/stats.ts +14 -14
- package/modules/core/types.ts +7 -7
- package/modules/im/feishu.ts +56 -56
- package/modules/im/telegram.ts +23 -23
- package/modules/im/wechat.ts +54 -54
- package/modules/im/wecom.ts +50 -50
- package/modules/media/feishu-inbound-adapter.ts +4 -4
- package/modules/media/resolver.ts +11 -11
- package/modules/media/telegram-inbound-adapter.ts +8 -8
- package/modules/prompt-builder.ts +12 -12
- package/modules/proxy/anthropic-proxy.ts +31 -31
- package/modules/proxy/codex-proxy.ts +18 -18
- package/modules/utils/backend-check.ts +12 -12
- package/modules/utils/paths.ts +8 -8
- package/package.json +1 -1
- package/scripts/postinstall.cjs +10 -10
- package/scripts/postinstall.ts +13 -13
- package/templates/soul.template/identity.md +5 -5
- package/templates/soul.template/profile.md +7 -7
- package/templates/soul.template/rules.md +5 -5
- package/templates/soul.template/skills.md +2 -2
- package/templates/soul.template/workspace.md +3 -3
package/modules/cli/setup.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
// ================================================================
|
|
2
|
-
// setup.ts —
|
|
2
|
+
// setup.ts — Interactive Setup Wizard (v2)
|
|
3
3
|
// ================================================================
|
|
4
|
-
//
|
|
5
|
-
// ↑↓
|
|
6
|
-
//
|
|
7
|
-
// ESC —
|
|
4
|
+
// Interaction:
|
|
5
|
+
// ↑↓ or Space — navigate options
|
|
6
|
+
// Enter — confirm selection
|
|
7
|
+
// ESC — go back
|
|
8
8
|
//
|
|
9
|
-
//
|
|
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
|
-
//
|
|
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`); //
|
|
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
|
-
//
|
|
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; //
|
|
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
|
-
//
|
|
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: '
|
|
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: '
|
|
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: 'Gemini
|
|
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: 'xAI
|
|
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: '
|
|
264
|
-
{ value: 'telegram', label: 'Telegram', desc: '
|
|
265
|
-
{ value: 'wecom', label: '
|
|
266
|
-
{ value: 'wechat', label: '
|
|
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
|
-
/**
|
|
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: '
|
|
273
|
-
{ key: 'appSecret', label: '
|
|
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: '
|
|
277
|
+
{ key: 'proxy', label: 'Proxy URL (leave blank to skip)', required: false },
|
|
278
278
|
],
|
|
279
279
|
wecom: [
|
|
280
|
-
//
|
|
280
|
+
// WeCom uses QR scan binding, no pre-filled credentials needed, QR scan triggered automatically on startup
|
|
281
281
|
],
|
|
282
282
|
wechat: [
|
|
283
|
-
//
|
|
284
|
-
{ key: 'botId', label: 'iLink Bot ID
|
|
285
|
-
{ key: 'botToken', label: 'iLink Bot Token
|
|
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(`\
|
|
301
|
-
console.log(
|
|
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('📋
|
|
309
|
-
const idx = await selectMenu('
|
|
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('👋
|
|
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📌
|
|
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⚠️
|
|
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('
|
|
336
|
-
if (r === false || r === -1) { console.log('\n👋
|
|
337
|
-
console.log('\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✅
|
|
339
|
+
console.log(`\n✅ Installed: ${installedBackends.map(b => b.label).join(', ')}\n`);
|
|
340
340
|
}
|
|
341
341
|
|
|
342
|
-
// ===== Step 3:
|
|
343
|
-
console.log('📌 Step 3:
|
|
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---
|
|
349
|
+
console.log(`\n--- Add New Bot (${bots.length} existing) ---\n`);
|
|
350
350
|
|
|
351
|
-
// 3a:
|
|
351
|
+
// 3a: Select IM platform
|
|
352
352
|
const imLabels = IM_PLATFORMS.map(p => `${p.label} ${p.desc}`);
|
|
353
|
-
const imIdx = await selectMenu('
|
|
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:
|
|
357
|
+
// 3b: Auto-generate Bot name, customizable
|
|
358
358
|
const defaultName = IM_PLATFORMS[imIdx].label + 'Bot';
|
|
359
|
-
const nameInput = await promptText('Bot
|
|
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('
|
|
368
|
-
if (backendIdx === -1) continue; // ESC
|
|
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}
|
|
382
|
+
console.log(`✅ ${backend} installed\n`);
|
|
383
383
|
} else {
|
|
384
|
-
console.log(`⚠️
|
|
385
|
-
const r2 = await confirm('
|
|
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('
|
|
391
|
+
console.log('Skipping installation\n');
|
|
392
392
|
}
|
|
393
393
|
}
|
|
394
394
|
|
|
395
|
-
// 3d:
|
|
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('
|
|
407
|
+
// 3e: Working directory
|
|
408
|
+
const cwd = await promptText('Working directory', os.homedir());
|
|
409
409
|
if ((cwd as any) === -1) continue;
|
|
410
410
|
|
|
411
|
-
//
|
|
411
|
+
// Generate unique ID (UUID, for directory isolation, renaming doesn't affect it)
|
|
412
412
|
const botId = randomUUID();
|
|
413
413
|
|
|
414
|
-
//
|
|
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
|
-
//
|
|
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
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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(`✅
|
|
454
|
+
console.log(`✅ Replaced: ${botName}`);
|
|
455
455
|
} else {
|
|
456
456
|
bots.push(bot);
|
|
457
|
-
console.log(`✅
|
|
457
|
+
console.log(`✅ Added: ${botName}`);
|
|
458
458
|
}
|
|
459
459
|
|
|
460
|
-
//
|
|
461
|
-
const r = await confirm('
|
|
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⚠️
|
|
468
|
-
const r = await confirm('
|
|
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⚠️
|
|
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:
|
|
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(`✅
|
|
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('---
|
|
488
|
+
console.log('--- Add new provider ---\n');
|
|
489
489
|
|
|
490
|
-
//
|
|
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('
|
|
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('
|
|
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(`
|
|
510
|
+
console.log(`\n✅ Preset loaded:`);
|
|
511
|
+
console.log(` Name: ${provName}`);
|
|
512
512
|
console.log(` URL: ${preset.baseUrl}`);
|
|
513
|
-
console.log(`
|
|
514
|
-
console.log(`
|
|
513
|
+
console.log(` Format: ${preset.format}`);
|
|
514
|
+
console.log(` Models: ${preset.models.join(', ')}\n`);
|
|
515
515
|
|
|
516
|
-
//
|
|
517
|
-
const nameEdit = await promptText('
|
|
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
|
-
//
|
|
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('
|
|
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(`⚠️
|
|
532
|
+
console.log(`⚠️ Provider "${provName}" already exists, will overwrite\n`);
|
|
533
533
|
}
|
|
534
534
|
} else {
|
|
535
|
-
//
|
|
536
|
-
provName = await promptText('
|
|
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(`⚠️
|
|
540
|
+
console.log(`⚠️ Provider "${provName}" already exists, will overwrite\n`);
|
|
541
541
|
}
|
|
542
542
|
|
|
543
|
-
baseUrl = await promptText('Base URL (
|
|
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
|
|
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
|
|
558
|
+
console.log('⚠️ API Key is empty, this provider will be temporarily unavailable\n');
|
|
559
559
|
}
|
|
560
560
|
|
|
561
|
-
//
|
|
562
|
-
const priceInput = await promptText('
|
|
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(`✅
|
|
576
|
+
console.log(`✅ Added: ${provName}\n`);
|
|
577
577
|
|
|
578
|
-
const r = await confirm('
|
|
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; //
|
|
587
|
-
if (r === -1) { console.log('\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:
|
|
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('
|
|
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('
|
|
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:
|
|
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(
|
|
620
|
+
const r = await confirm(`Soul files for ${bot.name} already exist, regenerate?`, false);
|
|
621
621
|
if (r === -1 || r === false) {
|
|
622
|
-
console.log(`⏭
|
|
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}:
|
|
644
|
+
console.log(`✅ ${bot.name}: soul files → ${botSoulDir}`);
|
|
645
645
|
}
|
|
646
646
|
|
|
647
|
-
// ===== Step 7:
|
|
648
|
-
console.log('\n📌 Step 7:
|
|
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('✅
|
|
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(
|
|
711
|
-
console.log(
|
|
712
|
-
console.log(`\
|
|
713
|
-
console.log(` imtoagent start
|
|
714
|
-
console.log(` imtoagent status
|
|
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> {
|