@wu529778790/open-im 1.6.5-beta.0 → 1.6.5-beta.2
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 +24 -1
- package/README.zh-CN.md +41 -5
- package/dist/codebuddy/cli-runner.d.ts +6 -0
- package/dist/codebuddy/cli-runner.js +41 -11
- package/dist/codebuddy/cli-runner.test.js +34 -1
- package/dist/setup.js +9 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -105,6 +105,25 @@ Both the official API and compatible third-party endpoints are supported:
|
|
|
105
105
|
}
|
|
106
106
|
```
|
|
107
107
|
|
|
108
|
+
### CodeBuddy
|
|
109
|
+
|
|
110
|
+
CodeBuddy uses the local CLI. Install it first, then either log in interactively or provide credentials through `env`.
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
npm install -g @tencent-ai/codebuddy-code
|
|
114
|
+
codebuddy --version
|
|
115
|
+
codebuddy login
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Common config keys:
|
|
119
|
+
|
|
120
|
+
- `tools.codebuddy.cliPath`: CLI path, defaults to `codebuddy`
|
|
121
|
+
- `tools.codebuddy.skipPermissions`: whether to skip permission confirmation, defaults to `true`
|
|
122
|
+
- `tools.codebuddy.timeoutMs`: total execution timeout, defaults to `600000`
|
|
123
|
+
- `platforms.<platform>.aiCommand`: set to `codebuddy` if that IM platform should use CodeBuddy
|
|
124
|
+
|
|
125
|
+
On Windows, if `cliPath` is still `codebuddy`, open-im also tries common npm global locations such as `AppData\\Roaming\\npm\\codebuddy.cmd`.
|
|
126
|
+
|
|
108
127
|
### Example Config File
|
|
109
128
|
|
|
110
129
|
The following is valid JSON and can be saved directly as `~/.open-im/config.json`:
|
|
@@ -152,7 +171,7 @@ The following is valid JSON and can be saved directly as `~/.open-im/config.json
|
|
|
152
171
|
},
|
|
153
172
|
"qq": {
|
|
154
173
|
"enabled": false,
|
|
155
|
-
"aiCommand": "
|
|
174
|
+
"aiCommand": "codebuddy",
|
|
156
175
|
"allowedUserIds": [],
|
|
157
176
|
"appId": "YOUR_QQ_APP_ID",
|
|
158
177
|
"secret": "YOUR_QQ_APP_SECRET"
|
|
@@ -195,6 +214,10 @@ The following is valid JSON and can be saved directly as `~/.open-im/config.json
|
|
|
195
214
|
| `CODEX_PROXY` | Proxy used by Codex to access `chatgpt.com` |
|
|
196
215
|
| `OPENAI_API_KEY` | Codex API key, can replace `codex login` |
|
|
197
216
|
| `CURSOR_API_KEY` | Cursor API key, can replace `agent login` |
|
|
217
|
+
| `CODEBUDDY_CLI_PATH` | Override CodeBuddy CLI path |
|
|
218
|
+
| `CODEBUDDY_TIMEOUT_MS` | Override CodeBuddy timeout |
|
|
219
|
+
| `CODEBUDDY_SKIP_PERMISSIONS` | Override CodeBuddy skip-permissions behavior |
|
|
220
|
+
| `CODEBUDDY_IDLE_TIMEOUT_MS` | Abort CodeBuddy when it stays silent for too long |
|
|
198
221
|
| `CODEBUDDY_API_KEY` | CodeBuddy API key, can replace `codebuddy login` |
|
|
199
222
|
| `CODEBUDDY_AUTH_TOKEN` | CodeBuddy auth token, can replace `codebuddy login` |
|
|
200
223
|
| `TELEGRAM_BOT_TOKEN` | Telegram bot token |
|
package/README.zh-CN.md
CHANGED
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
[English](./README.md)
|
|
4
4
|
|
|
5
|
-
多平台 IM 桥接工具,把 Telegram、飞书、企业微信、钉钉、QQ、微信接到 AI CLI 工具(Claude Code、Codex、Cursor),方便在手机或聊天窗口里远程使用 AI 编程助手。
|
|
5
|
+
多平台 IM 桥接工具,把 Telegram、飞书、企业微信、钉钉、QQ、微信接到 AI CLI 工具(Claude Code、Codex、Cursor、CodeBuddy),方便在手机或聊天窗口里远程使用 AI 编程助手。
|
|
6
6
|
|
|
7
7
|
## 功能特性
|
|
8
8
|
|
|
9
9
|
- 多平台:支持 Telegram、飞书、企业微信、钉钉、QQ、微信(测试中),可同时启用
|
|
10
|
-
- 多 AI 工具:支持 Claude、Codex、Cursor
|
|
10
|
+
- 多 AI 工具:支持 Claude、Codex、Cursor、CodeBuddy
|
|
11
11
|
- 按平台分配 AI:根级 `aiCommand` 作为默认值,`platforms.<name>.aiCommand` 可为不同 IM 单独指定 AI 工具
|
|
12
12
|
- 流式输出:实时回传 AI 回复与工具执行进度(目前钉钉暂未实现流式传输)
|
|
13
13
|
- 图形化配置页面 / CLI 配置引导
|
|
@@ -72,12 +72,16 @@ open-im start
|
|
|
72
72
|
"feishu": {
|
|
73
73
|
"enabled": true,
|
|
74
74
|
"aiCommand": "cursor"
|
|
75
|
+
},
|
|
76
|
+
"qq": {
|
|
77
|
+
"enabled": true,
|
|
78
|
+
"aiCommand": "codebuddy"
|
|
75
79
|
}
|
|
76
80
|
}
|
|
77
81
|
}
|
|
78
82
|
```
|
|
79
83
|
|
|
80
|
-
这个配置下,Telegram 会走 Codex,飞书会走 Cursor,其他未单独指定 `aiCommand` 的平台仍然使用 Claude。
|
|
84
|
+
这个配置下,Telegram 会走 Codex,飞书会走 Cursor,QQ 会走 CodeBuddy,其他未单独指定 `aiCommand` 的平台仍然使用 Claude。
|
|
81
85
|
|
|
82
86
|
### Claude
|
|
83
87
|
|
|
@@ -101,6 +105,25 @@ Claude 默认使用 Agent SDK,不依赖本地 `claude` 可执行文件;通
|
|
|
101
105
|
}
|
|
102
106
|
```
|
|
103
107
|
|
|
108
|
+
### CodeBuddy
|
|
109
|
+
|
|
110
|
+
CodeBuddy 依赖本地 CLI。先安装 CLI,再通过交互登录或在 `env` 中提供凭证。
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
npm install -g @tencent-ai/codebuddy-code
|
|
114
|
+
codebuddy --version
|
|
115
|
+
codebuddy login
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
常用配置项:
|
|
119
|
+
|
|
120
|
+
- `tools.codebuddy.cliPath`:CLI 路径,默认 `codebuddy`
|
|
121
|
+
- `tools.codebuddy.skipPermissions`:是否跳过权限确认,默认 `true`
|
|
122
|
+
- `tools.codebuddy.timeoutMs`:总执行超时,默认 `600000`
|
|
123
|
+
- `platforms.<platform>.aiCommand`:若某个平台要走 CodeBuddy,可设为 `codebuddy`
|
|
124
|
+
|
|
125
|
+
在 Windows 上,如果 `cliPath` 仍然是 `codebuddy`,open-im 还会自动尝试 `AppData\\Roaming\\npm\\codebuddy.cmd` 等常见全局安装路径。
|
|
126
|
+
|
|
104
127
|
### 配置文件示例
|
|
105
128
|
|
|
106
129
|
下面示例是合法 JSON,可直接保存为 `~/.open-im/config.json`:
|
|
@@ -124,6 +147,11 @@ Claude 默认使用 Agent SDK,不依赖本地 `claude` 可执行文件;通
|
|
|
124
147
|
"workDir": "D:/coding/open-im",
|
|
125
148
|
"skipPermissions": true,
|
|
126
149
|
"proxy": "http://127.0.0.1:7890"
|
|
150
|
+
},
|
|
151
|
+
"codebuddy": {
|
|
152
|
+
"cliPath": "codebuddy",
|
|
153
|
+
"skipPermissions": true,
|
|
154
|
+
"timeoutMs": 600000
|
|
127
155
|
}
|
|
128
156
|
},
|
|
129
157
|
"platforms": {
|
|
@@ -143,7 +171,7 @@ Claude 默认使用 Agent SDK,不依赖本地 `claude` 可执行文件;通
|
|
|
143
171
|
},
|
|
144
172
|
"qq": {
|
|
145
173
|
"enabled": false,
|
|
146
|
-
"aiCommand": "
|
|
174
|
+
"aiCommand": "codebuddy",
|
|
147
175
|
"allowedUserIds": [],
|
|
148
176
|
"appId": "YOUR_QQ_APP_ID",
|
|
149
177
|
"secret": "YOUR_QQ_APP_SECRET"
|
|
@@ -178,7 +206,7 @@ Claude 默认使用 Agent SDK,不依赖本地 `claude` 可执行文件;通
|
|
|
178
206
|
|
|
179
207
|
| 变量 | 说明 |
|
|
180
208
|
| ---- | ---- |
|
|
181
|
-
| `AI_COMMAND` | 选择 `claude` / `codex` / `cursor` |
|
|
209
|
+
| `AI_COMMAND` | 选择 `claude` / `codex` / `cursor` / `codebuddy` |
|
|
182
210
|
| `CLAUDE_WORK_DIR` | 默认会话目录 |
|
|
183
211
|
| `LOG_DIR` | 日志目录 |
|
|
184
212
|
| `LOG_LEVEL` | 日志级别 |
|
|
@@ -186,6 +214,12 @@ Claude 默认使用 Agent SDK,不依赖本地 `claude` 可执行文件;通
|
|
|
186
214
|
| `CODEX_PROXY` | Codex 访问 `chatgpt.com` 的代理 |
|
|
187
215
|
| `OPENAI_API_KEY` | Codex API Key,可替代 `codex login` |
|
|
188
216
|
| `CURSOR_API_KEY` | Cursor API Key,可替代 `agent login` |
|
|
217
|
+
| `CODEBUDDY_CLI_PATH` | 覆盖 CodeBuddy CLI 路径 |
|
|
218
|
+
| `CODEBUDDY_TIMEOUT_MS` | 覆盖 CodeBuddy 超时 |
|
|
219
|
+
| `CODEBUDDY_SKIP_PERMISSIONS` | 覆盖 CodeBuddy 的跳过权限确认行为 |
|
|
220
|
+
| `CODEBUDDY_IDLE_TIMEOUT_MS` | CodeBuddy 长时间无输出时自动终止 |
|
|
221
|
+
| `CODEBUDDY_API_KEY` | CodeBuddy API Key,可替代 `codebuddy login` |
|
|
222
|
+
| `CODEBUDDY_AUTH_TOKEN` | CodeBuddy Auth Token,可替代 `codebuddy login` |
|
|
189
223
|
| `TELEGRAM_BOT_TOKEN` | Telegram Bot Token |
|
|
190
224
|
| `TELEGRAM_PROXY` | Telegram 代理地址 |
|
|
191
225
|
| `TELEGRAM_ALLOWED_USER_IDS` | Telegram 白名单 |
|
|
@@ -261,6 +295,8 @@ Claude 默认使用 Agent SDK,不依赖本地 `claude` 可执行文件;通
|
|
|
261
295
|
|
|
262
296
|
**Codex 报 `stream disconnected` / `error sending request`**:无法访问 `chatgpt.com`,请配置 `tools.codex.proxy` 或环境变量 `CODEX_PROXY`。
|
|
263
297
|
|
|
298
|
+
**CodeBuddy 提示需要登录**:先执行 `codebuddy login`,或在 `env` 中设置 `CODEBUDDY_API_KEY` / `CODEBUDDY_AUTH_TOKEN`。
|
|
299
|
+
|
|
264
300
|
## License
|
|
265
301
|
|
|
266
302
|
[MIT](LICENSE)
|
|
@@ -26,5 +26,11 @@ export interface CodeBuddyRunHandle {
|
|
|
26
26
|
abort: () => void;
|
|
27
27
|
}
|
|
28
28
|
export declare function buildCodeBuddyArgs(prompt: string, sessionId: string | undefined, options?: CodeBuddyRunOptions): string[];
|
|
29
|
+
export declare function extractBufferedPayloads(state: {
|
|
30
|
+
buffer: string;
|
|
31
|
+
}): string[];
|
|
32
|
+
export declare function flushBufferedPayloads(state: {
|
|
33
|
+
buffer: string;
|
|
34
|
+
}): string[];
|
|
29
35
|
export declare function runCodeBuddy(cliPath: string, prompt: string, sessionId: string | undefined, workDir: string, callbacks: CodeBuddyRunCallbacks, options?: CodeBuddyRunOptions): CodeBuddyRunHandle;
|
|
30
36
|
export declare function checkCodeBuddyCliAvailable(cliPath: string): boolean;
|
|
@@ -97,6 +97,41 @@ function extractSseFrames(state) {
|
|
|
97
97
|
}
|
|
98
98
|
return frames;
|
|
99
99
|
}
|
|
100
|
+
function extractNdjsonPayloads(state) {
|
|
101
|
+
const payloads = [];
|
|
102
|
+
while (true) {
|
|
103
|
+
const newlineIndex = state.buffer.indexOf('\n');
|
|
104
|
+
if (newlineIndex < 0)
|
|
105
|
+
break;
|
|
106
|
+
const line = state.buffer.slice(0, newlineIndex).trim();
|
|
107
|
+
state.buffer = state.buffer.slice(newlineIndex + 1);
|
|
108
|
+
if (line)
|
|
109
|
+
payloads.push(line);
|
|
110
|
+
}
|
|
111
|
+
return payloads;
|
|
112
|
+
}
|
|
113
|
+
export function extractBufferedPayloads(state) {
|
|
114
|
+
const payloads = [];
|
|
115
|
+
if (state.buffer.includes('event:') || state.buffer.includes('data:')) {
|
|
116
|
+
for (const frame of extractSseFrames(state)) {
|
|
117
|
+
if (frame.event === 'done')
|
|
118
|
+
continue;
|
|
119
|
+
payloads.push(frame.data);
|
|
120
|
+
}
|
|
121
|
+
return payloads;
|
|
122
|
+
}
|
|
123
|
+
payloads.push(...extractNdjsonPayloads(state));
|
|
124
|
+
return payloads;
|
|
125
|
+
}
|
|
126
|
+
export function flushBufferedPayloads(state) {
|
|
127
|
+
const payloads = extractBufferedPayloads(state);
|
|
128
|
+
const trailing = state.buffer.trim();
|
|
129
|
+
if (trailing) {
|
|
130
|
+
payloads.push(trailing);
|
|
131
|
+
state.buffer = '';
|
|
132
|
+
}
|
|
133
|
+
return payloads;
|
|
134
|
+
}
|
|
100
135
|
function extractTextBlocks(content) {
|
|
101
136
|
if (!Array.isArray(content))
|
|
102
137
|
return { text: '', thinking: '' };
|
|
@@ -291,15 +326,13 @@ export function runCodeBuddy(cliPath, prompt, sessionId, workDir, callbacks, opt
|
|
|
291
326
|
child.stdout?.on('data', (chunk) => {
|
|
292
327
|
resetIdleTimeout();
|
|
293
328
|
stdoutState.buffer += chunk.toString();
|
|
294
|
-
const
|
|
295
|
-
for (const
|
|
296
|
-
if (frame.event === 'done')
|
|
297
|
-
continue;
|
|
329
|
+
const payloads = extractBufferedPayloads(stdoutState);
|
|
330
|
+
for (const payload of payloads) {
|
|
298
331
|
try {
|
|
299
|
-
handlePayload(JSON.parse(
|
|
332
|
+
handlePayload(JSON.parse(payload));
|
|
300
333
|
}
|
|
301
334
|
catch {
|
|
302
|
-
log.debug(`Failed to parse CodeBuddy stream payload: ${
|
|
335
|
+
log.debug(`Failed to parse CodeBuddy stream payload: ${payload.slice(0, 200)}`);
|
|
303
336
|
}
|
|
304
337
|
}
|
|
305
338
|
});
|
|
@@ -315,17 +348,14 @@ export function runCodeBuddy(cliPath, prompt, sessionId, workDir, callbacks, opt
|
|
|
315
348
|
if (completed)
|
|
316
349
|
return;
|
|
317
350
|
if (stdoutState.buffer.trim()) {
|
|
318
|
-
for (const
|
|
319
|
-
if (frame.event === 'done')
|
|
320
|
-
continue;
|
|
351
|
+
for (const payload of flushBufferedPayloads(stdoutState)) {
|
|
321
352
|
try {
|
|
322
|
-
handlePayload(JSON.parse(
|
|
353
|
+
handlePayload(JSON.parse(payload));
|
|
323
354
|
}
|
|
324
355
|
catch {
|
|
325
356
|
// Ignore trailing partial payloads.
|
|
326
357
|
}
|
|
327
358
|
}
|
|
328
|
-
stdoutState.buffer = '';
|
|
329
359
|
if (completed)
|
|
330
360
|
return;
|
|
331
361
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest';
|
|
2
|
-
import { buildCodeBuddyArgs } from './cli-runner.js';
|
|
2
|
+
import { buildCodeBuddyArgs, extractBufferedPayloads, flushBufferedPayloads } from './cli-runner.js';
|
|
3
3
|
describe('buildCodeBuddyArgs', () => {
|
|
4
4
|
it('builds print-mode stream-json args for new sessions', () => {
|
|
5
5
|
const args = buildCodeBuddyArgs('fix the bug', undefined, {
|
|
@@ -31,4 +31,37 @@ describe('buildCodeBuddyArgs', () => {
|
|
|
31
31
|
'review this change',
|
|
32
32
|
]);
|
|
33
33
|
});
|
|
34
|
+
it('keeps stream-json output for current CodeBuddy CLI compatibility', () => {
|
|
35
|
+
const args = buildCodeBuddyArgs('say hi', undefined);
|
|
36
|
+
expect(args.slice(0, 3)).toEqual([
|
|
37
|
+
'--print',
|
|
38
|
+
'--output-format',
|
|
39
|
+
'stream-json',
|
|
40
|
+
]);
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
describe('CodeBuddy stream parsing', () => {
|
|
44
|
+
it('parses newline-delimited JSON payloads emitted by current CodeBuddy CLI', () => {
|
|
45
|
+
const state = {
|
|
46
|
+
buffer: [
|
|
47
|
+
'{"type":"system","subtype":"init"}',
|
|
48
|
+
'{"type":"assistant","message":{"content":[{"type":"text","text":"ok"}]}}',
|
|
49
|
+
'',
|
|
50
|
+
].join('\n'),
|
|
51
|
+
};
|
|
52
|
+
expect(extractBufferedPayloads(state)).toEqual([
|
|
53
|
+
'{"type":"system","subtype":"init"}',
|
|
54
|
+
'{"type":"assistant","message":{"content":[{"type":"text","text":"ok"}]}}',
|
|
55
|
+
]);
|
|
56
|
+
expect(state.buffer).toBe('');
|
|
57
|
+
});
|
|
58
|
+
it('flushes trailing JSON payload without newline on close', () => {
|
|
59
|
+
const state = {
|
|
60
|
+
buffer: '{"type":"result","is_error":false,"result":"done"}',
|
|
61
|
+
};
|
|
62
|
+
expect(flushBufferedPayloads(state)).toEqual([
|
|
63
|
+
'{"type":"result","is_error":false,"result":"done"}',
|
|
64
|
+
]);
|
|
65
|
+
expect(state.buffer).toBe('');
|
|
66
|
+
});
|
|
34
67
|
});
|
package/dist/setup.js
CHANGED
|
@@ -96,6 +96,13 @@ function printManualInstructions(configPath) {
|
|
|
96
96
|
"appSecret": "你的飞书 App Secret(可选)",
|
|
97
97
|
"allowedUserIds": ["允许访问的飞书用户 ID(可选)"]
|
|
98
98
|
},
|
|
99
|
+
"qq": {
|
|
100
|
+
"enabled": false,
|
|
101
|
+
"aiCommand": "codebuddy",
|
|
102
|
+
"appId": "你的 QQ App ID(可选)",
|
|
103
|
+
"secret": "你的 QQ App Secret(可选)",
|
|
104
|
+
"allowedUserIds": ["允许访问的 QQ 用户 ID(可选)"]
|
|
105
|
+
},
|
|
99
106
|
"wework": {
|
|
100
107
|
"enabled": false,
|
|
101
108
|
"corpId": "你的企业微信 Corp ID(可选)",
|
|
@@ -119,8 +126,8 @@ function printManualInstructions(configPath) {
|
|
|
119
126
|
}
|
|
120
127
|
}`);
|
|
121
128
|
console.log("");
|
|
122
|
-
console.log("提示:至少需要配置 Telegram、Feishu、WeChat、WeWork 或 DingTalk 其中一个平台");
|
|
123
|
-
console.log("或设置环境变量: TELEGRAM_BOT_TOKEN=xxx、FEISHU_APP_ID=xxx、WECHAT_APP_ID=xxx、WEWORK_CORP_ID=xxx 或 DINGTALK_CLIENT_ID=xxx 后再运行");
|
|
129
|
+
console.log("提示:至少需要配置 Telegram、Feishu、QQ、WeChat、WeWork 或 DingTalk 其中一个平台");
|
|
130
|
+
console.log("或设置环境变量: TELEGRAM_BOT_TOKEN=xxx、FEISHU_APP_ID=xxx、QQ_BOT_APPID=xxx、WECHAT_APP_ID=xxx、WEWORK_CORP_ID=xxx 或 DINGTALK_CLIENT_ID=xxx 后再运行");
|
|
124
131
|
console.log("");
|
|
125
132
|
}
|
|
126
133
|
const CLAUDE_SETTINGS_PATH = join(homedir(), ".claude", "settings.json");
|