claude-codex-wechat 0.1.13 → 0.1.15

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.
@@ -2,7 +2,7 @@
2
2
  import { createRequire as __cjsCreateRequire } from 'node:module'; const require = __cjsCreateRequire(import.meta.url);
3
3
 
4
4
  // src/cli.ts
5
- import { existsSync as existsSync9, mkdirSync as mkdirSync6, readFileSync as readFileSync7, writeFileSync as writeFileSync6 } from "node:fs";
5
+ import { existsSync as existsSync10, mkdirSync as mkdirSync7, readFileSync as readFileSync8, writeFileSync as writeFileSync7 } from "node:fs";
6
6
  import { dirname as dirname12, join as join12 } from "node:path";
7
7
  import { fileURLToPath } from "node:url";
8
8
 
@@ -80,6 +80,7 @@ function normalizeBridgeConfig(raw, env, path) {
80
80
  return {
81
81
  wechat: normalizeWechatConfig(record.wechat, env),
82
82
  bridge: normalizeBridgeDefaultsConfig(record.bridge),
83
+ tunnel: normalizeTunnelConfig(record.tunnel),
83
84
  providers: normalizeProvidersConfig(record.providers, env)
84
85
  };
85
86
  }
@@ -103,6 +104,20 @@ function normalizeBridgeDefaultsConfig(raw) {
103
104
  ...defaultWorkspace ? { defaultWorkspace } : {}
104
105
  };
105
106
  }
107
+ function normalizeTunnelConfig(raw) {
108
+ const record = raw && typeof raw === "object" ? raw : {};
109
+ const enabled = record.enabled === true;
110
+ const relayRecord = record.relay && typeof record.relay === "object" ? record.relay : void 0;
111
+ const relay = relayRecord ? {
112
+ ...typeof relayRecord.serverUrl === "string" && relayRecord.serverUrl.trim() ? { serverUrl: relayRecord.serverUrl } : {},
113
+ ...typeof relayRecord.authToken === "string" && relayRecord.authToken.trim() ? { authToken: relayRecord.authToken } : {}
114
+ } : void 0;
115
+ if (!enabled && !relay) return void 0;
116
+ return {
117
+ enabled,
118
+ ...relay ? { relay } : {}
119
+ };
120
+ }
106
121
  function normalizeProviderCommand(raw, envFallback) {
107
122
  const record = raw && typeof raw === "object" ? raw : {};
108
123
  const command = typeof record.command === "string" && record.command ? record.command : typeof envFallback === "string" && envFallback ? envFallback : void 0;
@@ -125,6 +140,8 @@ function normalizeWechatConfig(raw, env) {
125
140
 
126
141
  // src/daemon/configPersistence.ts
127
142
  import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
143
+ import { randomBytes } from "node:crypto";
144
+ import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, writeFileSync } from "node:fs";
128
145
  import { dirname } from "node:path";
129
146
  async function persistWechatCredentialsToConfigFile(input) {
130
147
  const currentConfig = await readConfigFile(input.configPath);
@@ -152,18 +169,52 @@ async function deleteConfigFile(configPath) {
152
169
  }
153
170
  async function persistBridgeDefaultsToConfigFile(input) {
154
171
  const currentConfig = await readConfigFile(input.configPath);
172
+ const currentBridge = isRecord(currentConfig.bridge) ? currentConfig.bridge : void 0;
155
173
  const nextConfig = {
156
174
  ...currentConfig,
157
175
  bridge: {
158
- ...isRecord(currentConfig.bridge) ? currentConfig.bridge : {},
159
- defaultProvider: input.defaultProvider,
160
- defaultWorkspace: input.defaultWorkspace
161
- }
176
+ ...currentBridge ?? {},
177
+ ...input.defaultProvider ? { defaultProvider: input.defaultProvider } : {},
178
+ ...input.defaultWorkspace ? { defaultWorkspace: input.defaultWorkspace } : {}
179
+ },
180
+ ...input.tunnel ? {
181
+ tunnel: {
182
+ enabled: input.tunnel.enabled === true,
183
+ ...input.tunnel.relay ? {
184
+ relay: {
185
+ ...input.tunnel.relay.serverUrl ? { serverUrl: input.tunnel.relay.serverUrl } : {},
186
+ ...input.tunnel.relay.authToken ? { authToken: input.tunnel.relay.authToken } : {}
187
+ }
188
+ } : {}
189
+ }
190
+ } : {}
162
191
  };
163
192
  await mkdir(dirname(input.configPath), { recursive: true });
164
193
  await writeFile(input.configPath, `${JSON.stringify(nextConfig, null, 2)}
165
194
  `, "utf8");
166
195
  }
196
+ function ensureRelayAuthTokenSync(input) {
197
+ const currentConfig = readConfigFileSync(input.configPath);
198
+ const currentTunnel = isRecord(currentConfig.tunnel) ? currentConfig.tunnel : void 0;
199
+ const currentRelay = isRecord(currentTunnel?.relay) ? currentTunnel.relay : void 0;
200
+ const existing = typeof currentRelay?.authToken === "string" && currentRelay.authToken.trim() ? currentRelay.authToken.trim() : "";
201
+ if (existing) return existing;
202
+ const authToken = `clrt_${randomBytes(12).toString("hex")}`;
203
+ const nextConfig = {
204
+ ...currentConfig,
205
+ tunnel: {
206
+ ...currentTunnel ?? {},
207
+ relay: {
208
+ ...currentRelay ?? {},
209
+ authToken
210
+ }
211
+ }
212
+ };
213
+ mkdirSync(dirname(input.configPath), { recursive: true });
214
+ writeFileSync(input.configPath, `${JSON.stringify(nextConfig, null, 2)}
215
+ `, "utf8");
216
+ return authToken;
217
+ }
167
218
  async function persistProviderCommandsToConfigFile(input) {
168
219
  const currentConfig = await readConfigFile(input.configPath);
169
220
  const normalizedProviders = normalizeProvidersForPersistence(input.providers);
@@ -185,6 +236,17 @@ async function readConfigFile(path) {
185
236
  throw error;
186
237
  }
187
238
  }
239
+ function readConfigFileSync(path) {
240
+ try {
241
+ if (!existsSync2(path)) return {};
242
+ const raw = readFileSync2(path, "utf8");
243
+ const parsed = JSON.parse(raw);
244
+ return isRecord(parsed) ? parsed : {};
245
+ } catch (error) {
246
+ if (isMissingFileError(error)) return {};
247
+ throw error;
248
+ }
249
+ }
188
250
  function isRecord(value) {
189
251
  return typeof value === "object" && value !== null && !Array.isArray(value);
190
252
  }
@@ -202,7 +264,7 @@ function normalizeProvidersForPersistence(providers) {
202
264
  }
203
265
 
204
266
  // src/channels/weixin-direct/weixinStateStore.ts
205
- import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, writeFileSync } from "node:fs";
267
+ import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "node:fs";
206
268
  import { dirname as dirname2 } from "node:path";
