@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 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
@@ -1,5 +1,5 @@
1
1
  /**
2
- * 首次运行时的交互式配置引导
2
+ * 首次运行时的交互式配置引导(支持增量:保留已有平台配置)
3
3
  * 使用 prompts 库,兼容 tsx watch、IDE 终端等环境
4
4
  * Telegram Token 使用 readline 避免 Windows 终端 prompts 重绘问题
5
5
  */
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
- console.log("\n━━━ open-im 首次配置 ━━━\n");
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: "飞书 (Feishu/Lark) - 需要 App ID App Secret",
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: true },
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 token = await question("Telegram Bot Token(从 @BotFather 获取): ");
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
- if (feishuResp.appId && feishuResp.appSecret) {
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: feishuResp.appId.trim(),
149
- appSecret: feishuResp.appSecret.trim(),
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
- if (wechatResp.appId && wechatResp.appSecret) {
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: wechatResp.appId.trim(),
181
- appSecret: wechatResp.appSecret.trim(),
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 commonResp = await prompts([
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
- type: "select",
211
- name: "aiCommand",
212
- message: "AI 工具(↑↓ 选择)",
213
- choices: [
214
- { title: "claude-code", value: "claude" },
215
- { title: "codex", value: "codex" },
216
- { title: "cursor", value: "cursor" },
217
- ],
218
- initial: 0,
219
- },
220
- {
221
- type: "text",
222
- name: "workDir",
223
- message: "工作目录",
224
- initial: process.cwd(),
225
- },
226
- ], { onCancel });
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 = parseIds(commonResp.telegramAllowedUserIds);
235
- const feishuIds = parseIds(commonResp.feishuAllowedUserIds);
236
- const wechatIds = parseIds(commonResp.wechatAllowedUserIds);
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
- config.platforms.telegram = {
239
- ...config.platforms.telegram,
326
+ out.platforms.telegram = {
327
+ ...base?.platforms?.telegram,
240
328
  enabled: true,
241
- botToken: config.telegramBotToken ?? undefined,
329
+ botToken: config.telegramBotToken ?? base?.platforms?.telegram?.botToken,
242
330
  allowedUserIds: telegramIds,
243
331
  };
244
332
  }
245
- else {
246
- config.platforms.telegram = {
247
- enabled: false,
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
- config.platforms.feishu = {
253
- ...config.platforms.feishu,
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
- config.platforms.feishu = {
260
- enabled: false,
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
- config.platforms.wechat = {
266
- ...config.platforms.wechat,
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
- config.platforms.wechat = {
273
- enabled: false,
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
- config.claudeWorkDir = (commonResp.workDir || process.cwd()).trim();
278
- config.claudeSkipPermissions = true;
279
- config.aiCommand = commonResp.aiCommand ?? "claude";
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(config, null, 2), "utf-8");
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-beta.4",
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",