claude-codex-wechat 0.1.29 → 0.1.30

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.
@@ -0,0 +1,91 @@
1
+ import { createRequire as __cjsCreateRequire } from 'node:module'; const require = __cjsCreateRequire(import.meta.url);
2
+
3
+ // src/mcp/mediaServer.ts
4
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
5
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
6
+ import { z } from "zod";
7
+ var BRIDGE_API_URL = process.env.BRIDGE_API_URL || "http://localhost:4800";
8
+ var server = new McpServer({
9
+ name: "wechat-media",
10
+ version: "1.0.0"
11
+ });
12
+ async function sendMedia(kind, filePath, fileName) {
13
+ const response = await fetch(`${BRIDGE_API_URL}/api/channel/send-media`, {
14
+ method: "POST",
15
+ headers: { "content-type": "application/json" },
16
+ body: JSON.stringify({ kind, filePath, fileName })
17
+ });
18
+ if (!response.ok) {
19
+ const text = await response.text();
20
+ throw new Error(`\u53D1\u9001\u5931\u8D25 (HTTP ${response.status}): ${text}`);
21
+ }
22
+ const result = await response.json();
23
+ if (!result.ok) {
24
+ throw new Error(`\u53D1\u9001\u5931\u8D25: ${result.error ?? "\u672A\u77E5\u9519\u8BEF"}`);
25
+ }
26
+ return "\u53D1\u9001\u6210\u529F";
27
+ }
28
+ server.tool(
29
+ "send_image",
30
+ "\u53D1\u9001\u56FE\u7247\u6587\u4EF6\u5230\u5F53\u524D\u5FAE\u4FE1\u5BF9\u8BDD\u3002\u4F20\u5165\u56FE\u7247\u6587\u4EF6\u7684\u7EDD\u5BF9\u8DEF\u5F84\u3002",
31
+ { filePath: z.string().describe("\u56FE\u7247\u6587\u4EF6\u7684\u7EDD\u5BF9\u8DEF\u5F84\uFF0C\u5982 /path/to/image.png") },
32
+ async ({ filePath }) => {
33
+ try {
34
+ const msg = await sendMedia("image", filePath);
35
+ return { content: [{ type: "text", text: msg }] };
36
+ } catch (e) {
37
+ return { content: [{ type: "text", text: e.message }], isError: true };
38
+ }
39
+ }
40
+ );
41
+ server.tool(
42
+ "send_video",
43
+ "\u53D1\u9001\u89C6\u9891\u6587\u4EF6\u5230\u5F53\u524D\u5FAE\u4FE1\u5BF9\u8BDD\u3002\u4F20\u5165\u89C6\u9891\u6587\u4EF6\u7684\u7EDD\u5BF9\u8DEF\u5F84\u3002",
44
+ { filePath: z.string().describe("\u89C6\u9891\u6587\u4EF6\u7684\u7EDD\u5BF9\u8DEF\u5F84\uFF0C\u5982 /path/to/video.mp4") },
45
+ async ({ filePath }) => {
46
+ try {
47
+ const msg = await sendMedia("video", filePath);
48
+ return { content: [{ type: "text", text: msg }] };
49
+ } catch (e) {
50
+ return { content: [{ type: "text", text: e.message }], isError: true };
51
+ }
52
+ }
53
+ );
54
+ server.tool(
55
+ "send_audio",
56
+ "\u53D1\u9001\u97F3\u9891\u6587\u4EF6\u5230\u5F53\u524D\u5FAE\u4FE1\u5BF9\u8BDD\u3002\u4F20\u5165\u97F3\u9891\u6587\u4EF6\u7684\u7EDD\u5BF9\u8DEF\u5F84\u3002\u97F3\u9891\u6587\u4EF6\u4EE5\u6587\u4EF6\u9644\u4EF6\u5F62\u5F0F\u53D1\u9001\uFF0C\u7528\u6237\u53EF\u70B9\u51FB\u64AD\u653E\u3002",
57
+ { filePath: z.string().describe("\u97F3\u9891\u6587\u4EF6\u7684\u7EDD\u5BF9\u8DEF\u5F84\uFF0C\u5982 /path/to/audio.mp3") },
58
+ async ({ filePath }) => {
59
+ try {
60
+ const fileName = filePath.split("/").pop() || "audio.mp3";
61
+ const msg = await sendMedia("file", filePath, fileName);
62
+ return { content: [{ type: "text", text: msg }] };
63
+ } catch (e) {
64
+ return { content: [{ type: "text", text: e.message }], isError: true };
65
+ }
66
+ }
67
+ );
68
+ server.tool(
69
+ "send_file",
70
+ "\u53D1\u9001\u6587\u4EF6\u5230\u5F53\u524D\u5FAE\u4FE1\u5BF9\u8BDD\u3002\u4F20\u5165\u6587\u4EF6\u7684\u7EDD\u5BF9\u8DEF\u5F84\u548C\u53EF\u9009\u7684\u663E\u793A\u6587\u4EF6\u540D\u3002",
71
+ {
72
+ filePath: z.string().describe("\u6587\u4EF6\u7684\u7EDD\u5BF9\u8DEF\u5F84\uFF0C\u5982 /path/to/document.pdf"),
73
+ fileName: z.string().optional().describe("\u663E\u793A\u7ED9\u7528\u6237\u7684\u6587\u4EF6\u540D\uFF08\u53EF\u9009\uFF0C\u9ED8\u8BA4\u4F7F\u7528\u539F\u6587\u4EF6\u540D\uFF09")
74
+ },
75
+ async ({ filePath, fileName }) => {
76
+ try {
77
+ const msg = await sendMedia("file", filePath, fileName);
78
+ return { content: [{ type: "text", text: msg }] };
79
+ } catch (e) {
80
+ return { content: [{ type: "text", text: e.message }], isError: true };
81
+ }
82
+ }
83
+ );
84
+ async function main() {
85
+ const transport = new StdioServerTransport();
86
+ await server.connect(transport);
87
+ }
88
+ main().catch((err) => {
89
+ console.error("[wechat-media-mcp] fatal:", err);
90
+ process.exit(1);
91
+ });
@@ -2,17 +2,18 @@
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 existsSync10, readFileSync as readFileSync9 } from "node:fs";
5
+ import { existsSync as existsSync11, readFileSync as readFileSync10 } from "node:fs";
6
6
  import { dirname as dirname9, join as join14 } from "node:path";
7
- import { fileURLToPath as fileURLToPath2 } from "node:url";
7
+ import { fileURLToPath as fileURLToPath3 } from "node:url";
8
8
 
9
9
  // src/daemon/bootstrap.ts
10
10
  import { networkInterfaces } from "node:os";
11
11
 