207
269
  var PUSH_QUOTA_LIMIT = 10;
208
270
  var PUSH_WINDOW_MS = 24 * 60 * 60 * 1e3;
@@ -296,9 +358,9 @@ var FileWeixinStateStore = class {
296
358
  return this.readState().bridge?.weixinChannel ?? {};
297
359
  }
298
360
  readState() {
299
- if (!existsSync2(this.configPath)) return {};
361
+ if (!existsSync3(this.configPath)) return {};
300
362
  try {
301
- const raw = JSON.parse(readFileSync2(this.configPath, "utf8"));
363
+ const raw = JSON.parse(readFileSync3(this.configPath, "utf8"));
302
364
  return raw && typeof raw === "object" ? raw : {};
303
365
  } catch {
304
366
  return {};
@@ -309,8 +371,8 @@ var FileWeixinStateStore = class {
309
371
  ...state,
310
372
  bridge: { ...state.bridge ?? {}, weixinChannel: channel }
311
373
  };
312
- mkdirSync(dirname2(this.configPath), { recursive: true });
313
- writeFileSync(this.configPath, `${JSON.stringify(next, null, 2)}
374
+ mkdirSync2(dirname2(this.configPath), { recursive: true });
375
+ writeFileSync2(this.configPath, `${JSON.stringify(next, null, 2)}
314
376
  `, "utf8");
315
377
  }
316
378
  };
@@ -608,7 +670,7 @@ async function normalizeClaudeSessionFileForResume(sessionPath) {
608
670
  // src/providers/codex/nativeSessions.ts
609
671
  import Database from "better-sqlite3";
610
672
  import { readdir as readdir2, readFile as readFile3, stat as stat2 } from "node:fs/promises";
611
- import { existsSync as existsSync3 } from "node:fs";
673
+ import { existsSync as existsSync4 } from "node:fs";
612
674
  import { homedir as homedir3 } from "node:os";
613
675
  import { basename as basename2, join as join3 } from "node:path";
614
676
  function resolveCodexSessionsRoot(env = process.env) {
@@ -710,7 +772,7 @@ async function readCodexSessionIndex(env) {
710
772
  function readCodexThreadMetadata(env) {
711
773
  const dbPath = join3(env.CODEX_HOME || join3(env.HOME || homedir3(), ".codex"), "state_5.sqlite");
712
774
  const metadata = /* @__PURE__ */ new Map();
713
- if (!existsSync3(dbPath)) return metadata;
775
+ if (!existsSync4(dbPath)) return metadata;
714
776
  let db = null;
715
777
  try {
716
778
  db = new Database(dbPath, { readonly: true });
@@ -814,7 +876,7 @@ async function upsertCodexSessionIndexEntry(input, env = process.env) {
814
876
 
815
877
  // src/providers/codex/nativeThreads.ts
816
878
  import Database2 from "better-sqlite3";
817
- import { existsSync as existsSync4 } from "node:fs";
879
+ import { existsSync as existsSync5 } from "node:fs";
818
880
  import { readFile as readFile5, writeFile as writeFile4 } from "node:fs/promises";
819
881
  import { homedir as homedir5 } from "node:os";
820
882
  import { join as join5 } from "node:path";
@@ -824,7 +886,7 @@ function resolveCodexStateDbPath(env = process.env) {
824
886
  async function syncCodexThreadForResume(input) {
825
887
  const env = input.env ?? process.env;
826
888
  const dbPath = resolveCodexStateDbPath(env);
827
- if (!existsSync4(dbPath)) return false;
889
+ if (!existsSync5(dbPath)) return false;
828
890
  for (let attempt = 0; attempt < 50; attempt += 1) {
829
891
  const db = new Database2(dbPath);
830
892
  try {
@@ -863,7 +925,7 @@ async function syncCodexThreadForResume(input) {
863
925
  return false;
864
926
  }
865
927
  async function syncCodexRolloutSessionMeta(rolloutPath) {
866
- if (!existsSync4(rolloutPath)) return;
928
+ if (!existsSync5(rolloutPath)) return;
867
929
  const content = await readFile5(rolloutPath, "utf8");
868
930
  const lines = content.split(/\r?\n/);
869
931
  if (!lines[0]?.trim()) return;
@@ -1405,6 +1467,40 @@ async function resolveRecoverableProviderResumeHistorySynced(session) {
1405
1467
  });
1406
1468
  }
1407
1469
 
1470
+ // src/admin/tunnelRoutes.ts
1471
+ function registerTunnelRoutes(input) {
1472
+ const getStatus = async () => await input.tunnelManager.getStatus();
1473
+ input.app.get("/api/tunnel/status", getStatus);
1474
+ const start = async () => {
1475
+ input.defaults.tunnel.enabled = true;
1476
+ await persistBridgeDefaultsToConfigFile({
1477
+ configPath: input.configPath,
1478
+ tunnel: input.defaults.tunnel
1479
+ });
1480
+ return await input.tunnelManager.start();
1481
+ };
1482
+ input.app.post("/api/tunnel/start", start);
1483
+ const stop = async () => {
1484
+ input.defaults.tunnel.enabled = false;
1485
+ await persistBridgeDefaultsToConfigFile({
1486
+ configPath: input.configPath,
1487
+ tunnel: input.defaults.tunnel
1488
+ });
1489
+ return await input.tunnelManager.stop();
1490
+ };
1491
+ input.app.post("/api/tunnel/stop", stop);
1492
+ const setEnabled = async (request) => {
1493
+ const enabled = request.body?.enabled === true;
1494
+ input.defaults.tunnel.enabled = enabled;
1495
+ await persistBridgeDefaultsToConfigFile({
1496
+ configPath: input.configPath,
1497
+ tunnel: input.defaults.tunnel
1498
+ });
1499
+ return await input.tunnelManager.setEnabled(enabled);
1500
+ };
1501
+ input.app.post("/api/tunnel/settings", setEnabled);
1502
+ }
1503
+
1408
1504
  // src/admin/settingsRoutes.ts
1409
1505
  function registerSettingsRoutes(input) {
1410
1506
  input.app.get("/api/settings", async () => input.defaults);
@@ -1412,22 +1508,53 @@ function registerSettingsRoutes(input) {
1412
1508
  const current = { ...input.defaults };
1413
1509
  const next = normalizeSettings({
1414
1510
  ...current,
1415
- ...request.body
1511
+ ...request.body,
1512
+ tunnel: {
1513
+ ...current.tunnel,
1514
+ ...request.body.tunnel && typeof request.body.tunnel === "object" ? request.body.tunnel : {},
1515
+ relay: {
1516
+ ...current.tunnel?.relay ?? {},
1517
+ ...request.body.tunnel && typeof request.body.tunnel === "object" && "relay" in request.body.tunnel && request.body.tunnel.relay && typeof request.body.tunnel.relay === "object" ? request.body.tunnel.relay : {}
1518
+ }
1519
+ }
1416
1520
  }, current.defaultWorkspace);
1521
+ if (!next.tunnel.relay?.authToken) {
1522
+ const authToken = ensureRelayAuthTokenSync({ configPath: input.configPath });
1523
+ next.tunnel = {
1524
+ ...next.tunnel,
1525
+ relay: {
1526
+ ...next.tunnel.relay ?? {},
1527
+ authToken
1528
+ }
1529
+ };
1530
+ }
1417
1531
  input.defaults.defaultProvider = next.defaultProvider;
1418
1532
  input.defaults.defaultWorkspace = next.defaultWorkspace;
1533
+ input.defaults.tunnel = next.tunnel;
1419
1534
  await persistBridgeDefaultsToConfigFile({
1420
1535
  configPath: input.configPath,
1421
1536
  defaultProvider: next.defaultProvider,
1422
- defaultWorkspace: next.defaultWorkspace
1537
+ defaultWorkspace: next.defaultWorkspace,
1538
+ tunnel: next.tunnel
1423
1539
  });
1424
1540
  return { ok: true };
1425
1541
  });
1426
1542
  }
1427
1543
  function normalizeSettings(input, defaultWorkspace) {
1544
+ const tunnel = input.tunnel && typeof input.tunnel === "object" ? input.tunnel : {};
1545
+ const relay = tunnel.relay && typeof tunnel.relay === "object" ? tunnel.relay : {};
1428
1546
  return {
1429
1547
  defaultProvider: input.defaultProvider === "codex" ? "codex" : "claude-code",
1430
- defaultWorkspace: typeof input.defaultWorkspace === "string" && input.defaultWorkspace.trim() ? input.defaultWorkspace : defaultWorkspace
1548
+ defaultWorkspace: typeof input.defaultWorkspace === "string" && input.defaultWorkspace.trim() ? input.defaultWorkspace : defaultWorkspace,
1549
+ tunnel: {
1550
+ enabled: tunnel.enabled === true,
1551
+ ...typeof relay.serverUrl === "string" && relay.serverUrl.trim() || typeof relay.authToken === "string" && relay.authToken.trim() ? {
1552
+ relay: {
1553
+ ...typeof relay.serverUrl === "string" && relay.serverUrl.trim() ? { serverUrl: relay.serverUrl } : {},
1554
+ ...typeof relay.authToken === "string" && relay.authToken.trim() ? { authToken: relay.authToken } : {}
1555
+ }
1556
+ } : {}
1557
+ }
1431
1558
  };
1432
1559
  }
1433
1560
 
@@ -1967,7 +2094,7 @@ function chunkText(text, limit) {
1967
2094
  }
1968
2095
 
1969
2096
  // src/channels/weixin-direct/mediaDownloader.ts
1970
- import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "node:fs";
2097
+ import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync3 } from "node:fs";
1971
2098
  import { dirname as dirname6 } from "node:path";
1972
2099
 
1973
2100
  // src/channels/weixin-direct/mediaCrypto.ts
@@ -2031,8 +2158,8 @@ var WeixinMediaDownloader = class {
2031
2158
  } catch {
2032
2159
  return { ok: false, reason: "decrypt_failed" };
2033
2160
  }
2034
- mkdirSync2(dirname6(input.destPath), { recursive: true });
2035
- writeFileSync2(input.destPath, plaintext);
2161
+ mkdirSync3(dirname6(input.destPath), { recursive: true });
2162
+ writeFileSync3(input.destPath, plaintext);
2036
2163
  return { ok: true, localPath: input.destPath, bytes: plaintext.length };
2037
2164
  }
2038
2165
  };
@@ -3132,8 +3259,11 @@ var BRIDGE_COMMAND_HELP_GROUPS = [
3132
3259
  ]
3133
3260
  }
3134
3261
  ];
3135
- function buildBridgeCommandHelpMarkdown() {
3262
+ function buildBridgeCommandHelpMarkdown(input) {
3136
3263
  const lines = ["**\u53EF\u7528\u547D\u4EE4**", "", BRIDGE_COMMAND_HELP_INTRO];
3264
+ if (input?.publicUrl) {
3265
+ lines.push("", `**\u8BBF\u95EE\u5730\u5740**`, "", `[${input.publicUrl}](${input.publicUrl})`);
3266
+ }
3137
3267
  for (const group of BRIDGE_COMMAND_HELP_GROUPS) {
3138
3268
  lines.push("", `**${group.title}**${group.note ? `\uFF08${group.note}\uFF09` : ""}`);
3139
3269
  for (const entry of group.entries) {
@@ -3144,7 +3274,7 @@ function buildBridgeCommandHelpMarkdown() {
3144
3274
  }
3145
3275
 
3146
3276
  // src/session/currentConversationStore.ts
3147
- import { existsSync as existsSync5, mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "node:fs";
3277
+ import { existsSync as existsSync6, mkdirSync as mkdirSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync4 } from "node:fs";
3148
3278
  import { dirname as dirname7 } from "node:path";
3149
3279
  import { nanoid as nanoid2 } from "nanoid";
3150
3280
  var CurrentConversationStore = class {
@@ -3204,8 +3334,8 @@ var CurrentConversationStore = class {
3204
3334
  this.writeState(state);
3205
3335
  }
3206
3336
  readState() {
3207
- if (!existsSync5(this.configPath)) return {};
3208
- const raw = JSON.parse(readFileSync3(this.configPath, "utf8"));
3337
+ if (!existsSync6(this.configPath)) return {};
3338
+ const raw = JSON.parse(readFileSync4(this.configPath, "utf8"));
3209
3339
  return raw && typeof raw === "object" ? raw : {};
3210
3340
  }
3211
3341
  writeCurrent(record) {
@@ -3227,8 +3357,8 @@ var CurrentConversationStore = class {
3227
3357
  this.writeState(state);
3228
3358
  }
3229
3359
  writeState(state) {
3230
- mkdirSync3(dirname7(this.configPath), { recursive: true });
3231
- writeFileSync3(this.configPath, `${JSON.stringify(state, null, 2)}
3360
+ mkdirSync4(dirname7(this.configPath), { recursive: true });
3361
+ writeFileSync4(this.configPath, `${JSON.stringify(state, null, 2)}
3232
3362
  `, "utf8");
3233
3363
  }
3234
3364
  };
@@ -3518,10 +3648,11 @@ var MessageRouter = class _MessageRouter {
3518
3648
  }
3519
3649
  async handleCommand(chatId, user, command) {
3520
3650
  if (command.kind === "help") {
3651
+ const helpAddress = await this.options.getHelpAddress?.();
3521
3652
  await this.sendToChat({
3522
3653
  chatId,
3523
3654
  kind: "markdown",
3524
- text: buildBridgeCommandHelpMarkdown()
3655
+ text: buildBridgeCommandHelpMarkdown({ publicUrl: helpAddress })
3525
3656
  });
3526
3657
  return;
3527
3658
  }
@@ -3672,6 +3803,13 @@ var MessageRouter = class _MessageRouter {
3672
3803
  providerSessionId: session.providerSessionId,
3673
3804
  cwd: session.cwd
3674
3805
  });
3806
+ if (session.providerId === "codex") {
3807
+ await upsertCodexSessionIndexEntry({
3808
+ sessionId: session.providerSessionId,
3809
+ threadName: session.resumeTitle
3810
+ });
3811
+ return;
3812
+ }
3675
3813
  if (session.providerId === "claude-code") {
3676
3814
  await ensureClaudeSessionBridgeMetadata({
3677
3815
  sessionId: session.providerSessionId,
@@ -3733,7 +3871,7 @@ function composeInboundText(content) {
3733
3871
  }
3734
3872
 
3735
3873
  // src/storage/lastProviderSessionStore.ts
3736
- import { existsSync as existsSync6, mkdirSync as mkdirSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync4 } from "node:fs";
3874
+ import { existsSync as existsSync7, mkdirSync as mkdirSync5, readFileSync as readFileSync5, writeFileSync as writeFileSync5 } from "node:fs";
3737
3875
  import { dirname as dirname8 } from "node:path";
3738
3876
  var LastProviderSessionStore = class {
3739
3877
  constructor(configPath) {
@@ -3760,19 +3898,19 @@ var LastProviderSessionStore = class {
3760
3898
  return this.readState().bridge?.lastProviderSessions ?? {};
3761
3899
  }
3762
3900
  readState() {
3763
- if (!existsSync6(this.configPath)) return {};
3764
- const raw = JSON.parse(readFileSync4(this.configPath, "utf8"));
3901
+ if (!existsSync7(this.configPath)) return {};
3902
+ const raw = JSON.parse(readFileSync5(this.configPath, "utf8"));
3765
3903
  return raw && typeof raw === "object" ? raw : {};
3766
3904
  }
3767
3905
  writeState(state) {
3768
- mkdirSync4(dirname8(this.configPath), { recursive: true });
3769
- writeFileSync4(this.configPath, `${JSON.stringify(state, null, 2)}
3906
+ mkdirSync5(dirname8(this.configPath), { recursive: true });
3907
+ writeFileSync5(this.configPath, `${JSON.stringify(state, null, 2)}
3770
3908
  `, "utf8");
3771
3909
  }
3772
3910
  };
3773
3911
 
3774
3912
  // src/storage/runtimeUserStore.ts
3775
- import { mkdirSync as mkdirSync5, readFileSync as readFileSync5, writeFileSync as writeFileSync5, existsSync as existsSync7 } from "node:fs";
3913
+ import { mkdirSync as mkdirSync6, readFileSync as readFileSync6, writeFileSync as writeFileSync6, existsSync as existsSync8 } from "node:fs";
3776
3914
  import { dirname as dirname9 } from "node:path";
3777
3915
  import { nanoid as nanoid3 } from "nanoid";
3778
3916
  var RuntimeUserStore = class {
@@ -3833,13 +3971,13 @@ var RuntimeUserStore = class {
3833
3971
  return { ok: true };
3834
3972
  }
3835
3973
  readState() {
3836
- if (!existsSync7(this.configPath)) return {};
3837
- const raw = JSON.parse(readFileSync5(this.configPath, "utf8"));
3974
+ if (!existsSync8(this.configPath)) return {};
3975
+ const raw = JSON.parse(readFileSync6(this.configPath, "utf8"));
3838
3976
  return raw && typeof raw === "object" ? raw : {};
3839
3977
  }
3840
3978
  writeState(state) {
3841
- mkdirSync5(dirname9(this.configPath), { recursive: true });
3842
- writeFileSync5(this.configPath, `${JSON.stringify(state, null, 2)}
3979
+ mkdirSync6(dirname9(this.configPath), { recursive: true });
3980
+ writeFileSync6(this.configPath, `${JSON.stringify(state, null, 2)}
3843
3981
  `, "utf8");
3844
3982
  }
3845
3983
  };
@@ -3861,15 +3999,232 @@ var BridgeEventHub = class {
3861
3999
  }
3862
4000
  };
3863
4001
 
4002
+ // src/runtime/relayTunnelProvider.ts
4003
+ var RelayTunnelProvider = class {
4004
+ constructor(options) {
4005
+ this.options = options;
4006
+ }
4007
+ options;
4008
+ socket = null;
4009
+ status = {
4010
+ installed: true,
4011
+ enabled: false,
4012
+ running: false,
4013
+ status: "stopped"
4014
+ };
4015
+ static defaultCreateSocket(url) {
4016
+ const socket = new WebSocket(url);
4017
+ return {
4018
+ get readyState() {
4019
+ return socket.readyState;
4020
+ },
4021
+ send(payload) {
4022
+ socket.send(payload);
4023
+ },
4024
+ close() {
4025
+ socket.close();
4026
+ },
4027
+ on(event, listener) {
4028
+ socket.addEventListener(event, listener);
4029
+ }
4030
+ };
4031
+ }
4032
+ async getStatus() {
4033
+ return { ...this.status };
4034
+ }
4035
+ async start() {
4036
+ this.status = {
4037
+ ...this.status,
4038
+ enabled: true,
4039
+ status: "starting"
4040
+ };
4041
+ const socket = this.options.createSocket(this.options.serverUrl);
4042
+ this.socket = socket;
4043
+ return await new Promise((resolve2, reject) => {
4044
+ let settled = false;
4045
+ const finishResolve = (value) => {
4046
+ if (settled) return;
4047
+ settled = true;
4048
+ resolve2(value);
4049
+ };
4050
+ const finishReject = (error) => {
4051
+ if (settled) return;
4052
+ settled = true;
4053
+ reject(error);
4054
+ };
4055
+ socket.on("open", () => {
4056
+ socket.send(JSON.stringify({
4057
+ type: "register",
4058
+ clientVersion: "0.1.0",
4059
+ targetBaseUrl: this.options.targetBaseUrl,
4060
+ authToken: this.options.authToken
4061
+ }));
4062
+ });
4063
+ socket.on("message", (raw) => {
4064
+ const payload = typeof raw === "string" ? JSON.parse(raw) : typeof raw?.data === "string" ? JSON.parse(raw.data) : raw;
4065
+ if (payload?.type === "error") {
4066
+ const message = typeof payload?.error === "string" && payload.error ? payload.error : "relay_error";
4067
+ this.status = {
4068
+ ...this.status,
4069
+ running: false,
4070
+ status: "error",
4071
+ error: message
4072
+ };
4073
+ finishReject(new Error(message));
4074
+ return;
4075
+ }
4076
+ if (payload?.type === "registered") {
4077
+ const publicUrl = typeof payload?.token === "string" ? `${derivePublicBaseUrl(this.options.serverUrl)}/${payload.token}` : void 0;
4078
+ this.status = {
4079
+ installed: true,
4080
+ enabled: true,
4081
+ running: true,
4082
+ status: "running",
4083
+ ...publicUrl ? { publicUrl } : {}
4084
+ };
4085
+ finishResolve({ ...this.status });
4086
+ return;
4087
+ }
4088
+ if (payload?.type === "request") {
4089
+ void this.handleRequest(payload);
4090
+ }
4091
+ });
4092
+ socket.on("close", () => {
4093
+ this.status = {
4094
+ ...this.status,
4095
+ running: false,
4096
+ status: "error",
4097
+ error: "relay_disconnected"
4098
+ };
4099
+ finishReject(new Error("relay_disconnected"));
4100
+ });
4101
+ socket.on("error", (error) => {
4102
+ const message = error instanceof Error ? error.message : String(error);
4103
+ this.status = {
4104
+ ...this.status,
4105
+ running: false,
4106
+ status: "error",
4107
+ error: message
4108
+ };
4109
+ finishReject(error);
4110
+ });
4111
+ });
4112
+ }
4113
+ async stop() {
4114
+ this.socket?.close();
4115
+ this.socket = null;
4116
+ this.status = {
4117
+ installed: true,
4118
+ enabled: false,
4119
+ running: false,
4120
+ status: "stopped"
4121
+ };
4122
+ return { ...this.status };
4123
+ }
4124
+ async setEnabled(enabled) {
4125
+ return enabled ? await this.start() : await this.stop();
4126
+ }
4127
+ async handleRequest(payload) {
4128
+ const fetchImpl = this.options.fetchImpl ?? fetch;
4129
+ const response = await fetchImpl(`${this.options.targetBaseUrl}${payload.path}`, {
4130
+ method: payload.method,
4131
+ headers: payload.headers,
4132
+ body: payload.bodyBase64 ? Buffer.from(payload.bodyBase64, "base64") : void 0
4133
+ });
4134
+ const bodyBuffer = Buffer.from(await response.arrayBuffer());
4135
+ this.socket?.send(JSON.stringify({
4136
+ type: "response",
4137
+ requestId: payload.requestId,
4138
+ status: response.status,
4139
+ headers: Object.fromEntries(response.headers.entries()),
4140
+ bodyBase64: bodyBuffer.toString("base64")
4141
+ }));
4142
+ }
4143
+ };
4144
+ function derivePublicBaseUrl(serverUrl) {
4145
+ return serverUrl.replace(/^ws:\/\//, "http://").replace(/^wss:\/\//, "https://").replace(/\/agent\/?$/, "").replace(/\/+$/, "");
4146
+ }
4147
+
4148
+ // src/runtime/relayTunnelRouter.ts
4149
+ var RelayTunnelRouter = class {
4150
+ constructor(options) {
4151
+ this.options = options;
4152
+ }
4153
+ options;
4154
+ relayProvider = null;
4155
+ relayKey = "";
4156
+ async getStatus() {
4157
+ const provider = this.getOrCreateRelayProvider();
4158
+ if (!provider) return this.notConfiguredStatus();
4159
+ return await provider.getStatus();
4160
+ }
4161
+ async start() {
4162
+ const provider = this.getOrCreateRelayProvider();
4163
+ if (!provider) return this.notConfiguredStatus();
4164
+ return await provider.start();
4165
+ }
4166
+ async stop() {
4167
+ const provider = this.getOrCreateRelayProvider();
4168
+ if (!provider) return this.notConfiguredStatus();
4169
+ return await provider.stop();
4170
+ }
4171
+ async setEnabled(enabled) {
4172
+ const provider = this.getOrCreateRelayProvider();
4173
+ if (!provider) return this.notConfiguredStatus();
4174
+ return await provider.setEnabled(enabled);
4175
+ }
4176
+ getOrCreateRelayProvider() {
4177
+ const relay = this.options.defaults.tunnel.relay;
4178
+ if (!relay?.serverUrl || !relay.authToken) return null;
4179
+ const key = `${relay.serverUrl}|${relay.authToken}|${this.options.bridgePort}`;
4180
+ if (this.relayProvider && this.relayKey === key) return this.relayProvider;
4181
+ this.relayKey = key;
4182
+ this.relayProvider = new RelayTunnelProvider({
4183
+ serverUrl: relay.serverUrl,
4184
+ authToken: relay.authToken,
4185
+ targetBaseUrl: `http://127.0.0.1:${this.options.bridgePort}`,
4186
+ createSocket: RelayTunnelProvider.defaultCreateSocket
4187
+ });
4188
+ return this.relayProvider;
4189
+ }
4190
+ notConfiguredStatus() {
4191
+ return {
4192
+ installed: false,
4193
+ enabled: false,
4194
+ running: false,
4195
+ status: "error",
4196
+ error: "relay_not_configured"
4197
+ };
4198
+ }
4199
+ };
4200
+
3864
4201
  // src/daemon/server.ts
3865
4202
  function createDaemonServer(options = {}) {
3866
4203
  const app = Fastify({ logger: true });
3867
4204
  const events = new BridgeEventHub();
4205
+ const configPath = options.configPath ?? process.env.BRIDGE_CONFIG ?? join9(mkdtempSync(join9(tmpdir2(), "claude-codex-wechat-")), "config.json");
4206
+ const persistedConfig = loadBridgeConfig(configPath);
3868
4207
  const bridgeDefaults = {
3869
- defaultProvider: options.bridgeDefaults?.defaultProvider ?? "claude-code",
3870
- defaultWorkspace: options.bridgeDefaults?.defaultWorkspace ?? process.cwd()
4208
+ defaultProvider: options.bridgeDefaults?.defaultProvider ?? persistedConfig.bridge?.defaultProvider ?? "claude-code",
4209
+ defaultWorkspace: options.bridgeDefaults?.defaultWorkspace ?? persistedConfig.bridge?.defaultWorkspace ?? process.cwd(),
4210
+ tunnel: options.bridgeDefaults?.tunnel ?? {
4211
+ enabled: persistedConfig.tunnel?.enabled === true,
4212
+ ...persistedConfig.tunnel?.relay ? { relay: persistedConfig.tunnel.relay } : {}
4213
+ }
3871
4214
  };
3872
- const configPath = options.configPath ?? process.env.BRIDGE_CONFIG ?? join9(mkdtempSync(join9(tmpdir2(), "claude-codex-wechat-")), "config.json");
4215
+ const existingRelayAuthToken = bridgeDefaults.tunnel.relay?.authToken?.trim();
4216
+ if (existingRelayAuthToken) {
4217
+ bridgeDefaults.tunnel.relay = {
4218
+ ...bridgeDefaults.tunnel.relay ?? {},
4219
+ authToken: existingRelayAuthToken
4220
+ };
4221
+ } else {
4222
+ const authToken = ensureRelayAuthTokenSync({ configPath });
4223
+ bridgeDefaults.tunnel.relay = {
4224
+ ...bridgeDefaults.tunnel.relay ?? {},
4225
+ authToken
4226
+ };
4227
+ }
3873
4228
  const conversation = new CurrentConversationStore(configPath, {
3874
4229
  defaultCwd: bridgeDefaults.defaultWorkspace,
3875
4230
  defaultProviderId: bridgeDefaults.defaultProvider
@@ -3911,6 +4266,10 @@ function createDaemonServer(options = {}) {
3911
4266
  await managedWechatChannel.sendMessage({ chatId, kind: msg.kind, text: msg.text });
3912
4267
  }
3913
4268
  }) : void 0;
4269
+ const tunnelProvider = options.tunnelProvider ?? new RelayTunnelRouter({
4270
+ bridgePort: Number(process.env.BRIDGE_PORT ?? 8787),
4271
+ defaults: bridgeDefaults
4272
+ });
3914
4273
  const messageRouter = channel ? new MessageRouter({
3915
4274
  channel,
3916
4275
  providers: providerAdapters,
@@ -3963,7 +4322,14 @@ function createDaemonServer(options = {}) {
3963
4322
  },
3964
4323
  lastProviderSessions,
3965
4324
  events,
3966
- defaults: bridgeDefaults
4325
+ defaults: bridgeDefaults,
4326
+ getHelpAddress: async () => {
4327
+ const tunnel = await tunnelProvider?.getStatus().catch(() => null);
4328
+ if (tunnel?.running && tunnel.publicUrl) return tunnel.publicUrl;
4329
+ const lan = listLanIpv4Addresses()[0];
4330
+ if (lan) return `http://${lan}:${process.env.BRIDGE_PORT ?? 8787}`;
4331
+ return `http://127.0.0.1:${process.env.BRIDGE_PORT ?? 8787}`;
4332
+ }
3967
4333
  }) : void 0;
3968
4334
  if (channel && messageRouter) {
3969
4335
  channel.onMessage(async (message) => {
@@ -3992,10 +4358,24 @@ function createDaemonServer(options = {}) {
3992
4358
  defaults: bridgeDefaults,
3993
4359
  configPath
3994
4360
  });
4361
+ if (tunnelProvider) {
4362
+ registerTunnelRoutes({
4363
+ app,
4364
+ configPath,
4365
+ defaults: { tunnel: bridgeDefaults.tunnel },
4366
+ tunnelManager: tunnelProvider
4367
+ });
4368
+ }
3995
4369
  registerFsBrowseRoutes({ app });
3996
4370
  app.get("/api/status", async () => ({
3997
4371
  ok: true,
3998
- sessions: conversation.getCurrent() ? [conversation.getCurrent()] : []
4372
+ sessions: conversation.getCurrent() ? [conversation.getCurrent()] : [],
4373
+ preferredLocalUrl: (() => {
4374
+ const lan = listLanIpv4Addresses()[0];
4375
+ const port = process.env.BRIDGE_PORT ?? 8787;
4376
+ if (lan) return `http://${lan}:${port}`;
4377
+ return `http://127.0.0.1:${port}`;
4378
+ })()
3999
4379
  }));
4000
4380
  app.get("/api/providers/status", async () => providers.getStatus());
4001
4381
  app.post("/api/bridge-events", async (request, reply) => {
@@ -4029,7 +4409,7 @@ function createDaemonServer(options = {}) {
4029
4409
  app.addHook("onClose", async () => {
4030
4410
  await channel?.stop();
4031
4411
  });
4032
- return { app, conversation, sessions: conversation, events, activeUserStore };
4412
+ return { app, conversation, sessions: conversation, events, activeUserStore, tunnelProvider };
4033
4413
  }
4034
4414
 
4035
4415
  // src/daemon/bootstrap.ts
@@ -4043,24 +4423,35 @@ async function startDaemon(options) {
4043
4423
  configPath,
4044
4424
  providers: providerCommands
4045
4425
  });
4046
- const { app } = createDaemonServer({
4426
+ const bridgeDefaults = {
4427
+ defaultProvider: config.bridge?.defaultProvider ?? "claude-code",
4428
+ defaultWorkspace: config.bridge?.defaultWorkspace ?? process.cwd(),
4429
+ tunnel: {
4430
+ enabled: config.tunnel?.enabled === true,
4431
+ ...config.tunnel?.relay ? { relay: config.tunnel.relay } : {}
4432
+ }
4433
+ };
4434
+ const { app, tunnelProvider: resolvedTunnelProvider } = createDaemonServer({
4047
4435
  wechat: config.wechat,
4048
4436
  providerCommands,
4049
- bridgeDefaults: {
4050
- defaultProvider: config.bridge?.defaultProvider ?? "claude-code",
4051
- defaultWorkspace: config.bridge?.defaultWorkspace ?? process.cwd()
4052
- },
4053
- configPath
4437
+ bridgeDefaults,
4438
+ configPath,
4439
+ ...options.tunnelProvider ? { tunnelProvider: options.tunnelProvider } : {}
4054
4440
  });
4055
4441
  await options.attachFrontend(app);
4056
4442
  await app.listen({ host, port });
4443
+ const address = app.server.address();
4444
+ const actualPort = typeof address === "object" && address ? address.port : port;
4445
+ if (config.tunnel?.enabled === true) {
4446
+ await resolvedTunnelProvider?.start().catch(() => void 0);
4447
+ }
4057
4448
  console.log("claude-codex-wechat listening:");
4058
- console.log(` Local: http://127.0.0.1:${port}`);
4449
+ console.log(` Local: http://127.0.0.1:${actualPort}`);
4059
4450
  for (const ip of listLanIpv4Addresses()) {
4060
- console.log(` Network: http://${ip}:${port}`);
4451
+ console.log(` Network: http://${ip}:${actualPort}`);
4061
4452
  }
4062
4453
  console.log(`config path: ${configPath}`);
4063
- return { app, port, host, configPath };
4454
+ return { app, port: actualPort, host, configPath };
4064
4455
  }
4065
4456
  function listLanIpv4Addresses() {
4066
4457
  return Object.values(networkInterfaces()).flat().filter((iface) => Boolean(iface)).filter((iface) => iface.family === "IPv4" && !iface.internal).map((iface) => iface.address);
@@ -4076,7 +4467,7 @@ async function resolveProviderCommands(providers) {
4076
4467
  }
4077
4468
 
4078
4469
  // src/daemon/staticFrontend.ts
4079
- import { readFileSync as readFileSync6 } from "node:fs";
4470
+ import { readFileSync as readFileSync7 } from "node:fs";
4080
4471
  import { join as join10 } from "node:path";
4081
4472
  import fastifyStatic from "@fastify/static";
4082
4473
  function attachStaticFrontend(webRoot2) {
@@ -4085,7 +4476,7 @@ function attachStaticFrontend(webRoot2) {
4085
4476
  root: webRoot2,
4086
4477
  wildcard: false
4087
4478
  });
4088
- const indexHtml = readFileSync6(join10(webRoot2, "index.html"), "utf8");
4479
+ const indexHtml = readFileSync7(join10(webRoot2, "index.html"), "utf8");
4089
4480
  app.setNotFoundHandler((request, reply) => {
4090
4481
  if (request.url.startsWith("/api")) {
4091
4482
  reply.code(404).send({ error: "not_found" });
@@ -4097,7 +4488,7 @@ function attachStaticFrontend(webRoot2) {
4097
4488
  }
4098
4489
 
4099
4490
  // src/daemon/service.ts
4100
- import { existsSync as existsSync8, statSync } from "node:fs";
4491
+ import { existsSync as existsSync9, statSync } from "node:fs";
4101
4492
  import { mkdir as mkdir4, readFile as readFile6, rm as rm2, writeFile as writeFile5 } from "node:fs/promises";
4102
4493
  import { homedir as homedir8 } from "node:os";
4103
4494
  import { dirname as dirname11, join as join11 } from "node:path";
@@ -4147,13 +4538,13 @@ async function startService(context) {
4147
4538
  async function stopService(context) {
4148
4539
  if (process.platform === "darwin") {
4149
4540
  const spec = buildLaunchdSpec(context);
4150
- if (!existsSync8(spec.plistPath)) return await readServiceStatus(context);
4541
+ if (!existsSync9(spec.plistPath)) return await readServiceStatus(context);
4151
4542
  await runLaunchctl(["bootout", `gui/${process.getuid?.() ?? 0}`, spec.plistPath]).catch(() => void 0);
4152
4543
  return await readServiceStatus(context);
4153
4544
  }
4154
4545
  if (process.platform === "linux") {
4155
4546
  const spec = buildSystemdUserSpec(context);
4156
- if (!existsSync8(spec.unitPath)) return await readServiceStatus(context);
4547
+ if (!existsSync9(spec.unitPath)) return await readServiceStatus(context);
4157
4548
  await runSystemctl(["--user", "stop", spec.unitName]).catch(() => void 0);
4158
4549
  return await readServiceStatus(context);
4159
4550
  }
@@ -4162,7 +4553,7 @@ async function stopService(context) {
4162
4553
  async function uninstallService(context) {
4163
4554
  if (process.platform === "darwin") {
4164
4555
  const spec = buildLaunchdSpec(context);
4165
- if (existsSync8(spec.plistPath)) {
4556
+ if (existsSync9(spec.plistPath)) {
4166
4557
  await runLaunchctl(["bootout", `gui/${process.getuid?.() ?? 0}`, spec.plistPath]).catch(() => void 0);
4167
4558
  await rm2(spec.plistPath, { force: true });
4168
4559
  }
@@ -4170,7 +4561,7 @@ async function uninstallService(context) {
4170
4561
  }
4171
4562
  if (process.platform === "linux") {
4172
4563
  const spec = buildSystemdUserSpec(context);
4173
- if (existsSync8(spec.unitPath)) {
4564
+ if (existsSync9(spec.unitPath)) {
4174
4565
  await runSystemctl(["--user", "disable", "--now", spec.unitName]).catch(() => void 0);
4175
4566
  await rm2(spec.unitPath, { force: true });
4176
4567
  await runSystemctl(["--user", "daemon-reload"]).catch(() => void 0);
@@ -4213,7 +4604,7 @@ async function tailServiceLogs(context) {
4213
4604
  async function readServiceStatus(context) {
4214
4605
  if (process.platform === "darwin") {
4215
4606
  const spec = buildLaunchdSpec(context);
4216
- const installed = existsSync8(spec.plistPath);
4607
+ const installed = existsSync9(spec.plistPath);
4217
4608
  const running = installed ? await runLaunchctl(["print", `gui/${process.getuid?.() ?? 0}/${spec.label}`]).then(() => true, () => false) : false;
4218
4609
  return {
4219
4610
  manager: "launchd",
@@ -4227,7 +4618,7 @@ async function readServiceStatus(context) {
4227
4618
  }
4228
4619
  if (process.platform === "linux") {
4229
4620
  const spec = buildSystemdUserSpec(context);
4230
- const installed = existsSync8(spec.unitPath);
4621
+ const installed = existsSync9(spec.unitPath);
4231
4622
  const running = installed ? await runSystemctl(["--user", "is-active", "--quiet", spec.unitName]).then(() => true, () => false) : false;
4232
4623
  return {
4233
4624
  manager: "systemd-user",
@@ -4363,16 +4754,16 @@ async function runSystemctl(args) {
4363
4754
  await execFileAsync("systemctl", args);
4364
4755
  }
4365
4756
  async function ensureFileExists(path, errorCode) {
4366
- if (!existsSync8(path)) throw new Error(errorCode);
4757
+ if (!existsSync9(path)) throw new Error(errorCode);
4367
4758
  await readFile6(path, "utf8");
4368
4759
  }
4369
4760
  async function tailFile(path, lines) {
4370
- if (!existsSync8(path)) return "";
4761
+ if (!existsSync9(path)) return "";
4371
4762
  const content = await readFile6(path, "utf8");
4372
4763
  return content.split(/\r?\n/).slice(-lines).join("\n").trim();
4373
4764
  }
4374
4765
  async function tailFiles(paths) {
4375
- const readers = paths.filter((path) => existsSync8(path)).map((path) => {
4766
+ const readers = paths.filter((path) => existsSync9(path)).map((path) => {
4376
4767
  const rl = createInterface({
4377
4768
  input: createReadStream(path, { encoding: "utf8", start: Math.max(0, fileSizeHint(path) - 8192) }),
4378
4769
  crlfDelay: Infinity
@@ -4386,7 +4777,7 @@ async function tailFiles(paths) {
4386
4777
  if (process.platform !== "linux" && process.platform !== "darwin") {
4387
4778
  return;
4388
4779
  }
4389
- const child = spawn6("tail", ["-f", ...paths.filter((path) => existsSync8(path))], { stdio: ["ignore", "pipe", "inherit"] });
4780
+ const child = spawn6("tail", ["-f", ...paths.filter((path) => existsSync9(path))], { stdio: ["ignore", "pipe", "inherit"] });
4390
4781
  child.stdout.on("data", (chunk) => {
4391
4782
  process.stdout.write(String(chunk));
4392
4783
  });
@@ -4459,7 +4850,7 @@ async function main() {
4459
4850
  }
4460
4851
  }
4461
4852
  async function cmdStart() {
4462
- if (!existsSync9(webRoot)) {
4853
+ if (!existsSync10(webRoot)) {
4463
4854
  console.error(`\u627E\u4E0D\u5230\u524D\u7AEF\u6784\u5EFA\u4EA7\u7269: ${webRoot}`);
4464
4855
  console.error("\u8BF7\u91CD\u65B0\u5B89\u88C5\u5B8C\u6574\u7684 npm \u5305\u3002");
4465
4856
  process.exitCode = 1;
@@ -4489,7 +4880,7 @@ async function cmdTail() {
4489
4880
  await tailServiceLogs(createServiceContext());
4490
4881
  }
4491
4882
  async function cmdDaemon() {
4492
- if (!existsSync9(webRoot)) {
4883
+ if (!existsSync10(webRoot)) {
4493
4884
  console.error(`\u627E\u4E0D\u5230\u524D\u7AEF\u6784\u5EFA\u4EA7\u7269: ${webRoot}`);
4494
4885
  process.exitCode = 1;
4495
4886
  return;
@@ -4501,8 +4892,8 @@ async function cmdDaemon() {
4501
4892
  function cmdInit() {
4502
4893
  const configPath = process.env.BRIDGE_CONFIG ?? defaultConfigPath();
4503
4894
  const dir = dirname12(configPath);
4504
- mkdirSync6(dir, { recursive: true });
4505
- if (existsSync9(configPath)) {
4895
+ mkdirSync7(dir, { recursive: true });
4896
+ if (existsSync10(configPath)) {
4506
4897
  console.log(`\u914D\u7F6E\u5DF2\u5B58\u5728\uFF0C\u672A\u8986\u76D6: ${configPath}`);
4507
4898
  return;
4508
4899
  }
@@ -4519,7 +4910,7 @@ function cmdInit() {
4519
4910
  defaultWorkspace: process.cwd()
4520
4911
  }
4521
4912
  };
4522
- writeFileSync6(configPath, `${JSON.stringify(template, null, 2)}
4913
+ writeFileSync7(configPath, `${JSON.stringify(template, null, 2)}
4523
4914
  `, "utf8");
4524
4915
  console.log(`\u5DF2\u521B\u5EFA\u914D\u7F6E: ${configPath}`);
4525
4916
  console.log("\u8BF7\u586B\u5165\u5FAE\u4FE1 token / accountId \u540E\u8FD0\u884C: claude-codex-wechat start");
@@ -4527,13 +4918,13 @@ function cmdInit() {
4527
4918
  async function cmdDoctor() {
4528
4919
  const configPath = process.env.BRIDGE_CONFIG ?? defaultConfigPath();
4529
4920
  console.log("claude-codex-wechat doctor\n");
4530
- report("\u914D\u7F6E\u6587\u4EF6", existsSync9(configPath) ? configPath : `\u7F3A\u5931 (${configPath})\uFF0C\u8FD0\u884C init \u521B\u5EFA`);
4531
- report("\u524D\u7AEF\u4EA7\u7269", existsSync9(webRoot) ? webRoot : `\u7F3A\u5931 (${webRoot})`);
4921
+ report("\u914D\u7F6E\u6587\u4EF6", existsSync10(configPath) ? configPath : `\u7F3A\u5931 (${configPath})\uFF0C\u8FD0\u884C init \u521B\u5EFA`);
4922
+ report("\u524D\u7AEF\u4EA7\u7269", existsSync10(webRoot) ? webRoot : `\u7F3A\u5931 (${webRoot})`);
4532
4923
  const claude = await findExecutable("claude");
4533
4924
  report("claude \u53EF\u6267\u884C", claude ?? "\u672A\u627E\u5230\uFF0C\u9700\u5148\u5B89\u88C5\u5E76\u767B\u5F55 Claude Code");
4534
4925
  const codex = await findExecutable("codex");
4535
4926
  report("codex \u53EF\u6267\u884C", codex ?? "\u672A\u627E\u5230\uFF08\u4EC5\u4F7F\u7528 Claude \u65F6\u53EF\u5FFD\u7565\uFF09");
4536
- if (existsSync9(configPath)) {
4927
+ if (existsSync10(configPath)) {
4537
4928
  const config = loadBridgeConfig(configPath);
4538
4929
  const wechat = config.wechat;
4539
4930
  report("\u5FAE\u4FE1\u542F\u7528", wechat?.enabled ? "\u662F" : "\u5426");
@@ -4543,12 +4934,12 @@ async function cmdDoctor() {
4543
4934
  }
4544
4935
  function cmdPrintConfig() {
4545
4936
  const configPath = process.env.BRIDGE_CONFIG ?? defaultConfigPath();
4546
- if (!existsSync9(configPath)) {
4937
+ if (!existsSync10(configPath)) {
4547
4938
  console.error(`\u914D\u7F6E\u4E0D\u5B58\u5728: ${configPath}\uFF08\u8FD0\u884C init \u521B\u5EFA\uFF09`);
4548
4939
  process.exitCode = 1;
4549
4940
  return;
4550
4941
  }
4551
- console.log(readFileSync7(configPath, "utf8"));
4942
+ console.log(readFileSync8(configPath, "utf8"));
4552
4943
  }
4553
4944
  async function cmdUninstall() {
4554
4945
  const status = await uninstallService(createServiceContext());