@sethdouglasford/claude-flow
Version:
Claude Code Flow - Advanced AI-powered development workflows with SPARC methodology
676 lines • 24.7 kB
JavaScript
/**
* Node.js Interactive REPL for Claude-Flow
* Compatible implementation using Node.js readline and inquirer
*/
import readline from "readline";
import fs from "fs/promises";
import path from "path";
import { spawn } from "child_process";
import colors from "chalk";
import Table from "cli-table3";
class CommandHistory {
history = [];
maxSize = 1000;
historyFile;
constructor(historyFile) {
this.historyFile = historyFile || path.join(process.cwd(), ".claude-flow-history");
this.loadHistory();
}
add(command) {
if (command.trim() && command !== this.history[this.history.length - 1]) {
this.history.push(command);
if (this.history.length > this.maxSize) {
this.history = this.history.slice(-this.maxSize);
}
this.saveHistory();
}
}
get() {
return [...this.history];
}
search(query) {
return this.history.filter(cmd => cmd.includes(query));
}
async loadHistory() {
try {
const content = await fs.readFile(this.historyFile, "utf-8");
this.history = content.split("\n").filter(line => line.trim());
}
catch {
// History file doesn't exist yet
}
}
async saveHistory() {
try {
await fs.writeFile(this.historyFile, this.history.join("\n"));
}
catch {
// Ignore save errors
}
}
}
class CommandCompleter {
commands = new Map();
setCommands(commands) {
this.commands.clear();
for (const cmd of commands) {
this.commands.set(cmd.name, cmd);
if (cmd.aliases) {
for (const alias of cmd.aliases) {
this.commands.set(alias, cmd);
}
}
}
}
complete(line) {
const parts = line.trim().split(/\s+/);
if (parts.length === 1) {
// Complete command names
const prefix = parts[0];
const completions = Array.from(this.commands.keys())
.filter(name => name.startsWith(prefix))
.sort();
return [completions, prefix];
}
// Complete subcommands and arguments
const commandName = parts[0];
const command = this.commands.get(commandName);
if (command) {
const subCompletions = this.completeForCommand(command, parts.slice(1));
return [subCompletions, parts[parts.length - 1]];
}
return [[], line];
}
completeForCommand(command, args) {
// Basic completion for known commands
switch (command.name) {
case "agent":
if (args.length === 1) {
return ["spawn", "list", "terminate", "info"].filter(sub => sub.startsWith(args[0]));
}
if (args[0] === "spawn" && args.length === 2) {
return ["coordinator", "researcher", "implementer", "analyst", "custom"]
.filter(type => type.startsWith(args[1]));
}
break;
case "task":
if (args.length === 1) {
return ["create", "list", "status", "cancel", "workflow"].filter(sub => sub.startsWith(args[0]));
}
if (args[0] === "create" && args.length === 2) {
return ["research", "implementation", "analysis", "coordination"]
.filter(type => type.startsWith(args[1]));
}
break;
case "session":
if (args.length === 1) {
return ["list", "save", "restore", "delete", "export", "import"]
.filter(sub => sub.startsWith(args[0]));
}
break;
case "workflow":
if (args.length === 1) {
return ["run", "validate", "list", "status", "stop", "template"]
.filter(sub => sub.startsWith(args[0]));
}
break;
}
return [];
}
}
/**
* Start the Node.js interactive REPL
*/
export async function startNodeREPL(options = {}) {
// Create completer before interface
const completer = new CommandCompleter();
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
prompt: "",
completer: (line) => {
return completer.complete(line);
},
});
const context = {
options,
history: [],
workingDirectory: process.cwd(),
connectionStatus: "disconnected",
lastActivity: new Date(),
rl,
};
const history = new CommandHistory(options.historyFile);
const commands = [
{
name: "help",
aliases: ["h", "?"],
description: "Show available commands or help for a specific command",
usage: "help [command]",
examples: ["help", "help agent", "help task create"],
handler: async (args) => {
if (args.length === 0) {
showHelp(commands);
}
else {
showCommandHelp(commands, args[0]);
}
},
},
{
name: "status",
aliases: ["st"],
description: "Show system status and connection info",
usage: "status [component]",
examples: ["status", "status orchestrator"],
handler: async (args, ctx) => {
await showSystemStatus(ctx, args[0]);
},
},
{
name: "connect",
aliases: ["conn"],
description: "Connect to Claude-Flow orchestrator",
usage: "connect [host:port]",
examples: ["connect", "connect localhost:3000"],
handler: async (args, ctx) => {
await connectToOrchestrator(ctx, args[0]);
},
},
{
name: "agent",
description: "Agent management (spawn, list, terminate, info)",
usage: "agent <subcommand> [options]",
examples: [
"agent list",
"agent spawn researcher --name \"Research Agent\"",
"agent info agent-001",
"agent terminate agent-001",
],
handler: async (args, ctx) => {
await handleAgentCommand(args, ctx);
},
},
{
name: "task",
description: "Task management (create, list, status, cancel)",
usage: "task <subcommand> [options]",
examples: [
"task list",
"task create research \"Find quantum computing papers\"",
"task status task-001",
"task cancel task-001",
],
handler: async (args, ctx) => {
await handleTaskCommand(args, ctx);
},
},
{
name: "memory",
description: "Memory operations (query, stats, export)",
usage: "memory <subcommand> [options]",
examples: [
"memory stats",
"memory query --agent agent-001",
"memory export memory.json",
],
handler: async (args, ctx) => {
await handleMemoryCommand(args, ctx);
},
},
{
name: "session",
description: "Session management (save, restore, list)",
usage: "session <subcommand> [options]",
examples: [
"session list",
"session save \"Development Session\"",
"session restore session-001",
],
handler: async (args, ctx) => {
await handleSessionCommand(args, ctx);
},
},
{
name: "workflow",
description: "Workflow operations (run, list, status)",
usage: "workflow <subcommand> [options]",
examples: [
"workflow list",
"workflow run workflow.json",
"workflow status workflow-001",
],
handler: async (args, ctx) => {
await handleWorkflowCommand(args, ctx);
},
},
{
name: "monitor",
aliases: ["mon"],
description: "Start monitoring mode",
usage: "monitor [--interval seconds]",
examples: ["monitor", "monitor --interval 5"],
handler: async (_args) => {
console.log(colors.cyan("Starting monitor mode..."));
console.log(colors.gray("(This would start the live dashboard)"));
},
},
{
name: "history",
aliases: ["hist"],
description: "Show command history",
usage: "history [--search query]",
examples: ["history", "history --search agent"],
handler: async (args) => {
const searchQuery = args.indexOf("--search") >= 0 ? args[args.indexOf("--search") + 1] : null;
const historyItems = searchQuery ? history.search(searchQuery) : history.get();
console.log(colors.cyan.bold(`Command History${searchQuery ? ` (search: ${searchQuery})` : ""}`));
console.log("─".repeat(50));
if (historyItems.length === 0) {
console.log(colors.gray("No commands in history"));
return;
}
const recent = historyItems.slice(-20); // Show last 20
recent.forEach((cmd, i) => {
const lineNumber = historyItems.length - recent.length + i + 1;
console.log(`${colors.gray(lineNumber.toString().padStart(3))} ${cmd}`);
});
},
},
{
name: "clear",
aliases: ["cls"],
description: "Clear the screen",
handler: async () => {
console.clear();
},
},
{
name: "cd",
description: "Change working directory",
usage: "cd <directory>",
examples: ["cd /path/to/project", "cd .."],
handler: async (args, ctx) => {
if (args.length === 0) {
console.log(ctx.workingDirectory);
return;
}
try {
const newDir = args[0] === "~" ? process.env.HOME ?? "/" : args[0];
process.chdir(newDir);
ctx.workingDirectory = process.cwd();
console.log(colors.gray(`Changed to: ${ctx.workingDirectory}`));
}
catch (error) {
console.error(colors.red("Error:"), error instanceof Error ? error.message : String(error));
}
},
},
{
name: "pwd",
description: "Print working directory",
handler: async (_, ctx) => {
console.log(ctx.workingDirectory);
},
},
{
name: "echo",
description: "Echo arguments",
usage: "echo <text>",
examples: ["echo \"Hello, world!\""],
handler: async (args) => {
console.log(args.join(" "));
},
},
{
name: "exit",
aliases: ["quit", "q"],
description: "Exit the REPL",
handler: async (_, ctx) => {
console.log(colors.gray("Goodbye!"));
ctx.rl.close();
process.exit(0);
},
},
];
// Set up command completion
completer.setCommands(commands);
// Show initial status
if (options.banner !== false) {
displayBanner();
}
await showSystemStatus(context);
console.log(colors.gray("Type \"help\" for available commands or \"exit\" to quit.\n"));
// Main REPL loop
const processCommand = async (input) => {
if (!input.trim()) {
return;
}
// Add to history
history.add(input);
context.history.push(input);
context.lastActivity = new Date();
// Parse command
const args = parseCommand(input);
const [commandName, ...commandArgs] = args;
// Find and execute command
const command = commands.find(c => c.name === commandName ||
(c.aliases?.includes(commandName)));
if (command) {
try {
await command.handler(commandArgs, context);
}
catch (error) {
console.error(colors.red("Command failed:"), error instanceof Error ? error.message : String(error));
}
}
else {
console.log(colors.red(`Unknown command: ${commandName}`));
console.log(colors.gray("Type \"help\" for available commands"));
// Suggest similar commands
const suggestions = findSimilarCommands(commandName, commands);
if (suggestions.length > 0) {
console.log(colors.gray("Did you mean:"), suggestions.map(s => colors.cyan(s)).join(", "));
}
}
};
// Set up readline prompt
const showPrompt = () => {
const prompt = createPrompt(context);
rl.setPrompt(prompt);
rl.prompt();
};
rl.on("line", async (input) => {
try {
await processCommand(input);
}
catch (error) {
console.error(colors.red("REPL Error:"), error instanceof Error ? error.message : String(error));
}
showPrompt();
});
rl.on("close", () => {
console.log(`\n${colors.gray("Goodbye!")}`);
process.exit(0);
});
rl.on("SIGINT", () => {
console.log(`\n${colors.gray("Use \"exit\" to quit or Ctrl+D")}`);
showPrompt();
});
// Start the REPL
showPrompt();
}
function displayBanner() {
const banner = `
${colors.cyan.bold("╔══════════════════════════════════════════════════════════════╗")}
${colors.cyan.bold("║")} ${colors.white.bold("🧠 Claude-Flow REPL")} ${colors.cyan.bold("║")}
${colors.cyan.bold("║")} ${colors.gray("Interactive AI Agent Orchestration")} ${colors.cyan.bold("║")}
${colors.cyan.bold("╚══════════════════════════════════════════════════════════════╝")}
`;
console.log(banner);
}
function createPrompt(context) {
const statusIcon = getConnectionStatusIcon(context.connectionStatus);
const dir = path.basename(context.workingDirectory) || "/";
return `${statusIcon} ${colors.cyan("claude-flow")}:${colors.yellow(dir)}${colors.white("> ")}`;
}
function getConnectionStatusIcon(status) {
switch (status) {
case "connected": return colors.green("●");
case "connecting": return colors.yellow("◐");
case "disconnected": return colors.red("○");
default: return colors.gray("?");
}
}
function parseCommand(input) {
// Simple command parsing - handle quoted strings
const args = [];
let current = "";
let inQuotes = false;
let quoteChar = "";
for (let i = 0; i < input.length; i++) {
const char = input[i];
if (inQuotes) {
if (char === quoteChar) {
inQuotes = false;
quoteChar = "";
}
else {
current += char;
}
}
else {
if (char === "\"" || char === "'") {
inQuotes = true;
quoteChar = char;
}
else if (char === " " || char === "\t") {
if (current.trim()) {
args.push(current.trim());
current = "";
}
}
else {
current += char;
}
}
}
if (current.trim()) {
args.push(current.trim());
}
return args;
}
function showHelp(commands) {
console.log(colors.cyan.bold("Claude-Flow Interactive REPL"));
console.log("─".repeat(50));
console.log();
console.log(colors.white.bold("Available Commands:"));
console.log();
const table = new Table({
head: ["Command", "Aliases", "Description"],
style: { head: ["cyan"] },
});
for (const cmd of commands) {
table.push([
colors.cyan(cmd.name),
cmd.aliases ? colors.gray(cmd.aliases.join(", ")) : "",
cmd.description,
]);
}
console.log(table.toString());
console.log();
console.log(colors.gray("Tips:"));
console.log(colors.gray("• Use TAB for command completion"));
console.log(colors.gray("• Use \"help <command>\" for detailed help"));
console.log(colors.gray("• Use UP/DOWN arrows for command history"));
console.log(colors.gray("• Use Ctrl+C or \"exit\" to quit"));
}
function showCommandHelp(commands, commandName) {
const command = commands.find(c => c.name === commandName ||
(c.aliases?.includes(commandName)));
if (!command) {
console.log(colors.red(`Unknown command: ${commandName}`));
return;
}
console.log(colors.cyan.bold(`Command: ${command.name}`));
console.log("─".repeat(30));
console.log(`${colors.white("Description:")} ${command.description}`);
if (command.aliases) {
console.log(`${colors.white("Aliases:")} ${command.aliases.join(", ")}`);
}
if (command.usage) {
console.log(`${colors.white("Usage:")} ${command.usage}`);
}
if (command.examples) {
console.log();
console.log(colors.white.bold("Examples:"));
for (const example of command.examples) {
console.log(` ${colors.gray("$")} ${colors.cyan(example)}`);
}
}
}
async function showSystemStatus(context, component) {
console.log(colors.cyan.bold("System Status"));
console.log("─".repeat(30));
const statusIcon = context.connectionStatus === "connected" ? colors.green("✓") : colors.red("✗");
console.log(`${statusIcon} Connection: ${context.connectionStatus}`);
console.log(`${colors.white("Working Directory:")} ${context.workingDirectory}`);
console.log(`${colors.white("Last Activity:")} ${context.lastActivity.toLocaleTimeString()}`);
if (context.currentSession) {
console.log(`${colors.white("Current Session:")} ${context.currentSession}`);
}
console.log(`${colors.white("Commands in History:")} ${context.history.length}`);
if (context.connectionStatus === "disconnected") {
console.log();
console.log(colors.yellow("⚠ Not connected to orchestrator"));
console.log(colors.gray("Use \"connect\" command to establish connection"));
}
}
async function connectToOrchestrator(context, target) {
const host = target || "localhost:3000";
console.log(colors.yellow(`Connecting to ${host}...`));
context.connectionStatus = "connecting";
// Mock connection attempt
await new Promise(resolve => setTimeout(resolve, 1000));
// Check if orchestrator is actually running by trying to execute status command
try {
const result = await executeCliCommand(["status"]);
if (result.success) {
context.connectionStatus = "connected";
console.log(colors.green("✓ Connected successfully"));
}
else {
context.connectionStatus = "disconnected";
console.log(colors.red("✗ Connection failed"));
console.log(colors.gray("Make sure Claude-Flow is running with: npx claude-flow start"));
}
}
catch (_error) {
context.connectionStatus = "disconnected";
console.log(colors.red("✗ Connection failed"));
console.log(colors.gray("Make sure Claude-Flow is running with: npx claude-flow start"));
}
}
async function executeCliCommand(args) {
return new Promise((resolve) => {
const child = spawn("node", ["dist/cli/index.js", ...args], {
stdio: "pipe",
cwd: process.cwd(),
});
let output = "";
let error = "";
child.stdout?.on("data", (data) => {
output += data.toString();
});
child.stderr?.on("data", (data) => {
error += data.toString();
});
child.on("close", (code) => {
resolve({
success: code === 0,
output: output || error,
});
});
child.on("error", (err) => {
resolve({
success: false,
output: err.message,
});
});
});
}
async function handleAgentCommand(args, context) {
if (context.connectionStatus !== "connected") {
console.log(colors.yellow("⚠ Not connected to orchestrator"));
console.log(colors.gray("Use \"connect\" to establish connection first"));
return;
}
if (args.length === 0) {
console.log(colors.gray("Usage: agent <spawn|list|terminate|info> [options]"));
return;
}
const subcommand = args[0];
const cliArgs = ["agent", ...args];
try {
const result = await executeCliCommand(cliArgs);
console.log(result.output);
}
catch (error) {
console.error(colors.red("Error executing agent command:"), error instanceof Error ? error.message : String(error));
}
}
async function handleTaskCommand(args, context) {
if (context.connectionStatus !== "connected") {
console.log(colors.yellow("⚠ Not connected to orchestrator"));
return;
}
if (args.length === 0) {
console.log(colors.gray("Usage: task <create|list|status|cancel> [options]"));
return;
}
const cliArgs = ["task", ...args];
try {
const result = await executeCliCommand(cliArgs);
console.log(result.output);
}
catch (error) {
console.error(colors.red("Error executing task command:"), error instanceof Error ? error.message : String(error));
}
}
async function handleMemoryCommand(args, context) {
if (args.length === 0) {
console.log(colors.gray("Usage: memory <query|stats|export> [options]"));
return;
}
const cliArgs = ["memory", ...args];
try {
const result = await executeCliCommand(cliArgs);
console.log(result.output);
}
catch (error) {
console.error(colors.red("Error executing memory command:"), error instanceof Error ? error.message : String(error));
}
}
async function handleSessionCommand(args, context) {
if (args.length === 0) {
console.log(colors.gray("Usage: session <list|save|restore> [options]"));
return;
}
const cliArgs = ["session", ...args];
try {
const result = await executeCliCommand(cliArgs);
console.log(result.output);
}
catch (error) {
console.error(colors.red("Error executing session command:"), error instanceof Error ? error.message : String(error));
}
}
async function handleWorkflowCommand(args, context) {
if (context.connectionStatus !== "connected") {
console.log(colors.yellow("⚠ Not connected to orchestrator"));
return;
}
if (args.length === 0) {
console.log(colors.gray("Usage: workflow <list|run|status> [options]"));
return;
}
const cliArgs = ["workflow", ...args];
try {
const result = await executeCliCommand(cliArgs);
console.log(result.output);
}
catch (error) {
console.error(colors.red("Error executing workflow command:"), error instanceof Error ? error.message : String(error));
}
}
function findSimilarCommands(input, commands) {
const allNames = commands.flatMap(c => [c.name, ...(c.aliases || [])]);
return allNames
.filter(name => {
// Simple similarity check - could use Levenshtein distance
const commonChars = input.split("").filter(char => name.includes(char)).length;
return commonChars >= Math.min(2, input.length / 2);
})
.slice(0, 3); // Top 3 suggestions
}
//# sourceMappingURL=node-repl.js.map