From 5ed0e63095f06ff1e36023f283fa68e4557fec43 Mon Sep 17 00:00:00 2001 From: claude-code-best Date: Sun, 19 Apr 2026 20:55:13 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E5=B0=86=20weixin=20=E6=A8=A1?= =?UTF-8?q?=E5=9D=97=E4=BB=8E=20src/=20=E8=BF=81=E7=A7=BB=E8=87=B3=20packa?= =?UTF-8?q?ges/weixin=20=E5=B7=A5=E4=BD=9C=E5=8C=BA=E5=8C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 将 src/services/weixin/ 中的纯业务逻辑迁入 @claude-code-best/weixin workspace 包,降低 src/ 耦合度。仅保留 server.ts 作为薄适配层。 - 迁移 7 个无修改的纯模块 (types/api/accounts/login/pairing/media/send) - monitor.ts 内联 PERMISSION_REPLY_RE 正则,解除对 src/ 的依赖 - permissions.ts 本地定义 ChannelPermissionRequestParams 接口 - cli.ts 拆分:serve 子命令通过回调注入,login/access 保留在包内 - server.ts 重写为从 @claude-code-best/weixin 导入 - 新增 cli-serve.ts 作为 serve 入口薄壳 Co-Authored-By: Claude Opus 4.6 --- bun.lock | 10 ++ package.json | 1 + packages/weixin/package.json | 11 ++ .../weixin/src}/__tests__/accounts.test.ts | 0 .../weixin/src}/__tests__/media.test.ts | 0 .../weixin/src}/__tests__/monitor.test.ts | 0 .../weixin/src}/__tests__/pairing.test.ts | 0 .../weixin/src}/__tests__/permissions.test.ts | 0 .../weixin/src}/__tests__/send.test.ts | 0 .../weixin/src}/accounts.ts | 0 .../weixin => packages/weixin/src}/api.ts | 0 .../weixin => packages/weixin/src}/cli.ts | 13 +- packages/weixin/src/index.ts | 111 ++++++++++++++++++ .../weixin => packages/weixin/src}/login.ts | 0 .../weixin => packages/weixin/src}/media.ts | 0 .../weixin => packages/weixin/src}/monitor.ts | 3 +- .../weixin => packages/weixin/src}/pairing.ts | 0 .../weixin/src}/permissions.ts | 12 +- .../weixin => packages/weixin/src}/send.ts | 0 .../weixin => packages/weixin/src}/types.ts | 0 packages/weixin/tsconfig.json | 5 + src/entrypoints/cli.tsx | 5 +- src/services/weixin/cli-serve.ts | 1 + src/services/weixin/server.ts | 21 +++- tsconfig.json | 4 +- 25 files changed, 183 insertions(+), 14 deletions(-) create mode 100644 packages/weixin/package.json rename {src/services/weixin => packages/weixin/src}/__tests__/accounts.test.ts (100%) rename {src/services/weixin => packages/weixin/src}/__tests__/media.test.ts (100%) rename {src/services/weixin => packages/weixin/src}/__tests__/monitor.test.ts (100%) rename {src/services/weixin => packages/weixin/src}/__tests__/pairing.test.ts (100%) rename {src/services/weixin => packages/weixin/src}/__tests__/permissions.test.ts (100%) rename {src/services/weixin => packages/weixin/src}/__tests__/send.test.ts (100%) rename {src/services/weixin => packages/weixin/src}/accounts.ts (100%) rename {src/services/weixin => packages/weixin/src}/api.ts (100%) rename {src/services/weixin => packages/weixin/src}/cli.ts (89%) create mode 100644 packages/weixin/src/index.ts rename {src/services/weixin => packages/weixin/src}/login.ts (100%) rename {src/services/weixin => packages/weixin/src}/media.ts (100%) rename {src/services/weixin => packages/weixin/src}/monitor.ts (98%) rename {src/services/weixin => packages/weixin/src}/pairing.ts (100%) rename {src/services/weixin => packages/weixin/src}/permissions.ts (86%) rename {src/services/weixin => packages/weixin/src}/send.ts (100%) rename {src/services/weixin => packages/weixin/src}/types.ts (100%) create mode 100644 packages/weixin/tsconfig.json create mode 100644 src/services/weixin/cli-serve.ts diff --git a/bun.lock b/bun.lock index 6b1c83465..01df05be8 100644 --- a/bun.lock +++ b/bun.lock @@ -34,6 +34,7 @@ "@claude-code-best/agent-tools": "workspace:*", "@claude-code-best/builtin-tools": "workspace:*", "@claude-code-best/mcp-client": "workspace:*", + "@claude-code-best/weixin": "workspace:*", "@commander-js/extra-typings": "^14.0.0", "@growthbook/growthbook": "^1.6.5", "@langfuse/otel": "^5.1.0", @@ -320,6 +321,13 @@ "name": "url-handler-napi", "version": "1.0.0", }, + "packages/weixin": { + "name": "@claude-code-best/weixin", + "version": "1.0.0", + "dependencies": { + "qrcode": "^1.5.4", + }, + }, }, "packages": { "@agentclientprotocol/sdk": ["@agentclientprotocol/sdk@0.19.0", "https://registry.npmmirror.com/@agentclientprotocol/sdk/-/sdk-0.19.0.tgz", { "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" } }, "sha512-U9I8ws9WTOk6jCBAWpXefGSDgVXn14/kV6HFzwWGcstQ02mOQgClMAROHmoIn9GqZbDBDEOkdIbP4P4TEMQdug=="], @@ -564,6 +572,8 @@ "@claude-code-best/mcp-client": ["@claude-code-best/mcp-client@workspace:packages/mcp-client"], + "@claude-code-best/weixin": ["@claude-code-best/weixin@workspace:packages/weixin"], + "@commander-js/extra-typings": ["@commander-js/extra-typings@14.0.0", "https://registry.npmmirror.com/@commander-js/extra-typings/-/extra-typings-14.0.0.tgz", { "peerDependencies": { "commander": "~14.0.0" } }, "sha512-hIn0ncNaJRLkZrxBIp5AsW/eXEHNKYQBh0aPdoUqNgD+Io3NIykQqpKFyKcuasZhicGaEZJX/JBSIkZ4e5x8Dg=="], "@emnapi/core": ["@emnapi/core@1.9.2", "https://registry.npmmirror.com/@emnapi/core/-/core-1.9.2.tgz", { "dependencies": { "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" } }, "sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA=="], diff --git a/package.json b/package.json index 3111471f5..fdb64dd14 100644 --- a/package.json +++ b/package.json @@ -90,6 +90,7 @@ "@claude-code-best/agent-tools": "workspace:*", "@claude-code-best/builtin-tools": "workspace:*", "@claude-code-best/mcp-client": "workspace:*", + "@claude-code-best/weixin": "workspace:*", "@commander-js/extra-typings": "^14.0.0", "@growthbook/growthbook": "^1.6.5", "@langfuse/otel": "^5.1.0", diff --git a/packages/weixin/package.json b/packages/weixin/package.json new file mode 100644 index 000000000..f57d36295 --- /dev/null +++ b/packages/weixin/package.json @@ -0,0 +1,11 @@ +{ + "name": "@claude-code-best/weixin", + "version": "1.0.0", + "private": true, + "type": "module", + "main": "./src/index.ts", + "types": "./src/index.ts", + "dependencies": { + "qrcode": "^1.5.4" + } +} diff --git a/src/services/weixin/__tests__/accounts.test.ts b/packages/weixin/src/__tests__/accounts.test.ts similarity index 100% rename from src/services/weixin/__tests__/accounts.test.ts rename to packages/weixin/src/__tests__/accounts.test.ts diff --git a/src/services/weixin/__tests__/media.test.ts b/packages/weixin/src/__tests__/media.test.ts similarity index 100% rename from src/services/weixin/__tests__/media.test.ts rename to packages/weixin/src/__tests__/media.test.ts diff --git a/src/services/weixin/__tests__/monitor.test.ts b/packages/weixin/src/__tests__/monitor.test.ts similarity index 100% rename from src/services/weixin/__tests__/monitor.test.ts rename to packages/weixin/src/__tests__/monitor.test.ts diff --git a/src/services/weixin/__tests__/pairing.test.ts b/packages/weixin/src/__tests__/pairing.test.ts similarity index 100% rename from src/services/weixin/__tests__/pairing.test.ts rename to packages/weixin/src/__tests__/pairing.test.ts diff --git a/src/services/weixin/__tests__/permissions.test.ts b/packages/weixin/src/__tests__/permissions.test.ts similarity index 100% rename from src/services/weixin/__tests__/permissions.test.ts rename to packages/weixin/src/__tests__/permissions.test.ts diff --git a/src/services/weixin/__tests__/send.test.ts b/packages/weixin/src/__tests__/send.test.ts similarity index 100% rename from src/services/weixin/__tests__/send.test.ts rename to packages/weixin/src/__tests__/send.test.ts diff --git a/src/services/weixin/accounts.ts b/packages/weixin/src/accounts.ts similarity index 100% rename from src/services/weixin/accounts.ts rename to packages/weixin/src/accounts.ts diff --git a/src/services/weixin/api.ts b/packages/weixin/src/api.ts similarity index 100% rename from src/services/weixin/api.ts rename to packages/weixin/src/api.ts diff --git a/src/services/weixin/cli.ts b/packages/weixin/src/cli.ts similarity index 89% rename from src/services/weixin/cli.ts rename to packages/weixin/src/cli.ts index 1cc214aed..1170eb351 100644 --- a/src/services/weixin/cli.ts +++ b/packages/weixin/src/cli.ts @@ -1,7 +1,6 @@ import { clearAccount, DEFAULT_BASE_URL, loadAccount, saveAccount } from './accounts.js' import { startLogin, waitForLogin } from './login.js' import { confirmPairing } from './pairing.js' -import { runWeixinMcpServer } from './server.js' function printUsage(): void { process.stdout.write( @@ -91,12 +90,20 @@ function runAccess(args: string[]): void { process.stdout.write(`Paired successfully: ${userId}\n`) } -export async function handleWeixinCli(args: string[]): Promise { +export async function handleWeixinCli( + args: string[], + serveHandler?: () => Promise, +): Promise { const [subcommand, ...rest] = args switch (subcommand) { case 'serve': - await runWeixinMcpServer() + if (serveHandler) { + await serveHandler() + } else { + process.stderr.write('[weixin] serve handler not available in this context.\n') + process.exit(1) + } return case 'login': await runLogin(rest[0] === 'clear') diff --git a/packages/weixin/src/index.ts b/packages/weixin/src/index.ts new file mode 100644 index 000000000..8bc0967f4 --- /dev/null +++ b/packages/weixin/src/index.ts @@ -0,0 +1,111 @@ +// @claude-code-best/weixin — WeChat channel integration + +// Types +export { + MessageType, + MessageItemType, + MessageState, + UploadMediaType, + TypingStatus, +} from './types.js' +export type { + BaseInfo, + CDNMedia, + TextItem, + ImageItem, + VoiceItem, + FileItem, + VideoItem, + RefMessage, + MessageItem, + WeixinMessage, + GetUpdatesReq, + GetUpdatesResp, + SendMessageReq, + GetUploadUrlReq, + GetUploadUrlResp, + GetConfigResp, + SendTypingReq, + SendTypingResp, +} from './types.js' + +// API client +export { + getUpdates, + sendMessage, + getUploadUrl, + getConfig, + sendTyping, +} from './api.js' + +// Account management +export { + DEFAULT_BASE_URL, + CDN_BASE_URL, + getStateDir, + loadAccount, + saveAccount, + clearAccount, +} from './accounts.js' +export type { AccountData } from './accounts.js' + +// Login +export { startLogin, waitForLogin } from './login.js' +export type { QRCodeResult, LoginResult } from './login.js' + +// Pairing / access control +export { + loadAccessConfig, + saveAccessConfig, + isAllowed, + addPendingPairing, + confirmPairing, +} from './pairing.js' +export type { AccessConfig } from './pairing.js' + +// Media encryption / upload +export { + encryptAesEcb, + decryptAesEcb, + aesEcbPaddedSize, + buildCdnDownloadUrl, + buildCdnUploadUrl, + parseAesKey, + downloadAndDecrypt, + uploadFile, + guessMediaType, + downloadRemoteToTemp, +} from './media.js' +export type { UploadedFileInfo } from './media.js' + +// Message sending +export { markdownToPlainText, sendText, sendMediaFile } from './send.js' + +// Monitor (message polling) +export { + getContextToken, + extractPermissionReply, + startPollLoop, +} from './monitor.js' +export type { + ParsedMessage, + OnMessageCallback, + PermissionResponse, + OnPermissionResponseCallback, +} from './monitor.js' + +// Permission state +export { + ChannelPermissionRequestParams, + setActivePermissionChat, + getActivePermissionChat, + savePendingPermission, + consumePendingPermission, +} from './permissions.js' +export type { + PendingPermissionRequest, + ActivePermissionChat, +} from './permissions.js' + +// CLI +export { handleWeixinCli } from './cli.js' diff --git a/src/services/weixin/login.ts b/packages/weixin/src/login.ts similarity index 100% rename from src/services/weixin/login.ts rename to packages/weixin/src/login.ts diff --git a/src/services/weixin/media.ts b/packages/weixin/src/media.ts similarity index 100% rename from src/services/weixin/media.ts rename to packages/weixin/src/media.ts diff --git a/src/services/weixin/monitor.ts b/packages/weixin/src/monitor.ts similarity index 98% rename from src/services/weixin/monitor.ts rename to packages/weixin/src/monitor.ts index f99f81b90..2b2db0307 100644 --- a/src/services/weixin/monitor.ts +++ b/packages/weixin/src/monitor.ts @@ -6,7 +6,8 @@ import { } from 'node:fs' import { tmpdir } from 'node:os' import { basename, join } from 'node:path' -import { PERMISSION_REPLY_RE } from '../mcp/channelPermissions.js' +// Matches the canonical definition in src/services/mcp/channelPermissions.ts +const PERMISSION_REPLY_RE = /^\s*(y|yes|n|no)\s+([a-km-z]{5})\s*$/i import { getUpdates } from './api.js' import { getStateDir } from './accounts.js' import { downloadAndDecrypt } from './media.js' diff --git a/src/services/weixin/pairing.ts b/packages/weixin/src/pairing.ts similarity index 100% rename from src/services/weixin/pairing.ts rename to packages/weixin/src/pairing.ts diff --git a/src/services/weixin/permissions.ts b/packages/weixin/src/permissions.ts similarity index 86% rename from src/services/weixin/permissions.ts rename to packages/weixin/src/permissions.ts index 03d9fc759..9d4480c8e 100644 --- a/src/services/weixin/permissions.ts +++ b/packages/weixin/src/permissions.ts @@ -1,4 +1,14 @@ -import type { ChannelPermissionRequestParams } from '../mcp/channelNotification.js' +/** Mirrors ChannelPermissionRequestParams from src/services/mcp/channelNotification.ts */ +export interface ChannelPermissionRequestParams { + request_id: string + tool_name: string + description: string + input_preview: string + channel_context?: { + source_server?: string + chat_id?: string + } +} export type PendingPermissionRequest = ChannelPermissionRequestParams & { chatId: string diff --git a/src/services/weixin/send.ts b/packages/weixin/src/send.ts similarity index 100% rename from src/services/weixin/send.ts rename to packages/weixin/src/send.ts diff --git a/src/services/weixin/types.ts b/packages/weixin/src/types.ts similarity index 100% rename from src/services/weixin/types.ts rename to packages/weixin/src/types.ts diff --git a/packages/weixin/tsconfig.json b/packages/weixin/tsconfig.json new file mode 100644 index 000000000..af2850cc4 --- /dev/null +++ b/packages/weixin/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "../../tsconfig.base.json", + "include": ["src/**/*.ts"], + "exclude": ["node_modules", "dist"] +} diff --git a/src/entrypoints/cli.tsx b/src/entrypoints/cli.tsx index 68b587610..90ae0232f 100644 --- a/src/entrypoints/cli.tsx +++ b/src/entrypoints/cli.tsx @@ -142,8 +142,9 @@ async function main(): Promise { if (args[0] === 'weixin') { profileCheckpoint('cli_weixin_path') - const { handleWeixinCli } = await import('../services/weixin/cli.js') - await handleWeixinCli(args.slice(1)) + const { handleWeixinCli } = await import('@claude-code-best/weixin') + const { runWeixinMcpServer } = await import('../services/weixin/cli-serve.js') + await handleWeixinCli(args.slice(1), runWeixinMcpServer) return } diff --git a/src/services/weixin/cli-serve.ts b/src/services/weixin/cli-serve.ts new file mode 100644 index 000000000..d4b655dc3 --- /dev/null +++ b/src/services/weixin/cli-serve.ts @@ -0,0 +1 @@ +export { runWeixinMcpServer } from './server.js' diff --git a/src/services/weixin/server.ts b/src/services/weixin/server.ts index bc19ee482..d0cde3cff 100644 --- a/src/services/weixin/server.ts +++ b/src/services/weixin/server.ts @@ -14,12 +14,21 @@ import { shutdownDatadog } from '../analytics/datadog.js' import { shutdown1PEventLogging } from '../analytics/firstPartyEventLogger.js' import { enableConfigs } from '../../utils/config.js' import { logForDebugging } from '../../utils/debug.js' -import { CDN_BASE_URL, DEFAULT_BASE_URL, loadAccount } from './accounts.js' -import { getConfig, sendTyping } from './api.js' -import { getContextToken, startPollLoop, type ParsedMessage } from './monitor.js' -import { getActivePermissionChat, savePendingPermission } from './permissions.js' -import { sendMediaFile, sendText } from './send.js' -import { TypingStatus } from './types.js' +import { + CDN_BASE_URL, + DEFAULT_BASE_URL, + loadAccount, + getConfig, + sendTyping, + getContextToken, + startPollLoop, + getActivePermissionChat, + savePendingPermission, + sendMediaFile, + sendText, + TypingStatus, +} from '@claude-code-best/weixin' +import type { ParsedMessage } from '@claude-code-best/weixin' function formatPermissionRequestMessage( request: ChannelPermissionRequestParams, diff --git a/tsconfig.json b/tsconfig.json index 4c5d39ab8..9472b1e18 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -19,7 +19,9 @@ "@claude-code-best/mcp-client/*": ["./packages/mcp-client/src/*"], "@claude-code-best/mcp-client": ["./packages/mcp-client/src/index.ts"], "@claude-code-best/agent-tools/*": ["./packages/agent-tools/src/*"], - "@claude-code-best/agent-tools": ["./packages/agent-tools/src/index.ts"] + "@claude-code-best/agent-tools": ["./packages/agent-tools/src/index.ts"], + "@claude-code-best/weixin/*": ["./packages/weixin/src/*"], + "@claude-code-best/weixin": ["./packages/weixin/src/index.ts"] } }, "include": ["src/**/*.ts", "src/**/*.tsx", "packages/**/*.ts", "packages/**/*.tsx"],