@aster110/cc2wechat 1.0.3 → 2.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.
package/dist/cli.js CHANGED
@@ -12,10 +12,11 @@ function printUsage() {
12
12
  🦞 wechat-claude — WeChat channel for Claude Code
13
13
 
14
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
15
+ npx cc2wechat start v2 Pipe Mode: listen & auto-reply via claude -p
16
+ npx cc2wechat install Setup: register MCP + scan QR login (v1 MCP mode)
17
+ npx cc2wechat login Re-login (scan QR code)
18
+ npx cc2wechat status Check connection status
19
+ npx cc2wechat help Show this help
19
20
  `);
20
21
  }
21
22
  async function install() {
@@ -99,6 +100,9 @@ function status() {
99
100
  }
100
101
  }
101
102
  switch (command) {
103
+ case 'start':
104
+ import('./daemon.js');
105
+ break;
102
106
  case 'install':
103
107
  case 'setup':
104
108
  install().catch(console.error);
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
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 {};
package/dist/daemon.js ADDED
@@ -0,0 +1,295 @@
1
+ #!/usr/bin/env node
2
+ import { execSync } from 'node:child_process';
3
+ import { createHash } from 'node:crypto';
4
+ import fs from 'node:fs';
5
+ import path from 'node:path';
6
+ import { fileURLToPath } from 'node:url';
7
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
8
+ const replyCli = path.join(__dirname, 'reply-cli.js');
9
+ import { loginWithQRWeb } from './auth.js';
10
+ import { getActiveAccount, saveAccount, loadSyncBuf, saveSyncBuf } from './store.js';
11
+ import { getUpdates, sendMessage, sendTyping, getConfig, uploadAndSendMedia } from './wechat-api.js';
12
+ import { MessageItemType } from './types.js';
13
+ // ---------------------------------------------------------------------------
14
+ // Constants
15
+ // ---------------------------------------------------------------------------
16
+ const SESSION_EXPIRED_ERRCODE = -14;
17
+ const MAX_CONSECUTIVE_FAILURES = 3;
18
+ const BACKOFF_DELAY_MS = 30_000;
19
+ const RETRY_DELAY_MS = 2_000;
20
+ const SESSION_PAUSE_MS = 5 * 60_000;
21
+ const MAX_CHUNK_LENGTH = 3900;
22
+ // ---------------------------------------------------------------------------
23
+ // Helpers
24
+ // ---------------------------------------------------------------------------
25
+ function userIdToSessionUUID(userId) {
26
+ const hash = createHash('md5').update(`cc2wechat:${userId}`).digest('hex');
27
+ return `${hash.slice(0, 8)}-${hash.slice(8, 12)}-4${hash.slice(13, 16)}-${hash.slice(16, 20)}-${hash.slice(20, 32)}`;
28
+ }
29
+ function extractText(msg) {
30
+ const parts = [];
31
+ for (const item of msg.item_list ?? []) {
32
+ if (item.type === MessageItemType.TEXT && item.text_item?.text) {
33
+ parts.push(item.text_item.text);
34
+ }
35
+ else if (item.type === MessageItemType.IMAGE) {
36
+ parts.push('[Image]');
37
+ }
38
+ else if (item.type === MessageItemType.VOICE && item.voice_item?.text) {
39
+ parts.push(`[Voice] ${item.voice_item.text}`);
40
+ }
41
+ else if (item.type === MessageItemType.FILE && item.file_item?.file_name) {
42
+ parts.push(`[File: ${item.file_item.file_name}]`);
43
+ }
44
+ else if (item.type === MessageItemType.VIDEO) {
45
+ parts.push('[Video]');
46
+ }
47
+ }
48
+ return parts.join('\n') || '[Empty message]';
49
+ }
50
+ function stripMarkdown(text) {
51
+ let result = text;
52
+ // Code blocks: strip fences, keep content
53
+ result = result.replace(/```[^\n]*\n?([\s\S]*?)```/g, (_, code) => code.trim());
54
+ // Images: remove
55
+ result = result.replace(/!\[[^\]]*\]\([^)]*\)/g, '');
56
+ // Links: keep display text
57
+ result = result.replace(/\[([^\]]+)\]\([^)]*\)/g, '$1');
58
+ // Bold/italic
59
+ result = result.replace(/\*\*(.+?)\*\*/g, '$1');
60
+ result = result.replace(/\*(.+?)\*/g, '$1');
61
+ result = result.replace(/__(.+?)__/g, '$1');
62
+ result = result.replace(/_(.+?)_/g, '$1');
63
+ // Headings
64
+ result = result.replace(/^#{1,6}\s+/gm, '');
65
+ // Horizontal rules
66
+ result = result.replace(/^[-*_]{3,}$/gm, '');
67
+ // Blockquotes
68
+ result = result.replace(/^>\s?/gm, '');
69
+ return result.trim();
70
+ }
71
+ function chunkText(text) {
72
+ if (text.length <= MAX_CHUNK_LENGTH)
73
+ return [text];
74
+ const chunks = [];
75
+ let remaining = text;
76
+ while (remaining.length > 0) {
77
+ if (remaining.length <= MAX_CHUNK_LENGTH) {
78
+ chunks.push(remaining);
79
+ break;
80
+ }
81
+ // Try to break at newline
82
+ let breakAt = remaining.lastIndexOf('\n', MAX_CHUNK_LENGTH);
83
+ if (breakAt < MAX_CHUNK_LENGTH * 0.5) {
84
+ // No good newline break, try space
85
+ breakAt = remaining.lastIndexOf(' ', MAX_CHUNK_LENGTH);
86
+ }
87
+ if (breakAt < MAX_CHUNK_LENGTH * 0.3) {
88
+ breakAt = MAX_CHUNK_LENGTH;
89
+ }
90
+ chunks.push(remaining.slice(0, breakAt));
91
+ remaining = remaining.slice(breakAt).trimStart();
92
+ }
93
+ return chunks;
94
+ }
95
+ function sleep(ms) {
96
+ return new Promise((resolve) => setTimeout(resolve, ms));
97
+ }
98
+ // ---------------------------------------------------------------------------
99
+ // Core message handler
100
+ // ---------------------------------------------------------------------------
101
+ async function handleMessage(msg, account) {
102
+ const text = extractText(msg);
103
+ const userId = msg.from_user_id ?? '';
104
+ const contextToken = msg.context_token ?? '';
105
+ const sessionId = userIdToSessionUUID(userId);
106
+ console.log(`[cc2wechat] <- ${userId.slice(0, 10)}...: ${text.slice(0, 50)}`);
107
+ const systemPrompt = `You are responding to a WeChat message. Keep replies concise (under 500 chars when possible).
108
+
109
+ You have these WeChat commands available via Bash:
110
+ - Send image/file to user: node ${replyCli} --image /absolute/path/to/file
111
+ - Send text message mid-process: node ${replyCli} --text "processing..."
112
+
113
+ When user asks for screenshots, files, or images:
114
+ 1. Create/save the file (e.g. screencapture -x /tmp/screenshot.png)
115
+ 2. Send it: node ${replyCli} --image /tmp/screenshot.png
116
+ 3. Confirm in your response
117
+
118
+ Your final text response will also be sent to WeChat automatically.
119
+
120
+ IMPORTANT: You are running in non-interactive mode. Do NOT use Agent Teams (TeamCreate/TaskCreate). Handle all tasks yourself sequentially.`;
121
+ // Write context for cc2wechat-reply CLI
122
+ const contextPath = '/tmp/cc2wechat-context.json';
123
+ fs.writeFileSync(contextPath, JSON.stringify({
124
+ token: account.token,
125
+ baseUrl: account.baseUrl,
126
+ userId,
127
+ contextToken,
128
+ }));
129
+ // Send typing indicator
130
+ try {
131
+ const cfg = await getConfig(account.token, userId, contextToken, account.baseUrl);
132
+ if (cfg.typing_ticket) {
133
+ await sendTyping(account.token, userId, cfg.typing_ticket, 1, account.baseUrl).catch(() => { });
134
+ }
135
+ }
136
+ catch {
137
+ // non-critical
138
+ }
139
+ // Call claude -p (try resume first, fallback to new session)
140
+ let result;
141
+ const prompt = JSON.stringify(text);
142
+ try {
143
+ // Try resuming existing session
144
+ result = execSync(`claude -p ${prompt} --resume ${sessionId} --output-format text --permission-mode bypassPermissions --system-prompt ${JSON.stringify(systemPrompt)}`, { encoding: 'utf-8', timeout: 120_000, maxBuffer: 10 * 1024 * 1024, cwd: process.cwd() }).trim();
145
+ }
146
+ catch (err) {
147
+ const execErr = err;
148
+ const stderr = execErr.stderr ?? execErr.message ?? '';
149
+ // If session not found, create new one
150
+ if (stderr.includes('session') || stderr.includes('resume') || !execErr.stdout?.trim()) {
151
+ try {
152
+ result = execSync(`claude -p ${prompt} --session-id ${sessionId} --output-format text --permission-mode bypassPermissions --system-prompt ${JSON.stringify(systemPrompt)}`, { encoding: 'utf-8', timeout: 120_000, maxBuffer: 10 * 1024 * 1024, cwd: process.cwd() }).trim();
153
+ }
154
+ catch (err2) {
155
+ const execErr2 = err2;
156
+ result = execErr2.stdout?.trim() || `Error: ${execErr2.message ?? 'unknown'}`;
157
+ }
158
+ }
159
+ else {
160
+ result = execErr.stdout?.trim() || `Error: ${stderr}`;
161
+ }
162
+ }
163
+ console.log(`[cc2wechat] -> ${result.slice(0, 100)}...`);
164
+ // Strip markdown + chunk + send text
165
+ const plainText = stripMarkdown(result);
166
+ const chunks = chunkText(plainText);
167
+ for (const chunk of chunks) {
168
+ await sendMessage(account.token, userId, chunk, contextToken, account.baseUrl);
169
+ }
170
+ // Detect file paths in output, auto-send
171
+ const fileMatch = result.match(/\/(tmp|Users|home)[\w/._-]+\.(png|jpg|jpeg|gif|mp4|pdf|zip)/gi);
172
+ if (fileMatch) {
173
+ for (const filePath of fileMatch) {
174
+ if (fs.existsSync(filePath)) {
175
+ console.log(`[cc2wechat] Sending file: ${filePath}`);
176
+ try {
177
+ await uploadAndSendMedia({
178
+ token: account.token,
179
+ toUser: userId,
180
+ contextToken,
181
+ filePath,
182
+ baseUrl: account.baseUrl,
183
+ });
184
+ }
185
+ catch (err) {
186
+ const sendErr = err;
187
+ console.error(`[cc2wechat] Failed to send file: ${sendErr.message ?? 'unknown'}`);
188
+ }
189
+ }
190
+ }
191
+ }
192
+ // Cancel typing
193
+ try {
194
+ const cfg = await getConfig(account.token, userId, contextToken, account.baseUrl);
195
+ if (cfg.typing_ticket) {
196
+ await sendTyping(account.token, userId, cfg.typing_ticket, 2, account.baseUrl).catch(() => { });
197
+ }
198
+ }
199
+ catch {
200
+ // non-critical
201
+ }
202
+ }
203
+ // ---------------------------------------------------------------------------
204
+ // Long-polling loop
205
+ // ---------------------------------------------------------------------------
206
+ async function pollLoop(account) {
207
+ let buf = loadSyncBuf(account.accountId);
208
+ let consecutiveFailures = 0;
209
+ let nextTimeoutMs = 35_000;
210
+ console.log(`[cc2wechat] Polling started for account ${account.accountId}`);
211
+ // eslint-disable-next-line no-constant-condition
212
+ while (true) {
213
+ try {
214
+ const resp = await getUpdates(account.token, buf, account.baseUrl, nextTimeoutMs);
215
+ // Update timeout if server suggests one
216
+ if (resp.longpolling_timeout_ms != null && resp.longpolling_timeout_ms > 0) {
217
+ nextTimeoutMs = resp.longpolling_timeout_ms;
218
+ }
219
+ // Check for API errors
220
+ const isApiError = (resp.ret !== undefined && resp.ret !== 0) ||
221
+ (resp.errcode !== undefined && resp.errcode !== 0);
222
+ if (isApiError) {
223
+ const isSessionExpired = resp.errcode === SESSION_EXPIRED_ERRCODE || resp.ret === SESSION_EXPIRED_ERRCODE;
224
+ if (isSessionExpired) {
225
+ console.log(`[cc2wechat] Session expired (errcode ${SESSION_EXPIRED_ERRCODE}), pausing ${Math.ceil(SESSION_PAUSE_MS / 60_000)} min`);
226
+ consecutiveFailures = 0;
227
+ await sleep(SESSION_PAUSE_MS);
228
+ continue;
229
+ }
230
+ consecutiveFailures++;
231
+ console.error(`[cc2wechat] getUpdates error: ret=${resp.ret} errcode=${resp.errcode} errmsg=${resp.errmsg ?? ''} (${consecutiveFailures}/${MAX_CONSECUTIVE_FAILURES})`);
232
+ if (consecutiveFailures >= MAX_CONSECUTIVE_FAILURES) {
233
+ consecutiveFailures = 0;
234
+ await sleep(BACKOFF_DELAY_MS);
235
+ }
236
+ else {
237
+ await sleep(RETRY_DELAY_MS);
238
+ }
239
+ continue;
240
+ }
241
+ consecutiveFailures = 0;
242
+ // Save sync buf
243
+ if (resp.get_updates_buf != null && resp.get_updates_buf !== '') {
244
+ saveSyncBuf(account.accountId, resp.get_updates_buf);
245
+ buf = resp.get_updates_buf;
246
+ }
247
+ // Process messages
248
+ const msgs = resp.msgs ?? [];
249
+ for (const msg of msgs) {
250
+ // Only process user messages (message_type === 1)
251
+ if (msg.message_type !== 1)
252
+ continue;
253
+ // Handle each message (sequential to avoid race conditions)
254
+ await handleMessage(msg, account);
255
+ }
256
+ }
257
+ catch (err) {
258
+ consecutiveFailures++;
259
+ console.error(`[cc2wechat] Poll error (${consecutiveFailures}/${MAX_CONSECUTIVE_FAILURES}): ${String(err)}`);
260
+ if (consecutiveFailures >= MAX_CONSECUTIVE_FAILURES) {
261
+ consecutiveFailures = 0;
262
+ await sleep(BACKOFF_DELAY_MS);
263
+ }
264
+ else {
265
+ await sleep(RETRY_DELAY_MS);
266
+ }
267
+ }
268
+ }
269
+ }
270
+ // ---------------------------------------------------------------------------
271
+ // Main
272
+ // ---------------------------------------------------------------------------
273
+ async function main() {
274
+ console.log('\n cc2wechat v2 — Pipe Mode\n');
275
+ let account = getActiveAccount();
276
+ if (!account) {
277
+ console.log(' No saved credentials. Starting login...');
278
+ const result = await loginWithQRWeb();
279
+ saveAccount({
280
+ accountId: result.accountId.replace(/@/g, '-').replace(/\./g, '-'),
281
+ token: result.token,
282
+ baseUrl: result.baseUrl,
283
+ savedAt: new Date().toISOString(),
284
+ });
285
+ account = getActiveAccount();
286
+ }
287
+ console.log(` Account: ${account.accountId}`);
288
+ console.log(' Listening for WeChat messages...\n');
289
+ await pollLoop(account);
290
+ }
291
+ main().catch((err) => {
292
+ console.error(`Fatal: ${String(err)}`);
293
+ process.exit(1);
294
+ });
295
+ //# sourceMappingURL=daemon.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"daemon.js","sourceRoot":"","sources":["../src/daemon.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,42 @@
1
+ #!/usr/bin/env node
2
+ // CC calls this from Bash: cc2wechat-reply --image /path/to/file
3
+ // Or: cc2wechat-reply --text "hello"
4
+ import fs from 'node:fs';
5
+ import { sendMessage, uploadAndSendMedia } from './wechat-api.js';
6
+ const args = process.argv.slice(2);
7
+ const contextPath = '/tmp/cc2wechat-context.json';
8
+ if (!fs.existsSync(contextPath)) {
9
+ console.error('No active WeChat context. cc2wechat must be running.');
10
+ process.exit(1);
11
+ }
12
+ const ctx = JSON.parse(fs.readFileSync(contextPath, 'utf-8'));
13
+ async function main() {
14
+ if (args[0] === '--image' || args[0] === '--file') {
15
+ const filePath = args[1];
16
+ if (!filePath || !fs.existsSync(filePath)) {
17
+ console.error(`File not found: ${filePath}`);
18
+ process.exit(1);
19
+ }
20
+ await uploadAndSendMedia({
21
+ token: ctx.token,
22
+ toUser: ctx.userId,
23
+ contextToken: ctx.contextToken,
24
+ filePath,
25
+ baseUrl: ctx.baseUrl,
26
+ });
27
+ console.log(`Sent: ${filePath}`);
28
+ }
29
+ else if (args[0] === '--text') {
30
+ const text = args.slice(1).join(' ');
31
+ await sendMessage(ctx.token, ctx.userId, text, ctx.contextToken, ctx.baseUrl);
32
+ console.log(`Sent: ${text.slice(0, 50)}...`);
33
+ }
34
+ else {
35
+ console.log('Usage: cc2wechat-reply --image <path> | --text <message>');
36
+ }
37
+ }
38
+ main().catch((err) => {
39
+ console.error(String(err));
40
+ process.exit(1);
41
+ });
42
+ //# sourceMappingURL=reply-cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reply-cli.js","sourceRoot":"","sources":["../src/reply-cli.ts"],"names":[],"mappings":";AACA,iEAAiE;AACjE,qCAAqC;AAErC,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAElE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AACnC,MAAM,WAAW,GAAG,6BAA6B,CAAC;AAElD,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,WAAW,CAAC,EAAE,CAAC;IAChC,OAAO,CAAC,KAAK,CAAC,sDAAsD,CAAC,CAAC;IACtE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC;AAED,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,WAAW,EAAE,OAAO,CAAC,CAK3D,CAAC;AAEF,KAAK,UAAU,IAAI;IACjB,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,SAAS,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,QAAQ,EAAE,CAAC;QAClD,MAAM,QAAQ,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;QACzB,IAAI,CAAC,QAAQ,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC1C,OAAO,CAAC,KAAK,CAAC,mBAAmB,QAAQ,EAAE,CAAC,CAAC;YAC7C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;QACD,MAAM,kBAAkB,CAAC;YACvB,KAAK,EAAE,GAAG,CAAC,KAAK;YAChB,MAAM,EAAE,GAAG,CAAC,MAAM;YAClB,YAAY,EAAE,GAAG,CAAC,YAAY;YAC9B,QAAQ;YACR,OAAO,EAAE,GAAG,CAAC,OAAO;SACrB,CAAC,CAAC;QACH,OAAO,CAAC,GAAG,CAAC,SAAS,QAAQ,EAAE,CAAC,CAAC;IACnC,CAAC;SAAM,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,QAAQ,EAAE,CAAC;QAChC,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACrC,MAAM,WAAW,CAAC,GAAG,CAAC,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,IAAI,EAAE,GAAG,CAAC,YAAY,EAAE,GAAG,CAAC,OAAO,CAAC,CAAC;QAC9E,OAAO,CAAC,GAAG,CAAC,SAAS,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,CAAC;IAC/C,CAAC;SAAM,CAAC;QACN,OAAO,CAAC,GAAG,CAAC,0DAA0D,CAAC,CAAC;IAC1E,CAAC;AACH,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IACnB,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;IAC3B,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
package/package.json CHANGED
@@ -1,10 +1,11 @@
1
1
  {
2
2
  "name": "@aster110/cc2wechat",
3
- "version": "1.0.3",
3
+ "version": "2.0.0",
4
4
  "description": "WeChat channel for Claude Code — chat with Claude Code from WeChat via iLink Bot API",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "cc2wechat": "dist/cli.js",
8
+ "cc2wechat-reply": "dist/reply-cli.js",
8
9
  "wechat-cc-channel": "dist/server.js"
9
10
  },
10
11
  "scripts": {
package/src/cli.ts CHANGED
@@ -16,10 +16,11 @@ function printUsage(): void {
16
16
  🦞 wechat-claude — WeChat channel for Claude Code
17
17
 
18
18
  Usage:
19
- npx @aster110/wechat-claude install Setup: register MCP + scan QR login
20
- npx @aster110/wechat-claude login Re-login (scan QR code)
21
- npx @aster110/wechat-claude status Check connection status
22
- npx @aster110/wechat-claude help Show this help
19
+ npx cc2wechat start v2 Pipe Mode: listen & auto-reply via claude -p
20
+ npx cc2wechat install Setup: register MCP + scan QR login (v1 MCP mode)
21
+ npx cc2wechat login Re-login (scan QR code)
22
+ npx cc2wechat status Check connection status
23
+ npx cc2wechat help Show this help
23
24
  `);
