@wu529778790/open-im 0.2.7 → 0.2.8

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 CHANGED
@@ -19,23 +19,67 @@ open-im 是一个轻量级的 IM 桥接工具,让你通过 Telegram 就能使
19
19
  ### 方式一:npx(无需安装)
20
20
 
21
21
  ```bash
22
- npx @wu529778790/open-im start
22
+ npx @wu529778790/open-im run
23
23
  ```
24
24
 
25
25
  ### 方式二:全局安装(推荐常用用户)
26
26
 
27
27
  ```bash
28
28
  npm i @wu529778790/open-im -g
29
- open-im start
29
+ open-im run
30
30
  ```
31
31
 
32
32
  首次运行会引导你完成配置,30 秒即可搞定。
33
33
 
34
+ 如果配置引导未出现,可以手动运行:
35
+
36
+ ```bash
37
+ npx @wu529778790/open-im init
38
+ ```
39
+
40
+ ## ⚙️ 配置说明
41
+
42
+ 配置文件位置:`~/.open-im/config.json`
43
+
44
+ 配置文件示例:
45
+
46
+ ```json
47
+ {
48
+ "telegramBotToken": "你的Bot Token(从 @BotFather 获取)",
49
+ "allowedUserIds": ["你的Telegram用户ID"],
50
+ "claudeWorkDir": "/path/to/your/work/dir",
51
+ "claudeSkipPermissions": true,
52
+ "aiCommand": "claude"
53
+ }
54
+ ```
55
+
56
+ 获取 Telegram Bot Token:
57
+ 1. 在 Telegram 中搜索 @BotFather
58
+ 2. 发送 `/newbot` 创建新机器人
59
+ 3. 按提示设置机器人名称
60
+ 4. BotFather 会返回 Token,格式如:`123456789:ABCdefGHIjklMNOpqrsTUVwxyz`
61
+
62
+ 获取 Telegram 用户 ID(可选):
63
+ 1. 在 Telegram 中搜索 @userinfobot
64
+ 2. 发送任意消息
65
+ 3. 机器人会返回你的用户 ID
66
+ 4. 如不设置,则所有人都可以使用你的机器人
67
+
68
+ **或者通过环境变量配置:**
69
+
70
+ ```bash
71
+ export TELEGRAM_BOT_TOKEN="你的Bot Token"
72
+ export ALLOWED_USER_IDS="用户ID1,用户ID2"
73
+ open-im run
74
+ ```
75
+
34
76
  ## 📖 常用命令
35
77
 
36
78
  | 命令 | 说明 |
37
79
  |------|------|
38
- | `open-im start` | 启动服务(后台运行) |
80
+ | `open-im` / `open-im run` | 前台运行(首次使用会引导配置) |
81
+ | `open-im init` | 初始化配置(首次使用或重新配置) |
82
+ | `open-im start` | 后台启动服务 |
39
83
  | `open-im stop` | 停止服务 |
40
84
 
41
85
  ### Telegram 机器人命令
@@ -59,7 +103,7 @@ open-im start
59
103
 
