@aster110/cc2wechat 1.0.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.
@@ -0,0 +1,6 @@
1
+ {
2
+ "id": "wechat-channel",
3
+ "name": "WeChat Channel",
4
+ "version": "1.0.0",
5
+ "description": "WeChat channel for Claude Code via iLink Bot API"
6
+ }
package/.mcp.json ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "wechat-channel": {
3
+ "command": "node",
4
+ "args": ["dist/server.js"]
5
+ }
6
+ }
package/EXPERIENCE.md ADDED
@@ -0,0 +1,148 @@
1
+ # wechat-cc-channel 踩坑经验
2
+
3
+ > 2026-03-22 首次开发 + 联调
4
+
5
+ ## 一、微信 iLink Bot API 踩坑
6
+
7
+ ### 1. API 发现过程
8
+
9
+ 微信官方给 OpenClaw 做了 `@tencent-weixin/openclaw-weixin` 插件,源码里暴露了完整的 iLink Bot API:
10
+
11
+ - **Base URL**: `https://ilinkai.weixin.qq.com`
12
+ - **CDN URL**: `https://novac2c.cdn.weixin.qq.com/c2c`
13
+ - **协议**: HTTP JSON,长轮询
14
+
15
+ 这套 API 跟 OpenClaw 无关,是微信自己的 Bot 平台,可以独立使用。
16
+
17
+ ### 2. QR 登录流程
18
+
19
+ ```
20
+ GET /ilink/bot/get_bot_qrcode?bot_type=3
21
+ → 返回 { qrcode: "xxx", qrcode_img_content: "https://..." }
22
+
23
+ GET /ilink/bot/get_qrcode_status?qrcode=xxx
24
+ → 轮询,返回 { status: "wait"|"scaned"|"confirmed"|"expired" }
25
+ → confirmed 时返回 bot_token + ilink_bot_id
26
+ ```
27
+
28
+ **坑 1**: `bot_type=3` 是关键参数,不传或传错值会失败。这个值是从 openclaw 插件源码里找到的(`DEFAULT_ILINK_BOT_TYPE = "3"`)。
29
+
30
+ **坑 2**: 二维码有效期很短(约 2 分钟),需要自动刷新。openclaw 最多刷新 3 次。
31
+
32
+ **坑 3**: `qrcode_img_content` 返回的是一个图片 URL,不是 base64。终端渲染用 qrcode-terminal 对这个 URL 生成二维码。
33
+
34
+ ### 3. 消息收发
35
+
36
+ **getUpdates 长轮询**:
37
+ - `POST /ilink/bot/getupdates`
38
+ - `get_updates_buf` 是同步游标,首次传空字符串
39
+ - 服务端建议的 `longpolling_timeout_ms` 通常是 35000ms
40
+ - 客户端超时不算错误,重试即可
41
+
42
+ **坑 4**: 必须带 `base_info: { channel_version: "1.0.0" }`,否则可能被拒绝。
43
+
44
+ **坑 5**: Headers 很讲究:
45
+ ```
46
+ Content-Type: application/json
47
+ AuthorizationType: ilink_bot_token // 固定值,不是 Bearer
48
+ Authorization: Bearer <token>
49
+ X-WECHAT-UIN: <random base64 uint32> // 每次请求随机生成
50
+ ```
51
+
52
+ **坑 6**: `context_token` 极其重要!每条消息带一个 context_token,回复时必须回传。不传就报错。
53
+
54
+ ### 4. sendMessage
55
+
56
+ - `POST /ilink/bot/sendmessage`
57
+ - Body: `{ msg: { to_user_id, context_token, item_list: [{ type: 1, text_item: { text } }] } }`
58
+ - 微信有 4000 字符限制,需要分片
59
+
60
+ ### 5. Session 过期
61
+
62
+ - `errcode = -14` 表示会话过期
63
+ - 暂停 5 分钟后重试(openclaw 的策略)
64
+ - 不需要重新扫码,用保存的 token 重新 getUpdates 即可
65
+
66
+ ## 二、MCP Server 踩坑
67
+
68
+ ### 6. stderr 不显示给用户
69
+
70
+ MCP server 是 CC 的子进程,`process.stderr` 输出被 CC 内部捕获为日志,**用户终端看不到**。
71
+
72
+ 所以 login 二维码不能输出到 stderr,必须通过 tool 返回值或开网页展示。
73
+
74
+ **解决方案**: 启动临时 HTTP server(localhost:18891),弹浏览器显示二维码页面。
75
+
76
+ ### 7. MCP tool 超时
77
+
78
+ login tool 需要等用户扫码(最长 8 分钟),这会阻塞 tool call。CC 会显示 "running...",用户可能以为卡了。
79
+
80
+ **解决方案**: 页面侧定时轮询 `/status`,给用户实时反馈。
81
+
82
+ ### 8. Channel capability
83
+
84
+ 声明 `capabilities.experimental['claude/channel']: {}` 让 CC 识别为 channel。但这是 research preview 功能,可能需要特殊启动参数。
85
+
86
+ 实测:用户级 MCP 配置(`claude mcp add -s user`)也能加载 tools,但 channel notification push 是否被识别需要进一步测试。
87
+
88
+ ## 三、测试结果
89
+
90
+ | 功能 | 状态 | 备注 |
91
+ |------|------|------|
92
+ | QR 扫码登录 | ✅ 通过 | bot_type=3,终端二维码可扫 |
93
+ | 凭证持久化 | ✅ 通过 | ~/.claude/channels/wechat-channel/accounts.json |
94
+ | 收文字消息 | ✅ 通过 | getUpdates 长轮询正常 |
95
+ | 发文字消息 | ✅ 通过 | 中英文都 OK |
96
+ | 发长消息 | ✅ 通过 | 510 字正常,分片逻辑备用 |
97
+ | getConfig | ✅ 通过 | typing_ticket 获取正常 |
98
+ | sendTyping | ✅ 通过 | 输入状态指示正常 |
99
+ | 收图片消息 | ⏳ 待测 | item.type=2,需要 CDN 解密 |
100
+ | 收语音消息 | ⏳ 待测 | item.type=3,voice_item.text 有 ASR 结果 |
101
+ | 发图片消息 | ✅ 通过 | CDN 上传 + AES-128-ECB 加密,完整流程验证 |
102
+ | 网页二维码 | ✅ HTTP server 正常 | localhost:18891 |
103
+ | MCP tool 调用 | ⚠️ 部分 | login tool 阻塞问题已修(改为网页) |
104
+ | Channel push | ⏳ 待测 | notifications/claude/channel 需要开新 CC 测 |
105
+
106
+ ## 四、架构决策
107
+
108
+ | 决策 | 选择 | 理由 |
109
+ |------|------|------|
110
+ | 接入方式 | CC Channel (MCP) | 原生集成,不需要额外网关 |
111
+ | 微信协议 | iLink Bot API | 微信官方,OpenClaw 已验证 |
112
+ | 登录方式 | 网页二维码 | 终端二维码 MCP 下不可见 |
113
+ | 凭证存储 | ~/.claude/channels/ | 跟 CC 生态一致 |
114
+ | 不用 OpenClaw | 独立实现 | 减少依赖,代码量小(~1000行) |
115
+
116
+ ## 五、图片发送踩坑
117
+
118
+ ### 坑 7:直接传 image URL 不行
119
+
120
+ sendMessage 的 `image_item.url` 字段虽然存在,但直接传远程 URL 微信显示"无法加载图片"。**必须走 CDN 上传流程**。
121
+
122
+ ### 坑 8:图片发送完整流程
123
+
124
+ ```
125
+ 1. 读取本地文件 → 算 rawsize + MD5
126
+ 2. 生成随机 AES-128 key (16 bytes)
127
+ 3. 算 ciphertext size: Math.ceil((rawsize + 1) / 16) * 16
128
+ 4. POST /ilink/bot/getuploadurl → 拿到 upload_param
129
+ 5. AES-128-ECB 加密文件内容(PKCS7 padding)
130
+ 6. POST CDN upload URL (application/octet-stream) → 拿到 x-encrypted-param header
131
+ 7. POST /ilink/bot/sendmessage → image_item.media.encrypt_query_param = downloadParam
132
+ ```
133
+
134
+ 关键参数:
135
+ - `media_type: 1` = IMAGE
136
+ - `no_need_thumb: true`(不传缩略图简化流程)
137
+ - `aes_key` 在 getUploadUrl 和 image_item 里格式不同:
138
+ - getUploadUrl: hex string
139
+ - image_item.media.aes_key: base64(hex string)
140
+ - CDN Base URL: `https://novac2c.cdn.weixin.qq.com/c2c`
141
+
142
+ ## 六、待解决
143
+
144
+ 1. **CDN 媒体上传/下载**:图片/文件需要 AES-128-ECB 加密,openclaw 插件有完整实现可参考
145
+ 2. **Channel notification 实测**:需要用 `--dangerously-load-development-channels` 启动 CC 测试
146
+ 3. **多账号支持**:当前只支持一个账号
147
+ 4. **重连机制**:token 失效后是否需要重新扫码?
148
+ 5. **npm 包发布**:package name、README、license
package/README.md ADDED
@@ -0,0 +1,66 @@
1
+ # wechat-cc-channel
2
+
3
+ WeChat channel plugin for Claude Code. Bridges WeChat messages (via iLink Bot API) into your local Claude Code session.
4
+
5
+ ## Architecture
6
+
7
+ ```
8
+ WeChat user sends message
9
+ |
10
+ v
11
+ iLink Bot API (ilinkai.weixin.qq.com)
12
+ | long-poll /ilink/bot/getupdates
13
+ v
14
+ wechat-cc-channel (MCP server, stdio)
15
+ | server.notification('notifications/claude/channel')
16
+ v
17
+ Claude Code session processes the message
18
+ | Claude calls the reply tool
19
+ v
20
+ MCP server -> /ilink/bot/sendmessage -> WeChat user
21
+ ```
22
+
23
+ ## Quick Start
24
+
25
+ ```bash
26
+ # Install dependencies
27
+ npm install
28
+
29
+ # Build
30
+ npm run build
31
+
32
+ # Run with Claude Code (development mode)
33
+ claude --dangerously-load-development-channels server:wechat-channel
34
+ ```
35
+
36
+ ## First-time Setup
37
+
38
+ 1. Start Claude Code with the channel loaded
39
+ 2. Claude will see "No saved account" — ask it to run the `login` tool
40
+ 3. Scan the QR code with WeChat
41
+ 4. Done! Messages will flow in automatically
42
+
43
+ ## Tools
44
+
45
+ | Tool | Description |
46
+ |------|-------------|
47
+ | `login` | Scan QR code to connect WeChat account |
48
+ | `reply` | Reply to a WeChat message (auto-strips markdown, auto-chunks long text) |
49
+
50
+ ## Storage
51
+
52
+ Credentials stored in `~/.claude/channels/wechat-channel/`:
53
+ - `accounts.json` — saved account tokens
54
+ - `sync-buf-{accountId}.txt` — long-poll cursor (resume position)
55
+
56
+ ## Key Details
57
+
58
+ - Messages longer than 3900 chars are automatically chunked
59
+ - Markdown is stripped to plain text for WeChat
60
+ - Typing indicators are sent while processing
61
+ - Session expiry (errcode -14) triggers automatic pause and retry
62
+ - All logs go to stderr (won't interfere with MCP stdio)
63
+
64
+ ## License
65
+
66
+ MIT
package/dist/auth.d.ts ADDED
@@ -0,0 +1,15 @@
1
+ export interface LoginResult {
2
+ token: string;
3
+ accountId: string;
4
+ baseUrl?: string;
5
+ }
6
+ /**
7
+ * Full QR login flow: fetch QR code, display in terminal, poll until confirmed.
8
+ * Writes to stderr so it doesn't interfere with stdio MCP transport.
9
+ */
10
+ export declare function loginWithQR(baseUrl?: string): Promise<LoginResult>;
11
+ /**
12
+ * Web-based QR login: opens a local browser page with the QR code.
13
+ * Solves the MCP stderr capture issue where terminal QR codes are invisible.
14
+ */
15
+ export declare function loginWithQRWeb(baseUrl?: string): Promise<LoginResult>;
package/dist/auth.js ADDED
@@ -0,0 +1,230 @@
1
+ import http from 'node:http';
2
+ import { exec } from 'node:child_process';
3
+ import { getQRCode, pollQRStatus } from './wechat-api.js';
4
+ const MAX_QR_REFRESH = 3;
5
+ const LOGIN_TIMEOUT_MS = 5 * 60_000;
6
+ const QR_WEB_PORT = 18891;
7
+ /**
8
+ * Full QR login flow: fetch QR code, display in terminal, poll until confirmed.
9
+ * Writes to stderr so it doesn't interfere with stdio MCP transport.
10
+ */
11
+ export async function loginWithQR(baseUrl) {
12
+ let qrRefreshCount = 0;
13
+ while (qrRefreshCount < MAX_QR_REFRESH) {
14
+ qrRefreshCount++;
15
+ const qrResp = await getQRCode(baseUrl);
16
+ // Display QR code in terminal via stderr
17
+ process.stderr.write('\n--- Scan this QR code with WeChat ---\n');
18
+ try {
19
+ const qrterm = await import('qrcode-terminal');
20
+ // qrcode-terminal writes to stdout by default; we redirect
21
+ qrterm.default.generate(qrResp.qrcode_img_content, { small: true }, (qr) => {
22
+ process.stderr.write(qr + '\n');
23
+ });
24
+ }
25
+ catch {
26
+ process.stderr.write(`QR URL: ${qrResp.qrcode_img_content}\n`);
27
+ }
28
+ process.stderr.write('Waiting for scan...\n');
29
+ const deadline = Date.now() + LOGIN_TIMEOUT_MS;
30
+ let scannedLogged = false;
31
+ while (Date.now() < deadline) {
32
+ const status = await pollQRStatus(qrResp.qrcode, baseUrl);
33
+ switch (status.status) {
34
+ case 'wait':
35
+ break;
36
+ case 'scaned':
37
+ if (!scannedLogged) {
38
+ process.stderr.write('Scanned! Confirm on your phone...\n');
39
+ scannedLogged = true;
40
+ }
41
+ break;
42
+ case 'expired':
43
+ process.stderr.write(`QR expired (${qrRefreshCount}/${MAX_QR_REFRESH}), refreshing...\n`);
44
+ break;
45
+ case 'confirmed':
46
+ if (!status.ilink_bot_id || !status.bot_token) {
47
+ throw new Error('Login confirmed but missing bot_token or ilink_bot_id');
48
+ }
49
+ process.stderr.write('Login successful!\n');
50
+ return {
51
+ token: status.bot_token,
52
+ accountId: status.ilink_bot_id,
53
+ baseUrl: status.baseurl,
54
+ };
55
+ }
56
+ if (status.status === 'expired')
57
+ break; // break inner loop to refresh QR
58
+ await new Promise((r) => setTimeout(r, 1000));
59
+ }
60
+ }
61
+ throw new Error('Login failed: QR code expired too many times');
62
+ }
63
+ // ---------------------------------------------------------------------------
64
+ // Web-based QR login (opens browser, avoids stderr QR display issue in MCP)
65
+ // ---------------------------------------------------------------------------
66
+ function buildQRPage(qrUrl) {
67
+ return `<!DOCTYPE html>
68
+ <html>
69
+ <head>
70
+ <meta charset="utf-8">
71
+ <title>WeChat Login - Claude Code</title>
72
+ <style>
73
+ * { margin: 0; padding: 0; box-sizing: border-box; }
74
+ body { background: #1a1a2e; color: #e0e0e0; display: flex; align-items: center; justify-content: center; height: 100vh; font-family: system-ui, -apple-system, sans-serif; }
75
+ .container { text-align: center; }
76
+ h2 { font-size: 1.6rem; margin-bottom: 8px; }
77
+ .subtitle { color: #888; margin-bottom: 24px; }
78
+ #qr-container { margin: 0 auto 20px; }
79
+ #qr-img { width: 300px; height: 300px; border-radius: 12px; background: white; padding: 16px; }
80
+ #status { color: #888; font-size: 1.1rem; transition: color 0.3s; }
81
+ .success-box { background: #1e3a1e; border: 1px solid #5cb85c; border-radius: 12px; padding: 32px; margin-top: 16px; }
82
+ </style>
83
+ </head>
84
+ <body>
85
+ <div class="container">
86
+ <h2>WeChat × Claude Code</h2>
87
+ <p class="subtitle">Scan with WeChat to connect</p>
88
+ <div id="qr-container">
89
+ <img id="qr-img" src="${qrUrl}" onerror="this.alt='QR failed to load: ${qrUrl}'" />
90
+ </div>
91
+ <p id="status">Waiting for scan...</p>
92
+ </div>
93
+ <script>
94
+ const poll = setInterval(async () => {
95
+ try {
96
+ const res = await fetch('/status');
97
+ const data = await res.json();
98
+ const el = document.getElementById('status');
99
+ if (data.status === 'scanned') {
100
+ el.textContent = 'Scanned! Confirm on your phone...';
101
+ el.style.color = '#f0ad4e';
102
+ } else if (data.status === 'success') {
103
+ el.textContent = 'Connected!';
104
+ el.style.color = '#5cb85c';
105
+ document.getElementById('qr-container').style.display = 'none';
106
+ clearInterval(poll);
107
+ setTimeout(() => window.close(), 3000);
108
+ } else if (data.status === 'expired') {
109
+ el.textContent = 'QR expired, refreshing...';
110
+ el.style.color = '#d9534f';
111
+ try {
112
+ const r2 = await fetch('/qr-refresh');
113
+ const d2 = await r2.json();
114
+ if (d2.url) {
115
+ document.getElementById('qr-img').src = d2.url;
116
+ el.textContent = 'QR refreshed, scan again...';
117
+ el.style.color = '#888';
118
+ }
119
+ } catch {}
120
+ } else if (data.status === 'failed') {
121
+ el.textContent = 'Login failed: ' + (data.message || 'unknown error');
122
+ el.style.color = '#d9534f';
123
+ clearInterval(poll);
124
+ }
125
+ } catch {}
126
+ }, 2000);
127
+ </script>
128
+ </body>
129
+ </html>`;
130
+ }
131
+ function openBrowser(url) {
132
+ const cmd = process.platform === 'darwin' ? 'open' : 'xdg-open';
133
+ exec(`${cmd} ${url}`, () => { });
134
+ }
135
+ /**
136
+ * Web-based QR login: opens a local browser page with the QR code.
137
+ * Solves the MCP stderr capture issue where terminal QR codes are invisible.
138
+ */
139
+ export async function loginWithQRWeb(baseUrl) {
140
+ let currentStatus = 'waiting';
141
+ let currentQrUrl = '';
142
+ let failMessage = '';
143
+ let qrRefreshCount = 0;
144
+ let currentQrCode = ''; // the qrcode token for polling
145
+ // Fetch initial QR code
146
+ const qrResp = await getQRCode(baseUrl);
147
+ currentQrUrl = qrResp.qrcode_img_content;
148
+ currentQrCode = qrResp.qrcode;
149
+ qrRefreshCount = 1;
150
+ // Start HTTP server
151
+ const httpServer = http.createServer((req, res) => {
152
+ if (req.url === '/' || req.url === '') {
153
+ res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
154
+ res.end(buildQRPage(currentQrUrl));
155
+ return;
156
+ }
157
+ if (req.url === '/status') {
158
+ res.writeHead(200, { 'Content-Type': 'application/json' });
159
+ res.end(JSON.stringify({ status: currentStatus, message: failMessage }));
160
+ return;
161
+ }
162
+ if (req.url === '/qr-refresh') {
163
+ // The refresh is triggered by the polling loop setting status to 'expired',
164
+ // but the actual new QR is fetched there too. Just return current URL.
165
+ res.writeHead(200, { 'Content-Type': 'application/json' });
166
+ res.end(JSON.stringify({ url: currentQrUrl }));
167
+ return;
168
+ }
169
+ res.writeHead(404);
170
+ res.end('Not found');
171
+ });
172
+ await new Promise((resolve, reject) => {
173
+ httpServer.on('error', reject);
174
+ httpServer.listen(QR_WEB_PORT, () => resolve());
175
+ });
176
+ process.stderr.write(`[wechat-channel] QR login page at http://localhost:${QR_WEB_PORT}\n`);
177
+ openBrowser(`http://localhost:${QR_WEB_PORT}`);
178
+ // Poll for QR scan
179
+ try {
180
+ while (qrRefreshCount <= MAX_QR_REFRESH) {
181
+ const deadline = Date.now() + LOGIN_TIMEOUT_MS;
182
+ while (Date.now() < deadline) {
183
+ const status = await pollQRStatus(currentQrCode, baseUrl);
184
+ switch (status.status) {
185
+ case 'wait':
186
+ currentStatus = 'waiting';
187
+ break;
188
+ case 'scaned':
189
+ currentStatus = 'scanned';
190
+ break;
191
+ case 'expired':
192
+ currentStatus = 'expired';
193
+ break;
194
+ case 'confirmed':
195
+ if (!status.ilink_bot_id || !status.bot_token) {
196
+ throw new Error('Login confirmed but missing bot_token or ilink_bot_id');
197
+ }
198
+ currentStatus = 'success';
199
+ // Give the browser a moment to show success
200
+ await new Promise((r) => setTimeout(r, 500));
201
+ return {
202
+ token: status.bot_token,
203
+ accountId: status.ilink_bot_id,
204
+ baseUrl: status.baseurl,
205
+ };
206
+ }
207
+ if (status.status === 'expired')
208
+ break;
209
+ await new Promise((r) => setTimeout(r, 1000));
210
+ }
211
+ // Refresh QR
212
+ qrRefreshCount++;
213
+ if (qrRefreshCount <= MAX_QR_REFRESH) {
214
+ process.stderr.write(`[wechat-channel] QR expired (${qrRefreshCount - 1}/${MAX_QR_REFRESH}), refreshing...\n`);
215
+ const newQr = await getQRCode(baseUrl);
216
+ currentQrUrl = newQr.qrcode_img_content;
217
+ currentQrCode = newQr.qrcode;
218
+ currentStatus = 'waiting';
219
+ }
220
+ }
221
+ currentStatus = 'failed';
222
+ failMessage = 'QR code expired too many times';
223
+ throw new Error('Login failed: QR code expired too many times');
224
+ }
225
+ finally {
226
+ // Shut down HTTP server
227
+ httpServer.close();
228
+ }
229
+ }
230
+ //# 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/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,123 @@
1
+ #!/usr/bin/env node
2
+ import { execSync } from 'node:child_process';
3
+ import path from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+ import { loginWithQRWeb } from './auth.js';
6
+ import { saveAccount, getActiveAccount } from './store.js';
7
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
8
+ const serverPath = path.join(__dirname, 'server.js');
9
+ const command = process.argv[2];
10
+ function printUsage() {
11
+ console.log(`
12
+ 🦞 wechat-claude — WeChat channel for Claude Code
13
+
14
+ Usage:
15
+ npx @aster110/wechat-claude install Setup: register MCP + scan QR login
16
+ npx @aster110/wechat-claude login Re-login (scan QR code)
17
+ npx @aster110/wechat-claude status Check connection status
18
+ npx @aster110/wechat-claude help Show this help
19
+ `);
20
+ }
21
+ async function install() {
22
+ console.log('\n 🦞 wechat-claude installer\n');
23
+ // Step 1: Register MCP server
24
+ console.log(' [1/3] Registering MCP server...');
25
+ try {
26
+ execSync(`claude mcp add -s user wechat-channel node ${serverPath}`, { stdio: 'pipe' });
27
+ console.log(' ✅ MCP server registered (user-level)\n');
28
+ }
29
+ catch {
30
+ // May already exist, try remove + add
31
+ try {
32
+ execSync(`claude mcp remove -s user wechat-channel`, { stdio: 'pipe' });
33
+ execSync(`claude mcp add -s user wechat-channel node ${serverPath}`, { stdio: 'pipe' });
34
+ console.log(' ✅ MCP server updated (user-level)\n');
35
+ }
36
+ catch (err) {
37
+ console.error(' ⚠️ Failed to register MCP server. You may need to add it manually:');
38
+ console.log(` claude mcp add -s user wechat-channel node ${serverPath}\n`);
39
+ }
40
+ }
41
+ // Step 2: QR Login
42
+ console.log(' [2/3] WeChat QR login...');
43
+ const existing = getActiveAccount();
44
+ if (existing) {
45
+ console.log(` ℹ️ Found existing account: ${existing.accountId}`);
46
+ console.log(' Skipping login. Run "npx @aster110/wechat-claude login" to re-login.\n');
47
+ }
48
+ else {
49
+ try {
50
+ const result = await loginWithQRWeb();
51
+ saveAccount({
52
+ accountId: result.accountId.replace(/@/g, '-').replace(/\./g, '-'),
53
+ token: result.token,
54
+ baseUrl: result.baseUrl,
55
+ savedAt: new Date().toISOString(),
56
+ });
57
+ console.log(` ✅ Login successful! Account: ${result.accountId}\n`);
58
+ }
59
+ catch (err) {
60
+ console.error(` ❌ Login failed: ${err}`);
61
+ console.log(' Run "npx @aster110/wechat-claude login" to retry.\n');
62
+ }
63
+ }
64
+ // Step 3: Print next steps
65
+ console.log(' [3/3] Setup complete!\n');
66
+ console.log(' Next steps:');
67
+ console.log(' 1. Start Claude Code with WeChat channel:');
68
+ console.log(' claude --dangerously-load-development-channels server:wechat-channel\n');
69
+ console.log(' 2. Send a message to your WeChat — Claude Code will auto-reply!\n');
70
+ }
71
+ async function login() {
72
+ console.log('\n 🦞 WeChat QR Login\n');
73
+ try {
74
+ const result = await loginWithQRWeb();
75
+ saveAccount({
76
+ accountId: result.accountId.replace(/@/g, '-').replace(/\./g, '-'),
77
+ token: result.token,
78
+ baseUrl: result.baseUrl,
79
+ savedAt: new Date().toISOString(),
80
+ });
81
+ console.log(`\n ✅ Login successful! Account: ${result.accountId}\n`);
82
+ }
83
+ catch (err) {
84
+ console.error(`\n ❌ Login failed: ${err}\n`);
85
+ process.exit(1);
86
+ }
87
+ }
88
+ function status() {
89
+ const account = getActiveAccount();
90
+ if (account) {
91
+ console.log(`\n 🦞 WeChat Channel Status\n`);
92
+ console.log(` Account: ${account.accountId}`);
93
+ console.log(` Token: ${account.token.slice(0, 10)}...`);
94
+ console.log(` Base URL: ${account.baseUrl || 'https://ilinkai.weixin.qq.com'}`);
95
+ console.log(` Saved: ${account.savedAt}\n`);
96
+ }
97
+ else {
98
+ console.log('\n ⚠️ Not logged in. Run: npx @aster110/wechat-claude install\n');
99
+ }
100
+ }
101
+ switch (command) {
102
+ case 'install':
103
+ case 'setup':
104
+ install().catch(console.error);
105
+ break;
106
+ case 'login':
107
+ login().catch(console.error);
108
+ break;
109
+ case 'status':
110
+ status();
111
+ break;
112
+ case 'help':
113
+ case '--help':
114
+ case '-h':
115
+ case undefined:
116
+ printUsage();
117
+ break;
118
+ default:
119
+ console.error(` Unknown command: ${command}`);
120
+ printUsage();
121
+ process.exit(1);
122
+ }
123
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};