@wu529778790/open-im 1.8.0 → 1.8.1-beta.1

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,16 @@ 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 claudeSettingsLabel: "~/.claude/settings.json";
124
+ readonly configJson: "~/.open-im/config.json";
125
+ readonly configJsonHint: "Edit the configuration JSON. Changes will be saved when you click \"Save Config\" in the Service section.";
126
+ readonly formatJson: "Format";
127
+ readonly resetJson: "Reset";
128
+ readonly saveBtn: "Save";
129
+ readonly jsonValid: "Valid JSON";
130
+ readonly jsonInvalid: "Invalid JSON: {error}";
121
131
  };
122
132
  readonly zh: {
123
133
  readonly pageTitle: "open-im 本地控制台";
@@ -232,5 +242,15 @@ export declare const PAGE_TEXTS: {
232
242
  readonly saveOk: "配置已保存。";
233
243
  readonly startOk: "桥接已启动。";
234
244
  readonly stopOk: "桥接已停止。";
245
+ readonly configEditorTitle: "JSON 配置编辑器";
246
+ readonly configEditorHint: "直接编辑 ~/.open-im/config.json";
247
+ readonly claudeSettingsLabel: "~/.claude/settings.json";
248
+ readonly configJson: "~/.open-im/config.json";
249
+ readonly configJsonHint: "编辑配置 JSON。点击服务区的“保存配置”按钮后侘存更改。";
250
+ readonly formatJson: "格式化";
251
+ readonly resetJson: "重置";
252
+ readonly saveBtn: "保存";
253
+ readonly jsonValid: "JSON 有效";
254
+ readonly jsonInvalid: "JSON 无效:{error}";
235
255
  };
236
256
  };
@@ -118,6 +118,16 @@ 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
+ claudeSettingsLabel: "~/.claude/settings.json",
124
+ configJson: "~/.open-im/config.json",
125
+ configJsonHint: "Edit the configuration JSON. Changes will be saved when you click \"Save Config\" in the Service section.",
126
+ formatJson: "Format",
127
+ resetJson: "Reset",
128
+ saveBtn: "Save",
129
+ jsonValid: "Valid JSON",
130
+ jsonInvalid: "Invalid JSON: {error}",
121
131
  },
122
132
  zh: {
123
133
  pageTitle: "open-im \u672c\u5730\u63a7\u5236\u53f0",
@@ -232,5 +242,15 @@ export const PAGE_TEXTS = {
232
242
  saveOk: "\u914d\u7f6e\u5df2\u4fdd\u5b58\u3002",
233
243
  startOk: "\u6865\u63a5\u5df2\u542f\u52a8\u3002",
234
244
  stopOk: "\u6865\u63a5\u5df2\u505c\u6b62\u3002",
245
+ configEditorTitle: "JSON \u914d\u7f6e\u7f16\u8f91\u5668",
246
+ configEditorHint: "\u76f4\u63a5\u7f16\u8f91 ~/.open-im/config.json",
247
+ claudeSettingsLabel: "~/.claude/settings.json",
248
+ configJson: "~/.open-im/config.json",
249
+ configJsonHint: "\u7f16\u8f91\u914d\u7f6e JSON\u3002\u70b9\u51fb\u670d\u52a1\u533a\u7684\u201c\u4fdd\u5b58\u914d\u7f6e\u201d\u6309\u94ae\u540e\u4f98\u5b58\u66f4\u6539\u3002",
250
+ formatJson: "\u683c\u5f0f\u5316",
251
+ resetJson: "\u91cd\u7f6e",
252
+ saveBtn: "\u4fdd\u5b58",
253
+ jsonValid: "JSON \u6709\u6548",
254
+ jsonInvalid: "JSON \u65e0\u6548\uff1a{error}",
235
255
  }
236
256
  };
@@ -159,6 +159,12 @@ export const PAGE_SCRIPT = String.raw ` const platformDefinitions = [
159
159
  { id: "statConfiguredLabel", key: "statConfiguredLabel" },
160
160
  { id: "statEnabledLabel", key: "statEnabledLabel" },
161
161
  { id: "statServiceLabel", key: "statServiceLabel" },
162
+ { id: "openImConfigSummary", key: "configJson" },
163
+ { id: "claudeSettingsSummary", key: "claudeSettingsLabel" },
164
+ { id: "formatJsonButtonText", key: "formatJson" },
165
+ { id: "resetJsonButtonText", key: "resetJson" },
166
+ { id: "saveClaudeSettingsBtnText", key: "saveBtn" },
167
+ { id: "saveOpenImConfigBtnText", key: "saveBtn" },
162
168
  ],
163
169
  platformLabels: {
164
170
  enabled: { suffix: "-label", key: "enabled" },
@@ -445,6 +451,70 @@ export const PAGE_SCRIPT = String.raw ` const platformDefinitions = [
445
451
  }
