cc-viewer 1.6.299 → 1.6.301

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/cli.js CHANGED
@@ -14,6 +14,8 @@ import { t } from './server/i18n.js';
14
14
  import { INJECT_IMPORT, LEGACY_INJECT_IMPORTS, resolveCliPath, resolveNativePath, resolveNpmClaudePath, buildShellCandidates, setLogDir, LOG_DIR, hasClaude2xWrapper, getGlobalNodeModulesDir, PACKAGES, getClaudeConfigDir } from './findcc.js';
15
15
  import { ensureHooks, removeAllManagedHooks } from './server/lib/ensure-hooks.js';
16
16
  import { injectCliJsAt, removeCliJsInjectionAt, INJECT_START as _INJECT_START, INJECT_END as _INJECT_END, buildInjectBlock as _buildInjectBlock } from './server/lib/cli-inject.js';
17
+ import { normalizeBasePath } from './server/lib/base-path.js';
18
+ import { createHardenedCleanup, installWinKeypressFallback } from './server/lib/term-signals.js';
17
19
 
18
20
  const __dirname = fileURLToPath(new URL('.', import.meta.url));
19
21
 
@@ -389,8 +391,8 @@ async function runCliMode(extraClaudeArgs = [], cwd, noOpen = false) {
389
391
 
390
392
  // 4. 自动打开浏览器
391
393
  const protocol = serverMod.getProtocol();
392
- const basePath = process.env.CCV_BASE_PATH || '';
393
- const url = `${protocol}://127.0.0.1:${port}${basePath}`;
394
+ const basePath = normalizeBasePath(process.env.CCV_BASE_PATH);
395
+ const url = `${protocol}://127.0.0.1:${port}${basePath}`;
394
396
  if (!noOpen) {
395
397
  try {
396
398
  // URL 含 & 在 cmd.exe 下会被当命令分隔符切断 query;用 spawn 数组传参避免 shell interpolation。
@@ -421,13 +423,20 @@ const url = `${protocol}://127.0.0.1:${port}${basePath}`;
421
423
  else console.log(` ${t('server.passwordActive', { password: _auth.password })}`);
422
424
  }
423
425
 
424
- // 5. 注册退出处理
425
- const cleanup = () => {
426
- killPty();
427
- serverMod.stopViewer().finally(() => process.exit());
428
- };
426
+ // 5. 注册退出处理(hardened:watchdog 5s 强退 + 连按 Ctrl+C 立退,
427
+ // 防 Windows ConPTY kill / IM teardown 挂住导致"Ctrl+C 完全无反应")
428
+ const cleanup = createHardenedCleanup({
429
+ doCleanup: () => {
430
+ killPty();
431
+ return serverMod.stopViewer();
432
+ },
433
+ });
429
434
  process.on('SIGINT', cleanup);
430
435
  process.on('SIGTERM', cleanup);
436
+ // Windows 兜底:ConPTY 下控制台 Ctrl+C 事件偶发不送达(SIGINT 永不触发),
437
+ // raw mode keypress 直连 cleanup。silent 模式本地终端无人读 stdin,无副作用;
438
+ // darwin / 非 TTY 内部自动跳过。
439
+ installWinKeypressFallback({ onInterrupt: cleanup });
431
440
  }
432
441
 
433
442
  // 启动一个独立常驻 IM worker。本质是「在 IM_<id>/ 工作目录、绑 127.0.0.1、skip-permissions」的 runCliMode,
@@ -552,8 +561,8 @@ async function runSdkMode(extraClaudeArgs = [], cwd, noOpen = false) {
552
561
 
553
562
  // 自动打开浏览器
554
563
  const protocol = serverMod.getProtocol();
555
- const basePath = process.env.CCV_BASE_PATH || '';
556
- const url = `${protocol}://127.0.0.1:${port}${basePath}`;
564
+ const basePath = normalizeBasePath(process.env.CCV_BASE_PATH);
565
+ const url = `${protocol}://127.0.0.1:${port}${basePath}`;
557
566
  if (!noOpen) {
558
567
  try {
559
568
  // URL 含 & 在 cmd.exe 下会被当命令分隔符切断 query;用 spawn 数组传参避免 shell interpolation。
@@ -584,86 +593,16 @@ const url = `${protocol}://127.0.0.1:${port}${basePath}`;
584
593
  else console.log(` ${t('server.passwordActive', { password: _auth.password })}`);
585
594
  }
586
595
 
587
- // 注册退出处理
588
- const cleanup = () => {
589
- sdkManager.stopSession();
590
- serverMod.stopViewer().finally(() => process.exit());
591
- };
592
- process.on('SIGINT', cleanup);
593
- process.on('SIGTERM', cleanup);
594
- }
595
-
596
- async function runCliModeWorkspaceSelector(extraClaudeArgs = [], noOpen = false) {
597
- // 首先尝试 npm 版本(包括 nvm 安装),找不到再尝试 native 版本
598
- let claudePath = resolveNpmClaudePath();
599
- let isNpmVersion = !!claudePath;
600
-
601
- if (!claudePath) {
602
- claudePath = resolveNativePath();
603
- }
604
-
605
- if (!claudePath) {
606
- reportClaudeNotFound(cliPath);
607
- process.exit(1);
608
- }
609
-
610
- console.log(t('cli.cMode.starting'));
611
-
612
- process.env.CCV_CLI_MODE = '1';
613
- process.env.CCV_WORKSPACE_MODE = '1';
614
-
615
- // 启动代理
616
- const { startProxy } = await import('./server/proxy.js');
617
- const proxyPort = await startProxy();
618
- process.env.CCV_PROXY_PORT = String(proxyPort);
619
-
620
- // 启动 HTTP 服务器(工作区模式,不初始化 interceptor 日志)
621
- const serverMod = await import('./server/server.js');
622
-
623
- // 工作区模式下 server.js 跳过了自动启动,需要手动调用
624
- await serverMod.startViewer();
625
-
626
- const port = serverMod.getPort();
627
-
628
- // 保存 extraClaudeArgs 和 claudePath 供后续 launch 使用
629
- serverMod.setWorkspaceClaudeArgs(extraClaudeArgs);
630
- serverMod.setWorkspaceClaudePath(claudePath, isNpmVersion);
631
-
632
- // 自动打开浏览器
633
- const basePath = process.env.CCV_BASE_PATH || '';
634
- const wsProtocol = serverMod.getProtocol();
635
- const url = `${wsProtocol}://127.0.0.1:${port}${basePath}`;
636
- if (!noOpen) {
637
- try {
638
- // URL 含 & 在 cmd.exe 下会被当命令分隔符切断 query;用 spawn 数组传参避免 shell interpolation。
639
- // Win 上 `start` 是 cmd.exe 内置不是 .exe,必须 shell:true;用 spawn + 数组让 Node 自己 escape。
640
- // 第二个 arg '""' 是 `start` 的 window-title 占位(否则 start 会把 URL 当 title)。
641
- const { spawn } = await import('node:child_process');
642
- if (process.platform === 'win32') {
643
- spawn('cmd.exe', ['/c', 'start', '""', url], { stdio: 'ignore', detached: true, windowsHide: true }).unref();
644
- } else {
645
- const cmd = process.platform === 'darwin' ? 'open' : 'xdg-open';
646
- spawn(cmd, [url], { stdio: 'ignore', detached: true }).unref();
647
- }
648
- } catch {}
649
- }
650
-
651
- console.log(`CC Viewer (Workspace):`);
652
- console.log(` ➜ Local: ${url}`);
653
- const _lanIps = serverMod.getAllLocalIps();
654
- const _token = serverMod.getAccessToken();
655
- for (const _ip of _lanIps) {
656
- console.log(` ➜ Network: ${wsProtocol}://${_ip}:${port}${basePath}?token=${_token}`);
657
- }
658
-
659
- // 注册退出处理
660
- const { killPty } = await import('./server/pty-manager.js');
661
- const cleanup = () => {
662
- killPty();
663
- serverMod.stopViewer().finally(() => process.exit());
664
- };
596
+ // 注册退出处理(hardened,与 PTY 模式同款三层防御)
597
+ const cleanup = createHardenedCleanup({
598
+ doCleanup: () => {
599
+ sdkManager.stopSession();
600
+ return serverMod.stopViewer();
601
+ },
602
+ });
665
603
  process.on('SIGINT', cleanup);
666
604
  process.on('SIGTERM', cleanup);
605
+ installWinKeypressFallback({ onInterrupt: cleanup });
667
606
  }
668
607
 
669
608
  // === 主逻辑 ===