@wu529778790/open-im 1.7.1-beta.9 → 1.8.1-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.
@@ -118,6 +118,14 @@ export declare const PAGE_TEXTS: {
118
118
  readonly saveOk: "Configuration saved.";
119
119
  readonly startOk: "Bridge started.";
120
120
  readonly stopOk: "Bridge stopped.";
121
+ readonly configEditorTitle: "Config Editor";
122
+ readonly configEditorHint: "Edit ~/.open-im/config.json directly";
123
+ readonly configJson: "Configuration (JSON)";
124
+ readonly configJsonHint: "Edit the configuration JSON. Changes will be saved when you click \"Save Config\" in the Service section.";
125
+ readonly formatJson: "Format";
126
+ readonly resetJson: "Reset";
127
+ readonly jsonValid: "Valid JSON";
128
+ readonly jsonInvalid: "Invalid JSON: {error}";
121
129
  };
122
130
  readonly zh: {
123
131
  readonly pageTitle: "open-im 本地控制台";
@@ -232,5 +240,13 @@ export declare const PAGE_TEXTS: {
232
240
  readonly saveOk: "配置已保存。";
233
241
  readonly startOk: "桥接已启动。";
234
242
  readonly stopOk: "桥接已停止。";
243
+ readonly configEditorTitle: "JSON 配置编辑器";
244
+ readonly configEditorHint: "直接编辑 ~/.open-im/config.json";
245
+ readonly configJson: "配置文件 (JSON)";
246
+ readonly configJsonHint: "编辑配置 JSON。点击服务区的“保存配置”按钮后保存更改。";
247
+ readonly formatJson: "格式化";
248
+ readonly resetJson: "重置";
249
+ readonly jsonValid: "JSON 有效";
250
+ readonly jsonInvalid: "JSON 无效:{error}";
235
251
  };
236
252
  };
@@ -118,6 +118,14 @@ export const PAGE_TEXTS = {
118
118
  saveOk: "Configuration saved.",
119
119
  startOk: "Bridge started.",
120
120
  stopOk: "Bridge stopped.",
121
+ configEditorTitle: "Config Editor",
122
+ configEditorHint: "Edit ~/.open-im/config.json directly",
123
+ configJson: "Configuration (JSON)",
124
+ configJsonHint: "Edit the configuration JSON. Changes will be saved when you click \"Save Config\" in the Service section.",
125
+ formatJson: "Format",
126
+ resetJson: "Reset",
127
+ jsonValid: "Valid JSON",
128
+ jsonInvalid: "Invalid JSON: {error}",
121
129
  },
122
130
  zh: {
123
131
  pageTitle: "open-im \u672c\u5730\u63a7\u5236\u53f0",
@@ -232,5 +240,13 @@ export const PAGE_TEXTS = {
232
240
  saveOk: "\u914d\u7f6e\u5df2\u4fdd\u5b58\u3002",
233
241
  startOk: "\u6865\u63a5\u5df2\u542f\u52a8\u3002",
234
242
  stopOk: "\u6865\u63a5\u5df2\u505c\u6b62\u3002",
243
+ configEditorTitle: "JSON \u914d\u7f6e\u7f16\u8f91\u5668",
244
+ configEditorHint: "\u76f4\u63a5\u7f16\u8f91 ~/.open-im/config.json",
245
+ configJson: "\u914d\u7f6e\u6587\u4ef6 (JSON)",
246
+ configJsonHint: "\u7f16\u8f91\u914d\u7f6e JSON\u3002\u70b9\u51fb\u670d\u52a1\u533a\u7684\u201c\u4fdd\u5b58\u914d\u7f6e\u201d\u6309\u94ae\u540e\u4fdd\u5b58\u66f4\u6539\u3002",
247
+ formatJson: "\u683c\u5f0f\u5316",
248
+ resetJson: "\u91cd\u7f6e",
249
+ jsonValid: "JSON \u6709\u6548",
250
+ jsonInvalid: "JSON \u65e0\u6548\uff1a{error}",
235
251
  }
236
252
  };
