claude-codex-wechat 0.1.28 → 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";
@@ -2463,6 +2782,10 @@ function executableExtensions() {
2463
2782
  const pathext = process.env.PATHEXT ?? ".COM;.EXE;.BAT;.CMD";
2464
2783
  return ["", ...pathext.split(";").filter(Boolean)];
2465
2784
  }
2785
+ function isTemporaryShim(candidate) {
2786
+ const tempDir = tmpdir();
2787
+ return candidate.startsWith(tempDir);
2788
+ }
2466
2789
  async function findExecutable(command) {
2467
2790
  const extensions = executableExtensions();
2468
2791
  if (command.includes("/") || command.includes("\\")) {
@@ -2472,13 +2795,20 @@ async function findExecutable(command) {
2472
2795
  return void 0;
2473
2796
  }
2474
2797
  const dirs = (process.env.PATH ?? "").split(delimiter).filter(Boolean);
2798
+ let fallback;
2475
2799
  for (const dir of dirs) {
2476
2800
  for (const ext of extensions) {
2477
2801
  const candidate = join9(dir, command + ext);
2478
- if (await isExecutableFile(candidate)) return candidate;
2802
+ if (await isExecutableFile(candidate)) {
2803
+ if (isTemporaryShim(candidate)) {
2804
+ fallback ??= candidate;
2805
+ continue;
2806
+ }
2807
+ return candidate;
2808
+ }
2479
2809
  }
2480
2810
  }
2481
- return void 0;
2811
+ return fallback;
2482
2812
  }
2483
2813
  function terminateChild(child, signal = "SIGTERM") {
2484
2814
  if (child.pid === void 0 || child.exitCode !== null || child.signalCode !== null) {
@@ -2625,10 +2955,12 @@ var ClaudeStreamingRunner = class {
2625
2955
  command;
2626
2956
  spawner;
2627
2957
  capabilityProbe;
2958
+ mcpConfigPath;
2628
2959
  constructor(input = {}) {
2629
2960
  this.command = input.command ?? "claude";
2630
2961
  this.spawner = input.spawner ?? defaultClaudeStreamSpawner;
2631
2962
  this.capabilityProbe = input.capabilityProbe ?? probeAppendSystemPromptSupport;
2963
+ this.mcpConfigPath = input.mcpConfigPath;
2632
2964
  }
2633
2965
  async startSession(input) {
2634
2966
  const resumeId = input.options?.providerSessionId;
@@ -2738,12 +3070,12 @@ var ClaudeStreamingRunner = class {
2738
3070
  }
2739
3071
  ensureHandle(session) {
2740
3072
  if (session.handle) return session.handle;
2741
- const args = buildStreamingArgs(session);
3073
+ const args = buildStreamingArgs(session, this.mcpConfigPath);
2742
3074
  session.handle = this.spawner({ command: this.command, args, cwd: session.cwd });
2743
3075
  return session.handle;
2744
3076
  }
2745
3077
  };
2746
- function buildStreamingArgs(session) {
3078
+ function buildStreamingArgs(session, mcpConfigPath) {
2747
3079
  const args = [
2748
3080
  "-p",
2749
3081
  "--input-format",
@@ -2754,7 +3086,8 @@ function buildStreamingArgs(session) {
2754
3086
  "--verbose",
2755
3087
  "--dangerously-skip-permissions",
2756
3088
  ...session.supportsAppendSystemPrompt ? ["--append-system-prompt", BRIDGE_APPEND_SYSTEM_PROMPT] : [],
2757
- "--replay-user-messages"
3089
+ "--replay-user-messages",
3090
+ ...mcpConfigPath ? ["--mcp-config", mcpConfigPath] : []
2758
3091
  ];
2759
3092
  if (session.resumeId) {
2760
3093
  args.push("--resume", session.resumeId);
@@ -3299,7 +3632,7 @@ var CodexProvider = class {
3299
3632
  // src/providers/defaultProviders.ts
3300
3633
  function createDefaultProviders(input = {}) {
3301
3634
  return [
3302
- new ClaudeCodeProvider({ runner: new ClaudeStreamingRunner({ command: input.claudeCommand }) }),
3635
+ new ClaudeCodeProvider({ runner: new ClaudeStreamingRunner({ command: input.claudeCommand, mcpConfigPath: input.mcpConfigPath }) }),
3303
3636
  new CodexProvider({ runner: new CodexInteractiveRunner({ command: input.codexCommand }) })
3304
3637
  ];
3305
3638
  }
@@ -3491,7 +3824,7 @@ function buildBridgeCommandHelpMarkdown(input) {
3491
3824
  }
3492
3825
 
3493
3826
  // src/session/currentConversationStore.ts
3494
- import { existsSync as existsSync6, readFileSync as readFileSync4 } from "node:fs";
3827
+ import { existsSync as existsSync6, readFileSync as readFileSync5 } from "node:fs";
3495
3828
  import { nanoid as nanoid2 } from "nanoid";
3496
3829
  var CurrentConversationStore = class {
3497
3830
  constructor(configPath, defaults) {
@@ -3551,7 +3884,7 @@ var CurrentConversationStore = class {
3551
3884
  }
3552
3885
  readState() {
3553
3886
  if (!existsSync6(this.configPath)) return {};
3554
- const raw = JSON.parse(readFileSync4(this.configPath, "utf8"));
3887
+ const raw = JSON.parse(readFileSync5(this.configPath, "utf8"));
3555
3888
  return raw && typeof raw === "object" ? raw : {};
3556
3889
  }
3557
3890
  writeCurrent(record) {
@@ -3622,7 +3955,12 @@ var MessageRouter = class _MessageRouter {
3622
3955
  */
3623
3956
  async sendToChat(message) {
3624
3957
  if (this.options.outboundGate) {
3625
- 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
+ });
3626
3964
  } else {
3627
3965
  await this.options.channel.sendMessage(message);
3628
3966
  }
@@ -4110,7 +4448,7 @@ function composeInboundText(content) {
4110
4448
  }
4111
4449
 
4112
4450
  // src/storage/lastProviderSessionStore.ts
4113
- import { existsSync as existsSync7, readFileSync as readFileSync5 } from "node:fs";
4451
+ import { existsSync as existsSync7, readFileSync as readFileSync6 } from "node:fs";
4114
4452
  var LastProviderSessionStore = class {
4115
4453
  constructor(configPath) {
4116
4454
  this.configPath = configPath;
@@ -4137,7 +4475,7 @@ var LastProviderSessionStore = class {
4137
4475
  }
4138
4476
  readState() {
4139
4477
  if (!existsSync7(this.configPath)) return {};
4140
- const raw = JSON.parse(readFileSync5(this.configPath, "utf8"));
4478
+ const raw = JSON.parse(readFileSync6(this.configPath, "utf8"));
4141
4479
  return raw && typeof raw === "object" ? raw : {};
4142
4480
  }
4143
4481
  writeState(state) {
@@ -4147,7 +4485,7 @@ var LastProviderSessionStore = class {
4147
4485
  };
4148
4486
 
4149
4487
  // src/storage/runtimeUserStore.ts
4150
- import { readFileSync as readFileSync6, existsSync as existsSync8 } from "node:fs";
4488
+ import { readFileSync as readFileSync7, existsSync as existsSync8 } from "node:fs";
4151
4489
  import { nanoid as nanoid3 } from "nanoid";
4152
4490
  var RuntimeUserStore = class {
4153
4491
  constructor(configPath) {
@@ -4208,7 +4546,7 @@ var RuntimeUserStore = class {
4208
4546
  }
4209
4547
  readState() {
4210
4548
  if (!existsSync8(this.configPath)) return {};
4211
- const raw = JSON.parse(readFileSync6(this.configPath, "utf8"));
4549
+ const raw = JSON.parse(readFileSync7(this.configPath, "utf8"));
4212
4550
  return raw && typeof raw === "object" ? raw : {};
4213
4551
  }
4214
4552
  writeState(state) {
@@ -4540,13 +4878,13 @@ var RelayTunnelRouter = class {
4540
4878
  };
4541
4879
 
4542
4880
  // src/shared/version.ts
4543
- import { readFileSync as readFileSync7 } from "node:fs";
4881
+ import { readFileSync as readFileSync8 } from "node:fs";
4544
4882
  import { dirname as dirname6, join as join10 } from "node:path";
4545
4883
  import { fileURLToPath } from "node:url";
4546
4884
  function readClientVersion() {
4547
4885
  try {
4548
4886
  const here2 = dirname6(fileURLToPath(import.meta.url));
4549
- const pkg = JSON.parse(readFileSync7(join10(here2, "..", "..", "package.json"), "utf8"));
4887
+ const pkg = JSON.parse(readFileSync8(join10(here2, "..", "..", "package.json"), "utf8"));
4550
4888
  return typeof pkg.version === "string" && pkg.version ? pkg.version : "0.0.0";
4551
4889
  } catch {
4552
4890
  return "0.0.0";
@@ -4601,9 +4939,27 @@ function createDaemonServer(options = {}) {
4601
4939
  const activeUserStore = options.activeUserStore ?? new RuntimeUserStore(configPath);
4602
4940
  const lastProviderSessions = new LastProviderSessionStore(configPath);
4603
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");
4604
4959
  const providerAdapters = options.providers ?? createDefaultProviders({
4605
4960
  claudeCommand: options.providerCommands?.claude?.command,
4606
- codexCommand: options.providerCommands?.codex?.command
4961
+ codexCommand: options.providerCommands?.codex?.command,
4962
+ mcpConfigPath
4607
4963
  });
4608
4964
  let currentConversation = conversation.getCurrent();
4609
4965
  if (currentConversation) {
@@ -4628,7 +4984,13 @@ function createDaemonServer(options = {}) {
4628
4984
  const weixinOutboundGate = managedWechatChannel && weixinStateStore ? new WeixinOutboundGate({
4629
4985
  store: weixinStateStore,
4630
4986
  send: async (chatId, msg) => {
4631
- 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
+ });
4632
4994
  }
4633
4995
  }) : void 0;
4634
4996
  const tunnelProvider = options.tunnelProvider ?? new RelayTunnelRouter({
@@ -4774,6 +5136,36 @@ function createDaemonServer(options = {}) {
4774
5136
  request.raw.on("error", cleanup);
4775
5137
  return reply.hijack();
4776
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
+ });
4777
5169
  app.addHook("onReady", async () => {
4778
5170
  await channel?.start({ background: true });
4779
5171
  });
@@ -4913,7 +5305,7 @@ async function resolveProviderCommands(providers) {
4913
5305
  }
4914
5306
 
4915
5307
  // src/daemon/staticFrontend.ts
4916
- import { readFileSync as readFileSync8 } from "node:fs";
5308
+ import { readFileSync as readFileSync9 } from "node:fs";
4917
5309
  import { join as join12 } from "node:path";
4918
5310
  import fastifyStatic from "@fastify/static";
4919
5311
  function attachStaticFrontend(webRoot2) {
@@ -4922,7 +5314,7 @@ function attachStaticFrontend(webRoot2) {
4922
5314
  root: webRoot2,
4923
5315
  wildcard: false
4924
5316
  });
4925
- const indexHtml = readFileSync8(join12(webRoot2, "index.html"), "utf8");
5317
+ const indexHtml = readFileSync9(join12(webRoot2, "index.html"), "utf8");
4926
5318
  app.setNotFoundHandler((request, reply) => {
4927
5319
  if (request.url.startsWith("/api")) {
4928
5320
  reply.code(404).send({ error: "not_found" });
@@ -4934,7 +5326,7 @@ function attachStaticFrontend(webRoot2) {
4934
5326
  }
4935
5327
 
4936
5328
  // src/daemon/service.ts
4937
- 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";
4938
5330
  import { mkdir as mkdir4, readFile as readFile6, rm as rm2, writeFile as writeFile4 } from "node:fs/promises";
4939
5331
  import { homedir as homedir8 } from "node:os";
4940
5332
  import { dirname as dirname8, join as join13 } from "node:path";
@@ -4988,13 +5380,13 @@ async function startService(context) {
4988
5380
  async function stopService(context) {
4989
5381
  if (process.platform === "darwin") {
4990
5382
  const spec = buildLaunchdSpec(context);
4991
- if (!existsSync9(spec.plistPath)) return await readServiceStatus(context);
5383
+ if (!existsSync10(spec.plistPath)) return await readServiceStatus(context);
4992
5384
  await runLaunchctl(["bootout", `gui/${process.getuid?.() ?? 0}`, spec.plistPath]).catch(() => void 0);
4993
5385
  return await readServiceStatus(context);
4994
5386
  }
4995
5387
  if (process.platform === "linux") {
4996
5388
  const spec = buildSystemdUserSpec(context);
4997
- if (!existsSync9(spec.unitPath)) return await readServiceStatus(context);
5389
+ if (!existsSync10(spec.unitPath)) return await readServiceStatus(context);
4998
5390
  await runSystemctl(["--user", "stop", spec.unitName]).catch(() => void 0);
4999
5391
  return await readServiceStatus(context);
5000
5392
  }
@@ -5012,7 +5404,7 @@ async function stopService(context) {
5012
5404
  async function uninstallService(context) {
5013
5405
  if (process.platform === "darwin") {
5014
5406
  const spec = buildLaunchdSpec(context);
5015
- if (existsSync9(spec.plistPath)) {
5407
+ if (existsSync10(spec.plistPath)) {
5016
5408
  await runLaunchctl(["bootout", `gui/${process.getuid?.() ?? 0}`, spec.plistPath]).catch(() => void 0);
5017
5409
  await rm2(spec.plistPath, { force: true });
5018
5410
  }
@@ -5020,7 +5412,7 @@ async function uninstallService(context) {
5020
5412
  }
5021
5413
  if (process.platform === "linux") {
5022
5414
  const spec = buildSystemdUserSpec(context);
5023
- if (existsSync9(spec.unitPath)) {
5415
+ if (existsSync10(spec.unitPath)) {
5024
5416
  await runSystemctl(["--user", "disable", "--now", spec.unitName]).catch(() => void 0);
5025
5417
  await rm2(spec.unitPath, { force: true });
5026
5418
  await runSystemctl(["--user", "daemon-reload"]).catch(() => void 0);
@@ -5070,7 +5462,7 @@ async function tailServiceLogs(context) {
5070
5462
  async function readServiceStatus(context) {
5071
5463
  if (process.platform === "darwin") {
5072
5464
  const spec = buildLaunchdSpec(context);
5073
- const installed = existsSync9(spec.plistPath);
5465
+ const installed = existsSync10(spec.plistPath);
5074
5466
  const running = installed ? await runLaunchctl(["print", `gui/${process.getuid?.() ?? 0}/${spec.label}`]).then(() => true, () => false) : false;
5075
5467
  return {
5076
5468
  manager: "launchd",
@@ -5084,7 +5476,7 @@ async function readServiceStatus(context) {
5084
5476
  }
5085
5477
  if (process.platform === "linux") {
5086
5478
  const spec = buildSystemdUserSpec(context);
5087
- const installed = existsSync9(spec.unitPath);
5479
+ const installed = existsSync10(spec.unitPath);
5088
5480
  const running = installed ? await runSystemctl(["--user", "is-active", "--quiet", spec.unitName]).then(() => true, () => false) : false;
5089
5481
  return {
5090
5482
  manager: "systemd-user",
@@ -5269,7 +5661,7 @@ function isProcessAlive(pid) {
5269
5661
  }
5270
5662
  }
5271
5663
  async function readPidFile(pidPath) {
5272
- if (!existsSync9(pidPath)) return null;
5664
+ if (!existsSync10(pidPath)) return null;
5273
5665
  try {
5274
5666
  const raw = (await readFile6(pidPath, "utf8")).trim();
5275
5667
  const pid = Number.parseInt(raw, 10);
@@ -5320,12 +5712,12 @@ async function spawnDetachedDaemon(spec) {
5320
5712
  }
5321
5713
  }
5322
5714
  async function tailFile(path, lines) {
5323
- if (!existsSync9(path)) return "";
5715
+ if (!existsSync10(path)) return "";
5324
5716
  const content = await readFile6(path, "utf8");
5325
5717
  return content.split(/\r?\n/).slice(-lines).join("\n").trim();
5326
5718
  }
5327
5719
  async function tailFiles(paths) {
5328
- const existing = paths.filter((path) => existsSync9(path));
5720
+ const existing = paths.filter((path) => existsSync10(path));
5329
5721
  for (const path of existing) {
5330
5722
  const tail = await tailFile(path, 200);
5331
5723
  if (tail) process.stdout.write(`[${path}]
@@ -5376,7 +5768,7 @@ function fileSizeHint(path) {
5376
5768
  }
5377
5769
 
5378
5770
  // src/cli.ts
5379
- var here = dirname9(fileURLToPath2(import.meta.url));
5771
+ var here = dirname9(fileURLToPath3(import.meta.url));
5380
5772
  var webRoot = join14(here, "..", "web");
5381
5773
  async function main() {
5382
5774
  const command = process.argv[2];
@@ -5426,7 +5818,7 @@ async function main() {
5426
5818
  }
5427
5819
  }
5428
5820
  async function cmdStart() {
5429
- if (!existsSync10(webRoot)) {
5821
+ if (!existsSync11(webRoot)) {
5430
5822
  console.error(`\u627E\u4E0D\u5230\u524D\u7AEF\u6784\u5EFA\u4EA7\u7269: ${webRoot}`);
5431
5823
  console.error("\u8BF7\u91CD\u65B0\u5B89\u88C5\u5B8C\u6574\u7684 npm \u5305\u3002");
5432
5824
  process.exitCode = 1;
@@ -5457,7 +5849,7 @@ async function cmdTail() {
5457
5849
  await tailServiceLogs(createServiceContext());
5458
5850
  }
5459
5851
  async function cmdDaemon() {
5460
- if (!existsSync10(webRoot)) {
5852
+ if (!existsSync11(webRoot)) {
5461
5853
  console.error(`\u627E\u4E0D\u5230\u524D\u7AEF\u6784\u5EFA\u4EA7\u7269: ${webRoot}`);
5462
5854
  process.exitCode = 1;
5463
5855
  return;
@@ -5470,13 +5862,13 @@ async function cmdDaemon() {
5470
5862
  async function cmdDoctor() {
5471
5863
  const configPath = process.env.BRIDGE_CONFIG ?? defaultConfigPath();
5472
5864
  console.log("claude-codex-wechat doctor\n");
5473
- report("\u914D\u7F6E\u6587\u4EF6", existsSync10(configPath) ? configPath : `\u7F3A\u5931 (${configPath})\uFF0C\u9996\u6B21 start \u65F6\u81EA\u52A8\u521B\u5EFA`);
5474
- 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})`);
5475
5867
  const claude = await findExecutable("claude");
5476
5868
  report("claude \u53EF\u6267\u884C", claude ?? "\u672A\u627E\u5230\uFF0C\u9700\u5148\u5B89\u88C5\u5E76\u767B\u5F55 Claude Code");
5477
5869
  const codex = await findExecutable("codex");
5478
5870
  report("codex \u53EF\u6267\u884C", codex ?? "\u672A\u627E\u5230\uFF08\u4EC5\u4F7F\u7528 Claude \u65F6\u53EF\u5FFD\u7565\uFF09");
5479
- if (existsSync10(configPath)) {
5871
+ if (existsSync11(configPath)) {
5480
5872
  const config = loadBridgeConfig(configPath);
5481
5873
  const wechat = config.wechat;
5482
5874
  report("\u5FAE\u4FE1\u542F\u7528", wechat?.enabled ? "\u662F" : "\u5426");
@@ -5487,7 +5879,7 @@ async function cmdDoctor() {
5487
5879
  }
5488
5880
  function printUpdateHint() {
5489
5881
  const configPath = process.env.BRIDGE_CONFIG ?? defaultConfigPath();
5490
- if (!existsSync10(configPath)) return;
5882
+ if (!existsSync11(configPath)) return;
5491
5883
  let update;
5492
5884
  try {
5493
5885
  update = loadBridgeConfig(configPath).update;
@@ -5503,12 +5895,12 @@ function printUpdateHint() {
5503
5895
  }
5504
5896
  function cmdPrintConfig() {
5505
5897
  const configPath = process.env.BRIDGE_CONFIG ?? defaultConfigPath();
5506
- if (!existsSync10(configPath)) {
5898
+ if (!existsSync11(configPath)) {
5507
5899
  console.error(`\u914D\u7F6E\u4E0D\u5B58\u5728: ${configPath}\uFF08\u9996\u6B21 start \u540E\u81EA\u52A8\u751F\u6210\uFF09`);
5508
5900
  process.exitCode = 1;
5509
5901
  return;
5510
5902
  }
5511
- console.log(readFileSync9(configPath, "utf8"));
5903
+ console.log(readFileSync10(configPath, "utf8"));
5512
5904
  }
5513
5905
  async function cmdUninstall() {
5514
5906
  const status = await uninstallService(createServiceContext());
@@ -5516,7 +5908,7 @@ async function cmdUninstall() {
5516
5908
  }
5517
5909
  function createServiceContext() {
5518
5910
  return {
5519
- cliEntrypointPath: fileURLToPath2(import.meta.url),
5911
+ cliEntrypointPath: fileURLToPath3(import.meta.url),
5520
5912
  nodePath: process.execPath,
5521
5913
  configPath: process.env.BRIDGE_CONFIG ?? defaultConfigPath(),
5522
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.28",
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",