claude-codex-wechat 0.1.12 → 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
  };
@@ -2738,7 +2865,8 @@ var CodexInteractiveRunner = class {
2738
2865
  sessionName: input.options?.sessionName,
2739
2866
  cwd: input.cwd,
2740
2867
  status: "idle",
2741
- pendingMessages: []
2868
+ pendingEvents: [],
2869
+ turnClosed: false
2742
2870
  };
2743
2871
  this.sessions.set(input.bridgeSessionId, session);
2744
2872
  return session;
@@ -2747,8 +2875,10 @@ var CodexInteractiveRunner = class {
2747
2875
  const session = this.sessions.get(input.bridgeSessionId);
2748
2876
  if (!session) throw new Error(`codex_session_not_found:${input.bridgeSessionId}`);
2749
2877
  const client = await this.ensureClient(session);
2750
- session.pendingMessages = [];
2878
+ session.pendingEvents = [];
2879
+ session.activeMessage = void 0;
2751
2880
  session.activeTurnId = void 0;
2881
+ session.turnClosed = false;
2752
2882
  session.turnCompletedPromise = new Promise((resolve2) => {
2753
2883
  session.turnCompletedResolver = resolve2;
2754
2884
  });
@@ -2788,6 +2918,11 @@ var CodexInteractiveRunner = class {
2788
2918
  });
2789
2919
  const maybeTurnId = readTurnId(response);
2790
2920
  if (maybeTurnId) session.activeTurnId = maybeTurnId;
2921
+ for (; ; ) {
2922
+ const event = await this.takeNextEvent(session);
2923
+ if (!event) break;
2924
+ yield event;
2925
+ }
2791
2926
  await session.turnCompletedPromise;
2792
2927
  if (session.threadId) {
2793
2928
  await this.syncThreadForResume({
@@ -2796,11 +2931,6 @@ var CodexInteractiveRunner = class {
2796
2931
  cwd: session.cwd
2797
2932
  });
2798
2933
  }
2799
- for (const message of session.pendingMessages) {
2800
- if (!message.text) continue;
2801
- yield { type: "text_delta", text: message.text };
2802
- yield { type: "message_done" };
2803
- }
2804
2934
  yield {
2805
2935
  type: "session_state",
2806
2936
  state: {
@@ -2826,6 +2956,10 @@ var CodexInteractiveRunner = class {
2826
2956
  turnId: session.activeTurnId
2827
2957
  }).catch(() => void 0);
2828
2958
  }
2959
+ this.flushActiveMessage(session);
2960
+ session.turnClosed = true;
2961
+ session.eventResolver?.();
2962
+ session.eventResolver = void 0;
2829
2963
  session.turnCompletedResolver?.();
2830
2964
  session.turnCompletedResolver = void 0;
2831
2965
  }
