UNPKG

@juspay/neurolink

Version:

The pipe layer of an AI nervous system: one interface connecting provider neurons to your application, across three inference types — generate, stream and decide. `decide` returns typed, calibrated judgements from a non-generative model (~400ms, ~$0.00002

718 lines • 31.3 kB
/** * Agent CLI Commands for NeuroLink * Implements comprehensive multi-agent orchestration commands * * Commands: * - agent create: Create a new agent definition * - agent list: List registered agents * - agent execute: Execute a single agent * - network create: Create an agent network * - network execute: Execute a network */ import { NeuroLink } from "../../neurolink.js"; import { logger } from "../../utils/logger.js"; import chalk from "chalk"; import ora from "ora"; import fs from "fs"; import path from "path"; // In-memory storage for agents and networks (session-based) const registeredAgents = new Map(); const registeredNetworks = new Map(); /** * Agent CLI command factory */ export class AgentCommandFactory { /** * Create the main agent command with subcommands */ static createAgentCommands() { return { command: "agent <subcommand>", describe: "Manage AI agents for multi-agent orchestration", builder: (yargs) => { return yargs .command("create", "Create a new agent definition", (yargs) => this.buildCreateOptions(yargs), (argv) => this.executeCreate(argv)) .command("list", "List registered agents", (yargs) => this.buildListOptions(yargs), (argv) => this.executeList(argv)) .command("execute <id> <input>", "Execute a single agent with given input", (yargs) => this.buildExecuteOptions(yargs), (argv) => this.executeAgent(argv)) .command("run <id> <input>", "Execute a single agent (alias for execute)", (yargs) => this.buildExecuteOptions(yargs), (argv) => this.executeAgent(argv)) .option("format", { choices: ["text", "json", "table"], default: "text", description: "Output format", }) .option("output", { type: "string", description: "Save output to file", }) .option("quiet", { type: "boolean", alias: "q", default: false, description: "Suppress non-essential output", }) .option("debug", { type: "boolean", default: false, description: "Enable debug output", }) .demandCommand(1, "Please specify an agent subcommand") .help(); }, handler: () => { // No-op handler as subcommands handle everything }, }; } /** * Create the network command with subcommands */ static createNetworkCommands() { return { command: "network <subcommand>", describe: "Manage agent networks for multi-agent orchestration", builder: (yargs) => { return yargs .command("create", "Create a new agent network", (yargs) => this.buildNetworkCreateOptions(yargs), (argv) => this.executeNetworkCreate(argv)) .command("list", "List registered networks", (yargs) => this.buildNetworkListOptions(yargs), (argv) => this.executeNetworkList(argv)) .command("execute <id> <input>", "Execute an agent network with given input", (yargs) => this.buildNetworkExecuteOptions(yargs), (argv) => this.executeNetwork(argv)) .command("run <id> <input>", "Execute an agent network (alias for execute)", (yargs) => this.buildNetworkExecuteOptions(yargs), (argv) => this.executeNetwork(argv)) .option("format", { choices: ["text", "json", "table"], default: "text", description: "Output format", }) .option("output", { type: "string", description: "Save output to file", }) .option("quiet", { type: "boolean", alias: "q", default: false, description: "Suppress non-essential output", }) .option("debug", { type: "boolean", default: false, description: "Enable debug output", }) .demandCommand(1, "Please specify a network subcommand") .help(); }, handler: () => { // No-op handler as subcommands handle everything }, }; } // ============================================================================ // AGENT COMMAND BUILDERS // ============================================================================ static buildCreateOptions(yargs) { return yargs .option("id", { type: "string", description: "Unique agent identifier", demandOption: true, }) .option("name", { type: "string", description: "Human-readable agent name", demandOption: true, }) .option("description", { type: "string", description: "Description of agent capabilities (for routing)", demandOption: true, }) .option("instructions", { type: "string", description: "System instructions for the agent", demandOption: true, }) .option("provider", { type: "string", description: "AI provider (e.g., openai, anthropic, vertex)", }) .option("model", { type: "string", description: "Model to use (e.g., gpt-4o, claude-3-sonnet)", }) .option("tools", { type: "array", description: "Tools available to the agent", }) .option("maxSteps", { type: "number", default: 10, description: "Maximum execution steps", }) .option("temperature", { type: "number", default: 0.7, description: "Generation temperature (0-1)", }) .option("file", { type: "string", description: "Load agent definition from JSON file", }) .example('$0 agent create --id researcher --name "Research Agent" --description "Searches and analyzes information" --instructions "You are a research assistant..."', "Create a research agent") .example("$0 agent create --file agent-config.json", "Create agent from config file"); } static buildListOptions(yargs) { return yargs .option("detailed", { type: "boolean", default: false, description: "Show detailed agent information", }) .example("$0 agent list", "List all registered agents") .example("$0 agent list --format json", "List agents in JSON format"); } static buildExecuteOptions(yargs) { return yargs .positional("id", { type: "string", description: "Agent ID to execute", demandOption: true, }) .positional("input", { type: "string", description: "Input prompt for the agent", demandOption: true, }) .option("context", { type: "string", description: "Additional context as JSON", }) .option("maxSteps", { type: "number", description: "Override maximum steps", }) .option("stream", { type: "boolean", default: false, description: "Stream output in real-time", }) .example('$0 agent execute researcher "Find information about AI trends"', "Execute the researcher agent") .example('$0 agent run writer "Write a blog post" --stream', "Execute with streaming"); } // ============================================================================ // NETWORK COMMAND BUILDERS // ============================================================================ static buildNetworkCreateOptions(yargs) { return yargs .option("name", { type: "string", description: "Network name", demandOption: true, }) .option("description", { type: "string", description: "Network description", }) .option("file", { type: "string", description: "Load network configuration from JSON file", demandOption: true, }) .option("routerProvider", { type: "string", description: "Provider for the routing agent", }) .option("routerModel", { type: "string", description: "Model for the routing agent", }) .example('$0 network create --name "Content Team" --file network-config.json', "Create a content team network"); } static buildNetworkListOptions(yargs) { return yargs .option("detailed", { type: "boolean", default: false, description: "Show detailed network information", }) .example("$0 network list", "List all registered networks") .example("$0 network list --format json", "List networks in JSON format"); } static buildNetworkExecuteOptions(yargs) { return yargs .positional("id", { type: "string", description: "Network ID to execute", demandOption: true, }) .positional("input", { type: "string", description: "Input message for the network", demandOption: true, }) .option("context", { type: "string", description: "Additional context as JSON", }) .option("maxSteps", { type: "number", default: 10, description: "Maximum execution steps", }) .option("timeout", { type: "number", default: 120000, description: "Execution timeout in milliseconds", }) .option("stream", { type: "boolean", default: false, description: "Stream output in real-time", }) .example('$0 network execute content-team "Write an article about AI"', "Execute the content team network") .example('$0 network run research-team "Analyze market trends" --stream', "Execute with streaming"); } // ============================================================================ // AGENT COMMAND HANDLERS // ============================================================================ static async executeCreate(argv) { const spinner = ora("Creating agent...").start(); try { let definition; if (argv.file) { // Load from file const filePath = path.resolve(argv.file); if (!fs.existsSync(filePath)) { throw new Error(`Agent definition file not found: ${filePath}`); } const content = fs.readFileSync(filePath, "utf-8"); definition = JSON.parse(content); } else { // Build from command line arguments definition = { id: argv.id, name: argv.name, description: argv.description, instructions: argv.instructions, provider: argv.provider, model: argv.model, tools: argv.tools, maxSteps: argv.maxSteps, temperature: argv.temperature, }; } // Validate required fields if (!definition.id || !definition.name || !definition.description || !definition.instructions) { throw new Error("Agent definition requires: id, name, description, and instructions"); } // Register the agent registeredAgents.set(definition.id, definition); spinner.succeed(chalk.green(`Agent "${definition.name}" created successfully`)); if (!argv.quiet) { logger.always(chalk.cyan("\nAgent Details:")); logger.always(` ID: ${definition.id}`); logger.always(` Name: ${definition.name}`); logger.always(` Description: ${definition.description}`); if (definition.provider) { logger.always(` Provider: ${definition.provider}`); } if (definition.model) { logger.always(` Model: ${definition.model}`); } if (definition.tools?.length) { logger.always(` Tools: ${definition.tools.join(", ")}`); } logger.always(` Max Steps: ${definition.maxSteps || 10}`); logger.always(` Temperature: ${definition.temperature || 0.7}`); } if (argv.format === "json") { logger.always(JSON.stringify(definition, null, 2)); } } catch (error) { spinner.fail(chalk.red("Failed to create agent")); logger.error(error.message); process.exitCode = 1; } } static async executeList(argv) { try { const agents = Array.from(registeredAgents.values()); if (agents.length === 0) { logger.always(chalk.yellow("No agents registered. Use 'neurolink agent create' to create one.")); return; } if (argv.format === "json") { logger.always(JSON.stringify(agents, null, 2)); return; } if (argv.format === "table") { const tableData = agents.map((a) => ({ ID: a.id, Name: a.name, Provider: a.provider || "default", Model: a.model || "default", Tools: a.tools?.length || 0, })); logger.info(JSON.stringify(tableData, null, 2)); return; } // Text format logger.always(chalk.cyan(`\nRegistered Agents (${agents.length}):\n`)); for (const agent of agents) { logger.always(chalk.bold(` ${agent.name} (${agent.id})`)); logger.always(chalk.gray(` ${agent.description}`)); if (agent.provider || agent.model) { logger.always(chalk.gray(` Provider: ${agent.provider || "default"}, Model: ${agent.model || "default"}`)); } if (agent.tools?.length) { logger.always(chalk.gray(` Tools: ${agent.tools.join(", ")}`)); } logger.always(""); } } catch (error) { logger.error(`Failed to list agents: ${error.message}`); process.exitCode = 1; } } static async executeAgent(argv) { const spinner = ora("Executing agent...").start(); try { const agentId = argv.id; const input = argv.input; // Get agent definition const definition = registeredAgents.get(agentId); if (!definition) { throw new Error(`Agent not found: ${agentId}. Use 'neurolink agent list' to see available agents.`); } // Parse context if provided let context; if (argv.context) { try { context = JSON.parse(argv.context); } catch { throw new Error("Invalid JSON in --context parameter"); } } // Create NeuroLink instance and agent const neurolink = new NeuroLink(); const agent = await neurolink.createAgent(definition); if (argv.stream) { spinner.stop(); logger.always(chalk.cyan(`\n[${definition.name}] Streaming response:\n`)); // Stream execution for await (const chunk of agent.stream(input, { context, maxSteps: argv.maxSteps, })) { if (chunk.type === "agent-text" && chunk.content) { process.stdout.write(chunk.content); } else if (chunk.type === "agent-tool-call" && chunk.toolName) { logger.always(chalk.yellow(`\n[Tool: ${chunk.toolName}]`)); } else if (chunk.type === "agent-tool-result") { logger.always(chalk.green(`[Tool Result: ${chunk.success ? "Success" : "Failed"}]`)); } else if (chunk.type === "agent-complete") { logger.always(chalk.gray(`\n\nCompleted in ${chunk.duration}ms`)); } else if (chunk.type === "agent-error") { logger.always(chalk.red(`\nError: ${chunk.error}`)); process.exitCode = 1; } } logger.always(""); } else { // Non-streaming execution const result = await agent.execute(input, { context, maxSteps: argv.maxSteps, }); spinner.succeed(chalk.green("Agent execution completed")); if (argv.format === "json") { logger.always(JSON.stringify(result, null, 2)); } else { logger.always(chalk.cyan(`\n[${definition.name}] Response:\n`)); logger.always(result.content); if (!argv.quiet && result.toolsUsed?.length) { logger.always(chalk.gray(`\nTools used: ${result.toolsUsed.join(", ")}`)); } if (!argv.quiet) { logger.always(chalk.gray(`\nCompleted in ${result.duration}ms`)); } } // Save to file if output specified if (argv.output) { const output = argv.format === "json" ? JSON.stringify(result, null, 2) : result.content; fs.writeFileSync(argv.output, output); logger.always(chalk.green(`\nOutput saved to: ${argv.output}`)); } } } catch (error) { spinner.fail(chalk.red("Agent execution failed")); logger.error(error.message); process.exitCode = 1; } } // ============================================================================ // NETWORK COMMAND HANDLERS // ============================================================================ static async executeNetworkCreate(argv) { const spinner = ora("Creating agent network...").start(); try { if (!argv.file) { throw new Error("Network configuration file is required. Use --file to specify."); } // Load network configuration from file const filePath = path.resolve(argv.file); if (!fs.existsSync(filePath)) { throw new Error(`Network configuration file not found: ${filePath}`); } const content = fs.readFileSync(filePath, "utf-8"); const config = JSON.parse(content); // Override with command line options if (argv.name) { config.name = argv.name; } if (argv.description) { config.description = argv.description; } if (argv.routerProvider || argv.routerModel) { config.router = config.router || {}; if (argv.routerProvider) { config.router.provider = argv.routerProvider; } if (argv.routerModel) { config.router.model = argv.routerModel; } } // Validate required fields if (!config.name || !config.agents || config.agents.length === 0) { throw new Error("Network configuration requires: name and at least one agent"); } // Generate ID if not provided const networkId = config.id || config.name.toLowerCase().replace(/\s+/g, "-"); config.id = networkId; // Register the network registeredNetworks.set(networkId, config); // Also register individual agents for (const agentDef of config.agents) { registeredAgents.set(agentDef.id, agentDef); } spinner.succeed(chalk.green(`Network "${config.name}" created successfully`)); if (!argv.quiet) { logger.always(chalk.cyan("\nNetwork Details:")); logger.always(` ID: ${networkId}`); logger.always(` Name: ${config.name}`); if (config.description) { logger.always(` Description: ${config.description}`); } logger.always(` Agents: ${config.agents.length}`); for (const agent of config.agents) { logger.always(` - ${agent.name} (${agent.id})`); } if (config.router?.provider) { logger.always(` Router Provider: ${config.router.provider}`); } if (config.router?.model) { logger.always(` Router Model: ${config.router.model}`); } } if (argv.format === "json") { logger.always(JSON.stringify(config, null, 2)); } } catch (error) { spinner.fail(chalk.red("Failed to create network")); logger.error(error.message); process.exitCode = 1; } } static async executeNetworkList(argv) { try { const networks = Array.from(registeredNetworks.values()); if (networks.length === 0) { logger.always(chalk.yellow("No networks registered. Use 'neurolink network create' to create one.")); return; } if (argv.format === "json") { logger.always(JSON.stringify(networks, null, 2)); return; } if (argv.format === "table") { const tableData = networks.map((n) => ({ ID: n.id || n.name.toLowerCase().replace(/\s+/g, "-"), Name: n.name, Agents: n.agents.length, Workflows: n.workflows?.length || 0, Tools: n.tools?.length || 0, })); logger.info(JSON.stringify(tableData, null, 2)); return; } // Text format logger.always(chalk.cyan(`\nRegistered Networks (${networks.length}):\n`)); for (const network of networks) { const networkId = network.id || network.name.toLowerCase().replace(/\s+/g, "-"); logger.always(chalk.bold(` ${network.name} (${networkId})`)); if (network.description) { logger.always(chalk.gray(` ${network.description}`)); } logger.always(chalk.gray(` Agents: ${network.agents.map((a) => a.name).join(", ")}`)); if (network.workflows?.length) { logger.always(chalk.gray(` Workflows: ${network.workflows.length}`)); } if (network.tools?.length) { logger.always(chalk.gray(` Tools: ${network.tools.join(", ")}`)); } logger.always(""); } } catch (error) { logger.error(`Failed to list networks: ${error.message}`); process.exitCode = 1; } } static async executeNetwork(argv) { const spinner = ora("Executing agent network...").start(); try { const networkId = argv.id; const input = argv.input; // Get network configuration const config = registeredNetworks.get(networkId); if (!config) { throw new Error(`Network not found: ${networkId}. Use 'neurolink network list' to see available networks.`); } // Parse context if provided let context; if (argv.context) { try { context = JSON.parse(argv.context); } catch { throw new Error("Invalid JSON in --context parameter"); } } // Create NeuroLink instance and network const neurolink = new NeuroLink(); const network = await neurolink.createNetwork(config); if (argv.stream) { spinner.stop(); logger.always(chalk.cyan(`\n[${config.name}] Streaming execution:\n`)); // Stream execution for await (const chunk of neurolink.streamNetwork(network, { message: input, context, }, { maxSteps: argv.maxSteps, timeout: argv.timeout, })) { switch (chunk.type) { case "network-start": { const startChunk = chunk; logger.always(chalk.blue(`Network started: ${startChunk.networkId}`)); break; } case "routing-decision": { const routingChunk = chunk; logger.always(chalk.yellow(`\nRouting to: ${routingChunk.decision.selectedPrimitive.name}`)); logger.always(chalk.gray(` Confidence: ${(routingChunk.decision.confidence * 100).toFixed(0)}%`)); logger.always(chalk.gray(` Reasoning: ${routingChunk.decision.reasoning}`)); break; } case "primitive-start": { const primitiveStartChunk = chunk; logger.always(chalk.cyan(`\n[${primitiveStartChunk.primitive.name}] Starting...`)); break; } case "agent-text": { const textChunk = chunk; process.stdout.write(textChunk.content); break; } case "agent-tool-call": { const toolCallChunk = chunk; logger.always(chalk.yellow(`\n[Tool: ${toolCallChunk.toolName}]`)); break; } case "agent-tool-result": { const toolResultChunk = chunk; logger.always(chalk.green(`[Tool Result: ${toolResultChunk.success ? "Success" : "Failed"}]`)); break; } case "primitive-end": { const primitiveEndChunk = chunk; logger.always(chalk.cyan(`\n[${primitiveEndChunk.primitive.name}] Completed`)); break; } case "network-complete": { const completeChunk = chunk; logger.always(chalk.green(`\n\nNetwork completed in ${completeChunk.result.duration}ms`)); logger.always(chalk.gray(`Steps taken: ${completeChunk.result.trace.steps.length}`)); break; } case "network-error": { const errorChunk = chunk; logger.always(chalk.red(`\nNetwork error: ${errorChunk.error}`)); process.exitCode = 1; break; } } } logger.always(""); } else { // Non-streaming execution const result = await neurolink.executeNetwork(network, { message: input, context, }, { maxSteps: argv.maxSteps, timeout: argv.timeout, }); spinner.succeed(chalk.green("Network execution completed")); if (argv.format === "json") { logger.always(JSON.stringify(result, null, 2)); } else { logger.always(chalk.cyan(`\n[${config.name}] Result:\n`)); logger.always(result.content); if (!argv.quiet) { logger.always(chalk.gray(`\nStatus: ${result.status}`)); logger.always(chalk.gray(`Duration: ${result.duration}ms`)); logger.always(chalk.gray(`Steps: ${result.trace.steps.length}`)); if (result.trace.routingDecisions.length > 0) { logger.always(chalk.gray("\nRouting History:")); for (const decision of result.trace.routingDecisions) { logger.always(chalk.gray(` ${decision.stepIndex + 1}. ${decision.selectedPrimitive.name} (${(decision.confidence * 100).toFixed(0)}%)`)); } } } } // Save to file if output specified if (argv.output) { const output = argv.format === "json" ? JSON.stringify(result, null, 2) : result.content; fs.writeFileSync(argv.output, output); logger.always(chalk.green(`\nOutput saved to: ${argv.output}`)); } } } catch (error) { spinner.fail(chalk.red("Network execution failed")); logger.error(error.message); process.exitCode = 1; } } } //# sourceMappingURL=agent.js.map