@wu529778790/open-im 1.11.7 → 1.11.8-beta.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/dist/setup.d.ts +9 -3
- package/dist/setup.js +86 -1149
- package/package.json +1 -1
package/dist/setup.d.ts
CHANGED
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* 首次运行配置引导
|
|
3
|
+
*
|
|
4
|
+
* Web 向导已替代大部分 CLI 交互配置。
|
|
5
|
+
* 本文件仅保留:
|
|
6
|
+
* 1. runInteractiveSetup — 启动 Web 配置页面
|
|
7
|
+
* 2. runClaudeApiSetup — Claude API 凭证配置(写入 ~/.claude/settings.json)
|
|
5
8
|
*/
|
|
6
9
|
/**
|
|
7
10
|
* Claude API 专用配置向导,保存到 ~/.claude/settings.json(与 Claude Code 共用)
|
|
8
11
|
*/
|
|
9
12
|
export declare function runClaudeApiSetup(): Promise<boolean>;
|
|
13
|
+
/**
|
|
14
|
+
* 交互式配置 — 现在直接引导到 Web 控制台
|
|
15
|
+
*/
|
|
10
16
|
export declare function runInteractiveSetup(): Promise<boolean>;
|
package/dist/setup.js
CHANGED
|
@@ -1,228 +1,106 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* 首次运行配置引导
|
|
3
|
+
*
|
|
4
|
+
* Web 向导已替代大部分 CLI 交互配置。
|
|
5
|
+
* 本文件仅保留:
|
|
6
|
+
* 1. runInteractiveSetup — 启动 Web 配置页面
|
|
7
|
+
* 2. runClaudeApiSetup — Claude API 凭证配置(写入 ~/.claude/settings.json)
|
|
5
8
|
*/
|
|
6
|
-
import prompts from
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
import { normalizeAiCommand } from "./adapters/tool-registry.js";
|
|
14
|
-
function loadExistingConfig() {
|
|
15
|
-
const configPath = join(APP_HOME, "config.json");
|
|
16
|
-
if (!existsSync(configPath))
|
|
17
|
-
return null;
|
|
18
|
-
try {
|
|
19
|
-
return JSON.parse(readFileSync(configPath, "utf-8"));
|
|
20
|
-
}
|
|
21
|
-
catch {
|
|
22
|
-
return null;
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
function getConfiguredPlatforms(existing) {
|
|
26
|
-
if (!existing?.platforms)
|
|
27
|
-
return [];
|
|
28
|
-
const names = [
|
|
29
|
-
{ k: "telegram", label: "Telegram" },
|
|
30
|
-
{ k: "qq", label: "QQ" },
|
|
31
|
-
{ k: "feishu", label: "飞书" },
|
|
32
|
-
{ k: "wework", label: "企业微信" },
|
|
33
|
-
{ k: "dingtalk", label: "钉钉" },
|
|
34
|
-
{ k: "workbuddy", label: "WorkBuddy (微信)" },
|
|
35
|
-
{ k: "clawbot", label: "ClawBot (微信 iLink)" },
|
|
36
|
-
];
|
|
37
|
-
return names
|
|
38
|
-
.filter(({ k }) => {
|
|
39
|
-
const p = existing.platforms?.[k];
|
|
40
|
-
if (!p)
|
|
41
|
-
return false;
|
|
42
|
-
if (k === "telegram")
|
|
43
|
-
return !!p.botToken;
|
|
44
|
-
if (k === "feishu")
|
|
45
|
-
return !!(p.appId && p.appSecret);
|
|
46
|
-
if (k === "qq")
|
|
47
|
-
return !!(p.appId && p.secret);
|
|
48
|
-
if (k === "workbuddy")
|
|
49
|
-
return !!(p.accessToken && p.refreshToken);
|
|
50
|
-
if (k === "wework")
|
|
51
|
-
return !!(p.corpId && p.secret);
|
|
52
|
-
if (k === "dingtalk")
|
|
53
|
-
return !!(p.clientId && p.clientSecret);
|
|
54
|
-
if (k === "clawbot")
|
|
55
|
-
return !!p.apiToken;
|
|
56
|
-
return false;
|
|
57
|
-
})
|
|
58
|
-
.map(({ label }) => label);
|
|
59
|
-
}
|
|
60
|
-
function defaultPlatformAi(v) {
|
|
61
|
-
return normalizeAiCommand(v, "claude");
|
|
62
|
-
}
|
|
63
|
-
function question(prompt) {
|
|
64
|
-
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
65
|
-
return new Promise((resolve) => {
|
|
66
|
-
rl.question(prompt, (answer) => {
|
|
67
|
-
rl.close();
|
|
68
|
-
resolve(answer.trim());
|
|
69
|
-
});
|
|
70
|
-
});
|
|
71
|
-
}
|
|
72
|
-
function printManualInstructions(configPath) {
|
|
73
|
-
console.log("\n━━━ open-im 首次配置 ━━━\n");
|
|
74
|
-
console.log("当前环境不支持交互输入,请手动创建配置文件:");
|
|
75
|
-
console.log("");
|
|
76
|
-
console.log(" 1. 创建目录:", dirname(configPath));
|
|
77
|
-
console.log(" 2. 创建文件:", configPath);
|
|
78
|
-
console.log(" 3. 填入以下内容(替换为你的 Token/App ID 和用户 ID):");
|
|
79
|
-
console.log("");
|
|
80
|
-
console.log(`{
|
|
81
|
-
"tools": {
|
|
82
|
-
"claude": {
|
|
83
|
-
"cliPath": "claude",
|
|
84
|
-
"workDir": "${process.cwd().replace(/\\/g, "/")}"
|
|
85
|
-
},
|
|
86
|
-
"codex": { "cliPath": "codex", "workDir": "${process.cwd().replace(/\\/g, "/")}", "proxy": "http://127.0.0.1:7890" },
|
|
87
|
-
"codebuddy": { "cliPath": "codebuddy" }
|
|
88
|
-
},
|
|
89
|
-
"platforms": {
|
|
90
|
-
"telegram": {
|
|
91
|
-
"enabled": true,
|
|
92
|
-
"aiCommand": "claude",
|
|
93
|
-
"botToken": "你的 Telegram Bot Token(可选)",
|
|
94
|
-
"allowedUserIds": ["允许访问的 Telegram 用户 ID(可选)"]
|
|
95
|
-
},
|
|
96
|
-
"feishu": {
|
|
97
|
-
"enabled": false,
|
|
98
|
-
"aiCommand": "claude",
|
|
99
|
-
"appId": "你的飞书 App ID(可选)",
|
|
100
|
-
"appSecret": "你的飞书 App Secret(可选)",
|
|
101
|
-
"allowedUserIds": ["允许访问的飞书用户 ID(可选)"]
|
|
102
|
-
},
|
|
103
|
-
"qq": {
|
|
104
|
-
"enabled": false,
|
|
105
|
-
"aiCommand": "claude",
|
|
106
|
-
"appId": "你的 QQ App ID(可选)",
|
|
107
|
-
"secret": "你的 QQ App Secret(可选)",
|
|
108
|
-
"allowedUserIds": ["允许访问的 QQ 用户 ID(可选)"]
|
|
109
|
-
},
|
|
110
|
-
"wework": {
|
|
111
|
-
"enabled": false,
|
|
112
|
-
"aiCommand": "claude",
|
|
113
|
-
"corpId": "你的企业微信 Corp ID(可选)",
|
|
114
|
-
"secret": "你的企业微信 Secret(可选)",
|
|
115
|
-
"allowedUserIds": ["允许访问的企业微信用户 ID(可选)"]
|
|
116
|
-
},
|
|
117
|
-
"dingtalk": {
|
|
118
|
-
"enabled": false,
|
|
119
|
-
"aiCommand": "claude",
|
|
120
|
-
"clientId": "你的钉钉 Client ID(可选)",
|
|
121
|
-
"clientSecret": "你的钉钉 Client Secret(可选)",
|
|
122
|
-
"cardTemplateId": "你的钉钉 AI 卡片模板 ID(可选,配置后启用单条流式)",
|
|
123
|
-
"allowedUserIds": ["允许访问的钉钉用户 ID(可选)"]
|
|
124
|
-
},
|
|
125
|
-
"clawbot": {
|
|
126
|
-
"enabled": false,
|
|
127
|
-
"aiCommand": "claude",
|
|
128
|
-
"apiUrl": "https://ilinkai.weixin.qq.com",
|
|
129
|
-
"apiToken": "你的 ClawBot Bearer Token(可选)",
|
|
130
|
-
"allowedUserIds": ["允许访问的微信用户 ID(可选)"]
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
}`);
|
|
134
|
-
console.log("");
|
|
135
|
-
console.log("提示:至少需要配置 Telegram、Feishu、QQ、WeWork、DingTalk、WorkBuddy 或 ClawBot 其中一个平台");
|
|
136
|
-
console.log("或设置环境变量: TELEGRAM_BOT_TOKEN=xxx、FEISHU_APP_ID=xxx、QQ_BOT_APPID=xxx、WECHAT_WORKBUDDY_ACCESS_TOKEN=xxx、WEWORK_CORP_ID=xxx、DINGTALK_CLIENT_ID=xxx 或 CLAWBOT_API_TOKEN=xxx 后再运行");
|
|
137
|
-
console.log("");
|
|
138
|
-
}
|
|
139
|
-
const CLAUDE_SETTINGS_PATH = join(homedir(), ".claude", "settings.json");
|
|
9
|
+
import prompts from 'prompts';
|
|
10
|
+
import { mkdirSync, writeFileSync, existsSync, readFileSync } from 'node:fs';
|
|
11
|
+
import { join, dirname } from 'node:path';
|
|
12
|
+
import { homedir } from 'node:os';
|
|
13
|
+
import { createLogger } from './logger.js';
|
|
14
|
+
const log = createLogger('Setup');
|
|
15
|
+
const CLAUDE_SETTINGS_PATH = join(homedir(), '.claude', 'settings.json');
|
|
140
16
|
function loadClaudeSettings() {
|
|
141
17
|
if (!existsSync(CLAUDE_SETTINGS_PATH))
|
|
142
18
|
return {};
|
|
143
19
|
try {
|
|
144
|
-
return JSON.parse(readFileSync(CLAUDE_SETTINGS_PATH,
|
|
20
|
+
return JSON.parse(readFileSync(CLAUDE_SETTINGS_PATH, 'utf-8'));
|
|
145
21
|
}
|
|
146
22
|
catch {
|
|
147
23
|
return {};
|
|
148
24
|
}
|
|
149
25
|
}
|
|
150
|
-
/** 检查 ~/.claude/settings.json 中是否已有 API Key 或 Auth Token
|
|
26
|
+
/** 检查 ~/.claude/settings.json 中是否已有 API Key 或 Auth Token */
|
|
151
27
|
function hasClaudeCredsInSettings() {
|
|
152
28
|
const s = loadClaudeSettings();
|
|
153
29
|
const env = s?.env;
|
|
154
|
-
|
|
155
|
-
const fromTop = !!(s?.ANTHROPIC_API_KEY || s?.ANTHROPIC_AUTH_TOKEN);
|
|
156
|
-
return fromEnv || fromTop;
|
|
157
|
-
}
|
|
158
|
-
function printManualClaudeInstructions() {
|
|
159
|
-
console.log("\n━━━ Claude API 配置 ━━━\n");
|
|
160
|
-
console.log("当前环境不支持交互输入,请手动配置:");
|
|
161
|
-
console.log("");
|
|
162
|
-
console.log(" 1. 编辑 ~/.claude/settings.json(与 Claude Code 共用)");
|
|
163
|
-
console.log(" 2. 添加 env 字段,例如:");
|
|
164
|
-
console.log("");
|
|
165
|
-
console.log(' { "env": { "ANTHROPIC_API_KEY": "sk-ant-..." } }');
|
|
166
|
-
console.log("");
|
|
167
|
-
console.log(" 或使用第三方模型:");
|
|
168
|
-
console.log(' { "env": { "ANTHROPIC_AUTH_TOKEN": "xxx", "ANTHROPIC_BASE_URL": "https://...", "ANTHROPIC_MODEL": "glm-4.7" } }');
|
|
169
|
-
console.log("");
|
|
30
|
+
return !!(env?.ANTHROPIC_API_KEY || env?.ANTHROPIC_AUTH_TOKEN);
|
|
170
31
|
}
|
|
171
32
|
/**
|
|
172
33
|
* Claude API 专用配置向导,保存到 ~/.claude/settings.json(与 Claude Code 共用)
|
|
173
34
|
*/
|
|
174
35
|
export async function runClaudeApiSetup() {
|
|
175
36
|
if (!process.stdin.isTTY) {
|
|
176
|
-
|
|
37
|
+
console.log('\n━━━ Claude API 配置 ━━━\n');
|
|
38
|
+
console.log('当前环境不支持交互输入。请通过 Web 控制台配置:');
|
|
39
|
+
console.log(' 1. 运行 open-im start');
|
|
40
|
+
console.log(' 2. 打开 http://127.0.0.1:39282');
|
|
41
|
+
console.log(' 3. 在设置向导中配置 Claude API\n');
|
|
177
42
|
return false;
|
|
178
43
|
}
|
|
179
44
|
const existing = loadClaudeSettings();
|
|
180
45
|
const existingEnv = existing.env || {};
|
|
181
|
-
console.log(
|
|
182
|
-
console.log(
|
|
46
|
+
console.log('\n━━━ Claude API 配置向导 ━━━\n');
|
|
47
|
+
console.log('配置将保存到 ~/.claude/settings.json\n');
|
|
183
48
|
const onCancel = () => {
|
|
184
|
-
console.log(
|
|
49
|
+
console.log('\n已取消配置。');
|
|
185
50
|
process.exit(0);
|
|
186
51
|
};
|
|
187
52
|
const apiTypeResp = await prompts({
|
|
188
|
-
type:
|
|
189
|
-
name:
|
|
190
|
-
message:
|
|
53
|
+
type: 'select',
|
|
54
|
+
name: 'apiType',
|
|
55
|
+
message: '选择 API 类型',
|
|
191
56
|
choices: [
|
|
192
|
-
{ title:
|
|
193
|
-
{ title:
|
|
57
|
+
{ title: '官方 API(Anthropic)', value: 'official' },
|
|
58
|
+
{ title: '第三方模型 / 自定义 API', value: 'thirdparty' },
|
|
59
|
+
{ title: '跳过(已在 Web 控制台配置)', value: 'skip' },
|
|
194
60
|
],
|
|
195
61
|
initial: 0,
|
|
196
62
|
}, { onCancel });
|
|
197
|
-
if (!apiTypeResp.apiType)
|
|
63
|
+
if (!apiTypeResp.apiType || apiTypeResp.apiType === 'skip')
|
|
198
64
|
return false;
|
|
199
65
|
const env = { ...existingEnv };
|
|
200
|
-
if (apiTypeResp.apiType ===
|
|
66
|
+
if (apiTypeResp.apiType === 'official') {
|
|
201
67
|
const keyTypeResp = await prompts({
|
|
202
|
-
type:
|
|
203
|
-
name:
|
|
204
|
-
message:
|
|
68
|
+
type: 'select',
|
|
69
|
+
name: 'keyType',
|
|
70
|
+
message: '选择认证方式',
|
|
205
71
|
choices: [
|
|
206
|
-
{ title:
|
|
207
|
-
{ title:
|
|
72
|
+
{ title: 'API Key(sk-ant-...)', value: 'apikey' },
|
|
73
|
+
{ title: 'Auth Token(claude setup-token 生成)', value: 'token' },
|
|
208
74
|
],
|
|
209
75
|
initial: 0,
|
|
210
76
|
}, { onCancel });
|
|
211
|
-
if (
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
const key = await
|
|
77
|
+
if (keyTypeResp.keyType === 'apikey') {
|
|
78
|
+
const { default: readline } = await import('node:readline');
|
|
79
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
80
|
+
const key = await new Promise((resolve) => {
|
|
81
|
+
rl.question('ANTHROPIC_API_KEY: ', (answer) => {
|
|
82
|
+
rl.close();
|
|
83
|
+
resolve(answer.trim());
|
|
84
|
+
});
|
|
85
|
+
});
|
|
215
86
|
if (!key.trim()) {
|
|
216
|
-
console.log(
|
|
87
|
+
console.log('API Key 不能为空');
|
|
217
88
|
return false;
|
|
218
89
|
}
|
|
219
90
|
env.ANTHROPIC_API_KEY = key.trim();
|
|
220
91
|
delete env.ANTHROPIC_AUTH_TOKEN;
|
|
221
92
|
}
|
|
222
93
|
else {
|
|
223
|
-
const
|
|
94
|
+
const { default: readline } = await import('node:readline');
|
|
95
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
96
|
+
const token = await new Promise((resolve) => {
|
|
97
|
+
rl.question('ANTHROPIC_AUTH_TOKEN: ', (answer) => {
|
|
98
|
+
rl.close();
|
|
99
|
+
resolve(answer.trim());
|
|
100
|
+
});
|
|
101
|
+
});
|
|
224
102
|
if (!token.trim()) {
|
|
225
|
-
console.log(
|
|
103
|
+
console.log('Auth Token 不能为空');
|
|
226
104
|
return false;
|
|
227
105
|
}
|
|
228
106
|
env.ANTHROPIC_AUTH_TOKEN = token.trim();
|
|
@@ -230,991 +108,50 @@ export async function runClaudeApiSetup() {
|
|
|
230
108
|
}
|
|
231
109
|
}
|
|
232
110
|
else {
|
|
233
|
-
const
|
|
234
|
-
|
|
235
|
-
|
|
111
|
+
const { default: readline } = await import('node:readline');
|
|
112
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
113
|
+
const ask = (q) => new Promise((resolve) => rl.question(q, (a) => resolve(a.trim())));
|
|
114
|
+
const token = await ask('ANTHROPIC_AUTH_TOKEN(第三方模型 Token): ');
|
|
115
|
+
if (!token) {
|
|
116
|
+
console.log('Token 不能为空');
|
|
117
|
+
rl.close();
|
|
236
118
|
return false;
|
|
237
119
|
}
|
|
238
|
-
const baseUrl = await
|
|
239
|
-
if (!baseUrl
|
|
240
|
-
console.log(
|
|
120
|
+
const baseUrl = await ask('ANTHROPIC_BASE_URL(API 地址): ');
|
|
121
|
+
if (!baseUrl) {
|
|
122
|
+
console.log('Base URL 不能为空');
|
|
123
|
+
rl.close();
|
|
241
124
|
return false;
|
|
242
125
|
}
|
|
243
|
-
const model = await
|
|
244
|
-
if (!model
|
|
245
|
-
console.log(
|
|
126
|
+
const model = await ask('ANTHROPIC_MODEL(模型名称,如 glm-4.7): ');
|
|
127
|
+
if (!model) {
|
|
128
|
+
console.log('模型名称不能为空');
|
|
129
|
+
rl.close();
|
|
246
130
|
return false;
|
|
247
131
|
}
|
|
248
|
-
|
|
249
|
-
env.
|
|
250
|
-
env.
|
|
132
|
+
rl.close();
|
|
133
|
+
env.ANTHROPIC_AUTH_TOKEN = token;
|
|
134
|
+
env.ANTHROPIC_BASE_URL = baseUrl;
|
|
135
|
+
env.ANTHROPIC_MODEL = model;
|
|
251
136
|
delete env.ANTHROPIC_API_KEY;
|
|
252
137
|
}
|
|
253
138
|
const dir = dirname(CLAUDE_SETTINGS_PATH);
|
|
254
139
|
if (!existsSync(dir))
|
|
255
140
|
mkdirSync(dir, { recursive: true });
|
|
256
141
|
const merged = { ...existing, env };
|
|
257
|
-
writeFileSync(CLAUDE_SETTINGS_PATH, JSON.stringify(merged, null, 2),
|
|
258
|
-
console.log(
|
|
259
|
-
return true;
|
|
260
|
-
}
|
|
261
|
-
export async function runInteractiveSetup() {
|
|
262
|
-
const configPath = join(APP_HOME, "config.json");
|
|
263
|
-
const forceManual = process.argv.includes("--manual") || process.argv.includes("-m");
|
|
264
|
-
if (forceManual || !process.stdin.isTTY) {
|
|
265
|
-
printManualInstructions(configPath);
|
|
266
|
-
return false;
|
|
267
|
-
}
|
|
268
|
-
const existing = loadExistingConfig();
|
|
269
|
-
const configured = getConfiguredPlatforms(existing);
|
|
270
|
-
console.log("\n━━━ open-im 配置向导 ━━━\n");
|
|
271
|
-
console.log("配置将保存到:", configPath);
|
|
272
|
-
if (configured.length > 0) {
|
|
273
|
-
console.log("当前已配置:", configured.join("、"));
|
|
274
|
-
console.log("(本次可追加或修改,未选中的平台配置将保留)");
|
|
275
|
-
}
|
|
276
|
-
console.log("");
|
|
277
|
-
const onCancel = () => {
|
|
278
|
-
console.log("\n已取消配置。");
|
|
279
|
-
process.exit(0);
|
|
280
|
-
};
|
|
281
|
-
const hasTg = !!existing?.platforms?.telegram?.botToken;
|
|
282
|
-
const hasFs = !!(existing?.platforms?.feishu?.appId && existing?.platforms?.feishu?.appSecret);
|
|
283
|
-
const hasQq = !!(existing?.platforms?.qq?.appId && existing?.platforms?.qq?.secret);
|
|
284
|
-
const wc = existing?.platforms?.workbuddy;
|
|
285
|
-
const hasWc = !!(wc?.accessToken && wc?.refreshToken);
|
|
286
|
-
const hasWw = !!(existing?.platforms?.wework?.corpId && existing?.platforms?.wework?.secret);
|
|
287
|
-
const hasDt = !!(existing?.platforms?.dingtalk?.clientId && existing?.platforms?.dingtalk?.clientSecret);
|
|
288
|
-
// 第一步:选择平台(在选项和提示中显示已配置项)
|
|
289
|
-
const configuredHint = configured.length > 0 ? `(当前已配置: ${configured.join("、")})` : "";
|
|
290
|
-
const platformResp = await prompts({
|
|
291
|
-
type: "select",
|
|
292
|
-
name: "platform",
|
|
293
|
-
message: `选择要配置的平台 ${configuredHint}(↑↓ 选择)`,
|
|
294
|
-
choices: [
|
|
295
|
-
{
|
|
296
|
-
title: "Telegram - 需要 Bot Token" + (hasTg ? " ✓已配置" : ""),
|
|
297
|
-
value: "telegram",
|
|
298
|
-
},
|
|
299
|
-
{
|
|
300
|
-
title: "飞书 (Feishu/Lark) - 需要 App ID 和 App Secret" +
|
|
301
|
-
(hasFs ? " ✓已配置" : ""),
|
|
302
|
-
value: "feishu",
|
|
303
|
-
},
|
|
304
|
-
{
|
|
305
|
-
title: "QQ Bot - App ID + Secret" + (hasQq ? " configured" : ""),
|
|
306
|
-
value: "qq",
|
|
307
|
-
},
|
|
308
|
-
{
|
|
309
|
-
title: "企业微信 (WeCom/WeWork) - 需要 Bot ID 和 Secret" +
|
|
310
|
-
(hasWw ? " ✓已配置" : ""),
|
|
311
|
-
value: "wework",
|
|
312
|
-
},
|
|
313
|
-
{
|
|
314
|
-
title: "钉钉 (DingTalk) - 需要 Client ID 和 Client Secret" +
|
|
315
|
-
(hasDt ? " ✓已配置" : ""),
|
|
316
|
-
value: "dingtalk",
|
|
317
|
-
},
|
|
318
|
-
{
|
|
319
|
-
title: "WorkBuddy 微信客服 (WeChat KF)" +
|
|
320
|
-
(hasWc ? " ✓已配置" : ""),
|
|
321
|
-
value: "workbuddy",
|
|
322
|
-
},
|
|
323
|
-
{
|
|
324
|
-
title: "ClawBot 微信 iLink (需要 API Token)" +
|
|
325
|
-
(existing?.platforms?.clawbot?.apiToken ? " ✓已配置" : ""),
|
|
326
|
-
value: "clawbot",
|
|
327
|
-
},
|
|
328
|
-
{ title: "配置多个平台", value: "multi" },
|
|
329
|
-
],
|
|
330
|
-
initial: 0,
|
|
331
|
-
}, { onCancel });
|
|
332
|
-
if (!platformResp.platform) {
|
|
333
|
-
return false;
|
|
334
|
-
}
|
|
335
|
-
const platform = platformResp.platform;
|
|
336
|
-
const config = { platforms: {} };
|
|
337
|
-
// 第二步:选择要配置的多个平台(如果选择了 multi)
|
|
338
|
-
let selectedPlatforms = [];
|
|
339
|
-
if (platform === "multi") {
|
|
340
|
-
const multiResp = await prompts({
|
|
341
|
-
type: "multiselect",
|
|
342
|
-
name: "platforms",
|
|
343
|
-
message: "选择要配置的平台(空格选择,回车确认)",
|
|
344
|
-
choices: [
|
|
345
|
-
{ title: "Telegram" + (hasTg ? " ✓已配置" : ""), value: "telegram", selected: hasTg },
|
|
346
|
-
{ title: "飞书 (Feishu)" + (hasFs ? " ✓已配置" : ""), value: "feishu", selected: hasFs },
|
|
347
|
-
{ title: "企业微信 (WeWork)" + (hasWw ? " ✓已配置" : ""), value: "wework", selected: hasWw },
|
|
348
|
-
{ title: "钉钉 (DingTalk)" + (hasDt ? " ✓已配置" : ""), value: "dingtalk", selected: hasDt },
|
|
349
|
-
{ title: "WorkBuddy 微信客服 (WeChat KF)" + (hasWc ? " ✓已配置" : ""), value: "workbuddy", selected: hasWc },
|
|
350
|
-
{ title: "ClawBot 微信 iLink" + (existing?.platforms?.clawbot?.apiToken ? " ✓已配置" : ""), value: "clawbot" },
|
|
351
|
-
],
|
|
352
|
-
}, { onCancel });
|
|
353
|
-
if (!multiResp.platforms || multiResp.platforms.length === 0) {
|
|
354
|
-
console.log("至少需要选择一个平台");
|
|
355
|
-
return false;
|
|
356
|
-
}
|
|
357
|
-
selectedPlatforms = multiResp.platforms;
|
|
358
|
-
}
|
|
359
|
-
else {
|
|
360
|
-
selectedPlatforms = [platform];
|
|
361
|
-
}
|
|
362
|
-
// 收集平台配置(Telegram 用 readline 避免 Windows 下 prompts 重绘/重复行问题)
|
|
363
|
-
if (selectedPlatforms.includes("telegram")) {
|
|
364
|
-
const hint = hasTg ? "(留空保留现有)" : "";
|
|
365
|
-
const token = await question(`Telegram Bot Token(从 @BotFather 获取)${hint}: `);
|
|
366
|
-
if (token) {
|
|
367
|
-
config.telegramBotToken = token;
|
|
368
|
-
}
|
|
369
|
-
else if (hasTg) {
|
|
370
|
-
config.telegramBotToken = existing.platforms.telegram.botToken;
|
|
371
|
-
}
|
|
372
|
-
else if (platform === "telegram") {
|
|
373
|
-
console.log("Token 不能为空");
|
|
374
|
-
return false;
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
if (selectedPlatforms.includes("feishu")) {
|
|
378
|
-
const feishuResp = await prompts([
|
|
379
|
-
{
|
|
380
|
-
type: "text",
|
|
381
|
-
name: "appId",
|
|
382
|
-
message: "飞书 App ID(从飞书开放平台获取)",
|
|
383
|
-
initial: existing?.platforms?.feishu?.appId ?? "",
|
|
384
|
-
validate: (v) => (v.trim() ? true : "App ID 不能为空"),
|
|
385
|
-
},
|
|
386
|
-
{
|
|
387
|
-
type: "text",
|
|
388
|
-
name: "appSecret",
|
|
389
|
-
message: "飞书 App Secret(从飞书开放平台获取)",
|
|
390
|
-
initial: existing?.platforms?.feishu?.appSecret ?? "",
|
|
391
|
-
validate: (v) => (v.trim() ? true : "App Secret 不能为空"),
|
|
392
|
-
},
|
|
393
|
-
], { onCancel });
|
|
394
|
-
const fsAppId = feishuResp.appId?.trim() || existing?.platforms?.feishu?.appId;
|
|
395
|
-
const fsAppSecret = feishuResp.appSecret?.trim() || existing?.platforms?.feishu?.appSecret;
|
|
396
|
-
if (fsAppId && fsAppSecret) {
|
|
397
|
-
config.platforms.feishu = {
|
|
398
|
-
...config.platforms?.feishu,
|
|
399
|
-
enabled: true,
|
|
400
|
-
appId: fsAppId,
|
|
401
|
-
appSecret: fsAppSecret,
|
|
402
|
-
};
|
|
403
|
-
}
|
|
404
|
-
else if (platform === "feishu") {
|
|
405
|
-
return false;
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
if (selectedPlatforms.includes("qq")) {
|
|
409
|
-
const qqResp = await prompts([
|
|
410
|
-
{
|
|
411
|
-
type: "text",
|
|
412
|
-
name: "appId",
|
|
413
|
-
message: "QQ Bot App ID",
|
|
414
|
-
initial: existing?.platforms?.qq?.appId ?? "",
|
|
415
|
-
validate: (v) => (v.trim() ? true : "App ID 不能为空"),
|
|
416
|
-
},
|
|
417
|
-
{
|
|
418
|
-
type: "text",
|
|
419
|
-
name: "secret",
|
|
420
|
-
message: "QQ Bot Secret",
|
|
421
|
-
initial: existing?.platforms?.qq?.secret ?? "",
|
|
422
|
-
validate: (v) => (v.trim() ? true : "Secret 不能为空"),
|
|
423
|
-
},
|
|
424
|
-
], { onCancel });
|
|
425
|
-
const qqAppId = qqResp.appId?.trim() || existing?.platforms?.qq?.appId;
|
|
426
|
-
const qqSecret = qqResp.secret?.trim() || existing?.platforms?.qq?.secret;
|
|
427
|
-
if (qqAppId && qqSecret) {
|
|
428
|
-
config.platforms.qq = {
|
|
429
|
-
enabled: true,
|
|
430
|
-
appId: qqAppId,
|
|
431
|
-
secret: qqSecret,
|
|
432
|
-
};
|
|
433
|
-
}
|
|
434
|
-
else if (platform === "qq") {
|
|
435
|
-
return false;
|
|
436
|
-
}
|
|
437
|
-
}
|
|
438
|
-
if (selectedPlatforms.includes("workbuddy")) {
|
|
439
|
-
const wb = existing?.platforms?.workbuddy;
|
|
440
|
-
const hasWbCreds = !!(wb?.accessToken && wb?.refreshToken);
|
|
441
|
-
const wbModeResp = await prompts({
|
|
442
|
-
type: "select",
|
|
443
|
-
name: "mode",
|
|
444
|
-
message: "WorkBuddy 微信客服(CodeBuddy OAuth)",
|
|
445
|
-
choices: [
|
|
446
|
-
{
|
|
447
|
-
title: "在浏览器中完成 CodeBuddy 登录并绑定微信客服(推荐)",
|
|
448
|
-
value: "oauth",
|
|
449
|
-
},
|
|
450
|
-
{
|
|
451
|
-
title: "使用已有 WorkBuddy 凭证" + (hasWbCreds ? " ✓" : ""),
|
|
452
|
-
value: "keep",
|
|
453
|
-
disabled: !hasWbCreds,
|
|
454
|
-
},
|
|
455
|
-
],
|
|
456
|
-
initial: hasWbCreds ? 1 : 0,
|
|
457
|
-
}, { onCancel });
|
|
458
|
-
if (wbModeResp.mode === "oauth") {
|
|
459
|
-
console.log("\n正在启动 WorkBuddy OAuth 登录...\n");
|
|
460
|
-
// Phase 1: OAuth token acquisition (fatal if fails)
|
|
461
|
-
let oauthOk = false;
|
|
462
|
-
try {
|
|
463
|
-
const { WorkBuddyOAuth } = await import("./workbuddy/oauth.js");
|
|
464
|
-
const oauth = new WorkBuddyOAuth();
|
|
465
|
-
console.log("步骤 1/3: 获取登录链接...");
|
|
466
|
-
const { authUrl, state } = await oauth.fetchAuthState();
|
|
467
|
-
console.log("\n请在浏览器中打开以下链接完成登录:");
|
|
468
|
-
console.log(authUrl);
|
|
469
|
-
console.log("\n等待登录完成(最长 5 分钟)...\n");
|
|
470
|
-
const tokenResult = await oauth.pollToken(state);
|
|
471
|
-
console.log("✅ 登录成功! 正在获取账号信息...");
|
|
472
|
-
let accountInfo = {};
|
|
473
|
-
try {
|
|
474
|
-
accountInfo = await oauth.getAccount(state);
|
|
475
|
-
}
|
|
476
|
-
catch {
|
|
477
|
-
// account info is optional
|
|
478
|
-
}
|
|
479
|
-
const ai = accountInfo;
|
|
480
|
-
// Try multiple field names; fall back to tokenResult.userId, then existing config
|
|
481
|
-
const existingUserId = wb?.userId;
|
|
482
|
-
const userId = String(ai?.uid ?? ai?.userId ?? ai?.user_id ??
|
|
483
|
-
tokenResult.userId ??
|
|
484
|
-
existingUserId ?? "");
|
|
485
|
-
if (!userId) {
|
|
486
|
-
console.warn("⚠️ 未能获取 WorkBuddy 用户 ID,sessionId 可能不正确,建议稍后重试");
|
|
487
|
-
}
|
|
488
|
-
// Set oauth.userId so buildSessionId() produces the correct sessionId.
|
|
489
|
-
oauth.userId = userId;
|
|
490
|
-
console.log(` userId: ${userId || "(空)"}, sessionId 将为: ${oauth.buildSessionId()}`);
|
|
491
|
-
// Save credentials immediately — even if binding fails below
|
|
492
|
-
config.platforms.workbuddy = {
|
|
493
|
-
enabled: true,
|
|
494
|
-
accessToken: tokenResult.accessToken,
|
|
495
|
-
refreshToken: tokenResult.refreshToken,
|
|
496
|
-
userId,
|
|
497
|
-
};
|
|
498
|
-
oauthOk = true;
|
|
499
|
-
console.log("\n✅ WorkBuddy 凭证已获取,正在生成微信客服绑定链接...");
|
|
500
|
-
// Phase 2: WeChat KF binding link (non-fatal, no polling needed)
|
|
501
|
-
try {
|
|
502
|
-
const { join: pathJoin } = await import("node:path");
|
|
503
|
-
const { homedir } = await import("node:os");
|
|
504
|
-
const clawPath = pathJoin(homedir(), "WorkBuddy", "Claw");
|
|
505
|
-
const sessionId = oauth.buildSessionId(clawPath);
|
|
506
|
-
const linkResult = await oauth.getWeChatKfLink(sessionId);
|
|
507
|
-
if (linkResult.success && linkResult.url) {
|
|
508
|
-
console.log("\n━━━ 微信客服绑定 ━━━");
|
|
509
|
-
console.log("请将以下链接发给微信「文件传输助手」并点击打开,即完成绑定:");
|
|
510
|
-
console.log(linkResult.url);
|
|
511
|
-
console.log("\n绑定完成后运行 open-im start 启动服务即可收发消息。");
|
|
512
|
-
}
|
|
513
|
-
else {
|
|
514
|
-
console.log(`⚠️ 获取微信客服链接失败: ${linkResult.message ?? "未知错误"}`);
|
|
515
|
-
console.log(" 凭证已保存,稍后重新运行 open-im init 可重试绑定");
|
|
516
|
-
}
|
|
517
|
-
}
|
|
518
|
-
catch (bindErr) {
|
|
519
|
-
const cause = bindErr?.cause ?? bindErr?.code;
|
|
520
|
-
const detail = cause ? ` (${cause})` : "";
|
|
521
|
-
console.log(`⚠️ 获取微信客服绑定链接网络失败: ${bindErr instanceof Error ? bindErr.message : String(bindErr)}${detail}`);
|
|
522
|
-
console.log(" 凭证已保存,稍后重新运行 open-im init 可重试绑定");
|
|
523
|
-
}
|
|
524
|
-
console.log("\n✅ WorkBuddy 登录成功,配置已保存");
|
|
525
|
-
}
|
|
526
|
-
catch (err) {
|
|
527
|
-
const cause = err?.cause ?? err?.code;
|
|
528
|
-
const detail = cause ? ` (${cause})` : "";
|
|
529
|
-
console.error(`\n❌ WorkBuddy 登录失败: ${err instanceof Error ? err.message : String(err)}${detail}`);
|
|
530
|
-
if (!oauthOk && platform === "workbuddy")
|
|
531
|
-
return false;
|
|
532
|
-
}
|
|
533
|
-
}
|
|
534
|
-
else if (hasWbCreds) {
|
|
535
|
-
config.platforms.workbuddy = {
|
|
536
|
-
...wb,
|
|
537
|
-
enabled: true,
|
|
538
|
-
};
|
|
539
|
-
// Also (re-)generate the WeChat KF binding link with existing credentials
|
|
540
|
-
try {
|
|
541
|
-
const { WorkBuddyOAuth } = await import("./workbuddy/oauth.js");
|
|
542
|
-
const oauthKeep = new WorkBuddyOAuth(wb?.baseUrl);
|
|
543
|
-
oauthKeep.loadCredentials({
|
|
544
|
-
accessToken: wb?.accessToken ?? '',
|
|
545
|
-
refreshToken: wb?.refreshToken ?? '',
|
|
546
|
-
userId: wb?.userId ?? '',
|
|
547
|
-
});
|
|
548
|
-
oauthKeep.userId = wb?.userId ?? '';
|
|
549
|
-
const { join: pathJoin2 } = await import("node:path");
|
|
550
|
-
const { homedir: homedir2 } = await import("node:os");
|
|
551
|
-
const clawPath2 = pathJoin2(homedir2(), "WorkBuddy", "Claw");
|
|
552
|
-
const sessionId = oauthKeep.buildSessionId(clawPath2);
|
|
553
|
-
console.log(`\n正在获取微信客服绑定链接... (sessionId: ${sessionId})`);
|
|
554
|
-
const linkResult = await oauthKeep.getWeChatKfLink(sessionId);
|
|
555
|
-
if (linkResult.success && linkResult.url) {
|
|
556
|
-
console.log("\n━━━ 微信客服绑定 ━━━");
|
|
557
|
-
console.log("请将以下链接发给微信「文件传输助手」并点击打开,即完成绑定:");
|
|
558
|
-
console.log(linkResult.url);
|
|
559
|
-
console.log("\n绑定完成后运行 open-im start 启动服务即可收发消息。");
|
|
560
|
-
}
|
|
561
|
-
else {
|
|
562
|
-
console.log(`⚠️ 获取绑定链接失败: ${linkResult.message ?? "未知"}`);
|
|
563
|
-
}
|
|
564
|
-
}
|
|
565
|
-
catch (keepErr) {
|
|
566
|
-
const cause = keepErr?.cause ?? keepErr?.code;
|
|
567
|
-
console.log(`⚠️ 绑定链接请求失败: ${keepErr instanceof Error ? keepErr.message : String(keepErr)}${cause ? ` (${cause})` : ""}`);
|
|
568
|
-
console.log(" 稍后重新运行 open-im init 可重试");
|
|
569
|
-
}
|
|
570
|
-
}
|
|
571
|
-
else if (platform === "workbuddy") {
|
|
572
|
-
return false;
|
|
573
|
-
}
|
|
574
|
-
}
|
|
575
|
-
if (selectedPlatforms.includes("clawbot")) {
|
|
576
|
-
const hasCbToken = !!existing?.platforms?.clawbot?.apiToken;
|
|
577
|
-
const cbModeResp = await prompts({
|
|
578
|
-
type: "select",
|
|
579
|
-
name: "mode",
|
|
580
|
-
message: "ClawBot 微信 iLink",
|
|
581
|
-
choices: [
|
|
582
|
-
{ title: "扫码登录(自动获取 Token)", value: "qr" },
|
|
583
|
-
{ title: "使用已有 Token" + (hasCbToken ? " ✓" : ""), value: "token", disabled: !hasCbToken },
|
|
584
|
-
],
|
|
585
|
-
initial: 0,
|
|
586
|
-
}, { onCancel });
|
|
587
|
-
let cbApiToken = "";
|
|
588
|
-
let cbApiUrl = existing?.platforms?.clawbot?.apiUrl ?? "http://127.0.0.1:26322";
|
|
589
|
-
if (cbModeResp.mode === "qr") {
|
|
590
|
-
console.log("\n正在获取二维码...\n");
|
|
591
|
-
try {
|
|
592
|
-
const { startQRLogin, waitForQRLogin } = await import("./clawbot/qr-login.js");
|
|
593
|
-
const session = await startQRLogin();
|
|
594
|
-
console.log("请用微信扫描以下链接中的二维码:");
|
|
595
|
-
console.log(session.qrcodeUrl);
|
|
596
|
-
console.log("\n等待扫码...(最长 5 分钟)\n");
|
|
597
|
-
const result = await waitForQRLogin(session, (status) => {
|
|
598
|
-
if (status === "scaned")
|
|
599
|
-
process.stdout.write("已扫码,验证中...\n");
|
|
600
|
-
});
|
|
601
|
-
if (result.connected && result.botToken) {
|
|
602
|
-
cbApiToken = result.botToken;
|
|
603
|
-
if (result.baseUrl)
|
|
604
|
-
cbApiUrl = result.baseUrl;
|
|
605
|
-
console.log("\n✅ 登录成功!");
|
|
606
|
-
if (result.userId) {
|
|
607
|
-
console.log(` 用户 ID: ${result.userId}`);
|
|
608
|
-
console.log(" 提示:可将此 ID 添加到白名单");
|
|
609
|
-
}
|
|
610
|
-
}
|
|
611
|
-
else {
|
|
612
|
-
console.log(`\n❌ 登录失败: ${result.message}`);
|
|
613
|
-
if (platform === "clawbot")
|
|
614
|
-
return false;
|
|
615
|
-
}
|
|
616
|
-
}
|
|
617
|
-
catch (err) {
|
|
618
|
-
console.error(`\n❌ 二维码获取失败: ${err instanceof Error ? err.message : String(err)}`);
|
|
619
|
-
if (platform === "clawbot")
|
|
620
|
-
return false;
|
|
621
|
-
}
|
|
622
|
-
}
|
|
623
|
-
else {
|
|
624
|
-
const cbResp = await prompts([
|
|
625
|
-
{
|
|
626
|
-
type: "text",
|
|
627
|
-
name: "apiUrl",
|
|
628
|
-
message: "ClawBot iLink API 地址(默认 http://127.0.0.1:26322)",
|
|
629
|
-
initial: cbApiUrl,
|
|
630
|
-
},
|
|
631
|
-
{
|
|
632
|
-
type: "text",
|
|
633
|
-
name: "apiToken",
|
|
634
|
-
message: "ClawBot Bearer Token",
|
|
635
|
-
initial: existing?.platforms?.clawbot?.apiToken ?? "",
|
|
636
|
-
validate: (v) => (v.trim() ? true : "Token 不能为空"),
|
|
637
|
-
},
|
|
638
|
-
], { onCancel });
|
|
639
|
-
cbApiUrl = cbResp.apiUrl?.trim() || cbApiUrl;
|
|
640
|
-
cbApiToken = cbResp.apiToken?.trim() || "";
|
|
641
|
-
}
|
|
642
|
-
if (cbApiToken) {
|
|
643
|
-
config.platforms.clawbot = {
|
|
644
|
-
enabled: true,
|
|
645
|
-
apiUrl: cbApiUrl,
|
|
646
|
-
apiToken: cbApiToken,
|
|
647
|
-
};
|
|
648
|
-
}
|
|
649
|
-
else if (platform === "clawbot") {
|
|
650
|
-
return false;
|
|
651
|
-
}
|
|
652
|
-
}
|
|
653
|
-
if (selectedPlatforms.includes("wework")) {
|
|
654
|
-
const weworkResp = await prompts([
|
|
655
|
-
{
|
|
656
|
-
type: "text",
|
|
657
|
-
name: "corpId",
|
|
658
|
-
message: "企业微信 Bot ID(从企业微信管理后台获取)",
|
|
659
|
-
initial: existing?.platforms?.wework?.corpId ?? "",
|
|
660
|
-
validate: (v) => (v.trim() ? true : "Bot ID 不能为空"),
|
|
661
|
-
},
|
|
662
|
-
{
|
|
663
|
-
type: "text",
|
|
664
|
-
name: "secret",
|
|
665
|
-
message: "企业微信 Secret(从企业微信管理后台获取)",
|
|
666
|
-
initial: existing?.platforms?.wework?.secret ?? "",
|
|
667
|
-
validate: (v) => (v.trim() ? true : "Secret 不能为空"),
|
|
668
|
-
},
|
|
669
|
-
], { onCancel });
|
|
670
|
-
const wwCorpId = weworkResp.corpId?.trim() || existing?.platforms?.wework?.corpId;
|
|
671
|
-
const wwSecret = weworkResp.secret?.trim() || existing?.platforms?.wework?.secret;
|
|
672
|
-
if (wwCorpId && wwSecret) {
|
|
673
|
-
config.platforms.wework = {
|
|
674
|
-
enabled: true,
|
|
675
|
-
corpId: wwCorpId,
|
|
676
|
-
secret: wwSecret,
|
|
677
|
-
};
|
|
678
|
-
}
|
|
679
|
-
else if (platform === "wework") {
|
|
680
|
-
return false;
|
|
681
|
-
}
|
|
682
|
-
}
|
|
683
|
-
if (selectedPlatforms.includes("dingtalk")) {
|
|
684
|
-
const dingtalkResp = await prompts([
|
|
685
|
-
{
|
|
686
|
-
type: "text",
|
|
687
|
-
name: "clientId",
|
|
688
|
-
message: "钉钉 Client ID / AppKey(从钉钉开放平台获取)",
|
|
689
|
-
initial: existing?.platforms?.dingtalk?.clientId ?? "",
|
|
690
|
-
validate: (v) => (v.trim() ? true : "Client ID 不能为空"),
|
|
691
|
-
},
|
|
692
|
-
{
|
|
693
|
-
type: "text",
|
|
694
|
-
name: "clientSecret",
|
|
695
|
-
message: "钉钉 Client Secret / AppSecret(从钉钉开放平台获取)",
|
|
696
|
-
initial: existing?.platforms?.dingtalk?.clientSecret ?? "",
|
|
697
|
-
validate: (v) => (v.trim() ? true : "Client Secret 不能为空"),
|
|
698
|
-
},
|
|
699
|
-
{
|
|
700
|
-
type: "text",
|
|
701
|
-
name: "cardTemplateId",
|
|
702
|
-
message: "钉钉 AI 卡片模板 ID(可选,配置后启用单条流式)",
|
|
703
|
-
initial: existing?.platforms?.dingtalk?.cardTemplateId ?? "",
|
|
704
|
-
},
|
|
705
|
-
], { onCancel });
|
|
706
|
-
const dtClientId = dingtalkResp.clientId?.trim() || existing?.platforms?.dingtalk?.clientId;
|
|
707
|
-
const dtClientSecret = dingtalkResp.clientSecret?.trim() || existing?.platforms?.dingtalk?.clientSecret;
|
|
708
|
-
const dtCardTemplateId = dingtalkResp.cardTemplateId?.trim() || existing?.platforms?.dingtalk?.cardTemplateId;
|
|
709
|
-
if (dtClientId && dtClientSecret) {
|
|
710
|
-
config.platforms.dingtalk = {
|
|
711
|
-
enabled: true,
|
|
712
|
-
clientId: dtClientId,
|
|
713
|
-
clientSecret: dtClientSecret,
|
|
714
|
-
cardTemplateId: dtCardTemplateId,
|
|
715
|
-
};
|
|
716
|
-
}
|
|
717
|
-
else if (platform === "dingtalk") {
|
|
718
|
-
return false;
|
|
719
|
-
}
|
|
720
|
-
}
|
|
721
|
-
// 通用配置:只询问所选平台的白名单,未选平台沿用已有配置
|
|
722
|
-
const tgIds = existing?.platforms?.telegram?.allowedUserIds?.join(", ") ?? "";
|
|
723
|
-
const fsIds = existing?.platforms?.feishu?.allowedUserIds?.join(", ") ?? "";
|
|
724
|
-
const qqIds = existing?.platforms?.qq?.allowedUserIds?.join(", ") ?? "";
|
|
725
|
-
const wcIds = existing?.platforms?.workbuddy?.allowedUserIds?.join(", ") ?? "";
|
|
726
|
-
const wwIds = existing?.platforms?.wework?.allowedUserIds?.join(", ") ?? "";
|
|
727
|
-
const dtIds = existing?.platforms?.dingtalk?.allowedUserIds?.join(", ") ?? "";
|
|
728
|
-
const commonPrompts = [];
|
|
729
|
-
if (selectedPlatforms.includes("qq")) {
|
|
730
|
-
commonPrompts.push({
|
|
731
|
-
type: "text",
|
|
732
|
-
name: "qqAllowedUserIds",
|
|
733
|
-
message: "QQ allowed user IDs (optional, comma-separated, empty means allow all)",
|
|
734
|
-
initial: qqIds,
|
|
735
|
-
});
|
|
736
|
-
}
|
|
737
|
-
if (selectedPlatforms.includes("telegram")) {
|
|
738
|
-
commonPrompts.push({
|
|
739
|
-
type: "text",
|
|
740
|
-
name: "telegramAllowedUserIds",
|
|
741
|
-
message: "Telegram 白名单用户 ID(可选,逗号分隔,留空=所有人可访问)",
|
|
742
|
-
initial: tgIds,
|
|
743
|
-
});
|
|
744
|
-
}
|
|
745
|
-
if (selectedPlatforms.includes("feishu")) {
|
|
746
|
-
commonPrompts.push({
|
|
747
|
-
type: "text",
|
|
748
|
-
name: "feishuAllowedUserIds",
|
|
749
|
-
message: "飞书白名单用户 ID(可选,逗号分隔,留空=所有人可访问)",
|
|
750
|
-
initial: fsIds,
|
|
751
|
-
});
|
|
752
|
-
}
|
|
753
|
-
if (selectedPlatforms.includes("workbuddy")) {
|
|
754
|
-
commonPrompts.push({
|
|
755
|
-
type: "text",
|
|
756
|
-
name: "workbuddyAllowedUserIds",
|
|
757
|
-
message: "WorkBuddy 白名单用户 ID(可选,逗号分隔,留空=所有人可访问)",
|
|
758
|
-
initial: wcIds,
|
|
759
|
-
});
|
|
760
|
-
}
|
|
761
|
-
if (selectedPlatforms.includes("wework")) {
|
|
762
|
-
commonPrompts.push({
|
|
763
|
-
type: "text",
|
|
764
|
-
name: "weworkAllowedUserIds",
|
|
765
|
-
message: "企业微信白名单用户 ID(可选,逗号分隔,留空=所有人可访问)",
|
|
766
|
-
initial: wwIds,
|
|
767
|
-
});
|
|
768
|
-
}
|
|
769
|
-
if (selectedPlatforms.includes("dingtalk")) {
|
|
770
|
-
commonPrompts.push({
|
|
771
|
-
type: "text",
|
|
772
|
-
name: "dingtalkAllowedUserIds",
|
|
773
|
-
message: "钉钉白名单用户 ID(可选,逗号分隔,留空=所有人可访问)",
|
|
774
|
-
initial: dtIds,
|
|
775
|
-
});
|
|
776
|
-
}
|
|
777
|
-
if (selectedPlatforms.includes("clawbot")) {
|
|
778
|
-
const cbIds = existing?.platforms?.clawbot?.allowedUserIds?.join(", ") ?? "";
|
|
779
|
-
commonPrompts.push({
|
|
780
|
-
type: "text",
|
|
781
|
-
name: "clawbotAllowedUserIds",
|
|
782
|
-
message: "ClawBot 白名单用户 ID(可选,逗号分隔,留空=所有人可访问)",
|
|
783
|
-
initial: cbIds,
|
|
784
|
-
});
|
|
785
|
-
}
|
|
786
|
-
commonPrompts.push({
|
|
787
|
-
type: "text",
|
|
788
|
-
name: "workDir",
|
|
789
|
-
message: "工作目录",
|
|
790
|
-
initial: existing?.tools?.claude?.workDir ?? process.cwd(),
|
|
791
|
-
}, {
|
|
792
|
-
type: "text",
|
|
793
|
-
name: "codexProxy",
|
|
794
|
-
message: "Codex 代理(可选,如 http://127.0.0.1:7890)",
|
|
795
|
-
initial: existing?.tools?.codex?.proxy ?? "",
|
|
796
|
-
});
|
|
797
|
-
const commonResp = await prompts(commonPrompts, { onCancel });
|
|
798
|
-
// 若需配置 Claude API,询问 API 配置
|
|
799
|
-
let claudeApiConfig = {};
|
|
800
|
-
{
|
|
801
|
-
const legacyRootEnv = existing?.env;
|
|
802
|
-
const claudeFileEnv = existing?.tools?.claude?.env ?? legacyRootEnv;
|
|
803
|
-
// 检查是否已配置 API 密钥(环境变量、tools.claude.env、旧版根 env、或 ~/.claude/settings.json)
|
|
804
|
-
const hasExistingApiKey = !!(process.env.ANTHROPIC_API_KEY ||
|
|
805
|
-
process.env.ANTHROPIC_AUTH_TOKEN ||
|
|
806
|
-
process.env.CLAUDE_CODE_OAUTH_TOKEN ||
|
|
807
|
-
claudeFileEnv?.ANTHROPIC_API_KEY ||
|
|
808
|
-
claudeFileEnv?.ANTHROPIC_AUTH_TOKEN ||
|
|
809
|
-
hasClaudeCredsInSettings());
|
|
810
|
-
if (hasExistingApiKey) {
|
|
811
|
-
// 已经配置过,直接保留原有配置,跳过询问
|
|
812
|
-
if (claudeFileEnv && Object.keys(claudeFileEnv).length > 0) {
|
|
813
|
-
claudeApiConfig = {
|
|
814
|
-
apiKey: claudeFileEnv.ANTHROPIC_API_KEY || claudeFileEnv.ANTHROPIC_AUTH_TOKEN,
|
|
815
|
-
baseUrl: claudeFileEnv.ANTHROPIC_BASE_URL,
|
|
816
|
-
model: claudeFileEnv.ANTHROPIC_MODEL,
|
|
817
|
-
haikuModel: claudeFileEnv.ANTHROPIC_DEFAULT_HAIKU_MODEL,
|
|
818
|
-
sonnetModel: claudeFileEnv.ANTHROPIC_DEFAULT_SONNET_MODEL,
|
|
819
|
-
opusModel: claudeFileEnv.ANTHROPIC_DEFAULT_OPUS_MODEL,
|
|
820
|
-
};
|
|
821
|
-
}
|
|
822
|
-
}
|
|
823
|
-
else {
|
|
824
|
-
// 没有配置过,引导用户配置
|
|
825
|
-
console.log('');
|
|
826
|
-
console.log('━━━ Claude API 配置 ━━━');
|
|
827
|
-
console.log('提示:以下配置均为可选,留空将使用默认值');
|
|
828
|
-
console.log('');
|
|
829
|
-
const apiResp = await prompts([
|
|
830
|
-
{
|
|
831
|
-
type: "text",
|
|
832
|
-
name: "apiKey",
|
|
833
|
-
message: "API Key / Auth Token(回车跳过,稍后手动配置)",
|
|
834
|
-
initial: "",
|
|
835
|
-
},
|
|
836
|
-
{
|
|
837
|
-
type: "text",
|
|
838
|
-
name: "baseUrl",
|
|
839
|
-
message: "Base URL(回车跳过,使用官方 API)",
|
|
840
|
-
initial: "",
|
|
841
|
-
},
|
|
842
|
-
{
|
|
843
|
-
type: "text",
|
|
844
|
-
name: "model",
|
|
845
|
-
message: "默认模型(回车跳过)",
|
|
846
|
-
initial: "",
|
|
847
|
-
},
|
|
848
|
-
{
|
|
849
|
-
type: "text",
|
|
850
|
-
name: "haikuModel",
|
|
851
|
-
message: "Haiku 模型(回车跳过)",
|
|
852
|
-
initial: "",
|
|
853
|
-
},
|
|
854
|
-
{
|
|
855
|
-
type: "text",
|
|
856
|
-
name: "sonnetModel",
|
|
857
|
-
message: "Sonnet 模型(回车跳过)",
|
|
858
|
-
initial: "",
|
|
859
|
-
},
|
|
860
|
-
{
|
|
861
|
-
type: "text",
|
|
862
|
-
name: "opusModel",
|
|
863
|
-
message: "Opus 模型(回车跳过)",
|
|
864
|
-
initial: "",
|
|
865
|
-
},
|
|
866
|
-
], { onCancel });
|
|
867
|
-
claudeApiConfig = apiResp;
|
|
868
|
-
}
|
|
869
|
-
}
|
|
870
|
-
const parseIds = (value) => value
|
|
871
|
-
? value
|
|
872
|
-
.split(",")
|
|
873
|
-
.map((s) => s.trim())
|
|
874
|
-
.filter(Boolean)
|
|
875
|
-
: [];
|
|
876
|
-
// 分平台白名单:已询问的用输入值,未询问的用已有配置
|
|
877
|
-
const telegramIds = selectedPlatforms.includes("telegram")
|
|
878
|
-
? parseIds(commonResp.telegramAllowedUserIds)
|
|
879
|
-
: parseIds(existing?.platforms?.telegram?.allowedUserIds?.join(", "));
|
|
880
|
-
const feishuIds = selectedPlatforms.includes("feishu")
|
|
881
|
-
? parseIds(commonResp.feishuAllowedUserIds)
|
|
882
|
-
: parseIds(existing?.platforms?.feishu?.allowedUserIds?.join(", "));
|
|
883
|
-
const qqIdsFinal = selectedPlatforms.includes("qq")
|
|
884
|
-
? parseIds(commonResp.qqAllowedUserIds)
|
|
885
|
-
: parseIds(existing?.platforms?.qq?.allowedUserIds?.join(", "));
|
|
886
|
-
const workbuddyIds = selectedPlatforms.includes("workbuddy")
|
|
887
|
-
? parseIds(commonResp.workbuddyAllowedUserIds)
|
|
888
|
-
: parseIds(existing?.platforms?.workbuddy?.allowedUserIds?.join(", "));
|
|
889
|
-
const weworkIds = selectedPlatforms.includes("wework")
|
|
890
|
-
? parseIds(commonResp.weworkAllowedUserIds)
|
|
891
|
-
: parseIds(existing?.platforms?.wework?.allowedUserIds?.join(", "));
|
|
892
|
-
const dingtalkIds = selectedPlatforms.includes("dingtalk")
|
|
893
|
-
? parseIds(commonResp.dingtalkAllowedUserIds)
|
|
894
|
-
: parseIds(existing?.platforms?.dingtalk?.allowedUserIds?.join(", "));
|
|
895
|
-
const clawbotIds = selectedPlatforms.includes("clawbot")
|
|
896
|
-
? parseIds(commonResp.clawbotAllowedUserIds)
|
|
897
|
-
: parseIds(existing?.platforms?.clawbot?.allowedUserIds?.join(", "));
|
|
898
|
-
// 增量合并:以已有配置为底,只覆盖本次选中的平台(不写入根级旧字段 telegramBotToken 等)
|
|
899
|
-
const base = existing
|
|
900
|
-
? JSON.parse(JSON.stringify(existing))
|
|
901
|
-
: null;
|
|
902
|
-
const { telegramBotToken: _, feishuAppId: __, feishuAppSecret: ___, claudeWorkDir: _cwd, claudeTimeoutMs: _ctm, claudeModel: _cm, env: _stripLegacyRootEnv, aiCommand: _stripLegacyGlobalAi, ...baseRest } = (base ?? {});
|
|
903
|
-
// 若用户在向导中输入了 Claude 配置,写入 ~/.claude/settings.json(与 Claude Code 共用)
|
|
904
|
-
if (claudeApiConfig.apiKey || claudeApiConfig.baseUrl || claudeApiConfig.model) {
|
|
905
|
-
const claudeExisting = loadClaudeSettings();
|
|
906
|
-
const claudeEnv = { ...(claudeExisting.env ?? {}) };
|
|
907
|
-
if (claudeApiConfig.apiKey?.trim()) {
|
|
908
|
-
const key = claudeApiConfig.apiKey.trim();
|
|
909
|
-
if (key.startsWith("sk-")) {
|
|
910
|
-
claudeEnv.ANTHROPIC_API_KEY = key;
|
|
911
|
-
delete claudeEnv.ANTHROPIC_AUTH_TOKEN;
|
|
912
|
-
}
|
|
913
|
-
else {
|
|
914
|
-
claudeEnv.ANTHROPIC_AUTH_TOKEN = key;
|
|
915
|
-
delete claudeEnv.ANTHROPIC_API_KEY;
|
|
916
|
-
}
|
|
917
|
-
}
|
|
918
|
-
if (claudeApiConfig.baseUrl?.trim())
|
|
919
|
-
claudeEnv.ANTHROPIC_BASE_URL = claudeApiConfig.baseUrl.trim();
|
|
920
|
-
if (claudeApiConfig.model?.trim())
|
|
921
|
-
claudeEnv.ANTHROPIC_MODEL = claudeApiConfig.model.trim();
|
|
922
|
-
if (claudeApiConfig.haikuModel?.trim())
|
|
923
|
-
claudeEnv.ANTHROPIC_DEFAULT_HAIKU_MODEL = claudeApiConfig.haikuModel.trim();
|
|
924
|
-
if (claudeApiConfig.sonnetModel?.trim())
|
|
925
|
-
claudeEnv.ANTHROPIC_DEFAULT_SONNET_MODEL = claudeApiConfig.sonnetModel.trim();
|
|
926
|
-
if (claudeApiConfig.opusModel?.trim())
|
|
927
|
-
claudeEnv.ANTHROPIC_DEFAULT_OPUS_MODEL = claudeApiConfig.opusModel.trim();
|
|
928
|
-
const claudeDir = dirname(CLAUDE_SETTINGS_PATH);
|
|
929
|
-
if (!existsSync(claudeDir))
|
|
930
|
-
mkdirSync(claudeDir, { recursive: true });
|
|
931
|
-
writeFileSync(CLAUDE_SETTINGS_PATH, JSON.stringify({ ...claudeExisting, env: claudeEnv }, null, 2), "utf-8");
|
|
932
|
-
console.log("\n✓ Claude API 配置已保存到", CLAUDE_SETTINGS_PATH);
|
|
933
|
-
}
|
|
934
|
-
const workDir = (commonResp.workDir || process.cwd()).trim();
|
|
935
|
-
const baseTools = base?.tools ?? {};
|
|
936
|
-
const codexProxyVal = commonResp.codexProxy?.trim();
|
|
937
|
-
const out = {
|
|
938
|
-
...baseRest,
|
|
939
|
-
platforms: { ...(base?.platforms ?? {}) },
|
|
940
|
-
tools: {
|
|
941
|
-
claude: {
|
|
942
|
-
...baseTools.claude,
|
|
943
|
-
cliPath: baseTools.claude?.cliPath ?? "claude",
|
|
944
|
-
workDir,
|
|
945
|
-
},
|
|
946
|
-
codex: {
|
|
947
|
-
...baseTools.codex,
|
|
948
|
-
cliPath: baseTools.codex?.cliPath ?? "codex",
|
|
949
|
-
workDir: workDir,
|
|
950
|
-
proxy: codexProxyVal || baseTools.codex?.proxy,
|
|
951
|
-
},
|
|
952
|
-
codebuddy: {
|
|
953
|
-
...baseTools.codebuddy,
|
|
954
|
-
cliPath: baseTools.codebuddy?.cliPath ?? "codebuddy",
|
|
955
|
-
},
|
|
956
|
-
opencode: {
|
|
957
|
-
...baseTools.opencode,
|
|
958
|
-
cliPath: baseTools.opencode?.cliPath ?? "opencode",
|
|
959
|
-
},
|
|
960
|
-
},
|
|
961
|
-
};
|
|
962
|
-
const outPlatforms = out.platforms;
|
|
963
|
-
const configPlatforms = config.platforms;
|
|
964
|
-
const basePlatforms = base?.platforms;
|
|
965
|
-
if (selectedPlatforms.includes("telegram")) {
|
|
966
|
-
outPlatforms.telegram = {
|
|
967
|
-
...basePlatforms?.telegram,
|
|
968
|
-
enabled: true,
|
|
969
|
-
aiCommand: defaultPlatformAi(basePlatforms?.telegram?.aiCommand),
|
|
970
|
-
botToken: config.telegramBotToken ?? basePlatforms?.telegram?.botToken,
|
|
971
|
-
allowedUserIds: telegramIds,
|
|
972
|
-
};
|
|
973
|
-
}
|
|
974
|
-
else if (basePlatforms?.telegram) {
|
|
975
|
-
outPlatforms.telegram = {
|
|
976
|
-
...basePlatforms.telegram,
|
|
977
|
-
aiCommand: defaultPlatformAi(basePlatforms.telegram.aiCommand),
|
|
978
|
-
allowedUserIds: telegramIds.length > 0 ? telegramIds : basePlatforms.telegram.allowedUserIds ?? [],
|
|
979
|
-
};
|
|
980
|
-
}
|
|
981
|
-
else {
|
|
982
|
-
outPlatforms.telegram = { enabled: false, aiCommand: "claude", allowedUserIds: telegramIds };
|
|
983
|
-
}
|
|
984
|
-
if (selectedPlatforms.includes("feishu")) {
|
|
985
|
-
outPlatforms.feishu = {
|
|
986
|
-
...basePlatforms?.feishu,
|
|
987
|
-
enabled: true,
|
|
988
|
-
aiCommand: defaultPlatformAi(basePlatforms?.feishu?.aiCommand),
|
|
989
|
-
appId: configPlatforms?.feishu?.appId,
|
|
990
|
-
appSecret: configPlatforms?.feishu?.appSecret,
|
|
991
|
-
allowedUserIds: feishuIds,
|
|
992
|
-
};
|
|
993
|
-
}
|
|
994
|
-
else if (basePlatforms?.feishu) {
|
|
995
|
-
outPlatforms.feishu = {
|
|
996
|
-
...basePlatforms.feishu,
|
|
997
|
-
aiCommand: defaultPlatformAi(basePlatforms.feishu.aiCommand),
|
|
998
|
-
allowedUserIds: feishuIds.length > 0 ? feishuIds : basePlatforms.feishu.allowedUserIds ?? [],
|
|
999
|
-
};
|
|
1000
|
-
}
|
|
1001
|
-
else {
|
|
1002
|
-
outPlatforms.feishu = { enabled: false, aiCommand: "claude", allowedUserIds: feishuIds };
|
|
1003
|
-
}
|
|
1004
|
-
if (selectedPlatforms.includes("qq")) {
|
|
1005
|
-
outPlatforms.qq = {
|
|
1006
|
-
...basePlatforms?.qq,
|
|
1007
|
-
enabled: true,
|
|
1008
|
-
aiCommand: defaultPlatformAi(basePlatforms?.qq?.aiCommand),
|
|
1009
|
-
appId: configPlatforms?.qq?.appId,
|
|
1010
|
-
secret: configPlatforms?.qq?.secret,
|
|
1011
|
-
allowedUserIds: qqIdsFinal,
|
|
1012
|
-
};
|
|
1013
|
-
}
|
|
1014
|
-
else if (basePlatforms?.qq) {
|
|
1015
|
-
outPlatforms.qq = {
|
|
1016
|
-
...basePlatforms.qq,
|
|
1017
|
-
aiCommand: defaultPlatformAi(basePlatforms.qq.aiCommand),
|
|
1018
|
-
allowedUserIds: qqIdsFinal.length > 0 ? qqIdsFinal : basePlatforms.qq.allowedUserIds ?? [],
|
|
1019
|
-
};
|
|
1020
|
-
}
|
|
1021
|
-
else {
|
|
1022
|
-
outPlatforms.qq = { enabled: false, aiCommand: "claude", allowedUserIds: qqIdsFinal };
|
|
1023
|
-
}
|
|
1024
|
-
if (selectedPlatforms.includes("workbuddy")) {
|
|
1025
|
-
const wbConfig = config.platforms?.workbuddy;
|
|
1026
|
-
const baseWb = base?.platforms?.workbuddy;
|
|
1027
|
-
const wbOut = {
|
|
1028
|
-
enabled: true,
|
|
1029
|
-
aiCommand: defaultPlatformAi(baseWb?.aiCommand),
|
|
1030
|
-
accessToken: wbConfig?.accessToken ?? baseWb?.accessToken,
|
|
1031
|
-
refreshToken: wbConfig?.refreshToken ?? baseWb?.refreshToken,
|
|
1032
|
-
userId: wbConfig?.userId ?? baseWb?.userId ?? "",
|
|
1033
|
-
allowedUserIds: workbuddyIds,
|
|
1034
|
-
};
|
|
1035
|
-
const wbBaseUrl = wbConfig?.baseUrl ?? baseWb?.baseUrl;
|
|
1036
|
-
if (wbBaseUrl)
|
|
1037
|
-
wbOut.baseUrl = wbBaseUrl;
|
|
1038
|
-
out.platforms.workbuddy = wbOut;
|
|
1039
|
-
}
|
|
1040
|
-
else if (basePlatforms?.workbuddy) {
|
|
1041
|
-
outPlatforms.workbuddy = {
|
|
1042
|
-
...basePlatforms.workbuddy,
|
|
1043
|
-
aiCommand: defaultPlatformAi(basePlatforms.workbuddy.aiCommand),
|
|
1044
|
-
allowedUserIds: workbuddyIds.length > 0 ? workbuddyIds : basePlatforms.workbuddy.allowedUserIds ?? [],
|
|
1045
|
-
};
|
|
1046
|
-
}
|
|
1047
|
-
else {
|
|
1048
|
-
outPlatforms.workbuddy = { enabled: false, aiCommand: "claude", allowedUserIds: workbuddyIds };
|
|
1049
|
-
}
|
|
1050
|
-
if (selectedPlatforms.includes("wework")) {
|
|
1051
|
-
outPlatforms.wework = {
|
|
1052
|
-
...basePlatforms?.wework,
|
|
1053
|
-
enabled: true,
|
|
1054
|
-
aiCommand: defaultPlatformAi(basePlatforms?.wework?.aiCommand),
|
|
1055
|
-
corpId: configPlatforms?.wework?.corpId,
|
|
1056
|
-
secret: configPlatforms?.wework?.secret,
|
|
1057
|
-
allowedUserIds: weworkIds,
|
|
1058
|
-
};
|
|
1059
|
-
}
|
|
1060
|
-
else if (basePlatforms?.wework) {
|
|
1061
|
-
outPlatforms.wework = {
|
|
1062
|
-
...basePlatforms.wework,
|
|
1063
|
-
aiCommand: defaultPlatformAi(basePlatforms.wework.aiCommand),
|
|
1064
|
-
allowedUserIds: weworkIds.length > 0 ? weworkIds : basePlatforms.wework.allowedUserIds ?? [],
|
|
1065
|
-
};
|
|
1066
|
-
}
|
|
1067
|
-
else {
|
|
1068
|
-
outPlatforms.wework = { enabled: false, aiCommand: "claude", allowedUserIds: weworkIds };
|
|
1069
|
-
}
|
|
1070
|
-
if (selectedPlatforms.includes("dingtalk")) {
|
|
1071
|
-
outPlatforms.dingtalk = {
|
|
1072
|
-
...basePlatforms?.dingtalk,
|
|
1073
|
-
enabled: true,
|
|
1074
|
-
aiCommand: defaultPlatformAi(basePlatforms?.dingtalk?.aiCommand),
|
|
1075
|
-
clientId: configPlatforms?.dingtalk?.clientId,
|
|
1076
|
-
clientSecret: configPlatforms?.dingtalk?.clientSecret,
|
|
1077
|
-
cardTemplateId: configPlatforms?.dingtalk?.cardTemplateId,
|
|
1078
|
-
allowedUserIds: dingtalkIds,
|
|
1079
|
-
};
|
|
1080
|
-
}
|
|
1081
|
-
else if (basePlatforms?.dingtalk) {
|
|
1082
|
-
outPlatforms.dingtalk = {
|
|
1083
|
-
...basePlatforms.dingtalk,
|
|
1084
|
-
aiCommand: defaultPlatformAi(basePlatforms.dingtalk.aiCommand),
|
|
1085
|
-
allowedUserIds: dingtalkIds.length > 0 ? dingtalkIds : basePlatforms.dingtalk.allowedUserIds ?? [],
|
|
1086
|
-
};
|
|
1087
|
-
}
|
|
1088
|
-
else {
|
|
1089
|
-
outPlatforms.dingtalk = { enabled: false, aiCommand: "claude", allowedUserIds: dingtalkIds };
|
|
1090
|
-
}
|
|
1091
|
-
if (selectedPlatforms.includes("clawbot")) {
|
|
1092
|
-
const cbConfig = config.platforms?.clawbot;
|
|
1093
|
-
const baseCb = base?.platforms?.clawbot;
|
|
1094
|
-
outPlatforms.clawbot = {
|
|
1095
|
-
enabled: true,
|
|
1096
|
-
aiCommand: defaultPlatformAi(baseCb?.aiCommand),
|
|
1097
|
-
apiUrl: String(cbConfig?.apiUrl ?? baseCb?.apiUrl ?? "http://127.0.0.1:26322"),
|
|
1098
|
-
apiToken: String(cbConfig?.apiToken ?? baseCb?.apiToken ?? ""),
|
|
1099
|
-
allowedUserIds: clawbotIds,
|
|
1100
|
-
};
|
|
1101
|
-
}
|
|
1102
|
-
else if (basePlatforms?.clawbot) {
|
|
1103
|
-
outPlatforms.clawbot = {
|
|
1104
|
-
...basePlatforms.clawbot,
|
|
1105
|
-
aiCommand: defaultPlatformAi(basePlatforms.clawbot.aiCommand),
|
|
1106
|
-
allowedUserIds: clawbotIds.length > 0 ? clawbotIds : basePlatforms.clawbot.allowedUserIds ?? [],
|
|
1107
|
-
};
|
|
1108
|
-
}
|
|
1109
|
-
else {
|
|
1110
|
-
outPlatforms.clawbot = { enabled: false, aiCommand: "claude", allowedUserIds: clawbotIds };
|
|
1111
|
-
}
|
|
1112
|
-
const dir = dirname(configPath);
|
|
1113
|
-
if (!existsSync(dir)) {
|
|
1114
|
-
mkdirSync(dir, { recursive: true });
|
|
1115
|
-
}
|
|
1116
|
-
writeFileSync(configPath, JSON.stringify(out, null, 2), "utf-8");
|
|
1117
|
-
try {
|
|
1118
|
-
chmodSync(configPath, 0o600);
|
|
1119
|
-
}
|
|
1120
|
-
catch { /* ignore on unsupported platforms */ }
|
|
1121
|
-
console.log("\n✅ 配置已保存到", configPath);
|
|
1122
|
-
console.log("");
|
|
142
|
+
writeFileSync(CLAUDE_SETTINGS_PATH, JSON.stringify(merged, null, 2), 'utf-8');
|
|
143
|
+
console.log('\n✓ Claude API 配置已保存到', CLAUDE_SETTINGS_PATH);
|
|
1123
144
|
return true;
|
|
1124
145
|
}
|
|
1125
|
-
const PLATFORM_LABELS = {
|
|
1126
|
-
telegram: "Telegram",
|
|
1127
|
-
qq: "QQ",
|
|
1128
|
-
feishu: "飞书",
|
|
1129
|
-
wework: "企业微信",
|
|
1130
|
-
dingtalk: "钉钉",
|
|
1131
|
-
workbuddy: "WorkBuddy 微信客服",
|
|
1132
|
-
clawbot: "ClawBot 微信 iLink",
|
|
1133
|
-
};
|
|
1134
|
-
const ALL_PLATFORMS = ["telegram", "feishu", "qq", "wework", "dingtalk", "workbuddy", "clawbot"];
|
|
1135
146
|
/**
|
|
1136
|
-
*
|
|
1137
|
-
* 显示全部 4 个平台,已配置的预选;若用户选择未配置的,引导运行 init
|
|
1138
|
-
* @returns 更新后的 config,或 null 表示取消
|
|
147
|
+
* 交互式配置 — 现在直接引导到 Web 控制台
|
|
1139
148
|
*/
|
|
1140
|
-
async function
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
? (isEnabled ? " ✓已配置且启用" : " ✓已配置")
|
|
1149
|
-
: "(未配置)";
|
|
1150
|
-
return {
|
|
1151
|
-
title: `${PLATFORM_LABELS[p]}${suffix}`,
|
|
1152
|
-
value: p,
|
|
1153
|
-
selected: isEnabled && hasCreds,
|
|
1154
|
-
};
|
|
1155
|
-
});
|
|
1156
|
-
console.log("\n━━━ 选择要启用的平台 ━━━\n");
|
|
1157
|
-
const resp = await prompts({
|
|
1158
|
-
type: "multiselect",
|
|
1159
|
-
name: "platforms",
|
|
1160
|
-
message: "选择要启用的平台(空格切换,回车确认)",
|
|
1161
|
-
choices,
|
|
1162
|
-
hint: "未配置的平台需先运行 open-im init",
|
|
1163
|
-
}, {
|
|
1164
|
-
onCancel: () => {
|
|
1165
|
-
console.log("\n已取消启动。");
|
|
1166
|
-
process.exit(0);
|
|
1167
|
-
},
|
|
1168
|
-
});
|
|
1169
|
-
if (!resp.platforms || !Array.isArray(resp.platforms))
|
|
1170
|
-
return null;
|
|
1171
|
-
const selected = new Set(resp.platforms);
|
|
1172
|
-
const selectedUnconfigured = ALL_PLATFORMS.filter((p) => selected.has(p) && !withCreds.has(p));
|
|
1173
|
-
if (selectedUnconfigured.length > 0) {
|
|
1174
|
-
console.log("");
|
|
1175
|
-
console.log("您选择了 " +
|
|
1176
|
-
selectedUnconfigured.map((p) => PLATFORM_LABELS[p]).join("、") +
|
|
1177
|
-
",但这些平台尚未配置。");
|
|
1178
|
-
console.log("请运行 open-im init 进行配置,配置完成后再次启动。");
|
|
1179
|
-
console.log("");
|
|
1180
|
-
const runNow = await prompts({
|
|
1181
|
-
type: "confirm",
|
|
1182
|
-
name: "run",
|
|
1183
|
-
message: "是否现在运行配置向导?",
|
|
1184
|
-
initial: true,
|
|
1185
|
-
}, { onCancel: () => process.exit(0) });
|
|
1186
|
-
if (runNow.run) {
|
|
1187
|
-
const saved = await runInteractiveSetup();
|
|
1188
|
-
if (saved) {
|
|
1189
|
-
console.log("\n配置完成,请再次运行 open-im start 或 open-im dev 启动。");
|
|
1190
|
-
}
|
|
1191
|
-
}
|
|
1192
|
-
process.exit(0);
|
|
1193
|
-
}
|
|
1194
|
-
// 至少需要 1 个已配置的平台被选中
|
|
1195
|
-
const selectedWithCreds = ALL_PLATFORMS.filter((p) => selected.has(p) && withCreds.has(p));
|
|
1196
|
-
if (selectedWithCreds.length === 0) {
|
|
1197
|
-
console.log("\n请至少选择 1 个已配置的平台,或运行 open-im init 添加配置。");
|
|
1198
|
-
return null;
|
|
1199
|
-
}
|
|
1200
|
-
// 更新 config.json 中的 platforms.xxx.enabled
|
|
1201
|
-
const updated = { ...existing };
|
|
1202
|
-
if (!updated.platforms)
|
|
1203
|
-
updated.platforms = {};
|
|
1204
|
-
for (const p of ALL_PLATFORMS) {
|
|
1205
|
-
const plat = updated.platforms[p] ?? {};
|
|
1206
|
-
updated.platforms[p] = {
|
|
1207
|
-
...plat,
|
|
1208
|
-
enabled: selected.has(p) && withCreds.has(p),
|
|
1209
|
-
};
|
|
1210
|
-
}
|
|
1211
|
-
const dir = dirname(configPath);
|
|
1212
|
-
if (!existsSync(dir))
|
|
1213
|
-
mkdirSync(dir, { recursive: true });
|
|
1214
|
-
writeFileSync(configPath, JSON.stringify(updated, null, 2), "utf-8");
|
|
1215
|
-
try {
|
|
1216
|
-
chmodSync(configPath, 0o600);
|
|
1217
|
-
}
|
|
1218
|
-
catch { /* ignore on unsupported platforms */ }
|
|
1219
|
-
return loadConfig();
|
|
149
|
+
export async function runInteractiveSetup() {
|
|
150
|
+
console.log('\n━━━ open-im 配置 ━━━\n');
|
|
151
|
+
console.log('请通过 Web 控制台完成配置:');
|
|
152
|
+
console.log(' 1. 运行 open-im start');
|
|
153
|
+
console.log(' 2. 打开 http://127.0.0.1:39282');
|
|
154
|
+
console.log(' 3. 按照设置向导完成配置\n');
|
|
155
|
+
console.log('或运行 open-im dashboard 仅启动 Web 配置服务。\n');
|
|
156
|
+
return true;
|
|
1220
157
|
}
|
package/package.json
CHANGED