@@ -2848,19 +2982,23 @@ var CodexInteractiveRunner = class {
2848
2982
  client.onNotification("item/agentMessage/delta", (params) => {
2849
2983
  const record = params;
2850
2984
  if (typeof record.delta !== "string" || !record.delta) return;
2851
- const itemId = readAgentMessageItemId(record) ?? `fallback-${session.pendingMessages.length}`;
2852
- const last = session.pendingMessages.at(-1);
2853
- if (last?.itemId === itemId) {
2854
- last.text += record.delta;
2985
+ const itemId = readAgentMessageItemId(record) ?? `fallback-${session.pendingEvents.length}`;
2986
+ if (session.activeMessage?.itemId === itemId) {
2987
+ session.activeMessage.text += record.delta;
2855
2988
  return;
2856
2989
  }
2857
- session.pendingMessages.push({ itemId, text: record.delta });
2990
+ this.flushActiveMessage(session);
2991
+ session.activeMessage = { itemId, text: record.delta };
2858
2992
  });
2859
2993
  client.onNotification("turn/started", (params) => {
2860
2994
  const turnId = readTurnId(params);
2861
2995
  if (turnId) session.activeTurnId = turnId;
2862
2996
  });
2863
2997
  client.onNotification("turn/completed", () => {
2998
+ this.flushActiveMessage(session);
2999
+ session.turnClosed = true;
3000
+ session.eventResolver?.();
3001
+ session.eventResolver = void 0;
2864
3002
  session.turnCompletedResolver?.();
2865
3003
  session.turnCompletedResolver = void 0;
2866
3004
  });
@@ -2873,6 +3011,27 @@ var CodexInteractiveRunner = class {
2873
3011
  session.client = client;
2874
3012
  return client;
2875
3013
  }
3014
+ flushActiveMessage(session) {
3015
+ if (!session.activeMessage?.text) return;
3016
+ this.enqueueEvent(session, { type: "text_delta", text: session.activeMessage.text });
3017
+ this.enqueueEvent(session, { type: "message_done" });
3018
+ session.activeMessage = void 0;
3019
+ }
3020
+ enqueueEvent(session, event) {
3021
+ session.pendingEvents.push(event);
3022
+ session.eventResolver?.();
3023
+ session.eventResolver = void 0;
3024
+ }
3025
+ async takeNextEvent(session) {
3026
+ for (; ; ) {
3027
+ const next = session.pendingEvents.shift();
3028
+ if (next) return next;
3029
+ if (session.turnClosed) return null;
3030
+ await new Promise((resolve2) => {
3031
+ session.eventResolver = resolve2;
3032
+ });
3033
+ }
3034
+ }
2876
3035
  };
2877
3036
 
2878
3037
  // src/providers/codex/codexProvider.ts
@@ -3100,8 +3259,11 @@ var BRIDGE_COMMAND_HELP_GROUPS = [
3100
3259
  ]
3101
3260
  }
3102
3261
  ];
