mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-10 02:24:35 +08:00
Compare commits
7 Commits
v6.0.2
...
copilot/fi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
de3d15c099 | ||
|
|
9232947fb0 | ||
|
|
8b29f6bb7c | ||
|
|
27273405f7 | ||
|
|
f4299457fb | ||
|
|
06983a35ad | ||
|
|
a80953527b |
@@ -24,15 +24,15 @@
|
|||||||
"node": ">=16.0.0"
|
"node": ">=16.0.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"commander": "^11.0.0",
|
|
||||||
"open": "^9.1.0",
|
|
||||||
"chalk": "^5.3.0",
|
|
||||||
"glob": "^10.3.0",
|
|
||||||
"inquirer": "^9.2.0",
|
|
||||||
"ora": "^7.0.0",
|
|
||||||
"figlet": "^1.7.0",
|
|
||||||
"boxen": "^7.1.0",
|
"boxen": "^7.1.0",
|
||||||
"gradient-string": "^2.0.2"
|
"chalk": "^5.3.0",
|
||||||
|
"commander": "^11.0.0",
|
||||||
|
"figlet": "^1.7.0",
|
||||||
|
"glob": "^10.3.0",
|
||||||
|
"gradient-string": "^2.0.2",
|
||||||
|
"inquirer": "^9.2.0",
|
||||||
|
"open": "^9.1.0",
|
||||||
|
"ora": "^7.0.0"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"bin/",
|
"bin/",
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { Command } from 'commander';
|
import { Command } from 'commander';
|
||||||
import { viewCommand } from './commands/view.js';
|
import { viewCommand } from './commands/view.js';
|
||||||
import { serveCommand } from './commands/serve.js';
|
import { serveCommand } from './commands/serve.js';
|
||||||
|
import { stopCommand } from './commands/stop.js';
|
||||||
import { installCommand } from './commands/install.js';
|
import { installCommand } from './commands/install.js';
|
||||||
import { uninstallCommand } from './commands/uninstall.js';
|
import { uninstallCommand } from './commands/uninstall.js';
|
||||||
import { upgradeCommand } from './commands/upgrade.js';
|
import { upgradeCommand } from './commands/upgrade.js';
|
||||||
@@ -68,6 +69,14 @@ export function run(argv) {
|
|||||||
.option('--no-browser', 'Start server without opening browser')
|
.option('--no-browser', 'Start server without opening browser')
|
||||||
.action(serveCommand);
|
.action(serveCommand);
|
||||||
|
|
||||||
|
// Stop command
|
||||||
|
program
|
||||||
|
.command('stop')
|
||||||
|
.description('Stop the running CCW dashboard server')
|
||||||
|
.option('--port <port>', 'Server port', '3456')
|
||||||
|
.option('-f, --force', 'Force kill process on the port')
|
||||||
|
.action(stopCommand);
|
||||||
|
|
||||||
// Install command
|
// Install command
|
||||||
program
|
program
|
||||||
.command('install')
|
.command('install')
|
||||||
|
|||||||
101
ccw/src/commands/stop.js
Normal file
101
ccw/src/commands/stop.js
Normal 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`));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,14 +1,105 @@
|
|||||||
import { serveCommand } from './serve.js';
|
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<boolean>} 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<Object>} 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
|
* @param {Object} options - Command options
|
||||||
*/
|
*/
|
||||||
export async function viewCommand(options) {
|
export async function viewCommand(options) {
|
||||||
// Forward to serve command with same options
|
const port = options.port || 3456;
|
||||||
await serveCommand({
|
|
||||||
path: options.path,
|
// Resolve workspace path
|
||||||
port: options.port || 3456,
|
let workspacePath = process.cwd();
|
||||||
browser: options.browser
|
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
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,8 +8,11 @@ import { scanSessions } from './session-scanner.js';
|
|||||||
import { aggregateData } from './data-aggregator.js';
|
import { aggregateData } from './data-aggregator.js';
|
||||||
import { resolvePath, getRecentPaths, trackRecentPath, removeRecentPath, normalizePathForDisplay, getWorkflowDir } from '../utils/path-resolver.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_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
|
// WebSocket clients for real-time notifications
|
||||||
const wsClients = new Set();
|
const wsClients = new Set();
|
||||||
@@ -126,6 +129,58 @@ export async function startServer(options = {}) {
|
|||||||
return;
|
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: 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
|
// API: Remove a recent path
|
||||||
if (pathname === '/api/remove-recent-path' && req.method === 'POST') {
|
if (pathname === '/api/remove-recent-path' && req.method === 'POST') {
|
||||||
handlePostRequest(req, res, async (body) => {
|
handlePostRequest(req, res, async (body) => {
|
||||||
@@ -972,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}
|
* @returns {Object}
|
||||||
*/
|
*/
|
||||||
function getMcpConfig() {
|
function getMcpConfig() {
|
||||||
try {
|
try {
|
||||||
if (!existsSync(CLAUDE_CONFIG_PATH)) {
|
const result = { projects: {}, globalServers: {} };
|
||||||
return { projects: {} };
|
|
||||||
|
// 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);
|
// 2. Read global MCP servers from ~/.claude/settings.json and settings.local.json
|
||||||
return {
|
const globalSettings = getMcpServersFromSettings(CLAUDE_GLOBAL_SETTINGS);
|
||||||
projects: config.projects || {}
|
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) {
|
} catch (error) {
|
||||||
console.error('Error reading MCP config:', error);
|
console.error('Error reading MCP config:', error);
|
||||||
return { projects: {}, error: error.message };
|
return { projects: {}, globalServers: {}, error: error.message };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
// ========== MCP State ==========
|
// ========== MCP State ==========
|
||||||
let mcpConfig = null;
|
let mcpConfig = null;
|
||||||
let mcpAllProjects = {};
|
let mcpAllProjects = {};
|
||||||
|
let mcpGlobalServers = {};
|
||||||
let mcpCurrentProjectServers = {};
|
let mcpCurrentProjectServers = {};
|
||||||
let mcpCreateMode = 'form'; // 'form' or 'json'
|
let mcpCreateMode = 'form'; // 'form' or 'json'
|
||||||
|
|
||||||
@@ -31,6 +32,7 @@ async function loadMcpConfig() {
|
|||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
mcpConfig = data;
|
mcpConfig = data;
|
||||||
mcpAllProjects = data.projects || {};
|
mcpAllProjects = data.projects || {};
|
||||||
|
mcpGlobalServers = data.globalServers || {};
|
||||||
|
|
||||||
// Get current project servers
|
// Get current project servers
|
||||||
const currentPath = projectPath.replace(/\//g, '\\');
|
const currentPath = projectPath.replace(/\//g, '\\');
|
||||||
@@ -150,6 +152,15 @@ function updateMcpBadge() {
|
|||||||
function getAllAvailableMcpServers() {
|
function getAllAvailableMcpServers() {
|
||||||
const allServers = {};
|
const allServers = {};
|
||||||
|
|
||||||
|
// Collect global servers first
|
||||||
|
for (const [name, serverConfig] of Object.entries(mcpGlobalServers)) {
|
||||||
|
allServers[name] = {
|
||||||
|
config: serverConfig,
|
||||||
|
usedIn: [],
|
||||||
|
isGlobal: true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// Collect servers from all projects
|
// Collect servers from all projects
|
||||||
for (const [path, config] of Object.entries(mcpAllProjects)) {
|
for (const [path, config] of Object.entries(mcpAllProjects)) {
|
||||||
const servers = config.mcpServers || {};
|
const servers = config.mcpServers || {};
|
||||||
@@ -157,7 +168,8 @@ function getAllAvailableMcpServers() {
|
|||||||
if (!allServers[name]) {
|
if (!allServers[name]) {
|
||||||
allServers[name] = {
|
allServers[name] = {
|
||||||
config: serverConfig,
|
config: serverConfig,
|
||||||
usedIn: []
|
usedIn: [],
|
||||||
|
isGlobal: false
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
allServers[name].usedIn.push(path);
|
allServers[name].usedIn.push(path);
|
||||||
|
|||||||
@@ -157,9 +157,9 @@ async function refreshWorkspace() {
|
|||||||
// Reload data from server
|
// Reload data from server
|
||||||
const data = await loadDashboardData(projectPath);
|
const data = await loadDashboardData(projectPath);
|
||||||
if (data) {
|
if (data) {
|
||||||
// Update stores
|
// Update stores - clear existing properties
|
||||||
sessionDataStore = {};
|
Object.keys(sessionDataStore).forEach(k => delete sessionDataStore[k]);
|
||||||
liteTaskDataStore = {};
|
Object.keys(liteTaskDataStore).forEach(k => delete liteTaskDataStore[k]);
|
||||||
|
|
||||||
// Populate stores
|
// Populate stores
|
||||||
[...(data.activeSessions || []), ...(data.archivedSessions || [])].forEach(s => {
|
[...(data.activeSessions || []), ...(data.archivedSessions || [])].forEach(s => {
|
||||||
|
|||||||
@@ -20,7 +20,17 @@ document.addEventListener('DOMContentLoaded', async () => {
|
|||||||
// Server mode: load data from API
|
// Server mode: load data from API
|
||||||
try {
|
try {
|
||||||
if (window.SERVER_MODE) {
|
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 {
|
} else {
|
||||||
renderDashboard();
|
renderDashboard();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,8 +26,12 @@ async function renderMcpManager() {
|
|||||||
|
|
||||||
// Separate current project servers and available servers
|
// Separate current project servers and available servers
|
||||||
const currentProjectServerNames = Object.keys(projectServers);
|
const currentProjectServerNames = Object.keys(projectServers);
|
||||||
const otherAvailableServers = Object.entries(allAvailableServers)
|
|
||||||
|
// Separate global servers and project servers that are not in current project
|
||||||
|
const globalServerEntries = Object.entries(mcpGlobalServers)
|
||||||
.filter(([name]) => !currentProjectServerNames.includes(name));
|
.filter(([name]) => !currentProjectServerNames.includes(name));
|
||||||
|
const otherProjectServers = Object.entries(allAvailableServers)
|
||||||
|
.filter(([name, info]) => !currentProjectServerNames.includes(name) && !info.isGlobal);
|
||||||
|
|
||||||
container.innerHTML = `
|
container.innerHTML = `
|
||||||
<div class="mcp-manager">
|
<div class="mcp-manager">
|
||||||
@@ -61,20 +65,39 @@ async function renderMcpManager() {
|
|||||||
`}
|
`}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Global MCP Servers -->
|
||||||
|
${globalServerEntries.length > 0 ? `
|
||||||
|
<div class="mcp-section mb-6">
|
||||||
|
<div class="flex items-center justify-between mb-4">
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<span class="text-lg">🌐</span>
|
||||||
|
<h3 class="text-lg font-semibold text-foreground">Global MCP Servers</h3>
|
||||||
|
</div>
|
||||||
|
<span class="text-sm text-muted-foreground">${globalServerEntries.length} servers from ~/.claude/settings</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mcp-server-grid grid gap-3">
|
||||||
|
${globalServerEntries.map(([serverName, serverConfig]) => {
|
||||||
|
return renderGlobalServerCard(serverName, serverConfig);
|
||||||
|
}).join('')}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
` : ''}
|
||||||
|
|
||||||
<!-- Available MCP Servers from Other Projects -->
|
<!-- Available MCP Servers from Other Projects -->
|
||||||
<div class="mcp-section">
|
<div class="mcp-section">
|
||||||
<div class="flex items-center justify-between mb-4">
|
<div class="flex items-center justify-between mb-4">
|
||||||
<h3 class="text-lg font-semibold text-foreground">Available from Other Projects</h3>
|
<h3 class="text-lg font-semibold text-foreground">Available from Other Projects</h3>
|
||||||
<span class="text-sm text-muted-foreground">${otherAvailableServers.length} servers available</span>
|
<span class="text-sm text-muted-foreground">${otherProjectServers.length} servers available</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
${otherAvailableServers.length === 0 ? `
|
${otherProjectServers.length === 0 ? `
|
||||||
<div class="mcp-empty-state bg-card border border-border rounded-lg p-6 text-center">
|
<div class="mcp-empty-state bg-card border border-border rounded-lg p-6 text-center">
|
||||||
<p class="text-muted-foreground">No additional MCP servers found in other projects</p>
|
<p class="text-muted-foreground">No additional MCP servers found in other projects</p>
|
||||||
</div>
|
</div>
|
||||||
` : `
|
` : `
|
||||||
<div class="mcp-server-grid grid gap-3">
|
<div class="mcp-server-grid grid gap-3">
|
||||||
${otherAvailableServers.map(([serverName, serverInfo]) => {
|
${otherProjectServers.map(([serverName, serverInfo]) => {
|
||||||
return renderAvailableServerCard(serverName, serverInfo);
|
return renderAvailableServerCard(serverName, serverInfo);
|
||||||
}).join('')}
|
}).join('')}
|
||||||
</div>
|
</div>
|
||||||
@@ -240,6 +263,52 @@ function renderAvailableServerCard(serverName, serverInfo) {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function renderGlobalServerCard(serverName, serverConfig) {
|
||||||
|
const command = serverConfig.command || 'N/A';
|
||||||
|
const args = serverConfig.args || [];
|
||||||
|
const hasEnv = serverConfig.env && Object.keys(serverConfig.env).length > 0;
|
||||||
|
|
||||||
|
return `
|
||||||
|
<div class="mcp-server-card mcp-server-global bg-card border border-primary/30 rounded-lg p-4 hover:shadow-md transition-all">
|
||||||
|
<div class="flex items-start justify-between mb-3">
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<span class="text-xl">🌐</span>
|
||||||
|
<h4 class="font-semibold text-foreground">${escapeHtml(serverName)}</h4>
|
||||||
|
<span class="text-xs px-2 py-0.5 bg-primary/10 text-primary rounded-full">Global</span>
|
||||||
|
</div>
|
||||||
|
<button class="px-3 py-1 text-xs bg-primary text-primary-foreground rounded hover:opacity-90 transition-opacity"
|
||||||
|
data-server-name="${escapeHtml(serverName)}"
|
||||||
|
data-server-config='${JSON.stringify(serverConfig).replace(/'/g, "'")}'
|
||||||
|
data-action="add">
|
||||||
|
Add to Project
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mcp-server-details text-sm space-y-1">
|
||||||
|
<div class="flex items-center gap-2 text-muted-foreground">
|
||||||
|
<span class="font-mono text-xs bg-muted px-1.5 py-0.5 rounded">cmd</span>
|
||||||
|
<span class="truncate" title="${escapeHtml(command)}">${escapeHtml(command)}</span>
|
||||||
|
</div>
|
||||||
|
${args.length > 0 ? `
|
||||||
|
<div class="flex items-start gap-2 text-muted-foreground">
|
||||||
|
<span class="font-mono text-xs bg-muted px-1.5 py-0.5 rounded shrink-0">args</span>
|
||||||
|
<span class="text-xs font-mono truncate" title="${escapeHtml(args.join(' '))}">${escapeHtml(args.slice(0, 3).join(' '))}${args.length > 3 ? '...' : ''}</span>
|
||||||
|
</div>
|
||||||
|
` : ''}
|
||||||
|
${hasEnv ? `
|
||||||
|
<div class="flex items-center gap-2 text-muted-foreground">
|
||||||
|
<span class="font-mono text-xs bg-muted px-1.5 py-0.5 rounded">env</span>
|
||||||
|
<span class="text-xs">${Object.keys(serverConfig.env).length} variables</span>
|
||||||
|
</div>
|
||||||
|
` : ''}
|
||||||
|
<div class="flex items-center gap-2 text-muted-foreground mt-1">
|
||||||
|
<span class="text-xs italic">Available to all projects from ~/.claude/settings</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
function attachMcpEventListeners() {
|
function attachMcpEventListeners() {
|
||||||
// Toggle switches
|
// Toggle switches
|
||||||
document.querySelectorAll('.mcp-server-card input[data-action="toggle"]').forEach(input => {
|
document.querySelectorAll('.mcp-server-card input[data-action="toggle"]').forEach(input => {
|
||||||
|
|||||||
@@ -7929,3 +7929,259 @@ code.ctx-meta-chip-value {
|
|||||||
width: 16px;
|
width: 16px;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ===================================
|
||||||
|
Path Selection Modal
|
||||||
|
=================================== */
|
||||||
|
|
||||||
|
.path-modal-overlay {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: rgba(0, 0, 0, 0.5);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
z-index: 1000;
|
||||||
|
backdrop-filter: blur(2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.path-modal {
|
||||||
|
background: hsl(var(--card));
|
||||||
|
border: 1px solid hsl(var(--border));
|
||||||
|
border-radius: 0.75rem;
|
||||||
|
width: 90%;
|
||||||
|
max-width: 480px;
|
||||||
|
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.2);
|
||||||
|
animation: modal-enter 0.2s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes modal-enter {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(0.95) translateY(-10px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: scale(1) translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.path-modal-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.75rem;
|
||||||
|
padding: 1.25rem 1.5rem;
|
||||||
|
border-bottom: 1px solid hsl(var(--border));
|
||||||
|
}
|
||||||
|
|
||||||
|
.path-modal-icon {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
color: hsl(var(--primary));
|
||||||
|
}
|
||||||
|
|
||||||
|
.path-modal-header h3 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: hsl(var(--foreground));
|
||||||
|
}
|
||||||
|
|
||||||
|
.path-modal-body {
|
||||||
|
padding: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.path-modal-body p {
|
||||||
|
margin: 0 0 1rem;
|
||||||
|
color: hsl(var(--muted-foreground));
|
||||||
|
font-size: 0.9rem;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.path-modal-command {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.75rem;
|
||||||
|
background: hsl(var(--muted));
|
||||||
|
padding: 0.75rem 1rem;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
}
|
||||||
|
|
||||||
|
.path-modal-command code {
|
||||||
|
flex: 1;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
color: hsl(var(--foreground));
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
|
||||||
|
.path-modal-command .copy-btn {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.375rem;
|
||||||
|
padding: 0.375rem 0.75rem;
|
||||||
|
background: hsl(var(--primary));
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 0.375rem;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.15s;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.path-modal-command .copy-btn:hover {
|
||||||
|
background: hsl(var(--primary) / 0.9);
|
||||||
|
}
|
||||||
|
|
||||||
|
.path-modal-note {
|
||||||
|
font-size: 0.85rem !important;
|
||||||
|
color: hsl(var(--muted-foreground)) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.path-modal-note code {
|
||||||
|
background: hsl(var(--muted));
|
||||||
|
padding: 0.125rem 0.375rem;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.path-modal-input {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0.75rem 1rem;
|
||||||
|
background: hsl(var(--background));
|
||||||
|
border: 1px solid hsl(var(--border));
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
color: hsl(var(--foreground));
|
||||||
|
outline: none;
|
||||||
|
transition: border-color 0.15s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.path-modal-input:focus {
|
||||||
|
border-color: hsl(var(--primary));
|
||||||
|
}
|
||||||
|
|
||||||
|
.path-modal-input::placeholder {
|
||||||
|
color: hsl(var(--muted-foreground));
|
||||||
|
}
|
||||||
|
|
||||||
|
.path-modal-footer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 0.75rem;
|
||||||
|
padding: 1rem 1.5rem;
|
||||||
|
border-top: 1px solid hsl(var(--border));
|
||||||
|
background: hsl(var(--muted) / 0.3);
|
||||||
|
border-radius: 0 0 0.75rem 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.path-modal-close {
|
||||||
|
padding: 0.5rem 1.25rem;
|
||||||
|
background: hsl(var(--muted));
|
||||||
|
color: hsl(var(--foreground));
|
||||||
|
border: none;
|
||||||
|
border-radius: 0.375rem;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.15s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.path-modal-close:hover {
|
||||||
|
background: hsl(var(--hover));
|
||||||
|
}
|
||||||
|
|
||||||
|
.path-modal-confirm {
|
||||||
|
padding: 0.5rem 1.25rem;
|
||||||
|
background: hsl(var(--primary));
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 0.375rem;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.15s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.path-modal-confirm:hover {
|
||||||
|
background: hsl(var(--primary) / 0.9);
|
||||||
|
}
|
||||||
|
|
||||||
|
.path-modal-confirm:disabled {
|
||||||
|
opacity: 0.5;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Path Input Group */
|
||||||
|
.path-input-group {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.75rem;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.path-input-group label {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
color: hsl(var(--muted-foreground));
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.path-input-group input {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 200px;
|
||||||
|
padding: 0.625rem 0.875rem;
|
||||||
|
background: hsl(var(--background));
|
||||||
|
border: 1px solid hsl(var(--border));
|
||||||
|
border-radius: 0.375rem;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
color: hsl(var(--foreground));
|
||||||
|
outline: none;
|
||||||
|
transition: border-color 0.15s, box-shadow 0.15s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.path-input-group input:focus {
|
||||||
|
border-color: hsl(var(--primary));
|
||||||
|
box-shadow: 0 0 0 3px hsl(var(--primary) / 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.path-input-group input::placeholder {
|
||||||
|
color: hsl(var(--muted-foreground));
|
||||||
|
}
|
||||||
|
|
||||||
|
.path-go-btn {
|
||||||
|
padding: 0.625rem 1.25rem;
|
||||||
|
background: hsl(var(--primary));
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 0.375rem;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-weight: 500;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.15s;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.path-go-btn:hover {
|
||||||
|
background: hsl(var(--primary) / 0.9);
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.path-go-btn:active {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Selected Folder Display */
|
||||||
|
.selected-folder {
|
||||||
|
padding: 0.75rem 1rem;
|
||||||
|
background: hsl(var(--muted));
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
margin-bottom: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selected-folder strong {
|
||||||
|
font-size: 1rem;
|
||||||
|
color: hsl(var(--foreground));
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,13 +3,24 @@ import { platform } from 'os';
|
|||||||
import { resolve } from 'path';
|
import { resolve } from 'path';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Launch a file in the default browser
|
* Launch a URL or file in the default browser
|
||||||
* Cross-platform compatible (Windows/macOS/Linux)
|
* Cross-platform compatible (Windows/macOS/Linux)
|
||||||
* @param {string} filePath - Path to HTML file
|
* @param {string} urlOrPath - HTTP URL or path to HTML file
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
export async function launchBrowser(filePath) {
|
export async function launchBrowser(urlOrPath) {
|
||||||
const absolutePath = resolve(filePath);
|
// Check if it's already a URL (http:// or https://)
|
||||||
|
if (urlOrPath.startsWith('http://') || urlOrPath.startsWith('https://')) {
|
||||||
|
try {
|
||||||
|
await open(urlOrPath);
|
||||||
|
return;
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`Failed to open browser: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// It's a file path - convert to file:// URL
|
||||||
|
const absolutePath = resolve(urlOrPath);
|
||||||
|
|
||||||
// Construct file:// URL based on platform
|
// Construct file:// URL based on platform
|
||||||
let url;
|
let url;
|
||||||
|
|||||||
28
package-lock.json
generated
28
package-lock.json
generated
@@ -1,17 +1,16 @@
|
|||||||
{
|
{
|
||||||
"name": "claude-code-workflow",
|
"name": "claude-code-workflow",
|
||||||
"version": "6.0.0",
|
"version": "6.0.5",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "claude-code-workflow",
|
"name": "claude-code-workflow",
|
||||||
"version": "6.0.0",
|
"version": "6.0.5",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"boxen": "^7.1.0",
|
"boxen": "^7.1.0",
|
||||||
"chalk": "^5.3.0",
|
"chalk": "^5.3.0",
|
||||||
"claude-code-workflow": "^6.0.0",
|
|
||||||
"commander": "^11.0.0",
|
"commander": "^11.0.0",
|
||||||
"figlet": "^1.7.0",
|
"figlet": "^1.7.0",
|
||||||
"glob": "^10.3.0",
|
"glob": "^10.3.0",
|
||||||
@@ -352,29 +351,6 @@
|
|||||||
"integrity": "sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==",
|
"integrity": "sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/claude-code-workflow": {
|
|
||||||
"version": "6.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/claude-code-workflow/-/claude-code-workflow-6.0.0.tgz",
|
|
||||||
"integrity": "sha512-YcYOplsnHqB/IcHIQgCrXPR1TK/Cbb5zFaFwrMNFzTEPU6oHRcGUc4RK9xtd/3LhfOGitZXSvANVooYPnNScVA==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"boxen": "^7.1.0",
|
|
||||||
"chalk": "^5.3.0",
|
|
||||||
"commander": "^11.0.0",
|
|
||||||
"figlet": "^1.7.0",
|
|
||||||
"glob": "^10.3.0",
|
|
||||||
"gradient-string": "^2.0.2",
|
|
||||||
"inquirer": "^9.2.0",
|
|
||||||
"open": "^9.1.0",
|
|
||||||
"ora": "^7.0.0"
|
|
||||||
},
|
|
||||||
"bin": {
|
|
||||||
"ccw": "ccw/bin/ccw.js"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=16.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/cli-boxes": {
|
"node_modules/cli-boxes": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "claude-code-workflow",
|
"name": "claude-code-workflow",
|
||||||
"version": "6.0.2",
|
"version": "6.0.5",
|
||||||
"description": "JSON-driven multi-agent development framework with intelligent CLI orchestration (Gemini/Qwen/Codex), context-first architecture, and automated workflow execution",
|
"description": "JSON-driven multi-agent development framework with intelligent CLI orchestration (Gemini/Qwen/Codex), context-first architecture, and automated workflow execution",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "ccw/src/index.js",
|
"main": "ccw/src/index.js",
|
||||||
|
|||||||
Reference in New Issue
Block a user