feat: CCW Dashboard 增强 - 停止命令、浏览器修复和MCP多源配置

- 新增 ccw stop 命令支持优雅停止和强制终止 (--force)
- 修复 ccw view 服务器检测时浏览器无法打开的问题
- MCP 配置现在从多个源读取:
  - ~/.claude.json (项目级)
  - ~/.claude/settings.json 和 settings.local.json (全局)
  - 各工作空间的 .claude/settings.json (工作空间级)
- 新增全局 MCP 服务器显示区域
- 修复路径选择模态框样式问题

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
catlog22
2025-12-08 10:28:07 +08:00
parent f4299457fb
commit 27273405f7
8 changed files with 377 additions and 21 deletions

101
ccw/src/commands/stop.js Normal file
View File

@@ -0,0 +1,101 @@
import chalk from 'chalk';
import { exec } from 'child_process';
import { promisify } from 'util';
const execAsync = promisify(exec);
/**
* Find process using a specific port (Windows)
* @param {number} port - Port number
* @returns {Promise<string|null>} PID or null
*/
async function findProcessOnPort(port) {
try {
const { stdout } = await execAsync(`netstat -ano | findstr :${port} | findstr LISTENING`);
const lines = stdout.trim().split('\n');
if (lines.length > 0) {
const parts = lines[0].trim().split(/\s+/);
return parts[parts.length - 1]; // PID is the last column
}
} catch {
// No process found
}
return null;
}
/**
* Kill process by PID (Windows)
* @param {string} pid - Process ID
* @returns {Promise<boolean>} Success status
*/
async function killProcess(pid) {
try {
await execAsync(`taskkill /PID ${pid} /F`);
return true;
} catch {
return false;
}
}
/**
* Stop command handler - stops the running CCW dashboard server
* @param {Object} options - Command options
*/
export async function stopCommand(options) {
const port = options.port || 3456;
const force = options.force || false;
console.log(chalk.blue.bold('\n CCW Dashboard\n'));
console.log(chalk.gray(` Checking server on port ${port}...`));
try {
// Try graceful shutdown via API first
const healthCheck = await fetch(`http://localhost:${port}/api/health`, {
signal: AbortSignal.timeout(2000)
}).catch(() => null);
if (healthCheck && healthCheck.ok) {
// CCW server is running - send shutdown signal
console.log(chalk.cyan(' CCW server found, sending shutdown signal...'));
await fetch(`http://localhost:${port}/api/shutdown`, {
method: 'POST',
signal: AbortSignal.timeout(5000)
}).catch(() => null);
// Wait a moment for shutdown
await new Promise(resolve => setTimeout(resolve, 500));
console.log(chalk.green.bold('\n Server stopped successfully!\n'));
return;
}
// No CCW server responding, check if port is in use
const pid = await findProcessOnPort(port);
if (!pid) {
console.log(chalk.yellow(` No server running on port ${port}\n`));
return;
}
// Port is in use by another process
console.log(chalk.yellow(` Port ${port} is in use by process PID: ${pid}`));
if (force) {
console.log(chalk.cyan(' Force killing process...'));
const killed = await killProcess(pid);
if (killed) {
console.log(chalk.green.bold('\n Process killed successfully!\n'));
} else {
console.log(chalk.red('\n Failed to kill process. Try running as administrator.\n'));
}
} else {
console.log(chalk.gray(`\n This is not a CCW server. Use --force to kill it:`));
console.log(chalk.white(` ccw stop --force\n`));
}
} catch (err) {
console.error(chalk.red(`\n Error: ${err.message}\n`));
}
}