@vrs-soft/wecom-aibot-mcp 1.5.0 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +50 -337
- package/dist/bin.js +172 -85
- package/dist/channel-server.d.ts +15 -0
- package/dist/channel-server.js +492 -0
- package/dist/channel-server.test.d.ts +5 -0
- package/dist/channel-server.test.js +324 -0
- package/dist/client-pool.js +4 -3
- package/dist/client.js +49 -49
- package/dist/config-wizard.d.ts +13 -3
- package/dist/config-wizard.js +424 -141
- package/dist/connection-log.js +7 -6
- package/dist/connection-manager.d.ts +0 -8
- package/dist/connection-manager.js +11 -23
- package/dist/daemon.js +7 -6
- package/dist/headless-state.d.ts +0 -12
- package/dist/headless-state.js +11 -35
- package/dist/http-server.d.ts +21 -2
- package/dist/http-server.js +421 -88
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/keepalive-monitor.js +5 -4
- package/dist/logger.d.ts +51 -0
- package/dist/logger.js +84 -0
- package/dist/message-bus.d.ts +13 -1
- package/dist/message-bus.js +56 -3
- package/dist/project-config.d.ts +16 -0
- package/dist/project-config.js +91 -12
- package/dist/tools/index.js +226 -57
- package/package.json +1 -1
- package/skills/headless-mode/SKILL.md +136 -181
package/dist/config-wizard.js
CHANGED
|
@@ -12,17 +12,19 @@ import * as fs from 'fs';
|
|
|
12
12
|
import * as path from 'path';
|
|
13
13
|
import * as os from 'os';
|
|
14
14
|
import { fileURLToPath } from 'url';
|
|
15
|
+
import { logger } from './logger.js';
|
|
15
16
|
const CONFIG_DIR = path.join(os.homedir(), '.wecom-aibot-mcp');
|
|
16
17
|
const BOT_CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
|
|
17
18
|
const VERSION_FILE = path.join(CONFIG_DIR, 'version.json');
|
|
18
19
|
const CLAUDE_CONFIG_FILE = path.join(os.homedir(), '.claude.json');
|
|
19
20
|
const CLAUDE_SETTINGS_FILE = path.join(os.homedir(), '.claude', 'settings.local.json');
|
|
20
21
|
const HOOK_SCRIPT_PATH = path.join(CONFIG_DIR, 'permission-hook.sh');
|
|
21
|
-
|
|
22
|
-
const VERSION = '1.4.2';
|
|
22
|
+
const TASK_COMPLETED_HOOK_SCRIPT_PATH = path.join(CONFIG_DIR, 'task-completed-hook.sh');
|
|
23
23
|
// Skill 模板路径(包内)- 使用 fileURLToPath 确保跨平台兼容
|
|
24
24
|
const __filename = fileURLToPath(import.meta.url);
|
|
25
25
|
const __dirname = path.dirname(__filename);
|
|
26
|
+
// 版本号(从 package.json 读取)
|
|
27
|
+
const VERSION = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'package.json'), 'utf-8')).version;
|
|
26
28
|
const SKILL_TEMPLATE_DIR = path.join(__dirname, '..', 'skills', 'headless-mode');
|
|
27
29
|
const SKILL_TEMPLATE_FILE = path.join(SKILL_TEMPLATE_DIR, 'SKILL.md');
|
|
28
30
|
// MCP 工具权限列表(需要预授权以避免 headless 模式阻断)
|
|
@@ -52,7 +54,7 @@ export function loadConfig() {
|
|
|
52
54
|
}
|
|
53
55
|
}
|
|
54
56
|
catch (err) {
|
|
55
|
-
|
|
57
|
+
logger.error('[config] 读取配置失败:', err);
|
|
56
58
|
}
|
|
57
59
|
return null;
|
|
58
60
|
}
|
|
@@ -79,7 +81,7 @@ export function deleteConfig() {
|
|
|
79
81
|
}
|
|
80
82
|
}
|
|
81
83
|
catch (err) {
|
|
82
|
-
|
|
84
|
+
logger.error('[config] 删除配置失败:', err);
|
|
83
85
|
}
|
|
84
86
|
}
|
|
85
87
|
// 删除 PermissionRequest hook(从 ~/.claude/settings.local.json)
|
|
@@ -105,7 +107,7 @@ export function deleteHook() {
|
|
|
105
107
|
}
|
|
106
108
|
}
|
|
107
109
|
catch (err) {
|
|
108
|
-
|
|
110
|
+
logger.error('[config] 删除 hook 失败:', err);
|
|
109
111
|
}
|
|
110
112
|
}
|
|
111
113
|
// 删除 skill 文件
|
|
@@ -118,10 +120,80 @@ export function deleteSkills() {
|
|
|
118
120
|
}
|
|
119
121
|
}
|
|
120
122
|
catch (err) {
|
|
121
|
-
|
|
123
|
+
logger.error('[config] 删除 skill 失败:', err);
|
|
122
124
|
}
|
|
123
125
|
}
|
|
124
|
-
//
|
|
126
|
+
// 删除单个机器人配置(按名称)
|
|
127
|
+
export function deleteRobotConfig(robotName) {
|
|
128
|
+
try {
|
|
129
|
+
const robots = listAllRobots();
|
|
130
|
+
const robot = robots.find(r => r.name === robotName);
|
|
131
|
+
if (!robot) {
|
|
132
|
+
console.log(`[config] 机器人 "${robotName}" 不存在`);
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
135
|
+
// 查找机器人对应的配置文件
|
|
136
|
+
let configFile = null;
|
|
137
|
+
let isDefault = false;
|
|
138
|
+
// 检查是否是默认机器人(config.json)
|
|
139
|
+
if (fs.existsSync(BOT_CONFIG_FILE)) {
|
|
140
|
+
const config = JSON.parse(fs.readFileSync(BOT_CONFIG_FILE, 'utf-8'));
|
|
141
|
+
const name = config.nameTag || `机器人-${config.botId?.slice(0, 8) || 'unknown'}`;
|
|
142
|
+
if (name === robotName) {
|
|
143
|
+
configFile = BOT_CONFIG_FILE;
|
|
144
|
+
isDefault = true;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
// 检查其他机器人配置文件
|
|
148
|
+
if (!configFile && fs.existsSync(CONFIG_DIR)) {
|
|
149
|
+
const files = fs.readdirSync(CONFIG_DIR).filter(f => f.startsWith('robot-') && f.endsWith('.json'));
|
|
150
|
+
for (const file of files) {
|
|
151
|
+
const filePath = path.join(CONFIG_DIR, file);
|
|
152
|
+
const config = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
|
|
153
|
+
const name = config.nameTag || file.replace('.json', '');
|
|
154
|
+
if (name === robotName) {
|
|
155
|
+
configFile = filePath;
|
|
156
|
+
break;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
if (!configFile) {
|
|
161
|
+
console.log(`[config] 未找到机器人 "${robotName}" 的配置文件`);
|
|
162
|
+
return false;
|
|
163
|
+
}
|
|
164
|
+
// 如果是默认机器人,需要处理迁移
|
|
165
|
+
if (isDefault) {
|
|
166
|
+
// 查找其他机器人配置文件
|
|
167
|
+
const otherRobotFiles = fs.existsSync(CONFIG_DIR)
|
|
168
|
+
? fs.readdirSync(CONFIG_DIR).filter(f => f.startsWith('robot-') && f.endsWith('.json'))
|
|
169
|
+
: [];
|
|
170
|
+
if (otherRobotFiles.length > 0) {
|
|
171
|
+
// 将第一个其他机器人提升为默认
|
|
172
|
+
const newDefaultFile = path.join(CONFIG_DIR, otherRobotFiles[0]);
|
|
173
|
+
const newDefaultConfig = JSON.parse(fs.readFileSync(newDefaultFile, 'utf-8'));
|
|
174
|
+
fs.writeFileSync(BOT_CONFIG_FILE, JSON.stringify(newDefaultConfig, null, 2));
|
|
175
|
+
fs.unlinkSync(newDefaultFile);
|
|
176
|
+
console.log(`[config] 已将 "${newDefaultConfig.nameTag || otherRobotFiles[0]}" 提升为默认机器人`);
|
|
177
|
+
}
|
|
178
|
+
else {
|
|
179
|
+
// 没有其他机器人,直接删除默认配置
|
|
180
|
+
fs.unlinkSync(BOT_CONFIG_FILE);
|
|
181
|
+
console.log('[config] 已删除最后一个机器人配置');
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
else {
|
|
185
|
+
// 不是默认机器人,直接删除
|
|
186
|
+
fs.unlinkSync(configFile);
|
|
187
|
+
}
|
|
188
|
+
console.log(`[config] 已删除机器人: ${robotName}`);
|
|
189
|
+
return true;
|
|
190
|
+
}
|
|
191
|
+
catch (err) {
|
|
192
|
+
logger.error('[config] 删除机器人配置失败:', err);
|
|
193
|
+
return false;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
// 删除单个 MCP 配置(按实例名)- 已弃用,保留用于 --uninstall
|
|
125
197
|
export function deleteMcpConfig(instanceName) {
|
|
126
198
|
try {
|
|
127
199
|
if (!fs.existsSync(CLAUDE_CONFIG_FILE)) {
|
|
@@ -146,45 +218,54 @@ export function deleteMcpConfig(instanceName) {
|
|
|
146
218
|
return true;
|
|
147
219
|
}
|
|
148
220
|
catch (err) {
|
|
149
|
-
|
|
221
|
+
logger.error('[config] 删除配置失败:', err);
|
|
150
222
|
return false;
|
|
151
223
|
}
|
|
152
224
|
}
|
|
153
225
|
// 交互式删除机器人配置
|
|
154
|
-
export async function
|
|
155
|
-
const
|
|
156
|
-
if (
|
|
226
|
+
export async function deleteRobotConfigInteractive(robotName) {
|
|
227
|
+
const robots = listAllRobots();
|
|
228
|
+
if (robots.length === 0) {
|
|
157
229
|
console.log('[config] 没有找到任何企业微信机器人配置');
|
|
158
230
|
return;
|
|
159
231
|
}
|
|
160
|
-
//
|
|
161
|
-
if (instanceName) {
|
|
162
|
-
deleteMcpConfig(instanceName);
|
|
163
|
-
return;
|
|
164
|
-
}
|
|
165
|
-
// 否则显示列表让用户选择
|
|
232
|
+
// 始终显示列表
|
|
166
233
|
console.log('\n企业微信机器人配置列表:\n');
|
|
167
|
-
|
|
168
|
-
|
|
234
|
+
robots.forEach((robot, idx) => {
|
|
235
|
+
const isDefault = idx === 0;
|
|
236
|
+
const defaultTag = isDefault ? ' [默认]' : '';
|
|
237
|
+
console.log(` ${idx + 1}. ${robot.name}${defaultTag} (Bot ID: ${robot.botId.slice(0, 12)}..., 用户: ${robot.targetUserId})`);
|
|
169
238
|
});
|
|
239
|
+
// 如果提供了机器人名称,验证并删除
|
|
240
|
+
if (robotName) {
|
|
241
|
+
const found = robots.find(r => r.name === robotName);
|
|
242
|
+
if (!found) {
|
|
243
|
+
console.log(`\n[config] 未找到名为 "${robotName}" 的机器人`);
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
console.log(`\n[config] 将删除机器人: ${robotName}`);
|
|
247
|
+
deleteRobotConfig(robotName);
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
// 没有提供名称,让用户选择
|
|
170
251
|
console.log(` 0. 取消\n`);
|
|
171
252
|
const rl = createRL();
|
|
172
253
|
try {
|
|
173
|
-
const choice = await question(rl, '
|
|
254
|
+
const choice = await question(rl, '请选择要删除的机器人序号: ');
|
|
174
255
|
const choiceNum = parseInt(choice);
|
|
175
256
|
if (choiceNum === 0) {
|
|
176
257
|
console.log('[config] 已取消');
|
|
177
258
|
return;
|
|
178
259
|
}
|
|
179
|
-
if (choiceNum < 1 || choiceNum >
|
|
260
|
+
if (choiceNum < 1 || choiceNum > robots.length) {
|
|
180
261
|
console.log('[config] 无效选择');
|
|
181
262
|
return;
|
|
182
263
|
}
|
|
183
|
-
const selected =
|
|
184
|
-
const confirm = await question(rl,
|
|
264
|
+
const selected = robots[choiceNum - 1];
|
|
265
|
+
const confirm = await question(rl, `确认删除机器人 "${selected.name}"?(y/N): `);
|
|
185
266
|
if (confirm.toLowerCase() === 'y') {
|
|
186
|
-
|
|
187
|
-
console.log(`[config]
|
|
267
|
+
deleteRobotConfig(selected.name);
|
|
268
|
+
console.log(`[config] MCP 配置保留,其他机器人仍可正常使用\n`);
|
|
188
269
|
}
|
|
189
270
|
else {
|
|
190
271
|
console.log('[config] 已取消');
|
|
@@ -194,6 +275,11 @@ export async function deleteMcpConfigInteractive(instanceName) {
|
|
|
194
275
|
rl.close();
|
|
195
276
|
}
|
|
196
277
|
}
|
|
278
|
+
// 交互式删除 MCP 配置(已弃用,保留用于兼容)
|
|
279
|
+
export async function deleteMcpConfigInteractive(instanceName) {
|
|
280
|
+
// 转换为删除机器人配置
|
|
281
|
+
await deleteRobotConfigInteractive(instanceName);
|
|
282
|
+
}
|
|
197
283
|
// 完全卸载(删除所有相关配置)
|
|
198
284
|
export function uninstall() {
|
|
199
285
|
console.log('\n[config] 开始卸载 wecom-aibot-mcp...\n');
|
|
@@ -208,7 +294,7 @@ export function uninstall() {
|
|
|
208
294
|
console.log('[config] 已删除 headless 状态索引');
|
|
209
295
|
}
|
|
210
296
|
catch (err) {
|
|
211
|
-
|
|
297
|
+
logger.error('[config] 删除 headless 状态索引失败:', err);
|
|
212
298
|
}
|
|
213
299
|
}
|
|
214
300
|
// 删除整个配置目录(包括 config.json、robot-*.json、hook 脚本、日志等)
|
|
@@ -242,7 +328,7 @@ export function uninstall() {
|
|
|
242
328
|
}
|
|
243
329
|
}
|
|
244
330
|
catch (err) {
|
|
245
|
-
|
|
331
|
+
logger.error('[config] 删除配置目录失败:', err);
|
|
246
332
|
}
|
|
247
333
|
}
|
|
248
334
|
console.log('\n[config] 卸载完成');
|
|
@@ -471,11 +557,91 @@ fi
|
|
|
471
557
|
fs.writeFileSync(HOOK_SCRIPT_PATH, script, { mode: 0o755 });
|
|
472
558
|
console.log(`[config] Hook 脚本已写入: ${HOOK_SCRIPT_PATH}`);
|
|
473
559
|
}
|
|
560
|
+
// 生成并写入 TaskCompleted hook 脚本
|
|
561
|
+
// 用于任务完成后自动恢复微信消息轮询
|
|
562
|
+
function writeTaskCompletedHookScript() {
|
|
563
|
+
const script = `#!/bin/bash
|
|
564
|
+
# wecom-aibot-mcp TaskCompleted hook
|
|
565
|
+
# 任务完成后检查是否需要恢复微信消息轮询
|
|
566
|
+
#
|
|
567
|
+
# 固定端口: 18963
|
|
568
|
+
# 检查 $(pwd)/.claude/wecom-aibot.json 的 wechatMode 和 autoApprove 字段
|
|
569
|
+
|
|
570
|
+
MCP_PORT=18963
|
|
571
|
+
|
|
572
|
+
# 先保存输入(TaskCompleted 事件数据)
|
|
573
|
+
INPUT=$(cat)
|
|
574
|
+
|
|
575
|
+
# 日志输出:--debug 模式下输出到 stderr,否则静默
|
|
576
|
+
DEBUG_FILE="$HOME/.wecom-aibot-mcp/debug"
|
|
577
|
+
log_debug() {
|
|
578
|
+
if [[ -f "$DEBUG_FILE" ]]; then
|
|
579
|
+
echo "$1" >&2
|
|
580
|
+
fi
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
log_debug "[$(date)] TaskCompleted hook called. INPUT: \${INPUT:0:200}"
|
|
584
|
+
|
|
585
|
+
# 检查项目目录的微信模式配置文件
|
|
586
|
+
PROJECT_DIR=$(pwd)
|
|
587
|
+
CONFIG_FILE="$PROJECT_DIR/.claude/wecom-aibot.json"
|
|
588
|
+
|
|
589
|
+
log_debug "[$(date)] Checking config: $CONFIG_FILE"
|
|
590
|
+
|
|
591
|
+
# 配置文件不存在,不在微信模式
|
|
592
|
+
if [[ ! -f "$CONFIG_FILE" ]]; then
|
|
593
|
+
log_debug "[$(date)] No config file, exit 0 (allow complete)"
|
|
594
|
+
exit 0
|
|
595
|
+
fi
|
|
596
|
+
|
|
597
|
+
# 检查 wechatMode 是否为 true(微信模式开关)
|
|
598
|
+
WECHAT_MODE=$(jq -r '.wechatMode // false' "$CONFIG_FILE" 2>/dev/null)
|
|
599
|
+
log_debug "[$(date)] wechatMode: $WECHAT_MODE"
|
|
600
|
+
if [[ "$WECHAT_MODE" != "true" ]]; then
|
|
601
|
+
log_debug "[$(date)] wechatMode not true, exit 0 (allow complete)"
|
|
602
|
+
exit 0
|
|
603
|
+
fi
|
|
604
|
+
|
|
605
|
+
# 检查 autoApprove 是否为 true(需要恢复轮询的模式)
|
|
606
|
+
AUTO_APPROVE=$(jq -r '.autoApprove // false' "$CONFIG_FILE" 2>/dev/null)
|
|
607
|
+
log_debug "[$(date)] autoApprove: $AUTO_APPROVE"
|
|
608
|
+
if [[ "$AUTO_APPROVE" != "true" ]]; then
|
|
609
|
+
log_debug "[$(date)] autoApprove not true, exit 0 (allow complete)"
|
|
610
|
+
exit 0
|
|
611
|
+
fi
|
|
612
|
+
|
|
613
|
+
# 检查 MCP Server 是否在线
|
|
614
|
+
HEALTH=$(curl -s -m 2 "http://127.0.0.1:$MCP_PORT/health" 2>/dev/null)
|
|
615
|
+
log_debug "[$(date)] Health check: $HEALTH"
|
|
616
|
+
if ! echo "$HEALTH" | jq -e '.status == "ok"' > /dev/null 2>&1; then
|
|
617
|
+
log_debug "[$(date)] MCP Server offline, exit 0 (allow complete)"
|
|
618
|
+
exit 0
|
|
619
|
+
fi
|
|
620
|
+
|
|
621
|
+
# 获取 ccId
|
|
622
|
+
CC_ID=$(jq -r '.ccId // empty' "$CONFIG_FILE" 2>/dev/null)
|
|
623
|
+
log_debug "[$(date)] ccId: $CC_ID"
|
|
624
|
+
if [[ -z "$CC_ID" ]]; then
|
|
625
|
+
log_debug "[$(date)] No ccId in config, exit 0 (allow complete)"
|
|
626
|
+
exit 0
|
|
627
|
+
fi
|
|
628
|
+
|
|
629
|
+
# 处于微信模式且 autoApprove 为 true,需要恢复轮询
|
|
630
|
+
# 使用 exit code 2 阻止任务完成,并提示 Claude 调用 MCP 工具
|
|
631
|
+
log_debug "[$(date)] ✅ WeChat mode active, blocking completion to resume polling"
|
|
632
|
+
log_debug "[$(date)] ccId=$CC_ID, will prompt Claude to call get_pending_messages"
|
|
633
|
+
echo "任务已完成,请调用 mcp__wecom-aibot__get_pending_messages(cc_id=\"$CC_ID\", timeout_ms=30000) 恢复微信消息轮询" >&2
|
|
634
|
+
exit 2
|
|
635
|
+
`;
|
|
636
|
+
ensureConfigDir();
|
|
637
|
+
fs.writeFileSync(TASK_COMPLETED_HOOK_SCRIPT_PATH, script, { mode: 0o755 });
|
|
638
|
+
console.log(`[config] TaskCompleted Hook 脚本已写入: ${TASK_COMPLETED_HOOK_SCRIPT_PATH}`);
|
|
639
|
+
}
|
|
474
640
|
// 写入 MCP Server 配置到 ~/.claude.json
|
|
475
641
|
function writeMcpServerConfig(config, instanceName) {
|
|
476
642
|
try {
|
|
477
|
-
// 1. 写入机器人配置到 ~/.wecom-aibot-mcp/config.json
|
|
478
643
|
ensureConfigDir();
|
|
644
|
+
// 构建机器人配置对象
|
|
479
645
|
const botConfig = {
|
|
480
646
|
botId: config.botId,
|
|
481
647
|
secret: config.secret,
|
|
@@ -484,8 +650,33 @@ function writeMcpServerConfig(config, instanceName) {
|
|
|
484
650
|
if (config.nameTag) {
|
|
485
651
|
botConfig.nameTag = config.nameTag;
|
|
486
652
|
}
|
|
487
|
-
|
|
488
|
-
|
|
653
|
+
// 检查名称唯一性(如果设置了新名称)
|
|
654
|
+
if (config.nameTag && isRobotNameExists(config.nameTag, config.botId)) {
|
|
655
|
+
console.log(`[config] ❌ 机器人名称 "${config.nameTag}" 已被其他机器人使用`);
|
|
656
|
+
console.log('[config] 请使用不同的名称');
|
|
657
|
+
return false;
|
|
658
|
+
}
|
|
659
|
+
// 按 botId 查找现有配置文件
|
|
660
|
+
const existingConfigFile = findRobotConfigFileByBotId(config.botId);
|
|
661
|
+
if (existingConfigFile) {
|
|
662
|
+
// 更新现有配置文件
|
|
663
|
+
fs.writeFileSync(existingConfigFile, JSON.stringify(botConfig, null, 2));
|
|
664
|
+
console.log(`[config] 已更新机器人配置: ${existingConfigFile}`);
|
|
665
|
+
}
|
|
666
|
+
else {
|
|
667
|
+
// 新配置:检查是否有默认配置文件
|
|
668
|
+
if (fs.existsSync(BOT_CONFIG_FILE)) {
|
|
669
|
+
// 有默认配置,创建新的 robot-*.json 文件
|
|
670
|
+
const newConfigPath = path.join(CONFIG_DIR, `robot-${Date.now()}.json`);
|
|
671
|
+
fs.writeFileSync(newConfigPath, JSON.stringify(botConfig, null, 2));
|
|
672
|
+
console.log(`[config] 已添加新机器人配置: ${newConfigPath}`);
|
|
673
|
+
}
|
|
674
|
+
else {
|
|
675
|
+
// 没有默认配置,写入 config.json
|
|
676
|
+
fs.writeFileSync(BOT_CONFIG_FILE, JSON.stringify(botConfig, null, 2));
|
|
677
|
+
console.log('[config] 已写入机器人配置 ~/.wecom-aibot-mcp/config.json');
|
|
678
|
+
}
|
|
679
|
+
}
|
|
489
680
|
// 2. 写入 MCP 配置到 ~/.claude.json(仅 URL)
|
|
490
681
|
let claudeConfig = {};
|
|
491
682
|
if (fs.existsSync(CLAUDE_CONFIG_FILE)) {
|
|
@@ -505,7 +696,7 @@ function writeMcpServerConfig(config, instanceName) {
|
|
|
505
696
|
return true;
|
|
506
697
|
}
|
|
507
698
|
catch (err) {
|
|
508
|
-
|
|
699
|
+
logger.error('[config] 写入配置失败:', err);
|
|
509
700
|
console.log('[config] ⚠️ 请手动配置:');
|
|
510
701
|
console.log('');
|
|
511
702
|
console.log('~/.wecom-aibot-mcp/config.json:');
|
|
@@ -562,11 +753,16 @@ export async function addMcpConfig() {
|
|
|
562
753
|
console.log(`[config] 如需更新配置,请使用 --config 命令`);
|
|
563
754
|
return;
|
|
564
755
|
}
|
|
565
|
-
//
|
|
756
|
+
// 检查名称是否重复(阻止)
|
|
566
757
|
const duplicateName = existingRobots.find(r => r.name === robotName);
|
|
567
758
|
if (duplicateName) {
|
|
568
|
-
console.log(`\n[config]
|
|
569
|
-
console.log(`[config]
|
|
759
|
+
console.log(`\n[config] ❌ 名称 "${robotName}" 已被使用`);
|
|
760
|
+
console.log(`[config] 请使用不同的名称以方便识别`);
|
|
761
|
+
console.log(`[config] 当前已配置的机器人:`);
|
|
762
|
+
existingRobots.forEach((r, i) => {
|
|
763
|
+
console.log(` ${i + 1}. ${r.name}`);
|
|
764
|
+
});
|
|
765
|
+
return;
|
|
570
766
|
}
|
|
571
767
|
// 先连接验证凭证
|
|
572
768
|
console.log('\n[config] 正在连接企业微信...');
|
|
@@ -631,7 +827,7 @@ export async function addMcpConfig() {
|
|
|
631
827
|
console.log('\n[config] MCP 配置无需修改,多个机器人共享同一个 HTTP 服务');
|
|
632
828
|
}
|
|
633
829
|
catch (err) {
|
|
634
|
-
|
|
830
|
+
logger.error('[config] 添加配置失败:', err);
|
|
635
831
|
rl.close();
|
|
636
832
|
}
|
|
637
833
|
}
|
|
@@ -673,44 +869,6 @@ export function listAllRobots() {
|
|
|
673
869
|
}
|
|
674
870
|
return robots;
|
|
675
871
|
}
|
|
676
|
-
// 安装 skill 文件到 ~/.claude/skills/
|
|
677
|
-
function installSkills() {
|
|
678
|
-
try {
|
|
679
|
-
const claudeSkillsDir = path.join(os.homedir(), '.claude', 'skills', 'headless-mode');
|
|
680
|
-
const skillFile = path.join(claudeSkillsDir, 'SKILL.md');
|
|
681
|
-
// 确保目录存在
|
|
682
|
-
if (!fs.existsSync(claudeSkillsDir)) {
|
|
683
|
-
fs.mkdirSync(claudeSkillsDir, { recursive: true });
|
|
684
|
-
}
|
|
685
|
-
// 尝试多个可能的源路径(支持 npm 安装和开发模式)
|
|
686
|
-
const possibleSources = [
|
|
687
|
-
// 1. 包内路径(npm 安装后的位置)
|
|
688
|
-
SKILL_TEMPLATE_FILE,
|
|
689
|
-
// 2. 开发模式路径
|
|
690
|
-
path.join(process.cwd(), 'skills', 'headless-mode', 'SKILL.md'),
|
|
691
|
-
// 3. 相对于 dist 目录的路径
|
|
692
|
-
path.join(__dirname, '..', 'skills', 'headless-mode', 'SKILL.md'),
|
|
693
|
-
];
|
|
694
|
-
let sourceFile = null;
|
|
695
|
-
for (const src of possibleSources) {
|
|
696
|
-
if (fs.existsSync(src)) {
|
|
697
|
-
sourceFile = src;
|
|
698
|
-
break;
|
|
699
|
-
}
|
|
700
|
-
}
|
|
701
|
-
if (sourceFile) {
|
|
702
|
-
fs.copyFileSync(sourceFile, skillFile);
|
|
703
|
-
console.log(`[config] skill 文件已安装: ${skillFile}`);
|
|
704
|
-
}
|
|
705
|
-
else {
|
|
706
|
-
console.log('[config] ⚠️ skill 文件未找到,请手动创建 ~/.claude/skills/headless-mode/SKILL.md');
|
|
707
|
-
console.log('[config] 查找路径:', possibleSources.join(', '));
|
|
708
|
-
}
|
|
709
|
-
}
|
|
710
|
-
catch (err) {
|
|
711
|
-
console.error('[config] 安装 skill 文件失败:', err);
|
|
712
|
-
}
|
|
713
|
-
}
|
|
714
872
|
// 写入 MCP 工具权限 + 注册 PermissionRequest hook 到 Claude settings
|
|
715
873
|
function writeMcpPermissions() {
|
|
716
874
|
try {
|
|
@@ -751,17 +909,17 @@ function writeMcpPermissions() {
|
|
|
751
909
|
writeHookScript();
|
|
752
910
|
}
|
|
753
911
|
catch (err) {
|
|
754
|
-
|
|
912
|
+
logger.error('[config] 写入配置失败:', err);
|
|
755
913
|
console.log('[config] ⚠️ 请手动配置,详见 README');
|
|
756
914
|
}
|
|
757
915
|
}
|
|
758
916
|
// 确保 hook 已安装(幂等,可多次调用)
|
|
759
917
|
export function ensureHookInstalled() {
|
|
760
918
|
writeMcpPermissions();
|
|
761
|
-
|
|
919
|
+
writeTaskCompletedHookScript();
|
|
762
920
|
}
|
|
763
921
|
// 确保所有全局配置已写入(强制覆盖,不依赖智能体)
|
|
764
|
-
export function ensureGlobalConfigs() {
|
|
922
|
+
export function ensureGlobalConfigs(mode = 'full') {
|
|
765
923
|
ensureConfigDir();
|
|
766
924
|
// 读取已安装版本
|
|
767
925
|
let previousVersion;
|
|
@@ -775,7 +933,16 @@ export function ensureGlobalConfigs() {
|
|
|
775
933
|
upgraded = true;
|
|
776
934
|
console.log(`[config] 版本升级: ${previousVersion || '未安装'} -> ${VERSION}`);
|
|
777
935
|
}
|
|
778
|
-
//
|
|
936
|
+
// http-only 模式:不写入 MCP 配置(远程部署场景)
|
|
937
|
+
if (mode === 'http-only') {
|
|
938
|
+
console.log('[config] HTTP-only 模式:跳过 MCP 配置写入');
|
|
939
|
+
// 只写权限配置和 Hook(可选,用于本地调试)
|
|
940
|
+
writeMcpPermissions();
|
|
941
|
+
console.log('[config] 已写入权限配置到 ~/.claude/settings.local.json');
|
|
942
|
+
fs.writeFileSync(VERSION_FILE, JSON.stringify({ version: VERSION, installedAt: Date.now() }, null, 2));
|
|
943
|
+
return { upgraded, previousVersion };
|
|
944
|
+
}
|
|
945
|
+
// 1. 强制写入 MCP 配置到 ~/.claude.json
|
|
779
946
|
let claudeConfig = {};
|
|
780
947
|
if (fs.existsSync(CLAUDE_CONFIG_FILE)) {
|
|
781
948
|
const content = fs.readFileSync(CLAUDE_CONFIG_FILE, 'utf-8');
|
|
@@ -783,20 +950,42 @@ export function ensureGlobalConfigs() {
|
|
|
783
950
|
}
|
|
784
951
|
if (!claudeConfig.mcpServers)
|
|
785
952
|
claudeConfig.mcpServers = {};
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
953
|
+
if (mode === 'channel-only') {
|
|
954
|
+
// Channel-only 模式:必须通过 MCP_URL 指定远程地址
|
|
955
|
+
const mcpUrl = process.env.MCP_URL;
|
|
956
|
+
if (!mcpUrl) {
|
|
957
|
+
console.log('[config] ❌ Channel-only 模式需要指定 MCP_URL');
|
|
958
|
+
console.log('[config] 请设置环境变量: MCP_URL=http://远程IP:18963');
|
|
959
|
+
return { upgraded: false, previousVersion };
|
|
960
|
+
}
|
|
961
|
+
// Channel MCP 配置:硬编码本地路径
|
|
962
|
+
claudeConfig.mcpServers['wecom-aibot-channel'] = {
|
|
963
|
+
command: 'node',
|
|
964
|
+
args: ['/Volumes/Mac_Data/VScode/wecom-aibot-mcp/dist/bin.js', '--channel'],
|
|
965
|
+
env: { MCP_URL: mcpUrl },
|
|
966
|
+
};
|
|
967
|
+
console.log(`[config] Channel-only 模式:Channel MCP 使用本地路径`);
|
|
968
|
+
}
|
|
969
|
+
else {
|
|
970
|
+
// full 模式:同时写入 HTTP MCP 和 Channel MCP 配置
|
|
971
|
+
claudeConfig.mcpServers['wecom-aibot'] = {
|
|
972
|
+
type: 'http',
|
|
973
|
+
url: 'http://127.0.0.1:18963/mcp',
|
|
974
|
+
};
|
|
975
|
+
// Channel MCP 配置:硬编码本地路径
|
|
976
|
+
claudeConfig.mcpServers['wecom-aibot-channel'] = {
|
|
977
|
+
command: 'node',
|
|
978
|
+
args: ['/Volumes/Mac_Data/VScode/wecom-aibot-mcp/dist/bin.js', '--channel'],
|
|
979
|
+
env: { MCP_URL: 'http://127.0.0.1:18963' },
|
|
980
|
+
};
|
|
981
|
+
console.log(`[config] full 模式:Channel MCP 使用本地路径`);
|
|
982
|
+
}
|
|
791
983
|
fs.writeFileSync(CLAUDE_CONFIG_FILE, JSON.stringify(claudeConfig, null, 2));
|
|
792
984
|
console.log('[config] 已写入 MCP 配置到 ~/.claude.json');
|
|
793
985
|
// 2. 强制写入权限配置和 Hook
|
|
794
986
|
writeMcpPermissions();
|
|
795
987
|
console.log('[config] 已写入权限配置到 ~/.claude/settings.local.json');
|
|
796
|
-
// 3.
|
|
797
|
-
installSkills();
|
|
798
|
-
console.log('[config] 已安装 skill 到 ~/.claude/skills/');
|
|
799
|
-
// 4. 写入版本号
|
|
988
|
+
// 3. 写入版本号
|
|
800
989
|
fs.writeFileSync(VERSION_FILE, JSON.stringify({ version: VERSION, installedAt: Date.now() }, null, 2));
|
|
801
990
|
console.log(`[config] 已记录版本号: ${VERSION}`);
|
|
802
991
|
return { upgraded, previousVersion };
|
|
@@ -805,16 +994,19 @@ export function ensureGlobalConfigs() {
|
|
|
805
994
|
export function saveConfig(config, instanceName) {
|
|
806
995
|
ensureConfigDir(); // 确保运行时文件目录存在
|
|
807
996
|
// 写入 MCP Server 配置到 ~/.claude.json
|
|
808
|
-
writeMcpServerConfig(config, instanceName);
|
|
997
|
+
const success = writeMcpServerConfig(config, instanceName);
|
|
998
|
+
if (!success) {
|
|
999
|
+
return false;
|
|
1000
|
+
}
|
|
809
1001
|
// 写入 MCP 工具权限和 Hook 到 ~/.claude/settings.local.json
|
|
810
1002
|
writeMcpPermissions();
|
|
811
|
-
// 安装 skill
|
|
812
|
-
installSkills();
|
|
813
|
-
// 安装 skill 到项目目录(项目级别)
|
|
1003
|
+
// 安装 skill 到项目目录(项目级别,支持远程部署)
|
|
814
1004
|
installSkill(process.cwd());
|
|
1005
|
+
return true;
|
|
815
1006
|
}
|
|
816
1007
|
/**
|
|
817
1008
|
* 安装 headless-mode skill 到项目目录
|
|
1009
|
+
* 返回:{ success, skillUrl? } - 如果模板不存在,返回 HTTP endpoint URL
|
|
818
1010
|
*/
|
|
819
1011
|
export function installSkill(projectDir) {
|
|
820
1012
|
const skillDir = path.join(projectDir, '.claude', 'skills', 'headless-mode');
|
|
@@ -825,12 +1017,18 @@ export function installSkill(projectDir) {
|
|
|
825
1017
|
}
|
|
826
1018
|
// 检查模板文件是否存在
|
|
827
1019
|
if (!fs.existsSync(SKILL_TEMPLATE_FILE)) {
|
|
828
|
-
console.log('[config] Skill
|
|
829
|
-
|
|
1020
|
+
console.log('[config] Skill 模板文件不存在,返回 HTTP endpoint URL');
|
|
1021
|
+
// 返回 HTTP endpoint URL,让 agent 通过 WebFetch 下载
|
|
1022
|
+
return {
|
|
1023
|
+
success: false,
|
|
1024
|
+
skillUrl: `${process.env.MCP_URL || 'http://127.0.0.1:18963'}/skill`,
|
|
1025
|
+
message: '请通过 skillUrl 下载 skill 文件并写入本地',
|
|
1026
|
+
};
|
|
830
1027
|
}
|
|
831
1028
|
// 写入 skill 文件
|
|
832
1029
|
fs.copyFileSync(SKILL_TEMPLATE_FILE, skillFile);
|
|
833
1030
|
console.log(`[config] 已安装 skill 到 ${skillFile}`);
|
|
1031
|
+
return { success: true };
|
|
834
1032
|
}
|
|
835
1033
|
// 创建 readline 接口
|
|
836
1034
|
function createRL() {
|
|
@@ -866,69 +1064,96 @@ export async function runConfigWizard() {
|
|
|
866
1064
|
console.log('╚════════════════════════════════════════════════════════════╝');
|
|
867
1065
|
const rl = createRL();
|
|
868
1066
|
try {
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
let
|
|
872
|
-
|
|
873
|
-
if (
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
1067
|
+
const robots = listAllRobots();
|
|
1068
|
+
let targetRobot = null;
|
|
1069
|
+
let isNewRobot = false;
|
|
1070
|
+
// 第一步:选择要修改的机器人
|
|
1071
|
+
if (robots.length === 0) {
|
|
1072
|
+
console.log('\n首次配置,将创建新机器人\n');
|
|
1073
|
+
isNewRobot = true;
|
|
1074
|
+
}
|
|
1075
|
+
else {
|
|
1076
|
+
console.log('\n请选择要操作的机器人:\n');
|
|
1077
|
+
robots.forEach((robot, idx) => {
|
|
1078
|
+
console.log(` ${idx + 1}. ${robot.name} (Bot ID: ${robot.botId.slice(0, 12)}...)`);
|
|
878
1079
|
});
|
|
879
|
-
console.log(` ${
|
|
1080
|
+
console.log(` ${robots.length + 1}. 添加新机器人\n`);
|
|
880
1081
|
const choice = await question(rl, '请输入序号: ');
|
|
881
1082
|
const choiceNum = parseInt(choice);
|
|
882
|
-
if (choiceNum >= 1 && choiceNum <=
|
|
883
|
-
|
|
884
|
-
console.log(`\n已选择修改: ${
|
|
1083
|
+
if (choiceNum >= 1 && choiceNum <= robots.length) {
|
|
1084
|
+
targetRobot = robots[choiceNum - 1];
|
|
1085
|
+
console.log(`\n已选择修改: ${targetRobot.name}\n`);
|
|
885
1086
|
}
|
|
886
|
-
else if (choiceNum ===
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
if (!newName) {
|
|
890
|
-
console.log('[config] 机器人名称不能为空');
|
|
891
|
-
process.exit(1);
|
|
892
|
-
}
|
|
893
|
-
robotName = newName;
|
|
894
|
-
console.log(`\n将创建新机器人: ${robotName}\n`);
|
|
1087
|
+
else if (choiceNum === robots.length + 1) {
|
|
1088
|
+
isNewRobot = true;
|
|
1089
|
+
console.log('\n将创建新机器人\n');
|
|
895
1090
|
}
|
|
896
1091
|
else {
|
|
897
1092
|
console.log('[config] 无效选择');
|
|
898
1093
|
process.exit(1);
|
|
899
1094
|
}
|
|
900
1095
|
}
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
if (!newName) {
|
|
1096
|
+
// 第二步:输入机器人名称
|
|
1097
|
+
let robotName = await question(rl, `机器人名称(${targetRobot ? `当前: ${targetRobot.name}` : '用于识别'}): `);
|
|
1098
|
+
if (!robotName) {
|
|
1099
|
+
if (targetRobot) {
|
|
1100
|
+
robotName = targetRobot.name; // 保持原名称
|
|
1101
|
+
console.log(`[config] 保持原名称: ${robotName}`);
|
|
1102
|
+
}
|
|
1103
|
+
else {
|
|
910
1104
|
console.log('[config] 机器人名称不能为空');
|
|
911
1105
|
process.exit(1);
|
|
912
1106
|
}
|
|
913
|
-
robotName = newName;
|
|
914
|
-
console.log(`\n将创建机器人: ${robotName}\n`);
|
|
915
1107
|
}
|
|
916
|
-
//
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
1108
|
+
// 检查名称是否与其他机器人重复
|
|
1109
|
+
if (isNewRobot || (targetRobot && robotName !== targetRobot.name)) {
|
|
1110
|
+
const duplicateName = robots.find(r => r.name === robotName && r !== targetRobot);
|
|
1111
|
+
if (duplicateName) {
|
|
1112
|
+
console.log(`[config] ❌ 名称 "${robotName}" 已被使用`);
|
|
1113
|
+
process.exit(1);
|
|
1114
|
+
}
|
|
921
1115
|
}
|
|
922
|
-
//
|
|
923
|
-
let
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
1116
|
+
// 第三步:输入 Bot ID
|
|
1117
|
+
let botId = await question(rl, `Bot ID(${targetRobot ? `当前: ${targetRobot.botId.slice(0, 12)}...` : '必填'}): `);
|
|
1118
|
+
if (!botId) {
|
|
1119
|
+
if (targetRobot) {
|
|
1120
|
+
botId = targetRobot.botId; // 保持原 Bot ID
|
|
1121
|
+
console.log(`[config] 保持原 Bot ID`);
|
|
1122
|
+
}
|
|
1123
|
+
else {
|
|
1124
|
+
console.log('Bot ID 不能为空');
|
|
1125
|
+
botId = await question(rl, 'Bot ID: ');
|
|
1126
|
+
if (!botId) {
|
|
1127
|
+
console.log('[config] Bot ID 不能为空');
|
|
1128
|
+
process.exit(1);
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
927
1131
|
}
|
|
928
|
-
//
|
|
1132
|
+
// 第四步:输入 Secret
|
|
1133
|
+
let secret = await question(rl, `Secret(${targetRobot ? `当前: ${targetRobot.botId.slice(0, 8)}...` : '必填'}): `);
|
|
1134
|
+
if (!secret) {
|
|
1135
|
+
if (targetRobot) {
|
|
1136
|
+
// 读取原 Secret
|
|
1137
|
+
const configFile = findRobotConfigFile(targetRobot.name);
|
|
1138
|
+
if (configFile) {
|
|
1139
|
+
const config = JSON.parse(fs.readFileSync(configFile, 'utf-8'));
|
|
1140
|
+
secret = config.secret;
|
|
1141
|
+
console.log(`[config] 保持原 Secret`);
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
else {
|
|
1145
|
+
console.log('Secret 不能为空');
|
|
1146
|
+
secret = await question(rl, 'Secret: ');
|
|
1147
|
+
if (!secret) {
|
|
1148
|
+
console.log('[config] Secret 不能为空');
|
|
1149
|
+
process.exit(1);
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
// 第五步:目标用户(稍后通过消息自动识别)
|
|
929
1154
|
console.log('\n─────────────────────────────────────');
|
|
930
1155
|
console.log('配置确认:');
|
|
931
|
-
console.log(` 机器人名称: ${robotName
|
|
1156
|
+
console.log(` 机器人名称: ${robotName}`);
|
|
932
1157
|
console.log(` Bot ID: ${botId}`);
|
|
933
1158
|
console.log(` Secret: ${secret.slice(0, 8)}...${secret.slice(-4)}`);
|
|
934
1159
|
console.log(` 目标用户: (将通过消息自动识别)`);
|
|
@@ -943,14 +1168,72 @@ export async function runConfigWizard() {
|
|
|
943
1168
|
botId,
|
|
944
1169
|
secret,
|
|
945
1170
|
targetUserId: '', // 稍后通过消息识别
|
|
946
|
-
nameTag: robotName
|
|
1171
|
+
nameTag: robotName,
|
|
947
1172
|
};
|
|
1173
|
+
// 如果是修改现有机器人,返回其 instanceName(用于删除旧配置)
|
|
1174
|
+
const instanceName = targetRobot ? targetRobot.name : 'wecom-aibot';
|
|
948
1175
|
return { config, instanceName };
|
|
949
1176
|
}
|
|
950
1177
|
finally {
|
|
951
1178
|
rl.close();
|
|
952
1179
|
}
|
|
953
1180
|
}
|
|
1181
|
+
// 查找机器人配置文件路径(按名称)
|
|
1182
|
+
export function findRobotConfigFile(robotName) {
|
|
1183
|
+
// 检查默认配置文件
|
|
1184
|
+
if (fs.existsSync(BOT_CONFIG_FILE)) {
|
|
1185
|
+
const config = JSON.parse(fs.readFileSync(BOT_CONFIG_FILE, 'utf-8'));
|
|
1186
|
+
const name = config.nameTag || `机器人-${config.botId?.slice(0, 8) || 'unknown'}`;
|
|
1187
|
+
if (name === robotName) {
|
|
1188
|
+
return BOT_CONFIG_FILE;
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1191
|
+
// 检查其他机器人配置文件
|
|
1192
|
+
if (fs.existsSync(CONFIG_DIR)) {
|
|
1193
|
+
const files = fs.readdirSync(CONFIG_DIR).filter(f => f.startsWith('robot-') && f.endsWith('.json'));
|
|
1194
|
+
for (const file of files) {
|
|
1195
|
+
const filePath = path.join(CONFIG_DIR, file);
|
|
1196
|
+
const config = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
|
|
1197
|
+
const name = config.nameTag || file.replace('.json', '');
|
|
1198
|
+
if (name === robotName) {
|
|
1199
|
+
return filePath;
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
}
|
|
1203
|
+
return null;
|
|
1204
|
+
}
|
|
1205
|
+
// 查找机器人配置文件路径(按 botId)
|
|
1206
|
+
export function findRobotConfigFileByBotId(botId) {
|
|
1207
|
+
// 检查默认配置文件
|
|
1208
|
+
if (fs.existsSync(BOT_CONFIG_FILE)) {
|
|
1209
|
+
const config = JSON.parse(fs.readFileSync(BOT_CONFIG_FILE, 'utf-8'));
|
|
1210
|
+
if (config.botId === botId) {
|
|
1211
|
+
return BOT_CONFIG_FILE;
|
|
1212
|
+
}
|
|
1213
|
+
}
|
|
1214
|
+
// 检查其他机器人配置文件
|
|
1215
|
+
if (fs.existsSync(CONFIG_DIR)) {
|
|
1216
|
+
const files = fs.readdirSync(CONFIG_DIR).filter(f => f.startsWith('robot-') && f.endsWith('.json'));
|
|
1217
|
+
for (const file of files) {
|
|
1218
|
+
const filePath = path.join(CONFIG_DIR, file);
|
|
1219
|
+
const config = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
|
|
1220
|
+
if (config.botId === botId) {
|
|
1221
|
+
return filePath;
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1225
|
+
return null;
|
|
1226
|
+
}
|
|
1227
|
+
// 检查机器人名称是否已存在(排除指定 botId)
|
|
1228
|
+
export function isRobotNameExists(name, excludeBotId) {
|
|
1229
|
+
const robots = listAllRobots();
|
|
1230
|
+
for (const robot of robots) {
|
|
1231
|
+
if (robot.name === name && robot.botId !== excludeBotId) {
|
|
1232
|
+
return true;
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1235
|
+
return false;
|
|
1236
|
+
}
|
|
954
1237
|
/**
|
|
955
1238
|
* 通过等待用户消息来识别用户 ID(使用已有的 client)
|
|
956
1239
|
*/
|
|
@@ -1029,9 +1312,9 @@ export async function getOrInitConfig() {
|
|
|
1029
1312
|
}
|
|
1030
1313
|
// 3. 非 TTY(MCP stdio 模式)不能启动交互向导
|
|
1031
1314
|
if (!process.stdin.isTTY) {
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1315
|
+
logger.error('[config] 未找到配置,且当前为非交互模式。');
|
|
1316
|
+
logger.error('[config] 请在 ~/.claude.json 的 mcpServers 中设置环境变量:');
|
|
1317
|
+
logger.error('[config] WECOM_BOT_ID, WECOM_SECRET, WECOM_TARGET_USER');
|
|
1035
1318
|
process.exit(1);
|
|
1036
1319
|
}
|
|
1037
1320
|
// 4. TTY 模式下运行配置向导
|