@wu529778790/open-im 0.2.6 → 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 +104 -4
- package/dist/cli.js +45 -3
- package/dist/commands/handler.js +10 -3
- package/dist/index.d.ts +3 -0
- package/dist/index.js +15 -7
- package/dist/session/session-manager.js +13 -1
- package/package.json +1 -1
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
|
|
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
|
|
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
|
|
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
|
|
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] === '
|
|
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
|
-
//
|
|
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);
|
package/dist/commands/handler.js
CHANGED
|
@@ -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,
|
|
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
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
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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;
|