cc-wechat 0.1.0

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.
Files changed (48) hide show
  1. package/.claude-plugin/plugin.json +6 -0
  2. package/.mcp.json +8 -0
  3. package/.pace/stop-block-count +1 -0
  4. package/LICENSE +21 -0
  5. package/README.md +83 -0
  6. package/dist/auth.d.ts +18 -0
  7. package/dist/auth.js +351 -0
  8. package/dist/auth.js.map +1 -0
  9. package/dist/cdn.d.ts +39 -0
  10. package/dist/cdn.js +228 -0
  11. package/dist/cdn.js.map +1 -0
  12. package/dist/cli.d.ts +5 -0
  13. package/dist/cli.js +127 -0
  14. package/dist/cli.js.map +1 -0
  15. package/dist/ilink-api.d.ts +33 -0
  16. package/dist/ilink-api.js +206 -0
  17. package/dist/ilink-api.js.map +1 -0
  18. package/dist/patch.d.ts +7 -0
  19. package/dist/patch.js +165 -0
  20. package/dist/patch.js.map +1 -0
  21. package/dist/server.d.ts +6 -0
  22. package/dist/server.js +406 -0
  23. package/dist/server.js.map +1 -0
  24. package/dist/store.d.ts +24 -0
  25. package/dist/store.js +57 -0
  26. package/dist/store.js.map +1 -0
  27. package/dist/text-utils.d.ts +7 -0
  28. package/dist/text-utils.js +56 -0
  29. package/dist/text-utils.js.map +1 -0
  30. package/dist/types.d.ts +98 -0
  31. package/dist/types.js +13 -0
  32. package/dist/types.js.map +1 -0
  33. package/package.json +24 -0
  34. package/packages/cc-channel-patch/README.md +36 -0
  35. package/packages/cc-channel-patch/index.mjs +228 -0
  36. package/packages/cc-channel-patch/package.json +11 -0
  37. package/skills/configure/SKILL.md +32 -0
  38. package/src/auth.ts +400 -0
  39. package/src/cdn.ts +261 -0
  40. package/src/cli.ts +121 -0
  41. package/src/ilink-api.ts +279 -0
  42. package/src/patch.ts +182 -0
  43. package/src/qrcode-terminal.d.ts +10 -0
  44. package/src/server.ts +445 -0
  45. package/src/store.ts +62 -0
  46. package/src/text-utils.ts +56 -0
  47. package/src/types.ts +94 -0
  48. package/tsconfig.json +17 -0
