@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.
Files changed (41) hide show
  1. package/README.md +19 -18
  2. package/README.zh-CN.md +18 -17
  3. package/dist/adapters/registry.js +6 -10
  4. package/dist/check-update.js +6 -4
  5. package/dist/codebuddy/cli-runner.js +1 -1
  6. package/dist/commands/handler.js +5 -7
  7. package/dist/config-web-page-i18n.d.ts +0 -8
  8. package/dist/config-web-page-i18n.js +0 -8
  9. package/dist/config-web-page-script.js +2 -14
  10. package/dist/config-web-page-template.js +0 -28
  11. package/dist/config-web-page.test.js +1 -1
  12. package/dist/config-web.js +4 -18
  13. package/dist/config-web.test.js +0 -30
  14. package/dist/config.d.ts +2 -19
  15. package/dist/config.js +11 -143
  16. package/dist/config.test.js +4 -36
  17. package/dist/dingtalk/client.d.ts +2 -0
  18. package/dist/dingtalk/client.js +42 -3
  19. package/dist/dingtalk/message-sender.test.js +3 -3
  20. package/dist/feishu/card-builder.d.ts +1 -1
  21. package/dist/feishu/cardkit-manager.js +4 -1
  22. package/dist/index.js +10 -17
  23. package/dist/manager.js +5 -3
  24. package/dist/service-control.test.js +12 -1
  25. package/dist/session/session-manager.d.ts +2 -2
  26. package/dist/session/session-manager.js +6 -6
  27. package/dist/setup.js +56 -60
  28. package/dist/shared/ai-task.js +29 -13
  29. package/dist/shared/message-title.test.js +1 -1
  30. package/dist/shared/utils.js +1 -2
  31. package/dist/telegram/event-handler.js +0 -1
  32. package/dist/wechat/auth/device-bind.js +7 -8
  33. package/dist/wechat/auth/wechat-login.js +24 -26
  34. package/dist/wework/message-sender.js +4 -0
  35. package/package.json +10 -5
  36. package/dist/adapters/cursor-adapter.d.ts +0 -11
  37. package/dist/adapters/cursor-adapter.js +0 -60
  38. package/dist/cursor/cli-runner.d.ts +0 -36
  39. package/dist/cursor/cli-runner.js +0 -328
  40. package/dist/cursor/cli-runner.test.d.ts +0 -1
  41. 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, Cursor, and CodeBuddy so you can use your coding assistant remotely from a phone or chat window.
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, Cursor, and CodeBuddy
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
- - Default URL: `http://127.0.0.1:39282`
63
- - `open-im start` also serves the local configuration page
64
- - `open-im dev` only opens the page automatically when setup is incomplete
65
- - If configuration already exists and you want to open the page manually, use `open-im start`
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": "cursor"
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 Cursor, QQ uses CodeBuddy, and any platform without its own `aiCommand` continues using Claude.
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": "cursor",
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`, `cursor`, or `codebuddy` |
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 most recently active DingTalk conversation. After a cold start, if no DingTalk conversation has interacted with the service yet, there is no target to notify
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、Cursor、CodeBuddy),方便在手机或聊天窗口里远程使用 AI 编程助手。
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、Cursor、CodeBuddy
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
- - 默认地址:`http://127.0.0.1:39282`
63
- - `open-im start` 会同时提供本地配置页面
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
- - 已有配置但想手动打开时,使用 `open-im start`
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": "cursor"
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,飞书会走 Cursor,QQ 会走 CodeBuddy,其他未单独指定 `aiCommand` 的平台仍然使用 Claude。
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": "cursor",
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` / `cursor` / `codebuddy` |
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
- console.log('Claude Agent SDK adapter enabled');
14
+ log.info('Claude Agent SDK adapter enabled');
14
15
  adapters.set('claude', new ClaudeSDKAdapter());
15
16
  }
16
17
  else {
17
- console.log('Claude CLI adapter enabled');
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
- console.log('Codex CLI adapter enabled');
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
- console.log('CodeBuddy CLI adapter enabled');
32
+ log.info('CodeBuddy CLI adapter enabled');
37
33
  adapters.set('codebuddy', new CodeBuddyAdapter(config.codebuddyCliPath));
38
34
  }
39
35
  }
@@ -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
- console.log(`\n📦 检测到新版本 v${latest},可手动更新: npm install -g ${PKG_NAME}@latest`);
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
- console.log(`\n📦 检测到新版本 v${latest}(当前 v${CURRENT_VERSION}),正在更新...`);
79
+ log.info(`检测到新版本 v${latest}(当前 v${CURRENT_VERSION}),正在更新...`);
78
80
  const ok = runGlobalUpdate();
79
81
  if (ok) {
80
- console.log(`\n✅ 已更新到 v${latest},继续按最新版本启动服务...\n`);
82
+ log.info(`已更新到 v${latest},继续按最新版本启动服务...`);
81
83
  return { updated: true, latest };
82
84
  }
83
- console.log("\n⚠️ 自动更新失败,请手动执行: npm install -g @wu529778790/open-im@latest");
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
- let stdoutState = { buffer: '' };
212
+ const stdoutState = { buffer: '' };
213
213
  const clearTimers = () => {
214
214
  if (timeoutHandle) {
215
215
  clearTimeout(timeoutHandle);
@@ -172,13 +172,11 @@ export class CommandHandler {
172
172
  return true;
173
173
  }
174
174
  getAiVersion(aiCommand) {
175
- const cmd = aiCommand === 'cursor'
176
- ? this.deps.config.cursorCliPath
177
- : aiCommand === 'codex'
178
- ? this.deps.config.codexCliPath
179
- : aiCommand === 'codebuddy'
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", "cursor", "codebuddy"];
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", cursor: "Cursor", codebuddy: "CodeBuddy" };
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", "cursor", "codebuddy"]);
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}"`);
@@ -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
- console.log(`Opened local configuration page: ${started.url}`);
728
- console.log(process.env.OPEN_IM_NO_BROWSER === "1" ? "Browser launch disabled. Open the URL manually." : "Save the configuration in your browser to continue.");
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
  }