@wu529778790/open-im 1.0.3-beta.4 → 1.0.3
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/cli.js +1 -7
- package/dist/setup.d.ts +1 -1
- package/dist/setup.js +175 -76
- package/package.json +2 -1
package/dist/cli.js
CHANGED
|
@@ -144,12 +144,6 @@ async function cmdStop() {
|
|
|
144
144
|
console.log(`open-im 已停止 (pid=${pid})`);
|
|
145
145
|
}
|
|
146
146
|
async function cmdInit() {
|
|
147
|
-
if (!needsSetup()) {
|
|
148
|
-
console.log("检测到已存在配置文件。如需重新配置,请先删除配置文件:");
|
|
149
|
-
console.log(` ${join(APP_HOME, "config.json")}`);
|
|
150
|
-
console.log("\n或直接编辑配置文件后重新运行。");
|
|
151
|
-
return;
|
|
152
|
-
}
|
|
153
147
|
console.log("\n━━━ open-im 配置向导 ━━━\n");
|
|
154
148
|
const saved = await runInteractiveSetup();
|
|
155
149
|
if (saved) {
|
|
@@ -170,7 +164,7 @@ function showHelp(exitCode = 0) {
|
|
|
170
164
|
命令:
|
|
171
165
|
start 后台运行服务
|
|
172
166
|
stop 停止后台服务
|
|
173
|
-
init
|
|
167
|
+
init 配置向导(首次或追加配置,会覆盖已有 config.json)
|
|
174
168
|
dev 前台运行(调试模式),Ctrl+C 停止
|
|
175
169
|
|
|
176
170
|
选项:
|
package/dist/setup.d.ts
CHANGED
package/dist/setup.js
CHANGED
|
@@ -1,13 +1,47 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* 首次运行时的交互式配置引导(支持增量:保留已有平台配置)
|
|
3
3
|
* 使用 prompts 库,兼容 tsx watch、IDE 终端等环境
|
|
4
4
|
* Telegram Token 使用 readline 避免 Windows 终端 prompts 重绘问题
|
|
5
5
|
*/
|
|
6
6
|
import prompts from "prompts";
|
|
7
7
|
import { createInterface } from "node:readline";
|
|
8
|
-
import { mkdirSync, writeFileSync, existsSync } from "node:fs";
|
|
8
|
+
import { mkdirSync, writeFileSync, existsSync, readFileSync } from "node:fs";
|
|
9
9
|
import { join, dirname } from "node:path";
|
|
10
10
|
import { APP_HOME } from "./constants.js";
|
|
11
|
+
function loadExistingConfig() {
|
|
12
|
+
const configPath = join(APP_HOME, "config.json");
|
|
13
|
+
if (!existsSync(configPath))
|
|
14
|
+
return null;
|
|
15
|
+
try {
|
|
16
|
+
return JSON.parse(readFileSync(configPath, "utf-8"));
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
function getConfiguredPlatforms(existing) {
|
|
23
|
+
if (!existing?.platforms)
|
|
24
|
+
return [];
|
|
25
|
+
const names = [
|
|
26
|
+
{ k: "telegram", label: "Telegram" },
|
|
27
|
+
{ k: "feishu", label: "飞书" },
|
|
28
|
+
{ k: "wechat", label: "微信" },
|
|
29
|
+
];
|
|
30
|
+
return names
|
|
31
|
+
.filter(({ k }) => {
|
|
32
|
+
const p = existing.platforms?.[k];
|
|
33
|
+
if (!p)
|
|
34
|
+
return false;
|
|
35
|
+
if (k === "telegram")
|
|
36
|
+
return !!p.botToken;
|
|
37
|
+
if (k === "feishu")
|
|
38
|
+
return !!(p.appId && p.appSecret);
|
|
39
|
+
if (k === "wechat")
|
|
40
|
+
return !!(p.appId && p.appSecret);
|
|
41
|
+
return false;
|
|
42
|
+
})
|
|
43
|
+
.map(({ label }) => label);
|
|
44
|
+
}
|
|
11
45
|
function question(prompt) {
|
|
12
46
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
13
47
|
return new Promise((resolve) => {
|
|
@@ -62,26 +96,41 @@ export async function runInteractiveSetup() {
|
|
|
62
96
|
printManualInstructions(configPath);
|
|
63
97
|
return false;
|
|
64
98
|
}
|
|
65
|
-
|
|
99
|
+
const existing = loadExistingConfig();
|
|
100
|
+
const configured = getConfiguredPlatforms(existing);
|
|
101
|
+
console.log("\n━━━ open-im 配置向导 ━━━\n");
|
|
66
102
|
console.log("配置将保存到:", configPath);
|
|
103
|
+
if (configured.length > 0) {
|
|
104
|
+
console.log("当前已配置:", configured.join("、"));
|
|
105
|
+
console.log("(本次可追加或修改,未选中的平台配置将保留)");
|
|
106
|
+
}
|
|
67
107
|
console.log("");
|
|
68
108
|
const onCancel = () => {
|
|
69
109
|
console.log("\n已取消配置。");
|
|
70
110
|
process.exit(0);
|
|
71
111
|
};
|
|
72
|
-
|
|
112
|
+
const hasTg = !!existing?.platforms?.telegram?.botToken;
|
|
113
|
+
const hasFs = !!(existing?.platforms?.feishu?.appId && existing?.platforms?.feishu?.appSecret);
|
|
114
|
+
const hasWc = !!(existing?.platforms?.wechat?.appId && existing?.platforms?.wechat?.appSecret);
|
|
115
|
+
// 第一步:选择平台(在选项和提示中显示已配置项)
|
|
116
|
+
const configuredHint = configured.length > 0 ? `(当前已配置: ${configured.join("、")})` : "";
|
|
73
117
|
const platformResp = await prompts({
|
|
74
118
|
type: "select",
|
|
75
119
|
name: "platform",
|
|
76
|
-
message:
|
|
120
|
+
message: `选择要配置的平台 ${configuredHint}(↑↓ 选择)`,
|
|
77
121
|
choices: [
|
|
78
|
-
{ title: "Telegram - 需要 Bot Token", value: "telegram" },
|
|
79
122
|
{
|
|
80
|
-
title: "
|
|
123
|
+
title: "Telegram - 需要 Bot Token" + (hasTg ? " ✓已配置" : ""),
|
|
124
|
+
value: "telegram",
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
title: "飞书 (Feishu/Lark) - 需要 App ID 和 App Secret" +
|
|
128
|
+
(hasFs ? " ✓已配置" : ""),
|
|
81
129
|
value: "feishu",
|
|
82
130
|
},
|
|
83
131
|
{
|
|
84
|
-
title: "微信 (WeChat) - 需要 App ID 和 App Secret(AGP 协议)"
|
|
132
|
+
title: "微信 (WeChat) - 需要 App ID 和 App Secret(AGP 协议)" +
|
|
133
|
+
(hasWc ? " ✓已配置" : ""),
|
|
85
134
|
value: "wechat",
|
|
86
135
|
},
|
|
87
136
|
{ title: "配置多个平台", value: "multi" },
|
|
@@ -101,9 +150,9 @@ export async function runInteractiveSetup() {
|
|
|
101
150
|
name: "platforms",
|
|
102
151
|
message: "选择要配置的平台(空格选择,回车确认)",
|
|
103
152
|
choices: [
|
|
104
|
-
{ title: "Telegram", value: "telegram", selected:
|
|
105
|
-
{ title: "飞书 (Feishu)", value: "feishu" },
|
|
106
|
-
{ title: "微信 (WeChat)", value: "wechat" },
|
|
153
|
+
{ title: "Telegram" + (hasTg ? " ✓已配置" : ""), value: "telegram", selected: hasTg },
|
|
154
|
+
{ title: "飞书 (Feishu)" + (hasFs ? " ✓已配置" : ""), value: "feishu", selected: hasFs },
|
|
155
|
+
{ title: "微信 (WeChat)" + (hasWc ? " ✓已配置" : ""), value: "wechat", selected: hasWc },
|
|
107
156
|
],
|
|
108
157
|
}, { onCancel });
|
|
109
158
|
if (!multiResp.platforms || multiResp.platforms.length === 0) {
|
|
@@ -117,10 +166,14 @@ export async function runInteractiveSetup() {
|
|
|
117
166
|
}
|
|
118
167
|
// 收集平台配置(Telegram 用 readline 避免 Windows 下 prompts 重绘/重复行问题)
|
|
119
168
|
if (selectedPlatforms.includes("telegram")) {
|
|
120
|
-
const
|
|
169
|
+
const hint = hasTg ? "(留空保留现有)" : "";
|
|
170
|
+
const token = await question(`Telegram Bot Token(从 @BotFather 获取)${hint}: `);
|
|
121
171
|
if (token) {
|
|
122
172
|
config.telegramBotToken = token;
|
|
123
173
|
}
|
|
174
|
+
else if (hasTg) {
|
|
175
|
+
config.telegramBotToken = existing.platforms.telegram.botToken;
|
|
176
|
+
}
|
|
124
177
|
else if (platform === "telegram") {
|
|
125
178
|
console.log("Token 不能为空");
|
|
126
179
|
return false;
|
|
@@ -132,21 +185,25 @@ export async function runInteractiveSetup() {
|
|
|
132
185
|
type: "text",
|
|
133
186
|
name: "appId",
|
|
134
187
|
message: "飞书 App ID(从飞书开放平台获取)",
|
|
188
|
+
initial: existing?.platforms?.feishu?.appId ?? "",
|
|
135
189
|
validate: (v) => (v.trim() ? true : "App ID 不能为空"),
|
|
136
190
|
},
|
|
137
191
|
{
|
|
138
192
|
type: "text",
|
|
139
193
|
name: "appSecret",
|
|
140
194
|
message: "飞书 App Secret(从飞书开放平台获取)",
|
|
195
|
+
initial: existing?.platforms?.feishu?.appSecret ?? "",
|
|
141
196
|
validate: (v) => (v.trim() ? true : "App Secret 不能为空"),
|
|
142
197
|
},
|
|
143
198
|
], { onCancel });
|
|
144
|
-
|
|
199
|
+
const fsAppId = feishuResp.appId?.trim() || existing?.platforms?.feishu?.appId;
|
|
200
|
+
const fsAppSecret = feishuResp.appSecret?.trim() || existing?.platforms?.feishu?.appSecret;
|
|
201
|
+
if (fsAppId && fsAppSecret) {
|
|
145
202
|
config.platforms.feishu = {
|
|
146
203
|
...config.platforms.feishu,
|
|
147
204
|
enabled: true,
|
|
148
|
-
appId:
|
|
149
|
-
appSecret:
|
|
205
|
+
appId: fsAppId,
|
|
206
|
+
appSecret: fsAppSecret,
|
|
150
207
|
};
|
|
151
208
|
}
|
|
152
209
|
else if (platform === "feishu") {
|
|
@@ -159,129 +216,171 @@ export async function runInteractiveSetup() {
|
|
|
159
216
|
type: "text",
|
|
160
217
|
name: "appId",
|
|
161
218
|
message: "微信 App ID(从微信开放平台获取)",
|
|
219
|
+
initial: existing?.platforms?.wechat?.appId ?? "",
|
|
162
220
|
validate: (v) => (v.trim() ? true : "App ID 不能为空"),
|
|
163
221
|
},
|
|
164
222
|
{
|
|
165
223
|
type: "text",
|
|
166
224
|
name: "appSecret",
|
|
167
225
|
message: "微信 App Secret(从微信开放平台获取)",
|
|
226
|
+
initial: existing?.platforms?.wechat?.appSecret ?? "",
|
|
168
227
|
validate: (v) => (v.trim() ? true : "App Secret 不能为空"),
|
|
169
228
|
},
|
|
170
229
|
{
|
|
171
230
|
type: "text",
|
|
172
231
|
name: "wsUrl",
|
|
173
232
|
message: "AGP WebSocket URL(可选,留空使用默认)",
|
|
174
|
-
initial: "",
|
|
233
|
+
initial: existing?.platforms?.wechat?.wsUrl ?? "",
|
|
175
234
|
},
|
|
176
235
|
], { onCancel });
|
|
177
|
-
|
|
236
|
+
const wcAppId = wechatResp.appId?.trim() || existing?.platforms?.wechat?.appId;
|
|
237
|
+
const wcAppSecret = wechatResp.appSecret?.trim() || existing?.platforms?.wechat?.appSecret;
|
|
238
|
+
if (wcAppId && wcAppSecret) {
|
|
178
239
|
config.platforms.wechat = {
|
|
179
240
|
enabled: true,
|
|
180
|
-
appId:
|
|
181
|
-
appSecret:
|
|
182
|
-
wsUrl: wechatResp.wsUrl?.trim() || undefined,
|
|
241
|
+
appId: wcAppId,
|
|
242
|
+
appSecret: wcAppSecret,
|
|
243
|
+
wsUrl: wechatResp.wsUrl?.trim() || existing?.platforms?.wechat?.wsUrl || undefined,
|
|
183
244
|
};
|
|
184
245
|
}
|
|
185
246
|
else if (platform === "wechat") {
|
|
186
247
|
return false;
|
|
187
248
|
}
|
|
188
249
|
}
|
|
189
|
-
//
|
|
190
|
-
const
|
|
191
|
-
|
|
250
|
+
// 通用配置:只询问所选平台的白名单,未选平台沿用已有配置
|
|
251
|
+
const tgIds = existing?.platforms?.telegram?.allowedUserIds?.join(", ") ?? "";
|
|
252
|
+
const fsIds = existing?.platforms?.feishu?.allowedUserIds?.join(", ") ?? "";
|
|
253
|
+
const wcIds = existing?.platforms?.wechat?.allowedUserIds?.join(", ") ?? "";
|
|
254
|
+
const aiIdx = ["claude", "codex", "cursor"].indexOf(existing?.aiCommand ?? "claude");
|
|
255
|
+
const commonPrompts = [];
|
|
256
|
+
if (selectedPlatforms.includes("telegram")) {
|
|
257
|
+
commonPrompts.push({
|
|
192
258
|
type: "text",
|
|
193
259
|
name: "telegramAllowedUserIds",
|
|
194
260
|
message: "Telegram 白名单用户 ID(可选,逗号分隔,留空=所有人可访问)",
|
|
195
|
-
initial:
|
|
196
|
-
}
|
|
197
|
-
|
|
261
|
+
initial: tgIds,
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
if (selectedPlatforms.includes("feishu")) {
|
|
265
|
+
commonPrompts.push({
|
|
198
266
|
type: "text",
|
|
199
267
|
name: "feishuAllowedUserIds",
|
|
200
268
|
message: "飞书白名单用户 ID(可选,逗号分隔,留空=所有人可访问)",
|
|
201
|
-
initial:
|
|
202
|
-
}
|
|
203
|
-
|
|
269
|
+
initial: fsIds,
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
if (selectedPlatforms.includes("wechat")) {
|
|
273
|
+
commonPrompts.push({
|
|
204
274
|
type: "text",
|
|
205
275
|
name: "wechatAllowedUserIds",
|
|
206
276
|
message: "微信白名单用户 ID(可选,逗号分隔,留空=所有人可访问)",
|
|
207
|
-
initial:
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
277
|
+
initial: wcIds,
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
commonPrompts.push({
|
|
281
|
+
type: "select",
|
|
282
|
+
name: "aiCommand",
|
|
283
|
+
message: "AI 工具(↑↓ 选择)",
|
|
284
|
+
choices: [
|
|
285
|
+
{ title: "claude-code", value: "claude" },
|
|
286
|
+
{ title: "codex", value: "codex" },
|
|
287
|
+
{ title: "cursor", value: "cursor" },
|
|
288
|
+
],
|
|
289
|
+
initial: aiIdx >= 0 ? aiIdx : 0,
|
|
290
|
+
}, {
|
|
291
|
+
type: "text",
|
|
292
|
+
name: "workDir",
|
|
293
|
+
message: "工作目录",
|
|
294
|
+
initial: existing?.claudeWorkDir ?? process.cwd(),
|
|
295
|
+
});
|
|
296
|
+
const commonResp = await prompts(commonPrompts, { onCancel });
|
|
227
297
|
const parseIds = (value) => value
|
|
228
298
|
? value
|
|
229
299
|
.split(",")
|
|
230
300
|
.map((s) => s.trim())
|
|
231
301
|
.filter(Boolean)
|
|
232
302
|
: [];
|
|
233
|
-
//
|
|
234
|
-
const telegramIds =
|
|
235
|
-
|
|
236
|
-
|
|
303
|
+
// 分平台白名单:已询问的用输入值,未询问的用已有配置
|
|
304
|
+
const telegramIds = selectedPlatforms.includes("telegram")
|
|
305
|
+
? parseIds(commonResp.telegramAllowedUserIds)
|
|
306
|
+
: parseIds(existing?.platforms?.telegram?.allowedUserIds?.join(", "));
|
|
307
|
+
const feishuIds = selectedPlatforms.includes("feishu")
|
|
308
|
+
? parseIds(commonResp.feishuAllowedUserIds)
|
|
309
|
+
: parseIds(existing?.platforms?.feishu?.allowedUserIds?.join(", "));
|
|
310
|
+
const wechatIds = selectedPlatforms.includes("wechat")
|
|
311
|
+
? parseIds(commonResp.wechatAllowedUserIds)
|
|
312
|
+
: parseIds(existing?.platforms?.wechat?.allowedUserIds?.join(", "));
|
|
313
|
+
// 增量合并:以已有配置为底,只覆盖本次选中的平台(不写入根级旧字段 telegramBotToken 等)
|
|
314
|
+
const base = existing
|
|
315
|
+
? JSON.parse(JSON.stringify(existing))
|
|
316
|
+
: null;
|
|
317
|
+
const { telegramBotToken: _, feishuAppId: __, feishuAppSecret: ___, ...baseRest } = (base ?? {});
|
|
318
|
+
const out = {
|
|
319
|
+
...baseRest,
|
|
320
|
+
platforms: { ...(base?.platforms ?? {}) },
|
|
321
|
+
claudeWorkDir: (commonResp.workDir || process.cwd()).trim(),
|
|
322
|
+
claudeSkipPermissions: base?.claudeSkipPermissions ?? true,
|
|
323
|
+
aiCommand: commonResp.aiCommand ?? base?.aiCommand ?? "claude",
|
|
324
|
+
};
|
|
237
325
|
if (selectedPlatforms.includes("telegram")) {
|
|
238
|
-
|
|
239
|
-
...
|
|
326
|
+
out.platforms.telegram = {
|
|
327
|
+
...base?.platforms?.telegram,
|
|
240
328
|
enabled: true,
|
|
241
|
-
botToken: config.telegramBotToken ??
|
|
329
|
+
botToken: config.telegramBotToken ?? base?.platforms?.telegram?.botToken,
|
|
242
330
|
allowedUserIds: telegramIds,
|
|
243
331
|
};
|
|
244
332
|
}
|
|
245
|
-
else {
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
allowedUserIds: telegramIds,
|
|
333
|
+
else if (base?.platforms?.telegram) {
|
|
334
|
+
out.platforms.telegram = {
|
|
335
|
+
...base.platforms.telegram,
|
|
336
|
+
allowedUserIds: telegramIds.length > 0 ? telegramIds : base.platforms.telegram.allowedUserIds,
|
|
249
337
|
};
|
|
250
338
|
}
|
|
339
|
+
else {
|
|
340
|
+
out.platforms.telegram = { enabled: false, allowedUserIds: telegramIds };
|
|
341
|
+
}
|
|
251
342
|
if (selectedPlatforms.includes("feishu")) {
|
|
252
|
-
|
|
253
|
-
...
|
|
343
|
+
out.platforms.feishu = {
|
|
344
|
+
...base?.platforms?.feishu,
|
|
254
345
|
enabled: true,
|
|
346
|
+
appId: config.platforms.feishu?.appId,
|
|
347
|
+
appSecret: config.platforms.feishu?.appSecret,
|
|
255
348
|
allowedUserIds: feishuIds,
|
|
256
349
|
};
|
|
257
350
|
}
|
|
258
|
-
else {
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
allowedUserIds: feishuIds,
|
|
351
|
+
else if (base?.platforms?.feishu) {
|
|
352
|
+
out.platforms.feishu = {
|
|
353
|
+
...base.platforms.feishu,
|
|
354
|
+
allowedUserIds: feishuIds.length > 0 ? feishuIds : base.platforms.feishu.allowedUserIds,
|
|
262
355
|
};
|
|
263
356
|
}
|
|
357
|
+
else {
|
|
358
|
+
out.platforms.feishu = { enabled: false, allowedUserIds: feishuIds };
|
|
359
|
+
}
|
|
264
360
|
if (selectedPlatforms.includes("wechat")) {
|
|
265
|
-
|
|
266
|
-
...
|
|
361
|
+
out.platforms.wechat = {
|
|
362
|
+
...base?.platforms?.wechat,
|
|
267
363
|
enabled: true,
|
|
364
|
+
appId: config.platforms.wechat?.appId,
|
|
365
|
+
appSecret: config.platforms.wechat?.appSecret,
|
|
366
|
+
wsUrl: config.platforms.wechat?.wsUrl,
|
|
268
367
|
allowedUserIds: wechatIds,
|
|
269
368
|
};
|
|
270
369
|
}
|
|
271
|
-
else {
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
allowedUserIds: wechatIds,
|
|
370
|
+
else if (base?.platforms?.wechat) {
|
|
371
|
+
out.platforms.wechat = {
|
|
372
|
+
...base.platforms.wechat,
|
|
373
|
+
allowedUserIds: wechatIds.length > 0 ? wechatIds : base.platforms.wechat.allowedUserIds,
|
|
275
374
|
};
|
|
276
375
|
}
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
376
|
+
else {
|
|
377
|
+
out.platforms.wechat = { enabled: false, allowedUserIds: wechatIds };
|
|
378
|
+
}
|
|
280
379
|
const dir = dirname(configPath);
|
|
281
380
|
if (!existsSync(dir)) {
|
|
282
381
|
mkdirSync(dir, { recursive: true });
|
|
283
382
|
}
|
|
284
|
-
writeFileSync(configPath, JSON.stringify(
|
|
383
|
+
writeFileSync(configPath, JSON.stringify(out, null, 2), "utf-8");
|
|
285
384
|
console.log("\n✅ 配置已保存到", configPath);
|
|
286
385
|
console.log("");
|
|
287
386
|
return true;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wu529778790/open-im",
|
|
3
|
-
"version": "1.0.3
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"description": "Multi-platform IM bridge for AI CLI tools (Claude, Codex, Cursor)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
"scripts": {
|
|
17
17
|
"build": "tsc",
|
|
18
18
|
"dev": "tsx src/index.ts",
|
|
19
|
+
"init": "tsx src/cli.ts init",
|
|
19
20
|
"start": "node dist/cli.js start",
|
|
20
21
|
"stop": "node dist/cli.js stop",
|
|
21
22
|
"test": "npm run build",
|