mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-10 02:24:35 +08:00
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:
@@ -8,8 +8,11 @@ import { scanSessions } from './session-scanner.js';
|
||||
import { aggregateData } from './data-aggregator.js';
|
||||
import { resolvePath, getRecentPaths, trackRecentPath, removeRecentPath, normalizePathForDisplay, getWorkflowDir } from '../utils/path-resolver.js';
|
||||
|
||||
// Claude config file path
|
||||
// Claude config file paths
|
||||
const CLAUDE_CONFIG_PATH = join(homedir(), '.claude.json');
|
||||
const CLAUDE_SETTINGS_DIR = join(homedir(), '.claude');
|
||||
const CLAUDE_GLOBAL_SETTINGS = join(CLAUDE_SETTINGS_DIR, 'settings.json');
|
||||
const CLAUDE_GLOBAL_SETTINGS_LOCAL = join(CLAUDE_SETTINGS_DIR, 'settings.local.json');
|
||||
|
||||
// WebSocket clients for real-time notifications
|
||||
const wsClients = new Set();
|
||||
@@ -160,6 +163,24 @@ export async function startServer(options = {}) {
|
||||
return;
|
||||
}
|
||||
|
||||
// API: Shutdown server (for ccw stop command)
|
||||
if (pathname === '/api/shutdown' && req.method === 'POST') {
|
||||
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({ status: 'shutting_down' }));
|
||||
|
||||
// Graceful shutdown
|
||||
console.log('\n Received shutdown signal...');
|
||||
setTimeout(() => {
|
||||
server.close(() => {
|
||||
console.log(' Server stopped.\n');
|
||||
process.exit(0);
|
||||
});
|
||||
// Force exit after 3 seconds if graceful shutdown fails
|
||||
setTimeout(() => process.exit(0), 3000);
|
||||
}, 100);
|
||||
return;
|
||||
}
|
||||
|
||||
// API: Remove a recent path
|
||||
if (pathname === '/api/remove-recent-path' && req.method === 'POST') {
|
||||
handlePostRequest(req, res, async (body) => {
|
||||
@@ -1006,22 +1027,82 @@ async function loadRecentPaths() {
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Get MCP configuration from .claude.json
|
||||
* Safely read and parse JSON file
|
||||
* @param {string} filePath
|
||||
* @returns {Object|null}
|
||||
*/
|
||||
function safeReadJson(filePath) {
|
||||
try {
|
||||
if (!existsSync(filePath)) return null;
|
||||
const content = readFileSync(filePath, 'utf8');
|
||||
return JSON.parse(content);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get MCP servers from a settings file
|
||||
* @param {string} filePath
|
||||
* @returns {Object} mcpServers object or empty object
|
||||
*/
|
||||
function getMcpServersFromSettings(filePath) {
|
||||
const config = safeReadJson(filePath);
|
||||
if (!config) return {};
|
||||
return config.mcpServers || {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get MCP configuration from multiple sources:
|
||||
* 1. ~/.claude.json (project-level MCP servers)
|
||||
* 2. ~/.claude/settings.json and settings.local.json (global MCP servers)
|
||||
* 3. Each workspace's .claude/settings.json and settings.local.json
|
||||
* @returns {Object}
|
||||
*/
|
||||
function getMcpConfig() {
|
||||
try {
|
||||
if (!existsSync(CLAUDE_CONFIG_PATH)) {
|
||||
return { projects: {} };
|
||||
const result = { projects: {}, globalServers: {} };
|
||||
|
||||
// 1. Read from ~/.claude.json (primary source for project MCP)
|
||||
if (existsSync(CLAUDE_CONFIG_PATH)) {
|
||||
const content = readFileSync(CLAUDE_CONFIG_PATH, 'utf8');
|
||||
const config = JSON.parse(content);
|
||||
result.projects = config.projects || {};
|
||||
}
|
||||
const content = readFileSync(CLAUDE_CONFIG_PATH, 'utf8');
|
||||
const config = JSON.parse(content);
|
||||
return {
|
||||
projects: config.projects || {}
|
||||
};
|
||||
|
||||
// 2. Read global MCP servers from ~/.claude/settings.json and settings.local.json
|
||||
const globalSettings = getMcpServersFromSettings(CLAUDE_GLOBAL_SETTINGS);
|
||||
const globalSettingsLocal = getMcpServersFromSettings(CLAUDE_GLOBAL_SETTINGS_LOCAL);
|
||||
result.globalServers = { ...globalSettings, ...globalSettingsLocal };
|
||||
|
||||
// 3. For each project, also check .claude/settings.json and settings.local.json
|
||||
for (const projectPath of Object.keys(result.projects)) {
|
||||
const projectClaudeDir = join(projectPath, '.claude');
|
||||
const projectSettings = join(projectClaudeDir, 'settings.json');
|
||||
const projectSettingsLocal = join(projectClaudeDir, 'settings.local.json');
|
||||
|
||||
// Merge MCP servers from workspace settings into project config
|
||||
const workspaceServers = {
|
||||
...getMcpServersFromSettings(projectSettings),
|
||||
...getMcpServersFromSettings(projectSettingsLocal)
|
||||
};
|
||||
|
||||
if (Object.keys(workspaceServers).length > 0) {
|
||||
// Merge workspace servers with existing project servers (workspace takes precedence)
|
||||
result.projects[projectPath] = {
|
||||
...result.projects[projectPath],
|
||||
mcpServers: {
|
||||
...(result.projects[projectPath]?.mcpServers || {}),
|
||||
...workspaceServers
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error('Error reading MCP config:', error);
|
||||
return { projects: {}, error: error.message };
|
||||
return { projects: {}, globalServers: {}, error: error.message };
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user