446
452
  }
447
453
 
454
+ // Open-im Config Editor functions
455
+ let originalConfigJson = "";
456
+
457
+ async function loadOpenImConfig() {
458
+ const textarea = document.getElementById("configJson");
459
+ if (!(textarea instanceof HTMLTextAreaElement)) return;
460
+ try {
461
+ const data = await request("/api/config/file");
462
+ const raw = (data.contents || "").trim();
463
+ originalConfigJson = raw;
464
+ try {
465
+ const parsed = JSON.parse(raw);
466
+ textarea.value = JSON.stringify(parsed, null, 2) + "\n";
467
+ } catch {
468
+ textarea.value = raw;
469
+ }
470
+ validateJson();
471
+ } catch (error) {
472
+ showJsonValidationMessage(error.message || String(error), "error");
473
+ }
474
+ }
475
+
476
+ function validateJson() {
477
+ const textarea = document.getElementById("configJson");
478
+ const message = document.getElementById("jsonValidationMessage");
479
+ if (!(textarea instanceof HTMLTextAreaElement) || !message) return;
480
+
481
+ const json = textarea.value;
482
+ try {
483
+ JSON.parse(json);
484
+ showJsonValidationMessage("Valid JSON", "success");
485
+ } catch (err) {
486
+ showJsonValidationMessage("Invalid JSON: " + (err.message || String(err)), "error");
487
+ }
488
+ }
489
+
490
+ function showJsonValidationMessage(text, type) {
491
+ const message = document.getElementById("jsonValidationMessage");
492
+ if (!message) return;
493
+ message.textContent = text;
494
+ message.className = "message";
495
+ if (type) message.classList.add("message-" + type);
496
+ message.classList.remove("hidden");
497
+ }
498
+
499
+ function formatJson() {
500
+ const textarea = document.getElementById("configJson");
501
+ if (!(textarea instanceof HTMLTextAreaElement)) return;
502
+ try {
503
+ const parsed = JSON.parse(textarea.value);
504
+ textarea.value = JSON.stringify(parsed, null, 2) + "\n";
505
+ validateJson();
506
+ } catch (err) {
507
+ showJsonValidationMessage("Cannot format: Invalid JSON", "error");
508
+ }
509
+ }
510
+
511
+ function resetJson() {
512
+ const textarea = document.getElementById("configJson");
513
+ if (!(textarea instanceof HTMLTextAreaElement)) return;
514
+ textarea.value = originalConfigJson;
515
+ validateJson();
516
+ }
517
+
448
518
  // Fill form with data
449
519
  const AI_FIELD_MAPPINGS = [
450
520
  { id: "ai-aiCommand", key: "aiCommand" },
@@ -475,6 +545,9 @@ export const PAGE_SCRIPT = String.raw ` const platformDefinitions = [
475
545
  }
476
546
  });
477
547
 
548
+ // Load JSON editor content
549
+ void loadOpenImConfig();
550
+
478
551
  updateVisualState();
479
552
  }
480
553
 
@@ -537,16 +610,37 @@ export const PAGE_SCRIPT = String.raw ` const platformDefinitions = [
537
610
  }
538
611
  });
539
612
 
540
- // Claude settings.json editor (advanced, inline & collapsible)
613
+ // Open-im config.json: load when expanded
614
+ const openImConfigContainer = document.getElementById("openImConfigContainer");
615
+ if (openImConfigContainer && openImConfigContainer instanceof HTMLDetailsElement) {
616
+ openImConfigContainer.addEventListener("toggle", () => {
617
+ if (openImConfigContainer.open) void loadOpenImConfig();
618
+ });
619
+ }
620
+ // Claude settings.json: load when expanded
541
621
  const claudeSettingsContainer = document.getElementById("claudeSettingsContainer");
542
622
  if (claudeSettingsContainer && claudeSettingsContainer instanceof HTMLDetailsElement) {
543
623
  claudeSettingsContainer.addEventListener("toggle", () => {
544
- if (claudeSettingsContainer.open) {
545
- void loadClaudeSettings();
546
- }
624
+ if (claudeSettingsContainer.open) void loadClaudeSettings();
547
625
  });
548
626
  }
549
627
 