12
12
  // src/daemon/server.ts
13
- import { mkdtempSync } from "node:fs";
13
+ import { mkdtempSync, existsSync as existsSync9, writeFileSync as writeFileSync2 } from "node:fs";
14
14
  import { tmpdir as tmpdir2 } from "node:os";
15
15
  import { dirname as dirname7, join as join11 } from "node:path";
16
+ import { fileURLToPath as fileURLToPath2 } from "node:url";
16
17
  import Fastify from "fastify";
17
18
 
18
19
  // src/channels/platforms.ts
@@ -1866,6 +1867,105 @@ var WeixinDirectApiClient = class {
1866
1867
  throw new Error(`weixin_send_typing_failed:${payload.ret ?? -1}:${payload.errmsg ?? "unknown_error"}`);
1867
1868
  }
1868
1869
  }
1870
+ /** Request a CDN upload URL from the iLink platform. */
1871
+ async getUploadUrl(input) {
1872
+ const response = await this.fetchImpl(`${this.baseUrl}/ilink/bot/getuploadurl`, {
1873
+ method: "POST",
1874
+ headers: {
1875
+ "content-type": "application/json",
1876
+ AuthorizationType: "ilink_bot_token",
1877
+ Authorization: `Bearer ${this.botToken}`,
1878
+ "X-WECHAT-UIN": this.wechatUin
1879
+ },
1880
+ body: JSON.stringify({
1881
+ filekey: input.filekey,
1882
+ media_type: input.mediaType,
1883
+ to_user_id: input.toUserId,
1884
+ rawsize: input.rawsize,
1885
+ rawfilemd5: input.rawfilemd5,
1886
+ filesize: input.filesize,
1887
+ no_need_thumb: input.noNeedThumb ?? true,
1888
+ aeskey: input.aeskey,
1889
+ ...input.contextToken ? { context_token: input.contextToken } : {},
1890
+ base_info: { channel_version: this.channelVersion }
1891
+ })
1892
+ });
1893
+ if (!response.ok) {
1894
+ const text = await response.text().catch(() => "");
1895
+ throw new Error(`weixin_get_upload_url_failed:http_${response.status}:${text.slice(0, 200)}`);
1896
+ }
1897
+ const payload = await response.json();
1898
+ if ((payload.errcode ?? 0) !== 0 || (payload.ret ?? 0) !== 0) {
1899
+ throw new Error(`weixin_get_upload_url_failed:${payload.errcode ?? payload.ret ?? -1}:${payload.errmsg ?? "unknown_error"}`);
1900
+ }
1901
+ if (!payload.upload_param && !payload.upload_full_url) {
1902
+ throw new Error("weixin_get_upload_url_failed:no_upload_param");
1903
+ }
1904
+ return {
1905
+ uploadParam: payload.upload_param ?? "",
1906
+ uploadFullUrl: payload.upload_full_url
1907
+ };
1908
+ }
1909
+ /**
1910
+ * Send a media message (image/voice/file/video) to a user.
1911
+ * The media must already be uploaded to CDN; pass the resulting CDNMedia reference.
1912
+ */
1913
+ async sendMediaMessage(input) {
1914
+ const item = this.buildMediaItem(input);
1915
+ const response = await this.fetchImpl(`${this.baseUrl}/ilink/bot/sendmessage`, {
1916
+ method: "POST",
1917
+ headers: {
1918
+ "content-type": "application/json",
1919
+ AuthorizationType: "ilink_bot_token",
1920
+ Authorization: `Bearer ${this.botToken}`,
1921
+ "X-WECHAT-UIN": this.wechatUin
1922
+ },
1923
+ body: JSON.stringify({
1924
+ msg: {
1925
+ from_user_id: "",
1926
+ to_user_id: input.toUserId,
1927
+ client_id: randomUUID(),
1928
+ message_type: 2,
1929
+ message_state: 2,
1930
+ item_list: [item],
1931
+ context_token: input.contextToken
1932
+ },
1933
+ base_info: { channel_version: this.channelVersion }
1934
+ })
1935
+ });
1936
+ if (!response.ok) {
1937
+ throw new Error(`weixin_send_media_failed:${response.status}`);
1938
+ }
1939
+ const payload = await response.json();
1940
+ if ((payload.errcode ?? 0) !== 0 || (payload.ret ?? 0) !== 0) {
1941
+ throw new Error(`weixin_send_media_failed:${payload.errcode ?? payload.ret ?? -1}:${payload.errmsg ?? "unknown_error"}`);
1942
+ }
1943
+ }
1944
+ buildMediaItem(input) {
1945
+ const aesKeyBase64 = Buffer.from(input.aesKeyHex).toString("base64");
1946
+ const cdnMedia = {
1947
+ encrypt_query_param: input.downloadParam,
1948
+ aes_key: aesKeyBase64,
1949
+ encrypt_type: 1
1950
+ };
1951
+ switch (input.itemType) {
1952
+ case 2:
1953
+ return { type: 2, image_item: { media: cdnMedia, mid_size: input.ciphertextSize } };
1954
+ case 3:
1955
+ return { type: 3, voice_item: {
1956
+ media: cdnMedia,
1957
+ ...input.voiceInfo ? {
1958
+ encode_type: input.voiceInfo.encodeType,
1959
+ playtime: input.voiceInfo.playtime,
1960
+ ...input.voiceInfo.sampleRate ? { sample_rate: input.voiceInfo.sampleRate } : {}
1961
+ } : {}
1962
+ } };
1963
+ case 4:
1964
+ return { type: 4, file_item: { media: cdnMedia, file_name: input.fileName ?? "file", len: String(input.rawSize) } };
1965
+ case 5:
1966
+ return { type: 5, video_item: { media: cdnMedia, video_size: input.ciphertextSize } };
1967
+ }
1968
+ }
1869
1969
  };
1870
1970
 
1871
1971
  // src/channels/weixin-direct/adapter.ts
