@feng3d/ctc 0.0.19 → 0.0.20
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/admin-server.d.ts +6 -0
- package/dist/admin-server.d.ts.map +1 -1
- package/dist/admin-server.js +19 -0
- package/dist/admin-server.js.map +1 -1
- package/dist/app.d.ts +17 -0
- package/dist/app.d.ts.map +1 -0
- package/dist/app.js +97 -0
- package/dist/app.js.map +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +152 -253
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +1 -5
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +0 -8
- package/dist/config.js.map +1 -1
- package/dist/controller.d.ts.map +1 -1
- package/dist/controller.js +8 -0
- package/dist/controller.js.map +1 -1
- package/dist/daemon-entry.d.ts +8 -0
- package/dist/daemon-entry.d.ts.map +1 -0
- package/dist/daemon-entry.js +15 -0
- package/dist/daemon-entry.js.map +1 -0
- package/dist/daemon.d.ts +32 -0
- package/dist/daemon.d.ts.map +1 -0
- package/dist/daemon.js +370 -0
- package/dist/daemon.js.map +1 -0
- package/dist/forward-proxy.js +1 -1
- package/dist/forward-proxy.js.map +1 -1
- package/dist/index.d.ts +16 -14
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +14 -326
- package/dist/index.js.map +1 -1
- package/dist/ipc-handler.d.ts +76 -4
- package/dist/ipc-handler.d.ts.map +1 -1
- package/dist/ipc-handler.js +221 -26
- package/dist/ipc-handler.js.map +1 -1
- package/dist/process-utils.d.ts +31 -0
- package/dist/process-utils.d.ts.map +1 -0
- package/dist/process-utils.js +87 -0
- package/dist/process-utils.js.map +1 -0
- package/dist/proxy-manager.d.ts.map +1 -1
- package/dist/proxy-manager.js +45 -0
- package/dist/proxy-manager.js.map +1 -1
- package/package.json +7 -3
package/dist/cli.js
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
* npx @feng3d/cts restart # 重启客户端
|
|
10
10
|
* npx @feng3d/cts status # 查看状态
|
|
11
11
|
* npx @feng3d/cts proxies # 管理代理映射
|
|
12
|
+
* npx @feng3d/cts forward # 管理正向穿透
|
|
12
13
|
* npx @feng3d/cts config # 管理配置
|
|
13
14
|
* npx @feng3d/cts boot # 管理开机自启动
|
|
14
15
|
* npx @feng3d/cts logs # 查看日志
|
|
@@ -23,8 +24,43 @@ import { spawn, execSync } from 'child_process';
|
|
|
23
24
|
import { fileURLToPath } from 'url';
|
|
24
25
|
import { getActiveBootLevel, hasSystemPrivileges } from '@feng3d/chuantou-shared';
|
|
25
26
|
import { registerBoot, unregisterBoot, isBootRegistered } from '@feng3d/chuantou-shared';
|
|
27
|
+
import { killProcess } from './process-utils.js';
|
|
26
28
|
/** 数据目录 */
|
|
27
29
|
const DATA_DIR = join(homedir(), '.chuantou');
|
|
30
|
+
/** IPC 请求目录 */
|
|
31
|
+
const IPC_REQUEST_DIR = join(homedir(), '.chuantou', 'proxy-requests');
|
|
32
|
+
/**
|
|
33
|
+
* 通过 IPC 向守护进程发送请求
|
|
34
|
+
* @param type - 请求类型
|
|
35
|
+
* @param payload - 请求负载
|
|
36
|
+
* @param timeout - 超时时间(毫秒)
|
|
37
|
+
* @returns 响应数据
|
|
38
|
+
*/
|
|
39
|
+
async function sendIpcRequest(type, payload = {}, timeout = 5000) {
|
|
40
|
+
const requestId = `${Date.now()}_${Math.random().toString(36).substring(7)}`;
|
|
41
|
+
const requestPath = join(IPC_REQUEST_DIR, `${requestId}.json`);
|
|
42
|
+
const responsePath = join(IPC_REQUEST_DIR, `${requestId}.resp`);
|
|
43
|
+
try {
|
|
44
|
+
// 写入请求文件
|
|
45
|
+
mkdirSync(IPC_REQUEST_DIR, { recursive: true });
|
|
46
|
+
writeFileSync(requestPath, JSON.stringify({ type, ...payload }));
|
|
47
|
+
// 轮询响应文件
|
|
48
|
+
const startTime = Date.now();
|
|
49
|
+
while (Date.now() - startTime < timeout) {
|
|
50
|
+
if (existsSync(responsePath)) {
|
|
51
|
+
const response = JSON.parse(readFileSync(responsePath, 'utf-8'));
|
|
52
|
+
// 删除响应文件
|
|
53
|
+
unlinkSync(responsePath);
|
|
54
|
+
return response;
|
|
55
|
+
}
|
|
56
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
57
|
+
}
|
|
58
|
+
return { success: false, error: '请求超时' };
|
|
59
|
+
}
|
|
60
|
+
catch (err) {
|
|
61
|
+
return { success: false, error: err.message };
|
|
62
|
+
}
|
|
63
|
+
}
|
|
28
64
|
/** 客户端目录 */
|
|
29
65
|
const CLIENT_DIR = join(DATA_DIR, 'client');
|
|
30
66
|
/** 配置文件 */
|
|
@@ -64,7 +100,6 @@ ${chalk.bold('start 命令选项:')}
|
|
|
64
100
|
${chalk.yellow('-t, --token <token>')} 认证令牌
|
|
65
101
|
${chalk.yellow('-p, --proxies <proxies>')} 代理配置 (如: 8080:3000,8081:3001)
|
|
66
102
|
${chalk.yellow('--no-boot')} 不注册开机自启动
|
|
67
|
-
${chalk.yellow('-o, --open')} 启动后打开管理页面
|
|
68
103
|
|
|
69
104
|
${chalk.bold('proxies 命令(反向代理):')}
|
|
70
105
|
${chalk.yellow('list')} 列出所有代理映射
|
|
@@ -91,7 +126,6 @@ ${chalk.bold('boot 命令选项:')}
|
|
|
91
126
|
|
|
92
127
|
${chalk.dim('配置文件位置:')} ${CONFIG_FILE}
|
|
93
128
|
${chalk.dim('日志文件位置:')} ${LOG_FILE}
|
|
94
|
-
${chalk.dim('管理页面:')} http://127.0.0.1:9001
|
|
95
129
|
|
|
96
130
|
${chalk.dim('示例:')}
|
|
97
131
|
${chalk.gray('# 启动客户端')}
|
|
@@ -212,8 +246,8 @@ async function getClientStatus(adminPort = ADMIN_PORT_DEFAULT) {
|
|
|
212
246
|
* 脚本路径
|
|
213
247
|
*/
|
|
214
248
|
const cliPath = fileURLToPath(import.meta.url);
|
|
215
|
-
//
|
|
216
|
-
const
|
|
249
|
+
// daemon-entry.js 是守护进程入口
|
|
250
|
+
const daemonEntryPath = join(dirname(cliPath), 'daemon-entry.js');
|
|
217
251
|
const nodePath = process.execPath;
|
|
218
252
|
/**
|
|
219
253
|
* 主程序入口
|
|
@@ -237,9 +271,7 @@ const startCmd = program.command('start')
|
|
|
237
271
|
.option('-s, --server <url>', '服务器地址')
|
|
238
272
|
.option('-t, --token <token>', '认证令牌')
|
|
239
273
|
.option('-p, --proxies <proxies>', '代理配置(格式:remotePort:localPort[:localHost],多个用逗号分隔)')
|
|
240
|
-
.option('--admin-port <port>', '管理页面端口(默认:9001)')
|
|
241
274
|
.option('--no-boot', '不注册开机自启动')
|
|
242
|
-
.option('-o, --open', '启动后打开管理页面')
|
|
243
275
|
.action(async (options) => {
|
|
244
276
|
// 检查是否已在运行
|
|
245
277
|
if (isClientRunning()) {
|
|
@@ -314,31 +346,20 @@ const startCmd = program.command('start')
|
|
|
314
346
|
serveArgs.push('--server', serverUrl);
|
|
315
347
|
if (token)
|
|
316
348
|
serveArgs.push('--token', token);
|
|
317
|
-
const adminPort = options.adminPort ? parseInt(options.adminPort, 10) : ADMIN_PORT_DEFAULT;
|
|
318
|
-
serveArgs.push('--admin-port', adminPort.toString());
|
|
319
349
|
// 确保目录存在并打开日志文件
|
|
320
350
|
mkdirSync(CLIENT_DIR, { recursive: true });
|
|
321
351
|
const logFd = openSync(LOG_FILE, 'a');
|
|
322
352
|
// 启动守护进程
|
|
323
|
-
const child = spawn(nodePath, [
|
|
353
|
+
const child = spawn(nodePath, [daemonEntryPath, ...serveArgs], {
|
|
324
354
|
detached: true,
|
|
325
355
|
stdio: ['ignore', logFd, logFd],
|
|
326
356
|
});
|
|
327
|
-
//
|
|
328
|
-
if (child.pid !== undefined) {
|
|
329
|
-
writePidFile({
|
|
330
|
-
pid: child.pid,
|
|
331
|
-
serverUrl,
|
|
332
|
-
adminPort,
|
|
333
|
-
startedAt: Date.now(),
|
|
334
|
-
});
|
|
335
|
-
}
|
|
357
|
+
// PID 文件由子进程(守护进程)在 checkSingleInstance 中自行写入
|
|
336
358
|
child.unref();
|
|
337
359
|
closeSync(logFd);
|
|
338
360
|
console.log(chalk.green('客户端已在后台启动'));
|
|
339
361
|
console.log(chalk.gray(` PID: ${child.pid}`));
|
|
340
362
|
console.log(chalk.gray(` 服务器: ${serverUrl}`));
|
|
341
|
-
console.log(chalk.gray(` 管理页面: http://127.0.0.1:${adminPort}`));
|
|
342
363
|
console.log(chalk.gray(` 配置: ${CONFIG_FILE}`));
|
|
343
364
|
console.log(chalk.gray(` 日志: ${LOG_FILE}`));
|
|
344
365
|
// 如果需要开机自启动(智能选择级别)
|
|
@@ -347,7 +368,7 @@ const startCmd = program.command('start')
|
|
|
347
368
|
const result = registerBoot({
|
|
348
369
|
isServer: false,
|
|
349
370
|
nodePath,
|
|
350
|
-
scriptPath:
|
|
371
|
+
scriptPath: daemonEntryPath,
|
|
351
372
|
args: serveArgs,
|
|
352
373
|
}, { level: 'auto' }); // 默认使用 auto 智能选择
|
|
353
374
|
if (result.level === 'system') {
|
|
@@ -365,15 +386,6 @@ const startCmd = program.command('start')
|
|
|
365
386
|
console.log(chalk.yellow(`注册开机自启动失败: ${err.message}`));
|
|
366
387
|
}
|
|
367
388
|
}
|
|
368
|
-
// 打开管理页面
|
|
369
|
-
if (options.open) {
|
|
370
|
-
setTimeout(() => {
|
|
371
|
-
const adminPage = `http://127.0.0.1:${adminPort}`;
|
|
372
|
-
console.log(chalk.gray(`打开管理页面: ${adminPage}`));
|
|
373
|
-
const openCmd = platform() === 'win32' ? 'start' : 'open';
|
|
374
|
-
execSync(`${openCmd} ${adminPage}`, { stdio: 'ignore' });
|
|
375
|
-
}, 1000);
|
|
376
|
-
}
|
|
377
389
|
});
|
|
378
390
|
// ==================== stop 命令 ====================
|
|
379
391
|
const stopCmd = program.command('stop')
|
|
@@ -384,28 +396,27 @@ const stopCmd = program.command('stop')
|
|
|
384
396
|
console.log(chalk.yellow('客户端未在运行'));
|
|
385
397
|
return;
|
|
386
398
|
}
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
399
|
+
// 降级关闭:kill → 检测存在性
|
|
400
|
+
const result = await killProcess(info.pid);
|
|
401
|
+
if (result.success) {
|
|
402
|
+
removePidFile();
|
|
403
|
+
// 取消开机自启动
|
|
404
|
+
try {
|
|
405
|
+
unregisterBoot();
|
|
406
|
+
}
|
|
407
|
+
catch {
|
|
408
|
+
// ignore
|
|
409
|
+
}
|
|
410
|
+
if (result.method === 'already-dead') {
|
|
411
|
+
console.log(chalk.yellow('进程已不存在,已清理状态'));
|
|
391
412
|
}
|
|
392
413
|
else {
|
|
393
|
-
|
|
414
|
+
console.log(chalk.green('客户端已关闭'));
|
|
394
415
|
}
|
|
395
416
|
}
|
|
396
|
-
|
|
397
|
-
console.log(chalk.
|
|
398
|
-
return;
|
|
399
|
-
}
|
|
400
|
-
removePidFile();
|
|
401
|
-
// 取消开机自启动
|
|
402
|
-
try {
|
|
403
|
-
unregisterBoot();
|
|
404
|
-
}
|
|
405
|
-
catch (err) {
|
|
406
|
-
// ignore
|
|
417
|
+
else {
|
|
418
|
+
console.log(chalk.red(`关闭失败: ${result.error || '未知错误'}`));
|
|
407
419
|
}
|
|
408
|
-
console.log(chalk.green('客户端已关闭'));
|
|
409
420
|
});
|
|
410
421
|
// ==================== restart 命令 ====================
|
|
411
422
|
const restartCmd = program.command('restart')
|
|
@@ -466,14 +477,14 @@ const statusCmd = program.command('status')
|
|
|
466
477
|
console.log(chalk.blue.bold('穿透客户端状态'));
|
|
467
478
|
console.log(chalk.gray(` PID: ${info.pid}`));
|
|
468
479
|
console.log(chalk.gray(` 服务器: ${info.serverUrl}`));
|
|
469
|
-
console.log(chalk.gray(` 管理页面: http://127.0.0.1:${info.adminPort}`));
|
|
470
480
|
console.log(chalk.gray(` 配置: ${CONFIG_FILE}`));
|
|
471
481
|
console.log(chalk.gray(` 日志: ${LOG_FILE}`));
|
|
472
482
|
console.log(chalk.gray(` 启动时间: ${new Date(info.startedAt).toLocaleString('zh-CN')}`));
|
|
473
483
|
console.log(chalk.gray(` 开机启动: ${isBootRegistered() ? '已启用' : '未启用'}`));
|
|
474
|
-
// 获取详细状态
|
|
475
|
-
const
|
|
476
|
-
if (status) {
|
|
484
|
+
// 通过 IPC 获取详细状态
|
|
485
|
+
const response = await sendIpcRequest('get-status');
|
|
486
|
+
if (response.success && response.status) {
|
|
487
|
+
const status = response.status;
|
|
477
488
|
const uptime = Math.floor(status.uptime / 1000);
|
|
478
489
|
const minutes = Math.floor(uptime / 60);
|
|
479
490
|
const seconds = uptime % 60;
|
|
@@ -496,9 +507,15 @@ const statusCmd = program.command('status')
|
|
|
496
507
|
console.log(chalk.gray(` ${index} ${proxy.remotePort} -> ${target}:${proxy.localPort}`) + registered);
|
|
497
508
|
}
|
|
498
509
|
}
|
|
510
|
+
if (status.forwardProxies && status.forwardProxies.length > 0) {
|
|
511
|
+
console.log(chalk.gray(` 正向穿透: ${status.forwardProxies.length}个`));
|
|
512
|
+
for (const fp of status.forwardProxies) {
|
|
513
|
+
console.log(chalk.gray(` :${fp.localPort} -> ${fp.targetClientId}:${fp.targetPort}`));
|
|
514
|
+
}
|
|
515
|
+
}
|
|
499
516
|
}
|
|
500
517
|
else {
|
|
501
|
-
console.log(chalk.yellow('
|
|
518
|
+
console.log(chalk.yellow(`无法获取状态: ${response.error || '守护进程未就绪'}`));
|
|
502
519
|
}
|
|
503
520
|
});
|
|
504
521
|
// ==================== proxies 命令 ====================
|
|
@@ -556,42 +573,17 @@ proxiesCmd.command('add')
|
|
|
556
573
|
console.log(chalk.yellow('本地端口无效:1-65535'));
|
|
557
574
|
return;
|
|
558
575
|
}
|
|
559
|
-
// 通过
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
remotePort,
|
|
568
|
-
localPort,
|
|
569
|
-
localHost,
|
|
570
|
-
}),
|
|
571
|
-
});
|
|
572
|
-
if (res.ok) {
|
|
573
|
-
console.log(chalk.green('代理映射已添加'));
|
|
574
|
-
// 更新本地配置文件
|
|
575
|
-
const config = readConfig();
|
|
576
|
-
if (config) {
|
|
577
|
-
if (!config.proxies)
|
|
578
|
-
config.proxies = [];
|
|
579
|
-
config.proxies.push({ remotePort, localPort, localHost });
|
|
580
|
-
writeConfig(config);
|
|
581
|
-
}
|
|
582
|
-
const target = localHost || 'localhost';
|
|
583
|
-
console.log(chalk.gray(` ${remotePort} -> ${target}:${localPort}`));
|
|
584
|
-
}
|
|
585
|
-
else if (res.status === 404) {
|
|
586
|
-
console.log(chalk.yellow('代理映射不存在(管理服务器未运行)'));
|
|
587
|
-
}
|
|
588
|
-
else {
|
|
589
|
-
const data = await res.json();
|
|
590
|
-
console.log(chalk.red(`添加失败: ${data.error || '未知错误'}`));
|
|
591
|
-
}
|
|
576
|
+
// 通过 IPC 添加
|
|
577
|
+
const response = await sendIpcRequest('add-proxy', {
|
|
578
|
+
proxy: { remotePort, localPort, localHost }
|
|
579
|
+
});
|
|
580
|
+
if (response.success) {
|
|
581
|
+
console.log(chalk.green('代理映射已添加'));
|
|
582
|
+
const target = localHost || 'localhost';
|
|
583
|
+
console.log(chalk.gray(` ${remotePort} -> ${target}:${localPort}`));
|
|
592
584
|
}
|
|
593
|
-
|
|
594
|
-
console.log(chalk.red(
|
|
585
|
+
else {
|
|
586
|
+
console.log(chalk.red(`添加失败: ${response.error || '未知错误'}`));
|
|
595
587
|
}
|
|
596
588
|
});
|
|
597
589
|
// proxies remove 子命令
|
|
@@ -604,63 +596,25 @@ proxiesCmd.command('remove')
|
|
|
604
596
|
console.log(chalk.yellow('端口号无效:1024-65535'));
|
|
605
597
|
return;
|
|
606
598
|
}
|
|
607
|
-
// 通过
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
const res = await fetch(`http://127.0.0.1:${adminPort}/_ctc/proxies/${port}`, {
|
|
612
|
-
method: 'DELETE',
|
|
613
|
-
});
|
|
614
|
-
if (res.ok) {
|
|
615
|
-
console.log(chalk.green(`端口 ${port} 的代理映射已移除`));
|
|
616
|
-
// 更新本地配置文件
|
|
617
|
-
const config = readConfig();
|
|
618
|
-
if (config && config.proxies) {
|
|
619
|
-
const index = config.proxies.findIndex(p => p.remotePort === port);
|
|
620
|
-
if (index !== -1) {
|
|
621
|
-
config.proxies.splice(index, 1);
|
|
622
|
-
writeConfig(config);
|
|
623
|
-
}
|
|
624
|
-
}
|
|
625
|
-
}
|
|
626
|
-
else if (res.status === 404) {
|
|
627
|
-
console.log(chalk.yellow('代理映射不存在'));
|
|
628
|
-
}
|
|
629
|
-
else {
|
|
630
|
-
const data = await res.json();
|
|
631
|
-
console.log(chalk.red(`移除失败: ${data.error || '未知错误'}`));
|
|
632
|
-
}
|
|
599
|
+
// 通过 IPC 删除
|
|
600
|
+
const response = await sendIpcRequest('remove-proxy', { remotePort: port });
|
|
601
|
+
if (response.success) {
|
|
602
|
+
console.log(chalk.green(`端口 ${port} 的代理映射已移除`));
|
|
633
603
|
}
|
|
634
|
-
|
|
635
|
-
console.log(chalk.red(
|
|
604
|
+
else {
|
|
605
|
+
console.log(chalk.red(`移除失败: ${response.error || '未知错误'}`));
|
|
636
606
|
}
|
|
637
607
|
});
|
|
638
608
|
// proxies clear 子命令
|
|
639
609
|
proxiesCmd.command('clear')
|
|
640
610
|
.description('清空所有代理映射')
|
|
641
611
|
.action(async () => {
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
const res = await fetch(`http://127.0.0.1:${adminPort}/_ctc/proxies`, {
|
|
646
|
-
method: 'DELETE',
|
|
647
|
-
});
|
|
648
|
-
if (res.ok) {
|
|
649
|
-
console.log(chalk.green('所有代理映射已清空'));
|
|
650
|
-
// 更新本地配置文件
|
|
651
|
-
const config = readConfig();
|
|
652
|
-
if (config) {
|
|
653
|
-
config.proxies = [];
|
|
654
|
-
writeConfig(config);
|
|
655
|
-
}
|
|
656
|
-
}
|
|
657
|
-
else {
|
|
658
|
-
const data = await res.json();
|
|
659
|
-
console.log(chalk.red(`清空失败: ${data.error || '未知错误'}`));
|
|
660
|
-
}
|
|
612
|
+
const response = await sendIpcRequest('clear-proxies');
|
|
613
|
+
if (response.success) {
|
|
614
|
+
console.log(chalk.green('所有代理映射已清空'));
|
|
661
615
|
}
|
|
662
|
-
|
|
663
|
-
console.log(chalk.red(
|
|
616
|
+
else {
|
|
617
|
+
console.log(chalk.red(`清空失败: ${response.error || '未知错误'}`));
|
|
664
618
|
}
|
|
665
619
|
});
|
|
666
620
|
// ==================== config 命令 ====================
|
|
@@ -775,38 +729,29 @@ const forwardCmd = program.command('forward')
|
|
|
775
729
|
forwardCmd.command('list')
|
|
776
730
|
.description('列出所有正向穿透代理')
|
|
777
731
|
.action(async () => {
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
if (!res.ok) {
|
|
783
|
-
console.log(chalk.yellow('无法获取代理列表(客户端未运行或功能不可用)'));
|
|
784
|
-
return;
|
|
785
|
-
}
|
|
786
|
-
const data = await res.json();
|
|
787
|
-
const proxies = data.proxies || [];
|
|
788
|
-
if (proxies.length === 0) {
|
|
789
|
-
console.log(chalk.yellow('暂无正向穿透代理'));
|
|
790
|
-
return;
|
|
791
|
-
}
|
|
792
|
-
console.log(chalk.blue.bold('正向穿透代理列表:'));
|
|
793
|
-
for (const proxy of proxies) {
|
|
794
|
-
const status = proxy.enabled ? chalk.green('●') : chalk.gray('○');
|
|
795
|
-
console.log(` ${status} 本地 :${proxy.localPort} → ${chalk.cyan(proxy.targetClientId)}:${proxy.targetPort}`);
|
|
796
|
-
}
|
|
732
|
+
const response = await sendIpcRequest('forward-list');
|
|
733
|
+
if (!response.success) {
|
|
734
|
+
console.log(chalk.yellow(`无法获取代理列表: ${response.error || '客户端未运行'}`));
|
|
735
|
+
return;
|
|
797
736
|
}
|
|
798
|
-
|
|
799
|
-
|
|
737
|
+
const proxies = response.proxies || [];
|
|
738
|
+
if (proxies.length === 0) {
|
|
739
|
+
console.log(chalk.yellow('暂无正向穿透代理'));
|
|
740
|
+
return;
|
|
741
|
+
}
|
|
742
|
+
console.log(chalk.blue.bold('正向穿透代理列表:'));
|
|
743
|
+
for (const proxy of proxies) {
|
|
744
|
+
console.log(` ${chalk.green('●')} 本地 :${proxy.localPort} → ${chalk.cyan(proxy.targetClientId)}:${proxy.targetPort}`);
|
|
800
745
|
}
|
|
801
746
|
});
|
|
802
747
|
// forward add 子命令
|
|
803
748
|
forwardCmd.command('add')
|
|
804
749
|
.description('添加正向穿透代理')
|
|
805
|
-
.argument('<mapping>', '映射格式:localPort:
|
|
750
|
+
.argument('<mapping>', '映射格式:localPort:targetPort:targetClientId (如: 8080:3000:clientB)')
|
|
806
751
|
.action(async (mapping) => {
|
|
807
752
|
const parts = mapping.split(':');
|
|
808
753
|
if (parts.length !== 3) {
|
|
809
|
-
console.log(chalk.yellow('参数格式错误,应为:localPort:
|
|
754
|
+
console.log(chalk.yellow('参数格式错误,应为:localPort:targetPort:targetClientId'));
|
|
810
755
|
console.log(chalk.gray('示例:8080:3000:clientB'));
|
|
811
756
|
return;
|
|
812
757
|
}
|
|
@@ -825,28 +770,15 @@ forwardCmd.command('add')
|
|
|
825
770
|
console.log(chalk.yellow('目标客户端 ID 不能为空'));
|
|
826
771
|
return;
|
|
827
772
|
}
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
body: JSON.stringify({ localPort, targetClientId, targetPort }),
|
|
835
|
-
});
|
|
836
|
-
if (res.ok) {
|
|
837
|
-
console.log(chalk.green('正向穿透代理已添加'));
|
|
838
|
-
console.log(chalk.gray(` 本地 :${localPort} → ${targetClientId}:${targetPort}`));
|
|
839
|
-
}
|
|
840
|
-
else if (res.status === 404) {
|
|
841
|
-
console.log(chalk.yellow('客户端未运行或功能不可用'));
|
|
842
|
-
}
|
|
843
|
-
else {
|
|
844
|
-
const data = await res.json();
|
|
845
|
-
console.log(chalk.red(`添加失败: ${data.error || '未知错误'}`));
|
|
846
|
-
}
|
|
773
|
+
const response = await sendIpcRequest('forward-add', {
|
|
774
|
+
forwardEntry: { localPort, targetPort, targetClientId }
|
|
775
|
+
});
|
|
776
|
+
if (response.success) {
|
|
777
|
+
console.log(chalk.green('正向穿透代理已添加'));
|
|
778
|
+
console.log(chalk.gray(` 本地 :${localPort} → ${targetClientId}:${targetPort}`));
|
|
847
779
|
}
|
|
848
|
-
|
|
849
|
-
console.log(chalk.red(
|
|
780
|
+
else {
|
|
781
|
+
console.log(chalk.red(`添加失败: ${response.error || '未知错误'}`));
|
|
850
782
|
}
|
|
851
783
|
});
|
|
852
784
|
// forward remove 子命令
|
|
@@ -859,52 +791,32 @@ forwardCmd.command('remove')
|
|
|
859
791
|
console.log(chalk.yellow('端口号无效:1-65535'));
|
|
860
792
|
return;
|
|
861
793
|
}
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
const res = await fetch(`http://127.0.0.1:${adminPort}/_ctc/forward/remove`, {
|
|
866
|
-
method: 'POST',
|
|
867
|
-
headers: { 'Content-Type': 'application/json' },
|
|
868
|
-
body: JSON.stringify({ localPort: port }),
|
|
869
|
-
});
|
|
870
|
-
if (res.ok) {
|
|
871
|
-
console.log(chalk.green(`本地端口 ${port} 的正向穿透代理已移除`));
|
|
872
|
-
}
|
|
873
|
-
else {
|
|
874
|
-
const data = await res.json();
|
|
875
|
-
console.log(chalk.red(`移除失败: ${data.error || '未知错误'}`));
|
|
876
|
-
}
|
|
794
|
+
const response = await sendIpcRequest('forward-remove', { localPort: port });
|
|
795
|
+
if (response.success) {
|
|
796
|
+
console.log(chalk.green(`本地端口 ${port} 的正向穿透代理已移除`));
|
|
877
797
|
}
|
|
878
|
-
|
|
879
|
-
console.log(chalk.red(
|
|
798
|
+
else {
|
|
799
|
+
console.log(chalk.red(`移除失败: ${response.error || '未知错误'}`));
|
|
880
800
|
}
|
|
881
801
|
});
|
|
882
802
|
// forward clients 子命令
|
|
883
803
|
forwardCmd.command('clients')
|
|
884
804
|
.description('查看在线客户端列表')
|
|
885
805
|
.action(async () => {
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
if (!res.ok) {
|
|
891
|
-
console.log(chalk.yellow('无法获取客户端列表'));
|
|
892
|
-
return;
|
|
893
|
-
}
|
|
894
|
-
const data = await res.json();
|
|
895
|
-
const clients = data.clients || [];
|
|
896
|
-
if (clients.length === 0) {
|
|
897
|
-
console.log(chalk.yellow('暂无在线客户端'));
|
|
898
|
-
return;
|
|
899
|
-
}
|
|
900
|
-
console.log(chalk.blue.bold('在线客户端列表:'));
|
|
901
|
-
for (const client of clients) {
|
|
902
|
-
const desc = client.description ? ` (${client.description})` : '';
|
|
903
|
-
console.log(` ${chalk.cyan(client.id)}${desc}`);
|
|
904
|
-
}
|
|
806
|
+
const response = await sendIpcRequest('forward-clients');
|
|
807
|
+
if (!response.success) {
|
|
808
|
+
console.log(chalk.yellow(`无法获取客户端列表: ${response.error || '未知错误'}`));
|
|
809
|
+
return;
|
|
905
810
|
}
|
|
906
|
-
|
|
907
|
-
|
|
811
|
+
const clients = response.clients || [];
|
|
812
|
+
if (clients.length === 0) {
|
|
813
|
+
console.log(chalk.yellow('暂无在线客户端'));
|
|
814
|
+
return;
|
|
815
|
+
}
|
|
816
|
+
console.log(chalk.blue.bold('在线客户端列表:'));
|
|
817
|
+
for (const client of clients) {
|
|
818
|
+
const desc = client.description ? ` (${client.description})` : '';
|
|
819
|
+
console.log(` ${chalk.cyan(client.id)}${desc}`);
|
|
908
820
|
}
|
|
909
821
|
});
|
|
910
822
|
// forward register 子命令
|
|
@@ -912,24 +824,12 @@ forwardCmd.command('register')
|
|
|
912
824
|
.description('注册到服务器(启用正向穿透模式)')
|
|
913
825
|
.option('-d, --description <desc>', '客户端描述')
|
|
914
826
|
.action(async (options) => {
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
const res = await fetch(`http://127.0.0.1:${adminPort}/_ctc/forward/register`, {
|
|
919
|
-
method: 'POST',
|
|
920
|
-
headers: { 'Content-Type': 'application/json' },
|
|
921
|
-
body: JSON.stringify({ description: options.description || '' }),
|
|
922
|
-
});
|
|
923
|
-
if (res.ok) {
|
|
924
|
-
console.log(chalk.green('已注册到服务器,正向穿透模式已启用'));
|
|
925
|
-
}
|
|
926
|
-
else {
|
|
927
|
-
const data = await res.json();
|
|
928
|
-
console.log(chalk.red(`注册失败: ${data.error || '未知错误'}`));
|
|
929
|
-
}
|
|
827
|
+
const response = await sendIpcRequest('forward-register', { description: options.description || '' });
|
|
828
|
+
if (response.success) {
|
|
829
|
+
console.log(chalk.green('已注册到服务器,正向穿透模式已启用'));
|
|
930
830
|
}
|
|
931
|
-
|
|
932
|
-
console.log(chalk.red(
|
|
831
|
+
else {
|
|
832
|
+
console.log(chalk.red(`注册失败: ${response.error || '未知错误'}`));
|
|
933
833
|
}
|
|
934
834
|
});
|
|
935
835
|
// ==================== boot 命令 ====================
|
|
@@ -979,7 +879,7 @@ bootCmd.command('enable')
|
|
|
979
879
|
const result = registerBoot({
|
|
980
880
|
isServer: false,
|
|
981
881
|
nodePath,
|
|
982
|
-
scriptPath:
|
|
882
|
+
scriptPath: daemonEntryPath,
|
|
983
883
|
args: info.args || [],
|
|
984
884
|
}, { level });
|
|
985
885
|
if (result.level === 'system') {
|
|
@@ -1015,6 +915,23 @@ bootCmd.command('disable')
|
|
|
1015
915
|
console.log(chalk.red(`禁用失败: ${err.message}`));
|
|
1016
916
|
}
|
|
1017
917
|
});
|
|
918
|
+
// ==================== open 命令 ====================
|
|
919
|
+
const openCmd = program.command('open')
|
|
920
|
+
.description('在浏览器中打开管理页面')
|
|
921
|
+
.action(async () => {
|
|
922
|
+
const info = readPidFile();
|
|
923
|
+
const adminPort = info?.adminPort ?? ADMIN_PORT_DEFAULT;
|
|
924
|
+
const adminPage = `http://127.0.0.1:${adminPort}`;
|
|
925
|
+
const openCmd = platform() === 'win32' ? 'start' : 'open';
|
|
926
|
+
console.log(chalk.gray(`打开管理页面: ${adminPage}`));
|
|
927
|
+
try {
|
|
928
|
+
execSync(`${openCmd} ${adminPage}`, { stdio: 'ignore' });
|
|
929
|
+
console.log(chalk.green('管理页面已在浏览器中打开'));
|
|
930
|
+
}
|
|
931
|
+
catch (err) {
|
|
932
|
+
console.log(chalk.yellow(`提示: 请确保客户端正在运行`));
|
|
933
|
+
}
|
|
934
|
+
});
|
|
1018
935
|
// ==================== logs 命令 ====================
|
|
1019
936
|
const logsCmd = program.command('logs')
|
|
1020
937
|
.description('查看或跟踪日志')
|
|
@@ -1079,24 +996,6 @@ const logsCmd = program.command('logs')
|
|
|
1079
996
|
console.log(chalk.gray(`(日志文件:${LOG_FILE})`));
|
|
1080
997
|
}
|
|
1081
998
|
});
|
|
1082
|
-
// ==================== open 命令 ====================
|
|
1083
|
-
const openCmd = program.command('open')
|
|
1084
|
-
.description('在浏览器中打开管理页面')
|
|
1085
|
-
.action(async () => {
|
|
1086
|
-
const info = readPidFile();
|
|
1087
|
-
const adminPort = info?.adminPort ?? ADMIN_PORT_DEFAULT;
|
|
1088
|
-
const adminPage = `http://127.0.0.1:${adminPort}`;
|
|
1089
|
-
const openCmd = platform() === 'win32' ? 'start' : 'open';
|
|
1090
|
-
console.log(chalk.gray(`打开管理页面: ${adminPage}`));
|
|
1091
|
-
try {
|
|
1092
|
-
execSync(`${openCmd} ${adminPage}`, { stdio: 'ignore' });
|
|
1093
|
-
console.log(chalk.green('管理页面已在浏览器中打开'));
|
|
1094
|
-
}
|
|
1095
|
-
catch (err) {
|
|
1096
|
-
console.log(chalk.yellow(`打开失败: ${err.message}`));
|
|
1097
|
-
console.log(chalk.gray(`请手动访问:${adminPage}`));
|
|
1098
|
-
}
|
|
1099
|
-
});
|
|
1100
999
|
// ==================== 默认处理 ====================
|
|
1101
1000
|
// 当没有指定命令时显示帮助
|
|
1102
1001
|
program.action(() => {
|