claude-codex-wechat 0.1.29 → 0.1.31

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:8787";
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);
@@ -2909,7 +3231,7 @@ var CodexAppServerClient = class {
2909
3231
  buffer = "";
2910
3232
  constructor(input) {
2911
3233
  const command = input.command ?? "codex";
2912
- this.child = spawn4(command, ["app-server", "--listen", "stdio://"], {
3234
+ this.child = spawn4(command, ["app-server", "--listen", "stdio://", ...input.mcpServerConfigs ?? []], {
2913
3235
  cwd: input.cwd,
2914
3236
  stdio: ["pipe", "pipe", "pipe"],
2915
3237
  shell: useShellForCli()
@@ -3080,8 +3402,10 @@ var CodexInteractiveRunner = class {
3080
3402
  sessions = /* @__PURE__ */ new Map();
3081
3403
  command;
3082
3404
  syncThreadForResume;
3405
+ mcpServerConfigs;
3083
3406
  constructor(input = {}) {
3084
3407
  this.command = input.command;
3408
+ this.mcpServerConfigs = input.mcpServerConfigs;
3085
3409
  this.syncThreadForResume = input.syncThreadForResume ?? syncCodexThreadForResume;
3086
3410
  }
3087
3411
  async startSession(input) {
@@ -3204,7 +3528,8 @@ var CodexInteractiveRunner = class {
3204
3528
  if (session.client) return session.client;
3205
3529
  const client = new CodexAppServerClient({
3206
3530
  command: this.command,
3207
- cwd: session.cwd
3531
+ cwd: session.cwd,
3532
+ ...this.mcpServerConfigs?.length ? { mcpServerConfigs: this.mcpServerConfigs } : {}
3208
3533
  });
3209
3534
  await client.initialize();
3210
3535
  client.onNotification("item/agentMessage/delta", (params) => {
@@ -3310,8 +3635,8 @@ var CodexProvider = class {
3310
3635
  // src/providers/defaultProviders.ts
3311
3636
  function createDefaultProviders(input = {}) {
3312
3637
  return [
3313
- new ClaudeCodeProvider({ runner: new ClaudeStreamingRunner({ command: input.claudeCommand }) }),
3314
- new CodexProvider({ runner: new CodexInteractiveRunner({ command: input.codexCommand }) })
3638
+ new ClaudeCodeProvider({ runner: new ClaudeStreamingRunner({ command: input.claudeCommand, mcpConfigPath: input.mcpConfigPath }) }),
3639
+ new CodexProvider({ runner: new CodexInteractiveRunner({ command: input.codexCommand, mcpServerConfigs: input.codexMcpServerConfigs }) })
3315
3640
  ];
3316
3641
  }
3317
3642
 
@@ -3502,7 +3827,7 @@ function buildBridgeCommandHelpMarkdown(input) {
3502
3827
  }
3503
3828
 
3504
3829
  // src/session/currentConversationStore.ts
3505
- import { existsSync as existsSync6, readFileSync as readFileSync4 } from "node:fs";
3830
+ import { existsSync as existsSync6, readFileSync as readFileSync5 } from "node:fs";
3506
3831
  import { nanoid as nanoid2 } from "nanoid";
3507
3832
  var CurrentConversationStore = class {
3508
3833
  constructor(configPath, defaults) {
@@ -3562,7 +3887,7 @@ var CurrentConversationStore = class {
3562
3887
  }
3563
3888
  readState() {
3564
3889
  if (!existsSync6(this.configPath)) return {};
3565
- const raw = JSON.parse(readFileSync4(this.configPath, "utf8"));
3890
+ const raw = JSON.parse(readFileSync5(this.configPath, "utf8"));
3566
3891
  return raw && typeof raw === "object" ? raw : {};
3567
3892
  }
3568
3893
  writeCurrent(record) {
@@ -3633,7 +3958,12 @@ var MessageRouter = class _MessageRouter {
3633
3958
  */
3634
3959
  async sendToChat(message) {
3635
3960
  if (this.options.outboundGate) {
3636
- await this.options.outboundGate.deliver(message.chatId, { kind: message.kind, text: message.text });
3961
+ await this.options.outboundGate.deliver(message.chatId, {
3962
+ kind: message.kind,
3963
+ text: message.text,
3964
+ ...message.filePath ? { filePath: message.filePath } : {},
3965
+ ...message.fileName ? { fileName: message.fileName } : {}
3966
+ });
3637
3967
  } else {
3638
3968
  await this.options.channel.sendMessage(message);
3639
3969
  }
@@ -4121,7 +4451,7 @@ function composeInboundText(content) {
4121
4451
  }
4122
4452
 
4123
4453
  // src/storage/lastProviderSessionStore.ts
4124
- import { existsSync as existsSync7, readFileSync as readFileSync5 } from "node:fs";
4454
+ import { existsSync as existsSync7, readFileSync as readFileSync6 } from "node:fs";
4125
4455
  var LastProviderSessionStore = class {
4126
4456
  constructor(configPath) {
4127
4457
  this.configPath = configPath;
@@ -4148,7 +4478,7 @@ var LastProviderSessionStore = class {
4148
4478
  }
4149
4479
  readState() {
4150
4480
  if (!existsSync7(this.configPath)) return {};
4151
- const raw = JSON.parse(readFileSync5(this.configPath, "utf8"));
4481
+ const raw = JSON.parse(readFileSync6(this.configPath, "utf8"));
4152
4482
  return raw && typeof raw === "object" ? raw : {};
4153
4483
  }
4154
4484
  writeState(state) {
@@ -4158,7 +4488,7 @@ var LastProviderSessionStore = class {
4158
4488
  };
4159
4489
 
4160
4490
  // src/storage/runtimeUserStore.ts
4161
- import { readFileSync as readFileSync6, existsSync as existsSync8 } from "node:fs";
4491
+ import { readFileSync as readFileSync7, existsSync as existsSync8 } from "node:fs";
4162
4492
  import { nanoid as nanoid3 } from "nanoid";
4163
4493
  var RuntimeUserStore = class {
4164
4494
  constructor(configPath) {
@@ -4219,7 +4549,7 @@ var RuntimeUserStore = class {
4219
4549
  }
4220
4550
  readState() {
4221
4551
  if (!existsSync8(this.configPath)) return {};
4222
- const raw = JSON.parse(readFileSync6(this.configPath, "utf8"));
4552
+ const raw = JSON.parse(readFileSync7(this.configPath, "utf8"));
4223
4553
  return raw && typeof raw === "object" ? raw : {};
4224
4554
  }
4225
4555
  writeState(state) {
@@ -4551,13 +4881,13 @@ var RelayTunnelRouter = class {
4551
4881
  };
4552
4882
 
4553
4883
  // src/shared/version.ts
4554
- import { readFileSync as readFileSync7 } from "node:fs";
4884
+ import { readFileSync as readFileSync8 } from "node:fs";
4555
4885
  import { dirname as dirname6, join as join10 } from "node:path";
4556
4886
  import { fileURLToPath } from "node:url";
4557
4887
  function readClientVersion() {
4558
4888
  try {
4559
4889
  const here2 = dirname6(fileURLToPath(import.meta.url));
4560
- const pkg = JSON.parse(readFileSync7(join10(here2, "..", "..", "package.json"), "utf8"));
4890
+ const pkg = JSON.parse(readFileSync8(join10(here2, "..", "..", "package.json"), "utf8"));
4561
4891
  return typeof pkg.version === "string" && pkg.version ? pkg.version : "0.0.0";
4562
4892
  } catch {
4563
4893
  return "0.0.0";
@@ -4612,9 +4942,37 @@ function createDaemonServer(options = {}) {
4612
4942
  const activeUserStore = options.activeUserStore ?? new RuntimeUserStore(configPath);
4613
4943
  const lastProviderSessions = new LastProviderSessionStore(configPath);
4614
4944
  const sessionBindingMatch = /* @__PURE__ */ new Map();
4945
+ const bridgePort = process.env.BRIDGE_PORT ?? "8787";
4946
+ const mcpConfigPath = join11(dirname7(configPath), "mcp-media.json");
4947
+ const serverDir = dirname7(fileURLToPath2(import.meta.url));
4948
+ const mcpMediaServerTs = join11(serverDir, "..", "mcp", "mediaServer.ts");
4949
+ const mcpMediaServerJs = join11(serverDir, "..", "mcp", "mediaServer.js");
4950
+ const useTs = existsSync9(mcpMediaServerTs) && !existsSync9(mcpMediaServerJs);
4951
+ const mcpCommand = useTs ? "tsx" : "node";
4952
+ const mcpArgs = [useTs ? mcpMediaServerTs : mcpMediaServerJs];
4953
+ writeFileSync2(mcpConfigPath, JSON.stringify({
4954
+ mcpServers: {
4955
+ "wechat-media": {
4956
+ command: mcpCommand,
4957
+ args: mcpArgs,
4958
+ env: { BRIDGE_API_URL: `http://localhost:${bridgePort}` }
4959
+ }
4960
+ }
4961
+ }, null, 2) + "\n");
4962
+ const codexMcpScript = useTs ? mcpMediaServerTs : mcpMediaServerJs;
4963
+ const codexMcpServerConfigs = [
4964
+ "-c",
4965
+ `mcp_servers.wechat-media.command="${mcpCommand}"`,
4966
+ "-c",
4967
+ `mcp_servers.wechat-media.args=["${codexMcpScript}"]`,
4968
+ "-c",
4969
+ `mcp_servers.wechat-media.env.BRIDGE_API_URL="http://localhost:${bridgePort}"`
4970
+ ];
4615
4971
  const providerAdapters = options.providers ?? createDefaultProviders({
4616
4972
  claudeCommand: options.providerCommands?.claude?.command,
4617
- codexCommand: options.providerCommands?.codex?.command
4973
+ codexCommand: options.providerCommands?.codex?.command,
4974
+ mcpConfigPath,
4975
+ codexMcpServerConfigs
4618
4976
  });
4619
4977
  let currentConversation = conversation.getCurrent();
4620
4978
  if (currentConversation) {
@@ -4639,7 +4997,13 @@ function createDaemonServer(options = {}) {
4639
4997
  const weixinOutboundGate = managedWechatChannel && weixinStateStore ? new WeixinOutboundGate({
4640
4998
  store: weixinStateStore,
4641
4999
  send: async (chatId, msg) => {
4642
- await managedWechatChannel.sendMessage({ chatId, kind: msg.kind, text: msg.text });
5000
+ await managedWechatChannel.sendMessage({
5001
+ chatId,
5002
+ kind: msg.kind,
5003
+ text: msg.text,
5004
+ ...msg.filePath ? { filePath: msg.filePath } : {},
5005
+ ...msg.fileName ? { fileName: msg.fileName } : {}
5006
+ });
4643
5007
  }
4644
5008
  }) : void 0;
4645
5009
  const tunnelProvider = options.tunnelProvider ?? new RelayTunnelRouter({
@@ -4785,6 +5149,36 @@ function createDaemonServer(options = {}) {
4785
5149
  request.raw.on("error", cleanup);
4786
5150
  return reply.hijack();
4787
5151
  });
5152
+ app.post("/api/channel/send-media", async (request, reply) => {
5153
+ const { kind, filePath, fileName, chatId } = request.body ?? {};
5154
+ if (!filePath || typeof filePath !== "string") {
5155
+ return reply.code(400).send({ ok: false, error: "filePath is required" });
5156
+ }
5157
+ const validKinds = ["image", "video", "audio", "file"];
5158
+ if (!kind || !validKinds.includes(kind)) {
5159
+ return reply.code(400).send({ ok: false, error: `kind must be one of: ${validKinds.join(", ")}` });
5160
+ }
5161
+ if (!channel) {
5162
+ return reply.code(503).send({ ok: false, error: "channel not available" });
5163
+ }
5164
+ const targetChatId = chatId || activeUserStore.getActiveUser()?.platformUserId;
5165
+ if (!targetChatId) {
5166
+ return reply.code(400).send({ ok: false, error: "no active chat target; provide chatId or ensure an active user exists" });
5167
+ }
5168
+ try {
5169
+ await channel.sendMessage({
5170
+ chatId: targetChatId,
5171
+ kind,
5172
+ text: "",
5173
+ filePath,
5174
+ ...fileName ? { fileName } : {}
5175
+ });
5176
+ return { ok: true };
5177
+ } catch (e) {
5178
+ const msg = e instanceof Error ? e.message : String(e);
5179
+ return reply.code(500).send({ ok: false, error: msg });
5180
+ }
5181
+ });
4788
5182
  app.addHook("onReady", async () => {
4789
5183
  await channel?.start({ background: true });
4790
5184
  });
@@ -4924,7 +5318,7 @@ async function resolveProviderCommands(providers) {
4924
5318
  }
4925
5319
 
4926
5320
  // src/daemon/staticFrontend.ts
4927
- import { readFileSync as readFileSync8 } from "node:fs";
5321
+ import { readFileSync as readFileSync9 } from "node:fs";
4928
5322
  import { join as join12 } from "node:path";
4929
5323
  import fastifyStatic from "@fastify/static";
4930
5324
  function attachStaticFrontend(webRoot2) {
@@ -4933,7 +5327,7 @@ function attachStaticFrontend(webRoot2) {
4933
5327
  root: webRoot2,
4934
5328
  wildcard: false
4935
5329
  });
4936
- const indexHtml = readFileSync8(join12(webRoot2, "index.html"), "utf8");
5330
+ const indexHtml = readFileSync9(join12(webRoot2, "index.html"), "utf8");
4937
5331
  app.setNotFoundHandler((request, reply) => {
4938
5332
  if (request.url.startsWith("/api")) {
4939
5333
  reply.code(404).send({ error: "not_found" });
@@ -4945,7 +5339,7 @@ function attachStaticFrontend(webRoot2) {
4945
5339
  }
4946
5340
 
4947
5341
  // src/daemon/service.ts
4948
- import { existsSync as existsSync9, statSync, openSync as openSync2, closeSync as closeSync2, watchFile } from "node:fs";
5342
+ import { existsSync as existsSync10, statSync, openSync as openSync2, closeSync as closeSync2, watchFile } from "node:fs";
4949
5343
  import { mkdir as mkdir4, readFile as readFile6, rm as rm2, writeFile as writeFile4 } from "node:fs/promises";
4950
5344
  import { homedir as homedir8 } from "node:os";
4951
5345
  import { dirname as dirname8, join as join13 } from "node:path";
@@ -4999,13 +5393,13 @@ async function startService(context) {
4999
5393
  async function stopService(context) {
5000
5394
  if (process.platform === "darwin") {
5001
5395
  const spec = buildLaunchdSpec(context);
5002
- if (!existsSync9(spec.plistPath)) return await readServiceStatus(context);
5396
+ if (!existsSync10(spec.plistPath)) return await readServiceStatus(context);
5003
5397
  await runLaunchctl(["bootout", `gui/${process.getuid?.() ?? 0}`, spec.plistPath]).catch(() => void 0);
5004
5398
  return await readServiceStatus(context);
5005
5399
  }
5006
5400
  if (process.platform === "linux") {
5007
5401
  const spec = buildSystemdUserSpec(context);
5008
- if (!existsSync9(spec.unitPath)) return await readServiceStatus(context);
5402
+ if (!existsSync10(spec.unitPath)) return await readServiceStatus(context);
5009
5403
  await runSystemctl(["--user", "stop", spec.unitName]).catch(() => void 0);
5010
5404
  return await readServiceStatus(context);
5011
5405
  }
@@ -5023,7 +5417,7 @@ async function stopService(context) {
5023
5417
  async function uninstallService(context) {
5024
5418
  if (process.platform === "darwin") {
5025
5419
  const spec = buildLaunchdSpec(context);
5026
- if (existsSync9(spec.plistPath)) {
5420
+ if (existsSync10(spec.plistPath)) {
5027
5421
  await runLaunchctl(["bootout", `gui/${process.getuid?.() ?? 0}`, spec.plistPath]).catch(() => void 0);
5028
5422
  await rm2(spec.plistPath, { force: true });
5029
5423
  }
@@ -5031,7 +5425,7 @@ async function uninstallService(context) {
5031
5425
  }
5032
5426
  if (process.platform === "linux") {
5033
5427
  const spec = buildSystemdUserSpec(context);
5034
- if (existsSync9(spec.unitPath)) {
5428
+ if (existsSync10(spec.unitPath)) {
5035
5429
  await runSystemctl(["--user", "disable", "--now", spec.unitName]).catch(() => void 0);
5036
5430
  await rm2(spec.unitPath, { force: true });
5037
5431
  await runSystemctl(["--user", "daemon-reload"]).catch(() => void 0);
@@ -5081,7 +5475,7 @@ async function tailServiceLogs(context) {
5081
5475
  async function readServiceStatus(context) {
5082
5476
  if (process.platform === "darwin") {
5083
5477
  const spec = buildLaunchdSpec(context);
5084
- const installed = existsSync9(spec.plistPath);
5478
+ const installed = existsSync10(spec.plistPath);
5085
5479
  const running = installed ? await runLaunchctl(["print", `gui/${process.getuid?.() ?? 0}/${spec.label}`]).then(() => true, () => false) : false;
5086
5480
  return {
5087
5481
  manager: "launchd",
@@ -5095,7 +5489,7 @@ async function readServiceStatus(context) {
5095
5489
  }
5096
5490
  if (process.platform === "linux") {
5097
5491
  const spec = buildSystemdUserSpec(context);
5098
- const installed = existsSync9(spec.unitPath);
5492
+ const installed = existsSync10(spec.unitPath);
5099
5493
  const running = installed ? await runSystemctl(["--user", "is-active", "--quiet", spec.unitName]).then(() => true, () => false) : false;
5100
5494
  return {
5101
5495
  manager: "systemd-user",
@@ -5280,7 +5674,7 @@ function isProcessAlive(pid) {
5280
5674
  }
5281
5675
  }
5282
5676
  async function readPidFile(pidPath) {
5283
- if (!existsSync9(pidPath)) return null;
5677
+ if (!existsSync10(pidPath)) return null;
5284
5678
  try {
5285
5679
  const raw = (await readFile6(pidPath, "utf8")).trim();
5286
5680
  const pid = Number.parseInt(raw, 10);
@@ -5331,12 +5725,12 @@ async function spawnDetachedDaemon(spec) {
5331
5725
  }
5332
5726
  }
5333
5727
  async function tailFile(path, lines) {
5334
- if (!existsSync9(path)) return "";
5728
+ if (!existsSync10(path)) return "";
5335
5729
  const content = await readFile6(path, "utf8");
5336
5730
  return content.split(/\r?\n/).slice(-lines).join("\n").trim();
5337
5731
  }
5338
5732
  async function tailFiles(paths) {
5339
- const existing = paths.filter((path) => existsSync9(path));
5733
+ const existing = paths.filter((path) => existsSync10(path));
5340
5734
  for (const path of existing) {
5341
5735
  const tail = await tailFile(path, 200);
5342
5736
  if (tail) process.stdout.write(`[${path}]
@@ -5387,7 +5781,7 @@ function fileSizeHint(path) {
5387
5781
  }
5388
5782
 
5389
5783
  // src/cli.ts
5390
- var here = dirname9(fileURLToPath2(import.meta.url));
5784
+ var here = dirname9(fileURLToPath3(import.meta.url));
5391
5785
  var webRoot = join14(here, "..", "web");
5392
5786
  async function main() {
5393
5787
  const command = process.argv[2];
@@ -5437,7 +5831,7 @@ async function main() {
5437
5831
  }
5438
5832
  }
5439
5833
  async function cmdStart() {
5440
- if (!existsSync10(webRoot)) {
5834
+ if (!existsSync11(webRoot)) {
5441
5835
  console.error(`\u627E\u4E0D\u5230\u524D\u7AEF\u6784\u5EFA\u4EA7\u7269: ${webRoot}`);
5442
5836
  console.error("\u8BF7\u91CD\u65B0\u5B89\u88C5\u5B8C\u6574\u7684 npm \u5305\u3002");
5443
5837
  process.exitCode = 1;
@@ -5468,7 +5862,7 @@ async function cmdTail() {
5468
5862
  await tailServiceLogs(createServiceContext());
5469
5863
  }
5470
5864
  async function cmdDaemon() {
5471
- if (!existsSync10(webRoot)) {
5865
+ if (!existsSync11(webRoot)) {
5472
5866
  console.error(`\u627E\u4E0D\u5230\u524D\u7AEF\u6784\u5EFA\u4EA7\u7269: ${webRoot}`);
5473
5867
  process.exitCode = 1;
5474
5868
  return;
@@ -5481,13 +5875,13 @@ async function cmdDaemon() {
5481
5875
  async function cmdDoctor() {
5482
5876
  const configPath = process.env.BRIDGE_CONFIG ?? defaultConfigPath();
5483
5877
  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})`);
5878
+ report("\u914D\u7F6E\u6587\u4EF6", existsSync11(configPath) ? configPath : `\u7F3A\u5931 (${configPath})\uFF0C\u9996\u6B21 start \u65F6\u81EA\u52A8\u521B\u5EFA`);
5879
+ report("\u524D\u7AEF\u4EA7\u7269", existsSync11(webRoot) ? webRoot : `\u7F3A\u5931 (${webRoot})`);
5486
5880
  const claude = await findExecutable("claude");
5487
5881
  report("claude \u53EF\u6267\u884C", claude ?? "\u672A\u627E\u5230\uFF0C\u9700\u5148\u5B89\u88C5\u5E76\u767B\u5F55 Claude Code");
5488
5882
  const codex = await findExecutable("codex");
5489
5883
  report("codex \u53EF\u6267\u884C", codex ?? "\u672A\u627E\u5230\uFF08\u4EC5\u4F7F\u7528 Claude \u65F6\u53EF\u5FFD\u7565\uFF09");
5490
- if (existsSync10(configPath)) {
5884
+ if (existsSync11(configPath)) {
5491
5885
  const config = loadBridgeConfig(configPath);
5492
5886
  const wechat = config.wechat;
5493
5887
  report("\u5FAE\u4FE1\u542F\u7528", wechat?.enabled ? "\u662F" : "\u5426");
@@ -5498,7 +5892,7 @@ async function cmdDoctor() {
5498
5892
  }
5499
5893
  function printUpdateHint() {
5500
5894
  const configPath = process.env.BRIDGE_CONFIG ?? defaultConfigPath();
5501
- if (!existsSync10(configPath)) return;
5895
+ if (!existsSync11(configPath)) return;
5502
5896
  let update;
5503
5897
  try {
5504
5898
  update = loadBridgeConfig(configPath).update;
@@ -5514,12 +5908,12 @@ function printUpdateHint() {
5514
5908
  }
5515
5909
  function cmdPrintConfig() {
5516
5910
  const configPath = process.env.BRIDGE_CONFIG ?? defaultConfigPath();
5517
- if (!existsSync10(configPath)) {
5911
+ if (!existsSync11(configPath)) {
5518
5912
  console.error(`\u914D\u7F6E\u4E0D\u5B58\u5728: ${configPath}\uFF08\u9996\u6B21 start \u540E\u81EA\u52A8\u751F\u6210\uFF09`);
5519
5913
  process.exitCode = 1;
5520
5914
  return;
5521
5915
  }
5522
- console.log(readFileSync9(configPath, "utf8"));
5916
+ console.log(readFileSync10(configPath, "utf8"));
5523
5917
  }
5524
5918
  async function cmdUninstall() {
5525
5919
  const status = await uninstallService(createServiceContext());
@@ -5527,7 +5921,7 @@ async function cmdUninstall() {
5527
5921
  }
5528
5922
  function createServiceContext() {
5529
5923
  return {
5530
- cliEntrypointPath: fileURLToPath2(import.meta.url),
5924
+ cliEntrypointPath: fileURLToPath3(import.meta.url),
5531
5925
  nodePath: process.execPath,
5532
5926
  configPath: process.env.BRIDGE_CONFIG ?? defaultConfigPath(),
5533
5927
  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.31",
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",