@@ -0,0 +1,6 @@
1
+ {
2
+ "id": "wechat-channel",
3
+ "name": "WeChat Channel",
4
+ "version": "0.1.0",
5
+ "description": "WeChat channel for Claude Code via iLink Bot API"
6
+ }
package/.mcp.json ADDED
@@ -0,0 +1,8 @@
1
+ {
2
+ "mcpServers": {
3
+ "wechat-channel": {
4
+ "command": "node",
5
+ "args": ["dist/server.js"]
6
+ }
7
+ }
8
+ }
@@ -0,0 +1 @@
1
+ 0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,83 @@
1
+ # cc-wechat
2
+
3
+ 用微信控制 Claude Code。扫码即用,不需要 OpenClaw。
4
+
5
+ ## 架构
6
+
7
+ 微信用户发消息 → iLink Bot API (ilinkai.weixin.qq.com) → cc-wechat MCP Server → Claude Code
8
+ Claude Code → reply tool → cc-wechat MCP Server → iLink Bot API → 微信用户
9
+
10
+ 底层直接调用腾讯的 iLink Bot API,不依赖 OpenClaw。
11
+
12
+ ## 前提
13
+
14
+ - [Claude Code](https://docs.anthropic.com/en/docs/claude-code) v2.1.80+
15
+ - Node.js >= 22
16
+ - 微信(iOS / Android / Mac / Windows 均可扫码)
17
+
18
+ ## 安装
19
+
20
+ ### 一键安装
21
+
22
+ npx cc-wechat install
23
+
24
+ 这会:
25
+ 1. 注册 MCP server 到 Claude Code(user 级别)
26
+ 2. 在终端显示二维码,微信扫码登录
27
+ 3. 打印启动命令
28
+
29
+ ### 启动
30
+
31
+ claude --dangerously-load-development-channels server:wechat-channel
32
+
33
+ ### 手动安装
34
+
35
+ npm i -g cc-wechat
36
+ claude mcp add -s user wechat-channel node $(which cc-wechat-server)
37
+ npx cc-wechat login
38
+ claude --dangerously-load-development-channels server:wechat-channel
39
+
40
+ ## 使用
41
+
42
+ 登录后,在微信里发消息,Claude Code 会实时收到并处理。Claude 通过 reply 工具回复,消息会出现在你的微信对话里。
43
+
44
+ 支持发送图片、视频和文件(通过 reply 工具的 media 参数)。
45
+
46
+ ### 重新登录
47
+
48
+ npx cc-wechat login
49
+
50
+ ### 在 Claude Code 中登录
51
+
52
+ 如果已经在 Claude Code 中,直接用 login 工具扫码。
53
+
54
+ ## 工作原理
55
+
56
+ 直接调用腾讯的 iLink Bot API(6 个 HTTP 接口):
57
+
58
+ | API | 功能 |
59
+ |-----|------|
60
+ | get_bot_qrcode | 获取登录二维码 |
61
+ | get_qrcode_status | 轮询扫码状态 |
62
+ | getupdates | 长轮询收消息(35s 超时) |
63
+ | sendmessage | 发送消息 |
64
+ | sendtyping | 打字状态指示 |
65
+ | getconfig | 获取 typing ticket |
66
+
67
+ ## 状态文件
68
+
69
+ ~/.claude/channels/wechat/
70
+ ├── account.json # 登录凭证
71
+ └── sync-buf.txt # 消息同步游标
72
+
73
+ ## 限制
74
+
75
+ - 仅支持单账号
76
+ - 权限审批仍需在终端(Claude Code 的固有限制)
77
+ - 语音消息仅提取转写文本
78
+ - Session 会过期,需重新扫码
79
+ - 需要用户先发消息(context_token 按消息发放)
80
+
81
+ ## License
82
+
83
+ MIT
package/dist/auth.d.ts ADDED
@@ -0,0 +1,18 @@
1
+ /**
2
+ * QR 扫码登录 — 终端 ASCII 模式 + 浏览器弹窗模式
3
+ */
4
+ export interface LoginResult {
5
+ token: string;
6
+ accountId: string;
7
+ baseUrl?: string;
8
+ }
9
+ /**
10
+ * 终端 ASCII 二维码登录,适用于 CLI 调用
11
+ * 输出到 stderr 避免干扰 MCP stdio
12
+ */
13
+ export declare function loginTerminal(baseUrl?: string): Promise<LoginResult>;
14
+ /**
15
+ * 浏览器弹窗二维码登录,适用于 MCP 内 login tool
16
+ * 启动本地 HTTP 服务展示二维码页面
17
+ */
18
+ export declare function loginBrowser(baseUrl?: string): Promise<LoginResult>;
package/dist/auth.js ADDED
@@ -0,0 +1,351 @@
1
+ /**
2
+ * QR 扫码登录 — 终端 ASCII 模式 + 浏览器弹窗模式
3
+ */
4
+ import http from 'node:http';
5
+ import { exec } from 'node:child_process';
6
+ import { getQRCode, pollQRStatus, DEFAULT_BASE_URL } from './ilink-api.js';
7
+ // ─── 常量 ─────────────────────────────────────────────
8
+ const MAX_QR_REFRESH = 3;
9
+ const LOGIN_TIMEOUT_MS = 5 * 60_000; // 5 分钟
10
+ const QR_WEB_PORT_START = 18891;
11
+ const QR_WEB_PORT_END = 18899;
12
+ // ─── 终端 ASCII 二维码模式 ────────────────────────────
13
+ /**
14
+ * 终端 ASCII 二维码登录,适用于 CLI 调用
15
+ * 输出到 stderr 避免干扰 MCP stdio
16
+ */
17
+ export async function loginTerminal(baseUrl) {
18
+ const qrMod = await import('qrcode-terminal');
19
+ const qrTerminal = qrMod.default ?? qrMod;
20
+ for (let qrRefreshCount = 0; qrRefreshCount < MAX_QR_REFRESH; qrRefreshCount++) {
21
+ const qrResp = await getQRCode(baseUrl);
22
+ // 输出 ASCII 二维码到 stderr
23
+ await new Promise((resolve) => {
24
+ qrTerminal.generate(qrResp.qrcode_img_content, { small: true }, (qr) => {
25
+ process.stderr.write('\n' + qr + '\n');
26
+ process.stderr.write('请使用微信扫描上方二维码登录\n\n');
27
+ resolve();
28
+ });
29
+ });
30
+ const deadline = Date.now() + LOGIN_TIMEOUT_MS;
31
+ let scannedNotified = false;
32
+ // 内层轮询循环
33
+ while (Date.now() < deadline) {
34
+ const status = await pollQRStatus(qrResp.qrcode, baseUrl);
35
+ switch (status.status) {
36
+ case 'wait':
37
+ break;
38
+ case 'scaned':
39
+ if (!scannedNotified) {
40
+ process.stderr.write('已扫码,请在手机上确认...\n');
41
+ scannedNotified = true;
42
+ }
43
+ break;
44
+ case 'expired':
45
+ process.stderr.write('二维码已过期,正在刷新...\n');
46
+ break;
47
+ case 'confirmed': {
48
+ if (!status.bot_token || !status.ilink_bot_id) {
49
+ throw new Error('登录确认但缺少 bot_token 或 ilink_bot_id');
50
+ }
51
+ process.stderr.write('登录成功!\n');
52
+ return {
53
+ token: status.bot_token,
54
+ accountId: status.ilink_bot_id,
55
+ baseUrl: status.baseurl ?? baseUrl ?? DEFAULT_BASE_URL,
56
+ };
57
+ }
58
+ }
59
+ if (status.status === 'expired')
60
+ break;
61
+ // 轮询间隔 1 秒
62
+ await new Promise((r) => setTimeout(r, 1000));
63
+ }
64
+ }
65
+ throw new Error(`二维码已刷新 ${MAX_QR_REFRESH} 次仍未登录,请重试`);
66
+ }
67
+ // ─── 浏览器弹窗模式 ──────────────────────────────────
68
+ /**
69
+ * 浏览器弹窗二维码登录,适用于 MCP 内 login tool
70
+ * 启动本地 HTTP 服务展示二维码页面
71
+ */
72
+ export async function loginBrowser(baseUrl) {
73
+ const qrResp = await getQRCode(baseUrl);
74
+ // 状态变量
75
+ let currentStatus = 'wait';
76
+ let currentQrUrl = qrResp.qrcode_img_content;
77
+ let currentQrCode = qrResp.qrcode;
78
+ let failMessage = '';
79
+ // 创建 HTTP 服务
80
+ const server = http.createServer((req, res) => {
81
+ const url = new URL(req.url ?? '/', `http://localhost`);
82
+ if (url.pathname === '/' && req.method === 'GET') {
83
+ // 主页面
84
+ res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
85
+ res.end(buildLoginPage(currentQrUrl));
86
+ return;
87
+ }
88
+ if (url.pathname === '/status' && req.method === 'GET') {
89
+ res.writeHead(200, { 'Content-Type': 'application/json' });
90
+ res.end(JSON.stringify({ status: currentStatus, message: failMessage }));
91
+ return;
92
+ }
93
+ if (url.pathname === '/qr-refresh' && req.method === 'GET') {
94
+ res.writeHead(200, { 'Content-Type': 'application/json' });
95
+ res.end(JSON.stringify({ url: currentQrUrl }));
96
+ return;
97
+ }
98
+ res.writeHead(404);
99
+ res.end('Not Found');
100
+ });
101
+ // 尝试绑定端口
102
+ const port = await findAvailablePort(server);
103
+ try {
104
+ // 打开浏览器
105
+ openBrowser(`http://localhost:${port}`);
106
+ process.stderr.write(`登录页面已打开: http://localhost:${port}\n`);
107
+ // 后台轮询循环
108
+ const deadline = Date.now() + LOGIN_TIMEOUT_MS;
109
+ let qrRefreshCount = 0;
110
+ while (Date.now() < deadline) {
111
+ const status = await pollQRStatus(currentQrCode, baseUrl);
112
+ currentStatus = status.status;
113
+ switch (status.status) {
114
+ case 'wait':
115
+ break;
116
+ case 'scaned':
117
+ break;
118
+ case 'expired': {
119
+ qrRefreshCount++;
120
+ if (qrRefreshCount >= MAX_QR_REFRESH) {
121
+ failMessage = '二维码已多次过期,请重新发起登录';
122
+ throw new Error(failMessage);
123
+ }
124
+ // 刷新二维码
125
+ const newQr = await getQRCode(baseUrl);
126
+ currentQrUrl = newQr.qrcode_img_content;
127
+ currentQrCode = newQr.qrcode;
128
+ currentStatus = 'wait';
129
+ break;
130
+ }
131
+ case 'confirmed': {
132
+ if (!status.bot_token || !status.ilink_bot_id) {
133
+ throw new Error('登录确认但缺少 bot_token 或 ilink_bot_id');
134
+ }
135
+ currentStatus = 'success';
136
+ return {
137
+ token: status.bot_token,
138
+ accountId: status.ilink_bot_id,
139
+ baseUrl: status.baseurl ?? baseUrl ?? DEFAULT_BASE_URL,
140
+ };
141
+ }
142
+ }
143
+ await new Promise((r) => setTimeout(r, 1000));
144
+ }
145
+ throw new Error('登录超时,请重试');
146
+ }
147
+ finally {
148
+ server.close();
149
+ }
150
+ }
151
+ // ─── 辅助函数 ─────────────────────────────────────────
152
+ /**
153
+ * 在端口范围内寻找可用端口并监听
154
+ */
155
+ function findAvailablePort(server) {
156
+ return new Promise((resolve, reject) => {
157
+ let port = QR_WEB_PORT_START;
158
+ const tryListen = () => {
159
+ if (port > QR_WEB_PORT_END) {
160
+ reject(new Error(`端口 ${QR_WEB_PORT_START}-${QR_WEB_PORT_END} 均不可用`));
161
+ return;
162
+ }
163
+ server.once('error', () => {
164
+ port++;
165
+ tryListen();
166
+ });
167
+ server.listen(port, '127.0.0.1', () => {
168
+ // 移除之前绑定的 error 监听器
169
+ server.removeAllListeners('error');
170
+ resolve(port);
171
+ });
172
+ };
173
+ tryListen();
174
+ });
175
+ }
176
+ /**
177
+ * 跨平台打开浏览器
178
+ */
179
+ function openBrowser(url) {
180
+ switch (process.platform) {
181
+ case 'win32':
182
+ exec(`start ${url}`);
183
+ break;
184
+ case 'darwin':
185
+ exec(`open ${url}`);
186
+ break;
187
+ default:
188
+ exec(`xdg-open ${url}`);
189
+ break;
190
+ }
191
+ }
192
+ /**
193
+ * 构建登录 HTML 页面
194
+ */
195
+ function buildLoginPage(qrUrl) {
196
+ const qrImgUrl = `https://api.qrserver.com/v1/create-qr-code/?size=280x280&data=${encodeURIComponent(qrUrl)}`;
197
+ return `<!DOCTYPE html>
198
+ <html lang="zh-CN">
199
+ <head>
200
+ <meta charset="utf-8">
201
+ <meta name="viewport" content="width=device-width, initial-scale=1">
202
+ <title>WeChat × Claude Code</title>
203
+ <style>
204
+ * { margin: 0; padding: 0; box-sizing: border-box; }
205
+ body {
206
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
207
+ background: #1a1a2e;
208
+ color: #e0e0e0;
209
+ display: flex;
210
+ justify-content: center;
211
+ align-items: center;
212
+ min-height: 100vh;
213
+ }
214
+ .container {
215
+ text-align: center;
216
+ padding: 2rem;
217
+ }
218
+ h1 {
219
+ font-size: 1.8rem;
220
+ margin-bottom: 0.5rem;
221
+ color: #fff;
222
+ }
223
+ .subtitle {
224
+ font-size: 1rem;
225
+ color: #888;
226
+ margin-bottom: 2rem;
227
+ }
228
+ .qr-wrapper {
229
+ background: #fff;
230
+ border-radius: 16px;
231
+ padding: 20px;
232
+ display: inline-block;
233
+ margin-bottom: 1.5rem;
234
+ }
235
+ .qr-wrapper img {
236
+ width: 280px;
237
+ height: 280px;
238
+ display: block;
239
+ }
240
+ .status {
241
+ font-size: 1.1rem;
242
+ min-height: 2rem;
243
+ transition: color 0.3s;
244
+ }
245
+ .status.scaned { color: #f0a030; }
246
+ .status.success { color: #4caf50; }
247
+ .status.expired { color: #f44336; }
248
+ .checkmark {
249
+ font-size: 3rem;
250
+ color: #4caf50;
251
+ display: none;
252
+ margin-bottom: 1rem;
253
+ }
254
+ .cmd-hint {
255
+ display: none;
256
+ margin-top: 1rem;
257
+ background: #16213e;
258
+ border: 1px solid #333;
259
+ border-radius: 8px;
260
+ padding: 1rem;
261
+ font-family: monospace;
262
+ font-size: 0.9rem;
263
+ color: #7ec8e3;
264
+ cursor: pointer;
265
+ position: relative;
266
+ }
267
+ .cmd-hint:hover { background: #1a2744; }
268
+ .copied {
269
+ position: absolute;
270
+ top: -1.5rem;
271
+ right: 0.5rem;
272
+ font-size: 0.75rem;
273
+ color: #4caf50;
274
+ opacity: 0;
275
+ transition: opacity 0.3s;
276
+ }
277
+ .copied.show { opacity: 1; }
278
+ </style>
279
+ </head>
280
+ <body>
281
+ <div class="container">
282
+ <h1>WeChat &times; Claude Code</h1>
283
+ <p class="subtitle">使用微信扫码连接</p>
284
+ <div class="checkmark" id="checkmark">&#10003;</div>
285
+ <div class="qr-wrapper" id="qr-wrapper">
286
+ <img id="qr-img" src="${qrImgUrl}" alt="QR Code">
287
+ </div>
288
+ <div class="status" id="status">等待扫码...</div>
289
+ <div class="cmd-hint" id="cmd-hint" onclick="copyCmd()">
290
+ npx cc-wechat
291
+ <span class="copied" id="copied">已复制</span>
292
+ </div>
293
+ </div>
294
+ <script>
295
+ const statusEl = document.getElementById('status');
296
+ const qrImg = document.getElementById('qr-img');
297
+ const checkmark = document.getElementById('checkmark');
298
+ const qrWrapper = document.getElementById('qr-wrapper');
299
+ const cmdHint = document.getElementById('cmd-hint');
300
+
301
+ setInterval(async () => {
302
+ try {
303
+ const res = await fetch('/status');
304
+ const data = await res.json();
305
+ statusEl.className = 'status ' + data.status;
306
+
307
+ switch (data.status) {
308
+ case 'wait':
309
+ statusEl.textContent = '等待扫码...';
310
+ break;
311
+ case 'scaned':
312
+ statusEl.textContent = '已扫码,请在手机上确认...';
313
+ break;
314
+ case 'expired':
315
+ statusEl.textContent = '二维码已过期,正在刷新...';
316
+ refreshQR();
317
+ break;
318
+ case 'success':
319
+ statusEl.textContent = '登录成功!';
320
+ checkmark.style.display = 'block';
321
+ qrWrapper.style.display = 'none';
322
+ cmdHint.style.display = 'block';
323
+ copyCmd();
324
+ break;
325
+ default:
326
+ if (data.message) statusEl.textContent = data.message;
327
+ }
328
+ } catch {}
329
+ }, 2000);
330
+
331
+ async function refreshQR() {
332
+ try {
333
+ const res = await fetch('/qr-refresh');
334
+ const data = await res.json();
335
+ const newUrl = 'https://api.qrserver.com/v1/create-qr-code/?size=280x280&data=' + encodeURIComponent(data.url);
336
+ qrImg.src = newUrl;
337
+ } catch {}
338
+ }
339
+
340
+ function copyCmd() {
341
+ navigator.clipboard.writeText('npx cc-wechat').then(() => {
342
+ const copied = document.getElementById('copied');
343
+ copied.classList.add('show');
344
+ setTimeout(() => copied.classList.remove('show'), 1500);
345
+ }).catch(() => {});
346
+ }
347
+ </script>
348
+ </body>
349
+ </html>`;
350
+ }
351
+ //# sourceMappingURL=auth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"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"}
package/dist/cdn.d.ts ADDED
@@ -0,0 +1,39 @@
1
+ /** AES-128-ECB 加密(PKCS7 padding 由 Node.js 自动处理) */
2
+ export declare function encryptAesEcb(plaintext: Buffer, key: Buffer): Buffer;
3
+ /** AES-128-ECB 解密 */
4
+ export declare function decryptAesEcb(ciphertext: Buffer, key: Buffer): Buffer;
5
+ /** 计算 AES-ECB PKCS7 padding 后的密文大小 */
6
+ export declare function aesEcbPaddedSize(plaintextSize: number): number;
7
+ /**
8
+ * 上传媒体文件到微信 CDN 并发送消息
9
+ * @param params.token - Bot 认证 token
10
+ * @param params.toUser - 目标用户 ID
11
+ * @param params.contextToken - 会话上下文 token
12
+ * @param params.filePath - 本地文件路径
13
+ * @param params.baseUrl - iLink API 基址(可选)
14
+ * @param params.cdnBaseUrl - CDN 基址(可选)
15
+ */
16
+ export declare function uploadMedia(params: {
17
+ token: string;
18
+ toUser: string;
19
+ contextToken: string;
20
+ filePath: string;
21
+ baseUrl?: string;
22
+ cdnBaseUrl?: string;
23
+ }): Promise<void>;
24
+ /**
25
+ * 从微信 CDN 下载并解密媒体文件
26
+ * @param params.encryptQueryParam - CDN 加密查询参数
27
+ * @param params.aesKeyBase64 - Base64 编码的 AES key
28
+ * @param params.cdnBaseUrl - CDN 基址(可选)
29
+ * @param params.outDir - 输出目录(可选,默认临时目录)
30
+ * @param params.fileName - 输出文件名(可选)
31
+ * @returns 文件绝对路径
32
+ */
33
+ export declare function downloadMedia(params: {
34
+ encryptQueryParam: string;
35
+ aesKeyBase64: string;
36
+ cdnBaseUrl?: string;
37
+ outDir?: string;
38
+ fileName?: string;
39
+ }): Promise<string>;