@wu529778790/open-im 1.6.8-beta.1 → 1.6.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 +19 -18
- package/README.zh-CN.md +18 -17
- package/dist/adapters/registry.js +6 -10
- package/dist/check-update.js +6 -4
- package/dist/codebuddy/cli-runner.js +1 -1
- package/dist/commands/handler.js +5 -7
- package/dist/config-web-page-i18n.d.ts +0 -8
- package/dist/config-web-page-i18n.js +0 -8
- package/dist/config-web-page-script.js +2 -14
- package/dist/config-web-page-template.js +0 -28
- package/dist/config-web-page.test.js +1 -1
- package/dist/config-web.js +4 -18
- package/dist/config-web.test.js +0 -30
- package/dist/config.d.ts +2 -19
- package/dist/config.js +11 -143
- package/dist/config.test.js +4 -36
- package/dist/dingtalk/client.d.ts +2 -0
- package/dist/dingtalk/client.js +42 -3
- package/dist/dingtalk/message-sender.test.js +3 -3
- package/dist/feishu/card-builder.d.ts +1 -1
- package/dist/feishu/cardkit-manager.js +4 -1
- package/dist/index.js +10 -17
- package/dist/manager.js +5 -3
- package/dist/service-control.test.js +12 -1
- package/dist/session/session-manager.d.ts +2 -2
- package/dist/session/session-manager.js +6 -6
- package/dist/setup.js +56 -60
- package/dist/shared/ai-task.js +29 -13
- package/dist/shared/message-title.test.js +1 -1
- package/dist/shared/utils.js +1 -2
- package/dist/telegram/event-handler.js +0 -1
- package/dist/wechat/auth/device-bind.js +7 -8
- package/dist/wechat/auth/wechat-login.js +24 -26
- package/dist/wework/message-sender.js +4 -0
- package/package.json +10 -5
- package/dist/adapters/cursor-adapter.d.ts +0 -11
- package/dist/adapters/cursor-adapter.js +0 -60
- package/dist/cursor/cli-runner.d.ts +0 -36
- package/dist/cursor/cli-runner.js +0 -328
- package/dist/cursor/cli-runner.test.d.ts +0 -1
- package/dist/cursor/cli-runner.test.js +0 -94
package/README.md
CHANGED
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
[中文](./README.zh-CN.md)
|
|
4
4
|
|
|
5
|
-
Multi-platform IM bridge for AI CLI tools. Connect Telegram, Feishu, WeCom, DingTalk, QQ, and WeChat to Claude Code, Codex,
|
|
5
|
+
Multi-platform IM bridge for AI CLI tools. Connect Telegram, Feishu, WeCom, DingTalk, QQ, and WeChat to Claude Code, Codex, and CodeBuddy so you can use your coding assistant remotely from a phone or chat window.
|
|
6
6
|
|
|
7
7
|
## Features
|
|
8
8
|
|
|
9
9
|
- Multi-platform support: Telegram, Feishu, WeCom, DingTalk, QQ, and WeChat (experimental), with multiple platforms enabled at the same time
|
|
10
|
-
- Multiple AI tools: Claude, Codex,
|
|
10
|
+
- Multiple AI tools: Claude, Codex, and CodeBuddy
|
|
11
11
|
- Per-platform AI routing: each IM platform can use a different AI tool, with `aiCommand` as the global default and `platforms.<name>.aiCommand` as the override
|
|
12
12
|
- Streaming replies: relay AI output and tool execution progress in real time (DingTalk streaming is not fully supported yet)
|
|
13
13
|
- Graphical configuration page and CLI setup flow
|
|
@@ -59,10 +59,18 @@ The config file is stored at `~/.open-im/config.json` by default.
|
|
|
59
59
|
|
|
60
60
|
## Graphical Config Page
|
|
61
61
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
-
|
|
65
|
-
-
|
|
62
|
+
Open the config page at **http://127.0.0.1:39282** (or the URL shown after `open-im start`). The page includes:
|
|
63
|
+
|
|
64
|
+
- **Dashboard** – Configured / Enabled platform count and service status (Idle or Running)
|
|
65
|
+
- **Platforms** – Enable and configure Telegram, Feishu, QQ, WeCom, and DingTalk (credentials, proxy, per-platform AI tool, allowed user IDs). Each platform has a “Test Configuration” button.
|
|
66
|
+
- **AI Tooling** – **General**: default AI tool (Claude / Codex / CodeBuddy), work directory, hook port, log level. **Per-tool tabs**: Claude (CLI path, timeout, proxy, config path, ANTHROPIC_* fields), Codex (CLI path, timeout, proxy), CodeBuddy (CLI path, timeout).
|
|
67
|
+
- **Service control** – Validate config, Save, Start bridge, Stop bridge.
|
|
68
|
+
|
|
69
|
+
WeChat is not in the web UI; configure it in `~/.open-im/config.json` or via `open-im init` if needed.
|
|
70
|
+
|
|
71
|
+
- `open-im start` serves the config page and the bridge.
|
|
72
|
+
- `open-im dev` opens the page automatically only when setup is incomplete.
|
|
73
|
+
- To open the page when config already exists, run `open-im start` and visit the URL above.
|
|
66
74
|
|
|
67
75
|
## Session Behavior
|
|
68
76
|
|
|
@@ -84,7 +92,7 @@ Example:
|
|
|
84
92
|
},
|
|
85
93
|
"feishu": {
|
|
86
94
|
"enabled": true,
|
|
87
|
-
"aiCommand": "
|
|
95
|
+
"aiCommand": "codex"
|
|
88
96
|
},
|
|
89
97
|
"qq": {
|
|
90
98
|
"enabled": true,
|
|
@@ -94,7 +102,7 @@ Example:
|
|
|
94
102
|
}
|
|
95
103
|
```
|
|
96
104
|
|
|
97
|
-
In that setup, Telegram uses Codex, Feishu uses
|
|
105
|
+
In that setup, Telegram uses Codex, Feishu uses Codex, QQ uses CodeBuddy, and any platform without its own `aiCommand` continues using Claude.
|
|
98
106
|
|
|
99
107
|
### Claude
|
|
100
108
|
|
|
@@ -151,10 +159,6 @@ The following is valid JSON and can be saved directly as `~/.open-im/config.json
|
|
|
151
159
|
"skipPermissions": true,
|
|
152
160
|
"timeoutMs": 600000
|
|
153
161
|
},
|
|
154
|
-
"cursor": {
|
|
155
|
-
"cliPath": "agent",
|
|
156
|
-
"skipPermissions": true
|
|
157
|
-
},
|
|
158
162
|
"codex": {
|
|
159
163
|
"cliPath": "codex",
|
|
160
164
|
"workDir": "D:/coding/open-im",
|
|
@@ -177,7 +181,7 @@ The following is valid JSON and can be saved directly as `~/.open-im/config.json
|
|
|
177
181
|
},
|
|
178
182
|
"feishu": {
|
|
179
183
|
"enabled": false,
|
|
180
|
-
"aiCommand": "
|
|
184
|
+
"aiCommand": "codex",
|
|
181
185
|
"allowedUserIds": [],
|
|
182
186
|
"appId": "YOUR_FEISHU_APP_ID",
|
|
183
187
|
"appSecret": "YOUR_FEISHU_APP_SECRET"
|
|
@@ -219,14 +223,13 @@ The following is valid JSON and can be saved directly as `~/.open-im/config.json
|
|
|
219
223
|
|
|
220
224
|
| Variable | Description |
|
|
221
225
|
| ---- | ---- |
|
|
222
|
-
| `AI_COMMAND` | Select `claude`, `codex`,
|
|
226
|
+
| `AI_COMMAND` | Select `claude`, `codex`, or `codebuddy` |
|
|
223
227
|
| `CLAUDE_WORK_DIR` | Default session working directory |
|
|
224
228
|
| `LOG_DIR` | Log directory |
|
|
225
229
|
| `LOG_LEVEL` | Log level |
|
|
226
230
|
| `HOOK_PORT` | Permission service port |
|
|
227
231
|
| `CODEX_PROXY` | Proxy used by Codex to access `chatgpt.com` |
|
|
228
232
|
| `OPENAI_API_KEY` | Codex API key, can replace `codex login` |
|
|
229
|
-
| `CURSOR_API_KEY` | Cursor API key, can replace `agent login` |
|
|
230
233
|
| `CODEBUDDY_CLI_PATH` | Override CodeBuddy CLI path |
|
|
231
234
|
| `CODEBUDDY_TIMEOUT_MS` | Override CodeBuddy timeout |
|
|
232
235
|
| `CODEBUDDY_SKIP_PERMISSIONS` | Override CodeBuddy skip-permissions behavior |
|
|
@@ -272,7 +275,7 @@ Notes on DingTalk: the current implementation uses a hybrid model of "Stream Mod
|
|
|
272
275
|
|
|
273
276
|
- Plain text replies in a session are sent through `sessionWebhook`
|
|
274
277
|
- If `cardTemplateId` is configured, the app will try AI assistant `prepare/update/finish` streaming cards; if that fails, it falls back to plain text. In custom bot or regular group scenarios, the interactive card API may return `param.error`, so single-message streaming updates are not available there yet
|
|
275
|
-
- Startup and shutdown notifications are sent to the
|
|
278
|
+
- Startup and shutdown notifications are not sent to DingTalk (the OpenAPI robot API does not support proactive messages in the same way). Other platforms (e.g. Telegram, Feishu, WeCom) still receive lifecycle notifications when configured
|
|
276
279
|
|
|
277
280
|
DingTalk AI card templates are already compatible with the official "Search Result Card" template and use the variables `lastMessage`, `content`, `resources`, `users`, and `flowStatus`. If you use that template, no template changes are required for streaming updates.
|
|
278
281
|
|
|
@@ -304,8 +307,6 @@ DingTalk AI card templates are already compatible with the official "Search Resu
|
|
|
304
307
|
|
|
305
308
|
**DingTalk has no streaming updates**: when `prepare` fails, the app falls back to plain text replies. In custom bot or regular group scenarios, neither the AI assistant API nor the interactive card API is available, so only single plain text replies are supported.
|
|
306
309
|
|
|
307
|
-
**Cursor shows `Authentication required`**: run `agent login` first, or set `CURSOR_API_KEY` in `env`.
|
|
308
|
-
|
|
309
310
|
**Codex shows `stream disconnected` or `error sending request`**: `chatgpt.com` is not reachable. Configure `tools.codex.proxy` or set `CODEX_PROXY`.
|
|
310
311
|
|
|
311
312
|
**CodeBuddy prompts for login**: run `codebuddy login` first. `open-im` does not read CodeBuddy login state from `~/.open-im/config.json`.
|
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、
|
|
5
|
+
多平台 IM 桥接工具,把 Telegram、飞书、企业微信、钉钉、QQ、微信接到 AI CLI 工具(Claude Code、Codex、CodeBuddy),方便在手机或聊天窗口里远程使用 AI 编程助手。
|
|
6
6
|
|
|
7
7
|
## 功能特性
|
|
8
8
|
|
|
9
9
|
- 多平台:支持 Telegram、飞书、企业微信、钉钉、QQ、微信(测试中),可同时启用
|
|
10
|
-
- 多 AI 工具:支持 Claude、Codex、
|
|
10
|
+
- 多 AI 工具:支持 Claude、Codex、CodeBuddy
|
|
11
11
|
- 按平台分配 AI:根级 `aiCommand` 作为默认值,`platforms.<name>.aiCommand` 可为不同 IM 单独指定 AI 工具
|
|
12
12
|
- 流式输出:实时回传 AI 回复与工具执行进度(目前钉钉暂未实现流式传输)
|
|
13
13
|
- 图形化配置页面 / CLI 配置引导
|
|
@@ -59,10 +59,18 @@ open-im start
|
|
|
59
59
|
|
|
60
60
|
## 图形化配置页面
|
|
61
61
|
|
|
62
|
-
|
|
63
|
-
|
|
62
|
+
在浏览器中打开 **http://127.0.0.1:39282**(或执行 `open-im start` 后提示的地址),页面结构如下:
|
|
63
|
+
|
|
64
|
+
- **概览** – 已配置/已启用平台数量、服务状态(未启动或运行中)
|
|
65
|
+
- **平台配置** – 启用并填写 Telegram、飞书、QQ、企业微信、钉钉的凭证(Bot Token/App ID/Secret、代理、该平台使用的 AI 工具、白名单用户 ID)。每个平台提供「校验配置」按钮
|
|
66
|
+
- **AI 工具配置** – **公共**:默认 AI 工具(Claude / Codex / CodeBuddy)、工作目录、Hook 端口、日志级别。**分工具**:Claude(CLI 路径、超时、代理、配置路径、ANTHROPIC_* 等)、Codex(CLI 路径、超时、代理)、CodeBuddy(CLI 路径、超时)
|
|
67
|
+
- **服务控制** – 校验配置、保存、启动桥接、停止桥接
|
|
68
|
+
|
|
69
|
+
微信暂不在网页中配置,如需使用请在 `~/.open-im/config.json` 中手动配置或通过 `open-im init` 引导。
|
|
70
|
+
|
|
71
|
+
- `open-im start` 会同时启动桥接服务并提供该配置页
|
|
64
72
|
- `open-im dev` 仅在未完成配置时自动打开页面
|
|
65
|
-
-
|
|
73
|
+
- 已有配置但想手动打开时,执行 `open-im start` 后访问上述地址即可
|
|
66
74
|
|
|
67
75
|
## 会话说明
|
|
68
76
|
|
|
@@ -84,7 +92,7 @@ open-im start
|
|
|
84
92
|
},
|
|
85
93
|
"feishu": {
|
|
86
94
|
"enabled": true,
|
|
87
|
-
"aiCommand": "
|
|
95
|
+
"aiCommand": "codex"
|
|
88
96
|
},
|
|
89
97
|
"qq": {
|
|
90
98
|
"enabled": true,
|
|
@@ -94,7 +102,7 @@ open-im start
|
|
|
94
102
|
}
|
|
95
103
|
```
|
|
96
104
|
|
|
97
|
-
这个配置下,Telegram 会走 Codex,飞书会走
|
|
105
|
+
这个配置下,Telegram 会走 Codex,飞书会走 Codex,QQ 会走 CodeBuddy,其他未单独指定 `aiCommand` 的平台仍然使用 Claude。
|
|
98
106
|
|
|
99
107
|
### Claude
|
|
100
108
|
|
|
@@ -151,10 +159,6 @@ codebuddy login
|
|
|
151
159
|
"skipPermissions": true,
|
|
152
160
|
"timeoutMs": 600000
|
|
153
161
|
},
|
|
154
|
-
"cursor": {
|
|
155
|
-
"cliPath": "agent",
|
|
156
|
-
"skipPermissions": true
|
|
157
|
-
},
|
|
158
162
|
"codex": {
|
|
159
163
|
"cliPath": "codex",
|
|
160
164
|
"workDir": "D:/coding/open-im",
|
|
@@ -177,7 +181,7 @@ codebuddy login
|
|
|
177
181
|
},
|
|
178
182
|
"feishu": {
|
|
179
183
|
"enabled": false,
|
|
180
|
-
"aiCommand": "
|
|
184
|
+
"aiCommand": "codex",
|
|
181
185
|
"allowedUserIds": [],
|
|
182
186
|
"appId": "YOUR_FEISHU_APP_ID",
|
|
183
187
|
"appSecret": "YOUR_FEISHU_APP_SECRET"
|
|
@@ -219,14 +223,13 @@ codebuddy login
|
|
|
219
223
|
|
|
220
224
|
| 变量 | 说明 |
|
|
221
225
|
| ---- | ---- |
|
|
222
|
-
| `AI_COMMAND` | 选择 `claude` / `codex` / `
|
|
226
|
+
| `AI_COMMAND` | 选择 `claude` / `codex` / `codebuddy` |
|
|
223
227
|
| `CLAUDE_WORK_DIR` | 默认会话目录 |
|
|
224
228
|
| `LOG_DIR` | 日志目录 |
|
|
225
229
|
| `LOG_LEVEL` | 日志级别 |
|
|
226
230
|
| `HOOK_PORT` | 权限服务端口 |
|
|
227
231
|
| `CODEX_PROXY` | Codex 访问 `chatgpt.com` 的代理 |
|
|
228
232
|
| `OPENAI_API_KEY` | Codex API Key,可替代 `codex login` |
|
|
229
|
-
| `CURSOR_API_KEY` | Cursor API Key,可替代 `agent login` |
|
|
230
233
|
| `CODEBUDDY_CLI_PATH` | 覆盖 CodeBuddy CLI 路径 |
|
|
231
234
|
| `CODEBUDDY_TIMEOUT_MS` | 覆盖 CodeBuddy 超时 |
|
|
232
235
|
| `CODEBUDDY_SKIP_PERMISSIONS` | 覆盖 CodeBuddy 的跳过权限确认行为 |
|
|
@@ -272,7 +275,7 @@ codebuddy login
|
|
|
272
275
|
|
|
273
276
|
- 会话内普通文本回复默认走 `sessionWebhook`
|
|
274
277
|
- 若配置了 `cardTemplateId`,会尝试 AI 助理 `prepare/update/finish` 流式卡片;失败则 fallback 为普通文本(自定义机器人/普通群场景下互动卡片 API 报 `param.error`,暂不支持单条流式更新)
|
|
275
|
-
-
|
|
278
|
+
- 启动/关闭通知不会发给钉钉(OpenAPI 机器人接口不支持主动发消息);其他已配置平台(如 Telegram、飞书、企业微信)仍会收到生命周期通知
|
|
276
279
|
|
|
277
280
|
钉钉 AI 卡片模板:已适配官方「搜索结果卡片」模板,使用变量 `lastMessage`、`content`、`resources`、`users`、`flowStatus`。若使用该模板,无需修改模板即可实现流式更新。
|
|
278
281
|
|
|
@@ -304,8 +307,6 @@ codebuddy login
|
|
|
304
307
|
|
|
305
308
|
**钉钉没有流式更新**:`prepare` 失败时 fallback 为普通文本回复。自定义机器人/普通群场景下,AI 助理和互动卡片 API 均不可用,仅支持单条文本回复。
|
|
306
309
|
|
|
307
|
-
**Cursor 报 `Authentication required`**:先执行 `agent login`,或在 `env` 中设置 `CURSOR_API_KEY`。
|
|
308
|
-
|
|
309
310
|
**Codex 报 `stream disconnected` / `error sending request`**:无法访问 `chatgpt.com`,请配置 `tools.codex.proxy` 或环境变量 `CODEX_PROXY`。
|
|
310
311
|
|
|
311
312
|
**CodeBuddy 提示需要登录**:先执行 `codebuddy login`。`open-im` 不会从 `~/.open-im/config.json` 读取 CodeBuddy 的登录态。
|
|
@@ -1,20 +1,21 @@
|
|
|
1
1
|
import { getConfiguredAiCommands } from '../config.js';
|
|
2
2
|
import { ClaudeAdapter } from './claude-adapter.js';
|
|
3
3
|
import { ClaudeSDKAdapter } from './claude-sdk-adapter.js';
|
|
4
|
-
import { CursorAdapter } from './cursor-adapter.js';
|
|
5
4
|
import { CodexAdapter } from './codex-adapter.js';
|
|
6
5
|
import { CodeBuddyAdapter } from './codebuddy-adapter.js';
|
|
6
|
+
import { createLogger } from '../logger.js';
|
|
7
|
+
const log = createLogger('Registry');
|
|
7
8
|
const adapters = new Map();
|
|
8
9
|
export function initAdapters(config) {
|
|
9
10
|
adapters.clear();
|
|
10
11
|
for (const aiCommand of getConfiguredAiCommands(config)) {
|
|
11
12
|
if (aiCommand === 'claude') {
|
|
12
13
|
if (config.useSdkMode) {
|
|
13
|
-
|
|
14
|
+
log.info('Claude Agent SDK adapter enabled');
|
|
14
15
|
adapters.set('claude', new ClaudeSDKAdapter());
|
|
15
16
|
}
|
|
16
17
|
else {
|
|
17
|
-
|
|
18
|
+
log.info('Claude CLI adapter enabled');
|
|
18
19
|
adapters.set('claude', new ClaudeAdapter(config.claudeCliPath, {
|
|
19
20
|
useProcessPool: true,
|
|
20
21
|
idleTimeoutMs: 2 * 60 * 1000,
|
|
@@ -22,18 +23,13 @@ export function initAdapters(config) {
|
|
|
22
23
|
}
|
|
23
24
|
continue;
|
|
24
25
|
}
|
|
25
|
-
if (aiCommand === 'cursor') {
|
|
26
|
-
console.log('Cursor CLI adapter enabled');
|
|
27
|
-
adapters.set('cursor', new CursorAdapter(config.cursorCliPath));
|
|
28
|
-
continue;
|
|
29
|
-
}
|
|
30
26
|
if (aiCommand === 'codex') {
|
|
31
|
-
|
|
27
|
+
log.info('Codex CLI adapter enabled');
|
|
32
28
|
adapters.set('codex', new CodexAdapter(config.codexCliPath));
|
|
33
29
|
continue;
|
|
34
30
|
}
|
|
35
31
|
if (aiCommand === 'codebuddy') {
|
|
36
|
-
|
|
32
|
+
log.info('CodeBuddy CLI adapter enabled');
|
|
37
33
|
adapters.set('codebuddy', new CodeBuddyAdapter(config.codebuddyCliPath));
|
|
38
34
|
}
|
|
39
35
|
}
|
package/dist/check-update.js
CHANGED
|
@@ -5,6 +5,8 @@ import { spawnSync } from "node:child_process";
|
|
|
5
5
|
import { createRequire } from "node:module";
|
|
6
6
|
import { createInterface } from "node:readline/promises";
|
|
7
7
|
import { stdin as input, stdout as output } from "node:process";
|
|
8
|
+
import { createLogger } from "./logger.js";
|
|
9
|
+
const log = createLogger("CheckUpdate");
|
|
8
10
|
const require = createRequire(import.meta.url);
|
|
9
11
|
const { version: CURRENT_VERSION } = require("../package.json");
|
|
10
12
|
const PKG_NAME = "@wu529778790/open-im";
|
|
@@ -49,7 +51,7 @@ function runGlobalUpdate() {
|
|
|
49
51
|
}
|
|
50
52
|
async function confirmUpdate(latest) {
|
|
51
53
|
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
52
|
-
|
|
54
|
+
log.info(`检测到新版本 v${latest},可手动更新: npm install -g ${PKG_NAME}@latest`);
|
|
53
55
|
return false;
|
|
54
56
|
}
|
|
55
57
|
const rl = createInterface({ input, output });
|
|
@@ -74,12 +76,12 @@ export async function checkAndUpdate() {
|
|
|
74
76
|
if (!shouldUpdate) {
|
|
75
77
|
return { updated: false, latest };
|
|
76
78
|
}
|
|
77
|
-
|
|
79
|
+
log.info(`检测到新版本 v${latest}(当前 v${CURRENT_VERSION}),正在更新...`);
|
|
78
80
|
const ok = runGlobalUpdate();
|
|
79
81
|
if (ok) {
|
|
80
|
-
|
|
82
|
+
log.info(`已更新到 v${latest},继续按最新版本启动服务...`);
|
|
81
83
|
return { updated: true, latest };
|
|
82
84
|
}
|
|
83
|
-
|
|
85
|
+
log.warn("自动更新失败,请手动执行: npm install -g @wu529778790/open-im@latest");
|
|
84
86
|
return { updated: false };
|
|
85
87
|
}
|
|
@@ -209,7 +209,7 @@ export function runCodeBuddy(cliPath, prompt, sessionId, workDir, callbacks, opt
|
|
|
209
209
|
const idleTimeoutMs = getIdleTimeoutMs(timeoutMs);
|
|
210
210
|
let timeoutHandle = null;
|
|
211
211
|
let idleTimeoutHandle = null;
|
|
212
|
-
|
|
212
|
+
const stdoutState = { buffer: '' };
|
|
213
213
|
const clearTimers = () => {
|
|
214
214
|
if (timeoutHandle) {
|
|
215
215
|
clearTimeout(timeoutHandle);
|
package/dist/commands/handler.js
CHANGED
|
@@ -172,13 +172,11 @@ export class CommandHandler {
|
|
|
172
172
|
return true;
|
|
173
173
|
}
|
|
174
174
|
getAiVersion(aiCommand) {
|
|
175
|
-
const cmd = aiCommand === '
|
|
176
|
-
? this.deps.config.
|
|
177
|
-
: aiCommand === '
|
|
178
|
-
? this.deps.config.
|
|
179
|
-
:
|
|
180
|
-
? this.deps.config.codebuddyCliPath
|
|
181
|
-
: this.deps.config.claudeCliPath;
|
|
175
|
+
const cmd = aiCommand === 'codex'
|
|
176
|
+
? this.deps.config.codexCliPath
|
|
177
|
+
: aiCommand === 'codebuddy'
|
|
178
|
+
? this.deps.config.codebuddyCliPath
|
|
179
|
+
: this.deps.config.claudeCliPath;
|
|
182
180
|
return new Promise((resolve) => {
|
|
183
181
|
execFile(cmd, ['--version'], { timeout: 5000 }, (err, stdout) => {
|
|
184
182
|
resolve(err ? '未知' : (stdout?.toString().trim() || '未知'));
|
|
@@ -85,7 +85,6 @@ export declare const PAGE_TEXTS: {
|
|
|
85
85
|
readonly aiTool: "Default AI tool";
|
|
86
86
|
readonly workDir: "Default work directory";
|
|
87
87
|
readonly claudeCli: "Claude CLI path";
|
|
88
|
-
readonly cursorCli: "Cursor CLI path";
|
|
89
88
|
readonly codexCli: "Codex CLI path";
|
|
90
89
|
readonly codebuddyCli: "CodeBuddy CLI path";
|
|
91
90
|
readonly codexProxy: "Codex proxy";
|
|
@@ -96,10 +95,7 @@ export declare const PAGE_TEXTS: {
|
|
|
96
95
|
readonly claudeModel: "ANTHROPIC_MODEL";
|
|
97
96
|
readonly claudeProxy: "Proxy (optional)";
|
|
98
97
|
readonly codexTimeout: "Codex timeout (ms)";
|
|
99
|
-
readonly cursorTimeout: "Cursor timeout (ms)";
|
|
100
|
-
readonly cursorModel: "Model (e.g. auto)";
|
|
101
98
|
readonly codebuddyTimeout: "CodeBuddy timeout (ms)";
|
|
102
|
-
readonly cursorProxy: "Proxy (optional)";
|
|
103
99
|
readonly hookPort: "Hook port";
|
|
104
100
|
readonly logLevel: "Log level";
|
|
105
101
|
readonly logLevelDefault: "default (app default)";
|
|
@@ -206,7 +202,6 @@ export declare const PAGE_TEXTS: {
|
|
|
206
202
|
readonly aiTool: "默认 AI 工具";
|
|
207
203
|
readonly workDir: "默认工作目录";
|
|
208
204
|
readonly claudeCli: "Claude CLI 路径";
|
|
209
|
-
readonly cursorCli: "Cursor CLI 路径";
|
|
210
205
|
readonly codexCli: "Codex CLI 路径";
|
|
211
206
|
readonly codebuddyCli: "CodeBuddy CLI 路径";
|
|
212
207
|
readonly codexProxy: "Codex 代理";
|
|
@@ -217,10 +212,7 @@ export declare const PAGE_TEXTS: {
|
|
|
217
212
|
readonly claudeModel: "ANTHROPIC_MODEL";
|
|
218
213
|
readonly claudeProxy: "代理(可选)";
|
|
219
214
|
readonly codexTimeout: "Codex 超时(毫秒)";
|
|
220
|
-
readonly cursorTimeout: "Cursor 超时(毫秒)";
|
|
221
|
-
readonly cursorModel: "模型(如 auto)";
|
|
222
215
|
readonly codebuddyTimeout: "CodeBuddy 超时(毫秒)";
|
|
223
|
-
readonly cursorProxy: "代理(可选)";
|
|
224
216
|
readonly hookPort: "Hook 端口";
|
|
225
217
|
readonly logLevel: "日志级别";
|
|
226
218
|
readonly logLevelDefault: "default(程序默认)";
|
|
@@ -85,7 +85,6 @@ export const PAGE_TEXTS = {
|
|
|
85
85
|
aiTool: "Default AI tool",
|
|
86
86
|
workDir: "Default work directory",
|
|
87
87
|
claudeCli: "Claude CLI path",
|
|
88
|
-
cursorCli: "Cursor CLI path",
|
|
89
88
|
codexCli: "Codex CLI path",
|
|
90
89
|
codebuddyCli: "CodeBuddy CLI path",
|
|
91
90
|
codexProxy: "Codex proxy",
|
|
@@ -96,10 +95,7 @@ export const PAGE_TEXTS = {
|
|
|
96
95
|
claudeModel: "ANTHROPIC_MODEL",
|
|
97
96
|
claudeProxy: "Proxy (optional)",
|
|
98
97
|
codexTimeout: "Codex timeout (ms)",
|
|
99
|
-
cursorTimeout: "Cursor timeout (ms)",
|
|
100
|
-
cursorModel: "Model (e.g. auto)",
|
|
101
98
|
codebuddyTimeout: "CodeBuddy timeout (ms)",
|
|
102
|
-
cursorProxy: "Proxy (optional)",
|
|
103
99
|
hookPort: "Hook port",
|
|
104
100
|
logLevel: "Log level",
|
|
105
101
|
logLevelDefault: "default (app default)",
|
|
@@ -206,7 +202,6 @@ export const PAGE_TEXTS = {
|
|
|
206
202
|
aiTool: "\u9ed8\u8ba4 AI \u5de5\u5177",
|
|
207
203
|
workDir: "\u9ed8\u8ba4\u5de5\u4f5c\u76ee\u5f55",
|
|
208
204
|
claudeCli: "Claude CLI \u8def\u5f84",
|
|
209
|
-
cursorCli: "Cursor CLI \u8def\u5f84",
|
|
210
205
|
codexCli: "Codex CLI \u8def\u5f84",
|
|
211
206
|
codebuddyCli: "CodeBuddy CLI \u8def\u5f84",
|
|
212
207
|
codexProxy: "Codex \u4ee3\u7406",
|
|
@@ -217,10 +212,7 @@ export const PAGE_TEXTS = {
|
|
|
217
212
|
claudeModel: "ANTHROPIC_MODEL",
|
|
218
213
|
claudeProxy: "\u4ee3\u7406\uff08\u53ef\u9009\uff09",
|
|
219
214
|
codexTimeout: "Codex \u8d85\u65f6\uff08\u6beb\u79d2\uff09",
|
|
220
|
-
cursorTimeout: "Cursor \u8d85\u65f6\uff08\u6beb\u79d2\uff09",
|
|
221
|
-
cursorModel: "模型(如 auto)",
|
|
222
215
|
codebuddyTimeout: "CodeBuddy \u8d85\u65f6\uff08\u6beb\u79d2\uff09",
|
|
223
|
-
cursorProxy: "\u4ee3\u7406\uff08\u53ef\u9009\uff09",
|
|
224
216
|
hookPort: "Hook \u7aef\u53e3",
|
|
225
217
|
logLevel: "\u65e5\u5fd7\u7ea7\u522b",
|
|
226
218
|
logLevelDefault: "default\uff08\u7a0b\u5e8f\u9ed8\u8ba4\uff09",
|
|
@@ -6,11 +6,11 @@ export const PAGE_SCRIPT = String.raw ` const platformDefinitions = [
|
|
|
6
6
|
{ key: "dingtalk", label: "DingTalk", fields: ["aiCommand", "clientId", "clientSecret", "cardTemplateId", "allowedUserIds"], testFields: ["clientId", "clientSecret"], requiredFields: ["clientId", "clientSecret"] },
|
|
7
7
|
];
|
|
8
8
|
const platformKeys = platformDefinitions.map((platform) => platform.key);
|
|
9
|
-
const aiTools = ["claude", "codex", "
|
|
9
|
+
const aiTools = ["claude", "codex", "codebuddy"];
|
|
10
10
|
const STORAGE_KEY_LANG = "open-im-web-lang";
|
|
11
11
|
const STORAGE_KEY_DARK_MODE = "open-im-web-dark-mode";
|
|
12
12
|
const POLLING_INTERVAL = 10000;
|
|
13
|
-
const toolLabels = { claude: "Claude", codex: "Codex",
|
|
13
|
+
const toolLabels = { claude: "Claude", codex: "Codex", codebuddy: "CodeBuddy" };
|
|
14
14
|
|
|
15
15
|
// Dark mode handling
|
|
16
16
|
const getSystemDarkMode = () => window.matchMedia("(prefers-color-scheme: dark)").matches;
|
|
@@ -209,10 +209,6 @@ export const PAGE_SCRIPT = String.raw ` const platformDefinitions = [
|
|
|
209
209
|
{ id: "ai-codexCliPath-label", key: "codexCli" },
|
|
210
210
|
{ id: "ai-codexTimeoutMs-label", key: "codexTimeout" },
|
|
211
211
|
{ id: "ai-codexProxy-label", key: "codexProxy" },
|
|
212
|
-
{ id: "ai-cursorCliPath-label", key: "cursorCli" },
|
|
213
|
-
{ id: "ai-cursorModel-label", key: "cursorModel" },
|
|
214
|
-
{ id: "ai-cursorTimeoutMs-label", key: "cursorTimeout" },
|
|
215
|
-
{ id: "ai-cursorProxy-label", key: "cursorProxy" },
|
|
216
212
|
{ id: "ai-codebuddyCliPath-label", key: "codebuddyCli" },
|
|
217
213
|
{ id: "ai-codebuddyTimeoutMs-label", key: "codebuddyTimeout" },
|
|
218
214
|
{ id: "ai-hookPort-label", key: "hookPort" },
|
|
@@ -424,10 +420,6 @@ export const PAGE_SCRIPT = String.raw ` const platformDefinitions = [
|
|
|
424
420
|
{ id: "ai-codexCliPath", key: "codexCliPath" },
|
|
425
421
|
{ id: "ai-codexTimeoutMs", key: "codexTimeoutMs" },
|
|
426
422
|
{ id: "ai-codexProxy", key: "codexProxy" },
|
|
427
|
-
{ id: "ai-cursorCliPath", key: "cursorCliPath" },
|
|
428
|
-
{ id: "ai-cursorModel", key: "cursorModel" },
|
|
429
|
-
{ id: "ai-cursorTimeoutMs", key: "cursorTimeoutMs" },
|
|
430
|
-
{ id: "ai-cursorProxy", key: "cursorProxy" },
|
|
431
423
|
{ id: "ai-codebuddyCliPath", key: "codebuddyCliPath" },
|
|
432
424
|
{ id: "ai-codebuddyTimeoutMs", key: "codebuddyTimeoutMs" },
|
|
433
425
|
{ id: "ai-hookPort", key: "hookPort" },
|
|
@@ -672,10 +664,6 @@ export const PAGE_SCRIPT = String.raw ` const platformDefinitions = [
|
|
|
672
664
|
codebuddyTimeoutMs: getNumber("ai-codebuddyTimeoutMs"),
|
|
673
665
|
codexCliPath: getValue("ai-codexCliPath"),
|
|
674
666
|
codexProxy: getValue("ai-codexProxy"),
|
|
675
|
-
cursorCliPath: getValue("ai-cursorCliPath"),
|
|
676
|
-
cursorModel: getValue("ai-cursorModel"),
|
|
677
|
-
cursorTimeoutMs: getNumber("ai-cursorTimeoutMs"),
|
|
678
|
-
cursorProxy: getValue("ai-cursorProxy"),
|
|
679
667
|
codebuddyCliPath: getValue("ai-codebuddyCliPath"),
|
|
680
668
|
hookPort: getNumber("ai-hookPort"),
|
|
681
669
|
logLevel: getValue("ai-logLevel"),
|
|
@@ -875,7 +875,6 @@ export const PAGE_HTML_PREFIX = String.raw `<!doctype html>
|
|
|
875
875
|
<option value="">(default)</option>
|
|
876
876
|
<option value="claude">claude</option>
|
|
877
877
|
<option value="codex">codex</option>
|
|
878
|
-
<option value="cursor">cursor</option>
|
|
879
878
|
<option value="codebuddy">codebuddy</option>
|
|
880
879
|
</select>
|
|
881
880
|
</div>
|
|
@@ -918,7 +917,6 @@ export const PAGE_HTML_PREFIX = String.raw `<!doctype html>
|
|
|
918
917
|
<option value="">(default)</option>
|
|
919
918
|
<option value="claude">claude</option>
|
|
920
919
|
<option value="codex">codex</option>
|
|
921
|
-
<option value="cursor">cursor</option>
|
|
922
920
|
<option value="codebuddy">codebuddy</option>
|
|
923
921
|
</select>
|
|
924
922
|
</div>
|
|
@@ -960,7 +958,6 @@ export const PAGE_HTML_PREFIX = String.raw `<!doctype html>
|
|
|
960
958
|
<option value="">(default)</option>
|
|
961
959
|
<option value="claude">claude</option>
|
|
962
960
|
<option value="codex">codex</option>
|
|
963
|
-
<option value="cursor">cursor</option>
|
|
964
961
|
<option value="codebuddy">codebuddy</option>
|
|
965
962
|
</select>
|
|
966
963
|
</div>
|
|
@@ -1002,7 +999,6 @@ export const PAGE_HTML_PREFIX = String.raw `<!doctype html>
|
|
|
1002
999
|
<option value="">(default)</option>
|
|
1003
1000
|
<option value="claude">claude</option>
|
|
1004
1001
|
<option value="codex">codex</option>
|
|
1005
|
-
<option value="cursor">cursor</option>
|
|
1006
1002
|
<option value="codebuddy">codebuddy</option>
|
|
1007
1003
|
</select>
|
|
1008
1004
|
</div>
|
|
@@ -1048,7 +1044,6 @@ export const PAGE_HTML_PREFIX = String.raw `<!doctype html>
|
|
|
1048
1044
|
<option value="">(default)</option>
|
|
1049
1045
|
<option value="claude">claude</option>
|
|
1050
1046
|
<option value="codex">codex</option>
|
|
1051
|
-
<option value="cursor">cursor</option>
|
|
1052
1047
|
<option value="codebuddy">codebuddy</option>
|
|
1053
1048
|
</select>
|
|
1054
1049
|
</div>
|
|
@@ -1084,7 +1079,6 @@ export const PAGE_HTML_PREFIX = String.raw `<!doctype html>
|
|
|
1084
1079
|
<select id="ai-aiCommand" class="form-select">
|
|
1085
1080
|
<option value="claude">claude</option>
|
|
1086
1081
|
<option value="codex">codex</option>
|
|
1087
|
-
<option value="cursor">cursor</option>
|
|
1088
1082
|
<option value="codebuddy">codebuddy</option>
|
|
1089
1083
|
</select>
|
|
1090
1084
|
</div>
|
|
@@ -1114,7 +1108,6 @@ export const PAGE_HTML_PREFIX = String.raw `<!doctype html>
|
|
|
1114
1108
|
<div class="tabs" id="aiToolSwitcher">
|
|
1115
1109
|
<button class="tab active" data-tool="claude" type="button">Claude</button>
|
|
1116
1110
|
<button class="tab" data-tool="codex" type="button">Codex</button>
|
|
1117
|
-
<button class="tab" data-tool="cursor" type="button">Cursor</button>
|
|
1118
1111
|
<button class="tab" data-tool="codebuddy" type="button">CodeBuddy</button>
|
|
1119
1112
|
</div>
|
|
1120
1113
|
</div>
|
|
@@ -1170,27 +1163,6 @@ export const PAGE_HTML_PREFIX = String.raw `<!doctype html>
|
|
|
1170
1163
|
</div>
|
|
1171
1164
|
</div>
|
|
1172
1165
|
|
|
1173
|
-
<div id="ai-tool-cursor" class="ai-tool-panel" data-tool-panel="cursor">
|
|
1174
|
-
<div class="form-group">
|
|
1175
|
-
<label class="form-label" id="ai-cursorCliPath-label">CLI Path</label>
|
|
1176
|
-
<input id="ai-cursorCliPath" class="form-input mono" type="text" />
|
|
1177
|
-
</div>
|
|
1178
|
-
<div class="form-group">
|
|
1179
|
-
<label class="form-label" id="ai-cursorModel-label">Model</label>
|
|
1180
|
-
<input id="ai-cursorModel" class="form-input mono" type="text" placeholder="auto" />
|
|
1181
|
-
<div class="form-hint" id="ai-cursorModel-hint">如 auto、Claude 4 Sonnet 等,agent --list-models 查看</div>
|
|
1182
|
-
</div>
|
|
1183
|
-
<div class="form-group">
|
|
1184
|
-
<label class="form-label" id="ai-cursorTimeoutMs-label">Timeout (ms)</label>
|
|
1185
|
-
<input id="ai-cursorTimeoutMs" class="form-input" type="number" min="1" />
|
|
1186
|
-
</div>
|
|
1187
|
-
<div class="form-group">
|
|
1188
|
-
<label class="form-label" id="ai-cursorProxy-label">Proxy (optional)</label>
|
|
1189
|
-
<input id="ai-cursorProxy" class="form-input mono" type="text" />
|
|
1190
|
-
<div class="form-hint" id="ai-cursorProxy-hint">HTTP proxy for API requests (e.g., http://127.0.0.1:7890)</div>
|
|
1191
|
-
</div>
|
|
1192
|
-
</div>
|
|
1193
|
-
|
|
1194
1166
|
<div id="ai-tool-codebuddy" class="ai-tool-panel" data-tool-panel="codebuddy">
|
|
1195
1167
|
<div class="form-group">
|
|
1196
1168
|
<label class="form-label" id="ai-codebuddyCliPath-label">CLI Path</label>
|
|
@@ -38,7 +38,7 @@ describe("config web page assembly", () => {
|
|
|
38
38
|
const toolListMatch = PAGE_SCRIPT.match(/const aiTools = \[([^\]]+)\]/);
|
|
39
39
|
expect(toolListMatch).toBeTruthy();
|
|
40
40
|
const tools = Array.from((toolListMatch?.[1] ?? "").matchAll(/"([^"]+)"/g), (match) => match[1]);
|
|
41
|
-
expect(tools).toEqual(["claude", "codex", "
|
|
41
|
+
expect(tools).toEqual(["claude", "codex", "codebuddy"]);
|
|
42
42
|
for (const tool of tools) {
|
|
43
43
|
expect(PAGE_HTML).toContain(`data-tool="${tool}"`);
|
|
44
44
|
expect(PAGE_HTML).toContain(`data-tool-panel="${tool}"`);
|
package/dist/config-web.js
CHANGED
|
@@ -7,6 +7,8 @@ import { CONFIG_PATH, getClaudeConfigHome, loadClaudeSettingsEnv, saveClaudeSett
|
|
|
7
7
|
import { PAGE_HTML } from "./config-web-page.js";
|
|
8
8
|
import { getServiceStatus, startBackgroundService, stopBackgroundService } from "./service-control.js";
|
|
9
9
|
import { initWeWork, stopWeWork } from "./wework/client.js";
|
|
10
|
+
import { createLogger } from "./logger.js";
|
|
11
|
+
const log = createLogger("ConfigWeb");
|
|
10
12
|
const TEST_TIMEOUT_MS = 10000;
|
|
11
13
|
export function getHealthPlatformSnapshot(file, env = process.env) {
|
|
12
14
|
const fileTelegram = file.platforms?.telegram;
|
|
@@ -143,14 +145,10 @@ function buildInitialPayload(file) {
|
|
|
143
145
|
claudeModel: file.tools?.claude?.model ?? claudeEnv.ANTHROPIC_MODEL ?? "",
|
|
144
146
|
claudeProxy: file.tools?.claude?.proxy ?? "",
|
|
145
147
|
codexTimeoutMs: file.tools?.codex?.timeoutMs ?? 600000,
|
|
146
|
-
cursorTimeoutMs: file.tools?.cursor?.timeoutMs ?? 600000,
|
|
147
148
|
codebuddyTimeoutMs: file.tools?.codebuddy?.timeoutMs ?? 600000,
|
|
148
|
-
cursorCliPath: file.tools?.cursor?.cliPath ?? "cursor",
|
|
149
|
-
cursorModel: file.tools?.cursor?.model ?? "auto",
|
|
150
149
|
codexCliPath: file.tools?.codex?.cliPath ?? "codex",
|
|
151
150
|
codebuddyCliPath: file.tools?.codebuddy?.cliPath ?? "codebuddy",
|
|
152
151
|
codexProxy: file.tools?.codex?.proxy ?? "",
|
|
153
|
-
cursorProxy: file.tools?.cursor?.proxy ?? "",
|
|
154
152
|
defaultPermissionMode: file.defaultPermissionMode ?? "ask",
|
|
155
153
|
hookPort: file.hookPort ?? 35801,
|
|
156
154
|
logDir: file.logDir ?? "",
|
|
@@ -188,8 +186,6 @@ function validatePayload(payload) {
|
|
|
188
186
|
errors.push("Claude timeout must be positive.");
|
|
189
187
|
if (!Number.isFinite(payload.ai.codexTimeoutMs) || payload.ai.codexTimeoutMs <= 0)
|
|
190
188
|
errors.push("Codex timeout must be positive.");
|
|
191
|
-
if (!Number.isFinite(payload.ai.cursorTimeoutMs) || payload.ai.cursorTimeoutMs <= 0)
|
|
192
|
-
errors.push("Cursor timeout must be positive.");
|
|
193
189
|
if (!Number.isFinite(payload.ai.codebuddyTimeoutMs) || payload.ai.codebuddyTimeoutMs <= 0)
|
|
194
190
|
errors.push("CodeBuddy timeout must be positive.");
|
|
195
191
|
if (!Number.isFinite(payload.ai.hookPort) || payload.ai.hookPort <= 0)
|
|
@@ -271,14 +267,12 @@ function createProbeConfig(values) {
|
|
|
271
267
|
dingtalkAllowedUserIds: [],
|
|
272
268
|
aiCommand: "claude",
|
|
273
269
|
claudeCliPath: "claude",
|
|
274
|
-
cursorCliPath: "cursor",
|
|
275
270
|
codexCliPath: "codex",
|
|
276
271
|
claudeWorkDir: process.cwd(),
|
|
277
272
|
claudeSkipPermissions: true,
|
|
278
273
|
defaultPermissionMode: "ask",
|
|
279
274
|
claudeTimeoutMs: 600000,
|
|
280
275
|
codexTimeoutMs: 600000,
|
|
281
|
-
cursorTimeoutMs: 600000,
|
|
282
276
|
codebuddyTimeoutMs: 600000,
|
|
283
277
|
hookPort: 35801,
|
|
284
278
|
logDir: "",
|
|
@@ -422,14 +416,6 @@ function toFileConfig(payload, existing) {
|
|
|
422
416
|
proxy: clean(payload.ai.claudeProxy),
|
|
423
417
|
// model is now saved to ~/.claude/settings.json as ANTHROPIC_MODEL
|
|
424
418
|
},
|
|
425
|
-
cursor: {
|
|
426
|
-
...existing.tools?.cursor,
|
|
427
|
-
cliPath: clean(payload.ai.cursorCliPath) ?? "cursor",
|
|
428
|
-
skipPermissions: existing.tools?.cursor?.skipPermissions ?? payload.ai.claudeSkipPermissions,
|
|
429
|
-
proxy: clean(payload.ai.cursorProxy),
|
|
430
|
-
timeoutMs: payload.ai.cursorTimeoutMs,
|
|
431
|
-
model: clean(payload.ai.cursorModel),
|
|
432
|
-
},
|
|
433
419
|
codex: {
|
|
434
420
|
...existing.tools?.codex,
|
|
435
421
|
cliPath: clean(payload.ai.codexCliPath) ?? "codex",
|
|
@@ -724,7 +710,7 @@ export async function startWebConfigServer(options) {
|
|
|
724
710
|
export async function runWebConfigFlow(options) {
|
|
725
711
|
const started = await startWebConfigServer(options);
|
|
726
712
|
openBrowser(started.url);
|
|
727
|
-
|
|
728
|
-
|
|
713
|
+
log.info(`Opened local configuration page: ${started.url}`);
|
|
714
|
+
log.info(process.env.OPEN_IM_NO_BROWSER === "1" ? "Browser launch disabled. Open the URL manually." : "Save the configuration in your browser to continue.");
|
|
729
715
|
return started.waitForResult;
|
|
730
716
|
}
|