@wu529778790/open-im 1.7.1-beta.4 → 1.7.1-beta.6

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/dist/cli.js CHANGED
@@ -120,11 +120,11 @@ async function cmdDev() {
120
120
  }
121
121
  async function cmdDashboard() {
122
122
  // Start web config server in persistent mode (no timeout)
123
- const { startWebConfigServer, openWebConfigUrl } = await import("./config-web.js");
123
+ const { startWebConfigServer } = await import("./config-web.js");
124
124
  const server = await startWebConfigServer({ mode: "dev", cwd: process.cwd(), persistent: true });
125
- console.log(`\nDashboard: ${server.url}`);
125
+ const url = server.loginUrl ?? server.url;
126
+ console.log(`\nDashboard: ${url}`);
126
127
  console.log("Press Ctrl+C to close.\n");
127
- openWebConfigUrl();
128
128
  await server.waitForResult;
129
129
  }
130
130
  function showHelp(exitCode = 0) {
@@ -207,9 +207,6 @@ export declare const PAGE_TEXTS: {
207
207
  readonly codexProxy: "Codex 代理";
208
208
  readonly claudeTimeout: "Claude 超时(毫秒)";
209
209
  readonly claudeConfigPath: "配置文件位置";
210
- readonly claudeAuthToken: "ANTHROPIC_AUTH_TOKEN";
211
- readonly claudeBaseUrl: "ANTHROPIC_BASE_URL";
212
- readonly claudeModel: "ANTHROPIC_MODEL";
213
210
  readonly claudeProxy: "代理(可选)";
214
211
  readonly codexTimeout: "Codex 超时(毫秒)";
215
212
  readonly codebuddyTimeout: "CodeBuddy 超时(毫秒)";
@@ -207,9 +207,6 @@ export const PAGE_TEXTS = {
207
207
  codexProxy: "Codex \u4ee3\u7406",
208
208
  claudeTimeout: "Claude \u8d85\u65f6\uff08\u6beb\u79d2\uff09",
209
209
  claudeConfigPath: "\u914d\u7f6e\u6587\u4ef6\u4f4d\u7f6e",
210
- claudeAuthToken: "ANTHROPIC_AUTH_TOKEN",
211
- claudeBaseUrl: "ANTHROPIC_BASE_URL",
212
- claudeModel: "ANTHROPIC_MODEL",
213
210
  claudeProxy: "\u4ee3\u7406\uff08\u53ef\u9009\uff09",
214
211
  codexTimeout: "Codex \u8d85\u65f6\uff08\u6beb\u79d2\uff09",
215
212
  codebuddyTimeout: "CodeBuddy \u8d85\u65f6\uff08\u6beb\u79d2\uff09",
@@ -201,9 +201,6 @@ export const PAGE_SCRIPT = String.raw ` const platformDefinitions = [
201
201
  { id: "ai-claudeWorkDir-label", key: "workDir" },
202
202
  { id: "ai-claudeTimeoutMs-label", key: "claudeTimeout" },
203
203
  { id: "ai-claudeConfigPath-label", key: "claudeConfigPath" },
204
- { id: "ai-claudeAuthToken-label", key: "claudeAuthToken" },
205
- { id: "ai-claudeBaseUrl-label", key: "claudeBaseUrl" },
206
- { id: "ai-claudeModel-label", key: "claudeModel" },
207
204
  { id: "ai-claudeProxy-label", key: "claudeProxy" },
208
205
  { id: "ai-codexCliPath-label", key: "codexCli" },
209
206
  { id: "ai-codexTimeoutMs-label", key: "codexTimeout" },
@@ -405,15 +402,55 @@ export const PAGE_SCRIPT = String.raw ` const platformDefinitions = [
405
402
  return body;
406
403
  }
407
404
 
405
+ async function loadClaudeSettings() {
406
+ const textarea = document.getElementById("claudeSettingsEditor");
407
+ if (!(textarea instanceof HTMLTextAreaElement)) return;
408
+ try {
409
+ const data = await request("/api/claude/settings");
410
+ const raw = (data.contents || "").trim();
411
+ const target = raw || "{\n}\n";
412
+ try {
413
+ const parsed = JSON.parse(target);
414
+ textarea.value = JSON.stringify(parsed, null, 2) + "\n";
415
+ } catch {
416
+ // 如果后端返回的不是合法 JSON,就原样展示,方便用户手动修
417
+ textarea.value = target;
418
+ }
419
+ } catch (error) {
420
+ setMessage(error.message || String(error), "error");
421
+ }
422
+ }
423
+
424
+ async function saveClaudeSettings() {
425
+ const textarea = document.getElementById("claudeSettingsEditor");
426
+ if (!(textarea instanceof HTMLTextAreaElement)) return;
427
+ const edited = textarea.value;
428
+ // Validate JSON before sending
429
+ try {
430
+ const parsed = JSON.parse(edited);
431
+ textarea.value = JSON.stringify(parsed, null, 2) + "\n";
432
+ } catch (err) {
433
+ setMessage("Invalid JSON: " + (err && err.message ? err.message : String(err)), "error");
434
+ return;
435
+ }
436
+
437
+ try {
438
+ await request("/api/claude/settings", {
439
+ method: "POST",
440
+ body: JSON.stringify({ contents: edited }),
441
+ });
442
+ setMessage("Claude settings.json saved.", "success");
443
+ } catch (error) {
444
+ setMessage(error.message || String(error), "error");
445
+ }
446
+ }
447
+
408
448
  // Fill form with data
409
449
  const AI_FIELD_MAPPINGS = [
410
450
  { id: "ai-aiCommand", key: "aiCommand" },
411
451
  { id: "ai-claudeWorkDir", key: "claudeWorkDir" },
412
452
  { id: "ai-claudeTimeoutMs", key: "claudeTimeoutMs" },
413
453
  { id: "ai-claudeConfigPath", key: "claudeConfigPath" },
414
- { id: "ai-claudeAuthToken", key: "claudeAuthToken" },
415
- { id: "ai-claudeBaseUrl", key: "claudeBaseUrl" },
416
- { id: "ai-claudeModel", key: "claudeModel" },
417
454
  { id: "ai-claudeProxy", key: "claudeProxy" },
418
455
  { id: "ai-codexCliPath", key: "codexCliPath" },
419
456
  { id: "ai-codexTimeoutMs", key: "codexTimeoutMs" },
@@ -460,6 +497,7 @@ export const PAGE_SCRIPT = String.raw ` const platformDefinitions = [
460
497
  applyLanguage();
461
498
  const data = await request("/api/config");
462
499
  fill(data.payload, data.meta);
500
+ await loadClaudeSettings();
463
501
  // Initialize current AI tool panel from dropdown value
464
502
  currentAiToolPanel = el("ai-aiCommand")?.value || "claude";
465
503
  await refreshStatus();
@@ -499,6 +537,16 @@ export const PAGE_SCRIPT = String.raw ` const platformDefinitions = [
499
537
  }
500
538
  });
501
539
 
540
+ // Claude settings.json editor (advanced, inline & collapsible)
541
+ const claudeSettingsContainer = document.getElementById("claudeSettingsContainer");
542
+ if (claudeSettingsContainer && claudeSettingsContainer instanceof HTMLDetailsElement) {
543
+ claudeSettingsContainer.addEventListener("toggle", () => {
544
+ if (claudeSettingsContainer.open) {
545
+ void loadClaudeSettings();
546
+ }
547
+ });
548
+ }
549
+
502
550
  // AI tool switcher
503
551
  document.querySelectorAll(".tab[data-tool]").forEach((tab) => {
504
552
  tab.addEventListener("click", () => {
@@ -652,9 +700,6 @@ export const PAGE_SCRIPT = String.raw ` const platformDefinitions = [
652
700
  claudeWorkDir: getValue("ai-claudeWorkDir"),
653
701
  claudeTimeoutMs: getNumber("ai-claudeTimeoutMs"),
654
702
  claudeConfigPath: getValue("ai-claudeConfigPath"),
655
- claudeAuthToken: getValue("ai-claudeAuthToken"),
656
- claudeBaseUrl: getValue("ai-claudeBaseUrl"),
657
- claudeModel: getValue("ai-claudeModel"),
658
703
  claudeProxy: getValue("ai-claudeProxy"),
659
704
  codexTimeoutMs: getNumber("ai-codexTimeoutMs"),
660
705
  codebuddyTimeoutMs: getNumber("ai-codebuddyTimeoutMs"),
@@ -1128,19 +1128,20 @@ 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
- <label class="form-label" id="ai-claudeAuthToken-label">ANTHROPIC_AUTH_TOKEN</label>
1132
- <input id="ai-claudeAuthToken" class="form-input mono" type="password" />
1133
- <div class="form-hint" id="ai-claudeAuthToken-hint">Auth token for API access (optional, overrides env)</div>
1134
- </div>
1135
- <div class="form-group">
1136
- <label class="form-label" id="ai-claudeBaseUrl-label">ANTHROPIC_BASE_URL</label>
1137
- <input id="ai-claudeBaseUrl" class="form-input mono" type="text" />
1138
- <div class="form-hint" id="ai-claudeBaseUrl-hint">Custom API base URL (optional, overrides env)</div>
1139
- </div>
1140
- <div class="form-group">
1141
- <label class="form-label" id="ai-claudeModel-label">ANTHROPIC_MODEL</label>
1142
- <input id="ai-claudeModel" class="form-input mono" type="text" />
1143
- <div class="form-hint" id="ai-claudeModel-hint">Model name (optional, overrides env)</div>
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 会提示错误。
1135
+ </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
+ 折叠/展开以隐藏或查看完整配置。
1143
+ </div>
1144
+ </details>
1144
1145
  </div>
1145
1146
  </div>
1146
1147
 
@@ -2,6 +2,8 @@ import { spawn } from "node:child_process";
2
2
  import { createServer } from "node:http";
3
3
  import { URL } from "node:url";
4
4
  import { randomBytes } from "node:crypto";
5
+ import { readFileSync, writeFileSync, existsSync, mkdirSync } from "node:fs";
6
+ import { join, dirname } from "node:path";
5
7
  import { DWClient } from "dingtalk-stream";
6
8
  import { WEB_CONFIG_PORT } from "./constants.js";
7
9
  import { CONFIG_PATH, getClaudeConfigHome, loadClaudeSettingsEnv, saveClaudeSettingsEnv, loadConfig, loadFileConfig, saveFileConfig } from "./config.js";
@@ -11,6 +13,11 @@ import { initWeWork, stopWeWork } from "./wework/client.js";
11
13
  import { createLogger } from "./logger.js";
12
14
  const log = createLogger("ConfigWeb");
13
15
  const TEST_TIMEOUT_MS = 10000;
16
+ function getClaudeSettingsPath() {
17
+ const home = getClaudeConfigHome();
18
+ const baseDir = join(home, ".claude");
19
+ return join(baseDir, "settings.json");
20
+ }
14
21
  const pendingLogins = new Map();
15
22
  const activeSessions = new Map();
16
23
  function getWebConfigHost() {
@@ -705,6 +712,57 @@ export async function startWebConfigServer(options) {
705
712
  json(response, 200, getServiceStatus());
706
713
  return;
707
714
  }
715
+ if (request.method === "GET" && requestUrl.pathname === "/api/claude/settings") {
716
+ try {
717
+ const settingsPath = getClaudeSettingsPath();
718
+ let contents = "{}";
719
+ if (existsSync(settingsPath)) {
720
+ contents = readFileSync(settingsPath, "utf-8");
721
+ }
722
+ else {
723
+ // Try to synthesize from env if file doesn't exist yet
724
+ const env = loadClaudeSettingsEnv();
725
+ if (Object.keys(env).length > 0) {
726
+ contents = JSON.stringify({ env }, null, 2);
727
+ }
728
+ }
729
+ json(response, 200, { path: settingsPath, contents });
730
+ }
731
+ catch (error) {
732
+ json(response, 500, { error: error instanceof Error ? error.message : String(error) });
733
+ }
734
+ return;
735
+ }
736
+ if (request.method === "POST" && requestUrl.pathname === "/api/claude/settings") {
737
+ try {
738
+ const body = await readJson(request);
739
+ const raw = body.contents ?? "";
740
+ if (!raw.trim()) {
741
+ json(response, 400, { error: "contents is required" });
742
+ return;
743
+ }
744
+ let parsed;
745
+ try {
746
+ parsed = JSON.parse(raw);
747
+ }
748
+ catch (err) {
749
+ json(response, 400, { error: `Invalid JSON: ${err instanceof Error ? err.message : String(err)}` });
750
+ return;
751
+ }
752
+ const pretty = JSON.stringify(parsed, null, 2);
753
+ const settingsPath = getClaudeSettingsPath();
754
+ const dir = dirname(settingsPath);
755
+ if (!existsSync(dir)) {
756
+ mkdirSync(dir, { recursive: true });
757
+ }
758
+ writeFileSync(settingsPath, pretty, "utf-8");
759
+ json(response, 200, { message: "Claude settings.json saved.", path: settingsPath });
760
+ }
761
+ catch (error) {
762
+ json(response, 500, { error: error instanceof Error ? error.message : String(error) });
763
+ }
764
+ return;
765
+ }
708
766
  if (request.method === "GET" && requestUrl.pathname === "/api/health") {
709
767
  const file = loadFileConfig();
710
768
  const fileTelegram = file.platforms?.telegram;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wu529778790/open-im",
3
- "version": "1.7.1-beta.4",
3
+ "version": "1.7.1-beta.6",
4
4
  "description": "Multi-platform IM bridge for AI CLI tools (Claude, Codex, CodeBuddy)",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",