orchestry-mcp
Version:
Orchestry MCP Server for multi-session task management
232 lines (205 loc) • 6.75 kB
text/typescript
import { create } from 'zustand';
import { io, Socket } from 'socket.io-client';
import type {
BMADProject,
Business,
Mission,
Approach,
Deployment,
TaskStatus,
KanbanColumn,
} from '../../../shared/types.js';
import { bmadAPI } from '../lib/api';
import toast from 'react-hot-toast';
interface BMADStore {
// State
socket: Socket | null;
currentProject: BMADProject | null;
kanbanColumns: Record<TaskStatus, Deployment[]>;
selectedDeployment: Deployment | null;
isLoading: boolean;
// Actions
initSocket: () => void;
loadProject: (projectId: string) => Promise<void>;
createProject: (name: string, description: string) => Promise<BMADProject>;
createBusiness: (projectId: string, business: Partial<Business>) => Promise<Business>;
createMission: (businessId: string, mission: Partial<Mission>) => Promise<Mission>;
createApproach: (missionId: string, approach: Partial<Approach>) => Promise<Approach>;
createDeployment: (approachId: string, deployment: Partial<Deployment>) => Promise<Deployment>;
updateDeploymentStatus: (id: string, status: TaskStatus) => Promise<void>;
loadKanbanBoard: (projectId: string) => Promise<void>;
selectDeployment: (deployment: Deployment | null) => void;
}
export const useBMADStore = create<BMADStore>((set, get) => ({
// Initial state
socket: null,
currentProject: null,
kanbanColumns: {
backlog: [],
todo: [],
in_progress: [],
review: [],
done: [],
blocked: [],
},
selectedDeployment: null,
isLoading: false,
// Initialize WebSocket connection
initSocket: () => {
const wsUrl = import.meta.env.VITE_WS_URL || 'ws://localhost:7531';
const socket = io(wsUrl.replace('ws://', 'http://'), {
transports: ['websocket'],
});
socket.on('connect', () => {
console.log('Connected to server');
toast.success('Connected to server');
});
socket.on('disconnect', () => {
console.log('Disconnected from server');
toast.error('Disconnected from server');
});
// Real-time updates
socket.on('deployment_created', (deployment: Deployment) => {
const { kanbanColumns } = get();
set({
kanbanColumns: {
...kanbanColumns,
[deployment.status]: [...kanbanColumns[deployment.status], deployment],
},
});
toast.success(`New task created: ${deployment.title}`);
});
socket.on('deployment_updated', (deployment: Deployment) => {
const { kanbanColumns } = get();
const newColumns = { ...kanbanColumns };
// Remove from all columns
Object.keys(newColumns).forEach((status) => {
newColumns[status as TaskStatus] = newColumns[status as TaskStatus].filter(
(d) => d.id !== deployment.id
);
});
// Add to new column
newColumns[deployment.status] = [...newColumns[deployment.status], deployment];
set({ kanbanColumns: newColumns });
toast.success(`Task updated: ${deployment.title}`);
});
set({ socket });
},
// Load project
loadProject: async (projectId: string) => {
set({ isLoading: true });
try {
const project = await bmadAPI.getProject(projectId);
set({ currentProject: project });
// Subscribe to project updates
const { socket } = get();
if (socket) {
socket.emit('subscribe', projectId);
}
// Load kanban board
await get().loadKanbanBoard(projectId);
} catch (error) {
toast.error('Failed to load project');
console.error(error);
} finally {
set({ isLoading: false });
}
},
// Create project
createProject: async (name: string, description: string) => {
try {
const project = await bmadAPI.createProject(name, description);
set({ currentProject: project });
toast.success(`Project created: ${name}`);
return project;
} catch (error) {
toast.error('Failed to create project');
throw error;
}
},
// Create business
createBusiness: async (projectId: string, business: Partial<Business>) => {
try {
const newBusiness = await bmadAPI.createBusiness(projectId, business);
// Update current project
const { currentProject } = get();
if (currentProject) {
set({
currentProject: {
...currentProject,
business: [...currentProject.business, newBusiness],
},
});
}
toast.success(`Business created: ${newBusiness.title}`);
return newBusiness;
} catch (error) {
toast.error('Failed to create business');
throw error;
}
},
// Create mission
createMission: async (businessId: string, mission: Partial<Mission>) => {
try {
const newMission = await bmadAPI.createMission(businessId, mission);
toast.success(`Mission created: ${newMission.title}`);
return newMission;
} catch (error) {
toast.error('Failed to create mission');
throw error;
}
},
// Create approach
createApproach: async (missionId: string, approach: Partial<Approach>) => {
try {
const newApproach = await bmadAPI.createApproach(missionId, approach);
toast.success(`Approach created: ${newApproach.title}`);
return newApproach;
} catch (error) {
toast.error('Failed to create approach');
throw error;
}
},
// Create deployment
createDeployment: async (approachId: string, deployment: Partial<Deployment>) => {
try {
const newDeployment = await bmadAPI.createDeployment(approachId, deployment);
// Add to kanban board
const { kanbanColumns } = get();
set({
kanbanColumns: {
...kanbanColumns,
[newDeployment.status]: [...kanbanColumns[newDeployment.status], newDeployment],
},
});
toast.success(`Task created: ${newDeployment.title}`);
return newDeployment;
} catch (error) {
toast.error('Failed to create task');
throw error;
}
},
// Update deployment status
updateDeploymentStatus: async (id: string, status: TaskStatus) => {
try {
await bmadAPI.updateDeploymentStatus(id, status);
} catch (error) {
toast.error('Failed to update task status');
throw error;
}
},
// Load kanban board
loadKanbanBoard: async (projectId: string) => {
try {
const board = await bmadAPI.getBoard(projectId);
set({ kanbanColumns: board });
} catch (error) {
toast.error('Failed to load board');
console.error(error);
}
},
// Select deployment
selectDeployment: (deployment: Deployment | null) => {
set({ selectedDeployment: deployment });
},
}));