@@ -380,7 +380,7 @@ export const PAGE_SCRIPT = String.raw ` const platformDefinitions = [
380
380
 
381
381
  // Navigation
382
382
  function setActiveNav(targetId) {
383
- ["navOverviewBtn","navPlatformsBtn","navAiBtn","navServiceBtn"].forEach((id) => {
383
+ ["navOverviewBtn","navPlatformsBtn","navAiBtn","navServiceBtn","navConfigEditorBtn"].forEach((id) => {
384
384
  const btn = el(id);
385
385
  if (btn) btn.classList.toggle("active", id === targetId);
386
386
  });
@@ -445,6 +445,70 @@ export const PAGE_SCRIPT = String.raw ` const platformDefinitions = [
445
445
  }
446
446
  }
447
447
 
448
+ // Open-im Config Editor functions
449
+ let originalConfigJson = "";
450
+
451
+ async function loadOpenImConfig() {
452
+ const textarea = document.getElementById("configJson");
453
+ if (!(textarea instanceof HTMLTextAreaElement)) return;
454
+ try {
455
+ const data = await request("/api/config/file");
456
+ const raw = (data.contents || "").trim();
457
+ originalConfigJson = raw;
458
+ try {
459
+ const parsed = JSON.parse(raw);
460
+ textarea.value = JSON.stringify(parsed, null, 2) + "\n";
461
+ } catch {
462
+ textarea.value = raw;
463
+ }
464
+ validateJson();
465
+ } catch (error) {
466
+ showJsonValidationMessage(error.message || String(error), "error");
467
+ }
468
+ }
469
+
470
+ function validateJson() {
471
+ const textarea = document.getElementById("configJson");
472
+ const message = document.getElementById("jsonValidationMessage");
473
+ if (!(textarea instanceof HTMLTextAreaElement) || !message) return;
474
+
475
+ const json = textarea.value;
476
+ try {
477
+ JSON.parse(json);
478
+ showJsonValidationMessage("Valid JSON", "success");
479
+ } catch (err) {
480
+ showJsonValidationMessage("Invalid JSON: " + (err.message || String(err)), "error");
481
+ }
482
+ }
483
+
484
+ function showJsonValidationMessage(text, type) {
485
+ const message = document.getElementById("jsonValidationMessage");
486
+ if (!message) return;
487
+ message.textContent = text;
488
+ message.className = "message";
489
+ if (type) message.classList.add("message-" + type);
490
+ message.classList.remove("hidden");
491
+ }
492
+
493
+ function formatJson() {
494
+ const textarea = document.getElementById("configJson");
495
+ if (!(textarea instanceof HTMLTextAreaElement)) return;
496
+ try {
497
+ const parsed = JSON.parse(textarea.value);
498
+ textarea.value = JSON.stringify(parsed, null, 2) + "\n";
499
+ validateJson();
500
+ } catch (err) {
501
+ showJsonValidationMessage("Cannot format: Invalid JSON", "error");
502
+ }
503
+ }
504
+
505
+ function resetJson() {
506
+ const textarea = document.getElementById("configJson");
507
+ if (!(textarea instanceof HTMLTextAreaElement)) return;
508
+ textarea.value = originalConfigJson;
509
+ validateJson();
510
+ }
511
+
448
512
  // Fill form with data
449
513
  const AI_FIELD_MAPPINGS = [
450
514
  { id: "ai-aiCommand", key: "aiCommand" },
@@ -475,6 +539,9 @@ export const PAGE_SCRIPT = String.raw ` const platformDefinitions = [
475
539
  }
476
540
  });
477
541
 
542
+ // Load JSON editor content
543
+ void loadOpenImConfig();
544
+
478
545
  updateVisualState();
479
546
  }
480
547
 
@@ -558,11 +625,30 @@ export const PAGE_SCRIPT = String.raw ` const platformDefinitions = [
558
625
  });
559
626
  });
560
627
 
628
+ // Config editor JSON textarea
629
+ const configJsonTextarea = document.getElementById("configJson");
630
+ if (configJsonTextarea) {
631
+ configJsonTextarea.addEventListener("input", validateJson);
632
+ }
633
+
634
+ // Config editor buttons
635
+ const formatJsonButton = document.getElementById("formatJsonButton");
636
+ if (formatJsonButton) {
637
+ formatJsonButton.addEventListener("click", formatJson);
638
+ }
639
+
640
+ const resetJsonButton = document.getElementById("resetJsonButton");
641
+ if (resetJsonButton) {
642
+ resetJsonButton.addEventListener("click", resetJson);
643
+ }
644
+
645
+
561
646
  // Navigation
