|
| 1 | +"""CLI entry point for Hive Coder agent.""" |
| 2 | + |
| 3 | +import asyncio |
| 4 | +import json |
| 5 | +import logging |
| 6 | +import sys |
| 7 | + |
| 8 | +import click |
| 9 | + |
| 10 | +from .agent import HiveCoderAgent, default_agent |
| 11 | + |
| 12 | + |
| 13 | +def setup_logging(verbose=False, debug=False): |
| 14 | + """Configure logging for execution visibility.""" |
| 15 | + if debug: |
| 16 | + level, fmt = logging.DEBUG, "%(asctime)s %(name)s: %(message)s" |
| 17 | + elif verbose: |
| 18 | + level, fmt = logging.INFO, "%(message)s" |
| 19 | + else: |
| 20 | + level, fmt = logging.WARNING, "%(levelname)s: %(message)s" |
| 21 | + logging.basicConfig(level=level, format=fmt, stream=sys.stderr) |
| 22 | + logging.getLogger("framework").setLevel(level) |
| 23 | + |
| 24 | + |
| 25 | +@click.group() |
| 26 | +@click.version_option(version="1.0.0") |
| 27 | +def cli(): |
| 28 | + """Hive Coder — Build Hive agent packages from natural language.""" |
| 29 | + pass |
| 30 | + |
| 31 | + |
| 32 | +@cli.command() |
| 33 | +@click.option("--request", "-r", type=str, required=True, help="What agent to build") |
| 34 | +@click.option("--mock", is_flag=True, help="Run in mock mode") |
| 35 | +@click.option("--quiet", "-q", is_flag=True, help="Only output result JSON") |
| 36 | +@click.option("--verbose", "-v", is_flag=True, help="Show execution details") |
| 37 | +@click.option("--debug", is_flag=True, help="Show debug logging") |
| 38 | +def run(request, mock, quiet, verbose, debug): |
| 39 | + """Execute agent building from a request.""" |
| 40 | + if not quiet: |
| 41 | + setup_logging(verbose=verbose, debug=debug) |
| 42 | + |
| 43 | + context = {"user_request": request} |
| 44 | + |
| 45 | + result = asyncio.run(default_agent.run(context, mock_mode=mock)) |
| 46 | + |
| 47 | + output_data = { |
| 48 | + "success": result.success, |
| 49 | + "steps_executed": result.steps_executed, |
| 50 | + "output": result.output, |
| 51 | + } |
| 52 | + if result.error: |
| 53 | + output_data["error"] = result.error |
| 54 | + |
| 55 | + click.echo(json.dumps(output_data, indent=2, default=str)) |
| 56 | + sys.exit(0 if result.success else 1) |
| 57 | + |
| 58 | + |
| 59 | +@cli.command() |
| 60 | +@click.option("--mock", is_flag=True, help="Run in mock mode") |
| 61 | +@click.option("--verbose", "-v", is_flag=True, help="Show execution details") |
| 62 | +@click.option("--debug", is_flag=True, help="Show debug logging") |
| 63 | +def tui(mock, verbose, debug): |
| 64 | + """Launch the TUI dashboard for interactive agent building.""" |
| 65 | + setup_logging(verbose=verbose, debug=debug) |
| 66 | + |
| 67 | + try: |
| 68 | + from framework.tui.app import AdenTUI |
| 69 | + except ImportError: |
| 70 | + click.echo("TUI requires the 'textual' package. Install with: pip install textual") |
| 71 | + sys.exit(1) |
| 72 | + |
| 73 | + from pathlib import Path |
| 74 | + |
| 75 | + from framework.llm import LiteLLMProvider |
| 76 | + from framework.runner.tool_registry import ToolRegistry |
| 77 | + from framework.runtime.agent_runtime import create_agent_runtime |
| 78 | + from framework.runtime.execution_stream import EntryPointSpec |
| 79 | + |
| 80 | + async def run_with_tui(): |
| 81 | + agent = HiveCoderAgent() |
| 82 | + |
| 83 | + agent._tool_registry = ToolRegistry() |
| 84 | + |
| 85 | + storage_path = Path.home() / ".hive" / "agents" / "hive_coder" |
| 86 | + storage_path.mkdir(parents=True, exist_ok=True) |
| 87 | + |
| 88 | + mcp_config_path = Path(__file__).parent / "mcp_servers.json" |
| 89 | + if mcp_config_path.exists(): |
| 90 | + agent._tool_registry.load_mcp_config(mcp_config_path) |
| 91 | + |
| 92 | + llm = None |
| 93 | + if not mock: |
| 94 | + llm = LiteLLMProvider( |
| 95 | + model=agent.config.model, |
| 96 | + api_key=agent.config.api_key, |
| 97 | + api_base=agent.config.api_base, |
| 98 | + ) |
| 99 | + |
| 100 | + tools = list(agent._tool_registry.get_tools().values()) |
| 101 | + tool_executor = agent._tool_registry.get_executor() |
| 102 | + graph = agent._build_graph() |
| 103 | + |
| 104 | + runtime = create_agent_runtime( |
| 105 | + graph=graph, |
| 106 | + goal=agent.goal, |
| 107 | + storage_path=storage_path, |
| 108 | + entry_points=[ |
| 109 | + EntryPointSpec( |
| 110 | + id="start", |
| 111 | + name="Build Agent", |
| 112 | + entry_node="coder", |
| 113 | + trigger_type="manual", |
| 114 | + isolation_level="isolated", |
| 115 | + ), |
| 116 | + ], |
| 117 | + llm=llm, |
| 118 | + tools=tools, |
| 119 | + tool_executor=tool_executor, |
| 120 | + ) |
| 121 | + |
| 122 | + await runtime.start() |
| 123 | + |
| 124 | + try: |
| 125 | + app = AdenTUI(runtime) |
| 126 | + await app.run_async() |
| 127 | + finally: |
| 128 | + await runtime.stop() |
| 129 | + |
| 130 | + asyncio.run(run_with_tui()) |
| 131 | + |
| 132 | + |
| 133 | +@cli.command() |
| 134 | +@click.option("--json", "output_json", is_flag=True) |
| 135 | +def info(output_json): |
| 136 | + """Show agent information.""" |
| 137 | + info_data = default_agent.info() |
| 138 | + if output_json: |
| 139 | + click.echo(json.dumps(info_data, indent=2)) |
| 140 | + else: |
| 141 | + click.echo(f"Agent: {info_data['name']}") |
| 142 | + click.echo(f"Version: {info_data['version']}") |
| 143 | + click.echo(f"Description: {info_data['description']}") |
| 144 | + click.echo(f"\nNodes: {', '.join(info_data['nodes'])}") |
| 145 | + click.echo(f"Client-facing: {', '.join(info_data['client_facing_nodes'])}") |
| 146 | + click.echo(f"Entry: {info_data['entry_node']}") |
| 147 | + click.echo(f"Terminal: {', '.join(info_data['terminal_nodes']) or '(forever-alive)'}") |
| 148 | + |
| 149 | + |
| 150 | +@cli.command() |
| 151 | +def validate(): |
| 152 | + """Validate agent structure.""" |
| 153 | + validation = default_agent.validate() |
| 154 | + if validation["valid"]: |
| 155 | + click.echo("Agent is valid") |
| 156 | + if validation["warnings"]: |
| 157 | + for warning in validation["warnings"]: |
| 158 | + click.echo(f" WARNING: {warning}") |
| 159 | + else: |
| 160 | + click.echo("Agent has errors:") |
| 161 | + for error in validation["errors"]: |
| 162 | + click.echo(f" ERROR: {error}") |
| 163 | + sys.exit(0 if validation["valid"] else 1) |
| 164 | + |
| 165 | + |
| 166 | +@cli.command() |
| 167 | +@click.option("--verbose", "-v", is_flag=True) |
| 168 | +def shell(verbose): |
| 169 | + """Interactive agent building session (CLI, no TUI).""" |
| 170 | + asyncio.run(_interactive_shell(verbose)) |
| 171 | + |
| 172 | + |
| 173 | +async def _interactive_shell(verbose=False): |
| 174 | + """Async interactive shell.""" |
| 175 | + setup_logging(verbose=verbose) |
| 176 | + |
| 177 | + click.echo("=== Hive Coder ===") |
| 178 | + click.echo("Describe the agent you want to build (or 'quit' to exit):\n") |
| 179 | + |
| 180 | + agent = HiveCoderAgent() |
| 181 | + await agent.start() |
| 182 | + |
| 183 | + try: |
| 184 | + while True: |
| 185 | + try: |
| 186 | + request = await asyncio.get_event_loop().run_in_executor(None, input, "Build> ") |
| 187 | + if request.lower() in ["quit", "exit", "q"]: |
| 188 | + click.echo("Goodbye!") |
| 189 | + break |
| 190 | + |
| 191 | + if not request.strip(): |
| 192 | + continue |
| 193 | + |
| 194 | + click.echo("\nBuilding agent...\n") |
| 195 | + |
| 196 | + result = await agent.trigger_and_wait("default", {"user_request": request}) |
| 197 | + |
| 198 | + if result is None: |
| 199 | + click.echo("\n[Execution timed out]\n") |
| 200 | + continue |
| 201 | + |
| 202 | + if result.success: |
| 203 | + output = result.output or {} |
| 204 | + agent_name = output.get("agent_name", "unknown") |
| 205 | + validation = output.get("validation_result", "unknown") |
| 206 | + click.echo(f"\nAgent '{agent_name}' built. Validation: {validation}\n") |
| 207 | + else: |
| 208 | + click.echo(f"\nBuild failed: {result.error}\n") |
| 209 | + |
| 210 | + except KeyboardInterrupt: |
| 211 | + click.echo("\nGoodbye!") |
| 212 | + break |
| 213 | + except Exception as e: |
| 214 | + click.echo(f"Error: {e}", err=True) |
| 215 | + import traceback |
| 216 | + |
| 217 | + traceback.print_exc() |
| 218 | + finally: |
| 219 | + await agent.stop() |
| 220 | + |
| 221 | + |
| 222 | +if __name__ == "__main__": |
| 223 | + cli() |
0 commit comments