claude-codex-wechat 0.1.13 → 0.1.16

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,17 @@ 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 relayRecord = record.relay && typeof record.relay === "object" ? record.relay : void 0;
110
+ const relay = {
111
+ serverUrl: typeof relayRecord?.serverUrl === "string" && relayRecord.serverUrl.trim() ? relayRecord.serverUrl : "wss://wechat.style520.com/agent",
112
+ ...typeof relayRecord?.authToken === "string" && relayRecord.authToken.trim() ? { authToken: relayRecord.authToken } : {}
113
+ };
114
+ return {
115
+ relay
116
+ };
117
+ }
106
118
  function normalizeProviderCommand(raw, envFallback) {
107
119
  const record = raw && typeof raw === "object" ? raw : {};
108
120
  const command = typeof record.command === "string" && record.command ? record.command : typeof envFallback === "string" && envFallback ? envFallback : void 0;
@@ -125,6 +137,8 @@ function normalizeWechatConfig(raw, env) {
125
137
 
126
138
  // src/daemon/configPersistence.ts
127
139
  import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
140
+ import { randomBytes } from "node:crypto";
141
+ import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, writeFileSync } from "node:fs";
128
142
  import { dirname } from "node:path";
129
143
  async function persistWechatCredentialsToConfigFile(input) {
130
144
  const currentConfig = await readConfigFile(input.configPath);
@@ -152,18 +166,51 @@ async function deleteConfigFile(configPath) {
152
166
  }
153
167
  async function persistBridgeDefaultsToConfigFile(input) {
154
168
  const currentConfig = await readConfigFile(input.configPath);
169
+ const currentBridge = isRecord(currentConfig.bridge) ? currentConfig.bridge : void 0;
155
170
  const nextConfig = {
156
171
  ...currentConfig,
157
172
  bridge: {
158
- ...isRecord(currentConfig.bridge) ? currentConfig.bridge : {},
159
- defaultProvider: input.defaultProvider,
160
- defaultWorkspace: input.defaultWorkspace
161
- }
173
+ ...currentBridge ?? {},
174
+ ...input.defaultProvider ? { defaultProvider: input.defaultProvider } : {},
175
+ ...input.defaultWorkspace ? { defaultWorkspace: input.defaultWorkspace } : {}
176
+ },
177
+ ...input.tunnel ? {
178
+ tunnel: {
179
+ ...input.tunnel.relay ? {
180
+ relay: {
181
+ ...input.tunnel.relay.serverUrl ? { serverUrl: input.tunnel.relay.serverUrl } : {},
182
+ ...input.tunnel.relay.authToken ? { authToken: input.tunnel.relay.authToken } : {}
183
+ }
184
+ } : {}
185
+ }
186
+ } : {}
162
187
  };
163
188
  await mkdir(dirname(input.configPath), { recursive: true });
164
189
  await writeFile(input.configPath, `${JSON.stringify(nextConfig, null, 2)}
165
190
  `, "utf8");
166
191
  }
192
+ function ensureRelayAuthTokenSync(input) {
193
+ const currentConfig = readConfigFileSync(input.configPath);
194
+ const currentTunnel = isRecord(currentConfig.tunnel) ? currentConfig.tunnel : void 0;
195
+ const currentRelay = isRecord(currentTunnel?.relay) ? currentTunnel.relay : void 0;
196
+ const existing = typeof currentRelay?.authToken === "string" && currentRelay.authToken.trim() ? currentRelay.authToken.trim() : "";
197
+ if (existing) return existing;
198
+ const authToken = `clrt_${randomBytes(12).toString("hex")}`;
199
+ const nextConfig = {
200
+ ...currentConfig,
201
+ tunnel: {
202
+ ...currentTunnel ?? {},
203
+ relay: {
204
+ ...currentRelay ?? {},
205
+ authToken
206
+ }
207
+ }
208
+ };
209
+ mkdirSync(dirname(input.configPath), { recursive: true });
210
+ writeFileSync(input.configPath, `${JSON.stringify(nextConfig, null, 2)}
211
+ `, "utf8");
212
+ return authToken;
213
+ }
167
214
  async function persistProviderCommandsToConfigFile(input) {
168
215
  const currentConfig = await readConfigFile(input.configPath);
169
216
  const normalizedProviders = normalizeProvidersForPersistence(input.providers);
@@ -185,6 +232,17 @@ async function readConfigFile(path) {
185
232
  throw error;
186
233
  }
187
234
  }
235
+ function readConfigFileSync(path) {
236
+ try {
237
+ if (!existsSync2(path)) return {};
238
+ const raw = readFileSync2(path, "utf8");
239
+ const parsed = JSON.parse(raw);
240
+ return isRecord(parsed) ? parsed : {};
241
+ } catch (error) {
242
+ if (isMissingFileError(error)) return {};
243
+ throw error;
244
+ }
245
+ }
188
246
  function isRecord(value) {
189
247
  return typeof value === "object" && value !== null && !Array.isArray(value);
190
248
  }
@@ -202,7 +260,7 @@ function normalizeProvidersForPersistence(providers) {
202
260
  }
203
261
 
204
262
  // src/channels/weixin-direct/weixinStateStore.ts
205
- import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, writeFileSync } from "node:fs";
263
+ import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "node:fs";
206
264
  import { dirname as dirname2 } from "node:path";
207
265
  var PUSH_QUOTA_LIMIT = 10;
208
266
  var PUSH_WINDOW_MS = 24 * 60 * 60 * 1e3;
@@ -296,9 +354,9 @@ var FileWeixinStateStore = class {
296
354
  return this.readState().bridge?.weixinChannel ?? {};
297
355
  }
