#!/usr/bin/env node
import readline from "readline";
import axios from "axios";
import { io } from "socket.io-client";
import chalk from "chalk";
import { v4 as uuidv4 } from "uuid";
const SERVER_URL = process.env.CHAT_SERVER_URL || "http://localhost:3000";
class OrchestratorCLI {
constructor() {
this.socket = null;
this.currentRoom = null;
this.agentId = `orchestrator-${uuidv4()}`;
this.agentName = "Orchestrator";
this.isOrchestrator = true;
this.rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
prompt: chalk.cyan("š > "),
});
}
async start() {
console.log(
chalk.green(`\n
______ ____ __ ____ _ _ ___ _ ___ __ ___ _____ ___ _ _ _____
/ ___\ \ / / \/ | _ \| | | |/ _ \| \ | \ \ / / / _ \| ___| / _ \| \ | | ____|
\___ \\ V /| |\/| | |_) | |_| | | | | \| |\ V / | | | | |_ | | | | \| | _|
___) || | | | | | __/| _ | |_| | |\ | | | | |_| | _| | |_| | |\ | |___
|____/ |_| |_| |_|_| |_| |_|\___/|_| \_| |_| \___/|_| \___/|_| \_|_____|
MCP Orchestrator`)
);
console.log(chalk.gray(` Hub Server: ${SERVER_URL}`));
console.log(chalk.yellow(` Role: User/Orchestrator\n`));
await this.setupHandlers();
await this.showStats();
this.showHelp();
this.rl.prompt();
}
async setupHandlers() {
this.rl.on("line", async (line) => {
const input = line.trim();
if (input.startsWith("/")) {
await this.handleCommand(input);
} else if (this.currentRoom) {
await this.sendMessage(input);
} else {
console.log(
chalk.yellow("Not in a room. Use /join <room> to join a room.")
);
}
this.rl.prompt();
});
this.rl.on("close", () => {
if (this.socket) this.socket.disconnect();
console.log(chalk.gray("\nGoodbye!"));
process.exit(0);
});
}
async handleCommand(input) {
const [command, ...args] = input.split(" ");
switch (command) {
case "/help":
case "/h":
this.showHelp();
break;
case "/join":
case "/j":
await this.joinRoom(args[0]);
break;
case "/leave":
case "/l":
await this.leaveRoom();
break;
case "/rooms":
case "/r":
await this.listRooms();
break;
case "/agents":
case "/a":
await this.listAgents();
break;
case "/history":
case "/hist":
await this.showHistory(args[0]);
break;
case "/task":
await this.handleTaskCommand(args);
break;
case "/broadcast":
case "/bc":
await this.broadcast(args.join(" "));
break;
case "/stats":
case "/st":
await this.showStats();
break;
case "/assign":
await this.assignTask(args);
break;
case "/monitor":
case "/mon":
await this.monitorMode(args[0]);
break;
case "/tag":
await this.tagAgent(args);
break;
case "/logs":
await this.viewLogs(args[0]);
break;
case "/memory":
case "/mem":
await this.manageMemory(args);
break;
case "/notifications":
case "/notif":
await this.viewNotifications(args[0]);
break;
case "/name":
this.agentName = args.join(" ") || "Orchestrator";
console.log(chalk.green(`Name set to: ${this.agentName}`));
break;
case "/clear":
console.clear();
break;
case "/quit":
case "/q":
this.rl.close();
break;
default:
console.log(chalk.red(`Unknown command: ${command}`));
this.showHelp();
}
}
showHelp() {
console.log(chalk.yellow("\nš Orchestrator Commands:"));
console.log(chalk.cyan("Room Management:"));
console.log(" /join <room> - Join a chat room");
console.log(" /leave - Leave current room");
console.log(" /rooms - List all rooms");
console.log(" /agents - List agents in current room");
console.log(" /history [n] - Show last n messages");
console.log(chalk.cyan("\nAgent Orchestration:"));
console.log(" /broadcast <msg> - Send message to all agents in room");
console.log(" /assign <task> - Assign task to specific agent");
console.log(
" /tag <agent> <msg> - Send tagged message to specific agent"
);
console.log(" /monitor [room] - Monitor room activity");
console.log(" /stats - Show system statistics");
console.log(chalk.cyan("\nTask Management:"));
console.log(" /task create - Create a new task");
console.log(" /task list - List room tasks");
console.log(" /task update <id> - Update task status");
console.log(chalk.cyan("\nMemory & Notifications:"));
console.log(" /memory list - View system memory logs");
console.log(" /notifications - View recent notifications");
console.log(" /logs [type] - View system logs");
console.log(chalk.cyan("\nSystem:"));
console.log(" /name <name> - Set your display name");
console.log(" /clear - Clear screen");
console.log(" /help - Show this help");
console.log(" /quit - Exit\n");
}
async joinRoom(roomName) {
if (!roomName) {
console.log(chalk.red("Please specify a room name"));
return;
}
try {
const response = await axios.post(`${SERVER_URL}/api/join/${roomName}`, {
agentId: this.agentId,
agentName: this.agentName,
capabilities: {
role: "orchestrator",
type: "human",
permissions: ["broadcast", "assign_tasks", "monitor"],
},
});
if (response.data.success) {
this.currentRoom = roomName;
this.connectSocket();
console.log(chalk.green(`\nā Joined room: ${roomName}`));
console.log(
chalk.gray(` ${response.data.currentAgents.length} agents in room\n`)
);
// Update prompt
this.rl.setPrompt(chalk.cyan(`š [${roomName}] > `));
}
} catch (error) {
console.log(chalk.red(`Failed to join room: ${error.message}`));
}
}
async leaveRoom() {
if (!this.currentRoom) {
console.log(chalk.yellow("Not in a room"));
return;
}
try {
await axios.post(`${SERVER_URL}/api/leave/${this.agentId}`);
if (this.socket) {
this.socket.disconnect();
this.socket = null;
}
console.log(chalk.gray(`Left room: ${this.currentRoom}`));
this.currentRoom = null;
this.rl.setPrompt(chalk.cyan("š > "));
} catch (error) {
console.log(chalk.red(`Failed to leave room: ${error.message}`));
}
}
connectSocket() {
if (this.socket) this.socket.disconnect();
this.socket = io(SERVER_URL);
this.socket.on("connect", () => {
this.socket.emit("register", {
agentId: this.agentId,
room: this.currentRoom,
});
});
this.socket.on("message", (message) => {
// Don't show our own messages again
if (message.agentId !== this.agentId) {
this.displayMessage(message);
}
});
this.socket.on("task", (data) => {
console.log(chalk.magenta(`\n[Task ${data.type}] ${data.task.title}`));
this.rl.prompt();
});
}
displayMessage(message) {
const time = new Date(message.timestamp).toLocaleTimeString();
if (message.type === "system") {
console.log(chalk.gray(`\n[${time}] ${message.content}`));
} else {
const name = chalk.bold(message.agentName);
console.log(`\n[${time}] ${name}: ${message.content}`);
}
this.rl.prompt();
}
async sendMessage(content) {
if (!content) return;
try {
await axios.post(`${SERVER_URL}/api/send`, {
agentId: this.agentId,
content,
metadata: {},
});
} catch (error) {
console.log(chalk.red(`Failed to send message: ${error.message}`));
}
}
async listRooms() {
try {
const response = await axios.get(`${SERVER_URL}/api/rooms`);
const rooms = response.data.rooms;
console.log(chalk.yellow("\nActive rooms:"));
if (rooms.length === 0) {
console.log(chalk.gray(" No active rooms"));
} else {
rooms.forEach((room) => {
const current = room.name === this.currentRoom ? " (current)" : "";
console.log(
` ${chalk.bold(room.name)} - ${room.agentCount} agents${current}`
);
});
}
} catch (error) {
console.log(chalk.red(`Failed to list rooms: ${error.message}`));
}
}
async listAgents() {
if (!this.currentRoom) {
console.log(chalk.yellow("Not in a room"));
return;
}
try {
const response = await axios.get(
`${SERVER_URL}/api/agents/${this.currentRoom}`
);
const agents = response.data.agents;
console.log(chalk.yellow(`\nAgents in ${this.currentRoom}:`));
agents.forEach((agent) => {
const role = agent.capabilities?.role || "unknown";
console.log(` ${chalk.bold(agent.name)} (${role})`);
});
} catch (error) {
console.log(chalk.red(`Failed to list agents: ${error.message}`));
}
}
async showHistory(limitStr) {
if (!this.currentRoom) {
console.log(chalk.yellow("Not in a room"));
return;
}
const limit = parseInt(limitStr) || 20;
try {
const response = await axios.get(
`${SERVER_URL}/api/messages/${this.currentRoom}`,
{
params: { limit },
}
);
const messages = response.data.messages;
console.log(chalk.yellow(`\nLast ${messages.length} messages:`));
messages.forEach((msg) => this.displayMessage(msg));
} catch (error) {
console.log(chalk.red(`Failed to get history: ${error.message}`));
}
}
async handleTaskCommand(args) {
if (!this.currentRoom) {
console.log(chalk.yellow("Not in a room"));
return;
}
const subcommand = args[0];
switch (subcommand) {
case "create":
await this.createTask();
break;
case "list":
await this.listTasks();
break;
default:
console.log(chalk.yellow("Usage: /task create | /task list"));
}
}
async createTask() {
const title = await this.question("Task title: ");
const description = await this.question("Description: ");
const priority =
(await this.question("Priority (low/medium/high) [medium]: ")) ||
"medium";
const assignee = await this.question("Assign to (agent name, optional): ");
try {
const response = await axios.post(`${SERVER_URL}/api/tasks`, {
roomName: this.currentRoom,
title,
description,
priority,
assignee: assignee || undefined,
creator: this.agentName,
});
console.log(chalk.green("ā Task created successfully"));
} catch (error) {
console.log(chalk.red(`Failed to create task: ${error.message}`));
}
}
async listTasks() {
try {
const response = await axios.get(
`${SERVER_URL}/api/tasks/${this.currentRoom}`
);
const tasks = response.data.tasks;
console.log(chalk.yellow(`\nTasks in ${this.currentRoom}:`));
if (tasks.length === 0) {
console.log(chalk.gray(" No tasks"));
} else {
tasks.forEach((task) => {
const status = task.status.toUpperCase();
const assignee = task.assignee || "Unassigned";
console.log(` [${status}] ${chalk.bold(task.title)} - ${assignee}`);
});
}
} catch (error) {
console.log(chalk.red(`Failed to list tasks: ${error.message}`));
}
}
// New orchestrator methods
async showStats() {
try {
const response = await axios.get(`${SERVER_URL}/api/stats`);
const stats = response.data;
console.log(chalk.yellow("\nš System Statistics:"));
console.log(` Total Rooms: ${stats.totalRooms}`);
console.log(` Total Agents: ${stats.totalAgents}`);
console.log(` Total Tasks: ${stats.totalTasks}`);
console.log(` Shared Dir: ${stats.sharedDirectory}`);
if (stats.rooms.length > 0) {
console.log(chalk.yellow("\nš Room Details:"));
stats.rooms.forEach((room) => {
console.log(
` ${room.name}: ${room.agentCount} agents, ${room.messageCount} messages`
);
});
}
} catch (error) {
console.log(chalk.red(`Failed to get stats: ${error.message}`));
}
}
async broadcast(message) {
if (!this.currentRoom) {
console.log(chalk.yellow("Not in a room"));
return;
}
if (!message) {
console.log(chalk.red("Please provide a message to broadcast"));
return;
}
try {
await axios.post(`${SERVER_URL}/api/broadcast/${this.currentRoom}`, {
content: message,
from: this.agentName,
});
console.log(
chalk.green(`š¢ Broadcast sent to room "${this.currentRoom}"`)
);
} catch (error) {
console.log(chalk.red(`Failed to broadcast: ${error.message}`));
}
}
async assignTask(args) {
if (!this.currentRoom) {
console.log(chalk.yellow("Not in a room"));
return;
}
const agentName = args[0];
const taskDescription = args.slice(1).join(" ");
if (!agentName || !taskDescription) {
console.log(chalk.red("Usage: /assign <agent_name> <task_description>"));
return;
}
try {
const response = await axios.post(`${SERVER_URL}/api/tasks`, {
roomName: this.currentRoom,
title: `Assigned to ${agentName}`,
description: taskDescription,
assignee: agentName,
creator: this.agentName,
priority: "medium",
});
console.log(
chalk.green(`ā
Task assigned to ${agentName}: "${taskDescription}"`)
);
} catch (error) {
console.log(chalk.red(`Failed to assign task: ${error.message}`));
}
}
async monitorMode(roomName) {
const targetRoom = roomName || this.currentRoom;
if (!targetRoom) {
console.log(chalk.yellow("Please specify a room or join one first"));
return;
}
console.log(
chalk.blue(
`šļø Monitoring room "${targetRoom}"... (Press any key to stop)`
)
);
// Simple monitoring - could be enhanced with real-time updates
const interval = setInterval(async () => {
try {
const response = await axios.get(
`${SERVER_URL}/api/messages/${targetRoom}`,
{
params: { limit: 1 },
}
);
if (response.data.messages.length > 0) {
const msg = response.data.messages[0];
console.log(
chalk.gray(
`[${new Date(msg.timestamp).toLocaleTimeString()}] ${
msg.agentName
}: ${msg.content}`
)
);
}
} catch (error) {
// Silent fail for monitoring
}
}, 2000);
// Stop monitoring on any key press
process.stdin.setRawMode(true);
process.stdin.once("data", () => {
clearInterval(interval);
process.stdin.setRawMode(false);
console.log(chalk.blue("\nšļø Monitoring stopped"));
this.rl.prompt();
});
}
async tagAgent(args) {
if (!this.currentRoom) {
console.log(chalk.yellow("Not in a room"));
return;
}
const agentName = args[0];
const message = args.slice(1).join(" ");
if (!agentName || !message) {
console.log(chalk.red("Usage: /tag <agent_name> <message>"));
return;
}
try {
const response = await axios.post(`${SERVER_URL}/api/send`, {
agentId: this.agentId,
content: `@${agentName} ${message}`,
metadata: { type: "direct_tag", target: agentName },
});
console.log(chalk.green(`š·ļø Tagged ${agentName}: "${message}"`));
} catch (error) {
console.log(chalk.red(`Failed to tag agent: ${error.message}`));
}
}
async viewLogs(type = "all") {
try {
const response = await axios.get(`${SERVER_URL}/api/stats`);
const stats = response.data;
console.log(chalk.yellow(`\nš System Logs (${type}):`));
console.log(
`Total Messages: ${stats.rooms.reduce(
(sum, room) => sum + room.messageCount,
0
)}`
);
console.log(`Active Agents: ${stats.totalAgents}`);
console.log(`Total Tasks: ${stats.totalTasks}`);
if (stats.rooms.length > 0) {
console.log(chalk.yellow("\nš Activity by Room:"));
stats.rooms.forEach((room) => {
console.log(
` ${room.name}: ${room.messageCount} messages, ${room.agentCount} agents`
);
});
}
} catch (error) {
console.log(chalk.red(`Failed to get logs: ${error.message}`));
}
}
async manageMemory(args) {
const action = args[0];
if (action === "list") {
try {
const response = await axios.get(`${SERVER_URL}/api/stats`);
console.log(chalk.yellow("\nš§ Memory Usage Summary:"));
console.log(`Rooms in memory: ${response.data.totalRooms}`);
console.log(`Agents in memory: ${response.data.totalAgents}`);
console.log(`Tasks in memory: ${response.data.totalTasks}`);
console.log(
`Data directory: ${response.data.sharedDirectory || "Not available"}`
);
} catch (error) {
console.log(chalk.red(`Failed to get memory info: ${error.message}`));
}
} else {
console.log(chalk.yellow("Usage: /memory list"));
}
}
async viewNotifications(agentName) {
if (!agentName && !this.currentRoom) {
console.log(chalk.yellow("Please specify an agent name or join a room"));
return;
}
try {
// For orchestrator, show recent system events as "notifications"
const response = await axios.get(
`${SERVER_URL}/api/messages/${this.currentRoom || "system"}`,
{
params: { limit: 10 },
}
);
const messages = response.data.messages.filter(
(msg) => msg.type === "system" || msg.mentions?.length > 0
);
console.log(chalk.yellow("\nš Recent Notifications & System Events:"));
if (messages.length === 0) {
console.log(chalk.gray(" No recent notifications"));
} else {
messages.forEach((msg) => {
const time = new Date(msg.timestamp).toLocaleTimeString();
const type = msg.mentions?.length > 0 ? "š·ļø " : "š¢ ";
console.log(` ${type}[${time}] ${msg.content}`);
});
}
} catch (error) {
console.log(chalk.red(`Failed to get notifications: ${error.message}`));
}
}
question(prompt) {
return new Promise((resolve) => {
this.rl.question(chalk.cyan(prompt), resolve);
});
}
}
// Start the orchestrator
const orchestrator = new OrchestratorCLI();
orchestrator.start();