diff --git a/ccw/src/commands/view.js b/ccw/src/commands/view.js index 4bbb7779..7eac4f1a 100644 --- a/ccw/src/commands/view.js +++ b/ccw/src/commands/view.js @@ -1,14 +1,105 @@ import { serveCommand } from './serve.js'; +import { launchBrowser } from '../utils/browser-launcher.js'; +import { validatePath } from '../utils/path-resolver.js'; +import chalk from 'chalk'; /** - * View command handler - starts dashboard server (unified with serve mode) + * Check if server is already running on the specified port + * @param {number} port - Port to check + * @returns {Promise} True if server is running + */ +async function isServerRunning(port) { + try { + const controller = new AbortController(); + const timeoutId = setTimeout(() => controller.abort(), 1000); + + const response = await fetch(`http://localhost:${port}/api/health`, { + signal: controller.signal + }); + clearTimeout(timeoutId); + + return response.ok; + } catch { + return false; + } +} + +/** + * Switch workspace on running server + * @param {number} port - Server port + * @param {string} path - New workspace path + * @returns {Promise} Result with success status + */ +async function switchWorkspace(port, path) { + try { + const response = await fetch( + `http://localhost:${port}/api/switch-path?path=${encodeURIComponent(path)}` + ); + return await response.json(); + } catch (err) { + return { success: false, error: err.message }; + } +} + +/** + * View command handler - opens dashboard for current workspace + * If server is already running, switches workspace and opens browser + * If not running, starts a new server * @param {Object} options - Command options */ export async function viewCommand(options) { - // Forward to serve command with same options - await serveCommand({ - path: options.path, - port: options.port || 3456, - browser: options.browser - }); + const port = options.port || 3456; + + // Resolve workspace path + let workspacePath = process.cwd(); + if (options.path) { + const pathValidation = validatePath(options.path, { mustExist: true }); + if (!pathValidation.valid) { + console.error(chalk.red(`\n Error: ${pathValidation.error}\n`)); + process.exit(1); + } + workspacePath = pathValidation.path; + } + + // Check if server is already running + const serverRunning = await isServerRunning(port); + + if (serverRunning) { + // Server is running - switch workspace and open browser + console.log(chalk.blue.bold('\n CCW Dashboard\n')); + console.log(chalk.gray(` Server already running on port ${port}`)); + console.log(chalk.cyan(` Switching workspace to: ${workspacePath}`)); + + const result = await switchWorkspace(port, workspacePath); + + if (result.success) { + console.log(chalk.green(` Workspace switched successfully`)); + + // Open browser with the new path + const url = `http://localhost:${port}/?path=${encodeURIComponent(result.path)}`; + + if (options.browser !== false) { + console.log(chalk.cyan(' Opening in browser...')); + try { + await launchBrowser(url); + console.log(chalk.green.bold('\n Dashboard opened!\n')); + } catch (err) { + console.log(chalk.yellow(`\n Could not open browser: ${err.message}`)); + console.log(chalk.gray(` Open manually: ${url}\n`)); + } + } else { + console.log(chalk.gray(`\n URL: ${url}\n`)); + } + } else { + console.error(chalk.red(`\n Failed to switch workspace: ${result.error}\n`)); + process.exit(1); + } + } else { + // Server not running - start new server + await serveCommand({ + path: workspacePath, + port: port, + browser: options.browser + }); + } } diff --git a/ccw/src/core/server.js b/ccw/src/core/server.js index 4a5f5f27..cc5989a6 100644 --- a/ccw/src/core/server.js +++ b/ccw/src/core/server.js @@ -126,6 +126,40 @@ export async function startServer(options = {}) { return; } + // API: Switch workspace path (for ccw view command) + if (pathname === '/api/switch-path') { + const newPath = url.searchParams.get('path'); + if (!newPath) { + res.writeHead(400, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ error: 'Path is required' })); + return; + } + + const resolved = resolvePath(newPath); + if (!existsSync(resolved)) { + res.writeHead(404, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ error: 'Path does not exist' })); + return; + } + + // Track the path and return success + trackRecentPath(resolved); + res.writeHead(200, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ + success: true, + path: resolved, + recentPaths: getRecentPaths() + })); + return; + } + + // API: Health check (for ccw view to detect running server) + if (pathname === '/api/health') { + res.writeHead(200, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ status: 'ok', timestamp: Date.now() })); + return; + } + // API: Remove a recent path if (pathname === '/api/remove-recent-path' && req.method === 'POST') { handlePostRequest(req, res, async (body) => { diff --git a/ccw/src/templates/dashboard-js/main.js b/ccw/src/templates/dashboard-js/main.js index 48a2277c..cdb22452 100644 --- a/ccw/src/templates/dashboard-js/main.js +++ b/ccw/src/templates/dashboard-js/main.js @@ -20,7 +20,17 @@ document.addEventListener('DOMContentLoaded', async () => { // Server mode: load data from API try { if (window.SERVER_MODE) { - await switchToPath(window.INITIAL_PATH || projectPath); + // Check URL for path parameter (from ccw view command) + const urlParams = new URLSearchParams(window.location.search); + const urlPath = urlParams.get('path'); + const initialPath = urlPath || window.INITIAL_PATH || projectPath; + + await switchToPath(initialPath); + + // Clean up URL after loading (remove query param) + if (urlPath && window.history.replaceState) { + window.history.replaceState({}, '', window.location.pathname); + } } else { renderDashboard(); } diff --git a/package.json b/package.json index 893016e6..c9a15233 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "claude-code-workflow", - "version": "6.0.2", + "version": "6.0.3", "description": "JSON-driven multi-agent development framework with intelligent CLI orchestration (Gemini/Qwen/Codex), context-first architecture, and automated workflow execution", "type": "module", "main": "ccw/src/index.js",