562
647
  el("navOverviewBtn").onclick = () => scrollToSection("dashboardSection", "navOverviewBtn");
563
648
  el("navPlatformsBtn").onclick = () => scrollToSection("configSection", "navPlatformsBtn");
564
649
  el("navAiBtn").onclick = () => scrollToSection("aiSection", "navAiBtn");
565
650
  el("navServiceBtn").onclick = () => scrollToSection("serviceSection", "navServiceBtn");
651
+ el("navConfigEditorBtn").onclick = () => scrollToSection("configEditorSection", "navConfigEditorBtn");
566
652
 
567
653
  // Language toggle
568
654
  el("langButton").onclick = () => {
@@ -617,6 +703,9 @@ export const PAGE_SCRIPT = String.raw ` const platformDefinitions = [
617
703
  async function save() {
618
704
  setBusy(true);
619
705
  try {
706
+ // First save JSON editor content if changed
707
+ await saveOpenImConfig();
708
+ // Then save form data
620
709
  await request("/api/config/save?final=1", { method: "POST", body: JSON.stringify(payload()) });
621
710
  setMessage(t("saveOk"), "success");
622
711
  } catch (error) {
@@ -626,6 +715,31 @@ export const PAGE_SCRIPT = String.raw ` const platformDefinitions = [
626
715
  }
627
716
  }
628
717
 