628
+ el("saveClaudeSettingsBtn").onclick = async () => {
629
+ try {
630
+ await saveClaudeSettings();
631
+ } catch (e) {
632
+ setMessage(e && e.message ? e.message : String(e), "error");
633
+ }
634
+ };
635
+ el("saveOpenImConfigBtn").onclick = async () => {
636
+ try {
637
+ await saveOpenImConfig();
638
+ setMessage(t("saveOk"), "success");
639
+ } catch (e) {
640
+ setMessage(e && e.message ? e.message : String(e), "error");
641
+ }
642
+ };
643
+
550
644
  // AI tool switcher
551
645
  document.querySelectorAll(".tab[data-tool]").forEach((tab) => {
552
646
  tab.addEventListener("click", () => {
@@ -558,6 +652,24 @@ export const PAGE_SCRIPT = String.raw ` const platformDefinitions = [
558
652
  });
559
653
  });
560
654
 
655
+ // Config editor JSON textarea
656
+ const configJsonTextarea = document.getElementById("configJson");
657
+ if (configJsonTextarea) {
658
+ configJsonTextarea.addEventListener("input", validateJson);
659
+ }
660
+
661
+ // Config editor buttons
662
+ const formatJsonButton = document.getElementById("formatJsonButton");
663
+ if (formatJsonButton) {
664
+ formatJsonButton.addEventListener("click", formatJson);
665
+ }
666
+
667
+ const resetJsonButton = document.getElementById("resetJsonButton");
668
+ if (resetJsonButton) {
669
+ resetJsonButton.addEventListener("click", resetJson);
670
+ }
671
+
672
+
561
673
  // Navigation
562
674
  el("navOverviewBtn").onclick = () => scrollToSection("dashboardSection", "navOverviewBtn");
563
675
  el("navPlatformsBtn").onclick = () => scrollToSection("configSection", "navPlatformsBtn");
@@ -617,6 +729,9 @@ export const PAGE_SCRIPT = String.raw ` const platformDefinitions = [
617
729
  async function save() {
618
730
  setBusy(true);
619
731
  try {
732
+ // First save JSON editor content if changed
733
+ await saveOpenImConfig();
734
+ // Then save form data
620
735
  await request("/api/config/save?final=1", { method: "POST", body: JSON.stringify(payload()) });
621
736
  setMessage(t("saveOk"), "success");
622
737
  } catch (error) {
@@ -626,6 +741,32 @@ export const PAGE_SCRIPT = String.raw ` const platformDefinitions = [
626
741
  }
627
742
  }
628
743
 
744
+ async function saveOpenImConfig() {
745
+ const textarea = document.getElementById("configJson");
746
+ if (!(textarea instanceof HTMLTextAreaElement)) return;
747
+ const json = textarea.value.trim();
748
+ if (!json) return;
749
+
750
+ // Validate JSON
751
+ try {
752
+ JSON.parse(json);
753
+ } catch (err) {
754
+ showJsonValidationMessage("Invalid JSON: " + (err.message || String(err)), "error");
755
+ throw new Error("Invalid JSON: " + (err.message || String(err)));
756
+ }
757
+
758
+ try {
759
+ await request("/api/config/file", {
760
+ method: "POST",
761
+ body: JSON.stringify({ contents: json }),
762
+ });
763
+ originalConfigJson = json;
764
+ } catch (error) {
765
+ showJsonValidationMessage(error.message || String(error), "error");
766
+ throw error;
767
+ }
768
+ }
769
+
629
770
  async function startService() {
630
771
  setBusy(true);
631
772
  try {
@@ -1128,18 +1128,31 @@ export const PAGE_HTML_PREFIX = String.raw `<!doctype html>
1128
1128
  <div class="form-hint" id="ai-claudeConfigPath-hint">Environment variables are saved to ~/.claude/settings.json</div>
1129
1129
  </div>
1130
1130
  <div class="form-group">
1131
- <details id="claudeSettingsContainer">
1132
- <summary class="form-label" style="cursor: pointer;">Edit ~/.claude/settings.json (advanced)</summary>
1133
- <div class="form-hint" style="margin-top: 8px; margin-bottom: 8px;">
1134
- JSON will be auto-formatted; invalid JSON 会提示错误。
1131
+ <details id="openImConfigContainer">
1132
+ <summary class="form-label" style="cursor: pointer;" id="openImConfigSummary">~/.open-im/config.json</summary>
1133
+ <div style="margin-top: 12px;">
1134
+ <div style="display:flex; justify-content:flex-end; gap:8px; margin-bottom:8px;">
1135
+ <button type="button" id="formatJsonButton" class="btn btn-sm btn-ghost"><span id="formatJsonButtonText">Format</span></button>
1136
+ <button type="button" id="resetJsonButton" class="btn btn-sm btn-ghost"><span id="resetJsonButtonText">Reset</span></button>
1137
+ </div>
1138
+ <textarea id="configJson" class="form-input mono" rows="16" style="font-family:monospace; font-size:13px; line-height:1.5; min-height:320px; resize:vertical; white-space:pre;" spellcheck="false"></textarea>
1139
+ <div id="jsonValidationMessage" class="message hidden" style="margin-top:6px;" aria-live="polite"></div>
1140
+ <div style="margin-top: 8px;">
1141
+ <button type="button" id="saveOpenImConfigBtn" class="btn btn-secondary btn-sm"><span id="saveOpenImConfigBtnText">Save</span></button>
1142
+ </div>
1135
1143
  </div>
1136
- <textarea
1137
- id="claudeSettingsEditor"
1138
- class="form-input mono"
1139
- style="min-height: 200px; white-space: pre; font-family: var(--font-mono);"
1140
- ></textarea>
1141
- <div class="form-hint" style="margin-top: 4px;">
1142
- 折叠/展开以隐藏或查看完整配置。
1144
+ </details>
1145
+ <details id="claudeSettingsContainer" style="margin-top: 12px;">
1146
+ <summary class="form-label" style="cursor: pointer;" id="claudeSettingsSummary">~/.claude/settings.json</summary>
1147
+ <div style="margin-top: 12px;">
1148
+ <textarea
1149
+ id="claudeSettingsEditor"
1150
+ class="form-input mono"
1151
+ style="min-height: 180px; white-space: pre; font-family: var(--font-mono);"
1152
+ ></textarea>
1153
+ <div style="margin-top: 8px;">
1154
+ <button type="button" id="saveClaudeSettingsBtn" class="btn btn-secondary btn-sm"><span id="saveClaudeSettingsBtnText">Save</span></button>
1155
+ </div>
1143
1156
  </div>
1144
1157
  </details>
1145
1158
  </div>
@@ -1196,6 +1209,7 @@ export const PAGE_HTML_PREFIX = String.raw `<!doctype html>
1196
1209
  </div>
1197
1210
  </div>
1198
1211
  </section>
1212
+
1199
1213
  </div>
1200
1214
  </main>
1201
1215
  </div>
@@ -673,6 +673,47 @@ export async function startWebConfigServer(options) {
673
673
  });
674
674
  return;
675
675
  }
676
+ if (request.method === "GET" && requestUrl.pathname === "/api/config/file") {
677
+ try {
678
+ let contents = "{}";
679
+ if (existsSync(CONFIG_PATH)) {
680
+ contents = readFileSync(CONFIG_PATH, "utf-8");
681
+ }
682
+ json(response, 200, { path: CONFIG_PATH, contents });
683
+ }
684
+ catch (error) {
685
+ json(response, 500, { error: error instanceof Error ? error.message : String(error) });
686
+ }
687
+ return;
688
+ }
689
+ if (request.method === "POST" && requestUrl.pathname === "/api/config/file") {
690
+ try {
691
+ const body = await readJson(request);
692
+ const raw = body.contents ?? "";
693
+ if (!raw.trim()) {
694
+ json(response, 400, { error: "contents is required" });
695
+ return;
696
+ }
697
+ try {
698
+ JSON.parse(raw);
699
+ }
700
+ catch {
701
+ json(response, 400, { error: "Invalid JSON" });
702
+ return;
703
+ }
704
+ const dir = dirname(CONFIG_PATH);
705
+ if (!existsSync(dir)) {
706
+ mkdirSync(dir, { recursive: true });
707
+ }
708
+ writeFileSync(CONFIG_PATH, raw, "utf-8");
709
+ loadConfig();
710
+ json(response, 200, { message: "Config file saved.", path: CONFIG_PATH });
711
+ }
712
+ catch (error) {
713
+ json(response, 500, { error: error instanceof Error ? error.message : String(error) });
714
+ }
715
+ return;
716
+ }
676
717
  if (request.method === "POST" && requestUrl.pathname === "/api/config/validate") {
677
718
  try {
678
719
  const body = await readJson(request);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wu529778790/open-im",
3
- "version": "1.8.0",
3
+ "version": "1.8.1-beta.1",
4
4
  "description": "Multi-platform IM bridge for AI CLI tools (Claude, Codex, CodeBuddy)",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",