@wu529778790/open-im 1.6.5 → 1.6.6-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -14,6 +14,19 @@ Multi-platform IM bridge for AI CLI tools. Connect Telegram, Feishu, WeCom, Ding
14
14
  - Isolated sessions: each user gets an independent local session, and `/new` resets it
15
15
  - Built-in commands: `/help`, `/new`, `/cd`, `/pwd`, `/status`
16
16
 
17
+ ## Coverage Matrix
18
+
19
+ Capability levels: `Native` = fully supported in-channel, `Fallback` = degraded behavior or text fallback, `None` = not currently supported.
20
+
21
+ | Platform | Text Inbound | Image Inbound | File Inbound | Voice Inbound | Video Inbound | Streaming Reply | Image Reply | Card Reply |
22
+ | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- |
23
+ | Telegram | Native | Native | Native | Native | Native | Native | Native | Native |
24
+ | Feishu | Native | Native | Native | Fallback | Fallback | Native | Native | Native |
25
+ | QQ | Native | Fallback | Fallback | Fallback | Fallback | None | Fallback | Fallback |
26
+ | WeCom | Native | Fallback | Fallback | Fallback | Fallback | Native | Native | Native |
27
+ | DingTalk | Native | Fallback | Fallback | Fallback | Fallback | Native | Fallback | Native |
28
+ | WeChat (experimental) | Native | Fallback | Fallback | Fallback | Fallback | Native | Fallback | Native |
29
+
17
30
  ## Requirements
18
31
 
19
32
  - Node.js >= 20
package/README.zh-CN.md CHANGED
@@ -14,6 +14,19 @@
14
14
  - 会话隔离:每个用户独立维护本地会话,`/new` 可重置
15
15
  - 常用命令:支持 `/help`、`/new`、`/cd`、`/pwd`、`/status`
16
16
 
17
+ ## 覆盖矩阵
18
+
19
+ 能力等级说明:`Native` 表示平台内原生支持,`Fallback` 表示有降级方案或文本兜底,`None` 表示当前暂不支持。
20
+
21
+ | 平台 | 文本输入 | 图片输入 | 文件输入 | 语音输入 | 视频输入 | 流式回复 | 图片回复 | 卡片回复 |
22
+ | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- |
23
+ | Telegram | Native | Native | Native | Native | Native | Native | Native | Native |
24
+ | 飞书 | Native | Native | Native | Fallback | Fallback | Native | Native | Native |
25
+ | QQ | Native | Fallback | Fallback | Fallback | Fallback | None | Fallback | Fallback |
26
+ | 企业微信 | Native | Fallback | Fallback | Fallback | Fallback | Native | Native | Native |
27
+ | 钉钉 | Native | Fallback | Fallback | Fallback | Fallback | Native | Fallback | Native |
28
+ | 微信(测试中) | Native | Fallback | Fallback | Fallback | Fallback | Native | Fallback | Native |
29
+
17
30
  ## 环境要求
18
31
 
19
32
  - Node.js >= 20