60
104
  ```bash
61
105
  # npx(无需安装)
62
- npx @wu529778790/open-im start
106
+ npx @wu529778790/open-im run
63
107
 
64
108
  # npm 全局安装
65
109
  npm i @wu529778790/open-im -g
@@ -74,3 +118,59 @@ pnpm i @wu529778790/open-im -g
74
118
  ## 📝 License
75
119
 
76
120
  [MIT](LICENSE)
121
+
122
+ ## 🔧 故障排除
123
+
124
+ ### Q: 首次运行没有配置引导?
125
+
126
+ 如果配置引导没有出现,尝试以下方法:
127
+
128
+ 1. **手动运行配置命令:**
129
+ ```bash
130
+ npx @wu529778790/open-im init
131
+ ```
132
+
133
+ 2. **检查是否已有配置文件:**
134
+ ```bash
135
+ cat ~/.open-im/config.json
136
+ ```
137
+
138
+ 3. **手动创建配置文件:**
139
+ ```bash
140
+ mkdir -p ~/.open-im
141
+ cat > ~/.open-im/config.json << 'EOF'
142
+ {
143
+ "telegramBotToken": "你的Bot Token",
144
+ "allowedUserIds": ["你的Telegram用户ID"],
145
+ "claudeWorkDir": "$(pwd)",
146
+ "claudeSkipPermissions": true,
147
+ "aiCommand": "claude"
148
+ }
149
+ EOF
150
+ ```
151
+
152
+ ### Q: 启动后服务立即退出?
153
+
154
+ 可能是配置文件无效,检查配置:
155
+
156
+ ```bash
157
+ # 查看日志
158
+ tail -f ~/.open-im/logs/*.log
159
+
160
+ # 重新配置
161
+ rm ~/.open-im/config.json
162
+ npx @wu529778790/open-im run
163
+ ```
164
+
165
+ ### Q: 如何获取 Telegram Bot Token?
166
+
167
+ 1. 在 Telegram 中搜索 @BotFather
168
+ 2. 发送 `/newbot` 创建新机器人
169
+ 3. 按提示设置机器人名称
170
+ 4. BotFather 会返回 Token,格式如:`123456789:ABCdefGHIjklMNOpqrsTUVwxyz`
171
+
172
+ ### Q: 如何获取 Telegram 用户 ID?
173
+
174
+ 1. 在 Telegram 中搜索 @userinfobot
175
+ 2. 点击"START"或发送任意消息
176
+ 3. 机器人会返回你的用户 ID(数字)
package/dist/cli.js CHANGED
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env node
2
- import { main } from './index.js';
2
+ import { main, needsSetup, runInteractiveSetup } from './index.js';
3
+ import { loadConfig } from './config.js';
3
4
  import { spawn, execFileSync } from 'node:child_process';
4
5
  import { fileURLToPath } from 'node:url';
5
6
  import { dirname, join } from 'node:path';
@@ -129,13 +130,43 @@ async function stopService() {
129
130
  }
130
131
  }
131
132
  const args = process.argv.slice(2);
132
- if (args[0] === 'stop') {
133
+ if (args[0] === 'init') {
134
+ // 手动触发配置
135
+ console.log('\n━━━ open-im 配置向导 ━━━\n');
136
+ const saved = await runInteractiveSetup();
137
+ if (!saved) {
138
+ console.log('配置未完成。');
139
+ process.exit(1);
140
+ }
141
+ console.log('\n✅ 配置完成!现在可以运行以下命令启动服务:\n open-im start\n');
142
+ }
143
+ else if (args[0] === 'stop') {
133
144
  stopService().catch((err) => {
134
145
  console.error('停止服务时出错:', err);
135
146
  process.exit(1);
136
147
  });
137
148
  }
138
149
  else if (args[0] === 'start') {
150
+ // 首先检查是否需要配置
151
+ if (needsSetup()) {
152
+ console.log('\n━━━ open-im 首次配置 ━━━\n');
153
+ console.log('检测到未配置,需要先完成配置才能启动服务\n');
154
+ const saved = await runInteractiveSetup();
155
+ if (!saved) {
156
+ console.log('配置未完成,取消启动。');
157
+ process.exit(1);
158
+ }
159
+ console.log('');
160
+ }
161
+ // 验证配置是否有效(避免有配置文件但缺少必要字段的情况)
162
+ try {
163
+ loadConfig();
164
+ }
165
+ catch (err) {
166
+ console.error('配置无效或缺少必要字段:', err instanceof Error ? err.message : err);
167
+ console.log('\n请运行以下命令重新配置:\n npx @wu529778790/open-im\n');
168
+ process.exit(1);
169
+ }
139
170
  // 获取当前工作目录
140
171
  const currentDir = process.cwd();
141
172
  // 更新配置中的工作目录
@@ -155,8 +186,19 @@ else if (args[0] === 'start') {
155
186
  child.unref();
156
187
  console.log(`服务已在后台启动 (PID: ${child.pid})`);
157
188
  }
189
+ else if (args[0] === 'run' || args.length === 0) {
190
+ // 前台运行(默认命令)
191
+ console.log('\n🚀 正在前台启动 open-im 服务...\n');
192
+ console.log('💡 提示:按 Ctrl+C 可随时停止服务\n');
193
+ main().catch((err) => {
194
+ console.error(err);
195
+ process.exit(1);
196
+ });
197
+ }
158
198
  else {
159
- // 默认启动(兼容直接运行 open-im)
199
+ // 兼容旧版本,无参数时也运行
200
+ console.log('\n🚀 正在前台启动 open-im 服务...\n');
201
+ console.log('💡 提示:按 Ctrl+C 可随时停止服务\n');
160
202
  main().catch((err) => {
161
203
  console.error(err);
162
204
  process.exit(1);
@@ -39,19 +39,24 @@ export class CommandHandler {
39
39
  '📋 可用命令:',
40
40
  '',
41
41
  '/help - 显示帮助',
42
- '/new - 开始新会话',
42
+ '/new - 开始新会话(AI 上下文重置)',
43
43
  '/status - 显示状态',
44
44
  '/cd <路径> - 切换工作目录',
45
45
  '/pwd - 当前工作目录',
46
46
  '/allow (/y) - 允许权限请求',
47
47
  '/deny (/n) - 拒绝权限请求',
48
+ '',
49
+ '💡 提示:清除聊天历史请点击 Telegram 右上角 ⋮ → 清除历史',
48
50
  ].join('\n');
49
51
  await this.deps.sender.sendTextReply(chatId, help);
50
52
  return true;
51
53
  }
52
54
  async handleNew(chatId, userId) {
53
55
  const ok = this.deps.sessionManager.newSession(userId);
54
- await this.deps.sender.sendTextReply(chatId, ok ? '✅ 已开始新会话。' : '当前没有活动会话。');
56
+ await this.deps.sender.sendTextReply(chatId, ok
57
+ ? '✅ AI 会话已重置,下一条消息将使用全新上下文。\n\n' +
58
+ '💡 提示:如需清除本对话的历史消息,请点击 Telegram 聊天右上角 ⋮ → 清除历史'
59
+ : '当前没有活动会话。');
55
60
  return true;
56
61
  }
57
62
  async handlePwd(chatId, userId) {
@@ -84,7 +89,9 @@ export class CommandHandler {
84
89
  }
85
90
  try {
86
91
  const resolved = await this.deps.sessionManager.setWorkDir(userId, dir);
87
- await this.deps.sender.sendTextReply(chatId, `工作目录已切换到: ${resolved}\n会话已重置。`);
92
+ await this.deps.sender.sendTextReply(chatId, `📁 工作目录已切换到: ${resolved}\n\n` +
93
+ `🔄 AI 会话已重置,下一条消息将使用全新上下文。\n` +
94
+ `💡 提示:如需清除本对话的历史消息,请点击 Telegram 聊天右上角 ⋮ → 清除历史`);
88
95
  }
89
96
  catch (err) {
90
97
  await this.deps.sender.sendTextReply(chatId, err instanceof Error ? err.message : String(err));
package/dist/index.d.ts CHANGED
@@ -1 +1,4 @@
1
+ import { needsSetup } from './config.js';
2
+ import { runInteractiveSetup } from './setup.js';
3
+ export { needsSetup, runInteractiveSetup };
1
4
  export declare function main(): Promise<void>;
package/dist/index.js CHANGED
@@ -1,5 +1,7 @@
1
1
  import { loadConfig, needsSetup } from './config.js';
2
2
  import { runInteractiveSetup } from './setup.js';
3
+ // 导出供 cli.ts 使用
4
+ export { needsSetup, runInteractiveSetup };
3
5
  import { initTelegram, stopTelegram } from './telegram/client.js';
4
6
  import { setupTelegramHandlers } from './telegram/event-handler.js';
5
7
  import { sendTextReply } from './telegram/message-sender.js';
@@ -72,17 +74,23 @@ export async function main() {
72
74
  ].join('\n');
73
75
  await sendLifecycleNotification('telegram', startupMsg).catch(() => { });
74
76
  const startedAt = Date.now();
77
+ // 防止重复发送关闭通知
78
+ let shutdownNotificationSent = false;
75
79
  const shutdown = async () => {
76
80
  log.info('Shutting down...');
77
81
  const uptimeSec = Math.floor((Date.now() - startedAt) / 1000);
78
82
  const m = Math.floor(uptimeSec / 60);
79
- try {
80
- await sendLifecycleNotification('telegram', `🔴 open-im 服务正在关闭...\n运行时长: ${m}分钟`);
81
- // 等待消息发送完成
82
- await new Promise(resolve => setTimeout(resolve, 500));
83
- }
84
- catch (err) {
85
- log.debug('Failed to send shutdown notification:', err);
83
+ // 只发送一次通知
84
+ if (!shutdownNotificationSent) {
85
+ shutdownNotificationSent = true;
86
+ try {
87
+ await sendLifecycleNotification('telegram', `🔴 open-im 服务正在关闭...\n运行时长: ${m}分钟`);
88
+ // 等待消息发送完成
89
+ await new Promise(resolve => setTimeout(resolve, 500));
90
+ }
91
+ catch (err) {
92
+ log.debug('Failed to send shutdown notification:', err);
93
+ }
86
94
  }
87
95
  // 清理停止标记文件
88
96
  await clearStopSignal();
@@ -68,12 +68,17 @@ export class SessionManager {
68
68
  const realPath = await this.resolveAndValidate(currentDir, workDir);
69
69
  const s = this.sessions.get(userId);
70
70
  if (s) {
71
+ const oldConvId = s.activeConvId;
71
72
  if (s.activeConvId && s.sessionId) {
72
73
  this.convSessionMap.set(`${userId}:${s.activeConvId}`, s.sessionId);
73
74
  }
74
75
  s.workDir = realPath;
75
76
  s.sessionId = undefined;
76
77
  s.activeConvId = randomBytes(4).toString('hex');
78
+ // 清除旧的 convSessionMap 中的映射
79
+ if (oldConvId) {
80
+ this.convSessionMap.delete(`${userId}:${oldConvId}`);
81
+ }
77
82
  }
78
83
  else {
79
84
  this.sessions.set(userId, {
@@ -82,19 +87,26 @@ export class SessionManager {
82
87
  });
83
88
  }
84
89
  this.flushSync();
85
- log.info(`WorkDir changed for user ${userId}: ${realPath}`);
90
+ log.info(`WorkDir changed for user ${userId}: ${realPath}, oldConvId=${s?.activeConvId}`);
86
91
  return realPath;
87
92
  }
88
93
  newSession(userId) {
89
94
  const s = this.sessions.get(userId);
90
95
  if (s) {
96
+ const oldSessionId = s.sessionId;
97
+ const oldConvId = s.activeConvId;
91
98
  if (s.activeConvId && s.sessionId) {
92
99
  this.convSessionMap.set(`${userId}:${s.activeConvId}`, s.sessionId);
93
100
  }
94
101
  s.sessionId = undefined;
95
102
  s.activeConvId = randomBytes(4).toString('hex');
96
103
  s.totalTurns = 0;
104
+ // 清除旧的 convSessionMap 中的映射,防止恢复旧的 sessionId
105
+ if (oldConvId) {
106
+ this.convSessionMap.delete(`${userId}:${oldConvId}`);
107
+ }
97
108
  this.flushSync();
109
+ log.info(`New session for user ${userId}: oldConvId=${oldConvId}, oldSessionId=${oldSessionId}, newConvId=${s.activeConvId}, sessionId=undefined`);
98
110
  return true;
99
111
  }
100
112
  return false;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wu529778790/open-im",
3
- "version": "0.2.7",
3
+ "version": "0.2.8",
4
4
  "description": "Multi-platform IM bridge for AI CLI tools (Claude, Codex, Cursor)",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",