24
25
  }
25
26
 
@@ -110,6 +111,9 @@ function status(): void {
110
111
  }
111
112
 
112
113
  switch (command) {
114
+ case 'start':
115
+ import('./daemon.js');
116
+ break;
113
117
  case 'install':
114
118
  case 'setup':
115
119
  install().catch(console.error);
package/src/daemon.ts ADDED
@@ -0,0 +1,336 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { execSync } from 'node:child_process';
4
+ import { createHash } from 'node:crypto';
5
+ import fs from 'node:fs';
6
+ import path from 'node:path';
7
+ import { fileURLToPath } from 'node:url';
8
+
9
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
10
+ const replyCli = path.join(__dirname, 'reply-cli.js');
11
+
12
+ import { loginWithQRWeb } from './auth.js';
13
+ import { getActiveAccount, saveAccount, loadSyncBuf, saveSyncBuf } from './store.js';
14
+ import { getUpdates, sendMessage, sendTyping, getConfig, uploadAndSendMedia } from './wechat-api.js';
15
+ import type { WeixinMessage } from './types.js';
16
+ import { MessageItemType } from './types.js';
17
+ import type { AccountData } from './store.js';
18
+
19
+ // ---------------------------------------------------------------------------
20
+ // Constants
21
+ // ---------------------------------------------------------------------------
22
+
23
+ const SESSION_EXPIRED_ERRCODE = -14;
24
+ const MAX_CONSECUTIVE_FAILURES = 3;
25
+ const BACKOFF_DELAY_MS = 30_000;
26
+ const RETRY_DELAY_MS = 2_000;
27
+ const SESSION_PAUSE_MS = 5 * 60_000;
28
+ const MAX_CHUNK_LENGTH = 3900;
29
+
30
+ // ---------------------------------------------------------------------------
31
+ // Helpers
32
+ // ---------------------------------------------------------------------------
33
+
34
+ function userIdToSessionUUID(userId: string): string {
35
+ const hash = createHash('md5').update(`cc2wechat:${userId}`).digest('hex');
36
+ return `${hash.slice(0, 8)}-${hash.slice(8, 12)}-4${hash.slice(13, 16)}-${hash.slice(16, 20)}-${hash.slice(20, 32)}`;
37
+ }
38
+
39
+ function extractText(msg: WeixinMessage): string {
40
+ const parts: string[] = [];
41
+ for (const item of msg.item_list ?? []) {
42
+ if (item.type === MessageItemType.TEXT && item.text_item?.text) {
43
+ parts.push(item.text_item.text);
44
+ } else if (item.type === MessageItemType.IMAGE) {
45
+ parts.push('[Image]');
46
+ } else if (item.type === MessageItemType.VOICE && item.voice_item?.text) {
47
+ parts.push(`[Voice] ${item.voice_item.text}`);
48
+ } else if (item.type === MessageItemType.FILE && item.file_item?.file_name) {
49
+ parts.push(`[File: ${item.file_item.file_name}]`);
50
+ } else if (item.type === MessageItemType.VIDEO) {
51
+ parts.push('[Video]');
52
+ }
53
+ }
54
+ return parts.join('\n') || '[Empty message]';
55
+ }
56
+
57
+ function stripMarkdown(text: string): string {
58
+ let result = text;
59
+ // Code blocks: strip fences, keep content
60
+ result = result.replace(/```[^\n]*\n?([\s\S]*?)```/g, (_, code: string) => code.trim());
61
+ // Images: remove
62
+ result = result.replace(/!\[[^\]]*\]\([^)]*\)/g, '');
63
+ // Links: keep display text
64
+ result = result.replace(/\[([^\]]+)\]\([^)]*\)/g, '$1');
65
+ // Bold/italic
66
+ result = result.replace(/\*\*(.+?)\*\*/g, '$1');
67
+ result = result.replace(/\*(.+?)\*/g, '$1');
68
+ result = result.replace(/__(.+?)__/g, '$1');
69
+ result = result.replace(/_(.+?)_/g, '$1');
70
+ // Headings
71
+ result = result.replace(/^#{1,6}\s+/gm, '');
72
+ // Horizontal rules
73
+ result = result.replace(/^[-*_]{3,}$/gm, '');
74
+ // Blockquotes
75
+ result = result.replace(/^>\s?/gm, '');
76
+ return result.trim();
77
+ }
78
+
79
+ function chunkText(text: string): string[] {
80
+ if (text.length <= MAX_CHUNK_LENGTH) return [text];
81
+ const chunks: string[] = [];
82
+ let remaining = text;
83
+ while (remaining.length > 0) {
84
+ if (remaining.length <= MAX_CHUNK_LENGTH) {
85
+ chunks.push(remaining);
86
+ break;
87
+ }
88
+ // Try to break at newline
89
+ let breakAt = remaining.lastIndexOf('\n', MAX_CHUNK_LENGTH);
90
+ if (breakAt < MAX_CHUNK_LENGTH * 0.5) {
91
+ // No good newline break, try space
92
+ breakAt = remaining.lastIndexOf(' ', MAX_CHUNK_LENGTH);
93
+ }
94
+ if (breakAt < MAX_CHUNK_LENGTH * 0.3) {
95
+ breakAt = MAX_CHUNK_LENGTH;
96
+ }
97
+ chunks.push(remaining.slice(0, breakAt));
98
+ remaining = remaining.slice(breakAt).trimStart();
99
+ }
100
+ return chunks;
101
+ }
102
+
103
+ function sleep(ms: number): Promise<void> {
104
+ return new Promise((resolve) => setTimeout(resolve, ms));
105
+ }
106
+
107
+ // ---------------------------------------------------------------------------
108
+ // Core message handler
109
+ // ---------------------------------------------------------------------------
110
+
111
+ async function handleMessage(msg: WeixinMessage, account: AccountData): Promise<void> {
112
+ const text = extractText(msg);
113
+ const userId = msg.from_user_id ?? '';
114
+ const contextToken = msg.context_token ?? '';
115
+ const sessionId = userIdToSessionUUID(userId);
116
+
117
+ console.log(`[cc2wechat] <- ${userId.slice(0, 10)}...: ${text.slice(0, 50)}`);
118
+
119
+ const systemPrompt = `You are responding to a WeChat message. Keep replies concise (under 500 chars when possible).
120
+
121
+ You have these WeChat commands available via Bash:
122
+ - Send image/file to user: node ${replyCli} --image /absolute/path/to/file
123
+ - Send text message mid-process: node ${replyCli} --text "processing..."
124
+
125
+ When user asks for screenshots, files, or images:
126
+ 1. Create/save the file (e.g. screencapture -x /tmp/screenshot.png)
127
+ 2. Send it: node ${replyCli} --image /tmp/screenshot.png
128
+ 3. Confirm in your response
129
+
130
+ Your final text response will also be sent to WeChat automatically.
131
+
132
+ IMPORTANT: You are running in non-interactive mode. Do NOT use Agent Teams (TeamCreate/TaskCreate). Handle all tasks yourself sequentially.`;
133
+
134
+ // Write context for cc2wechat-reply CLI
135
+ const contextPath = '/tmp/cc2wechat-context.json';
136
+ fs.writeFileSync(contextPath, JSON.stringify({
137
+ token: account.token,
138
+ baseUrl: account.baseUrl,
139
+ userId,
140
+ contextToken,
141
+ }));
142
+
143
+ // Send typing indicator
144
+ try {
145
+ const cfg = await getConfig(account.token, userId, contextToken, account.baseUrl);
146
+ if (cfg.typing_ticket) {
147
+ await sendTyping(account.token, userId, cfg.typing_ticket, 1, account.baseUrl).catch(() => {});
148
+ }
149
+ } catch {
150
+ // non-critical
151
+ }
152
+
153
+ // Call claude -p (try resume first, fallback to new session)
154
+ let result: string;
155
+ const prompt = JSON.stringify(text);
156
+ try {
157
+ // Try resuming existing session
158
+ result = execSync(
159
+ `claude -p ${prompt} --resume ${sessionId} --output-format text --permission-mode bypassPermissions --system-prompt ${JSON.stringify(systemPrompt)}`,
160
+ { encoding: 'utf-8', timeout: 120_000, maxBuffer: 10 * 1024 * 1024, cwd: process.cwd() },
161
+ ).trim();
162
+ } catch (err: unknown) {
163
+ const execErr = err as { stdout?: string; stderr?: string; message?: string };
164
+ const stderr = execErr.stderr ?? execErr.message ?? '';
165
+ // If session not found, create new one
166
+ if (stderr.includes('session') || stderr.includes('resume') || !execErr.stdout?.trim()) {
167
+ try {
168
+ result = execSync(
169
+ `claude -p ${prompt} --session-id ${sessionId} --output-format text --permission-mode bypassPermissions --system-prompt ${JSON.stringify(systemPrompt)}`,
170
+ { encoding: 'utf-8', timeout: 120_000, maxBuffer: 10 * 1024 * 1024, cwd: process.cwd() },
171
+ ).trim();
172
+ } catch (err2: unknown) {
173
+ const execErr2 = err2 as { stdout?: string; message?: string };
174
+ result = execErr2.stdout?.trim() || `Error: ${execErr2.message ?? 'unknown'}`;
175
+ }
176
+ } else {
177
+ result = execErr.stdout?.trim() || `Error: ${stderr}`;
178
+ }
179
+ }
180
+
181
+ console.log(`[cc2wechat] -> ${result.slice(0, 100)}...`);
182
+
183
+ // Strip markdown + chunk + send text
184
+ const plainText = stripMarkdown(result);
185
+ const chunks = chunkText(plainText);
186
+ for (const chunk of chunks) {
187
+ await sendMessage(account.token, userId, chunk, contextToken, account.baseUrl);
188
+ }
189
+
190
+ // Detect file paths in output, auto-send
191
+ const fileMatch = result.match(/\/(tmp|Users|home)[\w/._-]+\.(png|jpg|jpeg|gif|mp4|pdf|zip)/gi);
192
+ if (fileMatch) {
193
+ for (const filePath of fileMatch) {
194
+ if (fs.existsSync(filePath)) {
195
+ console.log(`[cc2wechat] Sending file: ${filePath}`);
196
+ try {
197
+ await uploadAndSendMedia({
198
+ token: account.token,
199
+ toUser: userId,
200
+ contextToken,
201
+ filePath,
202
+ baseUrl: account.baseUrl,
203
+ });
204
+ } catch (err: unknown) {
205
+ const sendErr = err as { message?: string };
206
+ console.error(`[cc2wechat] Failed to send file: ${sendErr.message ?? 'unknown'}`);
207
+ }
208
+ }
209
+ }
210
+ }
211
+
212
+ // Cancel typing
213
+ try {
214
+ const cfg = await getConfig(account.token, userId, contextToken, account.baseUrl);
215
+ if (cfg.typing_ticket) {
216
+ await sendTyping(account.token, userId, cfg.typing_ticket, 2, account.baseUrl).catch(() => {});
217
+ }
218
+ } catch {
219
+ // non-critical
220
+ }
221
+ }
222
+
223
+ // ---------------------------------------------------------------------------
224
+ // Long-polling loop
225
+ // ---------------------------------------------------------------------------
226
+
227
+ async function pollLoop(account: AccountData): Promise<void> {
228
+ let buf = loadSyncBuf(account.accountId);
229
+ let consecutiveFailures = 0;
230
+ let nextTimeoutMs = 35_000;
231
+
232
+ console.log(`[cc2wechat] Polling started for account ${account.accountId}`);
233
+
234
+ // eslint-disable-next-line no-constant-condition
235
+ while (true) {
236
+ try {
237
+ const resp = await getUpdates(account.token, buf, account.baseUrl, nextTimeoutMs);
238
+
239
+ // Update timeout if server suggests one
240
+ if (resp.longpolling_timeout_ms != null && resp.longpolling_timeout_ms > 0) {
241
+ nextTimeoutMs = resp.longpolling_timeout_ms;
242
+ }
243
+
244
+ // Check for API errors
245
+ const isApiError =
246
+ (resp.ret !== undefined && resp.ret !== 0) ||
247
+ (resp.errcode !== undefined && resp.errcode !== 0);
248
+
249
+ if (isApiError) {
250
+ const isSessionExpired =
251
+ resp.errcode === SESSION_EXPIRED_ERRCODE || resp.ret === SESSION_EXPIRED_ERRCODE;
252
+
253
+ if (isSessionExpired) {
254
+ console.log(
255
+ `[cc2wechat] Session expired (errcode ${SESSION_EXPIRED_ERRCODE}), pausing ${Math.ceil(SESSION_PAUSE_MS / 60_000)} min`,
256
+ );
257
+ consecutiveFailures = 0;
258
+ await sleep(SESSION_PAUSE_MS);
259
+ continue;
260
+ }
261
+
262
+ consecutiveFailures++;
263
+ console.error(
264
+ `[cc2wechat] getUpdates error: ret=${resp.ret} errcode=${resp.errcode} errmsg=${resp.errmsg ?? ''} (${consecutiveFailures}/${MAX_CONSECUTIVE_FAILURES})`,
265
+ );
266
+ if (consecutiveFailures >= MAX_CONSECUTIVE_FAILURES) {
267
+ consecutiveFailures = 0;
268
+ await sleep(BACKOFF_DELAY_MS);
269
+ } else {
270
+ await sleep(RETRY_DELAY_MS);
271
+ }
272
+ continue;
273
+ }
274
+
275
+ consecutiveFailures = 0;
276
+
277
+ // Save sync buf
278
+ if (resp.get_updates_buf != null && resp.get_updates_buf !== '') {
279
+ saveSyncBuf(account.accountId, resp.get_updates_buf);
280
+ buf = resp.get_updates_buf;
281
+ }
282
+
283
+ // Process messages
284
+ const msgs = resp.msgs ?? [];
285
+ for (const msg of msgs) {
286
+ // Only process user messages (message_type === 1)
287
+ if (msg.message_type !== 1) continue;
288
+
289
+ // Handle each message (sequential to avoid race conditions)
290
+ await handleMessage(msg, account);
291
+ }
292
+ } catch (err) {
293
+ consecutiveFailures++;
294
+ console.error(
295
+ `[cc2wechat] Poll error (${consecutiveFailures}/${MAX_CONSECUTIVE_FAILURES}): ${String(err)}`,
296
+ );
297
+ if (consecutiveFailures >= MAX_CONSECUTIVE_FAILURES) {
298
+ consecutiveFailures = 0;
299
+ await sleep(BACKOFF_DELAY_MS);
300
+ } else {
301
+ await sleep(RETRY_DELAY_MS);
302
+ }
303
+ }
304
+ }
305
+ }
306
+
307
+ // ---------------------------------------------------------------------------
308
+ // Main
309
+ // ---------------------------------------------------------------------------
310
+
311
+ async function main(): Promise<void> {
312
+ console.log('\n cc2wechat v2 — Pipe Mode\n');
313
+
314
+ let account = getActiveAccount();
315
+ if (!account) {
316
+ console.log(' No saved credentials. Starting login...');
317
+ const result = await loginWithQRWeb();
318
+ saveAccount({
319
+ accountId: result.accountId.replace(/@/g, '-').replace(/\./g, '-'),
320
+ token: result.token,
321
+ baseUrl: result.baseUrl,
322
+ savedAt: new Date().toISOString(),
323
+ });
324
+ account = getActiveAccount()!;
325
+ }
326
+
327
+ console.log(` Account: ${account.accountId}`);
328
+ console.log(' Listening for WeChat messages...\n');
329
+
330
+ await pollLoop(account);
331
+ }
332
+
333
+ main().catch((err) => {
334
+ console.error(`Fatal: ${String(err)}`);
335
+ process.exit(1);
336
+ });
@@ -0,0 +1,50 @@
1
+ #!/usr/bin/env node
2
+ // CC calls this from Bash: cc2wechat-reply --image /path/to/file
3
+ // Or: cc2wechat-reply --text "hello"
4
+
5
+ import fs from 'node:fs';
6
+ import { sendMessage, uploadAndSendMedia } from './wechat-api.js';
7
+
8
+ const args = process.argv.slice(2);
9
+ const contextPath = '/tmp/cc2wechat-context.json';
10
+
11
+ if (!fs.existsSync(contextPath)) {
12
+ console.error('No active WeChat context. cc2wechat must be running.');
13
+ process.exit(1);
14
+ }
15
+
16
+ const ctx = JSON.parse(fs.readFileSync(contextPath, 'utf-8')) as {
17
+ token: string;
18
+ baseUrl?: string;
19
+ userId: string;
20
+ contextToken: string;
21
+ };
22
+
23
+ async function main(): Promise<void> {
24
+ if (args[0] === '--image' || args[0] === '--file') {
25
+ const filePath = args[1];
26
+ if (!filePath || !fs.existsSync(filePath)) {
27
+ console.error(`File not found: ${filePath}`);
28
+ process.exit(1);
29
+ }
30
+ await uploadAndSendMedia({
31
+ token: ctx.token,
32
+ toUser: ctx.userId,
33
+ contextToken: ctx.contextToken,
34
+ filePath,
35
+ baseUrl: ctx.baseUrl,
36
+ });
37
+ console.log(`Sent: ${filePath}`);
38
+ } else if (args[0] === '--text') {
39
+ const text = args.slice(1).join(' ');
40
+ await sendMessage(ctx.token, ctx.userId, text, ctx.contextToken, ctx.baseUrl);
41
+ console.log(`Sent: ${text.slice(0, 50)}...`);
42
+ } else {
43
+ console.log('Usage: cc2wechat-reply --image <path> | --text <message>');
44
+ }
45
+ }
46
+
47
+ main().catch((err) => {
48
+ console.error(String(err));
49
+ process.exit(1);
50
+ });