@bastdewfn/cc-remote 1.0.9

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,136 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.WeixinClient = void 0;
4
+ const api_1 = require("./api");
5
+ const types_1 = require("./types");
6
+ const DEFAULT_LONG_POLL_TIMEOUT_MS = 35000;
7
+ const MAX_CONSECUTIVE_FAILURES = 3;
8
+ const BACKOFF_DELAY_MS = 30000;
9
+ const RETRY_DELAY_MS = 2000;
10
+ const SESSION_EXPIRED_ERRCODE = -14;
11
+ class WeixinClient {
12
+ constructor(opts) {
13
+ this.abortController = null;
14
+ this.getUpdatesBuf = '';
15
+ const account = (0, api_1.resolveAccount)(opts.accountId);
16
+ this.accountId = account.accountId;
17
+ this.baseUrl = account.baseUrl;
18
+ this.token = account.token;
19
+ this.onMessage = opts.onMessage;
20
+ this.onStatus = opts.onStatus ?? (() => { });
21
+ }
22
+ getAccountId() { return this.accountId; }
23
+ isConfigured() {
24
+ return Boolean(this.token);
25
+ }
26
+ async start() {
27
+ if (!this.token) {
28
+ this.onStatus(`\r\n⚠️ 微信未登录,请先执行登录\r\n`);
29
+ return;
30
+ }
31
+ this.abortController = new AbortController();
32
+ const signal = this.abortController.signal;
33
+ this.onStatus(`\r\n[微信] 启动监听 (${this.baseUrl}, account=${this.accountId})\r\n`);
34
+ try {
35
+ await (0, api_1.notifyStart)({ baseUrl: this.baseUrl, token: this.token });
36
+ }
37
+ catch (err) {
38
+ this.onStatus(`\r\n⚠️ notifyStart 失败 (ignored): ${err}\r\n`);
39
+ }
40
+ let nextTimeoutMs = DEFAULT_LONG_POLL_TIMEOUT_MS;
41
+ let consecutiveFailures = 0;
42
+ while (!signal.aborted) {
43
+ try {
44
+ const resp = await (0, api_1.getUpdates)({
45
+ baseUrl: this.baseUrl,
46
+ token: this.token,
47
+ get_updates_buf: this.getUpdatesBuf,
48
+ timeoutMs: nextTimeoutMs,
49
+ });
50
+ if (resp.longpolling_timeout_ms && resp.longpolling_timeout_ms > 0) {
51
+ nextTimeoutMs = resp.longpolling_timeout_ms;
52
+ }
53
+ const isSessionExpired = resp.errcode === SESSION_EXPIRED_ERRCODE || resp.ret === SESSION_EXPIRED_ERRCODE;
54
+ if (isSessionExpired) {
55
+ this.onStatus(`\r\n⚠️ 微信会话已过期 (errcode=${SESSION_EXPIRED_ERRCODE}),请运行 cc-remote config 重新登录\r\n`);
56
+ break;
57
+ }
58
+ const isApiError = (resp.ret !== undefined && resp.ret !== 0) ||
59
+ (resp.errcode !== undefined && resp.errcode !== 0);
60
+ if (isApiError) {
61
+ consecutiveFailures += 1;
62
+ this.onStatus(`\r\n⚠️ getUpdates 失败: ret=${resp.ret} errcode=${resp.errcode} (${consecutiveFailures}/${MAX_CONSECUTIVE_FAILURES})\r\n`);
63
+ if (consecutiveFailures >= MAX_CONSECUTIVE_FAILURES) {
64
+ await sleep(BACKOFF_DELAY_MS, signal);
65
+ consecutiveFailures = 0;
66
+ }
67
+ else {
68
+ await sleep(RETRY_DELAY_MS, signal);
69
+ }
70
+ continue;
71
+ }
72
+ consecutiveFailures = 0;
73
+ if (resp.get_updates_buf) {
74
+ this.getUpdatesBuf = resp.get_updates_buf;
75
+ }
76
+ const msgs = resp.msgs ?? [];
77
+ for (const msg of msgs) {
78
+ this.processMessage(msg);
79
+ }
80
+ }
81
+ catch (err) {
82
+ if (signal.aborted)
83
+ return;
84
+ consecutiveFailures += 1;
85
+ this.onStatus(`\r\n⚠️ getUpdates 异常 (${consecutiveFailures}/${MAX_CONSECUTIVE_FAILURES}): ${err}\r\n`);
86
+ if (consecutiveFailures >= MAX_CONSECUTIVE_FAILURES) {
87
+ await sleep(BACKOFF_DELAY_MS, signal);
88
+ consecutiveFailures = 0;
89
+ }
90
+ else {
91
+ await sleep(RETRY_DELAY_MS, signal);
92
+ }
93
+ }
94
+ }
95
+ }
96
+ async stop() {
97
+ this.abortController?.abort();
98
+ try {
99
+ if (this.token) {
100
+ await (0, api_1.notifyStop)({ baseUrl: this.baseUrl, token: this.token });
101
+ }
102
+ }
103
+ catch { /* ignore */ }
104
+ }
105
+ processMessage(msg) {
106
+ const userId = msg.from_user_id;
107
+ if (!userId)
108
+ return;
109
+ const items = msg.item_list ?? [];
110
+ const textParts = [];
111
+ for (const item of items) {
112
+ if (item.type === types_1.MessageItemType.TEXT && item.text_item?.text) {
113
+ textParts.push(item.text_item.text);
114
+ }
115
+ }
116
+ const text = textParts.join('').trim();
117
+ if (!text) {
118
+ this.onStatus(`\r\n[微信] 收到非文本消息: types=${items.map(i => i.type).join(',')}\r\n`);
119
+ return;
120
+ }
121
+ const meta = {
122
+ messageId: String(msg.message_id ?? msg.client_id ?? ''),
123
+ userId,
124
+ contextToken: msg.context_token,
125
+ };
126
+ this.onStatus(`\r\n[微信] 收到: ${text} | userId: ${userId}\r\n`);
127
+ this.onMessage(text, meta);
128
+ }
129
+ }
130
+ exports.WeixinClient = WeixinClient;
131
+ function sleep(ms, signal) {
132
+ return new Promise((resolve, reject) => {
133
+ const t = setTimeout(resolve, ms);
134
+ signal?.addEventListener('abort', () => { clearTimeout(t); reject(new Error('aborted')); }, { once: true });
135
+ });
136
+ }
@@ -0,0 +1,73 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.WeixinReplier = void 0;
4
+ const api_1 = require("./api");
5
+ const types_1 = require("./types");
6
+ class WeixinReplier {
7
+ constructor(accountId, logger) {
8
+ const account = (0, api_1.resolveAccount)(accountId);
9
+ this.accountId = account.accountId;
10
+ this.baseUrl = account.baseUrl;
11
+ this.token = account.token;
12
+ this.logger = logger ?? (() => { });
13
+ }
14
+ generateClientId() {
15
+ const ts = Date.now().toString(36);
16
+ const rand = Math.random().toString(36).slice(2, 10);
17
+ return `cc-remote-${ts}-${rand}`;
18
+ }
19
+ async sendText(userId, text, contextToken, refMessageId, retries = 2) {
20
+ if (!this.token) {
21
+ console.error('[微信] 未登录,无法发送消息');
22
+ return undefined;
23
+ }
24
+ for (let attempt = 0; attempt <= retries; attempt++) {
25
+ try {
26
+ this.logger(`[微信→] sendText(to=${userId}): ${text.slice(0, 200)}${text.length > 200 ? '…' : ''}`);
27
+ const clientId = this.generateClientId();
28
+ const msg = {
29
+ from_user_id: '',
30
+ to_user_id: userId,
31
+ client_id: clientId,
32
+ message_type: types_1.MessageType.BOT,
33
+ message_state: types_1.MessageState.FINISH,
34
+ item_list: text
35
+ ? [{ type: types_1.MessageItemType.TEXT, text_item: { text } }]
36
+ : undefined,
37
+ context_token: contextToken ?? undefined,
38
+ ...(refMessageId ? {
39
+ ref_msg: {
40
+ message_item: { msg_id: refMessageId },
41
+ title: '',
42
+ },
43
+ } : {}),
44
+ };
45
+ const req = { msg };
46
+ await (0, api_1.sendMessage)({
47
+ baseUrl: this.baseUrl,
48
+ token: this.token,
49
+ body: req,
50
+ });
51
+ return clientId;
52
+ }
53
+ catch (err) {
54
+ const errMsg = err instanceof Error ? err.message : String(err);
55
+ const isTransient = errMsg.includes('ECONN') || errMsg.includes('EOF') ||
56
+ errMsg.includes('timeout') || errMsg.includes('RESET') ||
57
+ errMsg.includes('AbortError');
58
+ if (attempt < retries && isTransient) {
59
+ this.logger(`[微信↻] sendText 重试 ${attempt + 1}/${retries}`);
60
+ await delay(300 * 2 ** attempt);
61
+ continue;
62
+ }
63
+ this.logger(`[微信] sendText 失败: ${errMsg}`);
64
+ return undefined;
65
+ }
66
+ }
67
+ return undefined;
68
+ }
69
+ }
70
+ exports.WeixinReplier = WeixinReplier;
71
+ function delay(ms) {
72
+ return new Promise((r) => setTimeout(r, ms));
73
+ }
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ // WeChat ilink API protocol types
3
+ // Based on @tencent-weixin/openclaw-weixin protocol
4
+ Object.defineProperty(exports, "__esModule", { value: true });
5
+ exports.MessageState = exports.MessageItemType = exports.MessageType = void 0;
6
+ exports.MessageType = { NONE: 0, USER: 1, BOT: 2 };
7
+ exports.MessageItemType = {
8
+ NONE: 0, TEXT: 1, IMAGE: 2, VOICE: 3, FILE: 4, VIDEO: 5,
9
+ };
10
+ exports.MessageState = { NEW: 0, GENERATING: 1, FINISH: 2 };
package/index.html ADDED
@@ -0,0 +1,32 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Claude Code</title>
7
+ <link rel="stylesheet" href="node_modules/@xterm/xterm/css/xterm.css">
8
+ <style>
9
+ * { margin: 0; padding: 0; box-sizing: border-box; }
10
+ html, body { height: 100%; overflow: hidden; background: #1e1e1e; }
11
+ #terminal { height: 100%; }
12
+ #search-bar {
13
+ position: fixed; top: 0; right: 0; z-index: 999;
14
+ display: none; padding: 6px 12px;
15
+ background: #333; color: #d4d4d4;
16
+ border: 1px solid #555; border-top: none; border-right: none;
17
+ font-family: "Cascadia Code", "Fira Code", Consolas, monospace;
18
+ font-size: 14px; outline: none; min-width: 250px;
19
+ }
20
+ #search-bar.visible { display: block; }
21
+ </style>
22
+ </head>
23
+ <body>
24
+ <input id="search-bar" type="text" placeholder="Find...">
25
+ <div id="terminal"></div>
26
+ <script src="node_modules/@xterm/xterm/lib/xterm.js"></script>
27
+ <script src="node_modules/@xterm/addon-fit/lib/addon-fit.js"></script>
28
+ <script src="node_modules/@xterm/addon-clipboard/lib/addon-clipboard.js"></script>
29
+ <script src="node_modules/@xterm/addon-search/lib/addon-search.js"></script>
30
+ <script src="dist/renderer.js"></script>
31
+ </body>
32
+ </html>
package/package.json ADDED
@@ -0,0 +1,85 @@
1
+ {
2
+ "name": "@bastdewfn/cc-remote",
3
+ "version": "1.0.9",
4
+ "description": "Claude Code 远程飞书微信控制终端",
5
+ "files": [
6
+ "dist/",
7
+ "bin/",
8
+ "commands/",
9
+ "index.html",
10
+ "config.example.json",
11
+ "scripts/",
12
+ "1.jpg",
13
+ "2.jpg"
14
+ ],
15
+ "main": "dist/main.js",
16
+ "bin": {
17
+ "cc-remote": "./bin/cc-remote.js"
18
+ },
19
+ "scripts": {
20
+ "build": "npx tsc",
21
+ "start": "npm run build && electron .",
22
+ "cli": "npm run build && node dist/cli.js",
23
+ "feishu": "node dist/index.js",
24
+ "pack:win": "npm run build && electron-builder --win",
25
+ "pack:mac": "npm run build && electron-builder --mac",
26
+ "pack": "npm run build && electron-builder",
27
+ "postinstall": "node scripts/patch-7za.js",
28
+ "test": "echo \"Error: no test specified\" && exit 1"
29
+ },
30
+ "build": {
31
+ "appId": "com.cc-remote",
32
+ "productName": "cc-remote",
33
+ "directories": {
34
+ "output": "release"
35
+ },
36
+ "files": [
37
+ "dist/**/*",
38
+ "index.html",
39
+ "commands/**/*",
40
+ "node_modules/**/*"
41
+ ],
42
+ "win": {
43
+ "target": "nsis"
44
+ },
45
+ "mac": {
46
+ "target": "dmg"
47
+ },
48
+ "nsis": {
49
+ "oneClick": false,
50
+ "allowToChangeInstallationDirectory": true
51
+ },
52
+ "npmRebuild": false
53
+ },
54
+ "keywords": [
55
+ "claude-code",
56
+ "feishu",
57
+ "lark",
58
+ "remote"
59
+ ],
60
+ "author": "wei.hu",
61
+ "license": "ISC",
62
+ "type": "commonjs",
63
+ "engines": {
64
+ "node": ">=18"
65
+ },
66
+ "dependencies": {
67
+ "@larksuiteoapi/node-sdk": "^1.63.1",
68
+ "@tencent-weixin/openclaw-weixin": "^2.4.3",
69
+ "@xterm/addon-clipboard": "^0.2.0",
70
+ "@xterm/addon-fit": "^0.11.0",
71
+ "@xterm/addon-search": "^0.16.0",
72
+ "@xterm/xterm": "^6.0.0",
73
+ "electron": "^42.0.1",
74
+ "@lydell/node-pty": "^1.2.0-beta.12",
75
+ "qrcode-terminal": "^0.12.0",
76
+ "strip-ansi": "^7.2.0"
77
+ },
78
+ "devDependencies": {
79
+ "@electron/rebuild": "^4.0.4",
80
+ "@types/node": "^25.7.0",
81
+ "electron-builder": "^26.8.1",
82
+ "ts-node": "^10.9.2",
83
+ "typescript": "^6.0.3"
84
+ }
85
+ }
@@ -0,0 +1,94 @@
1
+ // Patch 7za.exe to avoid symlink errors on Windows (no admin/Developer Mode).
2
+ // electron-builder's Go binary calls 7za with -snld, which requires symlink
3
+ // privileges on Windows. This wrapper replaces -snld with -snl- at the exe level.
4
+ const { execSync } = require('child_process');
5
+ const fs = require('fs');
6
+ const path = require('path');
7
+
8
+ if (process.platform !== 'win32') {
9
+ console.log('[patch-7za] Skipped (not Windows)');
10
+ process.exit(0);
11
+ }
12
+
13
+ const dir = path.join(__dirname, '..', 'node_modules', '7zip-bin', 'win', 'x64');
14
+ const exe = path.join(dir, '7za.exe');
15
+ const orig = path.join(dir, '7za_orig.exe');
16
+ const bak = path.join(dir, '7za_patched.txt');
17
+
18
+ // Already patched?
19
+ if (fs.existsSync(bak)) {
20
+ console.log('[patch-7za] Already patched');
21
+ process.exit(0);
22
+ }
23
+
24
+ if (!fs.existsSync(exe)) {
25
+ console.log('[patch-7za] 7za.exe not found, skipping');
26
+ process.exit(0);
27
+ }
28
+
29
+ // Check if already wrapper (small file)
30
+ const stat = fs.statSync(exe);
31
+ if (stat.size < 10000) {
32
+ console.log('[patch-7za] Already wrapper, marking patched');
33
+ fs.writeFileSync(bak, 'patched');
34
+ process.exit(0);
35
+ }
36
+
37
+ // Find CSC compiler
38
+ const cscPaths = [
39
+ 'C:/Windows/Microsoft.NET/Framework64/v4.0.30319/csc.exe',
40
+ 'C:/Windows/Microsoft.NET/Framework/v4.0.30319/csc.exe',
41
+ ];
42
+ let csc = null;
43
+ for (const p of cscPaths) {
44
+ if (fs.existsSync(p)) { csc = p; break; }
45
+ }
46
+
47
+ if (!csc) {
48
+ console.log('[patch-7za] CSC not found — NSIS builds may fail with symlink errors');
49
+ console.log('[patch-7za] Install .NET Framework 4.x or enable Windows Developer Mode');
50
+ process.exit(0);
51
+ }
52
+
53
+ const wrapperCs = path.join(dir, 'wrapper.cs');
54
+ fs.writeFileSync(wrapperCs, `
55
+ using System;
56
+ using System.Diagnostics;
57
+ using System.IO;
58
+ using System.Text;
59
+
60
+ class Wrapper {
61
+ static int Main(string[] args) {
62
+ var sb = new StringBuilder();
63
+ foreach (var a in args) {
64
+ var arg = a == "-snld" ? "-snl-" : a;
65
+ if (sb.Length > 0) sb.Append(' ');
66
+ if (arg.Contains(" ")) { sb.Append('"'); sb.Append(arg); sb.Append('"'); }
67
+ else sb.Append(arg);
68
+ }
69
+ var exeDir = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
70
+ var psi = new ProcessStartInfo {
71
+ FileName = Path.Combine(exeDir, "7za_orig.exe"),
72
+ Arguments = sb.ToString(),
73
+ UseShellExecute = false,
74
+ CreateNoWindow = true
75
+ };
76
+ var p = Process.Start(psi);
77
+ p.WaitForExit();
78
+ return p.ExitCode;
79
+ }
80
+ }
81
+ `.trim());
82
+
83
+ try {
84
+ fs.renameSync(exe, orig);
85
+ execSync(`"${csc}" -nologo -target:exe -out:"${exe}" "${wrapperCs}"`, { stdio: 'pipe' });
86
+ fs.unlinkSync(wrapperCs);
87
+ fs.writeFileSync(bak, 'patched');
88
+ console.log('[patch-7za] 7za.exe wrapper installed successfully');
89
+ } catch (e) {
90
+ console.error('[patch-7za] Failed:', e.message);
91
+ // Restore original
92
+ if (fs.existsSync(orig)) fs.renameSync(orig, exe);
93
+ process.exit(1);
94
+ }