@@ -2021,18 +2121,49 @@ var WeixinDirectAdapter = class _WeixinDirectAdapter {
2021
2121
  if (this.options.stateStore && !this.options.stateStore.canSend(message.chatId)) {
2022
2122
  throw new Error(`weixin_push_quota_exceeded:${message.chatId}`);
2023
2123
  }
2024
- const chunks = chunkText(message.text ?? "", MAX_TEXT_LENGTH);
2025
- const chunkDelayMs = this.options.chunkDelayMs ?? DEFAULT_CHUNK_DELAY_MS;
2026
- for (let i = 0; i < chunks.length; i += 1) {
2027
- if (i > 0 && chunkDelayMs > 0) await delay2(chunkDelayMs);
2028
- await this.options.api.sendTextMessage({
2029
- toUserId: message.chatId,
2030
- text: chunks[i],
2031
- contextToken
2032
- });
2124
+ const mediaKinds = /* @__PURE__ */ new Set(["image", "video", "audio", "file"]);
2125
+ if (mediaKinds.has(message.kind)) {
2126
+ await this.sendMediaMessage(message, contextToken);
2127
+ } else {
2128
+ const chunks = chunkText(message.text ?? "", MAX_TEXT_LENGTH);
2129
+ const chunkDelayMs = this.options.chunkDelayMs ?? DEFAULT_CHUNK_DELAY_MS;
2130
+ for (let i = 0; i < chunks.length; i += 1) {
2131
+ if (i > 0 && chunkDelayMs > 0) await delay2(chunkDelayMs);
2132
+ await this.options.api.sendTextMessage({
2133
+ toUserId: message.chatId,
2134
+ text: chunks[i],
2135
+ contextToken
2136
+ });
2137
+ }
2033
2138
  }
2034
2139
  this.options.stateStore?.recordSent(message.chatId);
2035
2140
  }
2141
+ async sendMediaMessage(message, contextToken) {
2142
+ const uploader = this.options.mediaUploader;
2143
+ const api = this.options.api;
2144
+ if (!uploader || !api.sendMediaMessage) {
2145
+ throw new Error("weixin_media_upload_unavailable");
2146
+ }
2147
+ if (!message.filePath) {
2148
+ throw new Error("weixin_media_no_file_path");
2149
+ }
2150
+ const result = await uploader.upload(message.filePath, { contextToken, mediaType: mediaKindToMediaType(message.kind), toUserId: message.chatId });
2151
+ if (!result.ok) {
2152
+ throw new Error(`weixin_media_upload_failed:${result.reason}`);
2153
+ }
2154
+ const itemType = mediaKindToItemType(message.kind);
2155
+ await api.sendMediaMessage({
2156
+ toUserId: message.chatId,
2157
+ contextToken,
2158
+ itemType,
2159
+ downloadParam: result.downloadParam,
2160
+ aesKeyHex: result.aesKeyHex,
2161
+ rawSize: result.rawSize,
2162
+ ciphertextSize: result.ciphertextSize,
2163
+ fileName: message.fileName,
2164
+ ...result.voiceInfo ? { voiceInfo: result.voiceInfo } : {}
2165
+ });
2166
+ }
2036
2167
  async setTyping(chatId, active) {
2037
2168
  await this.typing?.set(chatId, active);
2038
2169
  }
@@ -2193,13 +2324,41 @@ function chunkText(text, limit) {
2193
2324
  }
2194
2325
  return chunks.length > 0 ? chunks : [""];
2195
2326
  }
2327
+ function mediaKindToItemType(kind) {
2328
+ switch (kind) {
2329
+ case "image":
2330
+ return 2;
2331
+ case "audio":
2332
+ return 3;
2333
+ case "file":
2334
+ return 4;
2335
+ case "video":
2336
+ return 5;
2337
+ default:
2338
+ throw new Error(`weixin_unknown_media_kind:${kind}`);
2339
+ }
2340
+ }
2341
+ function mediaKindToMediaType(kind) {
2342
+ switch (kind) {
2343
+ case "image":
2344
+ return 1;
2345
+ case "video":
2346
+ return 2;
2347
+ case "file":
2348
+ return 3;
2349
+ case "audio":
2350
+ return 4;
2351
+ default:
2352
+ return 3;
2353
+ }
2354
+ }
2196
2355
 
2197
2356
  // src/channels/weixin-direct/mediaDownloader.ts
2198
2357
  import { mkdirSync as mkdirSync2, writeFileSync } from "node:fs";
2199
2358
  import { dirname as dirname5 } from "node:path";
2200
2359
 
2201
2360
  // src/channels/weixin-direct/mediaCrypto.ts
