@aster110/cc2wechat 1.0.3 → 3.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,292 @@
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
+ const IS_MACOS = process.platform === 'darwin';
10
+ import { loginWithQRWeb } from './auth.js';
11
+ import { getActiveAccount, saveAccount, loadSyncBuf, saveSyncBuf } from './store.js';
12
+ import { getUpdates, sendTyping, getConfig } from './wechat-api.js';
13
+ import { MessageItemType } from './types.js';
14
+ // ---------------------------------------------------------------------------
15
+ // Constants
16
+ // ---------------------------------------------------------------------------
17
+ const SESSION_EXPIRED_ERRCODE = -14;
18
+ const MAX_CONSECUTIVE_FAILURES = 3;
19
+ const BACKOFF_DELAY_MS = 30_000;
20
+ const RETRY_DELAY_MS = 2_000;
21
+ const SESSION_PAUSE_MS = 5 * 60_000;
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 sleep(ms) {
51
+ return new Promise((resolve) => setTimeout(resolve, ms));
52
+ }
53
+ // ---------------------------------------------------------------------------
54
+ // iTerm Tab Management
55
+ // ---------------------------------------------------------------------------
56
+ // Maintain tab state per WeChat user
57
+ const userTabs = new Map(); // userId -> tabName
58
+ // Track tab IDs - persisted to file so it survives daemon restart
59
+ const TAB_REGISTRY_PATH = '/tmp/cc2wechat-tabs.json';
60
+ const tabSessionIds = new Map(); // tabName -> iTerm session id
61
+ // Load persisted tab registry on startup
62
+ try {
63
+ if (fs.existsSync(TAB_REGISTRY_PATH)) {
64
+ const data = JSON.parse(fs.readFileSync(TAB_REGISTRY_PATH, 'utf-8'));
65
+ for (const [k, v] of Object.entries(data)) {
66
+ tabSessionIds.set(k, v);
67
+ }
68
+ }
69
+ }
70
+ catch { }
71
+ function saveTabRegistry() {
72
+ fs.writeFileSync(TAB_REGISTRY_PATH, JSON.stringify(Object.fromEntries(tabSessionIds)));
73
+ }
74
+ function tabExists(tabName) {
75
+ const windowId = tabSessionIds.get(tabName);
76
+ if (!windowId)
77
+ return false;
78
+ try {
79
+ const result = execSync(`osascript -e '
80
+ tell application "iTerm2"
81
+ try
82
+ set w to (first window whose id is ${windowId})
83
+ return "found"
84
+ on error
85
+ return "not_found"
86
+ end try
87
+ end tell
88
+ '`, { encoding: 'utf-8' }).trim();
89
+ return result === 'found';
90
+ }
91
+ catch {
92
+ return false;
93
+ }
94
+ }
95
+ function createTabAndStartCC(tabName, ccSessionId, cwd) {
96
+ // Create NEW WINDOW and capture window ID (same approach as cc-mesh)
97
+ const windowId = execSync(`osascript -e '
98
+ tell application "iTerm2"
99
+ set w to (create window with default profile)
100
+ tell current session of w
101
+ write text "cd ${cwd} && claude --resume ${ccSessionId} --dangerously-skip-permissions"
102
+ end tell
103
+ return id of w
104
+ end tell
105
+ '`, { encoding: 'utf-8' }).trim();
106
+ tabSessionIds.set(tabName, windowId);
107
+ saveTabRegistry();
108
+ console.log(`[cc2wechat] window created: ${tabName} -> window id: ${windowId}`);
109
+ }
110
+ function injectMessage(tabName, message) {
111
+ const windowId = tabSessionIds.get(tabName);
112
+ if (!windowId)
113
+ return;
114
+ const escaped = message.replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\n/g, '\\n');
115
+ execSync(`osascript -e '
116
+ tell application "iTerm2"
117
+ tell current session of (first window whose id is ${windowId})
118
+ write text "${escaped}"
119
+ end tell
120
+ end tell
121
+ '`);
122
+ }
123
+ // ---------------------------------------------------------------------------
124
+ // Core message handler
125
+ // ---------------------------------------------------------------------------
126
+ async function handleMessage(msg, account) {
127
+ const text = extractText(msg);
128
+ const userId = msg.from_user_id ?? '';
129
+ const contextToken = msg.context_token ?? '';
130
+ const sessionId = userIdToSessionUUID(userId);
131
+ const tabName = `wechat-${userId.slice(0, 8)}`;
132
+ const cwd = process.cwd();
133
+ console.log(`[cc2wechat] <- ${userId.slice(0, 10)}...: ${text.slice(0, 50)}`);
134
+ // Write context for reply-cli
135
+ fs.writeFileSync('/tmp/cc2wechat-context.json', JSON.stringify({
136
+ token: account.token,
137
+ baseUrl: account.baseUrl,
138
+ userId,
139
+ contextToken,
140
+ }));
141
+ // Send typing indicator
142
+ try {
143
+ const cfg = await getConfig(account.token, userId, contextToken, account.baseUrl);
144
+ if (cfg.typing_ticket) {
145
+ await sendTyping(account.token, userId, cfg.typing_ticket, 1, account.baseUrl).catch(() => { });
146
+ }
147
+ }
148
+ catch {
149
+ // non-critical
150
+ }
151
+ if (IS_MACOS) {
152
+ // v3: Interactive Terminal Mode (macOS + iTerm2)
153
+ if (tabExists(tabName)) {
154
+ console.log(`[cc2wechat] -> inject to existing window: ${tabName}`);
155
+ injectMessage(tabName, `[微信] ${text}`);
156
+ }
157
+ else {
158
+ console.log(`[cc2wechat] -> creating window: ${tabName} (session: ${sessionId})`);
159
+ createTabAndStartCC(tabName, sessionId, cwd);
160
+ await sleep(5000);
161
+ injectMessage(tabName, `[微信] ${text}`);
162
+ }
163
+ userTabs.set(userId, tabName);
164
+ }
165
+ else {
166
+ // v2: Pipe Mode fallback (Windows/Linux)
167
+ console.log(`[cc2wechat] -> pipe mode: ${text.slice(0, 30)}...`);
168
+ const prompt = JSON.stringify(text);
169
+ const systemPrompt = JSON.stringify(`You are responding to a WeChat message. Keep replies concise. Use this to reply: node ${replyCli} --text "reply" or node ${replyCli} --image /path/to/file`);
170
+ let result;
171
+ try {
172
+ result = execSync(`claude -p ${prompt} --resume ${sessionId} --output-format text --permission-mode bypassPermissions --system-prompt ${systemPrompt}`, { encoding: 'utf-8', timeout: 120_000, maxBuffer: 10 * 1024 * 1024, cwd }).trim();
173
+ }
174
+ catch (err) {
175
+ const execErr = err;
176
+ if (!execErr.stdout?.trim()) {
177
+ try {
178
+ result = execSync(`claude -p ${prompt} --session-id ${sessionId} --output-format text --permission-mode bypassPermissions --system-prompt ${systemPrompt}`, { encoding: 'utf-8', timeout: 120_000, maxBuffer: 10 * 1024 * 1024, cwd }).trim();
179
+ }
180
+ catch (err2) {
181
+ const execErr2 = err2;
182
+ result = execErr2.stdout?.trim() || `Error: ${execErr2.message ?? 'unknown'}`;
183
+ }
184
+ }
185
+ else {
186
+ result = execErr.stdout.trim();
187
+ }
188
+ }
189
+ // Auto-send text reply
190
+ const { sendMessage: sendMsg } = await import('./wechat-api.js');
191
+ const plain = result.replace(/```[^\n]*\n?([\s\S]*?)```/g, (_, c) => c.trim())
192
+ .replace(/\*\*(.+?)\*\*/g, '$1').replace(/^#{1,6}\s+/gm, '').trim();
193
+ const chunks = plain.length <= 3900 ? [plain] : [plain.slice(0, 3900), plain.slice(3900)];
194
+ for (const chunk of chunks) {
195
+ await sendMsg(account.token, userId, chunk, contextToken, account.baseUrl);
196
+ }
197
+ console.log(`[cc2wechat] -> replied (${chunks.length} chunk)`);
198
+ }
199
+ }
200
+ // ---------------------------------------------------------------------------
201
+ // Long-polling loop
202
+ // ---------------------------------------------------------------------------
203
+ async function pollLoop(account) {
204
+ let buf = loadSyncBuf(account.accountId);
205
+ let consecutiveFailures = 0;
206
+ let nextTimeoutMs = 35_000;
207
+ console.log(`[cc2wechat] Polling started for account ${account.accountId}`);
208
+ // eslint-disable-next-line no-constant-condition
209
+ while (true) {
210
+ try {
211
+ const resp = await getUpdates(account.token, buf, account.baseUrl, nextTimeoutMs);
212
+ // Update timeout if server suggests one
213
+ if (resp.longpolling_timeout_ms != null && resp.longpolling_timeout_ms > 0) {
214
+ nextTimeoutMs = resp.longpolling_timeout_ms;
215
+ }
216
+ // Check for API errors
217
+ const isApiError = (resp.ret !== undefined && resp.ret !== 0) ||
218
+ (resp.errcode !== undefined && resp.errcode !== 0);
219
+ if (isApiError) {
220
+ const isSessionExpired = resp.errcode === SESSION_EXPIRED_ERRCODE || resp.ret === SESSION_EXPIRED_ERRCODE;
221
+ if (isSessionExpired) {
222
+ console.log(`[cc2wechat] Session expired (errcode ${SESSION_EXPIRED_ERRCODE}), pausing ${Math.ceil(SESSION_PAUSE_MS / 60_000)} min`);
223
+ consecutiveFailures = 0;
224
+ await sleep(SESSION_PAUSE_MS);
225
+ continue;
226
+ }
227
+ consecutiveFailures++;
228
+ console.error(`[cc2wechat] getUpdates error: ret=${resp.ret} errcode=${resp.errcode} errmsg=${resp.errmsg ?? ''} (${consecutiveFailures}/${MAX_CONSECUTIVE_FAILURES})`);
229
+ if (consecutiveFailures >= MAX_CONSECUTIVE_FAILURES) {
230
+ consecutiveFailures = 0;
231
+ await sleep(BACKOFF_DELAY_MS);
232
+ }
233
+ else {
234
+ await sleep(RETRY_DELAY_MS);
235
+ }
236
+ continue;
237
+ }
238
+ consecutiveFailures = 0;
239
+ // Save sync buf
240
+ if (resp.get_updates_buf != null && resp.get_updates_buf !== '') {
241
+ saveSyncBuf(account.accountId, resp.get_updates_buf);
242
+ buf = resp.get_updates_buf;
243
+ }
244
+ // Process messages
245
+ const msgs = resp.msgs ?? [];
246
+ for (const msg of msgs) {
247
+ // Only process user messages (message_type === 1)
248
+ if (msg.message_type !== 1)
249
+ continue;
250
+ // Handle each message (sequential to avoid race conditions)
251
+ await handleMessage(msg, account);
252
+ }
253
+ }
254
+ catch (err) {
255
+ consecutiveFailures++;
256
+ console.error(`[cc2wechat] Poll error (${consecutiveFailures}/${MAX_CONSECUTIVE_FAILURES}): ${String(err)}`);
257
+ if (consecutiveFailures >= MAX_CONSECUTIVE_FAILURES) {
258
+ consecutiveFailures = 0;
259
+ await sleep(BACKOFF_DELAY_MS);
260
+ }
261
+ else {
262
+ await sleep(RETRY_DELAY_MS);
263
+ }
264
+ }
265
+ }
266
+ }
267
+ // ---------------------------------------------------------------------------
268
+ // Main
269
+ // ---------------------------------------------------------------------------
270
+ async function main() {
271
+ console.log('\n cc2wechat v3 — Interactive Terminal Mode\n');
272
+ let account = getActiveAccount();
273
+ if (!account) {
274
+ console.log(' No saved credentials. Starting login...');
275
+ const result = await loginWithQRWeb();
276
+ saveAccount({
277
+ accountId: result.accountId.replace(/@/g, '-').replace(/\./g, '-'),
278
+ token: result.token,
279
+ baseUrl: result.baseUrl,
280
+ savedAt: new Date().toISOString(),
281
+ });
282
+ account = getActiveAccount();
283
+ }
284
+ console.log(` Account: ${account.accountId}`);
285
+ console.log(' Listening for WeChat messages...\n');
286
+ await pollLoop(account);
287
+ }
288
+ main().catch((err) => {
289
+ console.error(`Fatal: ${String(err)}`);
290
+ process.exit(1);
291
+ });
292
+ //# 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": "3.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,331 @@
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
+ const IS_MACOS = process.platform === 'darwin';
12
+
13
+ import { loginWithQRWeb } from './auth.js';
14
+ import { getActiveAccount, saveAccount, loadSyncBuf, saveSyncBuf } from './store.js';
15
+ import { getUpdates, sendTyping, getConfig } from './wechat-api.js';
16
+ import type { WeixinMessage } from './types.js';
17
+ import { MessageItemType } from './types.js';
18
+ import type { AccountData } from './store.js';
19
+
20
+ // ---------------------------------------------------------------------------
21
+ // Constants
22
+ // ---------------------------------------------------------------------------
23
+
24
+ const SESSION_EXPIRED_ERRCODE = -14;
25
+ const MAX_CONSECUTIVE_FAILURES = 3;
26
+ const BACKOFF_DELAY_MS = 30_000;
27
+ const RETRY_DELAY_MS = 2_000;
28
+ const SESSION_PAUSE_MS = 5 * 60_000;
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 sleep(ms: number): Promise<void> {
58
+ return new Promise((resolve) => setTimeout(resolve, ms));
59
+ }
60
+
61
+ // ---------------------------------------------------------------------------
62
+ // iTerm Tab Management
63
+ // ---------------------------------------------------------------------------
64
+
65
+ // Maintain tab state per WeChat user
66
+ const userTabs = new Map<string, string>(); // userId -> tabName
67
+
68
+ // Track tab IDs - persisted to file so it survives daemon restart
69
+ const TAB_REGISTRY_PATH = '/tmp/cc2wechat-tabs.json';
70
+ const tabSessionIds = new Map<string, string>(); // tabName -> iTerm session id
71
+
72
+ // Load persisted tab registry on startup
73
+ try {
74
+ if (fs.existsSync(TAB_REGISTRY_PATH)) {
75
+ const data = JSON.parse(fs.readFileSync(TAB_REGISTRY_PATH, 'utf-8'));
76
+ for (const [k, v] of Object.entries(data)) {
77
+ tabSessionIds.set(k, v as string);
78
+ }
79
+ }
80
+ } catch {}
81
+
82
+ function saveTabRegistry(): void {
83
+ fs.writeFileSync(TAB_REGISTRY_PATH, JSON.stringify(Object.fromEntries(tabSessionIds)));
84
+ }
85
+
86
+ function tabExists(tabName: string): boolean {
87
+ const windowId = tabSessionIds.get(tabName);
88
+ if (!windowId) return false;
89
+ try {
90
+ const result = execSync(`osascript -e '
91
+ tell application "iTerm2"
92
+ try
93
+ set w to (first window whose id is ${windowId})
94
+ return "found"
95
+ on error
96
+ return "not_found"
97
+ end try
98
+ end tell
99
+ '`, { encoding: 'utf-8' }).trim();
100
+ return result === 'found';
101
+ } catch {
102
+ return false;
103
+ }
104
+ }
105
+
106
+ function createTabAndStartCC(tabName: string, ccSessionId: string, cwd: string): void {
107
+ // Create NEW WINDOW and capture window ID (same approach as cc-mesh)
108
+ const windowId = execSync(`osascript -e '
109
+ tell application "iTerm2"
110
+ set w to (create window with default profile)
111
+ tell current session of w
112
+ write text "cd ${cwd} && claude --resume ${ccSessionId} --dangerously-skip-permissions"
113
+ end tell
114
+ return id of w
115
+ end tell
116
+ '`, { encoding: 'utf-8' }).trim();
117
+ tabSessionIds.set(tabName, windowId);
118
+ saveTabRegistry();
119
+ console.log(`[cc2wechat] window created: ${tabName} -> window id: ${windowId}`);
120
+ }
121
+
122
+ function injectMessage(tabName: string, message: string): void {
123
+ const windowId = tabSessionIds.get(tabName);
124
+ if (!windowId) return;
125
+ const escaped = message.replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\n/g, '\\n');
126
+ execSync(`osascript -e '
127
+ tell application "iTerm2"
128
+ tell current session of (first window whose id is ${windowId})
129
+ write text "${escaped}"
130
+ end tell
131
+ end tell
132
+ '`);
133
+ }
134
+
135
+ // ---------------------------------------------------------------------------
136
+ // Core message handler
137
+ // ---------------------------------------------------------------------------
138
+
139
+ async function handleMessage(msg: WeixinMessage, account: AccountData): Promise<void> {
140
+ const text = extractText(msg);
141
+ const userId = msg.from_user_id ?? '';
142
+ const contextToken = msg.context_token ?? '';
143
+ const sessionId = userIdToSessionUUID(userId);
144
+ const tabName = `wechat-${userId.slice(0, 8)}`;
145
+ const cwd = process.cwd();
146
+
147
+ console.log(`[cc2wechat] <- ${userId.slice(0, 10)}...: ${text.slice(0, 50)}`);
148
+
149
+ // Write context for reply-cli
150
+ fs.writeFileSync('/tmp/cc2wechat-context.json', JSON.stringify({
151
+ token: account.token,
152
+ baseUrl: account.baseUrl,
153
+ userId,
154
+ contextToken,
155
+ }));
156
+
157
+ // Send typing indicator
158
+ try {
159
+ const cfg = await getConfig(account.token, userId, contextToken, account.baseUrl);
160
+ if (cfg.typing_ticket) {
161
+ await sendTyping(account.token, userId, cfg.typing_ticket, 1, account.baseUrl).catch(() => {});
162
+ }
163
+ } catch {
164
+ // non-critical
165
+ }
166
+
167
+ if (IS_MACOS) {
168
+ // v3: Interactive Terminal Mode (macOS + iTerm2)
169
+ if (tabExists(tabName)) {
170
+ console.log(`[cc2wechat] -> inject to existing window: ${tabName}`);
171
+ injectMessage(tabName, `[微信] ${text}`);
172
+ } else {
173
+ console.log(`[cc2wechat] -> creating window: ${tabName} (session: ${sessionId})`);
174
+ createTabAndStartCC(tabName, sessionId, cwd);
175
+ await sleep(5000);
176
+ injectMessage(tabName, `[微信] ${text}`);
177
+ }
178
+ userTabs.set(userId, tabName);
179
+ } else {
180
+ // v2: Pipe Mode fallback (Windows/Linux)
181
+ console.log(`[cc2wechat] -> pipe mode: ${text.slice(0, 30)}...`);
182
+ const prompt = JSON.stringify(text);
183
+ const systemPrompt = JSON.stringify(`You are responding to a WeChat message. Keep replies concise. Use this to reply: node ${replyCli} --text "reply" or node ${replyCli} --image /path/to/file`);
184
+ let result: string;
185
+ try {
186
+ result = execSync(
187
+ `claude -p ${prompt} --resume ${sessionId} --output-format text --permission-mode bypassPermissions --system-prompt ${systemPrompt}`,
188
+ { encoding: 'utf-8', timeout: 120_000, maxBuffer: 10 * 1024 * 1024, cwd },
189
+ ).trim();
190
+ } catch (err: unknown) {
191
+ const execErr = err as { stdout?: string; stderr?: string; message?: string };
192
+ if (!execErr.stdout?.trim()) {
193
+ try {
194
+ result = execSync(
195
+ `claude -p ${prompt} --session-id ${sessionId} --output-format text --permission-mode bypassPermissions --system-prompt ${systemPrompt}`,
196
+ { encoding: 'utf-8', timeout: 120_000, maxBuffer: 10 * 1024 * 1024, cwd },
197
+ ).trim();
198
+ } catch (err2: unknown) {
199
+ const execErr2 = err2 as { stdout?: string; message?: string };
200
+ result = execErr2.stdout?.trim() || `Error: ${execErr2.message ?? 'unknown'}`;
201
+ }
202
+ } else {
203
+ result = execErr.stdout.trim();
204
+ }
205
+ }
206
+ // Auto-send text reply
207
+ const { sendMessage: sendMsg } = await import('./wechat-api.js');
208
+ const plain = result.replace(/```[^\n]*\n?([\s\S]*?)```/g, (_, c: string) => c.trim())
209
+ .replace(/\*\*(.+?)\*\*/g, '$1').replace(/^#{1,6}\s+/gm, '').trim();
210
+ const chunks = plain.length <= 3900 ? [plain] : [plain.slice(0, 3900), plain.slice(3900)];
211
+ for (const chunk of chunks) {
212
+ await sendMsg(account.token, userId, chunk, contextToken, account.baseUrl);
213
+ }
214
+ console.log(`[cc2wechat] -> replied (${chunks.length} chunk)`);
215
+ }
216
+ }
217
+
218
+ // ---------------------------------------------------------------------------
219
+ // Long-polling loop
220
+ // ---------------------------------------------------------------------------
221
+
222
+ async function pollLoop(account: AccountData): Promise<void> {
223
+ let buf = loadSyncBuf(account.accountId);
224
+ let consecutiveFailures = 0;
225
+ let nextTimeoutMs = 35_000;
226
+
227
+ console.log(`[cc2wechat] Polling started for account ${account.accountId}`);
228
+
229
+ // eslint-disable-next-line no-constant-condition
230
+ while (true) {
231
+ try {
232
+ const resp = await getUpdates(account.token, buf, account.baseUrl, nextTimeoutMs);
233
+
234
+ // Update timeout if server suggests one
235
+ if (resp.longpolling_timeout_ms != null && resp.longpolling_timeout_ms > 0) {
236
+ nextTimeoutMs = resp.longpolling_timeout_ms;
237
+ }
238
+
239
+ // Check for API errors
240
+ const isApiError =
241
+ (resp.ret !== undefined && resp.ret !== 0) ||
242
+ (resp.errcode !== undefined && resp.errcode !== 0);
243
+
244
+ if (isApiError) {
245
+ const isSessionExpired =
246
+ resp.errcode === SESSION_EXPIRED_ERRCODE || resp.ret === SESSION_EXPIRED_ERRCODE;
247
+
248
+ if (isSessionExpired) {
249
+ console.log(
250
+ `[cc2wechat] Session expired (errcode ${SESSION_EXPIRED_ERRCODE}), pausing ${Math.ceil(SESSION_PAUSE_MS / 60_000)} min`,
251
+ );
252
+ consecutiveFailures = 0;
253
+ await sleep(SESSION_PAUSE_MS);
254
+ continue;
255
+ }
256
+
257
+ consecutiveFailures++;
258
+ console.error(
259
+ `[cc2wechat] getUpdates error: ret=${resp.ret} errcode=${resp.errcode} errmsg=${resp.errmsg ?? ''} (${consecutiveFailures}/${MAX_CONSECUTIVE_FAILURES})`,
260
+ );
261
+ if (consecutiveFailures >= MAX_CONSECUTIVE_FAILURES) {
262
+ consecutiveFailures = 0;
263
+ await sleep(BACKOFF_DELAY_MS);
264
+ } else {
265
+ await sleep(RETRY_DELAY_MS);
266
+ }
267
+ continue;
268
+ }
269
+
270
+ consecutiveFailures = 0;
271
+
272
+ // Save sync buf
273
+ if (resp.get_updates_buf != null && resp.get_updates_buf !== '') {
274
+ saveSyncBuf(account.accountId, resp.get_updates_buf);
275
+ buf = resp.get_updates_buf;
276
+ }
277
+
278
+ // Process messages
279
+ const msgs = resp.msgs ?? [];
280
+ for (const msg of msgs) {
281
+ // Only process user messages (message_type === 1)
282
+ if (msg.message_type !== 1) continue;
283
+
284
+ // Handle each message (sequential to avoid race conditions)
285
+ await handleMessage(msg, account);
286
+ }
287
+ } catch (err) {
288
+ consecutiveFailures++;
289
+ console.error(
290
+ `[cc2wechat] Poll error (${consecutiveFailures}/${MAX_CONSECUTIVE_FAILURES}): ${String(err)}`,
291
+ );
292
+ if (consecutiveFailures >= MAX_CONSECUTIVE_FAILURES) {
293
+ consecutiveFailures = 0;
294
+ await sleep(BACKOFF_DELAY_MS);
295
+ } else {
296
+ await sleep(RETRY_DELAY_MS);
297
+ }
298
+ }
299
+ }
300
+ }
301
+
302
+ // ---------------------------------------------------------------------------
303
+ // Main
304
+ // ---------------------------------------------------------------------------
305
+
306
+ async function main(): Promise<void> {
307
+ console.log('\n cc2wechat v3 — Interactive Terminal Mode\n');
308
+
309
+ let account = getActiveAccount();
310
+ if (!account) {
311
+ console.log(' No saved credentials. Starting login...');
312
+ const result = await loginWithQRWeb();
313
+ saveAccount({
314
+ accountId: result.accountId.replace(/@/g, '-').replace(/\./g, '-'),
315
+ token: result.token,
316
+ baseUrl: result.baseUrl,
317
+ savedAt: new Date().toISOString(),
318
+ });
319
+ account = getActiveAccount()!;
320
+ }
321
+
322
+ console.log(` Account: ${account.accountId}`);
323
+ console.log(' Listening for WeChat messages...\n');
324
+
325
+ await pollLoop(account);
326
+ }
327
+
328
+ main().catch((err) => {
329
+ console.error(`Fatal: ${String(err)}`);
330
+ process.exit(1);
331
+ });
@@ -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
+ });