@feng3d/ctc 0.0.18 → 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.
Files changed (45) hide show
  1. package/dist/admin-server.d.ts +6 -0
  2. package/dist/admin-server.d.ts.map +1 -1
  3. package/dist/admin-server.js +19 -0
  4. package/dist/admin-server.js.map +1 -1
  5. package/dist/app.d.ts +17 -0
  6. package/dist/app.d.ts.map +1 -0
  7. package/dist/app.js +97 -0
  8. package/dist/app.js.map +1 -0
  9. package/dist/cli.d.ts +1 -0
  10. package/dist/cli.d.ts.map +1 -1
  11. package/dist/cli.js +152 -253
  12. package/dist/cli.js.map +1 -1
  13. package/dist/config.d.ts +1 -5
  14. package/dist/config.d.ts.map +1 -1
  15. package/dist/config.js +0 -8
  16. package/dist/config.js.map +1 -1
  17. package/dist/controller.d.ts.map +1 -1
  18. package/dist/controller.js +8 -0
  19. package/dist/controller.js.map +1 -1
  20. package/dist/daemon-entry.d.ts +8 -0
  21. package/dist/daemon-entry.d.ts.map +1 -0
  22. package/dist/daemon-entry.js +15 -0
  23. package/dist/daemon-entry.js.map +1 -0
  24. package/dist/daemon.d.ts +32 -0
  25. package/dist/daemon.d.ts.map +1 -0
  26. package/dist/daemon.js +370 -0
  27. package/dist/daemon.js.map +1 -0
  28. package/dist/forward-proxy.js +1 -1
  29. package/dist/forward-proxy.js.map +1 -1
  30. package/dist/index.d.ts +16 -14
  31. package/dist/index.d.ts.map +1 -1
  32. package/dist/index.js +14 -240
  33. package/dist/index.js.map +1 -1
  34. package/dist/ipc-handler.d.ts +76 -4
  35. package/dist/ipc-handler.d.ts.map +1 -1
  36. package/dist/ipc-handler.js +221 -26
  37. package/dist/ipc-handler.js.map +1 -1
  38. package/dist/process-utils.d.ts +31 -0
  39. package/dist/process-utils.d.ts.map +1 -0
  40. package/dist/process-utils.js +87 -0
  41. package/dist/process-utils.js.map +1 -0
  42. package/dist/proxy-manager.d.ts.map +1 -1
  43. package/dist/proxy-manager.js +45 -0
  44. package/dist/proxy-manager.js.map +1 -1
  45. 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
- // index.js 是实际运行客户端的入口(包含管理服务器)
216
- const indexPath = join(dirname(cliPath), 'index.js');
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, [indexPath, ...serveArgs], {
353
+ const child = spawn(nodePath, [daemonEntryPath, ...serveArgs], {
324
354
  detached: true,
325
355
  stdio: ['ignore', logFd, logFd],
326
356
  });
327
- // 写入 PID 文件
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: indexPath,
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
- try {
388
- // Windows 不支持 SIGTERM,需要使用 taskkill 命令
389
- if (platform() === 'win32') {
390
- execSync(`taskkill /F /PID ${info.pid}`, { stdio: 'ignore' });
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
- process.kill(info.pid, 'SIGTERM');
414
+ console.log(chalk.green('客户端已关闭'));
394
415
  }
395
416
  }
396
- catch (err) {
397
- console.log(chalk.yellow(`关闭失败: ${err.message}`));
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 status = await getClientStatus(info.adminPort);
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
- // 通过 API 添加
560
- try {
561
- const info = readPidFile();
562
- const adminPort = info?.adminPort ?? ADMIN_PORT_DEFAULT;
563
- const res = await fetch(`http://127.0.0.1:${adminPort}/_ctc/proxies`, {
564
- method: 'POST',
565
- headers: { 'Content-Type': 'application/json' },
566
- body: JSON.stringify({
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
- catch (err) {
594
- console.log(chalk.red(`请求失败: ${err.message}`));
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
- // 通过 API 删除
608
- try {
609
- const info = readPidFile();
610
- const adminPort = info?.adminPort ?? ADMIN_PORT_DEFAULT;
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
- catch (err) {
635
- console.log(chalk.red(`请求失败: ${err.message}`));
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
- try {
643
- const info = readPidFile();
644
- const adminPort = info?.adminPort ?? ADMIN_PORT_DEFAULT;
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
- catch (err) {
663
- console.log(chalk.red(`请求失败: ${err.message}`));
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
- try {
779
- const info = readPidFile();
780
- const adminPort = info?.adminPort ?? ADMIN_PORT_DEFAULT;
781
- const res = await fetch(`http://127.0.0.1:${adminPort}/_ctc/forward/list`);
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
- catch (err) {
799
- console.log(chalk.red(`请求失败: ${err.message}`));
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:remotePort:targetClientId (如: 8080:3000:clientB)')
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:remotePort:targetClientId'));
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
- try {
829
- const info = readPidFile();
830
- const adminPort = info?.adminPort ?? ADMIN_PORT_DEFAULT;
831
- const res = await fetch(`http://127.0.0.1:${adminPort}/_ctc/forward/add`, {
832
- method: 'POST',
833
- headers: { 'Content-Type': 'application/json' },
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
- catch (err) {
849
- console.log(chalk.red(`请求失败: ${err.message}`));
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
- try {
863
- const info = readPidFile();
864
- const adminPort = info?.adminPort ?? ADMIN_PORT_DEFAULT;
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
- catch (err) {
879
- console.log(chalk.red(`请求失败: ${err.message}`));
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
- try {
887
- const info = readPidFile();
888
- const adminPort = info?.adminPort ?? ADMIN_PORT_DEFAULT;
889
- const res = await fetch(`http://127.0.0.1:${adminPort}/_ctc/forward/clients`);
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
- catch (err) {
907
- console.log(chalk.red(`请求失败: ${err.message}`));
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
- try {
916
- const info = readPidFile();
917
- const adminPort = info?.adminPort ?? ADMIN_PORT_DEFAULT;
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
- catch (err) {
932
- console.log(chalk.red(`请求失败: ${err.message}`));
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: indexPath,
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(() => {