mirror of
https://github.com/siteboon/claudecodeui.git
synced 2025-12-08 23:49:38 +00:00
Integration with TaskMaster AI
This commit is contained in:
@@ -43,6 +43,8 @@ import gitRoutes from './routes/git.js';
|
||||
import authRoutes from './routes/auth.js';
|
||||
import mcpRoutes from './routes/mcp.js';
|
||||
import cursorRoutes from './routes/cursor.js';
|
||||
import taskmasterRoutes from './routes/taskmaster.js';
|
||||
import mcpUtilsRoutes from './routes/mcp-utils.js';
|
||||
import { initializeDatabase } from './database/db.js';
|
||||
import { validateApiKey, authenticateToken, authenticateWebSocket } from './middleware/auth.js';
|
||||
|
||||
@@ -162,6 +164,9 @@ const wss = new WebSocketServer({
|
||||
}
|
||||
});
|
||||
|
||||
// Make WebSocket server available to routes
|
||||
app.locals.wss = wss;
|
||||
|
||||
app.use(cors());
|
||||
app.use(express.json());
|
||||
|
||||
@@ -180,6 +185,12 @@ app.use('/api/mcp', authenticateToken, mcpRoutes);
|
||||
// Cursor API Routes (protected)
|
||||
app.use('/api/cursor', authenticateToken, cursorRoutes);
|
||||
|
||||
// TaskMaster API Routes (protected)
|
||||
app.use('/api/taskmaster', authenticateToken, taskmasterRoutes);
|
||||
|
||||
// MCP utilities
|
||||
app.use('/api/mcp-utils', authenticateToken, mcpUtilsRoutes);
|
||||
|
||||
// Static files served after API routes
|
||||
app.use(express.static(path.join(__dirname, '../dist')));
|
||||
|
||||
@@ -547,16 +558,26 @@ function handleShellConnection(ws) {
|
||||
const sessionId = data.sessionId;
|
||||
const hasSession = data.hasSession;
|
||||
const provider = data.provider || 'claude';
|
||||
const initialCommand = data.initialCommand;
|
||||
const isPlainShell = data.isPlainShell || (!!initialCommand && !hasSession) || provider === 'plain-shell';
|
||||
|
||||
console.log('🚀 Starting shell in:', projectPath);
|
||||
console.log('📋 Session info:', hasSession ? `Resume session ${sessionId}` : 'New session');
|
||||
console.log('🤖 Provider:', provider);
|
||||
console.log('📋 Session info:', hasSession ? `Resume session ${sessionId}` : (isPlainShell ? 'Plain shell mode' : 'New session'));
|
||||
console.log('🤖 Provider:', isPlainShell ? 'plain-shell' : provider);
|
||||
if (initialCommand) {
|
||||
console.log('⚡ Initial command:', initialCommand);
|
||||
}
|
||||
|
||||
// First send a welcome message
|
||||
const providerName = provider === 'cursor' ? 'Cursor' : 'Claude';
|
||||
const welcomeMsg = hasSession ?
|
||||
`\x1b[36mResuming ${providerName} session ${sessionId} in: ${projectPath}\x1b[0m\r\n` :
|
||||
`\x1b[36mStarting new ${providerName} session in: ${projectPath}\x1b[0m\r\n`;
|
||||
let welcomeMsg;
|
||||
if (isPlainShell) {
|
||||
welcomeMsg = `\x1b[36mStarting terminal in: ${projectPath}\x1b[0m\r\n`;
|
||||
} else {
|
||||
const providerName = provider === 'cursor' ? 'Cursor' : 'Claude';
|
||||
welcomeMsg = hasSession ?
|
||||
`\x1b[36mResuming ${providerName} session ${sessionId} in: ${projectPath}\x1b[0m\r\n` :
|
||||
`\x1b[36mStarting new ${providerName} session in: ${projectPath}\x1b[0m\r\n`;
|
||||
}
|
||||
|
||||
ws.send(JSON.stringify({
|
||||
type: 'output',
|
||||
@@ -566,7 +587,14 @@ function handleShellConnection(ws) {
|
||||
try {
|
||||
// Prepare the shell command adapted to the platform and provider
|
||||
let shellCommand;
|
||||
if (provider === 'cursor') {
|
||||
if (isPlainShell) {
|
||||
// Plain shell mode - just run the initial command in the project directory
|
||||
if (os.platform() === 'win32') {
|
||||
shellCommand = `Set-Location -Path "${projectPath}"; ${initialCommand}`;
|
||||
} else {
|
||||
shellCommand = `cd "${projectPath}" && ${initialCommand}`;
|
||||
}
|
||||
} else if (provider === 'cursor') {
|
||||
// Use cursor-agent command
|
||||
if (os.platform() === 'win32') {
|
||||
if (hasSession && sessionId) {
|
||||
@@ -582,19 +610,20 @@ function handleShellConnection(ws) {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Use claude command (default)
|
||||
// Use claude command (default) or initialCommand if provided
|
||||
const command = initialCommand || 'claude';
|
||||
if (os.platform() === 'win32') {
|
||||
if (hasSession && sessionId) {
|
||||
// Try to resume session, but with fallback to new session if it fails
|
||||
shellCommand = `Set-Location -Path "${projectPath}"; claude --resume ${sessionId}; if ($LASTEXITCODE -ne 0) { claude }`;
|
||||
} else {
|
||||
shellCommand = `Set-Location -Path "${projectPath}"; claude`;
|
||||
shellCommand = `Set-Location -Path "${projectPath}"; ${command}`;
|
||||
}
|
||||
} else {
|
||||
if (hasSession && sessionId) {
|
||||
shellCommand = `cd "${projectPath}" && claude --resume ${sessionId} || claude`;
|
||||
} else {
|
||||
shellCommand = `cd "${projectPath}" && claude`;
|
||||
shellCommand = `cd "${projectPath}" && ${command}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,6 +66,134 @@ import sqlite3 from 'sqlite3';
|
||||
import { open } from 'sqlite';
|
||||
import os from 'os';
|
||||
|
||||
// Import TaskMaster detection functions
|
||||
async function detectTaskMasterFolder(projectPath) {
|
||||
try {
|
||||
const taskMasterPath = path.join(projectPath, '.taskmaster');
|
||||
|
||||
// Check if .taskmaster directory exists
|
||||
try {
|
||||
const stats = await fs.stat(taskMasterPath);
|
||||
if (!stats.isDirectory()) {
|
||||
return {
|
||||
hasTaskmaster: false,
|
||||
reason: '.taskmaster exists but is not a directory'
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
if (error.code === 'ENOENT') {
|
||||
return {
|
||||
hasTaskmaster: false,
|
||||
reason: '.taskmaster directory not found'
|
||||
};
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
|
||||
// Check for key TaskMaster files
|
||||
const keyFiles = [
|
||||
'tasks/tasks.json',
|
||||
'config.json'
|
||||
];
|
||||
|
||||
const fileStatus = {};
|
||||
let hasEssentialFiles = true;
|
||||
|
||||
for (const file of keyFiles) {
|
||||
const filePath = path.join(taskMasterPath, file);
|
||||
try {
|
||||
await fs.access(filePath);
|
||||
fileStatus[file] = true;
|
||||
} catch (error) {
|
||||
fileStatus[file] = false;
|
||||
if (file === 'tasks/tasks.json') {
|
||||
hasEssentialFiles = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Parse tasks.json if it exists for metadata
|
||||
let taskMetadata = null;
|
||||
if (fileStatus['tasks/tasks.json']) {
|
||||
try {
|
||||
const tasksPath = path.join(taskMasterPath, 'tasks/tasks.json');
|
||||
const tasksContent = await fs.readFile(tasksPath, 'utf8');
|
||||
const tasksData = JSON.parse(tasksContent);
|
||||
|
||||
// Handle both tagged and legacy formats
|
||||
let tasks = [];
|
||||
if (tasksData.tasks) {
|
||||
// Legacy format
|
||||
tasks = tasksData.tasks;
|
||||
} else {
|
||||
// Tagged format - get tasks from all tags
|
||||
Object.values(tasksData).forEach(tagData => {
|
||||
if (tagData.tasks) {
|
||||
tasks = tasks.concat(tagData.tasks);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Calculate task statistics
|
||||
const stats = tasks.reduce((acc, task) => {
|
||||
acc.total++;
|
||||
acc[task.status] = (acc[task.status] || 0) + 1;
|
||||
|
||||
// Count subtasks
|
||||
if (task.subtasks) {
|
||||
task.subtasks.forEach(subtask => {
|
||||
acc.subtotalTasks++;
|
||||
acc.subtasks = acc.subtasks || {};
|
||||
acc.subtasks[subtask.status] = (acc.subtasks[subtask.status] || 0) + 1;
|
||||
});
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, {
|
||||
total: 0,
|
||||
subtotalTasks: 0,
|
||||
pending: 0,
|
||||
'in-progress': 0,
|
||||
done: 0,
|
||||
review: 0,
|
||||
deferred: 0,
|
||||
cancelled: 0,
|
||||
subtasks: {}
|
||||
});
|
||||
|
||||
taskMetadata = {
|
||||
taskCount: stats.total,
|
||||
subtaskCount: stats.subtotalTasks,
|
||||
completed: stats.done || 0,
|
||||
pending: stats.pending || 0,
|
||||
inProgress: stats['in-progress'] || 0,
|
||||
review: stats.review || 0,
|
||||
completionPercentage: stats.total > 0 ? Math.round((stats.done / stats.total) * 100) : 0,
|
||||
lastModified: (await fs.stat(tasksPath)).mtime.toISOString()
|
||||
};
|
||||
} catch (parseError) {
|
||||
console.warn('Failed to parse tasks.json:', parseError.message);
|
||||
taskMetadata = { error: 'Failed to parse tasks.json' };
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
hasTaskmaster: true,
|
||||
hasEssentialFiles,
|
||||
files: fileStatus,
|
||||
metadata: taskMetadata,
|
||||
path: taskMasterPath
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error detecting TaskMaster folder:', error);
|
||||
return {
|
||||
hasTaskmaster: false,
|
||||
reason: `Error checking directory: ${error.message}`
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Cache for extracted project directories
|
||||
const projectDirectoryCache = new Map();
|
||||
|
||||
@@ -298,6 +426,25 @@ async function getProjects() {
|
||||
project.cursorSessions = [];
|
||||
}
|
||||
|
||||
// Add TaskMaster detection
|
||||
try {
|
||||
const taskMasterResult = await detectTaskMasterFolder(actualProjectDir);
|
||||
project.taskmaster = {
|
||||
hasTaskmaster: taskMasterResult.hasTaskmaster,
|
||||
hasEssentialFiles: taskMasterResult.hasEssentialFiles,
|
||||
metadata: taskMasterResult.metadata,
|
||||
status: taskMasterResult.hasTaskmaster && taskMasterResult.hasEssentialFiles ? 'configured' : 'not-configured'
|
||||
};
|
||||
} catch (e) {
|
||||
console.warn(`Could not detect TaskMaster for project ${entry.name}:`, e.message);
|
||||
project.taskmaster = {
|
||||
hasTaskmaster: false,
|
||||
hasEssentialFiles: false,
|
||||
metadata: null,
|
||||
status: 'error'
|
||||
};
|
||||
}
|
||||
|
||||
projects.push(project);
|
||||
}
|
||||
}
|
||||
@@ -341,6 +488,32 @@ async function getProjects() {
|
||||
console.warn(`Could not load Cursor sessions for manual project ${projectName}:`, e.message);
|
||||
}
|
||||
|
||||
// Add TaskMaster detection for manual projects
|
||||
try {
|
||||
const taskMasterResult = await detectTaskMasterFolder(actualProjectDir);
|
||||
|
||||
// Determine TaskMaster status
|
||||
let taskMasterStatus = 'not-configured';
|
||||
if (taskMasterResult.hasTaskmaster && taskMasterResult.hasEssentialFiles) {
|
||||
taskMasterStatus = 'taskmaster-only'; // We don't check MCP for manual projects in bulk
|
||||
}
|
||||
|
||||
project.taskmaster = {
|
||||
status: taskMasterStatus,
|
||||
hasTaskmaster: taskMasterResult.hasTaskmaster,
|
||||
hasEssentialFiles: taskMasterResult.hasEssentialFiles,
|
||||
metadata: taskMasterResult.metadata
|
||||
};
|
||||
} catch (error) {
|
||||
console.warn(`TaskMaster detection failed for manual project ${projectName}:`, error.message);
|
||||
project.taskmaster = {
|
||||
status: 'error',
|
||||
hasTaskmaster: false,
|
||||
hasEssentialFiles: false,
|
||||
error: error.message
|
||||
};
|
||||
}
|
||||
|
||||
projects.push(project);
|
||||
}
|
||||
}
|
||||
|
||||
48
server/routes/mcp-utils.js
Normal file
48
server/routes/mcp-utils.js
Normal file
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* MCP UTILITIES API ROUTES
|
||||
* ========================
|
||||
*
|
||||
* API endpoints for MCP server detection and configuration utilities.
|
||||
* These endpoints expose centralized MCP detection functionality.
|
||||
*/
|
||||
|
||||
import express from 'express';
|
||||
import { detectTaskMasterMCPServer, getAllMCPServers } from '../utils/mcp-detector.js';
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
/**
|
||||
* GET /api/mcp-utils/taskmaster-server
|
||||
* Check if TaskMaster MCP server is configured
|
||||
*/
|
||||
router.get('/taskmaster-server', async (req, res) => {
|
||||
try {
|
||||
const result = await detectTaskMasterMCPServer();
|
||||
res.json(result);
|
||||
} catch (error) {
|
||||
console.error('TaskMaster MCP detection error:', error);
|
||||
res.status(500).json({
|
||||
error: 'Failed to detect TaskMaster MCP server',
|
||||
message: error.message
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* GET /api/mcp-utils/all-servers
|
||||
* Get all configured MCP servers
|
||||
*/
|
||||
router.get('/all-servers', async (req, res) => {
|
||||
try {
|
||||
const result = await getAllMCPServers();
|
||||
res.json(result);
|
||||
} catch (error) {
|
||||
console.error('MCP servers detection error:', error);
|
||||
res.status(500).json({
|
||||
error: 'Failed to get MCP servers',
|
||||
message: error.message
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
export default router;
|
||||
1971
server/routes/taskmaster.js
Normal file
1971
server/routes/taskmaster.js
Normal file
File diff suppressed because it is too large
Load Diff
198
server/utils/mcp-detector.js
Normal file
198
server/utils/mcp-detector.js
Normal file
@@ -0,0 +1,198 @@
|
||||
/**
|
||||
* MCP SERVER DETECTION UTILITY
|
||||
* ============================
|
||||
*
|
||||
* Centralized utility for detecting MCP server configurations.
|
||||
* Used across TaskMaster integration and other MCP-dependent features.
|
||||
*/
|
||||
|
||||
import { promises as fsPromises } from 'fs';
|
||||
import path from 'path';
|
||||
import os from 'os';
|
||||
|
||||
/**
|
||||
* Check if task-master-ai MCP server is configured
|
||||
* Reads directly from Claude configuration files like claude-cli.js does
|
||||
* @returns {Promise<Object>} MCP detection result
|
||||
*/
|
||||
export async function detectTaskMasterMCPServer() {
|
||||
try {
|
||||
// Read Claude configuration files directly (same logic as mcp.js)
|
||||
const homeDir = os.homedir();
|
||||
const configPaths = [
|
||||
path.join(homeDir, '.claude.json'),
|
||||
path.join(homeDir, '.claude', 'settings.json')
|
||||
];
|
||||
|
||||
let configData = null;
|
||||
let configPath = null;
|
||||
|
||||
// Try to read from either config file
|
||||
for (const filepath of configPaths) {
|
||||
try {
|
||||
const fileContent = await fsPromises.readFile(filepath, 'utf8');
|
||||
configData = JSON.parse(fileContent);
|
||||
configPath = filepath;
|
||||
break;
|
||||
} catch (error) {
|
||||
// File doesn't exist or is not valid JSON, try next
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (!configData) {
|
||||
return {
|
||||
hasMCPServer: false,
|
||||
reason: 'No Claude configuration file found',
|
||||
hasConfig: false
|
||||
};
|
||||
}
|
||||
|
||||
// Look for task-master-ai in user-scoped MCP servers
|
||||
let taskMasterServer = null;
|
||||
if (configData.mcpServers && typeof configData.mcpServers === 'object') {
|
||||
const serverEntry = Object.entries(configData.mcpServers).find(([name, config]) =>
|
||||
name === 'task-master-ai' ||
|
||||
name.includes('task-master') ||
|
||||
(config && config.command && config.command.includes('task-master'))
|
||||
);
|
||||
|
||||
if (serverEntry) {
|
||||
const [name, config] = serverEntry;
|
||||
taskMasterServer = {
|
||||
name,
|
||||
scope: 'user',
|
||||
config,
|
||||
type: config.command ? 'stdio' : (config.url ? 'http' : 'unknown')
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Also check project-specific MCP servers if not found globally
|
||||
if (!taskMasterServer && configData.projects) {
|
||||
for (const [projectPath, projectConfig] of Object.entries(configData.projects)) {
|
||||
if (projectConfig.mcpServers && typeof projectConfig.mcpServers === 'object') {
|
||||
const serverEntry = Object.entries(projectConfig.mcpServers).find(([name, config]) =>
|
||||
name === 'task-master-ai' ||
|
||||
name.includes('task-master') ||
|
||||
(config && config.command && config.command.includes('task-master'))
|
||||
);
|
||||
|
||||
if (serverEntry) {
|
||||
const [name, config] = serverEntry;
|
||||
taskMasterServer = {
|
||||
name,
|
||||
scope: 'local',
|
||||
projectPath,
|
||||
config,
|
||||
type: config.command ? 'stdio' : (config.url ? 'http' : 'unknown')
|
||||
};
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (taskMasterServer) {
|
||||
const isValid = !!(taskMasterServer.config &&
|
||||
(taskMasterServer.config.command || taskMasterServer.config.url));
|
||||
const hasEnvVars = !!(taskMasterServer.config &&
|
||||
taskMasterServer.config.env &&
|
||||
Object.keys(taskMasterServer.config.env).length > 0);
|
||||
|
||||
return {
|
||||
hasMCPServer: true,
|
||||
isConfigured: isValid,
|
||||
hasApiKeys: hasEnvVars,
|
||||
scope: taskMasterServer.scope,
|
||||
config: {
|
||||
command: taskMasterServer.config?.command,
|
||||
args: taskMasterServer.config?.args || [],
|
||||
url: taskMasterServer.config?.url,
|
||||
envVars: hasEnvVars ? Object.keys(taskMasterServer.config.env) : [],
|
||||
type: taskMasterServer.type
|
||||
}
|
||||
};
|
||||
} else {
|
||||
// Get list of available servers for debugging
|
||||
const availableServers = [];
|
||||
if (configData.mcpServers) {
|
||||
availableServers.push(...Object.keys(configData.mcpServers));
|
||||
}
|
||||
if (configData.projects) {
|
||||
for (const projectConfig of Object.values(configData.projects)) {
|
||||
if (projectConfig.mcpServers) {
|
||||
availableServers.push(...Object.keys(projectConfig.mcpServers).map(name => `local:${name}`));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
hasMCPServer: false,
|
||||
reason: 'task-master-ai not found in configured MCP servers',
|
||||
hasConfig: true,
|
||||
configPath,
|
||||
availableServers
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error detecting MCP server config:', error);
|
||||
return {
|
||||
hasMCPServer: false,
|
||||
reason: `Error checking MCP config: ${error.message}`,
|
||||
hasConfig: false
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all configured MCP servers (not just TaskMaster)
|
||||
* @returns {Promise<Object>} All MCP servers configuration
|
||||
*/
|
||||
export async function getAllMCPServers() {
|
||||
try {
|
||||
const homeDir = os.homedir();
|
||||
const configPaths = [
|
||||
path.join(homeDir, '.claude.json'),
|
||||
path.join(homeDir, '.claude', 'settings.json')
|
||||
];
|
||||
|
||||
let configData = null;
|
||||
let configPath = null;
|
||||
|
||||
// Try to read from either config file
|
||||
for (const filepath of configPaths) {
|
||||
try {
|
||||
const fileContent = await fsPromises.readFile(filepath, 'utf8');
|
||||
configData = JSON.parse(fileContent);
|
||||
configPath = filepath;
|
||||
break;
|
||||
} catch (error) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (!configData) {
|
||||
return {
|
||||
hasConfig: false,
|
||||
servers: {},
|
||||
projectServers: {}
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
hasConfig: true,
|
||||
configPath,
|
||||
servers: configData.mcpServers || {},
|
||||
projectServers: configData.projects || {}
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error getting all MCP servers:', error);
|
||||
return {
|
||||
hasConfig: false,
|
||||
error: error.message,
|
||||
servers: {},
|
||||
projectServers: {}
|
||||
};
|
||||
}
|
||||
}
|
||||
129
server/utils/taskmaster-websocket.js
Normal file
129
server/utils/taskmaster-websocket.js
Normal file
@@ -0,0 +1,129 @@
|
||||
/**
|
||||
* TASKMASTER WEBSOCKET UTILITIES
|
||||
* ==============================
|
||||
*
|
||||
* Utilities for broadcasting TaskMaster state changes via WebSocket.
|
||||
* Integrates with the existing WebSocket system to provide real-time updates.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Broadcast TaskMaster project update to all connected clients
|
||||
* @param {WebSocket.Server} wss - WebSocket server instance
|
||||
* @param {string} projectName - Name of the updated project
|
||||
* @param {Object} taskMasterData - Updated TaskMaster data
|
||||
*/
|
||||
export function broadcastTaskMasterProjectUpdate(wss, projectName, taskMasterData) {
|
||||
if (!wss || !projectName) {
|
||||
console.warn('TaskMaster WebSocket broadcast: Missing wss or projectName');
|
||||
return;
|
||||
}
|
||||
|
||||
const message = {
|
||||
type: 'taskmaster-project-updated',
|
||||
projectName,
|
||||
taskMasterData,
|
||||
timestamp: new Date().toISOString()
|
||||
};
|
||||
|
||||
|
||||
wss.clients.forEach((client) => {
|
||||
if (client.readyState === 1) { // WebSocket.OPEN
|
||||
try {
|
||||
client.send(JSON.stringify(message));
|
||||
} catch (error) {
|
||||
console.error('Error sending TaskMaster project update:', error);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcast TaskMaster tasks update for a specific project
|
||||
* @param {WebSocket.Server} wss - WebSocket server instance
|
||||
* @param {string} projectName - Name of the project with updated tasks
|
||||
* @param {Object} tasksData - Updated tasks data
|
||||
*/
|
||||
export function broadcastTaskMasterTasksUpdate(wss, projectName, tasksData) {
|
||||
if (!wss || !projectName) {
|
||||
console.warn('TaskMaster WebSocket broadcast: Missing wss or projectName');
|
||||
return;
|
||||
}
|
||||
|
||||
const message = {
|
||||
type: 'taskmaster-tasks-updated',
|
||||
projectName,
|
||||
tasksData,
|
||||
timestamp: new Date().toISOString()
|
||||
};
|
||||
|
||||
|
||||
wss.clients.forEach((client) => {
|
||||
if (client.readyState === 1) { // WebSocket.OPEN
|
||||
try {
|
||||
client.send(JSON.stringify(message));
|
||||
} catch (error) {
|
||||
console.error('Error sending TaskMaster tasks update:', error);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcast MCP server status change
|
||||
* @param {WebSocket.Server} wss - WebSocket server instance
|
||||
* @param {Object} mcpStatus - Updated MCP server status
|
||||
*/
|
||||
export function broadcastMCPStatusChange(wss, mcpStatus) {
|
||||
if (!wss) {
|
||||
console.warn('TaskMaster WebSocket broadcast: Missing wss');
|
||||
return;
|
||||
}
|
||||
|
||||
const message = {
|
||||
type: 'taskmaster-mcp-status-changed',
|
||||
mcpStatus,
|
||||
timestamp: new Date().toISOString()
|
||||
};
|
||||
|
||||
|
||||
wss.clients.forEach((client) => {
|
||||
if (client.readyState === 1) { // WebSocket.OPEN
|
||||
try {
|
||||
client.send(JSON.stringify(message));
|
||||
} catch (error) {
|
||||
console.error('Error sending TaskMaster MCP status update:', error);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcast general TaskMaster update notification
|
||||
* @param {WebSocket.Server} wss - WebSocket server instance
|
||||
* @param {string} updateType - Type of update (e.g., 'initialization', 'configuration')
|
||||
* @param {Object} data - Additional data about the update
|
||||
*/
|
||||
export function broadcastTaskMasterUpdate(wss, updateType, data = {}) {
|
||||
if (!wss || !updateType) {
|
||||
console.warn('TaskMaster WebSocket broadcast: Missing wss or updateType');
|
||||
return;
|
||||
}
|
||||
|
||||
const message = {
|
||||
type: 'taskmaster-update',
|
||||
updateType,
|
||||
data,
|
||||
timestamp: new Date().toISOString()
|
||||
};
|
||||
|
||||
|
||||
wss.clients.forEach((client) => {
|
||||
if (client.readyState === 1) { // WebSocket.OPEN
|
||||
try {
|
||||
client.send(JSON.stringify(message));
|
||||
} catch (error) {
|
||||
console.error('Error sending TaskMaster update:', error);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user