3103
- function buildBridgeCommandHelpMarkdown() {
3262
+ function buildBridgeCommandHelpMarkdown(input) {
3104
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
+ }
3105
3267
  for (const group of BRIDGE_COMMAND_HELP_GROUPS) {
3106
3268
  lines.push("", `**${group.title}**${group.note ? `\uFF08${group.note}\uFF09` : ""}`);
3107
3269
  for (const entry of group.entries) {
@@ -3112,7 +3274,7 @@ function buildBridgeCommandHelpMarkdown() {
3112
3274
  }
3113
3275
 
3114
3276
  // src/session/currentConversationStore.ts
3115
- 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";
3116
3278
  import { dirname as dirname7 } from "node:path";
3117
3279
  import { nanoid as nanoid2 } from "nanoid";
3118
3280
  var CurrentConversationStore = class {
@@ -3172,8 +3334,8 @@ var CurrentConversationStore = class {
3172
3334
  this.writeState(state);
3173
3335
  }
3174
3336
  readState() {
3175
- if (!existsSync5(this.configPath)) return {};
3176
- const raw = JSON.parse(readFileSync3(this.configPath, "utf8"));
3337
+ if (!existsSync6(this.configPath)) return {};
3338
+ const raw = JSON.parse(readFileSync4(this.configPath, "utf8"));
3177
3339
  return raw && typeof raw === "object" ? raw : {};
3178
3340
  }
3179
3341
  writeCurrent(record) {
@@ -3195,8 +3357,8 @@ var CurrentConversationStore = class {
3195
3357
  this.writeState(state);
3196
3358
  }
3197
3359
  writeState(state) {
3198
- mkdirSync3(dirname7(this.configPath), { recursive: true });
3199
- writeFileSync3(this.configPath, `${JSON.stringify(state, null, 2)}
3360
+ mkdirSync4(dirname7(this.configPath), { recursive: true });
3361
+ writeFileSync4(this.configPath, `${JSON.stringify(state, null, 2)}
3200
3362
  `, "utf8");
3201
3363
  }
3202
3364
  };
@@ -3486,10 +3648,11 @@ var MessageRouter = class _MessageRouter {
3486
3648
  }
3487
3649
  async handleCommand(chatId, user, command) {
3488
3650
  if (command.kind === "help") {
3651
+ const helpAddress = await this.options.getHelpAddress?.();
3489
3652
  await this.sendToChat({
3490
3653
  chatId,
3491
3654
  kind: "markdown",
3492
- text: buildBridgeCommandHelpMarkdown()
3655
+ text: buildBridgeCommandHelpMarkdown({ publicUrl: helpAddress })
3493
3656
  });
3494
3657
  return;
3495
3658
  }
@@ -3640,6 +3803,13 @@ var MessageRouter = class _MessageRouter {
3640
3803
  providerSessionId: session.providerSessionId,
3641
3804
  cwd: session.cwd
3642
3805
  });
3806
+ if (session.providerId === "codex") {
3807
+ await upsertCodexSessionIndexEntry({
3808
+ sessionId: session.providerSessionId,
3809
+ threadName: session.resumeTitle
3810
+ });
3811
+ return;
3812
+ }
3643
3813
  if (session.providerId === "claude-code") {
3644
3814
  await ensureClaudeSessionBridgeMetadata({
3645
3815
  sessionId: session.providerSessionId,
@@ -3701,7 +3871,7 @@ function composeInboundText(content) {
3701
3871
  }
3702
3872
 
3703
3873
  // src/storage/lastProviderSessionStore.ts
3704
- 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";
3705
3875
  import { dirname as dirname8 } from "node:path";
3706
3876
  var LastProviderSessionStore = class {
3707
3877
  constructor(configPath) {
@@ -3728,19 +3898,19 @@ var LastProviderSessionStore = class {
3728
3898
  return this.readState().bridge?.lastProviderSessions ?? {};
3729
3899
  }
3730
3900
  readState() {
3731
- if (!existsSync6(this.configPath)) return {};
3732
- const raw = JSON.parse(readFileSync4(this.configPath, "utf8"));
3901
+ if (!existsSync7(this.configPath)) return {};
3902
+ const raw = JSON.parse(readFileSync5(this.configPath, "utf8"));
3733
3903
  return raw && typeof raw === "object" ? raw : {};
3734
3904
  }
3735
3905
  writeState(state) {
3736
- mkdirSync4(dirname8(this.configPath), { recursive: true });
3737
- writeFileSync4(this.configPath, `${JSON.stringify(state, null, 2)}
3906
+ mkdirSync5(dirname8(this.configPath), { recursive: true });
3907
+ writeFileSync5(this.configPath, `${JSON.stringify(state, null, 2)}
3738
3908
  `, "utf8");
3739
3909
  }
3740
3910
  };
3741
3911
 
3742
3912
  // src/storage/runtimeUserStore.ts
3743
- 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";
3744
3914
  import { dirname as dirname9 } from "node:path";
3745
3915
  import { nanoid as nanoid3 } from "nanoid";
3746
3916
  var RuntimeUserStore = class {
@@ -3801,13 +3971,13 @@ var RuntimeUserStore = class {
3801
3971
  return { ok: true };
3802
3972
  }
3803
3973
  readState() {
3804
- if (!existsSync7(this.configPath)) return {};
3805
- const raw = JSON.parse(readFileSync5(this.configPath, "utf8"));
3974
+ if (!existsSync8(this.configPath)) return {};
3975
+ const raw = JSON.parse(readFileSync6(this.configPath, "utf8"));
3806
3976
  return raw && typeof raw === "object" ? raw : {};
3807
3977
  }
3808
3978
  writeState(state) {
3809
- mkdirSync5(dirname9(this.configPath), { recursive: true });
3810
- writeFileSync5(this.configPath, `${JSON.stringify(state, null, 2)}
3979
+ mkdirSync6(dirname9(this.configPath), { recursive: true });
3980
+ writeFileSync6(this.configPath, `${JSON.stringify(state, null, 2)}
3811
3981
  `, "utf8");
3812
3982
  }
3813
3983
  };
@@ -3829,15 +3999,232 @@ var BridgeEventHub = class {
3829
3999
  }
3830
4000
  };
3831
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
+
3832
4201
  // src/daemon/server.ts
3833
4202
  function createDaemonServer(options = {}) {
3834
4203
  const app = Fastify({ logger: true });
3835
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);
3836
4207
  const bridgeDefaults = {
3837
- defaultProvider: options.bridgeDefaults?.defaultProvider ?? "claude-code",
3838
- 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
+ }
3839
4214
  };
3840
- 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
+ }
3841
4228
  const conversation = new CurrentConversationStore(configPath, {
3842
4229
  defaultCwd: bridgeDefaults.defaultWorkspace,
3843
4230
  defaultProviderId: bridgeDefaults.defaultProvider
@@ -3879,6 +4266,10 @@ function createDaemonServer(options = {}) {
3879
4266
  await managedWechatChannel.sendMessage({ chatId, kind: msg.kind, text: msg.text });
3880
4267
  }
3881
4268
  }) : void 0;
4269
+ const tunnelProvider = options.tunnelProvider ?? new RelayTunnelRouter({
4270
+ bridgePort: Number(process.env.BRIDGE_PORT ?? 8787),
4271
+ defaults: bridgeDefaults
4272
+ });
3882
4273
  const messageRouter = channel ? new MessageRouter({
3883
4274
  channel,
3884
4275
  providers: providerAdapters,
@@ -3931,7 +4322,14 @@ function createDaemonServer(options = {}) {
3931
4322
  },
3932
4323
  lastProviderSessions,
3933
4324
  events,
3934
- 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
+ }
3935
4333
  }) : void 0;
3936
4334
  if (channel && messageRouter) {
3937
4335
  channel.onMessage(async (message) => {
@@ -3960,10 +4358,24 @@ function createDaemonServer(options = {}) {
3960
4358
  defaults: bridgeDefaults,
3961
4359
  configPath
3962
4360
  });
4361
+ if (tunnelProvider) {
4362
+ registerTunnelRoutes({
4363
+ app,
4364
+ configPath,
4365
+ defaults: { tunnel: bridgeDefaults.tunnel },
4366
+ tunnelManager: tunnelProvider
4367
+ });
4368
+ }
3963
4369
  registerFsBrowseRoutes({ app });
3964
4370
  app.get("/api/status", async () => ({
3965
4371
  ok: true,
3966
- 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
+ })()
3967
4379
  }));
3968
4380
  app.get("/api/providers/status", async () => providers.getStatus());
3969
4381
  app.post("/api/bridge-events", async (request, reply) => {
@@ -3997,7 +4409,7 @@ function createDaemonServer(options = {}) {
3997
4409
  app.addHook("onClose", async () => {
3998
4410
  await channel?.stop();
3999
4411
  });
4000
- return { app, conversation, sessions: conversation, events, activeUserStore };
4412
+ return { app, conversation, sessions: conversation, events, activeUserStore, tunnelProvider };
4001
4413
  }
4002
4414
 
4003
4415
  // src/daemon/bootstrap.ts
@@ -4011,24 +4423,35 @@ async function startDaemon(options) {
4011
4423
  configPath,
4012
4424
  providers: providerCommands
4013
4425
  });
4014
- 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({
4015
4435
  wechat: config.wechat,
4016
4436
  providerCommands,
4017
- bridgeDefaults: {
4018
- defaultProvider: config.bridge?.defaultProvider ?? "claude-code",
4019
- defaultWorkspace: config.bridge?.defaultWorkspace ?? process.cwd()
4020
- },
4021
- configPath
4437
+ bridgeDefaults,
4438
+ configPath,
4439
+ ...options.tunnelProvider ? { tunnelProvider: options.tunnelProvider } : {}
4022
4440
  });
4023
4441
  await options.attachFrontend(app);
4024
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
+ }
4025
4448
  console.log("claude-codex-wechat listening:");
4026
- console.log(` Local: http://127.0.0.1:${port}`);
4449
+ console.log(` Local: http://127.0.0.1:${actualPort}`);
4027
4450
  for (const ip of listLanIpv4Addresses()) {
4028
- console.log(` Network: http://${ip}:${port}`);
4451
+ console.log(` Network: http://${ip}:${actualPort}`);
4029
4452
  }
4030
4453
  console.log(`config path: ${configPath}`);
4031
- return { app, port, host, configPath };
4454
+ return { app, port: actualPort, host, configPath };
4032
4455
  }
4033
4456
  function listLanIpv4Addresses() {
4034
4457
  return Object.values(networkInterfaces()).flat().filter((iface) => Boolean(iface)).filter((iface) => iface.family === "IPv4" && !iface.internal).map((iface) => iface.address);
@@ -4044,7 +4467,7 @@ async function resolveProviderCommands(providers) {
4044
4467
  }
4045
4468
 
4046
4469
  // src/daemon/staticFrontend.ts
4047
- import { readFileSync as readFileSync6 } from "node:fs";
4470
+ import { readFileSync as readFileSync7 } from "node:fs";
4048
4471
  import { join as join10 } from "node:path";
4049
4472
  import fastifyStatic from "@fastify/static";
4050
4473
  function attachStaticFrontend(webRoot2) {
@@ -4053,7 +4476,7 @@ function attachStaticFrontend(webRoot2) {
4053
4476
  root: webRoot2,
4054
4477
  wildcard: false
4055
4478
  });
4056
- const indexHtml = readFileSync6(join10(webRoot2, "index.html"), "utf8");
4479
+ const indexHtml = readFileSync7(join10(webRoot2, "index.html"), "utf8");
4057
4480
  app.setNotFoundHandler((request, reply) => {
4058
4481
  if (request.url.startsWith("/api")) {
4059
4482
  reply.code(404).send({ error: "not_found" });
@@ -4065,7 +4488,7 @@ function attachStaticFrontend(webRoot2) {
4065
4488
  }
4066
4489
 
4067
4490
  // src/daemon/service.ts
4068
- import { existsSync as existsSync8, statSync } from "node:fs";
4491
+ import { existsSync as existsSync9, statSync } from "node:fs";
4069
4492
  import { mkdir as mkdir4, readFile as readFile6, rm as rm2, writeFile as writeFile5 } from "node:fs/promises";
4070
4493
  import { homedir as homedir8 } from "node:os";
4071
4494
  import { dirname as dirname11, join as join11 } from "node:path";
@@ -4115,13 +4538,13 @@ async function startService(context) {
4115
4538
  async function stopService(context) {
4116
4539
  if (process.platform === "darwin") {
4117
4540
  const spec = buildLaunchdSpec(context);
4118
- if (!existsSync8(spec.plistPath)) return await readServiceStatus(context);
4541
+ if (!existsSync9(spec.plistPath)) return await readServiceStatus(context);
4119
4542
  await runLaunchctl(["bootout", `gui/${process.getuid?.() ?? 0}`, spec.plistPath]).catch(() => void 0);
4120
4543
  return await readServiceStatus(context);
4121
4544
  }
4122
4545
  if (process.platform === "linux") {
4123
4546
  const spec = buildSystemdUserSpec(context);
4124
- if (!existsSync8(spec.unitPath)) return await readServiceStatus(context);
4547
+ if (!existsSync9(spec.unitPath)) return await readServiceStatus(context);
4125
4548
  await runSystemctl(["--user", "stop", spec.unitName]).catch(() => void 0);
4126
4549
  return await readServiceStatus(context);
4127
4550
  }
@@ -4130,7 +4553,7 @@ async function stopService(context) {
4130
4553
  async function uninstallService(context) {
4131
4554
  if (process.platform === "darwin") {
4132
4555
  const spec = buildLaunchdSpec(context);
4133
- if (existsSync8(spec.plistPath)) {
4556
+ if (existsSync9(spec.plistPath)) {
4134
4557
  await runLaunchctl(["bootout", `gui/${process.getuid?.() ?? 0}`, spec.plistPath]).catch(() => void 0);
4135
4558
  await rm2(spec.plistPath, { force: true });
4136
4559
  }
@@ -4138,7 +4561,7 @@ async function uninstallService(context) {
4138
4561
  }
4139
4562
  if (process.platform === "linux") {
4140
4563
  const spec = buildSystemdUserSpec(context);
4141
- if (existsSync8(spec.unitPath)) {
4564
+ if (existsSync9(spec.unitPath)) {
4142
4565
  await runSystemctl(["--user", "disable", "--now", spec.unitName]).catch(() => void 0);
4143
4566
  await rm2(spec.unitPath, { force: true });
4144
4567
  await runSystemctl(["--user", "daemon-reload"]).catch(() => void 0);
@@ -4181,7 +4604,7 @@ async function tailServiceLogs(context) {
4181
4604
  async function readServiceStatus(context) {
4182
4605
  if (process.platform === "darwin") {
4183
4606
  const spec = buildLaunchdSpec(context);
4184
- const installed = existsSync8(spec.plistPath);
4607
+ const installed = existsSync9(spec.plistPath);
4185
4608
  const running = installed ? await runLaunchctl(["print", `gui/${process.getuid?.() ?? 0}/${spec.label}`]).then(() => true, () => false) : false;
4186
4609
  return {
4187
4610
  manager: "launchd",
@@ -4195,7 +4618,7 @@ async function readServiceStatus(context) {
4195
4618
  }
4196
4619
  if (process.platform === "linux") {
4197
4620
  const spec = buildSystemdUserSpec(context);
4198
- const installed = existsSync8(spec.unitPath);
4621
+ const installed = existsSync9(spec.unitPath);
4199
4622
  const running = installed ? await runSystemctl(["--user", "is-active", "--quiet", spec.unitName]).then(() => true, () => false) : false;
4200
4623
  return {
4201
4624
  manager: "systemd-user",
@@ -4331,16 +4754,16 @@ async function runSystemctl(args) {
4331
4754
  await execFileAsync("systemctl", args);
4332
4755
  }
4333
4756
  async function ensureFileExists(path, errorCode) {
4334
- if (!existsSync8(path)) throw new Error(errorCode);
4757
+ if (!existsSync9(path)) throw new Error(errorCode);
4335
4758
  await readFile6(path, "utf8");
4336
4759
  }
4337
4760
  async function tailFile(path, lines) {
4338
- if (!existsSync8(path)) return "";
4761
+ if (!existsSync9(path)) return "";
4339
4762
  const content = await readFile6(path, "utf8");
4340
4763
  return content.split(/\r?\n/).slice(-lines).join("\n").trim();
4341
4764
  }
4342
4765
  async function tailFiles(paths) {
4343
- const readers = paths.filter((path) => existsSync8(path)).map((path) => {
4766
+ const readers = paths.filter((path) => existsSync9(path)).map((path) => {
4344
4767
  const rl = createInterface({
4345
4768
  input: createReadStream(path, { encoding: "utf8", start: Math.max(0, fileSizeHint(path) - 8192) }),
4346
4769
  crlfDelay: Infinity
@@ -4354,7 +4777,7 @@ async function tailFiles(paths) {
4354
4777
  if (process.platform !== "linux" && process.platform !== "darwin") {
4355
4778
  return;
4356
4779
  }
4357
- 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"] });
4358
4781
  child.stdout.on("data", (chunk) => {
4359
4782
  process.stdout.write(String(chunk));
4360
4783
  });
@@ -4427,7 +4850,7 @@ async function main() {
4427
4850
  }
4428
4851
  }
4429
4852
  async function cmdStart() {
4430
- if (!existsSync9(webRoot)) {
4853
+ if (!existsSync10(webRoot)) {
4431
4854
  console.error(`\u627E\u4E0D\u5230\u524D\u7AEF\u6784\u5EFA\u4EA7\u7269: ${webRoot}`);
4432
4855
  console.error("\u8BF7\u91CD\u65B0\u5B89\u88C5\u5B8C\u6574\u7684 npm \u5305\u3002");
4433
4856
  process.exitCode = 1;
@@ -4457,7 +4880,7 @@ async function cmdTail() {
4457
4880
  await tailServiceLogs(createServiceContext());
4458
4881
  }
4459
4882
  async function cmdDaemon() {
4460
- if (!existsSync9(webRoot)) {
4883
+ if (!existsSync10(webRoot)) {
4461
4884
  console.error(`\u627E\u4E0D\u5230\u524D\u7AEF\u6784\u5EFA\u4EA7\u7269: ${webRoot}`);
4462
4885
  process.exitCode = 1;
4463
4886
  return;
@@ -4469,8 +4892,8 @@ async function cmdDaemon() {
4469
4892
  function cmdInit() {
4470
4893
  const configPath = process.env.BRIDGE_CONFIG ?? defaultConfigPath();
4471
4894
  const dir = dirname12(configPath);
4472
- mkdirSync6(dir, { recursive: true });
4473
- if (existsSync9(configPath)) {
4895
+ mkdirSync7(dir, { recursive: true });
4896
+ if (existsSync10(configPath)) {
4474
4897
  console.log(`\u914D\u7F6E\u5DF2\u5B58\u5728\uFF0C\u672A\u8986\u76D6: ${configPath}`);
4475
4898
  return;
4476
4899
  }
@@ -4487,7 +4910,7 @@ function cmdInit() {
4487
4910
  defaultWorkspace: process.cwd()
4488
4911
  }
4489
4912
  };
4490
- writeFileSync6(configPath, `${JSON.stringify(template, null, 2)}
4913
+ writeFileSync7(configPath, `${JSON.stringify(template, null, 2)}
4491
4914
  `, "utf8");
4492
4915
  console.log(`\u5DF2\u521B\u5EFA\u914D\u7F6E: ${configPath}`);
4493
4916
  console.log("\u8BF7\u586B\u5165\u5FAE\u4FE1 token / accountId \u540E\u8FD0\u884C: claude-codex-wechat start");
@@ -4495,13 +4918,13 @@ function cmdInit() {
4495
4918
  async function cmdDoctor() {
4496
4919
  const configPath = process.env.BRIDGE_CONFIG ?? defaultConfigPath();
4497
4920
  console.log("claude-codex-wechat doctor\n");
4498
- report("\u914D\u7F6E\u6587\u4EF6", existsSync9(configPath) ? configPath : `\u7F3A\u5931 (${configPath})\uFF0C\u8FD0\u884C init \u521B\u5EFA`);
4499
- 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})`);
4500
4923
  const claude = await findExecutable("claude");
4501
4924
  report("claude \u53EF\u6267\u884C", claude ?? "\u672A\u627E\u5230\uFF0C\u9700\u5148\u5B89\u88C5\u5E76\u767B\u5F55 Claude Code");
4502
4925
  const codex = await findExecutable("codex");
4503
4926
  report("codex \u53EF\u6267\u884C", codex ?? "\u672A\u627E\u5230\uFF08\u4EC5\u4F7F\u7528 Claude \u65F6\u53EF\u5FFD\u7565\uFF09");
4504
- if (existsSync9(configPath)) {
4927
+ if (existsSync10(configPath)) {
4505
4928
  const config = loadBridgeConfig(configPath);
4506
4929
  const wechat = config.wechat;
4507
4930
  report("\u5FAE\u4FE1\u542F\u7528", wechat?.enabled ? "\u662F" : "\u5426");
@@ -4511,12 +4934,12 @@ async function cmdDoctor() {
4511
4934
  }
4512
4935
  function cmdPrintConfig() {
4513
4936
  const configPath = process.env.BRIDGE_CONFIG ?? defaultConfigPath();
4514
- if (!existsSync9(configPath)) {
4937
+ if (!existsSync10(configPath)) {
4515
4938
  console.error(`\u914D\u7F6E\u4E0D\u5B58\u5728: ${configPath}\uFF08\u8FD0\u884C init \u521B\u5EFA\uFF09`);
4516
4939
  process.exitCode = 1;
4517
4940
  return;
4518
4941
  }
4519
- console.log(readFileSync7(configPath, "utf8"));
4942
+ console.log(readFileSync8(configPath, "utf8"));
4520
4943
  }
4521
4944
  async function cmdUninstall() {
4522
4945
  const status = await uninstallService(createServiceContext());