@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.
- package/1.jpg +0 -0
- package/2.jpg +0 -0
- package/README.md +150 -0
- package/bin/cc-remote.js +183 -0
- package/commands/cc-remote-close.md +5 -0
- package/commands/cc-remote-doctor.md +20 -0
- package/commands/cc-remote-open.md +5 -0
- package/config.example.json +10 -0
- package/dist/cli.js +313 -0
- package/dist/commands.js +122 -0
- package/dist/config-setup.js +366 -0
- package/dist/core.js +453 -0
- package/dist/engine/events.js +78 -0
- package/dist/feishu/cards/base.js +114 -0
- package/dist/feishu/cards/help.js +33 -0
- package/dist/feishu/cards/live.js +65 -0
- package/dist/feishu/cards/session.js +59 -0
- package/dist/feishu/cards/status.js +60 -0
- package/dist/feishu/client.js +174 -0
- package/dist/feishu/replier.js +143 -0
- package/dist/feishu/router.js +61 -0
- package/dist/feishu-bot.js +139 -0
- package/dist/feishu-mode.js +62 -0
- package/dist/index.js +62 -0
- package/dist/main.js +397 -0
- package/dist/port.js +79 -0
- package/dist/preload.js +41 -0
- package/dist/pty.js +23 -0
- package/dist/relay.js +851 -0
- package/dist/renderer.js +318 -0
- package/dist/weixin/api.js +328 -0
- package/dist/weixin/client.js +136 -0
- package/dist/weixin/replier.js +73 -0
- package/dist/weixin/types.js +10 -0
- package/index.html +32 -0
- package/package.json +85 -0
- package/scripts/patch-7za.js +94 -0
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.replier = exports.feishuClient = void 0;
|
|
37
|
+
exports.startFeishuBot = startFeishuBot;
|
|
38
|
+
const fs = __importStar(require("fs"));
|
|
39
|
+
const path = __importStar(require("path"));
|
|
40
|
+
const lark = __importStar(require("@larksuiteoapi/node-sdk"));
|
|
41
|
+
const replier_1 = require("./feishu/replier");
|
|
42
|
+
let onStatus = () => { };
|
|
43
|
+
const _warn = console.warn.bind(console);
|
|
44
|
+
console.warn = (...args) => {
|
|
45
|
+
const msg = args.map(a => typeof a === 'string' ? a : String(a)).join(' ');
|
|
46
|
+
if (msg.includes('persistent connection') || msg.includes('ws') || msg.includes('[ '))
|
|
47
|
+
return;
|
|
48
|
+
_warn(...args);
|
|
49
|
+
};
|
|
50
|
+
const configPath = path.join(__dirname, '../config.json');
|
|
51
|
+
let config;
|
|
52
|
+
try {
|
|
53
|
+
config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
54
|
+
}
|
|
55
|
+
catch (err) {
|
|
56
|
+
console.error(`\r\n[飞书] 读取 config.json 失败: ${configPath}`, err);
|
|
57
|
+
throw err;
|
|
58
|
+
}
|
|
59
|
+
exports.feishuClient = new lark.Client({
|
|
60
|
+
appId: config.appId,
|
|
61
|
+
appSecret: config.appSecret,
|
|
62
|
+
});
|
|
63
|
+
exports.replier = new replier_1.Replier(exports.feishuClient);
|
|
64
|
+
function extractMeta(data) {
|
|
65
|
+
const msg = data.message;
|
|
66
|
+
if (!msg?.message_id || !msg.chat_id || msg.message_type !== 'text')
|
|
67
|
+
return undefined;
|
|
68
|
+
return {
|
|
69
|
+
messageId: msg.message_id,
|
|
70
|
+
chatId: msg.chat_id,
|
|
71
|
+
senderId: data.sender?.sender_id?.open_id ?? '',
|
|
72
|
+
mentionBot: Array.isArray(msg.mentions) && msg.mentions.length > 0,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
function extractText(data, meta) {
|
|
76
|
+
let raw = data.message?.content ?? '';
|
|
77
|
+
let parsed;
|
|
78
|
+
try {
|
|
79
|
+
parsed = JSON.parse(raw);
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
return '';
|
|
83
|
+
}
|
|
84
|
+
let text = parsed.text?.trim() ?? '';
|
|
85
|
+
if (meta.mentionBot && Array.isArray(data.message?.mentions)) {
|
|
86
|
+
for (const m of data.message.mentions) {
|
|
87
|
+
if (m.key)
|
|
88
|
+
text = text.split(m.key).join('');
|
|
89
|
+
}
|
|
90
|
+
text = text.trim();
|
|
91
|
+
}
|
|
92
|
+
return text;
|
|
93
|
+
}
|
|
94
|
+
function startFeishuBot(onMessage, _onStatus, _onDisconnect) {
|
|
95
|
+
if (_onStatus)
|
|
96
|
+
onStatus = _onStatus;
|
|
97
|
+
onStatus(`\r\n飞书机器人启动中...\r\n`);
|
|
98
|
+
const eventDispatcher = new lark.EventDispatcher({
|
|
99
|
+
appId: config.appId,
|
|
100
|
+
appSecret: config.appSecret,
|
|
101
|
+
}).register({
|
|
102
|
+
'im.message.receive_v1': async (data) => {
|
|
103
|
+
const msg = data.message;
|
|
104
|
+
if (msg.message_type && msg.message_type !== 'text') {
|
|
105
|
+
onStatus(`\r\n[飞书] 收到非文本消息: ${msg.message_type}\r\n`);
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
const meta = extractMeta(data);
|
|
109
|
+
if (!meta)
|
|
110
|
+
return;
|
|
111
|
+
const text = extractText(data, meta);
|
|
112
|
+
if (!text)
|
|
113
|
+
return;
|
|
114
|
+
onStatus(`\r\n[飞书] 收到: ${text} | chatId: ${meta.chatId}\r\n`);
|
|
115
|
+
if (text === '收到')
|
|
116
|
+
return;
|
|
117
|
+
onMessage(text, meta);
|
|
118
|
+
},
|
|
119
|
+
});
|
|
120
|
+
const ws = new lark.WSClient({
|
|
121
|
+
appId: config.appId,
|
|
122
|
+
appSecret: config.appSecret,
|
|
123
|
+
});
|
|
124
|
+
let reconnectTimer = null;
|
|
125
|
+
let isRunning = true;
|
|
126
|
+
function startWithRetry() {
|
|
127
|
+
if (!isRunning)
|
|
128
|
+
return;
|
|
129
|
+
ws.start({ eventDispatcher }).then(() => {
|
|
130
|
+
onStatus(`\r\n✅ 飞书机器人已连接\r\n`);
|
|
131
|
+
}).catch((err) => {
|
|
132
|
+
onStatus(`\r\n⚠️ 飞书连接断开: ${err},3秒后重连...\r\n`);
|
|
133
|
+
if (_onDisconnect)
|
|
134
|
+
_onDisconnect();
|
|
135
|
+
reconnectTimer = setTimeout(startWithRetry, 3000);
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
startWithRetry();
|
|
139
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
const fs = __importStar(require("fs"));
|
|
37
|
+
const path = __importStar(require("path"));
|
|
38
|
+
const mode = process.argv[2];
|
|
39
|
+
if (mode !== 'remote' && mode !== 'close') {
|
|
40
|
+
console.error('Usage: node feishu-mode.js <remote|close>');
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
const targetDir = process.cwd();
|
|
44
|
+
const claudeDir = path.join(targetDir, '.claude');
|
|
45
|
+
if (!fs.existsSync(claudeDir))
|
|
46
|
+
fs.mkdirSync(claudeDir, { recursive: true });
|
|
47
|
+
const contextPath = path.join(claudeDir, 'cc_remote_context.json');
|
|
48
|
+
let context = {};
|
|
49
|
+
if (fs.existsSync(contextPath)) {
|
|
50
|
+
try {
|
|
51
|
+
context = JSON.parse(fs.readFileSync(contextPath, 'utf-8'));
|
|
52
|
+
}
|
|
53
|
+
catch { }
|
|
54
|
+
}
|
|
55
|
+
context.mode = mode;
|
|
56
|
+
fs.writeFileSync(contextPath, JSON.stringify(context));
|
|
57
|
+
if (mode === 'remote') {
|
|
58
|
+
console.log('🔗 飞书远程模式已启用');
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
console.log('🔌 飞书远程模式已关闭');
|
|
62
|
+
}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
const fs = __importStar(require("fs"));
|
|
37
|
+
const os = __importStar(require("os"));
|
|
38
|
+
const path = __importStar(require("path"));
|
|
39
|
+
const client_1 = require("./feishu/client");
|
|
40
|
+
const replier_1 = require("./feishu/replier");
|
|
41
|
+
const configPath = fs.existsSync(path.join(os.homedir(), '.cc-remote', 'config.json'))
|
|
42
|
+
? path.join(os.homedir(), '.cc-remote', 'config.json')
|
|
43
|
+
: path.join(__dirname, '../config.json');
|
|
44
|
+
let cfg;
|
|
45
|
+
try {
|
|
46
|
+
cfg = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
47
|
+
}
|
|
48
|
+
catch (err) {
|
|
49
|
+
console.error('\r\n[feishu] 读取 config.json 失败:', configPath, err);
|
|
50
|
+
console.error('[feishu] 请创建 ~/.cc-remote/config.json 并配置 appId/appSecret');
|
|
51
|
+
process.exit(1);
|
|
52
|
+
}
|
|
53
|
+
const feishuClient = (0, client_1.createFeishuClient)(cfg);
|
|
54
|
+
const feishuReplier = new replier_1.Replier(feishuClient, (msg) => console.log(msg));
|
|
55
|
+
const feishuWs = (0, client_1.createWsClient)(cfg);
|
|
56
|
+
(0, client_1.startFeishuBot)(feishuClient, feishuWs, {
|
|
57
|
+
onMessage: (text, meta) => {
|
|
58
|
+
console.log(`\r\n[飞书] 收到: "${text}" | chatId: ${meta.chatId}`);
|
|
59
|
+
feishuReplier.replyText(meta.messageId, text);
|
|
60
|
+
},
|
|
61
|
+
onStatus: (msg) => console.log(msg.replace(/\r?\n/g, '')),
|
|
62
|
+
});
|
package/dist/main.js
ADDED
|
@@ -0,0 +1,397 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
const electron_1 = require("electron");
|
|
40
|
+
const fs = __importStar(require("fs"));
|
|
41
|
+
const path = __importStar(require("path"));
|
|
42
|
+
const pty_1 = require("./pty");
|
|
43
|
+
const http = __importStar(require("http"));
|
|
44
|
+
const https = __importStar(require("https"));
|
|
45
|
+
const strip_ansi_1 = __importDefault(require("strip-ansi"));
|
|
46
|
+
const core_1 = require("./core");
|
|
47
|
+
const relay_1 = require("./relay");
|
|
48
|
+
const commands_1 = require("./commands");
|
|
49
|
+
const client_1 = require("./feishu/client");
|
|
50
|
+
const replier_1 = require("./feishu/replier");
|
|
51
|
+
const client_2 = require("./weixin/client");
|
|
52
|
+
const replier_2 = require("./weixin/replier");
|
|
53
|
+
const port_1 = require("./port");
|
|
54
|
+
// ---- Keep-Alive agent ----
|
|
55
|
+
try {
|
|
56
|
+
https.globalAgent = new https.Agent({ keepAlive: true, maxSockets: 20, keepAliveMsecs: 30000 });
|
|
57
|
+
http.globalAgent = new http.Agent({ keepAlive: true, maxSockets: 20, keepAliveMsecs: 30000 });
|
|
58
|
+
}
|
|
59
|
+
catch { /* read-only in some environments */ }
|
|
60
|
+
// ---- Config ----
|
|
61
|
+
const cfg = (0, core_1.loadConfig)();
|
|
62
|
+
// ---- State ----
|
|
63
|
+
let mainWindow = null;
|
|
64
|
+
let ptyProcess = null;
|
|
65
|
+
let feishuReplier;
|
|
66
|
+
let weixinClient = null;
|
|
67
|
+
let weixinReplier = null;
|
|
68
|
+
let relay;
|
|
69
|
+
let feishuStop = null;
|
|
70
|
+
// Diagnostics state
|
|
71
|
+
let wsConnected = false;
|
|
72
|
+
let wsConnectTime = 0;
|
|
73
|
+
let lastMessageTime = 0;
|
|
74
|
+
let lastMessageText = '';
|
|
75
|
+
let lastSendError = '';
|
|
76
|
+
let lastSendErrorTime = 0;
|
|
77
|
+
// ---- Parse args ----
|
|
78
|
+
const rawArgs = process.argv.slice(electron_1.app.isPackaged ? 1 : 2);
|
|
79
|
+
let targetDir = process.cwd();
|
|
80
|
+
const claudeArgs = [];
|
|
81
|
+
for (let i = 0; i < rawArgs.length; i++) {
|
|
82
|
+
if (rawArgs[i] === '--project' && i + 1 < rawArgs.length) {
|
|
83
|
+
targetDir = rawArgs[i + 1];
|
|
84
|
+
i++;
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
claudeArgs.push(rawArgs[i]);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
// ---- onStatus: IPC to renderer + log file ----
|
|
91
|
+
function sendStatus(text) {
|
|
92
|
+
if (mainWindow && !mainWindow.isDestroyed()) {
|
|
93
|
+
mainWindow.webContents.send('status:log', text.replace(/\r?\n$/, ''));
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
function onStatus(text) {
|
|
97
|
+
// Write to relay log file
|
|
98
|
+
const date = new Date().toISOString().slice(0, 10).replace(/-/g, '');
|
|
99
|
+
const logFile = (0, core_1.dataPath)(targetDir, `relay-${date}.log`);
|
|
100
|
+
try {
|
|
101
|
+
fs.appendFileSync(logFile, `[${new Date().toISOString()}] ${text}\n`);
|
|
102
|
+
}
|
|
103
|
+
catch { /* ignore */ }
|
|
104
|
+
// Send to Electron renderer
|
|
105
|
+
sendStatus(text);
|
|
106
|
+
}
|
|
107
|
+
const dataFile = (filename) => (0, core_1.dataPath)(targetDir, filename);
|
|
108
|
+
// ---- Message log (dedicated) ----
|
|
109
|
+
function messageLog(msg) {
|
|
110
|
+
const date = new Date().toISOString().slice(0, 10).replace(/-/g, '');
|
|
111
|
+
const logFile = (0, core_1.dataPath)(targetDir, `messages-${date}.log`);
|
|
112
|
+
try {
|
|
113
|
+
fs.appendFileSync(logFile, `[${new Date().toISOString()}] ${msg}\n`);
|
|
114
|
+
}
|
|
115
|
+
catch { /* ignore */ }
|
|
116
|
+
}
|
|
117
|
+
// Combined logger: status + message log
|
|
118
|
+
function logAll(msg) {
|
|
119
|
+
onStatus(msg);
|
|
120
|
+
// Also log send operations to message log if they look like send ops
|
|
121
|
+
if (msg.includes('[飞书→]') || msg.includes('[微信→]') || msg.includes('[飞书↻]') || msg.includes('[微信↻]')) {
|
|
122
|
+
messageLog(msg);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
// ---- Feishu ----
|
|
126
|
+
const feishuClient = (0, client_1.createFeishuClient)(cfg);
|
|
127
|
+
feishuReplier = new replier_1.Replier(feishuClient, logAll);
|
|
128
|
+
// ---- WeChat ----
|
|
129
|
+
const weixinEnabled = cfg.enableWeixin === true;
|
|
130
|
+
if (weixinEnabled) {
|
|
131
|
+
weixinClient = new client_2.WeixinClient({
|
|
132
|
+
onMessage: () => { }, // wired after handler creation
|
|
133
|
+
onStatus: (text) => sendStatus(text),
|
|
134
|
+
});
|
|
135
|
+
weixinReplier = new replier_2.WeixinReplier(weixinClient.getAccountId(), onStatus);
|
|
136
|
+
}
|
|
137
|
+
// Config path tracking for doctor
|
|
138
|
+
const userConfigPath = path.join(require('os').homedir(), '.cc-remote', 'config.json');
|
|
139
|
+
const localConfigPath = path.join(__dirname, '../config.json');
|
|
140
|
+
const configPathStr = fs.existsSync(userConfigPath) ? userConfigPath : localConfigPath;
|
|
141
|
+
// ---- Relay ----
|
|
142
|
+
relay = new relay_1.CoreRelay({
|
|
143
|
+
config: cfg,
|
|
144
|
+
configPath: configPathStr,
|
|
145
|
+
targetDir,
|
|
146
|
+
feishuReplier,
|
|
147
|
+
weixinReplier,
|
|
148
|
+
onStatus,
|
|
149
|
+
});
|
|
150
|
+
// ---- Electron-specific functions ----
|
|
151
|
+
function onPtyData(data) {
|
|
152
|
+
mainWindow?.webContents.send('pty:data', data);
|
|
153
|
+
}
|
|
154
|
+
async function captureTerminalSnapshot(mode) {
|
|
155
|
+
if (!mainWindow || mainWindow.isDestroyed())
|
|
156
|
+
return '';
|
|
157
|
+
try {
|
|
158
|
+
if (mode.type === 'last') {
|
|
159
|
+
const content = await mainWindow.webContents.executeJavaScript(`getLastLines(${mode.count})`);
|
|
160
|
+
return typeof content === 'string' ? content : '';
|
|
161
|
+
}
|
|
162
|
+
const content = await mainWindow.webContents.executeJavaScript('getSnapshotContent()');
|
|
163
|
+
return typeof content === 'string' ? content : '';
|
|
164
|
+
}
|
|
165
|
+
catch {
|
|
166
|
+
return '';
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
async function handleSnapshot(mode, channel, meta) {
|
|
170
|
+
let content = await captureTerminalSnapshot(mode);
|
|
171
|
+
content = (0, strip_ansi_1.default)(content);
|
|
172
|
+
if (!content)
|
|
173
|
+
content = '(终端缓冲区为空)';
|
|
174
|
+
const label = mode.type === 'last' ? `快照${mode.count}` : '快照';
|
|
175
|
+
if (channel === 'feishu' && meta.chatId) {
|
|
176
|
+
// 富文本: 用飞书 post 消息,分行展示
|
|
177
|
+
const lines = content.split('\n').filter(l => l.trim());
|
|
178
|
+
const sent = await feishuReplier.sendPost(meta.chatId, label, lines);
|
|
179
|
+
sendStatus(sent
|
|
180
|
+
? `\r\n[${label}] 已发送到飞书 (${content.length} 字符)\r\n`
|
|
181
|
+
: `\r\n[${label}] 飞书发送失败\r\n`);
|
|
182
|
+
}
|
|
183
|
+
else if (channel === 'weixin' && meta.userId) {
|
|
184
|
+
const sent = await weixinReplier?.sendText(meta.userId, content, meta.contextToken);
|
|
185
|
+
sendStatus(sent
|
|
186
|
+
? `\r\n[${label}] 已发送到微信 (${content.length} 字符)\r\n`
|
|
187
|
+
: `\r\n[${label}] 微信发送失败\r\n`);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
// ---- Message handlers ----
|
|
191
|
+
const handleFeishu = (0, core_1.createFeishuHandler)({
|
|
192
|
+
getPty: () => ptyProcess,
|
|
193
|
+
feishuReplier,
|
|
194
|
+
onStatus,
|
|
195
|
+
dataFile,
|
|
196
|
+
openId: cfg.openId,
|
|
197
|
+
onSnapshot: handleSnapshot,
|
|
198
|
+
onMessageReceived: (text) => {
|
|
199
|
+
lastMessageTime = Date.now();
|
|
200
|
+
lastMessageText = text;
|
|
201
|
+
},
|
|
202
|
+
messageLog,
|
|
203
|
+
});
|
|
204
|
+
const handleWeixin = weixinReplier ? (0, core_1.createWeixinHandler)({
|
|
205
|
+
getPty: () => ptyProcess,
|
|
206
|
+
weixinReplier,
|
|
207
|
+
onStatus,
|
|
208
|
+
dataFile,
|
|
209
|
+
wxPendingApprovals: relay.wxPendingApprovals,
|
|
210
|
+
onSnapshot: handleSnapshot,
|
|
211
|
+
onMessageReceived: (text) => {
|
|
212
|
+
lastMessageTime = Date.now();
|
|
213
|
+
lastMessageText = text;
|
|
214
|
+
},
|
|
215
|
+
messageLog,
|
|
216
|
+
}) : null;
|
|
217
|
+
// Wire WeChat message handler
|
|
218
|
+
if (weixinClient && handleWeixin) {
|
|
219
|
+
weixinClient.onMessage = handleWeixin;
|
|
220
|
+
}
|
|
221
|
+
// ---- PTY lifecycle ----
|
|
222
|
+
function cleanupPty() {
|
|
223
|
+
relay.stop();
|
|
224
|
+
weixinClient?.stop().catch(() => { });
|
|
225
|
+
if (ptyProcess) {
|
|
226
|
+
ptyProcess.kill();
|
|
227
|
+
ptyProcess = null;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
// ---- Window ----
|
|
231
|
+
async function createWindow() {
|
|
232
|
+
electron_1.Menu.setApplicationMenu(null);
|
|
233
|
+
mainWindow = new electron_1.BrowserWindow({
|
|
234
|
+
width: 900,
|
|
235
|
+
height: 600,
|
|
236
|
+
webPreferences: {
|
|
237
|
+
preload: path.join(__dirname, 'preload.js'),
|
|
238
|
+
contextIsolation: true,
|
|
239
|
+
nodeIntegration: false,
|
|
240
|
+
sandbox: false,
|
|
241
|
+
},
|
|
242
|
+
});
|
|
243
|
+
mainWindow.webContents.on('before-input-event', (event, input) => {
|
|
244
|
+
if (input.key === 'F5' && !input.control && !input.meta && !input.alt && !input.shift) {
|
|
245
|
+
event.preventDefault();
|
|
246
|
+
mainWindow?.webContents.reload();
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
const blocked = ['r', 'R', 'i', 'I', 'n', 'N', 'w', 'W', 'q', 'Q', 't', 'T',
|
|
250
|
+
'j', 'J', 'p', 'P', 'u', 'U', '0', '=', '-', '+', 'o', 'O'];
|
|
251
|
+
if ((input.control || input.meta) && blocked.includes(input.key)) {
|
|
252
|
+
event.preventDefault();
|
|
253
|
+
}
|
|
254
|
+
});
|
|
255
|
+
mainWindow.loadFile(path.join(__dirname, '../index.html'));
|
|
256
|
+
mainWindow.on('closed', () => {
|
|
257
|
+
cleanupPty();
|
|
258
|
+
mainWindow = null;
|
|
259
|
+
});
|
|
260
|
+
// ---- PTY ----
|
|
261
|
+
try {
|
|
262
|
+
const claudeExe = cfg.claudePath?.trim() || (process.platform === 'darwin' ? 'claude' : 'claude.exe');
|
|
263
|
+
console.log(`[main] 启动: ${claudeExe} ${claudeArgs.join(' ')}`);
|
|
264
|
+
console.log(`[main] 目录: ${targetDir}`);
|
|
265
|
+
console.log(`[main] ENV PATH: ${process.env.PATH || '(空)'}`);
|
|
266
|
+
ptyProcess = (0, pty_1.spawnPty)(claudeExe, claudeArgs, {
|
|
267
|
+
name: 'xterm-256color',
|
|
268
|
+
cols: 80,
|
|
269
|
+
rows: 24,
|
|
270
|
+
cwd: targetDir,
|
|
271
|
+
encoding: 'utf-8',
|
|
272
|
+
env: {
|
|
273
|
+
LANG: 'zh_CN.UTF-8',
|
|
274
|
+
LC_ALL: 'zh_CN.UTF-8',
|
|
275
|
+
LC_CTYPE: 'zh_CN.UTF-8',
|
|
276
|
+
},
|
|
277
|
+
});
|
|
278
|
+
ptyProcess.onData((data) => {
|
|
279
|
+
onPtyData(data);
|
|
280
|
+
});
|
|
281
|
+
ptyProcess.onExit(({ exitCode }) => {
|
|
282
|
+
mainWindow?.webContents.send('pty:exit', { code: exitCode });
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
catch (err) {
|
|
286
|
+
mainWindow?.webContents.send('pty:data', `\r\n[Error: ${err}]\r\n`);
|
|
287
|
+
}
|
|
288
|
+
// ---- IPC ----
|
|
289
|
+
electron_1.ipcMain.on('pty:input', (_event, data) => {
|
|
290
|
+
ptyProcess?.write(data);
|
|
291
|
+
});
|
|
292
|
+
electron_1.ipcMain.on('pty:resize', (_event, { cols, rows }) => {
|
|
293
|
+
ptyProcess?.resize(cols, rows);
|
|
294
|
+
});
|
|
295
|
+
electron_1.ipcMain.on('window:toggleFullscreen', () => {
|
|
296
|
+
if (mainWindow) {
|
|
297
|
+
mainWindow.setFullScreen(!mainWindow.isFullScreen());
|
|
298
|
+
}
|
|
299
|
+
});
|
|
300
|
+
electron_1.ipcMain.on('window:openDevTools', () => {
|
|
301
|
+
if (mainWindow) {
|
|
302
|
+
mainWindow.webContents.openDevTools({ mode: 'detach' });
|
|
303
|
+
}
|
|
304
|
+
});
|
|
305
|
+
// ---- Startup relay + commands + bots ----
|
|
306
|
+
(0, commands_1.registerFeishuCommands)(targetDir, cfg.relayPort || 19200);
|
|
307
|
+
const relayPort = cfg.relayPort || 19200;
|
|
308
|
+
const portFree = await (0, port_1.checkPortFree)(relayPort);
|
|
309
|
+
if (!portFree) {
|
|
310
|
+
const choice = electron_1.dialog.showMessageBoxSync({
|
|
311
|
+
type: 'warning',
|
|
312
|
+
title: '端口冲突',
|
|
313
|
+
message: `端口 ${relayPort} 已被占用`,
|
|
314
|
+
detail: '请选择处理方式',
|
|
315
|
+
buttons: ['关闭占用进程并继续启动', '退出'],
|
|
316
|
+
defaultId: 0,
|
|
317
|
+
cancelId: 1,
|
|
318
|
+
noLink: true,
|
|
319
|
+
});
|
|
320
|
+
if (choice === 1) {
|
|
321
|
+
electron_1.app.quit();
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
const killedPid = (0, port_1.killPortProcess)(relayPort);
|
|
325
|
+
if (killedPid) {
|
|
326
|
+
sendStatus(`\r\n✅ 已关闭占用进程 PID:${killedPid},继续启动...\r\n`);
|
|
327
|
+
}
|
|
328
|
+
else {
|
|
329
|
+
electron_1.dialog.showErrorBox('错误', `无法关闭占用端口 ${relayPort} 的进程,请手动处理`);
|
|
330
|
+
electron_1.app.quit();
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
relay.start(cfg.relayPort);
|
|
335
|
+
if (cfg.enableFeishu !== false) {
|
|
336
|
+
const feishuWs = (0, client_1.createWsClient)(cfg);
|
|
337
|
+
feishuStop = (0, client_1.startFeishuBot)(feishuClient, feishuWs, {
|
|
338
|
+
onMessage: handleFeishu,
|
|
339
|
+
onStatus: (text) => {
|
|
340
|
+
if (text.includes('已连接')) {
|
|
341
|
+
wsConnected = true;
|
|
342
|
+
wsConnectTime = Date.now();
|
|
343
|
+
}
|
|
344
|
+
if (text.includes('连接断开')) {
|
|
345
|
+
wsConnected = false;
|
|
346
|
+
}
|
|
347
|
+
sendStatus(text);
|
|
348
|
+
},
|
|
349
|
+
cardAction: (raw) => relay.handleCardAction(raw),
|
|
350
|
+
}).stop;
|
|
351
|
+
}
|
|
352
|
+
else {
|
|
353
|
+
sendStatus('\r\n⏸️ 飞书频道已禁用 (enable_feishu: false)\r\n');
|
|
354
|
+
}
|
|
355
|
+
if (weixinEnabled && weixinClient) {
|
|
356
|
+
weixinClient.start().catch((err) => {
|
|
357
|
+
sendStatus(`\r\n[微信] 启动失败: ${err}\r\n`);
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
else {
|
|
361
|
+
sendStatus('\r\n⏸️ 微信频道已禁用 (enable_weixin: false)\r\n');
|
|
362
|
+
}
|
|
363
|
+
// Update relay diagnostics periodically
|
|
364
|
+
function updateDiag() {
|
|
365
|
+
relay.diag.wsConnected = wsConnected;
|
|
366
|
+
relay.diag.wsConnectTime = wsConnectTime;
|
|
367
|
+
relay.diag.lastMessageTime = lastMessageTime;
|
|
368
|
+
relay.diag.lastMessageText = lastMessageText;
|
|
369
|
+
relay.diag.lastSendError = lastSendError;
|
|
370
|
+
relay.diag.lastSendErrorTime = lastSendErrorTime;
|
|
371
|
+
relay.diag.ptyPid = ptyProcess?.pid;
|
|
372
|
+
relay.diag.ptyAlive = ptyProcess !== null && ptyProcess.pid > 0;
|
|
373
|
+
relay.diag.weixinEnabled = weixinEnabled;
|
|
374
|
+
relay.diag.weixinClientStarted = weixinClient !== null;
|
|
375
|
+
relay.diag.weixinAccountId = weixinClient ? weixinClient.accountId || null : null;
|
|
376
|
+
}
|
|
377
|
+
setInterval(updateDiag, 5000);
|
|
378
|
+
}
|
|
379
|
+
// ---- Crash handlers: ensure clean shutdown on abnormal exit ----
|
|
380
|
+
process.on('uncaughtException', (err) => {
|
|
381
|
+
console.error('[cc-remote] 未捕获异常:', err.message || err);
|
|
382
|
+
cleanupPty();
|
|
383
|
+
electron_1.app.quit();
|
|
384
|
+
});
|
|
385
|
+
process.on('unhandledRejection', (reason) => {
|
|
386
|
+
console.error('[cc-remote] 未处理的 Promise 拒绝:', reason);
|
|
387
|
+
});
|
|
388
|
+
// ---- App lifecycle ----
|
|
389
|
+
electron_1.app.whenReady().then(createWindow);
|
|
390
|
+
electron_1.app.on('window-all-closed', () => {
|
|
391
|
+
cleanupPty();
|
|
392
|
+
electron_1.app.quit();
|
|
393
|
+
});
|
|
394
|
+
electron_1.app.on('will-quit', () => {
|
|
395
|
+
relay.stop();
|
|
396
|
+
cleanupPty();
|
|
397
|
+
});
|