298
356
  readState() {
299
- if (!existsSync2(this.configPath)) return {};
357
+ if (!existsSync3(this.configPath)) return {};
300
358
  try {
301
- const raw = JSON.parse(readFileSync2(this.configPath, "utf8"));
359
+ const raw = JSON.parse(readFileSync3(this.configPath, "utf8"));
302
360
  return raw && typeof raw === "object" ? raw : {};
303
361
  } catch {
304
362
  return {};
@@ -309,8 +367,8 @@ var FileWeixinStateStore = class {
309
367
  ...state,
310
368
  bridge: { ...state.bridge ?? {}, weixinChannel: channel }
311
369
  };
312
- mkdirSync(dirname2(this.configPath), { recursive: true });
313
- writeFileSync(this.configPath, `${JSON.stringify(next, null, 2)}
370
+ mkdirSync2(dirname2(this.configPath), { recursive: true });
371
+ writeFileSync2(this.configPath, `${JSON.stringify(next, null, 2)}
314
372
  `, "utf8");
315
373
  }
316
374
  };
@@ -608,7 +666,7 @@ async function normalizeClaudeSessionFileForResume(sessionPath) {
608
666
  // src/providers/codex/nativeSessions.ts
609
667
  import Database from "better-sqlite3";
610
668
  import { readdir as readdir2, readFile as readFile3, stat as stat2 } from "node:fs/promises";
611
- import { existsSync as existsSync3 } from "node:fs";
669
+ import { existsSync as existsSync4 } from "node:fs";
612
670
  import { homedir as homedir3 } from "node:os";
613
671
  import { basename as basename2, join as join3 } from "node:path";
614
672
  function resolveCodexSessionsRoot(env = process.env) {
@@ -710,7 +768,7 @@ async function readCodexSessionIndex(env) {
710
768
  function readCodexThreadMetadata(env) {
711
769
  const dbPath = join3(env.CODEX_HOME || join3(env.HOME || homedir3(), ".codex"), "state_5.sqlite");
712
770
  const metadata = /* @__PURE__ */ new Map();
713
- if (!existsSync3(dbPath)) return metadata;
771
+ if (!existsSync4(dbPath)) return metadata;
714
772
  let db = null;
715
773
  try {
716
774
  db = new Database(dbPath, { readonly: true });
@@ -814,7 +872,7 @@ async function upsertCodexSessionIndexEntry(input, env = process.env) {
814
872
 
815
873
  // src/providers/codex/nativeThreads.ts
816
874
  import Database2 from "better-sqlite3";
817
- import { existsSync as existsSync4 } from "node:fs";
875
+ import { existsSync as existsSync5 } from "node:fs";
818
876
  import { readFile as readFile5, writeFile as writeFile4 } from "node:fs/promises";
819
877
  import { homedir as homedir5 } from "node:os";
820
878
  import { join as join5 } from "node:path";
@@ -824,7 +882,7 @@ function resolveCodexStateDbPath(env = process.env) {
824
882
  async function syncCodexThreadForResume(input) {
825
883
  const env = input.env ?? process.env;
826
884
  const dbPath = resolveCodexStateDbPath(env);
827
- if (!existsSync4(dbPath)) return false;
885
+ if (!existsSync5(dbPath)) return false;
828
886
  for (let attempt = 0; attempt < 50; attempt += 1) {
829
887
  const db = new Database2(dbPath);
830
888
  try {
@@ -863,7 +921,7 @@ async function syncCodexThreadForResume(input) {
863
921
  return false;
864
922
  }
865
923
  async function syncCodexRolloutSessionMeta(rolloutPath) {
866
- if (!existsSync4(rolloutPath)) return;
924
+ if (!existsSync5(rolloutPath)) return;
867
925
  const content = await readFile5(rolloutPath, "utf8");
868
926
  const lines = content.split(/\r?\n/);
869
927
  if (!lines[0]?.trim()) return;
@@ -1405,6 +1463,24 @@ async function resolveRecoverableProviderResumeHistorySynced(session) {
1405
1463
  });
1406
1464
  }
1407
1465
 
1466
+ // src/admin/tunnelRoutes.ts
1467
+ function registerTunnelRoutes(input) {
1468
+ const getStatus = async () => await input.tunnelManager.getStatus();
1469
+ input.app.get("/api/tunnel/status", getStatus);
1470
+ const start = async () => {
1471
+ await persistBridgeDefaultsToConfigFile({
1472
+ configPath: input.configPath,
1473
+ tunnel: input.defaults.tunnel
1474
+ });
1475
+ return await input.tunnelManager.start();
1476
+ };
1477
+ input.app.post("/api/tunnel/start", start);
1478
+ const stop = async () => {
1479
+ return await input.tunnelManager.stop();
1480
+ };
1481
+ input.app.post("/api/tunnel/stop", stop);
1482
+ }
1483
+
1408
1484
  // src/admin/settingsRoutes.ts
1409
1485
  function registerSettingsRoutes(input) {
1410
1486
  input.app.get("/api/settings", async () => input.defaults);
@@ -1412,22 +1488,52 @@ function registerSettingsRoutes(input) {
1412
1488
  const current = { ...input.defaults };
1413
1489
  const next = normalizeSettings({
1414
1490
  ...current,
1415
- ...request.body
1491
+ ...request.body,
1492
+ tunnel: {
1493
+ ...current.tunnel,
1494
+ ...request.body.tunnel && typeof request.body.tunnel === "object" ? request.body.tunnel : {},
1495
+ relay: {
1496
+ ...current.tunnel?.relay ?? {},
1497
+ ...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 : {}
1498
+ }
1499
+ }
1416
1500
  }, current.defaultWorkspace);
1501
+ if (!next.tunnel.relay?.authToken) {
1502
+ const authToken = ensureRelayAuthTokenSync({ configPath: input.configPath });
1503
+ next.tunnel = {
1504
+ ...next.tunnel,
1505
+ relay: {
1506
+ ...next.tunnel.relay ?? {},
1507
+ authToken
1508
+ }
1509
+ };
1510
+ }
1417
1511
  input.defaults.defaultProvider = next.defaultProvider;
1418
1512
  input.defaults.defaultWorkspace = next.defaultWorkspace;
1513
+ input.defaults.tunnel = next.tunnel;
1419
1514
  await persistBridgeDefaultsToConfigFile({
1420
1515
  configPath: input.configPath,
1421
1516
  defaultProvider: next.defaultProvider,
1422
- defaultWorkspace: next.defaultWorkspace
1517
+ defaultWorkspace: next.defaultWorkspace,
1518
+ tunnel: next.tunnel
1423
1519
  });
1424
1520
  return { ok: true };
1425
1521
  });
1426
1522
  }
1427
1523
  function normalizeSettings(input, defaultWorkspace) {
1524
+ const tunnel = input.tunnel && typeof input.tunnel === "object" ? input.tunnel : {};
1525
+ const relay = tunnel.relay && typeof tunnel.relay === "object" ? tunnel.relay : {};
1428
1526
  return {
1429
1527
  defaultProvider: input.defaultProvider === "codex" ? "codex" : "claude-code",
1430
- defaultWorkspace: typeof input.defaultWorkspace === "string" && input.defaultWorkspace.trim() ? input.defaultWorkspace : defaultWorkspace
1528
+ defaultWorkspace: typeof input.defaultWorkspace === "string" && input.defaultWorkspace.trim() ? input.defaultWorkspace : defaultWorkspace,
1529
+ tunnel: {
1530
+ ...typeof relay.serverUrl === "string" && relay.serverUrl.trim() || typeof relay.authToken === "string" && relay.authToken.trim() || !("relay" in tunnel) ? {
1531
+ relay: {
1532
+ serverUrl: typeof relay.serverUrl === "string" && relay.serverUrl.trim() ? relay.serverUrl : "wss://wechat.style520.com/agent",
1533
+ ...typeof relay.authToken === "string" && relay.authToken.trim() ? { authToken: relay.authToken } : {}
1534
+ }
1535
+ } : {}
1536
+ }
1431
1537
  };
1432
1538
  }
1433
1539
 
@@ -1967,7 +2073,7 @@ function chunkText(text, limit) {
1967
2073
  }
1968
2074
 
1969
2075
  // src/channels/weixin-direct/mediaDownloader.ts
1970
- import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "node:fs";
2076
+ import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync3 } from "node:fs";
1971
2077
  import { dirname as dirname6 } from "node:path";
1972
2078
 
1973
2079
  // src/channels/weixin-direct/mediaCrypto.ts
@@ -2031,8 +2137,8 @@ var WeixinMediaDownloader = class {
2031
2137
  } catch {
2032
2138
  return { ok: false, reason: "decrypt_failed" };
2033
2139
  }
2034
- mkdirSync2(dirname6(input.destPath), { recursive: true });
2035
- writeFileSync2(input.destPath, plaintext);
2140
+ mkdirSync3(dirname6(input.destPath), { recursive: true });
2141
+ writeFileSync3(input.destPath, plaintext);
2036
2142
  return { ok: true, localPath: input.destPath, bytes: plaintext.length };
2037
2143
  }
2038
2144
  };
@@ -3114,9 +3220,9 @@ var BRIDGE_COMMAND_HELP_GROUPS = [
3114
3220
  entries: [
3115
3221
  { command: "/help", desc: "\u663E\u793A\u672C\u5E2E\u52A9" },
3116
3222
  { command: "/status", desc: "\u67E5\u770B\u5F53\u524D\u4F1A\u8BDD\uFF08provider\u3001\u5DE5\u4F5C\u76EE\u5F55\u3001\u72B6\u6001\uFF09" },
3117
- { command: "/new", desc: "\u65B0\u5EFA\u9ED8\u8BA4 provider \u4F1A\u8BDD" },
3223
+ { command: "/new", desc: "\u6309\u5F53\u524D provider \u65B0\u5EFA\u4F1A\u8BDD" },
3118
3224
  { command: "/new claude|codex", desc: "\u65B0\u5EFA\u6307\u5B9A provider \u4F1A\u8BDD" },
3119
- { command: "/new <\u76EE\u5F55>", desc: "\u5728\u6307\u5B9A\u76EE\u5F55\u65B0\u5EFA\u9ED8\u8BA4 provider \u4F1A\u8BDD" },
3225
+ { command: "/new <\u76EE\u5F55>", desc: "\u5728\u6307\u5B9A\u76EE\u5F55\u6309\u5F53\u524D provider \u65B0\u5EFA\u4F1A\u8BDD" },
3120
3226
  { command: "/new claude:<\u76EE\u5F55>", desc: "\u65B0\u5EFA\u6307\u5B9A provider + \u6307\u5B9A\u76EE\u5F55\u4F1A\u8BDD" },
3121
3227
  { command: "/stop", desc: "\u4E2D\u65AD\u5F53\u524D\u6B63\u5728\u751F\u6210\u7684\u56DE\u590D\uFF08\u4F1A\u8BDD\u4FDD\u7559\uFF09" }
3122
3228
  ]
@@ -3132,8 +3238,11 @@ var BRIDGE_COMMAND_HELP_GROUPS = [
3132
3238
  ]
3133
3239
  }
3134
3240
  ];
3135
- function buildBridgeCommandHelpMarkdown() {
3241
+ function buildBridgeCommandHelpMarkdown(input) {
3136
3242
  const lines = ["**\u53EF\u7528\u547D\u4EE4**", "", BRIDGE_COMMAND_HELP_INTRO];
3243
+ if (input?.publicUrl) {
3244
+ lines.push("", `**\u8BBF\u95EE\u5730\u5740**`, "", `[${input.publicUrl}](${input.publicUrl})`);
3245
+ }
3137
3246
  for (const group of BRIDGE_COMMAND_HELP_GROUPS) {
3138
3247
  lines.push("", `**${group.title}**${group.note ? `\uFF08${group.note}\uFF09` : ""}`);
3139
3248
  for (const entry of group.entries) {
@@ -3144,7 +3253,7 @@ function buildBridgeCommandHelpMarkdown() {
3144
3253
  }
3145
3254
 
3146
3255
  // src/session/currentConversationStore.ts
3147
- import { existsSync as existsSync5, mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "node:fs";
3256
+ import { existsSync as existsSync6, mkdirSync as mkdirSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync4 } from "node:fs";
3148
3257
  import { dirname as dirname7 } from "node:path";
3149
3258
  import { nanoid as nanoid2 } from "nanoid";
3150
3259
  var CurrentConversationStore = class {
@@ -3204,8 +3313,8 @@ var CurrentConversationStore = class {
3204
3313
  this.writeState(state);
3205
3314
  }
3206
3315
  readState() {
3207
- if (!existsSync5(this.configPath)) return {};
3208
- const raw = JSON.parse(readFileSync3(this.configPath, "utf8"));
3316
+ if (!existsSync6(this.configPath)) return {};
3317
+ const raw = JSON.parse(readFileSync4(this.configPath, "utf8"));
3209
3318
  return raw && typeof raw === "object" ? raw : {};
3210
3319
  }
3211
3320
  writeCurrent(record) {
@@ -3227,8 +3336,8 @@ var CurrentConversationStore = class {
3227
3336
  this.writeState(state);
3228
3337
  }
3229
3338
  writeState(state) {
3230
- mkdirSync3(dirname7(this.configPath), { recursive: true });
3231
- writeFileSync3(this.configPath, `${JSON.stringify(state, null, 2)}
3339
+ mkdirSync4(dirname7(this.configPath), { recursive: true });
3340
+ writeFileSync4(this.configPath, `${JSON.stringify(state, null, 2)}
3232
3341
  `, "utf8");
3233
3342
  }
3234
3343
  };
@@ -3518,10 +3627,11 @@ var MessageRouter = class _MessageRouter {
3518
3627
  }
3519
3628
  async handleCommand(chatId, user, command) {
3520
3629
  if (command.kind === "help") {
3630
+ const helpAddress = await this.options.getHelpAddress?.();
3521
3631
  await this.sendToChat({
3522
3632
  chatId,
3523
3633
  kind: "markdown",
3524
- text: buildBridgeCommandHelpMarkdown()
3634
+ text: buildBridgeCommandHelpMarkdown({ publicUrl: helpAddress })
3525
3635
  });
3526
3636
  return;
3527
3637
  }
@@ -3542,7 +3652,7 @@ var MessageRouter = class _MessageRouter {
3542
3652
  }
3543
3653
  if (command.kind === "new_session") {
3544
3654
  await this.preemptActiveGeneration(chatId);
3545
- const providerId = command.providerId ?? this.options.defaults?.defaultProvider ?? "claude-code";
3655
+ const providerId = command.providerId ?? this.conversation.getCurrent()?.providerId ?? this.options.defaults?.defaultProvider ?? "claude-code";
3546
3656
  const cwd = command.cwd ?? this.conversation.getCurrent()?.cwd ?? this.options.defaults?.defaultWorkspace ?? defaultWorkspaceDir();
3547
3657
  const session = this.conversation.create({
3548
3658
  chatId,
@@ -3672,6 +3782,13 @@ var MessageRouter = class _MessageRouter {
3672
3782
  providerSessionId: session.providerSessionId,
3673
3783
  cwd: session.cwd
3674
3784
  });
3785
+ if (session.providerId === "codex") {
3786
+ await upsertCodexSessionIndexEntry({
3787
+ sessionId: session.providerSessionId,
3788
+ threadName: session.resumeTitle
3789
+ });
3790
+ return;
3791
+ }
3675
3792
  if (session.providerId === "claude-code") {
3676
3793
  await ensureClaudeSessionBridgeMetadata({
3677
3794
  sessionId: session.providerSessionId,
@@ -3733,7 +3850,7 @@ function composeInboundText(content) {
3733
3850
  }
3734
3851
 
3735
3852
  // src/storage/lastProviderSessionStore.ts
3736
- import { existsSync as existsSync6, mkdirSync as mkdirSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync4 } from "node:fs";
3853
+ import { existsSync as existsSync7, mkdirSync as mkdirSync5, readFileSync as readFileSync5, writeFileSync as writeFileSync5 } from "node:fs";
3737
3854
  import { dirname as dirname8 } from "node:path";
3738
3855
  var LastProviderSessionStore = class {
3739
3856
  constructor(configPath) {
@@ -3760,19 +3877,19 @@ var LastProviderSessionStore = class {
3760
3877
  return this.readState().bridge?.lastProviderSessions ?? {};
3761
3878
  }
3762
3879
  readState() {
3763
- if (!existsSync6(this.configPath)) return {};
3764
- const raw = JSON.parse(readFileSync4(this.configPath, "utf8"));
3880
+ if (!existsSync7(this.configPath)) return {};
3881
+ const raw = JSON.parse(readFileSync5(this.configPath, "utf8"));
3765
3882
  return raw && typeof raw === "object" ? raw : {};
3766
3883
  }
3767
3884
  writeState(state) {
3768
- mkdirSync4(dirname8(this.configPath), { recursive: true });
3769
- writeFileSync4(this.configPath, `${JSON.stringify(state, null, 2)}
3885
+ mkdirSync5(dirname8(this.configPath), { recursive: true });
3886
+ writeFileSync5(this.configPath, `${JSON.stringify(state, null, 2)}
3770
3887
  `, "utf8");
3771
3888
  }
3772
3889
  };
3773
3890
 
3774
3891
  // src/storage/runtimeUserStore.ts
3775
- import { mkdirSync as mkdirSync5, readFileSync as readFileSync5, writeFileSync as writeFileSync5, existsSync as existsSync7 } from "node:fs";
3892
+ import { mkdirSync as mkdirSync6, readFileSync as readFileSync6, writeFileSync as writeFileSync6, existsSync as existsSync8 } from "node:fs";
3776
3893
  import { dirname as dirname9 } from "node:path";
3777
3894
  import { nanoid as nanoid3 } from "nanoid";
3778
3895
  var RuntimeUserStore = class {
@@ -3833,13 +3950,13 @@ var RuntimeUserStore = class {
3833
3950
  return { ok: true };
3834
3951
  }
3835
3952
  readState() {
3836
- if (!existsSync7(this.configPath)) return {};
3837
- const raw = JSON.parse(readFileSync5(this.configPath, "utf8"));
3953
+ if (!existsSync8(this.configPath)) return {};
3954
+ const raw = JSON.parse(readFileSync6(this.configPath, "utf8"));
3838
3955
  return raw && typeof raw === "object" ? raw : {};
3839
3956
  }
3840
3957
  writeState(state) {
3841
- mkdirSync5(dirname9(this.configPath), { recursive: true });
3842
- writeFileSync5(this.configPath, `${JSON.stringify(state, null, 2)}
3958
+ mkdirSync6(dirname9(this.configPath), { recursive: true });
3959
+ writeFileSync6(this.configPath, `${JSON.stringify(state, null, 2)}
3843
3960
  `, "utf8");
3844
3961
  }
3845
3962
  };
@@ -3861,15 +3978,221 @@ var BridgeEventHub = class {
3861
3978
  }
3862
3979
  };
3863
3980
 
3981
+ // src/runtime/relayTunnelProvider.ts
3982
+ var RelayTunnelProvider = class {
3983
+ constructor(options) {
3984
+ this.options = options;
3985
+ }
3986
+ options;
3987
+ socket = null;
3988
+ status = {
3989
+ installed: true,
3990
+ running: false,
3991
+ status: "stopped"
3992
+ };
3993
+ static defaultCreateSocket(url) {
3994
+ const socket = new WebSocket(url);
3995
+ return {
3996
+ get readyState() {
3997
+ return socket.readyState;
3998
+ },
3999
+ send(payload) {
4000
+ socket.send(payload);
4001
+ },
4002
+ close() {
4003
+ socket.close();
4004
+ },
4005
+ on(event, listener) {
4006
+ socket.addEventListener(event, listener);
4007
+ }
4008
+ };
4009
+ }
4010
+ async getStatus() {
4011
+ return { ...this.status };
4012
+ }
4013
+ async start() {
4014
+ this.status = {
4015
+ ...this.status,
4016
+ status: "starting"
4017
+ };
4018
+ const socket = this.options.createSocket(this.options.serverUrl);
4019
+ this.socket = socket;
4020
+ return await new Promise((resolve2, reject) => {
4021
+ let settled = false;
4022
+ const finishResolve = (value) => {
4023
+ if (settled) return;
4024
+ settled = true;
4025
+ resolve2(value);
4026
+ };
4027
+ const finishReject = (error) => {
4028
+ if (settled) return;
4029
+ settled = true;
4030
+ reject(error);
4031
+ };
4032
+ socket.on("open", () => {
4033
+ socket.send(JSON.stringify({
4034
+ type: "register",
4035
+ clientVersion: "0.1.0",
4036
+ targetBaseUrl: this.options.targetBaseUrl,
4037
+ authToken: this.options.authToken
4038
+ }));
4039
+ });
4040
+ socket.on("message", (raw) => {
4041
+ const payload = typeof raw === "string" ? JSON.parse(raw) : typeof raw?.data === "string" ? JSON.parse(raw.data) : raw;
4042
+ if (payload?.type === "error") {
4043
+ const message = typeof payload?.error === "string" && payload.error ? payload.error : "relay_error";
4044
+ this.status = {
4045
+ ...this.status,
4046
+ running: false,
4047
+ status: "error",
4048
+ error: message
4049
+ };
4050
+ finishReject(new Error(message));
4051
+ return;
4052
+ }
4053
+ if (payload?.type === "registered") {
4054
+ const publicUrl = typeof payload?.publicUrl === "string" && payload.publicUrl ? payload.publicUrl : typeof payload?.token === "string" ? `${derivePublicBaseUrl(this.options.serverUrl)}/${payload.token}` : void 0;
4055
+ this.status = {
4056
+ installed: true,
4057
+ running: true,
4058
+ status: "running",
4059
+ ...publicUrl ? { publicUrl } : {}
4060
+ };
4061
+ finishResolve({ ...this.status });
4062
+ return;
4063
+ }
4064
+ if (payload?.type === "request") {
4065
+ void this.handleRequest(payload);
4066
+ }
4067
+ });
4068
+ socket.on("close", () => {
4069
+ this.status = {
4070
+ ...this.status,
4071
+ running: false,
4072
+ status: "error",
4073
+ error: "relay_disconnected"
4074
+ };
4075
+ finishReject(new Error("relay_disconnected"));
4076
+ });
4077
+ socket.on("error", (error) => {
4078
+ const message = error instanceof Error ? error.message : String(error);
4079
+ this.status = {
4080
+ ...this.status,
4081
+ running: false,
4082
+ status: "error",
4083
+ error: message
4084
+ };
4085
+ finishReject(error);
4086
+ });
4087
+ });
4088
+ }
4089
+ async stop() {
4090
+ this.socket?.close();
4091
+ this.socket = null;
4092
+ this.status = {
4093
+ installed: true,
4094
+ running: false,
4095
+ status: "stopped"
4096
+ };
4097
+ return { ...this.status };
4098
+ }
4099
+ async handleRequest(payload) {
4100
+ const fetchImpl = this.options.fetchImpl ?? fetch;
4101
+ const response = await fetchImpl(`${this.options.targetBaseUrl}${payload.path}`, {
4102
+ method: payload.method,
4103
+ headers: payload.headers,
4104
+ body: payload.bodyBase64 ? Buffer.from(payload.bodyBase64, "base64") : void 0
4105
+ });
4106
+ const bodyBuffer = Buffer.from(await response.arrayBuffer());
4107
+ this.socket?.send(JSON.stringify({
4108
+ type: "response",
4109
+ requestId: payload.requestId,
4110
+ status: response.status,
4111
+ headers: Object.fromEntries(response.headers.entries()),
4112
+ bodyBase64: bodyBuffer.toString("base64")
4113
+ }));
4114
+ }
4115
+ };
4116
+ function derivePublicBaseUrl(serverUrl) {
4117
+ return serverUrl.replace(/^ws:\/\//, "http://").replace(/^wss:\/\//, "https://").replace(/\/agent\/?$/, "").replace(/\/+$/, "");
4118
+ }
4119
+
4120
+ // src/runtime/relayTunnelRouter.ts
4121
+ var RelayTunnelRouter = class {
4122
+ constructor(options) {
4123
+ this.options = options;
4124
+ }
4125
+ options;
4126
+ relayProvider = null;
4127
+ relayKey = "";
4128
+ async getStatus() {
4129
+ const provider = this.getOrCreateRelayProvider();
4130
+ if (!provider) return this.notConfiguredStatus();
4131
+ return await provider.getStatus();
4132
+ }
4133
+ async start() {
4134
+ const provider = this.getOrCreateRelayProvider();
4135
+ if (!provider) return this.notConfiguredStatus();
4136
+ return await provider.start();
4137
+ }
4138
+ async stop() {
4139
+ const provider = this.getOrCreateRelayProvider();
4140
+ if (!provider) return this.notConfiguredStatus();
4141
+ return await provider.stop();
4142
+ }
4143
+ getOrCreateRelayProvider() {
4144
+ const relay = this.options.defaults.tunnel.relay;
4145
+ if (!relay?.serverUrl || !relay.authToken) return null;
4146
+ const key = `${relay.serverUrl}|${relay.authToken}|${this.options.bridgePort}`;
4147
+ if (this.relayProvider && this.relayKey === key) return this.relayProvider;
4148
+ this.relayKey = key;
4149
+ this.relayProvider = new RelayTunnelProvider({
4150
+ serverUrl: relay.serverUrl,
4151
+ authToken: relay.authToken,
4152
+ targetBaseUrl: `http://127.0.0.1:${this.options.bridgePort}`,
4153
+ createSocket: RelayTunnelProvider.defaultCreateSocket
4154
+ });
4155
+ return this.relayProvider;
4156
+ }
4157
+ notConfiguredStatus() {
4158
+ return {
4159
+ installed: false,
4160
+ running: false,
4161
+ status: "error",
4162
+ error: "relay_not_configured"
4163
+ };
4164
+ }
4165
+ };
4166
+
3864
4167
  // src/daemon/server.ts
3865
4168
  function createDaemonServer(options = {}) {
3866
4169
  const app = Fastify({ logger: true });
3867
4170
  const events = new BridgeEventHub();
4171
+ const configPath = options.configPath ?? process.env.BRIDGE_CONFIG ?? join9(mkdtempSync(join9(tmpdir2(), "claude-codex-wechat-")), "config.json");
4172
+ const persistedConfig = loadBridgeConfig(configPath);
3868
4173
  const bridgeDefaults = {
3869
- defaultProvider: options.bridgeDefaults?.defaultProvider ?? "claude-code",
3870
- defaultWorkspace: options.bridgeDefaults?.defaultWorkspace ?? process.cwd()
4174
+ defaultProvider: options.bridgeDefaults?.defaultProvider ?? persistedConfig.bridge?.defaultProvider ?? "claude-code",
4175
+ defaultWorkspace: options.bridgeDefaults?.defaultWorkspace ?? persistedConfig.bridge?.defaultWorkspace ?? process.cwd(),
4176
+ tunnel: options.bridgeDefaults?.tunnel ?? {
4177
+ relay: {
4178
+ serverUrl: persistedConfig.tunnel?.relay?.serverUrl ?? "wss://wechat.style520.com/agent",
4179
+ ...persistedConfig.tunnel?.relay?.authToken ? { authToken: persistedConfig.tunnel.relay.authToken } : {}
4180
+ }
4181
+ }
3871
4182
  };
3872
- const configPath = options.configPath ?? process.env.BRIDGE_CONFIG ?? join9(mkdtempSync(join9(tmpdir2(), "claude-codex-wechat-")), "config.json");
4183
+ const existingRelayAuthToken = bridgeDefaults.tunnel.relay?.authToken?.trim();
4184
+ if (existingRelayAuthToken) {
4185
+ bridgeDefaults.tunnel.relay = {
4186
+ ...bridgeDefaults.tunnel.relay ?? {},
4187
+ authToken: existingRelayAuthToken
4188
+ };
4189
+ } else {
4190
+ const authToken = ensureRelayAuthTokenSync({ configPath });
4191
+ bridgeDefaults.tunnel.relay = {
4192
+ ...bridgeDefaults.tunnel.relay ?? {},
4193
+ authToken
4194
+ };
4195
+ }
3873
4196
  const conversation = new CurrentConversationStore(configPath, {
3874
4197
  defaultCwd: bridgeDefaults.defaultWorkspace,
3875
4198
  defaultProviderId: bridgeDefaults.defaultProvider
@@ -3911,6 +4234,10 @@ function createDaemonServer(options = {}) {
3911
4234
  await managedWechatChannel.sendMessage({ chatId, kind: msg.kind, text: msg.text });
3912
4235
  }
3913
4236
  }) : void 0;
4237
+ const tunnelProvider = options.tunnelProvider ?? new RelayTunnelRouter({
4238
+ bridgePort: Number(process.env.BRIDGE_PORT ?? 8787),
4239
+ defaults: bridgeDefaults
4240
+ });
3914
4241
  const messageRouter = channel ? new MessageRouter({
3915
4242
  channel,
3916
4243
  providers: providerAdapters,
@@ -3963,7 +4290,14 @@ function createDaemonServer(options = {}) {
3963
4290
  },
3964
4291
  lastProviderSessions,
3965
4292
  events,
3966
- defaults: bridgeDefaults
4293
+ defaults: bridgeDefaults,
4294
+ getHelpAddress: async () => {
4295
+ const tunnel = await tunnelProvider?.getStatus().catch(() => null);
4296
+ if (tunnel?.running && tunnel.publicUrl) return tunnel.publicUrl;
4297
+ const lan = listLanIpv4Addresses()[0];
4298
+ if (lan) return `http://${lan}:${process.env.BRIDGE_PORT ?? 8787}`;
4299
+ return `http://127.0.0.1:${process.env.BRIDGE_PORT ?? 8787}`;
4300
+ }
3967
4301
  }) : void 0;
3968
4302
  if (channel && messageRouter) {
3969
4303
  channel.onMessage(async (message) => {
@@ -3992,10 +4326,24 @@ function createDaemonServer(options = {}) {
3992
4326
  defaults: bridgeDefaults,
3993
4327
  configPath
3994
4328
  });
4329
+ if (tunnelProvider) {
4330
+ registerTunnelRoutes({
4331
+ app,
4332
+ configPath,
4333
+ defaults: { tunnel: bridgeDefaults.tunnel },
4334
+ tunnelManager: tunnelProvider
4335
+ });
4336
+ }
3995
4337
  registerFsBrowseRoutes({ app });
3996
4338
  app.get("/api/status", async () => ({
3997
4339
  ok: true,
3998
- sessions: conversation.getCurrent() ? [conversation.getCurrent()] : []
4340
+ sessions: conversation.getCurrent() ? [conversation.getCurrent()] : [],
4341
+ preferredLocalUrl: (() => {
4342
+ const lan = listLanIpv4Addresses()[0];
4343
+ const port = process.env.BRIDGE_PORT ?? 8787;
4344
+ if (lan) return `http://${lan}:${port}`;
4345
+ return `http://127.0.0.1:${port}`;
4346
+ })()
3999
4347
  }));
4000
4348
  app.get("/api/providers/status", async () => providers.getStatus());
4001
4349
  app.post("/api/bridge-events", async (request, reply) => {
@@ -4029,7 +4377,7 @@ function createDaemonServer(options = {}) {
4029
4377
  app.addHook("onClose", async () => {
4030
4378
  await channel?.stop();
4031
4379
  });
4032
- return { app, conversation, sessions: conversation, events, activeUserStore };
4380
+ return { app, conversation, sessions: conversation, events, activeUserStore, tunnelProvider };
4033
4381
  }
4034
4382
 
4035
4383
  // src/daemon/bootstrap.ts
@@ -4043,24 +4391,35 @@ async function startDaemon(options) {
4043
4391
  configPath,
4044
4392
  providers: providerCommands
4045
4393
  });
4046
- const { app } = createDaemonServer({
4394
+ const bridgeDefaults = {
4395
+ defaultProvider: config.bridge?.defaultProvider ?? "claude-code",
4396
+ defaultWorkspace: config.bridge?.defaultWorkspace ?? process.cwd(),
4397
+ tunnel: {
4398
+ enabled: true,
4399
+ ...config.tunnel?.relay ? { relay: config.tunnel.relay } : {}
4400
+ }
4401
+ };
4402
+ const { app, tunnelProvider: resolvedTunnelProvider } = createDaemonServer({
4047
4403
  wechat: config.wechat,
4048
4404
  providerCommands,
4049
- bridgeDefaults: {
4050
- defaultProvider: config.bridge?.defaultProvider ?? "claude-code",
4051
- defaultWorkspace: config.bridge?.defaultWorkspace ?? process.cwd()
4052
- },
4053
- configPath
4405
+ bridgeDefaults,
4406
+ configPath,
4407
+ ...options.tunnelProvider ? { tunnelProvider: options.tunnelProvider } : {}
4054
4408
  });
4055
4409
  await options.attachFrontend(app);
4056
4410
  await app.listen({ host, port });
4411
+ const address = app.server.address();
4412
+ const actualPort = typeof address === "object" && address ? address.port : port;
4413
+ if (config.tunnel?.relay?.serverUrl && config.tunnel?.relay?.authToken) {
4414
+ await resolvedTunnelProvider?.start().catch(() => void 0);
4415
+ }
4057
4416
  console.log("claude-codex-wechat listening:");
4058
- console.log(` Local: http://127.0.0.1:${port}`);
4417
+ console.log(` Local: http://127.0.0.1:${actualPort}`);
4059
4418
  for (const ip of listLanIpv4Addresses()) {
4060
- console.log(` Network: http://${ip}:${port}`);
4419
+ console.log(` Network: http://${ip}:${actualPort}`);
4061
4420
  }
4062
4421
  console.log(`config path: ${configPath}`);
4063
- return { app, port, host, configPath };
4422
+ return { app, port: actualPort, host, configPath };
4064
4423
  }
4065
4424
  function listLanIpv4Addresses() {
4066
4425
  return Object.values(networkInterfaces()).flat().filter((iface) => Boolean(iface)).filter((iface) => iface.family === "IPv4" && !iface.internal).map((iface) => iface.address);
@@ -4076,7 +4435,7 @@ async function resolveProviderCommands(providers) {
4076
4435
  }
4077
4436
 
4078
4437
  // src/daemon/staticFrontend.ts
4079
- import { readFileSync as readFileSync6 } from "node:fs";
4438
+ import { readFileSync as readFileSync7 } from "node:fs";
4080
4439
  import { join as join10 } from "node:path";
4081
4440
  import fastifyStatic from "@fastify/static";
4082
4441
  function attachStaticFrontend(webRoot2) {
@@ -4085,7 +4444,7 @@ function attachStaticFrontend(webRoot2) {
4085
4444
  root: webRoot2,
4086
4445
  wildcard: false
4087
4446
  });
4088
- const indexHtml = readFileSync6(join10(webRoot2, "index.html"), "utf8");
4447
+ const indexHtml = readFileSync7(join10(webRoot2, "index.html"), "utf8");
4089
4448
  app.setNotFoundHandler((request, reply) => {
4090
4449
  if (request.url.startsWith("/api")) {
4091
4450
  reply.code(404).send({ error: "not_found" });
@@ -4097,7 +4456,7 @@ function attachStaticFrontend(webRoot2) {
4097
4456
  }
4098
4457
 
4099
4458
  // src/daemon/service.ts
4100
- import { existsSync as existsSync8, statSync } from "node:fs";
4459
+ import { existsSync as existsSync9, statSync } from "node:fs";
4101
4460
  import { mkdir as mkdir4, readFile as readFile6, rm as rm2, writeFile as writeFile5 } from "node:fs/promises";
4102
4461
  import { homedir as homedir8 } from "node:os";
4103
4462
  import { dirname as dirname11, join as join11 } from "node:path";
@@ -4147,13 +4506,13 @@ async function startService(context) {
4147
4506
  async function stopService(context) {
4148
4507
  if (process.platform === "darwin") {
4149
4508
  const spec = buildLaunchdSpec(context);
4150
- if (!existsSync8(spec.plistPath)) return await readServiceStatus(context);
4509
+ if (!existsSync9(spec.plistPath)) return await readServiceStatus(context);
4151
4510
  await runLaunchctl(["bootout", `gui/${process.getuid?.() ?? 0}`, spec.plistPath]).catch(() => void 0);
4152
4511
  return await readServiceStatus(context);
4153
4512
  }
4154
4513
  if (process.platform === "linux") {
4155
4514
  const spec = buildSystemdUserSpec(context);
4156
- if (!existsSync8(spec.unitPath)) return await readServiceStatus(context);
4515
+ if (!existsSync9(spec.unitPath)) return await readServiceStatus(context);
4157
4516
  await runSystemctl(["--user", "stop", spec.unitName]).catch(() => void 0);
4158
4517
  return await readServiceStatus(context);
4159
4518
  }
@@ -4162,7 +4521,7 @@ async function stopService(context) {
4162
4521
  async function uninstallService(context) {
4163
4522
  if (process.platform === "darwin") {
4164
4523
  const spec = buildLaunchdSpec(context);
4165
- if (existsSync8(spec.plistPath)) {
4524
+ if (existsSync9(spec.plistPath)) {
4166
4525
  await runLaunchctl(["bootout", `gui/${process.getuid?.() ?? 0}`, spec.plistPath]).catch(() => void 0);
4167
4526
  await rm2(spec.plistPath, { force: true });
4168
4527
  }
@@ -4170,7 +4529,7 @@ async function uninstallService(context) {
4170
4529
  }
4171
4530
  if (process.platform === "linux") {
4172
4531
  const spec = buildSystemdUserSpec(context);
4173
- if (existsSync8(spec.unitPath)) {
4532
+ if (existsSync9(spec.unitPath)) {
4174
4533
  await runSystemctl(["--user", "disable", "--now", spec.unitName]).catch(() => void 0);
4175
4534
  await rm2(spec.unitPath, { force: true });
4176
4535
  await runSystemctl(["--user", "daemon-reload"]).catch(() => void 0);
@@ -4213,7 +4572,7 @@ async function tailServiceLogs(context) {
4213
4572
  async function readServiceStatus(context) {
4214
4573
  if (process.platform === "darwin") {
4215
4574
  const spec = buildLaunchdSpec(context);
4216
- const installed = existsSync8(spec.plistPath);
4575
+ const installed = existsSync9(spec.plistPath);
4217
4576
  const running = installed ? await runLaunchctl(["print", `gui/${process.getuid?.() ?? 0}/${spec.label}`]).then(() => true, () => false) : false;
4218
4577
  return {
4219
4578
  manager: "launchd",
@@ -4227,7 +4586,7 @@ async function readServiceStatus(context) {
4227
4586
  }
4228
4587
  if (process.platform === "linux") {
4229
4588
  const spec = buildSystemdUserSpec(context);
4230
- const installed = existsSync8(spec.unitPath);
4589
+ const installed = existsSync9(spec.unitPath);
4231
4590
  const running = installed ? await runSystemctl(["--user", "is-active", "--quiet", spec.unitName]).then(() => true, () => false) : false;
4232
4591
  return {
4233
4592
  manager: "systemd-user",
@@ -4363,16 +4722,16 @@ async function runSystemctl(args) {
4363
4722
  await execFileAsync("systemctl", args);
4364
4723
  }
4365
4724
  async function ensureFileExists(path, errorCode) {
4366
- if (!existsSync8(path)) throw new Error(errorCode);
4725
+ if (!existsSync9(path)) throw new Error(errorCode);
4367
4726
  await readFile6(path, "utf8");
4368
4727
  }
4369
4728
  async function tailFile(path, lines) {
4370
- if (!existsSync8(path)) return "";
4729
+ if (!existsSync9(path)) return "";
4371
4730
  const content = await readFile6(path, "utf8");
4372
4731
  return content.split(/\r?\n/).slice(-lines).join("\n").trim();
4373
4732
  }
4374
4733
  async function tailFiles(paths) {
4375
- const readers = paths.filter((path) => existsSync8(path)).map((path) => {
4734
+ const readers = paths.filter((path) => existsSync9(path)).map((path) => {
4376
4735
  const rl = createInterface({
4377
4736
  input: createReadStream(path, { encoding: "utf8", start: Math.max(0, fileSizeHint(path) - 8192) }),
4378
4737
  crlfDelay: Infinity
@@ -4386,7 +4745,7 @@ async function tailFiles(paths) {
4386
4745
  if (process.platform !== "linux" && process.platform !== "darwin") {
4387
4746
  return;
4388
4747
  }
4389
- const child = spawn6("tail", ["-f", ...paths.filter((path) => existsSync8(path))], { stdio: ["ignore", "pipe", "inherit"] });
4748
+ const child = spawn6("tail", ["-f", ...paths.filter((path) => existsSync9(path))], { stdio: ["ignore", "pipe", "inherit"] });
4390
4749
  child.stdout.on("data", (chunk) => {
4391
4750
  process.stdout.write(String(chunk));
4392
4751
  });
@@ -4459,7 +4818,7 @@ async function main() {
4459
4818
  }
4460
4819
  }
4461
4820
  async function cmdStart() {
4462
- if (!existsSync9(webRoot)) {
4821
+ if (!existsSync10(webRoot)) {
4463
4822
  console.error(`\u627E\u4E0D\u5230\u524D\u7AEF\u6784\u5EFA\u4EA7\u7269: ${webRoot}`);
4464
4823
  console.error("\u8BF7\u91CD\u65B0\u5B89\u88C5\u5B8C\u6574\u7684 npm \u5305\u3002");
4465
4824
  process.exitCode = 1;
@@ -4489,7 +4848,7 @@ async function cmdTail() {
4489
4848
  await tailServiceLogs(createServiceContext());
4490
4849
  }
4491
4850
  async function cmdDaemon() {
4492
- if (!existsSync9(webRoot)) {
4851
+ if (!existsSync10(webRoot)) {
4493
4852
  console.error(`\u627E\u4E0D\u5230\u524D\u7AEF\u6784\u5EFA\u4EA7\u7269: ${webRoot}`);
4494
4853
  process.exitCode = 1;
4495
4854
  return;
@@ -4501,8 +4860,8 @@ async function cmdDaemon() {
4501
4860
  function cmdInit() {
4502
4861
  const configPath = process.env.BRIDGE_CONFIG ?? defaultConfigPath();
4503
4862
  const dir = dirname12(configPath);
4504
- mkdirSync6(dir, { recursive: true });
4505
- if (existsSync9(configPath)) {
4863
+ mkdirSync7(dir, { recursive: true });
4864
+ if (existsSync10(configPath)) {
4506
4865
  console.log(`\u914D\u7F6E\u5DF2\u5B58\u5728\uFF0C\u672A\u8986\u76D6: ${configPath}`);
4507
4866
  return;
4508
4867
  }
@@ -4519,7 +4878,7 @@ function cmdInit() {
4519
4878
  defaultWorkspace: process.cwd()
4520
4879
  }
4521
4880
  };
4522
- writeFileSync6(configPath, `${JSON.stringify(template, null, 2)}
4881
+ writeFileSync7(configPath, `${JSON.stringify(template, null, 2)}
4523
4882
  `, "utf8");
4524
4883
  console.log(`\u5DF2\u521B\u5EFA\u914D\u7F6E: ${configPath}`);
4525
4884
  console.log("\u8BF7\u586B\u5165\u5FAE\u4FE1 token / accountId \u540E\u8FD0\u884C: claude-codex-wechat start");
@@ -4527,13 +4886,13 @@ function cmdInit() {
4527
4886
  async function cmdDoctor() {
4528
4887
  const configPath = process.env.BRIDGE_CONFIG ?? defaultConfigPath();
4529
4888
  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})`);
4889
+ report("\u914D\u7F6E\u6587\u4EF6", existsSync10(configPath) ? configPath : `\u7F3A\u5931 (${configPath})\uFF0C\u8FD0\u884C init \u521B\u5EFA`);
4890
+ report("\u524D\u7AEF\u4EA7\u7269", existsSync10(webRoot) ? webRoot : `\u7F3A\u5931 (${webRoot})`);
4532
4891
  const claude = await findExecutable("claude");
4533
4892
  report("claude \u53EF\u6267\u884C", claude ?? "\u672A\u627E\u5230\uFF0C\u9700\u5148\u5B89\u88C5\u5E76\u767B\u5F55 Claude Code");
4534
4893
  const codex = await findExecutable("codex");
4535
4894
  report("codex \u53EF\u6267\u884C", codex ?? "\u672A\u627E\u5230\uFF08\u4EC5\u4F7F\u7528 Claude \u65F6\u53EF\u5FFD\u7565\uFF09");
4536
- if (existsSync9(configPath)) {
4895
+ if (existsSync10(configPath)) {
4537
4896
  const config = loadBridgeConfig(configPath);
4538
4897
  const wechat = config.wechat;
4539
4898
  report("\u5FAE\u4FE1\u542F\u7528", wechat?.enabled ? "\u662F" : "\u5426");
@@ -4543,12 +4902,12 @@ async function cmdDoctor() {
4543
4902
  }
4544
4903
  function cmdPrintConfig() {
4545
4904
  const configPath = process.env.BRIDGE_CONFIG ?? defaultConfigPath();
4546
- if (!existsSync9(configPath)) {
4905
+ if (!existsSync10(configPath)) {
4547
4906
  console.error(`\u914D\u7F6E\u4E0D\u5B58\u5728: ${configPath}\uFF08\u8FD0\u884C init \u521B\u5EFA\uFF09`);
4548
4907
  process.exitCode = 1;
4549
4908
  return;
4550
4909
  }
4551
- console.log(readFileSync7(configPath, "utf8"));
4910
+ console.log(readFileSync8(configPath, "utf8"));
4552
4911
  }
4553
4912
  async function cmdUninstall() {
4554
4913
  const status = await uninstallService(createServiceContext());