2202
- import { createDecipheriv } from "node:crypto";
2361
+ import { createCipheriv, createDecipheriv, randomBytes as randomBytes3 } from "node:crypto";
2203
2362
  function decodeAesKey(encoded) {
2204
2363
  if (/^[0-9a-fA-F]{32}$/.test(encoded)) {
2205
2364
  return Buffer.from(encoded, "hex");
@@ -2223,6 +2382,19 @@ function decryptAesEcb(ciphertext, key) {
2223
2382
  const decipher = createDecipheriv("aes-128-ecb", key, null);
2224
2383
  return Buffer.concat([decipher.update(ciphertext), decipher.final()]);
2225
2384
  }
2385
+ function encryptAesEcb(plaintext, key) {
2386
+ if (key.length !== 16) {
2387
+ throw new Error(`weixin_media_aes_key_size:${key.length}`);
2388
+ }
2389
+ const cipher = createCipheriv("aes-128-ecb", key, null);
2390
+ return Buffer.concat([cipher.update(plaintext), cipher.final()]);
2391
+ }
2392
+ function generateAesKey() {
2393
+ return randomBytes3(16);
2394
+ }
2395
+ function encodeAesKeyHex(key) {
2396
+ return key.toString("hex");
2397
+ }
2226
2398
 
2227
2399
  // src/channels/weixin-direct/mediaDownloader.ts
2228
2400
  var CDN_BASE_URL = "https://novac2c.cdn.weixin.qq.com/c2c";
@@ -2265,6 +2437,143 @@ var WeixinMediaDownloader = class {
2265
2437
  }
2266
2438
  };
2267
2439
 
2440
+ // src/channels/weixin-direct/mediaUploader.ts
2441
+ import { createHash, randomBytes as randomBytes4 } from "node:crypto";
2442
+ import { readFileSync as readFileSync4 } from "node:fs";
2443
+
2444
+ // src/channels/weixin-direct/voiceInfo.ts
2445
+ function encodeTypeFromExt(ext) {
2446
+ switch (ext.toLowerCase()) {
2447
+ case ".mp3":
2448
+ return 7;
2449
+ case ".amr":
2450
+ return 5;
2451
+ case ".silk":
2452
+ case ".slk":
2453
+ return 6;
2454
+ case ".wav":
2455
+ case ".pcm":
2456
+ return 1;
2457
+ case ".ogg":
2458
+ case ".opus":
2459
+ return 8;
2460
+ case ".spx":
2461
+ return 4;
2462
+ default:
2463
+ return 7;
2464
+ }
2465
+ }
2466
+ function estimatePlaytime(buf, ext) {
2467
+ switch (ext.toLowerCase()) {
2468
+ case ".wav": {
2469
+ if (buf.length < 44) return 1e3;
2470
+ const byteRate = buf.readUInt32LE(28);
2471
+ if (byteRate === 0) return 1e3;
2472
+ const dataSize = buf.length - 44;
2473
+ return Math.round(dataSize / byteRate * 1e3);
2474
+ }
2475
+ case ".mp3": {
2476
+ return Math.round(buf.length * 8 / 128e3 * 1e3);
2477
+ }
2478
+ case ".amr": {
2479
+ const frames = Math.floor((buf.length - 6) / 32);
2480
+ return frames * 20;
2481
+ }
2482
+ default:
2483
+ return Math.round(buf.length * 8 / 16e3 * 1e3);
2484
+ }
2485
+ }
2486
+ function detectVoiceInfo(buf, fileName) {
2487
+ const ext = fileName.includes(".") ? "." + fileName.split(".").pop() : ".mp3";
2488
+ return {
2489
+ encodeType: encodeTypeFromExt(ext),
2490
+ playtime: estimatePlaytime(buf, ext),
2491
+ sampleRate: ext === ".wav" && buf.length >= 28 ? buf.readUInt32LE(24) : void 0
2492
+ };
2493
+ }
2494
+
2495
+ // src/channels/weixin-direct/mediaUploader.ts
2496
+ var CDN_UPLOAD_BASE_URL = "https://novac2c.cdn.weixin.qq.com/c2c";
2497
+ var WeixinMediaUploader = class {
2498
+ cdnBaseUrl;
2499
+ fetchImpl;
2500
+ getUploadUrl;
2501
+ constructor(options) {
2502
+ this.cdnBaseUrl = (options.cdnBaseUrl ?? CDN_UPLOAD_BASE_URL).replace(/\/+$/, "");
2503
+ this.fetchImpl = options.fetchImpl ?? fetch;
2504
+ this.getUploadUrl = options.getUploadUrl;
2505
+ }
2506
+ async upload(filePath, options) {
2507
+ let plaintext;
2508
+ try {
2509
+ plaintext = readFileSync4(filePath);
2510
+ } catch {
2511
+ return { ok: false, reason: "read_failed" };
2512
+ }
2513
+ const rawsize = plaintext.length;
2514
+ const rawfilemd5 = createHash("md5").update(plaintext).digest("hex");
2515
+ const key = generateAesKey();
2516
+ const aeskeyHex = encodeAesKeyHex(key);
2517
+ let ciphertext;
2518
+ try {
2519
+ ciphertext = encryptAesEcb(plaintext, key);
2520
+ } catch {
2521
+ return { ok: false, reason: "encrypt_failed" };
2522
+ }
2523
+ const filesize = ciphertext.length;
2524
+ const filekey = randomBytes4(16).toString("hex");
2525
+ let uploadParam;
2526
+ let uploadFullUrl;
2527
+ try {
2528
+ const result = await this.getUploadUrl({
2529
+ filekey,
2530
+ rawsize,
2531
+ rawfilemd5,
2532
+ filesize,
2533
+ aeskey: aeskeyHex,
2534
+ noNeedThumb: true,
2535
+ ...options?.contextToken ? { contextToken: options.contextToken } : {},
2536
+ ...options?.mediaType != null ? { mediaType: options.mediaType } : {},
2537
+ ...options?.toUserId ? { toUserId: options.toUserId } : {}
2538
+ });
2539
+ uploadParam = result.uploadParam;
2540
+ uploadFullUrl = result.uploadFullUrl;
2541
+ } catch (e) {
2542
+ const msg = e instanceof Error ? e.message : "";
2543
+ return { ok: false, reason: msg || "get_upload_url_failed" };
2544
+ }
2545
+ const uploadUrl = uploadFullUrl || `${this.cdnBaseUrl}/upload?encrypted_query_param=${encodeURIComponent(uploadParam)}&filekey=${encodeURIComponent(filekey)}`;
2546
+ let response;
2547
+ try {
2548
+ response = await this.fetchImpl(uploadUrl, {
2549
+ method: "POST",
2550
+ headers: { "content-type": "application/octet-stream" },
2551
+ body: new Uint8Array(ciphertext),
2552
+ signal: AbortSignal.timeout(12e4)
2553
+ });
2554
+ } catch {
2555
+ return { ok: false, reason: "upload_failed" };
2556
+ }
2557
+ if (!response.ok) {
2558
+ return { ok: false, reason: `upload_http_${response.status}` };
2559
+ }
2560
+ const encryptedParam = response.headers.get("x-encrypted-param") ?? "";
2561
+ if (!encryptedParam) {
2562
+ return { ok: false, reason: "no_encrypted_param_in_response" };
2563
+ }
2564
+ const isAudio = options?.mediaType === 4;
2565
+ const voiceInfo = isAudio ? detectVoiceInfo(plaintext, filePath) : void 0;
2566
+ return {
2567
+ ok: true,
2568
+ downloadParam: encryptedParam,
2569
+ aesKeyHex: aeskeyHex,
2570
+ rawSize: rawsize,
2571
+ ciphertextSize: filesize,
2572
+ ...voiceInfo ? { voiceInfo } : {}
2573
+ };
2574
+ }
2575
+ };
2576
+
2268
2577
  // src/channels/weixin-direct/managedAdapter.ts
2269
2578
  var ManagedWeixinDirectAdapter = class {
2270
2579
  id = "weixin-managed";
@@ -2342,13 +2651,18 @@ function createWeixinAdapter(config, stateStore, mediaDir) {
2342
2651
  if (config?.enabled !== true) return null;
2343
2652
  if (!config.baseUrl || !config.token) return null;
2344
2653
  const wechatUin = buildTransientWeixinUin();
2654
+ const apiClient = new WeixinDirectApiClient({
2655
+ baseUrl: config.baseUrl,
2656
+ botToken: config.token,
2657
+ wechatUin
2658
+ });
2659
+ const mediaUploader = new WeixinMediaUploader({
2660
+ getUploadUrl: (input) => apiClient.getUploadUrl(input)
2661
+ });
2345
2662
  return new WeixinDirectAdapter({
2346
- api: new WeixinDirectApiClient({
2347
- baseUrl: config.baseUrl,
2348
- botToken: config.token,
2349
- wechatUin
2350
- }),
2663
+ api: apiClient,
2351
2664
  stateStore,
2665
+ mediaUploader,
2352
2666
  ...mediaDir ? { mediaDownloader: new WeixinMediaDownloader(), mediaDir } : {}
2353
2667
  });
2354
2668
  }
@@ -2387,7 +2701,7 @@ var WeixinOutboundGate = class _WeixinOutboundGate {
2387
2701
  const buffered = this.pendingLast.get(chatId);
2388
2702
  if (buffered) {
2389
2703
  this.pendingLast.delete(chatId);
2390
- await this.options.send(chatId, { kind: buffered.kind, text: buffered.text + CONTINUATION_HINT });
2704
+ await this.options.send(chatId, appendHintIfText(buffered));
2391
2705
  this.hintedReplies.set(chatId, Date.now());
2392
2706
  store.enqueueOutbound(chatId, message);
2393
2707
  return;
@@ -2428,7 +2742,7 @@ var WeixinOutboundGate = class _WeixinOutboundGate {
2428
2742
  const isLastSlot = quota.remaining === 1;
2429
2743
  const hasMore = pending.length > 1;
2430
2744
  if (isLastSlot && hasMore) {
2431
- await this.options.send(chatId, { kind: next.kind, text: next.text + CONTINUATION_HINT });
2745
+ await this.options.send(chatId, appendHintIfText(next));
2432
2746
  this.hintedReplies.set(chatId, Date.now());
2433
2747
  store.shiftOutbound(chatId);
2434
2748
  break;
@@ -2439,6 +2753,11 @@ var WeixinOutboundGate = class _WeixinOutboundGate {
2439
2753
  if (!store.hasPendingOutbound(chatId)) this.hintedReplies.delete(chatId);
2440
2754
  }
2441
2755
  };
2756
+ var MEDIA_KINDS = /* @__PURE__ */ new Set(["image", "video", "audio", "file"]);
2757
+ function appendHintIfText(item) {
2758
+ if (MEDIA_KINDS.has(item.kind)) return item;
2759
+ return { ...item, text: item.text + CONTINUATION_HINT };
2760
+ }
2442
2761
 
2443
2762
  // src/providers/claude-code/claudeStreamingRunner.ts
2444
2763
  import { spawn as spawn3 } from "node:child_process";
@@ -2636,10 +2955,12 @@ var ClaudeStreamingRunner = class {
2636
2955
  command;
2637
2956
  spawner;
2638
2957
  capabilityProbe;
2958
+ mcpConfigPath;
2639
2959
  constructor(input = {}) {
2640
2960
  this.command = input.command ?? "claude";
2641
2961
  this.spawner = input.spawner ?? defaultClaudeStreamSpawner;
2642
2962
  this.capabilityProbe = input.capabilityProbe ?? probeAppendSystemPromptSupport;
2963
+ this.mcpConfigPath = input.mcpConfigPath;
2643
2964
  }
2644
2965
  async startSession(input) {
2645
2966
  const resumeId = input.options?.providerSessionId;
@@ -2749,12 +3070,12 @@ var ClaudeStreamingRunner = class {
2749
3070
  }
2750
3071
  ensureHandle(session) {
2751
3072
  if (session.handle) return session.handle;
2752
- const args = buildStreamingArgs(session);
3073
+ const args = buildStreamingArgs(session, this.mcpConfigPath);
2753
3074
  session.handle = this.spawner({ command: this.command, args, cwd: session.cwd });
2754
3075
  return session.handle;
2755
3076
  }
2756
3077
  };
2757
- function buildStreamingArgs(session) {
3078
+ function buildStreamingArgs(session, mcpConfigPath) {
2758
3079
  const args = [
2759
3080
  "-p",
2760
3081
  "--input-format",
@@ -2765,7 +3086,8 @@ function buildStreamingArgs(session) {
2765
3086
  "--verbose",
2766
3087
  "--dangerously-skip-permissions",
2767
3088
  ...session.supportsAppendSystemPrompt ? ["--append-system-prompt", BRIDGE_APPEND_SYSTEM_PROMPT] : [],
2768
- "--replay-user-messages"
3089
+ "--replay-user-messages",
3090
+ ...mcpConfigPath ? ["--mcp-config", mcpConfigPath] : []
2769
3091
  ];
2770
3092
  if (session.resumeId) {
2771
3093
  args.push("--resume", session.resumeId);
@@ -3310,7 +3632,7 @@ var CodexProvider = class {
3310
3632
  // src/providers/defaultProviders.ts
3311
3633
  function createDefaultProviders(input = {}) {
3312
3634
  return [
3313
- new ClaudeCodeProvider({ runner: new ClaudeStreamingRunner({ command: input.claudeCommand }) }),
3635
+ new ClaudeCodeProvider({ runner: new ClaudeStreamingRunner({ command: input.claudeCommand, mcpConfigPath: input.mcpConfigPath }) }),
3314
3636
  new CodexProvider({ runner: new CodexInteractiveRunner({ command: input.codexCommand }) })
3315
3637
  ];
3316
3638
  }
@@ -3502,7 +3824,7 @@ function buildBridgeCommandHelpMarkdown(input) {
3502
3824
  }
3503
3825
 
3504
3826
  // src/session/currentConversationStore.ts
3505
- import { existsSync as existsSync6, readFileSync as readFileSync4 } from "node:fs";
3827
+ import { existsSync as existsSync6, readFileSync as readFileSync5 } from "node:fs";
3506
3828
  import { nanoid as nanoid2 } from "nanoid";
3507
3829
  var CurrentConversationStore = class {
3508
3830
  constructor(configPath, defaults) {
@@ -3562,7 +3884,7 @@ var CurrentConversationStore = class {
3562
3884
  }
3563
3885
  readState() {
3564
3886
  if (!existsSync6(this.configPath)) return {};
3565
- const raw = JSON.parse(readFileSync4(this.configPath, "utf8"));
3887
+ const raw = JSON.parse(readFileSync5(this.configPath, "utf8"));
3566
3888
  return raw && typeof raw === "object" ? raw : {};
3567
3889
  }
3568
3890
  writeCurrent(record) {
@@ -3633,7 +3955,12 @@ var MessageRouter = class _MessageRouter {
3633
3955
  */
3634
3956
  async sendToChat(message) {
3635
3957
  if (this.options.outboundGate) {
3636
- await this.options.outboundGate.deliver(message.chatId, { kind: message.kind, text: message.text });
3958
+ await this.options.outboundGate.deliver(message.chatId, {
3959
+ kind: message.kind,
3960
+ text: message.text,
3961
+ ...message.filePath ? { filePath: message.filePath } : {},
3962
+ ...message.fileName ? { fileName: message.fileName } : {}
3963
+ });
3637
3964
  } else {
3638
3965
  await this.options.channel.sendMessage(message);
3639
3966
  }
@@ -4121,7 +4448,7 @@ function composeInboundText(content) {
4121
4448
  }
4122
4449
 
4123
4450
  // src/storage/lastProviderSessionStore.ts
4124
- import { existsSync as existsSync7, readFileSync as readFileSync5 } from "node:fs";
4451
+ import { existsSync as existsSync7, readFileSync as readFileSync6 } from "node:fs";
4125
4452
  var LastProviderSessionStore = class {
4126
4453
  constructor(configPath) {
4127
4454
  this.configPath = configPath;
@@ -4148,7 +4475,7 @@ var LastProviderSessionStore = class {
4148
4475
  }
4149
4476
  readState() {
4150
4477
  if (!existsSync7(this.configPath)) return {};
4151
- const raw = JSON.parse(readFileSync5(this.configPath, "utf8"));
4478
+ const raw = JSON.parse(readFileSync6(this.configPath, "utf8"));
4152
4479
  return raw && typeof raw === "object" ? raw : {};
4153
4480
  }
4154
4481
  writeState(state) {
@@ -4158,7 +4485,7 @@ var LastProviderSessionStore = class {
4158
4485
  };
4159
4486
 
4160
4487
  // src/storage/runtimeUserStore.ts
4161
- import { readFileSync as readFileSync6, existsSync as existsSync8 } from "node:fs";
4488
+ import { readFileSync as readFileSync7, existsSync as existsSync8 } from "node:fs";
4162
4489
  import { nanoid as nanoid3 } from "nanoid";
4163
4490
  var RuntimeUserStore = class {
4164
4491
  constructor(configPath) {
@@ -4219,7 +4546,7 @@ var RuntimeUserStore = class {
4219
4546
  }
4220
4547
  readState() {
4221
4548
  if (!existsSync8(this.configPath)) return {};
4222
- const raw = JSON.parse(readFileSync6(this.configPath, "utf8"));
4549
+ const raw = JSON.parse(readFileSync7(this.configPath, "utf8"));
4223
4550
  return raw && typeof raw === "object" ? raw : {};
4224
4551
  }
4225
4552
  writeState(state) {
@@ -4551,13 +4878,13 @@ var RelayTunnelRouter = class {
4551
4878
  };
4552
4879
 
4553
4880
  // src/shared/version.ts
4554
- import { readFileSync as readFileSync7 } from "node:fs";
4881
+ import { readFileSync as readFileSync8 } from "node:fs";
4555
4882
  import { dirname as dirname6, join as join10 } from "node:path";
4556
4883
  import { fileURLToPath } from "node:url";
4557
4884
  function readClientVersion() {
4558
4885
  try {
4559
4886
  const here2 = dirname6(fileURLToPath(import.meta.url));
4560
- const pkg = JSON.parse(readFileSync7(join10(here2, "..", "..", "package.json"), "utf8"));
4887
+ const pkg = JSON.parse(readFileSync8(join10(here2, "..", "..", "package.json"), "utf8"));
4561
4888
  return typeof pkg.version === "string" && pkg.version ? pkg.version : "0.0.0";
4562
4889
  } catch {
4563
4890
  return "0.0.0";
@@ -4612,9 +4939,27 @@ function createDaemonServer(options = {}) {
4612
4939
  const activeUserStore = options.activeUserStore ?? new RuntimeUserStore(configPath);
4613
4940
  const lastProviderSessions = new LastProviderSessionStore(configPath);
4614
4941
  const sessionBindingMatch = /* @__PURE__ */ new Map();
4942
+ const bridgePort = process.env.BRIDGE_PORT ?? "8787";
4943
+ const mcpConfigPath = join11(dirname7(configPath), "mcp-media.json");
4944
+ const serverDir = dirname7(fileURLToPath2(import.meta.url));
4945
+ const mcpMediaServerTs = join11(serverDir, "..", "mcp", "mediaServer.ts");
4946
+ const mcpMediaServerJs = join11(serverDir, "..", "mcp", "mediaServer.js");
4947
+ const useTs = existsSync9(mcpMediaServerTs) && !existsSync9(mcpMediaServerJs);
4948
+ const mcpCommand = useTs ? "tsx" : "node";
4949
+ const mcpArgs = [useTs ? mcpMediaServerTs : mcpMediaServerJs];
4950
+ writeFileSync2(mcpConfigPath, JSON.stringify({
4951
+ mcpServers: {
4952
+ "wechat-media": {
4953
+ command: mcpCommand,
4954
+ args: mcpArgs,
4955
+ env: { BRIDGE_API_URL: `http://localhost:${bridgePort}` }
4956
+ }
4957
+ }
4958
+ }, null, 2) + "\n");
4615
4959
  const providerAdapters = options.providers ?? createDefaultProviders({
4616
4960
  claudeCommand: options.providerCommands?.claude?.command,
4617
- codexCommand: options.providerCommands?.codex?.command
4961
+ codexCommand: options.providerCommands?.codex?.command,
4962
+ mcpConfigPath
4618
4963
  });
4619
4964
  let currentConversation = conversation.getCurrent();
4620
4965
  if (currentConversation) {
@@ -4639,7 +4984,13 @@ function createDaemonServer(options = {}) {
4639
4984
  const weixinOutboundGate = managedWechatChannel && weixinStateStore ? new WeixinOutboundGate({
4640
4985
  store: weixinStateStore,
4641
4986
  send: async (chatId, msg) => {
4642
- await managedWechatChannel.sendMessage({ chatId, kind: msg.kind, text: msg.text });
4987
+ await managedWechatChannel.sendMessage({
4988
+ chatId,
4989
+ kind: msg.kind,
4990
+ text: msg.text,
4991
+ ...msg.filePath ? { filePath: msg.filePath } : {},
4992
+ ...msg.fileName ? { fileName: msg.fileName } : {}
4993
+ });
4643
4994
  }
4644
4995
  }) : void 0;
4645
4996
  const tunnelProvider = options.tunnelProvider ?? new RelayTunnelRouter({
@@ -4785,6 +5136,36 @@ function createDaemonServer(options = {}) {
4785
5136
  request.raw.on("error", cleanup);
4786
5137
  return reply.hijack();
4787
5138
  });
5139
+ app.post("/api/channel/send-media", async (request, reply) => {
5140
+ const { kind, filePath, fileName, chatId } = request.body ?? {};
5141
+ if (!filePath || typeof filePath !== "string") {
5142
+ return reply.code(400).send({ ok: false, error: "filePath is required" });
5143
+ }
5144
+ const validKinds = ["image", "video", "audio", "file"];
5145
+ if (!kind || !validKinds.includes(kind)) {
5146
+ return reply.code(400).send({ ok: false, error: `kind must be one of: ${validKinds.join(", ")}` });
5147
+ }
5148
+ if (!channel) {
5149
+ return reply.code(503).send({ ok: false, error: "channel not available" });
5150
+ }
5151
+ const targetChatId = chatId || activeUserStore.getActiveUser()?.platformUserId;
5152
+ if (!targetChatId) {
5153
+ return reply.code(400).send({ ok: false, error: "no active chat target; provide chatId or ensure an active user exists" });
5154
+ }
5155
+ try {
5156
+ await channel.sendMessage({
5157
+ chatId: targetChatId,
5158
+ kind,
5159
+ text: "",
5160
+ filePath,
5161
+ ...fileName ? { fileName } : {}
5162
+ });
5163
+ return { ok: true };
5164
+ } catch (e) {
5165
+ const msg = e instanceof Error ? e.message : String(e);
5166
+ return reply.code(500).send({ ok: false, error: msg });
5167
+ }
5168
+ });
4788
5169
  app.addHook("onReady", async () => {
4789
5170
  await channel?.start({ background: true });
4790
5171
  });
@@ -4924,7 +5305,7 @@ async function resolveProviderCommands(providers) {
4924
5305
  }
4925
5306
 
4926
5307
  // src/daemon/staticFrontend.ts
4927
- import { readFileSync as readFileSync8 } from "node:fs";
5308
+ import { readFileSync as readFileSync9 } from "node:fs";
4928
5309
  import { join as join12 } from "node:path";
4929
5310
  import fastifyStatic from "@fastify/static";
4930
5311
  function attachStaticFrontend(webRoot2) {
@@ -4933,7 +5314,7 @@ function attachStaticFrontend(webRoot2) {
4933
5314
  root: webRoot2,
4934
5315
  wildcard: false
4935
5316
  });
4936
- const indexHtml = readFileSync8(join12(webRoot2, "index.html"), "utf8");
5317
+ const indexHtml = readFileSync9(join12(webRoot2, "index.html"), "utf8");
4937
5318
  app.setNotFoundHandler((request, reply) => {
4938
5319
  if (request.url.startsWith("/api")) {
4939
5320
  reply.code(404).send({ error: "not_found" });
@@ -4945,7 +5326,7 @@ function attachStaticFrontend(webRoot2) {
4945
5326
  }
4946
5327
 
4947
5328
  // src/daemon/service.ts
4948
- import { existsSync as existsSync9, statSync, openSync as openSync2, closeSync as closeSync2, watchFile } from "node:fs";
5329
+ import { existsSync as existsSync10, statSync, openSync as openSync2, closeSync as closeSync2, watchFile } from "node:fs";
4949
5330
  import { mkdir as mkdir4, readFile as readFile6, rm as rm2, writeFile as writeFile4 } from "node:fs/promises";
4950
5331
  import { homedir as homedir8 } from "node:os";
4951
5332
  import { dirname as dirname8, join as join13 } from "node:path";
@@ -4999,13 +5380,13 @@ async function startService(context) {
4999
5380
  async function stopService(context) {
5000
5381
  if (process.platform === "darwin") {
5001
5382
  const spec = buildLaunchdSpec(context);
5002
- if (!existsSync9(spec.plistPath)) return await readServiceStatus(context);
5383
+ if (!existsSync10(spec.plistPath)) return await readServiceStatus(context);
5003
5384
  await runLaunchctl(["bootout", `gui/${process.getuid?.() ?? 0}`, spec.plistPath]).catch(() => void 0);
5004
5385
  return await readServiceStatus(context);
5005
5386
  }
5006
5387
  if (process.platform === "linux") {
5007
5388
  const spec = buildSystemdUserSpec(context);
5008
- if (!existsSync9(spec.unitPath)) return await readServiceStatus(context);
5389
+ if (!existsSync10(spec.unitPath)) return await readServiceStatus(context);
5009
5390
  await runSystemctl(["--user", "stop", spec.unitName]).catch(() => void 0);
5010
5391
  return await readServiceStatus(context);
5011
5392
  }
@@ -5023,7 +5404,7 @@ async function stopService(context) {
5023
5404
  async function uninstallService(context) {
5024
5405
  if (process.platform === "darwin") {
5025
5406
  const spec = buildLaunchdSpec(context);
5026
- if (existsSync9(spec.plistPath)) {
5407
+ if (existsSync10(spec.plistPath)) {
5027
5408
  await runLaunchctl(["bootout", `gui/${process.getuid?.() ?? 0}`, spec.plistPath]).catch(() => void 0);
5028
5409
  await rm2(spec.plistPath, { force: true });
5029
5410
  }
@@ -5031,7 +5412,7 @@ async function uninstallService(context) {
5031
5412
  }
5032
5413
  if (process.platform === "linux") {
5033
5414
  const spec = buildSystemdUserSpec(context);
5034
- if (existsSync9(spec.unitPath)) {
5415
+ if (existsSync10(spec.unitPath)) {
5035
5416
  await runSystemctl(["--user", "disable", "--now", spec.unitName]).catch(() => void 0);
5036
5417
  await rm2(spec.unitPath, { force: true });
5037
5418
  await runSystemctl(["--user", "daemon-reload"]).catch(() => void 0);
@@ -5081,7 +5462,7 @@ async function tailServiceLogs(context) {
5081
5462
  async function readServiceStatus(context) {
5082
5463
  if (process.platform === "darwin") {
5083
5464
  const spec = buildLaunchdSpec(context);
5084
- const installed = existsSync9(spec.plistPath);
5465
+ const installed = existsSync10(spec.plistPath);
5085
5466
  const running = installed ? await runLaunchctl(["print", `gui/${process.getuid?.() ?? 0}/${spec.label}`]).then(() => true, () => false) : false;
5086
5467
  return {
5087
5468
  manager: "launchd",
@@ -5095,7 +5476,7 @@ async function readServiceStatus(context) {
5095
5476
  }
5096
5477
  if (process.platform === "linux") {
5097
5478
  const spec = buildSystemdUserSpec(context);
5098
- const installed = existsSync9(spec.unitPath);
5479
+ const installed = existsSync10(spec.unitPath);
5099
5480
  const running = installed ? await runSystemctl(["--user", "is-active", "--quiet", spec.unitName]).then(() => true, () => false) : false;
5100
5481
  return {
5101
5482
  manager: "systemd-user",
@@ -5280,7 +5661,7 @@ function isProcessAlive(pid) {
5280
5661
  }
5281
5662
  }
5282
5663
  async function readPidFile(pidPath) {
5283
- if (!existsSync9(pidPath)) return null;
5664
+ if (!existsSync10(pidPath)) return null;
5284
5665
  try {
5285
5666
  const raw = (await readFile6(pidPath, "utf8")).trim();
5286
5667
  const pid = Number.parseInt(raw, 10);
@@ -5331,12 +5712,12 @@ async function spawnDetachedDaemon(spec) {
5331
5712
  }
5332
5713
  }
5333
5714
  async function tailFile(path, lines) {
5334
- if (!existsSync9(path)) return "";
5715
+ if (!existsSync10(path)) return "";
5335
5716
  const content = await readFile6(path, "utf8");
5336
5717
  return content.split(/\r?\n/).slice(-lines).join("\n").trim();
5337
5718
  }
5338
5719
  async function tailFiles(paths) {
5339
- const existing = paths.filter((path) => existsSync9(path));
5720
+ const existing = paths.filter((path) => existsSync10(path));
5340
5721
  for (const path of existing) {
5341
5722
  const tail = await tailFile(path, 200);
5342
5723
  if (tail) process.stdout.write(`[${path}]
@@ -5387,7 +5768,7 @@ function fileSizeHint(path) {
5387
5768
  }
5388
5769
 
5389
5770
  // src/cli.ts
5390
- var here = dirname9(fileURLToPath2(import.meta.url));
5771
+ var here = dirname9(fileURLToPath3(import.meta.url));
5391
5772
  var webRoot = join14(here, "..", "web");
5392
5773
  async function main() {
5393
5774
  const command = process.argv[2];
@@ -5437,7 +5818,7 @@ async function main() {
5437
5818
  }
5438
5819
  }
5439
5820
  async function cmdStart() {
5440
- if (!existsSync10(webRoot)) {
5821
+ if (!existsSync11(webRoot)) {
5441
5822
  console.error(`\u627E\u4E0D\u5230\u524D\u7AEF\u6784\u5EFA\u4EA7\u7269: ${webRoot}`);
5442
5823
  console.error("\u8BF7\u91CD\u65B0\u5B89\u88C5\u5B8C\u6574\u7684 npm \u5305\u3002");
5443
5824
  process.exitCode = 1;
@@ -5468,7 +5849,7 @@ async function cmdTail() {
5468
5849
  await tailServiceLogs(createServiceContext());
5469
5850
  }
5470
5851
  async function cmdDaemon() {
5471
- if (!existsSync10(webRoot)) {
5852
+ if (!existsSync11(webRoot)) {
5472
5853
  console.error(`\u627E\u4E0D\u5230\u524D\u7AEF\u6784\u5EFA\u4EA7\u7269: ${webRoot}`);
5473
5854
  process.exitCode = 1;
5474
5855
  return;
@@ -5481,13 +5862,13 @@ async function cmdDaemon() {
5481
5862
  async function cmdDoctor() {
5482
5863
  const configPath = process.env.BRIDGE_CONFIG ?? defaultConfigPath();
5483
5864
  console.log("claude-codex-wechat doctor\n");
5484
- report("\u914D\u7F6E\u6587\u4EF6", existsSync10(configPath) ? configPath : `\u7F3A\u5931 (${configPath})\uFF0C\u9996\u6B21 start \u65F6\u81EA\u52A8\u521B\u5EFA`);
5485
- report("\u524D\u7AEF\u4EA7\u7269", existsSync10(webRoot) ? webRoot : `\u7F3A\u5931 (${webRoot})`);
5865
+ report("\u914D\u7F6E\u6587\u4EF6", existsSync11(configPath) ? configPath : `\u7F3A\u5931 (${configPath})\uFF0C\u9996\u6B21 start \u65F6\u81EA\u52A8\u521B\u5EFA`);
5866
+ report("\u524D\u7AEF\u4EA7\u7269", existsSync11(webRoot) ? webRoot : `\u7F3A\u5931 (${webRoot})`);
5486
5867
  const claude = await findExecutable("claude");
5487
5868
  report("claude \u53EF\u6267\u884C", claude ?? "\u672A\u627E\u5230\uFF0C\u9700\u5148\u5B89\u88C5\u5E76\u767B\u5F55 Claude Code");
5488
5869
  const codex = await findExecutable("codex");
5489
5870
  report("codex \u53EF\u6267\u884C", codex ?? "\u672A\u627E\u5230\uFF08\u4EC5\u4F7F\u7528 Claude \u65F6\u53EF\u5FFD\u7565\uFF09");
5490
- if (existsSync10(configPath)) {
5871
+ if (existsSync11(configPath)) {
5491
5872
  const config = loadBridgeConfig(configPath);
5492
5873
  const wechat = config.wechat;
5493
5874
  report("\u5FAE\u4FE1\u542F\u7528", wechat?.enabled ? "\u662F" : "\u5426");
@@ -5498,7 +5879,7 @@ async function cmdDoctor() {
5498
5879
  }
5499
5880
  function printUpdateHint() {
5500
5881
  const configPath = process.env.BRIDGE_CONFIG ?? defaultConfigPath();
5501
- if (!existsSync10(configPath)) return;
5882
+ if (!existsSync11(configPath)) return;
5502
5883
  let update;
5503
5884
  try {
5504
5885
  update = loadBridgeConfig(configPath).update;
@@ -5514,12 +5895,12 @@ function printUpdateHint() {
5514
5895
  }
5515
5896
  function cmdPrintConfig() {
5516
5897
  const configPath = process.env.BRIDGE_CONFIG ?? defaultConfigPath();
5517
- if (!existsSync10(configPath)) {
5898
+ if (!existsSync11(configPath)) {
5518
5899
  console.error(`\u914D\u7F6E\u4E0D\u5B58\u5728: ${configPath}\uFF08\u9996\u6B21 start \u540E\u81EA\u52A8\u751F\u6210\uFF09`);
5519
5900
  process.exitCode = 1;
5520
5901
  return;
5521
5902
  }
5522
- console.log(readFileSync9(configPath, "utf8"));
5903
+ console.log(readFileSync10(configPath, "utf8"));
5523
5904
  }
5524
5905
  async function cmdUninstall() {
5525
5906
  const status = await uninstallService(createServiceContext());
@@ -5527,7 +5908,7 @@ async function cmdUninstall() {
5527
5908
  }
5528
5909
  function createServiceContext() {
5529
5910
  return {
5530
- cliEntrypointPath: fileURLToPath2(import.meta.url),
5911
+ cliEntrypointPath: fileURLToPath3(import.meta.url),
5531
5912
  nodePath: process.execPath,
5532
5913
  configPath: process.env.BRIDGE_CONFIG ?? defaultConfigPath(),
5533
5914
  port: Number(process.env.BRIDGE_PORT ?? 8787)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-codex-wechat",
3
- "version": "0.1.29",
3
+ "version": "0.1.30",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "packageManager": "pnpm@11.5.0",
@@ -39,10 +39,12 @@
39
39
  },
40
40
  "dependencies": {
41
41
  "@fastify/static": "^9.1.3",
42
+ "@modelcontextprotocol/sdk": "^1.29.0",
42
43
  "better-sqlite3": "latest",
43
44
  "fastify": "latest",
44
45
  "lucide-react": "^1.22.0",
45
- "nanoid": "latest"
46
+ "nanoid": "latest",
47
+ "zod": "^4.4.3"
46
48
  },
47
49
  "devDependencies": {
48
50
  "@fastify/middie": "^9.3.2",