@@ -65,6 +65,11 @@ export declare const PAGE_TEXTS: {
65
65
  readonly aiTitle: "AI Tooling";
66
66
  readonly aiHint: "";
67
67
  readonly claudeNote: "Claude credentials are still read from environment variables or ~/.claude/settings.json. This page manages local bridge config, not Claude account auth.";
68
+ readonly aiCommonTitle: "Shared defaults";
69
+ readonly aiCommonHint: "Choose the default AI tool first. Tool-specific fields are shown below.";
70
+ readonly aiToolConfigTitle: "Tool-specific settings";
71
+ readonly aiToolConfigHint: "Only the selected tool's fields are shown.";
72
+ readonly aiConfigSummary: "Editing {tool} settings. Switch tools above if you need to configure another runtime.";
68
73
  readonly aiTool: "Default AI tool";
69
74
  readonly workDir: "Default work directory";
70
75
  readonly claudeCli: "Claude CLI path";
@@ -164,6 +169,11 @@ export declare const PAGE_TEXTS: {
164
169
  readonly aiTitle: "AI 工具配置";
165
170
  readonly aiHint: "";
166
171
  readonly claudeNote: "Claude 凭证仍然从环境变量或 ~/.claude/settings.json 读取。这个页面只管理本地桥接配置,不负责 Claude 账号登录。";
172
+ readonly aiCommonTitle: "公共默认配置";
173
+ readonly aiCommonHint: "先选择默认 AI 工具,下方再显示对应的工具专属配置。";
174
+ readonly aiToolConfigTitle: "工具专属配置";
175
+ readonly aiToolConfigHint: "只显示当前选中 AI 工具的字段。";
176
+ readonly aiConfigSummary: "正在编辑 {tool} 的配置,如需配置其他工具,可在上方切换。";
167
177
  readonly aiTool: "默认 AI 工具";
168
178
  readonly workDir: "默认工作目录";
169
179
  readonly claudeCli: "Claude CLI 路径";
@@ -65,6 +65,11 @@ export const PAGE_TEXTS = {
65
65
  aiTitle: "AI Tooling",
66
66
  aiHint: "",
67
67
  claudeNote: "Claude credentials are still read from environment variables or ~/.claude/settings.json. This page manages local bridge config, not Claude account auth.",
68
+ aiCommonTitle: "Shared defaults",
69
+ aiCommonHint: "Choose the default AI tool first. Tool-specific fields are shown below.",
70
+ aiToolConfigTitle: "Tool-specific settings",
71
+ aiToolConfigHint: "Only the selected tool's fields are shown.",
72
+ aiConfigSummary: "Editing {tool} settings. Switch tools above if you need to configure another runtime.",
68
73
  aiTool: "Default AI tool",
69
74
  workDir: "Default work directory",
70
75
  claudeCli: "Claude CLI path",
@@ -164,6 +169,11 @@ export const PAGE_TEXTS = {
164
169
  aiTitle: "AI \u5de5\u5177\u914d\u7f6e",
165
170
  aiHint: "",
166
171
  claudeNote: "Claude \u51ed\u8bc1\u4ecd\u7136\u4ece\u73af\u5883\u53d8\u91cf\u6216 ~/.claude/settings.json \u8bfb\u53d6\u3002\u8fd9\u4e2a\u9875\u9762\u53ea\u7ba1\u7406\u672c\u5730\u6865\u63a5\u914d\u7f6e\uff0c\u4e0d\u8d1f\u8d23 Claude \u8d26\u53f7\u767b\u5f55\u3002",
172
+ aiCommonTitle: "\u516c\u5171\u9ed8\u8ba4\u914d\u7f6e",
173
+ aiCommonHint: "\u5148\u9009\u62e9\u9ed8\u8ba4 AI \u5de5\u5177\uff0c\u4e0b\u65b9\u518d\u663e\u793a\u5bf9\u5e94\u7684\u5de5\u5177\u4e13\u5c5e\u914d\u7f6e\u3002",
174
+ aiToolConfigTitle: "\u5de5\u5177\u4e13\u5c5e\u914d\u7f6e",
175
+ aiToolConfigHint: "\u53ea\u663e\u793a\u5f53\u524d\u9009\u4e2d AI \u5de5\u5177\u7684\u5b57\u6bb5\u3002",
176
+ aiConfigSummary: "\u6b63\u5728\u7f16\u8f91 {tool} \u7684\u914d\u7f6e\uff0c\u5982\u9700\u914d\u7f6e\u5176\u4ed6\u5de5\u5177\uff0c\u53ef\u5728\u4e0a\u65b9\u5207\u6362\u3002",
167
177
  aiTool: "\u9ed8\u8ba4 AI \u5de5\u5177",
168
178
  workDir: "\u9ed8\u8ba4\u5de5\u4f5c\u76ee\u5f55",
169
179
  claudeCli: "Claude CLI \u8def\u5f84",
@@ -6,6 +6,7 @@ export const PAGE_SCRIPT = String.raw ` const platformDefinitions = [
6
6
  { key: "dingtalk", label: "DingTalk", fields: ["aiCommand", "clientId", "clientSecret", "cardTemplateId", "allowedUserIds"], testFields: ["clientId", "clientSecret"] },
7
7
  ];
8
8
  const platformKeys = platformDefinitions.map((platform) => platform.key);
9
+ const aiTools = ["claude", "codex", "cursor", "codebuddy"];
9
10
  const ids = platformDefinitions.flatMap((platform) => ["enabled", ...platform.fields].map((field) => platform.key + "-" + field)).concat(["ai-aiCommand","ai-claudeCliPath","ai-claudeWorkDir","ai-claudeSkipPermissions","ai-claudeTimeoutMs","ai-codexTimeoutMs","ai-codebuddyTimeoutMs","ai-claudeModel","ai-cursorCliPath","ai-codexCliPath","ai-codebuddyCliPath","ai-codexProxy","ai-hookPort","ai-logLevel","ai-useSdkMode"]);
10
11
  const el = (id) => document.getElementById(id);
11
12
  const storageKey = "open-im-web-lang";
@@ -25,6 +26,7 @@ export const PAGE_SCRIPT = String.raw ` const platformDefinitions = [
25
26
  const setChecked = (id, value) => { const node = el(id); if (node) node.checked = Boolean(value); };
26
27
  const setMessage = (text, type="") => { const node = el("message"); node.textContent = text; node.className = ("message " + type).trim(); };
27
28
  const setBusy = (busy) => ["validateButton","saveButton","startButton","stopButton","langButton"].forEach((id) => { el(id).disabled = busy; });
29
+ const getActiveAiTool = () => el("ai-aiCommand").value || "claude";
28
30
  const readPlatformConfig = (platform) => platform.testFields.reduce((config, field) => {
29
31
  config[field] = getValue(platform.key + "-" + field);
30
32
  return config;
@@ -54,6 +56,14 @@ export const PAGE_SCRIPT = String.raw ` const platformDefinitions = [
54
56
  el("aiHint").textContent = t("aiHint");
55
57
  el("aiHint").style.display = t("aiHint") ? "block" : "none";
56
58
  el("claudeNote").textContent = t("claudeNote");
59
+ setText("aiCommonTitle", t("aiCommonTitle"));
60
+ setText("aiCommonHint", t("aiCommonHint"));
61
+ setText("aiToolConfigTitle", t("aiToolConfigTitle"));
62
+ setText("aiToolConfigHint", t("aiToolConfigHint"));
63
+ setText("ai-claudeSectionLabel", "Claude");
64
+ setText("ai-codexSectionLabel", "Codex");
65
+ setText("ai-cursorSectionLabel", "Cursor");
66
+ setText("ai-codebuddySectionLabel", "CodeBuddy");
57
67
  setText("telegram-enabled-label", t("enabled"));
58
68
  setText("feishu-enabled-label", t("enabled"));
59
69
  setText("qq-enabled-label", t("enabled"));
@@ -123,6 +133,9 @@ export const PAGE_SCRIPT = String.raw ` const platformDefinitions = [
123
133
  el("ai-claudeModel").placeholder = t("optional");
124
134
  setText("ai-claudeSkipPermissions-label", t("autoApprove"));
125
135
  setText("ai-useSdkMode-label", t("sdkMode"));
136
+ document.querySelectorAll("[data-tool]").forEach((button) => {
137
+ button.textContent = button.getAttribute("data-tool");
138
+ });
126
139
  el("validateButton").textContent = t("validate");
127
140
  el("saveButton").textContent = t("save");
128
141
  el("startButton").textContent = t("start");
@@ -156,6 +169,20 @@ export const PAGE_SCRIPT = String.raw ` const platformDefinitions = [
156
169
  el("navAiBtn").textContent = t("aiTitle");
157
170
  el("navServiceBtn").textContent = t("serviceTitle");
158
171
  }
172
+ function updateAiToolVisibility() {
173
+ const activeTool = getActiveAiTool();
174
+ aiTools.forEach((tool) => {
175
+ const panel = document.querySelector('[data-tool-panel="' + tool + '"]');
176
+ if (panel) {
177
+ panel.hidden = tool !== activeTool;
178
+ }
179
+ });
180
+ document.querySelectorAll("[data-tool]").forEach((button) => {
181
+ button.classList.toggle("active", button.getAttribute("data-tool") === activeTool);
182
+ });
183
+ const toolLabels = { claude: "Claude", codex: "Codex", cursor: "Cursor", codebuddy: "CodeBuddy" };
184
+ el("aiConfigSummary").textContent = t("aiConfigSummary", { tool: toolLabels[activeTool] || activeTool });
185
+ }
159
186
  function updateVisualState() {
160
187
  const enabled = [];
161
188
  platformDefinitions.forEach((platform) => {
@@ -167,6 +194,7 @@ export const PAGE_SCRIPT = String.raw ` const platformDefinitions = [
167
194
  el("liveSummary").textContent = enabled.length
168
195
  ? t("summaryEnabled", { platforms: enabled.join(t("listSeparator")), tool: aiTool })
169
196
  : t("summaryEmpty", { tool: aiTool });
197
+ updateAiToolVisibility();
170
198
  }
171
199
  async function updateDashboard() {
172
200
  try {
@@ -328,6 +356,14 @@ export const PAGE_SCRIPT = String.raw ` const platformDefinitions = [
328
356
  node.addEventListener("change", updateVisualState);
329
357
  }
330
358
  });
359
+ document.querySelectorAll("[data-tool]").forEach((button) => {
360
+ button.addEventListener("click", () => {
361
+ const tool = button.getAttribute("data-tool");
362
+ if (!tool) return;
363
+ setValue("ai-aiCommand", tool);
364
+ updateVisualState();
365
+ });
366
+ });
331
367
  }
332
368
  async function validate() { setBusy(true); try { await request("/api/config/validate", { method: "POST", body: JSON.stringify(payload()) }); setMessage(t("validationOk"), "success"); } catch (error) { setMessage(error.message || String(error), "error"); } finally { setBusy(false); } }
333
369
  async function save() { setBusy(true); try { await request("/api/config/save?final=1", { method: "POST", body: JSON.stringify(payload()) }); setMessage(t("saveOk"), "success"); } catch (error) { setMessage(error.message || String(error), "error"); } finally { setBusy(false); } }
@@ -47,6 +47,16 @@ export const PAGE_HTML_PREFIX = String.raw `<!doctype html>
47
47
  #configSection .panel,#aiSection .panel,#serviceSection{background:rgba(255,253,247,.82)}
48
48
  #configSection .panel-head,#aiSection .panel-head,#serviceSection .panel-head{margin-bottom:14px}
49
49
  #configSection .panel .summary,#aiSection .panel .summary{line-height:1.58}
50
+ .ai-layout{display:grid;gap:16px}
51
+ .ai-switcher{display:flex;flex-wrap:wrap;gap:10px}
52
+ .tool-switch{padding:10px 14px;background:rgba(19,35,26,.06);color:var(--ink);border:1px solid var(--line);box-shadow:none}
53
+ .tool-switch:hover{box-shadow:none}
54
+ .tool-switch.active{background:var(--green);border-color:var(--green);color:#fff7eb}
55
+ .ai-card{display:grid;gap:14px}
56
+ .ai-card[hidden]{display:none}
57
+ .ai-section-label{font-size:.75rem;letter-spacing:.12em;text-transform:uppercase;color:var(--muted)}
58
+ .ai-summary-bar{padding:14px 16px;border:1px dashed var(--line-strong);background:rgba(255,255,255,.5);color:var(--muted)}
59
+ .toggle-grid{display:grid;gap:12px}
50
60
  #serviceSection .actions{grid-template-columns:repeat(auto-fit,minmax(170px,1fr));align-items:stretch}
51
61
  #startButton{background:var(--green);box-shadow:0 12px 22px rgba(26,106,68,.18)}
52
62
  #saveButton{background:rgba(19,35,26,.88)}
@@ -206,28 +216,67 @@ export const PAGE_HTML_PREFIX = String.raw `<!doctype html>
206
216
  </section>
207
217
  <section class="section" id="aiSection">
208
218
  <div class="panel-head"><h2 id="aiTitle">AI Tooling</h2><div id="aiHint">WeChat is intentionally excluded from this first version.</div></div>
209
- <article class="panel note" id="claudeNote">Claude credentials are still read from environment variables or ~/.claude/settings.json. This page manages local bridge config, not Claude account auth.</article>
210
- <article class="panel">
211
- <div class="two-col">
212
- <label><span id="ai-aiCommand-label">Default AI tool</span><select id="ai-aiCommand"><option value="claude">claude</option><option value="codex">codex</option><option value="cursor">cursor</option><option value="codebuddy">codebuddy</option></select></label>
213
- <label><span id="ai-claudeWorkDir-label">Default work directory</span><input id="ai-claudeWorkDir" class="mono" /></label>
214
- <label><span id="ai-claudeCliPath-label">Claude CLI path</span><input id="ai-claudeCliPath" class="mono" /></label>
215
- <label><span id="ai-cursorCliPath-label">Cursor CLI path</span><input id="ai-cursorCliPath" class="mono" /></label>
216
- <label><span id="ai-codexCliPath-label">Codex CLI path</span><input id="ai-codexCliPath" class="mono" /></label>
217
- <label><span id="ai-codebuddyCliPath-label">CodeBuddy CLI path</span><input id="ai-codebuddyCliPath" class="mono" /></label>
218
- <label><span id="ai-codexProxy-label">Codex proxy</span><input id="ai-codexProxy" class="mono" placeholder="Optional" /></label>
219
- <label><span id="ai-claudeTimeoutMs-label">Claude timeout (ms)</span><input id="ai-claudeTimeoutMs" type="number" min="1" /></label>
220
- <label><span id="ai-codexTimeoutMs-label">Codex timeout (ms)</span><input id="ai-codexTimeoutMs" type="number" min="1" /></label>
221
- <label><span id="ai-codebuddyTimeoutMs-label">CodeBuddy timeout (ms)</span><input id="ai-codebuddyTimeoutMs" type="number" min="1" /></label>
222
- <label><span id="ai-claudeModel-label">Claude model</span><input id="ai-claudeModel" placeholder="Optional" /></label>
223
- <label><span id="ai-hookPort-label">Hook port</span><input id="ai-hookPort" type="number" min="1" /></label>
224
- <label><span id="ai-logLevel-label">Log level</span><select id="ai-logLevel"><option value="default">default</option><option value="DEBUG">DEBUG</option><option value="INFO">INFO</option><option value="WARN">WARN</option><option value="ERROR">ERROR</option></select></label>
225
- </div>
226
- <div class="actions" style="margin-top:14px">
227
- <label class="toggle"><input id="ai-claudeSkipPermissions" type="checkbox" /> <span id="ai-claudeSkipPermissions-label">Auto-approve tool permissions</span></label>
228
- <label class="toggle"><input id="ai-useSdkMode" type="checkbox" /> <span id="ai-useSdkMode-label">Use Claude SDK mode</span></label>
229
- </div>
230
- </article>
219
+ <div class="ai-layout">
220
+ <article class="panel note" id="claudeNote">Claude credentials are still read from environment variables or ~/.claude/settings.json. This page manages local bridge config, not Claude account auth.</article>
221
+ <article class="panel ai-card">
222
+ <div class="panel-head">
223
+ <h3 id="aiCommonTitle">Shared defaults</h3>
224
+ <div class="summary" id="aiCommonHint">Choose the default AI tool first. Tool-specific fields are shown below.</div>
225
+ </div>
226
+ <div class="two-col">
227
+ <label><span id="ai-aiCommand-label">Default AI tool</span><select id="ai-aiCommand"><option value="claude">claude</option><option value="codex">codex</option><option value="cursor">cursor</option><option value="codebuddy">codebuddy</option></select></label>
228
+ <label><span id="ai-claudeWorkDir-label">Default work directory</span><input id="ai-claudeWorkDir" class="mono" /></label>
229
+ <label><span id="ai-hookPort-label">Hook port</span><input id="ai-hookPort" type="number" min="1" /></label>
230
+ <label><span id="ai-logLevel-label">Log level</span><select id="ai-logLevel"><option value="default">default</option><option value="DEBUG">DEBUG</option><option value="INFO">INFO</option><option value="WARN">WARN</option><option value="ERROR">ERROR</option></select></label>
231
+ </div>
232
+ <div class="ai-summary-bar" id="aiConfigSummary"></div>
233
+ </article>
234
+ <article class="panel ai-card">
235
+ <div class="panel-head">
236
+ <h3 id="aiToolConfigTitle">Tool-specific settings</h3>
237
+ <div class="summary" id="aiToolConfigHint">Only the selected tool's fields are shown.</div>
238
+ </div>
239
+ <div class="ai-switcher" id="aiToolSwitcher">
240
+ <button type="button" class="tool-switch" data-tool="claude">claude</button>
241
+ <button type="button" class="tool-switch" data-tool="codex">codex</button>
242
+ <button type="button" class="tool-switch" data-tool="cursor">cursor</button>
243
+ <button type="button" class="tool-switch" data-tool="codebuddy">codebuddy</button>
244
+ </div>
245
+ <section class="ai-card" id="ai-tool-claude" data-tool-panel="claude">
246
+ <div class="ai-section-label" id="ai-claudeSectionLabel">Claude</div>
247
+ <div class="two-col">
248
+ <label><span id="ai-claudeCliPath-label">Claude CLI path</span><input id="ai-claudeCliPath" class="mono" /></label>
249
+ <label><span id="ai-claudeTimeoutMs-label">Claude timeout (ms)</span><input id="ai-claudeTimeoutMs" type="number" min="1" /></label>
250
+ <label><span id="ai-claudeModel-label">Claude model</span><input id="ai-claudeModel" placeholder="Optional" /></label>
251
+ </div>
252
+ <div class="toggle-grid">
253
+ <label class="toggle"><input id="ai-claudeSkipPermissions" type="checkbox" /> <span id="ai-claudeSkipPermissions-label">Auto-approve tool permissions</span></label>
254
+ <label class="toggle"><input id="ai-useSdkMode" type="checkbox" /> <span id="ai-useSdkMode-label">Use Claude SDK mode</span></label>
255
+ </div>
256
+ </section>
257
+ <section class="ai-card" id="ai-tool-codex" data-tool-panel="codex" hidden>
258
+ <div class="ai-section-label" id="ai-codexSectionLabel">Codex</div>
259
+ <div class="two-col">
260
+ <label><span id="ai-codexCliPath-label">Codex CLI path</span><input id="ai-codexCliPath" class="mono" /></label>
261
+ <label><span id="ai-codexTimeoutMs-label">Codex timeout (ms)</span><input id="ai-codexTimeoutMs" type="number" min="1" /></label>
262
+ <label><span id="ai-codexProxy-label">Codex proxy</span><input id="ai-codexProxy" class="mono" placeholder="Optional" /></label>
263
+ </div>
264
+ </section>
265
+ <section class="ai-card" id="ai-tool-cursor" data-tool-panel="cursor" hidden>
266
+ <div class="ai-section-label" id="ai-cursorSectionLabel">Cursor</div>
267
+ <div class="two-col">
268
+ <label><span id="ai-cursorCliPath-label">Cursor CLI path</span><input id="ai-cursorCliPath" class="mono" /></label>
269
+ </div>
270
+ </section>
271
+ <section class="ai-card" id="ai-tool-codebuddy" data-tool-panel="codebuddy" hidden>
272
+ <div class="ai-section-label" id="ai-codebuddySectionLabel">CodeBuddy</div>
273
+ <div class="two-col">
274
+ <label><span id="ai-codebuddyCliPath-label">CodeBuddy CLI path</span><input id="ai-codebuddyCliPath" class="mono" /></label>
275
+ <label><span id="ai-codebuddyTimeoutMs-label">CodeBuddy timeout (ms)</span><input id="ai-codebuddyTimeoutMs" type="number" min="1" /></label>
276
+ </div>
277
+ </section>
278
+ </article>
279
+ </div>
231
280
  </section>
232
281
  <section class="footer" id="serviceSection">
233
282
  <div class="panel-head"><h2 id="serviceTitle">Service Control</h2><div id="serviceHint">Validate, save, start, and stop the local bridge from one place.</div></div>
@@ -30,4 +30,14 @@ describe("config web page assembly", () => {
30
30
  expect(PAGE_HTML).toContain(`id="${id}"`);
31
31
  }
32
32
  });
33
+ it("keeps AI tool switch buttons and tool panels aligned", () => {
34
+ const toolListMatch = PAGE_SCRIPT.match(/const aiTools = \[([^\]]+)\]/);
35
+ expect(toolListMatch).toBeTruthy();
36
+ const tools = Array.from((toolListMatch?.[1] ?? "").matchAll(/"([^"]+)"/g), (match) => match[1]);
37
+ expect(tools).toEqual(["claude", "codex", "cursor", "codebuddy"]);
38
+ for (const tool of tools) {
39
+ expect(PAGE_HTML).toContain(`data-tool="${tool}"`);
40
+ expect(PAGE_HTML).toContain(`data-tool-panel="${tool}"`);
41
+ }
42
+ });
33
43
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wu529778790/open-im",
3
- "version": "1.6.5",
3
+ "version": "1.6.6-beta.0",
4
4
  "description": "Multi-platform IM bridge for AI CLI tools (Claude, Codex, Cursor, CodeBuddy)",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",