imtoagent 0.3.24 → 0.3.26
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/bin/imtoagent-real +374 -1
- package/index.ts +31 -4
- package/modules/cli/setup.ts +1 -0
- package/modules/core/types.ts +1 -0
- package/modules/utils/config-manager.ts +374 -0
- package/modules/utils/doctor.ts +462 -0
- package/modules/utils/workspace-manager.ts +23 -3
- package/package.json +1 -1
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
// ================================================================
|
|
2
|
+
// config-manager.ts — 配置管理 CRUD
|
|
3
|
+
// ================================================================
|
|
4
|
+
// imtoagent config list — 列出所有 Bot
|
|
5
|
+
// imtoagent config show NAME — 显示某个 Bot 的完整配置
|
|
6
|
+
// imtoagent config add — 交互式添加 Bot
|
|
7
|
+
// imtoagent config remove NAME — 删除 Bot
|
|
8
|
+
// imtoagent config modify NAME — 修改 Bot 配置
|
|
9
|
+
// ================================================================
|
|
10
|
+
|
|
11
|
+
import * as fs from 'fs';
|
|
12
|
+
import * as path from 'path';
|
|
13
|
+
import * as readline from 'readline';
|
|
14
|
+
import { getDataDir, getConfigPath, getProvidersPath } from './paths';
|
|
15
|
+
import crypto from 'crypto';
|
|
16
|
+
|
|
17
|
+
// ================================================================
|
|
18
|
+
// 类型
|
|
19
|
+
// ================================================================
|
|
20
|
+
|
|
21
|
+
const VALID_BACKENDS = ['claude', 'codex', 'opencode'] as const;
|
|
22
|
+
const VALID_IMS = ['feishu', 'telegram', 'wecom', 'wechat'] as const;
|
|
23
|
+
|
|
24
|
+
export type BackendType = typeof VALID_BACKENDS[number];
|
|
25
|
+
export type IMType = typeof VALID_IMS[number];
|
|
26
|
+
|
|
27
|
+
export interface BotEntry {
|
|
28
|
+
id?: string;
|
|
29
|
+
name: string;
|
|
30
|
+
im: string;
|
|
31
|
+
appId: string;
|
|
32
|
+
appSecret: string;
|
|
33
|
+
backend: string;
|
|
34
|
+
cwd?: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
interface RawConfig {
|
|
38
|
+
system?: Record<string, any>;
|
|
39
|
+
providers?: Record<string, any>;
|
|
40
|
+
defaultModel?: string;
|
|
41
|
+
activeModel?: string;
|
|
42
|
+
modelAliases?: Record<string, string>;
|
|
43
|
+
bots?: BotEntry[];
|
|
44
|
+
execServer?: any;
|
|
45
|
+
codex?: any;
|
|
46
|
+
opencode?: any;
|
|
47
|
+
rateLimit?: any;
|
|
48
|
+
shutdown?: any;
|
|
49
|
+
[key: string]: any;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// ================================================================
|
|
53
|
+
// 加载/保存
|
|
54
|
+
// ================================================================
|
|
55
|
+
|
|
56
|
+
function loadConfig(): { config: RawConfig; configPath: string } {
|
|
57
|
+
const configPath = getConfigPath();
|
|
58
|
+
if (!fs.existsSync(configPath)) {
|
|
59
|
+
throw new Error(`config.json not found at ${configPath} — run "imtoagent setup" first`);
|
|
60
|
+
}
|
|
61
|
+
const raw = fs.readFileSync(configPath, 'utf-8');
|
|
62
|
+
const config = JSON.parse(raw) as RawConfig;
|
|
63
|
+
return { config, configPath };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function saveConfig(config: RawConfig, configPath: string): void {
|
|
67
|
+
const tmpPath = configPath + '.tmp';
|
|
68
|
+
try {
|
|
69
|
+
fs.writeFileSync(tmpPath, JSON.stringify(config, null, 2) + '\n');
|
|
70
|
+
fs.renameSync(tmpPath, configPath);
|
|
71
|
+
} catch (e) {
|
|
72
|
+
// Clean up tmp on failure
|
|
73
|
+
try { fs.unlinkSync(tmpPath); } catch {}
|
|
74
|
+
throw e;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// ================================================================
|
|
79
|
+
// 交互式 prompt(复用 setup 的风格)
|
|
80
|
+
// ================================================================
|
|
81
|
+
|
|
82
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
83
|
+
|
|
84
|
+
function prompt(question: string): Promise<string> {
|
|
85
|
+
return new Promise((resolve) => {
|
|
86
|
+
rl.question(question, (answer) => resolve(answer.trim()));
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async function confirm(question: string): Promise<boolean> {
|
|
91
|
+
const answer = await prompt(`${question} [y/N] `);
|
|
92
|
+
return answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes';
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// ================================================================
|
|
96
|
+
// config list
|
|
97
|
+
// ================================================================
|
|
98
|
+
|
|
99
|
+
export async function cmdConfigList(): Promise<void> {
|
|
100
|
+
const { config } = loadConfig();
|
|
101
|
+
const bots = config.bots || [];
|
|
102
|
+
|
|
103
|
+
if (bots.length === 0) {
|
|
104
|
+
console.log(' No Bots configured. Run "imtoagent config add" to add one.');
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
console.log(`\n📋 Configured Bots (${bots.length}):\n`);
|
|
109
|
+
for (const bot of bots) {
|
|
110
|
+
const botId = bot.id ? ` [${bot.id.slice(0, 8)}]` : '';
|
|
111
|
+
const adminTag = bot.isAdmin ? ' ⭐' : '';
|
|
112
|
+
const cwd = bot.cwd ? ` (cwd: ${bot.cwd})` : '';
|
|
113
|
+
console.log(` • ${bot.name}${adminTag}${botId}`);
|
|
114
|
+
console.log(` IM: ${bot.im || "(not set)"} | Backend: ${bot.backend}${cwd}`);
|
|
115
|
+
}
|
|
116
|
+
console.log();
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// ================================================================
|
|
120
|
+
// config show NAME
|
|
121
|
+
// ================================================================
|
|
122
|
+
|
|
123
|
+
export async function cmdConfigShow(name: string): Promise<void> {
|
|
124
|
+
const { config } = loadConfig();
|
|
125
|
+
const bots = config.bots || [];
|
|
126
|
+
const bot = bots.find(b => b.name === name);
|
|
127
|
+
|
|
128
|
+
if (!bot) {
|
|
129
|
+
console.error(`❌ Bot "${name}" not found`);
|
|
130
|
+
process.exit(1);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
console.log(`\n📋 Bot: ${bot.name}\n`);
|
|
134
|
+
console.log(` ID: ${bot.id || '(auto-generated)'}`);
|
|
135
|
+
console.log(` IM: ${bot.im || "(not set)"}`);
|
|
136
|
+
console.log(` Backend: ${bot.backend}`);
|
|
137
|
+
console.log(` App ID: ${maskSecret(bot.appId)}`);
|
|
138
|
+
console.log(` App Secret: ${maskSecret(bot.appSecret)}`);
|
|
139
|
+
console.log(` Admin: ${bot.isAdmin ? '✅ Yes' : '❌ No'}`);
|
|
140
|
+
if (bot.cwd) console.log(` CWD: ${bot.cwd}`);
|
|
141
|
+
console.log();
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/** @internal — exported for testing via __TEST_MASK_SECRET */
|
|
145
|
+
function maskSecret(s: string): string {
|
|
146
|
+
if (s.length <= 8) return '***';
|
|
147
|
+
return s.slice(0, 4) + '...' + s.slice(-4);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// ================================================================
|
|
151
|
+
// config add
|
|
152
|
+
// ================================================================
|
|
153
|
+
|
|
154
|
+
export async function cmdConfigAdd(): Promise<void> {
|
|
155
|
+
console.log('\n➕ Add a new Bot\n');
|
|
156
|
+
|
|
157
|
+
// 1. Bot name
|
|
158
|
+
let name: string;
|
|
159
|
+
while (true) {
|
|
160
|
+
name = await prompt('Bot name (e.g. MyAssistantBot): ');
|
|
161
|
+
if (!name) { console.log(' ⚠️ Name is required'); continue; }
|
|
162
|
+
if (!/^[a-zA-Z0-9_-]+$/.test(name)) { console.log(' ⚠️ Name can only contain letters, numbers, hyphens, underscores'); continue; }
|
|
163
|
+
|
|
164
|
+
const { config } = loadConfig();
|
|
165
|
+
if (config.bots?.find(b => b.name === name)) {
|
|
166
|
+
console.log(` ⚠️ Bot "${name}" already exists`);
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
break;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// 2. IM platform
|
|
173
|
+
console.log('\n IM platforms:');
|
|
174
|
+
console.log(' 1. feishu (飞书/Lark)');
|
|
175
|
+
console.log(' 2. telegram (Telegram)');
|
|
176
|
+
console.log(' 3. wecom (企业微信)');
|
|
177
|
+
console.log(' 4. wechat (个人微信)');
|
|
178
|
+
let im: string;
|
|
179
|
+
while (true) {
|
|
180
|
+
im = await prompt('\n IM platform (1-4 or name): ');
|
|
181
|
+
const numMap: Record<string, string> = { '1': 'feishu', '2': 'telegram', '3': 'wecom', '4': 'wechat' };
|
|
182
|
+
im = numMap[im] || im.toLowerCase();
|
|
183
|
+
if (VALID_IMS.includes(im as IMType)) break;
|
|
184
|
+
console.log(' ⚠️ Valid options: feishu, telegram, wecom, wechat');
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// 3. IM credentials
|
|
188
|
+
let appId = await prompt(`\n App ID (飞书 App ID / Telegram Bot Token / etc): `);
|
|
189
|
+
if (!appId) { console.log(' ⚠️ App ID is required'); process.exit(1); }
|
|
190
|
+
|
|
191
|
+
let appSecret = await prompt(` App Secret (飞书 App Secret / leave blank for Telegram): `);
|
|
192
|
+
|
|
193
|
+
// 4. Backend
|
|
194
|
+
console.log('\n Backends:');
|
|
195
|
+
console.log(' 1. claude (Claude Code)');
|
|
196
|
+
console.log(' 2. codex (OpenAI Codex)');
|
|
197
|
+
console.log(' 3. opencode (OpenCode)');
|
|
198
|
+
let backend: string;
|
|
199
|
+
while (true) {
|
|
200
|
+
backend = await prompt('\n Backend (1-3 or name): ');
|
|
201
|
+
const numMap: Record<string, string> = { '1': 'claude', '2': 'codex', '3': 'opencode' };
|
|
202
|
+
backend = numMap[backend] || backend.toLowerCase();
|
|
203
|
+
if (VALID_BACKENDS.includes(backend as BackendType)) break;
|
|
204
|
+
console.log(' ⚠️ Valid options: claude, codex, opencode');
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// 5. Working directory (optional)
|
|
208
|
+
const cwd = await prompt('\n Working directory (leave blank for default): ');
|
|
209
|
+
|
|
210
|
+
// Summary
|
|
211
|
+
console.log('\n── Summary ──');
|
|
212
|
+
console.log(` Name: ${name}`);
|
|
213
|
+
console.log(` IM: ${im}`);
|
|
214
|
+
console.log(` App ID: ${maskSecret(appId)}`);
|
|
215
|
+
console.log(` App Secret: ${appSecret ? maskSecret(appSecret) : '(not set)'}`);
|
|
216
|
+
console.log(` Backend: ${backend}`);
|
|
217
|
+
if (cwd) console.log(` CWD: ${cwd}`);
|
|
218
|
+
console.log();
|
|
219
|
+
|
|
220
|
+
const ok = await confirm('Create this Bot?');
|
|
221
|
+
if (!ok) { console.log(' Cancelled.'); rl.close(); return; }
|
|
222
|
+
|
|
223
|
+
// Apply
|
|
224
|
+
const { config, configPath } = loadConfig();
|
|
225
|
+
if (!config.bots) config.bots = [];
|
|
226
|
+
|
|
227
|
+
const newBot: BotEntry = {
|
|
228
|
+
id: crypto.randomUUID(),
|
|
229
|
+
name,
|
|
230
|
+
im,
|
|
231
|
+
appId,
|
|
232
|
+
appSecret: appSecret || '',
|
|
233
|
+
backend,
|
|
234
|
+
isAdmin: false, // 通过 CLI 添加的 Bot 默认非 admin
|
|
235
|
+
...(cwd ? { cwd } : {}),
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
config.bots.push(newBot);
|
|
239
|
+
saveConfig(config, configPath);
|
|
240
|
+
|
|
241
|
+
console.log(`\n✅ Bot "${name}" created!`);
|
|
242
|
+
console.log(` Run "imtoagent restore" to hot-reload the gateway.\n`);
|
|
243
|
+
rl.close();
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// ================================================================
|
|
247
|
+
// config remove NAME
|
|
248
|
+
// ================================================================
|
|
249
|
+
|
|
250
|
+
export async function cmdConfigRemove(name: string): Promise<void> {
|
|
251
|
+
const { config, configPath } = loadConfig();
|
|
252
|
+
const bots = config.bots || [];
|
|
253
|
+
const idx = bots.findIndex(b => b.name === name);
|
|
254
|
+
|
|
255
|
+
if (idx === -1) {
|
|
256
|
+
console.error(`❌ Bot "${name}" not found`);
|
|
257
|
+
process.exit(1);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const bot = bots[idx];
|
|
261
|
+
console.log(`\n🗑️ Remove Bot: ${name}`);
|
|
262
|
+
console.log(` IM: ${bot.im || "(not set)"} | Backend: ${bot.backend}\n`);
|
|
263
|
+
|
|
264
|
+
const ok = await confirm('Are you sure? This cannot be undone');
|
|
265
|
+
if (!ok) { console.log(' Cancelled.'); rl.close(); return; }
|
|
266
|
+
|
|
267
|
+
config.bots = bots.filter((_, i) => i !== idx);
|
|
268
|
+
saveConfig(config, configPath);
|
|
269
|
+
|
|
270
|
+
console.log(`\n✅ Bot "${name}" removed`);
|
|
271
|
+
console.log(` Run "imtoagent restore" to hot-reload the gateway.\n`);
|
|
272
|
+
rl.close();
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// ================================================================
|
|
276
|
+
// config modify NAME
|
|
277
|
+
// ================================================================
|
|
278
|
+
|
|
279
|
+
export async function cmdConfigModify(name: string): Promise<void> {
|
|
280
|
+
const { config, configPath } = loadConfig();
|
|
281
|
+
const bot = config.bots?.find(b => b.name === name);
|
|
282
|
+
|
|
283
|
+
if (!bot) {
|
|
284
|
+
console.error(`❌ Bot "${name}" not found`);
|
|
285
|
+
process.exit(1);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
console.log(`\n✏️ Modify Bot: ${name}\n`);
|
|
289
|
+
console.log(` Current settings:`);
|
|
290
|
+
console.log(` IM: ${bot.im || "(not set)"}`);
|
|
291
|
+
console.log(` App ID: ${maskSecret(bot.appId)}`);
|
|
292
|
+
console.log(` App Secret: ${bot.appSecret ? maskSecret(bot.appSecret) : '(not set)'}`);
|
|
293
|
+
console.log(` Backend: ${bot.backend}`);
|
|
294
|
+
console.log(` CWD: ${bot.cwd || '(default)'}`);
|
|
295
|
+
console.log();
|
|
296
|
+
|
|
297
|
+
console.log(' What do you want to change?');
|
|
298
|
+
console.log(' 1. IM platform');
|
|
299
|
+
console.log(' 2. App ID');
|
|
300
|
+
console.log(' 3. App Secret');
|
|
301
|
+
console.log(' 4. Backend');
|
|
302
|
+
console.log(' 5. Working directory');
|
|
303
|
+
console.log(` 6. Admin status (${bot.isAdmin ? 'ON' : 'OFF'})`);
|
|
304
|
+
console.log(' 7. Quit');
|
|
305
|
+
|
|
306
|
+
const choice = await prompt('\n Choice (1-7): ');
|
|
307
|
+
|
|
308
|
+
switch (choice) {
|
|
309
|
+
case '1': {
|
|
310
|
+
console.log('\n Valid: feishu, telegram, wecom, wechat');
|
|
311
|
+
const newIm = await prompt(` New IM platform (current: ${bot.im}): `);
|
|
312
|
+
if (newIm && VALID_IMS.includes(newIm as IMType)) bot.im = newIm;
|
|
313
|
+
break;
|
|
314
|
+
}
|
|
315
|
+
case '2': {
|
|
316
|
+
const newAppId = await prompt(` New App ID (current: ${maskSecret(bot.appId)}): `);
|
|
317
|
+
if (newAppId) bot.appId = newAppId;
|
|
318
|
+
break;
|
|
319
|
+
}
|
|
320
|
+
case '3': {
|
|
321
|
+
const newSecret = await prompt(` New App Secret: `);
|
|
322
|
+
if (newSecret) bot.appSecret = newSecret;
|
|
323
|
+
break;
|
|
324
|
+
}
|
|
325
|
+
case '4': {
|
|
326
|
+
console.log('\n Valid: claude, codex, opencode');
|
|
327
|
+
const newBackend = await prompt(` New backend (current: ${bot.backend}): `);
|
|
328
|
+
if (newBackend && VALID_BACKENDS.includes(newBackend as BackendType)) bot.backend = newBackend;
|
|
329
|
+
break;
|
|
330
|
+
}
|
|
331
|
+
case '5': {
|
|
332
|
+
const newCwd = await prompt(` New working directory (current: ${bot.cwd || 'default'}): `);
|
|
333
|
+
if (newCwd) bot.cwd = newCwd;
|
|
334
|
+
else if (newCwd === '') delete bot.cwd;
|
|
335
|
+
break;
|
|
336
|
+
}
|
|
337
|
+
case '6': {
|
|
338
|
+
const current = bot.isAdmin ? 'ON' : 'OFF';
|
|
339
|
+
const newVal = await prompt(` Toggle admin status (current: ${current}, yes/no): `);
|
|
340
|
+
if (newVal && (newVal.toLowerCase() === 'yes' || newVal.toLowerCase() === 'y')) {
|
|
341
|
+
bot.isAdmin = !bot.isAdmin;
|
|
342
|
+
console.log(` Admin status → ${bot.isAdmin ? 'ON' : 'OFF'}`);
|
|
343
|
+
}
|
|
344
|
+
break;
|
|
345
|
+
}
|
|
346
|
+
case '7':
|
|
347
|
+
console.log(' No changes.');
|
|
348
|
+
rl.close();
|
|
349
|
+
return;
|
|
350
|
+
default:
|
|
351
|
+
console.log(' Invalid choice.');
|
|
352
|
+
rl.close();
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
saveConfig(config, configPath);
|
|
357
|
+
console.log(`\n✅ Bot "${name}" updated`);
|
|
358
|
+
console.log(` Run "imtoagent restore" to hot-reload the gateway.\n`);
|
|
359
|
+
rl.close();
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// ================================================================
|
|
363
|
+
// Test exports (NOT for production use)
|
|
364
|
+
// ================================================================
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* Export maskSecret for testing only.
|
|
368
|
+
*/
|
|
369
|
+
export const __test_maskSecret = maskSecret;
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Export constants for validate command / testing.
|
|
373
|
+
*/
|
|
374
|
+
export { VALID_BACKENDS, VALID_IMS };
|