claude-code-workflow 6.0.4 → 6.0.5
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/ccw/package.json +6 -6
- package/ccw/src/cli.js +9 -0
- package/ccw/src/commands/stop.js +101 -0
- package/ccw/src/core/server.js +91 -10
- package/ccw/src/templates/dashboard-js/components/mcp-manager.js +13 -1
- package/ccw/src/templates/dashboard-js/views/mcp-manager.js +73 -4
- package/ccw/src/templates/dashboard.css +73 -0
- package/ccw/src/utils/browser-launcher.js +15 -4
- package/package.json +1 -1
package/ccw/package.json
CHANGED
|
@@ -24,15 +24,15 @@
|
|
|
24
24
|
"node": ">=16.0.0"
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"
|
|
28
|
-
"open": "^9.1.0",
|
|
27
|
+
"boxen": "^7.1.0",
|
|
29
28
|
"chalk": "^5.3.0",
|
|
29
|
+
"commander": "^11.0.0",
|
|
30
|
+
"figlet": "^1.7.0",
|
|
30
31
|
"glob": "^10.3.0",
|
|
32
|
+
"gradient-string": "^2.0.2",
|
|
31
33
|
"inquirer": "^9.2.0",
|
|
32
|
-
"
|
|
33
|
-
"
|
|
34
|
-
"boxen": "^7.1.0",
|
|
35
|
-
"gradient-string": "^2.0.2"
|
|
34
|
+
"open": "^9.1.0",
|
|
35
|
+
"ora": "^7.0.0"
|
|
36
36
|
},
|
|
37
37
|
"files": [
|
|
38
38
|
"bin/",
|
package/ccw/src/cli.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
2
|
import { viewCommand } from './commands/view.js';
|
|
3
3
|
import { serveCommand } from './commands/serve.js';
|
|
4
|
+
import { stopCommand } from './commands/stop.js';
|
|
4
5
|
import { installCommand } from './commands/install.js';
|
|
5
6
|
import { uninstallCommand } from './commands/uninstall.js';
|
|
6
7
|
import { upgradeCommand } from './commands/upgrade.js';
|
|
@@ -68,6 +69,14 @@ export function run(argv) {
|
|
|
68
69
|
.option('--no-browser', 'Start server without opening browser')
|
|
69
70
|
.action(serveCommand);
|
|
70
71
|
|
|
72
|
+
// Stop command
|
|
73
|
+
program
|
|
74
|
+
.command('stop')
|
|
75
|
+
.description('Stop the running CCW dashboard server')
|
|
76
|
+
.option('--port <port>', 'Server port', '3456')
|
|
77
|
+
.option('-f, --force', 'Force kill process on the port')
|
|
78
|
+
.action(stopCommand);
|
|
79
|
+
|
|
71
80
|
// Install command
|
|
72
81
|
program
|
|
73
82
|
.command('install')
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { exec } from 'child_process';
|
|
3
|
+
import { promisify } from 'util';
|
|
4
|
+
|
|
5
|
+
const execAsync = promisify(exec);
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Find process using a specific port (Windows)
|
|
9
|
+
* @param {number} port - Port number
|
|
10
|
+
* @returns {Promise<string|null>} PID or null
|
|
11
|
+
*/
|
|
12
|
+
async function findProcessOnPort(port) {
|
|
13
|
+
try {
|
|
14
|
+
const { stdout } = await execAsync(`netstat -ano | findstr :${port} | findstr LISTENING`);
|
|
15
|
+
const lines = stdout.trim().split('\n');
|
|
16
|
+
if (lines.length > 0) {
|
|
17
|
+
const parts = lines[0].trim().split(/\s+/);
|
|
18
|
+
return parts[parts.length - 1]; // PID is the last column
|
|
19
|
+
}
|
|
20
|
+
} catch {
|
|
21
|
+
// No process found
|
|
22
|
+
}
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Kill process by PID (Windows)
|
|
28
|
+
* @param {string} pid - Process ID
|
|
29
|
+
* @returns {Promise<boolean>} Success status
|
|
30
|
+
*/
|
|
31
|
+
async function killProcess(pid) {
|
|
32
|
+
try {
|
|
33
|
+
await execAsync(`taskkill /PID ${pid} /F`);
|
|
34
|
+
return true;
|
|
35
|
+
} catch {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Stop command handler - stops the running CCW dashboard server
|
|
42
|
+
* @param {Object} options - Command options
|
|
43
|
+
*/
|
|
44
|
+
export async function stopCommand(options) {
|
|
45
|
+
const port = options.port || 3456;
|
|
46
|
+
const force = options.force || false;
|
|
47
|
+
|
|
48
|
+
console.log(chalk.blue.bold('\n CCW Dashboard\n'));
|
|
49
|
+
console.log(chalk.gray(` Checking server on port ${port}...`));
|
|
50
|
+
|
|
51
|
+
try {
|
|
52
|
+
// Try graceful shutdown via API first
|
|
53
|
+
const healthCheck = await fetch(`http://localhost:${port}/api/health`, {
|
|
54
|
+
signal: AbortSignal.timeout(2000)
|
|
55
|
+
}).catch(() => null);
|
|
56
|
+
|
|
57
|
+
if (healthCheck && healthCheck.ok) {
|
|
58
|
+
// CCW server is running - send shutdown signal
|
|
59
|
+
console.log(chalk.cyan(' CCW server found, sending shutdown signal...'));
|
|
60
|
+
|
|
61
|
+
await fetch(`http://localhost:${port}/api/shutdown`, {
|
|
62
|
+
method: 'POST',
|
|
63
|
+
signal: AbortSignal.timeout(5000)
|
|
64
|
+
}).catch(() => null);
|
|
65
|
+
|
|
66
|
+
// Wait a moment for shutdown
|
|
67
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
68
|
+
|
|
69
|
+
console.log(chalk.green.bold('\n Server stopped successfully!\n'));
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// No CCW server responding, check if port is in use
|
|
74
|
+
const pid = await findProcessOnPort(port);
|
|
75
|
+
|
|
76
|
+
if (!pid) {
|
|
77
|
+
console.log(chalk.yellow(` No server running on port ${port}\n`));
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Port is in use by another process
|
|
82
|
+
console.log(chalk.yellow(` Port ${port} is in use by process PID: ${pid}`));
|
|
83
|
+
|
|
84
|
+
if (force) {
|
|
85
|
+
console.log(chalk.cyan(' Force killing process...'));
|
|
86
|
+
const killed = await killProcess(pid);
|
|
87
|
+
|
|
88
|
+
if (killed) {
|
|
89
|
+
console.log(chalk.green.bold('\n Process killed successfully!\n'));
|
|
90
|
+
} else {
|
|
91
|
+
console.log(chalk.red('\n Failed to kill process. Try running as administrator.\n'));
|
|
92
|
+
}
|
|
93
|
+
} else {
|
|
94
|
+
console.log(chalk.gray(`\n This is not a CCW server. Use --force to kill it:`));
|
|
95
|
+
console.log(chalk.white(` ccw stop --force\n`));
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
} catch (err) {
|
|
99
|
+
console.error(chalk.red(`\n Error: ${err.message}\n`));
|
|
100
|
+
}
|
|
101
|
+
}
|
package/ccw/src/core/server.js
CHANGED
|
@@ -8,8 +8,11 @@ import { scanSessions } from './session-scanner.js';
|
|
|
8
8
|
import { aggregateData } from './data-aggregator.js';
|
|
9
9
|
import { resolvePath, getRecentPaths, trackRecentPath, removeRecentPath, normalizePathForDisplay, getWorkflowDir } from '../utils/path-resolver.js';
|
|
10
10
|
|
|
11
|
-
// Claude config file
|
|
11
|
+
// Claude config file paths
|
|
12
12
|
const CLAUDE_CONFIG_PATH = join(homedir(), '.claude.json');
|
|
13
|
+
const CLAUDE_SETTINGS_DIR = join(homedir(), '.claude');
|
|
14
|
+
const CLAUDE_GLOBAL_SETTINGS = join(CLAUDE_SETTINGS_DIR, 'settings.json');
|
|
15
|
+
const CLAUDE_GLOBAL_SETTINGS_LOCAL = join(CLAUDE_SETTINGS_DIR, 'settings.local.json');
|
|
13
16
|
|
|
14
17
|
// WebSocket clients for real-time notifications
|
|
15
18
|
const wsClients = new Set();
|
|
@@ -160,6 +163,24 @@ export async function startServer(options = {}) {
|
|
|
160
163
|
return;
|
|
161
164
|
}
|
|
162
165
|
|
|
166
|
+
// API: Shutdown server (for ccw stop command)
|
|
167
|
+
if (pathname === '/api/shutdown' && req.method === 'POST') {
|
|
168
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
169
|
+
res.end(JSON.stringify({ status: 'shutting_down' }));
|
|
170
|
+
|
|
171
|
+
// Graceful shutdown
|
|
172
|
+
console.log('\n Received shutdown signal...');
|
|
173
|
+
setTimeout(() => {
|
|
174
|
+
server.close(() => {
|
|
175
|
+
console.log(' Server stopped.\n');
|
|
176
|
+
process.exit(0);
|
|
177
|
+
});
|
|
178
|
+
// Force exit after 3 seconds if graceful shutdown fails
|
|
179
|
+
setTimeout(() => process.exit(0), 3000);
|
|
180
|
+
}, 100);
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
|
|
163
184
|
// API: Remove a recent path
|
|
164
185
|
if (pathname === '/api/remove-recent-path' && req.method === 'POST') {
|
|
165
186
|
handlePostRequest(req, res, async (body) => {
|
|
@@ -1006,22 +1027,82 @@ async function loadRecentPaths() {
|
|
|
1006
1027
|
// ========================================
|
|
1007
1028
|
|
|
1008
1029
|
/**
|
|
1009
|
-
*
|
|
1030
|
+
* Safely read and parse JSON file
|
|
1031
|
+
* @param {string} filePath
|
|
1032
|
+
* @returns {Object|null}
|
|
1033
|
+
*/
|
|
1034
|
+
function safeReadJson(filePath) {
|
|
1035
|
+
try {
|
|
1036
|
+
if (!existsSync(filePath)) return null;
|
|
1037
|
+
const content = readFileSync(filePath, 'utf8');
|
|
1038
|
+
return JSON.parse(content);
|
|
1039
|
+
} catch {
|
|
1040
|
+
return null;
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
/**
|
|
1045
|
+
* Get MCP servers from a settings file
|
|
1046
|
+
* @param {string} filePath
|
|
1047
|
+
* @returns {Object} mcpServers object or empty object
|
|
1048
|
+
*/
|
|
1049
|
+
function getMcpServersFromSettings(filePath) {
|
|
1050
|
+
const config = safeReadJson(filePath);
|
|
1051
|
+
if (!config) return {};
|
|
1052
|
+
return config.mcpServers || {};
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
/**
|
|
1056
|
+
* Get MCP configuration from multiple sources:
|
|
1057
|
+
* 1. ~/.claude.json (project-level MCP servers)
|
|
1058
|
+
* 2. ~/.claude/settings.json and settings.local.json (global MCP servers)
|
|
1059
|
+
* 3. Each workspace's .claude/settings.json and settings.local.json
|
|
1010
1060
|
* @returns {Object}
|
|
1011
1061
|
*/
|
|
1012
1062
|
function getMcpConfig() {
|
|
1013
1063
|
try {
|
|
1014
|
-
|
|
1015
|
-
|
|
1064
|
+
const result = { projects: {}, globalServers: {} };
|
|
1065
|
+
|
|
1066
|
+
// 1. Read from ~/.claude.json (primary source for project MCP)
|
|
1067
|
+
if (existsSync(CLAUDE_CONFIG_PATH)) {
|
|
1068
|
+
const content = readFileSync(CLAUDE_CONFIG_PATH, 'utf8');
|
|
1069
|
+
const config = JSON.parse(content);
|
|
1070
|
+
result.projects = config.projects || {};
|
|
1016
1071
|
}
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
};
|
|
1072
|
+
|
|
1073
|
+
// 2. Read global MCP servers from ~/.claude/settings.json and settings.local.json
|
|
1074
|
+
const globalSettings = getMcpServersFromSettings(CLAUDE_GLOBAL_SETTINGS);
|
|
1075
|
+
const globalSettingsLocal = getMcpServersFromSettings(CLAUDE_GLOBAL_SETTINGS_LOCAL);
|
|
1076
|
+
result.globalServers = { ...globalSettings, ...globalSettingsLocal };
|
|
1077
|
+
|
|
1078
|
+
// 3. For each project, also check .claude/settings.json and settings.local.json
|
|
1079
|
+
for (const projectPath of Object.keys(result.projects)) {
|
|
1080
|
+
const projectClaudeDir = join(projectPath, '.claude');
|
|
1081
|
+
const projectSettings = join(projectClaudeDir, 'settings.json');
|
|
1082
|
+
const projectSettingsLocal = join(projectClaudeDir, 'settings.local.json');
|
|
1083
|
+
|
|
1084
|
+
// Merge MCP servers from workspace settings into project config
|
|
1085
|
+
const workspaceServers = {
|
|
1086
|
+
...getMcpServersFromSettings(projectSettings),
|
|
1087
|
+
...getMcpServersFromSettings(projectSettingsLocal)
|
|
1088
|
+
};
|
|
1089
|
+
|
|
1090
|
+
if (Object.keys(workspaceServers).length > 0) {
|
|
1091
|
+
// Merge workspace servers with existing project servers (workspace takes precedence)
|
|
1092
|
+
result.projects[projectPath] = {
|
|
1093
|
+
...result.projects[projectPath],
|
|
1094
|
+
mcpServers: {
|
|
1095
|
+
...(result.projects[projectPath]?.mcpServers || {}),
|
|
1096
|
+
...workspaceServers
|
|
1097
|
+
}
|
|
1098
|
+
};
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
return result;
|
|
1022
1103
|
} catch (error) {
|
|
1023
1104
|
console.error('Error reading MCP config:', error);
|
|
1024
|
-
return { projects: {}, error: error.message };
|
|
1105
|
+
return { projects: {}, globalServers: {}, error: error.message };
|
|
1025
1106
|
}
|
|
1026
1107
|
}
|
|
1027
1108
|
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
// ========== MCP State ==========
|
|
5
5
|
let mcpConfig = null;
|
|
6
6
|
let mcpAllProjects = {};
|
|
7
|
+
let mcpGlobalServers = {};
|
|
7
8
|
let mcpCurrentProjectServers = {};
|
|
8
9
|
let mcpCreateMode = 'form'; // 'form' or 'json'
|
|
9
10
|
|
|
@@ -31,6 +32,7 @@ async function loadMcpConfig() {
|
|
|
31
32
|
const data = await response.json();
|
|
32
33
|
mcpConfig = data;
|
|
33
34
|
mcpAllProjects = data.projects || {};
|
|
35
|
+
mcpGlobalServers = data.globalServers || {};
|
|
34
36
|
|
|
35
37
|
// Get current project servers
|
|
36
38
|
const currentPath = projectPath.replace(/\//g, '\\');
|
|
@@ -150,6 +152,15 @@ function updateMcpBadge() {
|
|
|
150
152
|
function getAllAvailableMcpServers() {
|
|
151
153
|
const allServers = {};
|
|
152
154
|
|
|
155
|
+
// Collect global servers first
|
|
156
|
+
for (const [name, serverConfig] of Object.entries(mcpGlobalServers)) {
|
|
157
|
+
allServers[name] = {
|
|
158
|
+
config: serverConfig,
|
|
159
|
+
usedIn: [],
|
|
160
|
+
isGlobal: true
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
153
164
|
// Collect servers from all projects
|
|
154
165
|
for (const [path, config] of Object.entries(mcpAllProjects)) {
|
|
155
166
|
const servers = config.mcpServers || {};
|
|
@@ -157,7 +168,8 @@ function getAllAvailableMcpServers() {
|
|
|
157
168
|
if (!allServers[name]) {
|
|
158
169
|
allServers[name] = {
|
|
159
170
|
config: serverConfig,
|
|
160
|
-
usedIn: []
|
|
171
|
+
usedIn: [],
|
|
172
|
+
isGlobal: false
|
|
161
173
|
};
|
|
162
174
|
}
|
|
163
175
|
allServers[name].usedIn.push(path);
|
|
@@ -26,8 +26,12 @@ async function renderMcpManager() {
|
|
|
26
26
|
|
|
27
27
|
// Separate current project servers and available servers
|
|
28
28
|
const currentProjectServerNames = Object.keys(projectServers);
|
|
29
|
-
|
|
29
|
+
|
|
30
|
+
// Separate global servers and project servers that are not in current project
|
|
31
|
+
const globalServerEntries = Object.entries(mcpGlobalServers)
|
|
30
32
|
.filter(([name]) => !currentProjectServerNames.includes(name));
|
|
33
|
+
const otherProjectServers = Object.entries(allAvailableServers)
|
|
34
|
+
.filter(([name, info]) => !currentProjectServerNames.includes(name) && !info.isGlobal);
|
|
31
35
|
|
|
32
36
|
container.innerHTML = `
|
|
33
37
|
<div class="mcp-manager">
|
|
@@ -61,20 +65,39 @@ async function renderMcpManager() {
|
|
|
61
65
|
`}
|
|
62
66
|
</div>
|
|
63
67
|
|
|
68
|
+
<!-- Global MCP Servers -->
|
|
69
|
+
${globalServerEntries.length > 0 ? `
|
|
70
|
+
<div class="mcp-section mb-6">
|
|
71
|
+
<div class="flex items-center justify-between mb-4">
|
|
72
|
+
<div class="flex items-center gap-2">
|
|
73
|
+
<span class="text-lg">🌐</span>
|
|
74
|
+
<h3 class="text-lg font-semibold text-foreground">Global MCP Servers</h3>
|
|
75
|
+
</div>
|
|
76
|
+
<span class="text-sm text-muted-foreground">${globalServerEntries.length} servers from ~/.claude/settings</span>
|
|
77
|
+
</div>
|
|
78
|
+
|
|
79
|
+
<div class="mcp-server-grid grid gap-3">
|
|
80
|
+
${globalServerEntries.map(([serverName, serverConfig]) => {
|
|
81
|
+
return renderGlobalServerCard(serverName, serverConfig);
|
|
82
|
+
}).join('')}
|
|
83
|
+
</div>
|
|
84
|
+
</div>
|
|
85
|
+
` : ''}
|
|
86
|
+
|
|
64
87
|
<!-- Available MCP Servers from Other Projects -->
|
|
65
88
|
<div class="mcp-section">
|
|
66
89
|
<div class="flex items-center justify-between mb-4">
|
|
67
90
|
<h3 class="text-lg font-semibold text-foreground">Available from Other Projects</h3>
|
|
68
|
-
<span class="text-sm text-muted-foreground">${
|
|
91
|
+
<span class="text-sm text-muted-foreground">${otherProjectServers.length} servers available</span>
|
|
69
92
|
</div>
|
|
70
93
|
|
|
71
|
-
${
|
|
94
|
+
${otherProjectServers.length === 0 ? `
|
|
72
95
|
<div class="mcp-empty-state bg-card border border-border rounded-lg p-6 text-center">
|
|
73
96
|
<p class="text-muted-foreground">No additional MCP servers found in other projects</p>
|
|
74
97
|
</div>
|
|
75
98
|
` : `
|
|
76
99
|
<div class="mcp-server-grid grid gap-3">
|
|
77
|
-
${
|
|
100
|
+
${otherProjectServers.map(([serverName, serverInfo]) => {
|
|
78
101
|
return renderAvailableServerCard(serverName, serverInfo);
|
|
79
102
|
}).join('')}
|
|
80
103
|
</div>
|
|
@@ -240,6 +263,52 @@ function renderAvailableServerCard(serverName, serverInfo) {
|
|
|
240
263
|
`;
|
|
241
264
|
}
|
|
242
265
|
|
|
266
|
+
function renderGlobalServerCard(serverName, serverConfig) {
|
|
267
|
+
const command = serverConfig.command || 'N/A';
|
|
268
|
+
const args = serverConfig.args || [];
|
|
269
|
+
const hasEnv = serverConfig.env && Object.keys(serverConfig.env).length > 0;
|
|
270
|
+
|
|
271
|
+
return `
|
|
272
|
+
<div class="mcp-server-card mcp-server-global bg-card border border-primary/30 rounded-lg p-4 hover:shadow-md transition-all">
|
|
273
|
+
<div class="flex items-start justify-between mb-3">
|
|
274
|
+
<div class="flex items-center gap-2">
|
|
275
|
+
<span class="text-xl">🌐</span>
|
|
276
|
+
<h4 class="font-semibold text-foreground">${escapeHtml(serverName)}</h4>
|
|
277
|
+
<span class="text-xs px-2 py-0.5 bg-primary/10 text-primary rounded-full">Global</span>
|
|
278
|
+
</div>
|
|
279
|
+
<button class="px-3 py-1 text-xs bg-primary text-primary-foreground rounded hover:opacity-90 transition-opacity"
|
|
280
|
+
data-server-name="${escapeHtml(serverName)}"
|
|
281
|
+
data-server-config='${JSON.stringify(serverConfig).replace(/'/g, "'")}'
|
|
282
|
+
data-action="add">
|
|
283
|
+
Add to Project
|
|
284
|
+
</button>
|
|
285
|
+
</div>
|
|
286
|
+
|
|
287
|
+
<div class="mcp-server-details text-sm space-y-1">
|
|
288
|
+
<div class="flex items-center gap-2 text-muted-foreground">
|
|
289
|
+
<span class="font-mono text-xs bg-muted px-1.5 py-0.5 rounded">cmd</span>
|
|
290
|
+
<span class="truncate" title="${escapeHtml(command)}">${escapeHtml(command)}</span>
|
|
291
|
+
</div>
|
|
292
|
+
${args.length > 0 ? `
|
|
293
|
+
<div class="flex items-start gap-2 text-muted-foreground">
|
|
294
|
+
<span class="font-mono text-xs bg-muted px-1.5 py-0.5 rounded shrink-0">args</span>
|
|
295
|
+
<span class="text-xs font-mono truncate" title="${escapeHtml(args.join(' '))}">${escapeHtml(args.slice(0, 3).join(' '))}${args.length > 3 ? '...' : ''}</span>
|
|
296
|
+
</div>
|
|
297
|
+
` : ''}
|
|
298
|
+
${hasEnv ? `
|
|
299
|
+
<div class="flex items-center gap-2 text-muted-foreground">
|
|
300
|
+
<span class="font-mono text-xs bg-muted px-1.5 py-0.5 rounded">env</span>
|
|
301
|
+
<span class="text-xs">${Object.keys(serverConfig.env).length} variables</span>
|
|
302
|
+
</div>
|
|
303
|
+
` : ''}
|
|
304
|
+
<div class="flex items-center gap-2 text-muted-foreground mt-1">
|
|
305
|
+
<span class="text-xs italic">Available to all projects from ~/.claude/settings</span>
|
|
306
|
+
</div>
|
|
307
|
+
</div>
|
|
308
|
+
</div>
|
|
309
|
+
`;
|
|
310
|
+
}
|
|
311
|
+
|
|
243
312
|
function attachMcpEventListeners() {
|
|
244
313
|
// Toggle switches
|
|
245
314
|
document.querySelectorAll('.mcp-server-card input[data-action="toggle"]').forEach(input => {
|
|
@@ -8112,3 +8112,76 @@ code.ctx-meta-chip-value {
|
|
|
8112
8112
|
opacity: 0.5;
|
|
8113
8113
|
cursor: not-allowed;
|
|
8114
8114
|
}
|
|
8115
|
+
|
|
8116
|
+
/* Path Input Group */
|
|
8117
|
+
.path-input-group {
|
|
8118
|
+
display: flex;
|
|
8119
|
+
align-items: center;
|
|
8120
|
+
gap: 0.75rem;
|
|
8121
|
+
flex-wrap: wrap;
|
|
8122
|
+
}
|
|
8123
|
+
|
|
8124
|
+
.path-input-group label {
|
|
8125
|
+
font-size: 0.875rem;
|
|
8126
|
+
color: hsl(var(--muted-foreground));
|
|
8127
|
+
white-space: nowrap;
|
|
8128
|
+
}
|
|
8129
|
+
|
|
8130
|
+
.path-input-group input {
|
|
8131
|
+
flex: 1;
|
|
8132
|
+
min-width: 200px;
|
|
8133
|
+
padding: 0.625rem 0.875rem;
|
|
8134
|
+
background: hsl(var(--background));
|
|
8135
|
+
border: 1px solid hsl(var(--border));
|
|
8136
|
+
border-radius: 0.375rem;
|
|
8137
|
+
font-size: 0.875rem;
|
|
8138
|
+
font-family: var(--font-mono);
|
|
8139
|
+
color: hsl(var(--foreground));
|
|
8140
|
+
outline: none;
|
|
8141
|
+
transition: border-color 0.15s, box-shadow 0.15s;
|
|
8142
|
+
}
|
|
8143
|
+
|
|
8144
|
+
.path-input-group input:focus {
|
|
8145
|
+
border-color: hsl(var(--primary));
|
|
8146
|
+
box-shadow: 0 0 0 3px hsl(var(--primary) / 0.1);
|
|
8147
|
+
}
|
|
8148
|
+
|
|
8149
|
+
.path-input-group input::placeholder {
|
|
8150
|
+
color: hsl(var(--muted-foreground));
|
|
8151
|
+
}
|
|
8152
|
+
|
|
8153
|
+
.path-go-btn {
|
|
8154
|
+
padding: 0.625rem 1.25rem;
|
|
8155
|
+
background: hsl(var(--primary));
|
|
8156
|
+
color: white;
|
|
8157
|
+
border: none;
|
|
8158
|
+
border-radius: 0.375rem;
|
|
8159
|
+
font-size: 0.875rem;
|
|
8160
|
+
font-weight: 500;
|
|
8161
|
+
cursor: pointer;
|
|
8162
|
+
transition: all 0.15s;
|
|
8163
|
+
white-space: nowrap;
|
|
8164
|
+
}
|
|
8165
|
+
|
|
8166
|
+
.path-go-btn:hover {
|
|
8167
|
+
background: hsl(var(--primary) / 0.9);
|
|
8168
|
+
transform: translateY(-1px);
|
|
8169
|
+
}
|
|
8170
|
+
|
|
8171
|
+
.path-go-btn:active {
|
|
8172
|
+
transform: translateY(0);
|
|
8173
|
+
}
|
|
8174
|
+
|
|
8175
|
+
/* Selected Folder Display */
|
|
8176
|
+
.selected-folder {
|
|
8177
|
+
padding: 0.75rem 1rem;
|
|
8178
|
+
background: hsl(var(--muted));
|
|
8179
|
+
border-radius: 0.5rem;
|
|
8180
|
+
margin-bottom: 0.75rem;
|
|
8181
|
+
}
|
|
8182
|
+
|
|
8183
|
+
.selected-folder strong {
|
|
8184
|
+
font-size: 1rem;
|
|
8185
|
+
color: hsl(var(--foreground));
|
|
8186
|
+
font-family: var(--font-mono);
|
|
8187
|
+
}
|
|
@@ -3,13 +3,24 @@ import { platform } from 'os';
|
|
|
3
3
|
import { resolve } from 'path';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
* Launch a file in the default browser
|
|
6
|
+
* Launch a URL or file in the default browser
|
|
7
7
|
* Cross-platform compatible (Windows/macOS/Linux)
|
|
8
|
-
* @param {string}
|
|
8
|
+
* @param {string} urlOrPath - HTTP URL or path to HTML file
|
|
9
9
|
* @returns {Promise<void>}
|
|
10
10
|
*/
|
|
11
|
-
export async function launchBrowser(
|
|
12
|
-
|
|
11
|
+
export async function launchBrowser(urlOrPath) {
|
|
12
|
+
// Check if it's already a URL (http:// or https://)
|
|
13
|
+
if (urlOrPath.startsWith('http://') || urlOrPath.startsWith('https://')) {
|
|
14
|
+
try {
|
|
15
|
+
await open(urlOrPath);
|
|
16
|
+
return;
|
|
17
|
+
} catch (error) {
|
|
18
|
+
throw new Error(`Failed to open browser: ${error.message}`);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// It's a file path - convert to file:// URL
|
|
23
|
+
const absolutePath = resolve(urlOrPath);
|
|
13
24
|
|
|
14
25
|
// Construct file:// URL based on platform
|
|
15
26
|
let url;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-code-workflow",
|
|
3
|
-
"version": "6.0.
|
|
3
|
+
"version": "6.0.5",
|
|
4
4
|
"description": "JSON-driven multi-agent development framework with intelligent CLI orchestration (Gemini/Qwen/Codex), context-first architecture, and automated workflow execution",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "ccw/src/index.js",
|