718
+ async function saveOpenImConfig() {
719
+ const textarea = document.getElementById("configJson");
720
+ if (!(textarea instanceof HTMLTextAreaElement)) return;
721
+ const json = textarea.value;
722
+
723
+ // Validate JSON
724
+ try {
725
+ JSON.parse(json);
726
+ } catch (err) {
727
+ showJsonValidationMessage("Invalid JSON: " + (err.message || String(err)), "error");
728
+ throw new Error("Invalid JSON: " + (err.message || String(err)));
729
+ }
730
+
731
+ try {
732
+ await request("/api/config/file", {
733
+ method: "POST",
734
+ body: JSON.stringify({ contents: json }),
735
+ });
736
+ originalConfigJson = json;
737
+ } catch (error) {
738
+ showJsonValidationMessage(error.message || String(error), "error");
739
+ throw error;
740
+ }
741
+ }
742
+
629
743
  async function startService() {
630
744
  setBusy(true);
631
745
  try {
@@ -781,6 +781,16 @@ export const PAGE_HTML_PREFIX = String.raw `<!doctype html>
781
781
  </svg>
782
782
  <span id="navServiceText">Service</span>
783
783
  </button>
784
+ <button class="nav-item" id="navConfigEditorBtn">
785
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
786
+ <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/>
787
+ <polyline points="14 2 14 8 20 8"/>
788
+ <line x1="16" y1="13" x2="8" y2="13"/>
789
+ <line x1="16" y1="17" x2="8" y2="17"/>
790
+ <polyline points="10 9 9 9 8 9"/>
791
+ </svg>
792
+ <span id="navConfigEditorText">Config Editor</span>
793
+ </button>
784
794
  </nav>
785
795
  </aside>
786
796
 
@@ -1196,6 +1206,33 @@ export const PAGE_HTML_PREFIX = String.raw `<!doctype html>
1196
1206
  </div>
1197
1207
  </div>
1198
1208
  </section>
1209
+
1210
+ <!-- Config Editor Section -->
1211
+ <section class="section" id="configEditorSection" style="display:none">
1212
+ <div class="section-header">
1213
+ <h2 class="section-title" id="configEditorTitle">Config Editor</h2>
1214
+ <p class="section-description" id="configEditorHint">Edit ~/.open-im/config.json directly</p>
1215
+ </div>
1216
+
1217
+ <div class="card">
1218
+ <div class="card-body">
1219
+ <div class="form-group">
1220
+ <div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:12px;">
1221
+ <label class="form-label" id="configJson-label">Configuration (JSON)</label>
1222
+ <div style="display:flex; gap:8px;">
1223
+ <button id="formatJsonButton" class="btn btn-sm btn-ghost">Format</button>
1224
+ <button id="resetJsonButton" class="btn btn-sm btn-ghost">Reset</button>
1225
+ </div>
1226
+ </div>
1227
+ <textarea id="configJson" class="form-input mono" rows="20" style="font-family:monospace; font-size:13px; line-height:1.5; min-height:400px; resize:vertical;" spellcheck="false"></textarea>
1228
+ <p class="form-hint" id="configJson-hint">Edit the configuration JSON. Changes will be saved when you click "Save Config" in the Service section.</p>
1229
+ </div>
1230
+ <div class="form-group">
1231
+ <div id="jsonValidationMessage" class="message hidden" aria-live="polite"></div>
1232
+ </div>
1233
+ </div>
1234
+ </div>
1235
+ </section>
1199
1236
  </div>
1200
1237
  </main>
1201
1238
  </div>
@@ -241,7 +241,7 @@ function buildInitialPayload(file) {
241
241
  : getClaudeConfigHome() + "/.claude/settings.json",
242
242
  claudeAuthToken: claudeEnv.ANTHROPIC_AUTH_TOKEN ?? "",
243
243
  claudeBaseUrl: claudeEnv.ANTHROPIC_BASE_URL ?? "",
244
- claudeModel: file.tools?.claude?.model ?? claudeEnv.ANTHROPIC_MODEL ?? "",
244
+ claudeModel: claudeEnv.ANTHROPIC_MODEL ?? "",
245
245
  claudeProxy: file.tools?.claude?.proxy ?? "",
246
246
  codexTimeoutMs: file.tools?.codex?.timeoutMs ?? 600000,
247
247
  codebuddyTimeoutMs: file.tools?.codebuddy?.timeoutMs ?? 600000,
@@ -498,7 +498,7 @@ function toFileConfig(payload, existing) {
498
498
  workDir: clean(payload.ai.claudeWorkDir) ?? process.cwd(),
499
499
  timeoutMs: payload.ai.claudeTimeoutMs,
500
500
  proxy: clean(payload.ai.claudeProxy),
501
- // model is now saved to ~/.claude/settings.json as ANTHROPIC_MODEL
501
+ // model is now saved to ~/.claude/settings.json as env var
502
502
  },
503
503
  codex: {
504
504
  ...existing.tools?.codex,
package/dist/config.d.ts CHANGED
@@ -32,6 +32,8 @@ export interface Config {
32
32
  aiCommand: AiCommand;
33
33
  codexCliPath: string;
34
34
  codebuddyCliPath: string;
35
+ /** Claude 访问 API 的代理(如 http://127.0.0.1:7890) */
36
+ claudeProxy?: string;
35
37
  /** Codex 访问 chatgpt.com 的代理(如 http://127.0.0.1:7890) */
36
38
  codexProxy?: string;
37
39
  claudeTimeoutMs: number;
@@ -133,10 +135,14 @@ export interface FilePlatformDingtalk {
133
135
  cardTemplateId?: string;
134
136
  }
135
137
  export interface FileToolClaude {
138
+ cliPath?: string;
136
139
  workDir?: string;
137
140
  timeoutMs?: number;
138
- model?: string;
141
+ skipPermissions?: boolean;
142
+ /** HTTP/HTTPS 代理,用于访问 Claude API(如 http://127.0.0.1:7890) */
139
143
  proxy?: string;
144
+ /** Claude API 配置(优先级:环境变量 > tools.claude.env > ~/.claude/settings.json) */
145
+ env?: Record<string, string>;
140
146
  }
141
147
  export interface FileToolCodex {
142
148
  cliPath?: string;
package/dist/config.js CHANGED
@@ -55,7 +55,8 @@ function migrateToNewConfigFormat(raw) {
55
55
  ...tc,
56
56
  workDir: tc.workDir ?? raw.claudeWorkDir ?? process.cwd(),
57
57
  timeoutMs: tc.timeoutMs ?? raw.claudeTimeoutMs ?? 600000,
58
- model: tc.model ?? raw.claudeModel,
58
+ proxy: tc.proxy,
59
+ // model 现在通过 env 配置,不再在这里处理
59
60
  },
60
61
  codex: {
61
62
  ...tcod,
@@ -216,11 +217,16 @@ export function loadConfig() {
216
217
  }
217
218
  }
218
219
  };
220
+ // 1. 全局 env(最低优先级之一)
219
221
  if (file.env)
220
222
  mergeEnv(file.env);
221
- // Claude Code 配置合并 API 凭证(~/.claude/settings.json ~/.claude.json,最低优先级)
222
- const claudeEnv = loadClaudeSettingsEnv();
223
- mergeEnv(claudeEnv);
223
+ // 2. tools.claude.env(优先级高于 Claude settings)
224
+ const claudeToolEnv = file.tools?.claude?.env;
225
+ if (claudeToolEnv)
226
+ mergeEnv(claudeToolEnv);
227
+ // 3. 从 Claude Code 配置合并(最低优先级)
228
+ const claudeSettingsEnv = loadClaudeSettingsEnv();
229
+ mergeEnv(claudeSettingsEnv);
224
230
  const fileTelegram = file.platforms?.telegram;
225
231
  const fileFeishu = file.platforms?.feishu;
226
232
  const fileQQ = file.platforms?.qq;
@@ -331,6 +337,7 @@ export function loadConfig() {
331
337
  const tc = file.tools?.claude ?? {};
332
338
  const tcod = file.tools?.codex ?? {};
333
339
  const tcb = file.tools?.codebuddy ?? {};
340
+ const claudeProxy = process.env.CLAUDE_PROXY ?? tc.proxy;
334
341
  const codexProxy = process.env.CODEX_PROXY ?? tcod.proxy;
335
342
  let codexCliPath = process.env.CODEX_CLI_PATH ?? tcod.cliPath ?? 'codex';
336
343
  if (process.platform === 'win32' && codexCliPath === 'codex') {
@@ -401,7 +408,7 @@ export function loadConfig() {
401
408
  ' open-im init',
402
409
  '',
403
410
  '方式 3:编辑配置文件',
404
- ' ~/.open-im/config.json: tools.claude.model = "..."',
411
+ ' ~/.open-im/config.json: tools.claude.env.ANTHROPIC_MODEL = "..."',
405
412
  ' ~/.claude/settings.json: env.ANTHROPIC_MODEL = "..."(与 Claude Code 共用)',
406
413
  '',
407
414
  ].join('\n');
@@ -604,12 +611,13 @@ export function loadConfig() {
604
611
  aiCommand,
605
612
  codexCliPath,
606
613
  codebuddyCliPath,
614
+ claudeProxy,
607
615
  codexProxy,
608
616
  claudeWorkDir,
609
617
  claudeTimeoutMs,
610
618
  codexTimeoutMs,
611
619
  codebuddyTimeoutMs,
612
- claudeModel: process.env.ANTHROPIC_MODEL ?? tc.model,
620
+ claudeModel: process.env.ANTHROPIC_MODEL,
613
621
  logDir,
614
622
  logLevel,
615
623
  platforms,
@@ -228,8 +228,8 @@ export function runAITask(deps, ctx, prompt, toolAdapter, platformAdapter) {
228
228
  timeoutMs,
229
229
  model: sessionManager.getModel(ctx.userId, ctx.threadId) ?? config.claudeModel,
230
230
  chatId: ctx.chatId,
231
- // Claude 默认跳过权限确认,保持与之前 CLI 行为一致(全自动执行)
232
- ...(aiCommand === 'claude' ? { skipPermissions: true } : {}),
231
+ // 默认跳过权限确认,保持全自动执行
232
+ skipPermissions: true,
233
233
  ...(aiCommand === 'codex' && config.codexProxy ? { proxy: config.codexProxy } : {}),
234
234
  });
235
235
  return activeHandle;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wu529778790/open-im",
3
- "version": "1.7.1-beta.9",
3
+ "version": "1.8.1-beta.0",
4
4
  "description": "Multi-platform IM bridge for AI CLI tools (Claude, Codex, CodeBuddy)",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",