fix: run black formatter on pkg/hanzo

This commit is contained in:
2025-12-26 04:37:43 -08:00
parent 80e1e43d66
commit e8eb9e2281
29 changed files with 682 additions and 216 deletions
+14 -4
View File
@@ -125,7 +125,9 @@ class BaseAgent(ABC, Generic[TContext, TResult]):
# Log start
if context and hasattr(context, "log"):
await context.log(f"Starting {self.name} with model {self.config.model}")
await context.log(
f"Starting {self.name} with model {self.config.model}"
)
# Execute with retries
result = await self._execute_with_retries(prompt, context, env, **kwargs)
@@ -143,7 +145,11 @@ class BaseAgent(ABC, Generic[TContext, TResult]):
except Exception as e:
self._end_time = datetime.now()
duration = (self._end_time - self._start_time).total_seconds() if self._start_time else None
duration = (
(self._end_time - self._start_time).total_seconds()
if self._start_time
else None
)
logger.error(f"Agent {self.name} failed: {e}")
@@ -225,7 +231,9 @@ class BaseAgent(ABC, Generic[TContext, TResult]):
if attempt < self.config.max_retries - 1:
await asyncio.sleep(2**attempt)
raise Exception(f"All {self.config.max_retries} attempts failed. Last error: {last_error}")
raise Exception(
f"All {self.config.max_retries} attempts failed. Last error: {last_error}"
)
@abstractmethod
async def _execute_impl(
@@ -318,7 +326,9 @@ class CLIAgent(BaseAgent[TContext, str]):
)
except asyncio.TimeoutError:
process.kill()
raise asyncio.TimeoutError(f"Command timed out after {self.config.timeout} seconds")
raise asyncio.TimeoutError(
f"Command timed out after {self.config.timeout} seconds"
)
# Check for errors
if process.returncode != 0:
+25 -7
View File
@@ -55,7 +55,11 @@ class BatchTask:
@property
def success(self) -> bool:
"""Check if task succeeded."""
return self.status == "completed" and self.result and getattr(self.result, "success", True)
return (
self.status == "completed"
and self.result
and getattr(self.result, "success", True)
)
def get_error(self) -> Optional[str]:
"""Get error message from direct error or result."""
@@ -157,7 +161,9 @@ class BatchConfig:
if not config.consensus_mode and not config.critic_mode:
agent_match = re.search(r"agent:(\w+)", command)
if agent_match:
config.agent_model = agent_match.group(1) # Will be resolved in __post_init__
config.agent_model = agent_match.group(
1
) # Will be resolved in __post_init__
# Parse worktree option
worktree_match = re.search(r"worktree:(true|false)", command)
@@ -214,7 +220,9 @@ class BatchOrchestrator:
self._task_counter += 1
return f"task_{self._task_counter:04d}"
async def _setup_worktree(self, task_id: str, config: BatchConfig) -> Optional[Path]:
async def _setup_worktree(
self, task_id: str, config: BatchConfig
) -> Optional[Path]:
"""Setup git worktree for parallel editing.
Args:
@@ -528,7 +536,9 @@ class BatchOrchestrator:
# If consensus reached, combine insights
if consensus_result["consensus_reached"] and successful_responses:
combined = "\n\n".join([f"[{r['model']}]: {r['response']}" for r in successful_responses])
combined = "\n\n".join(
[f"[{r['model']}]: {r['response']}" for r in successful_responses]
)
consensus_result["combined_response"] = combined
return consensus_result
@@ -617,7 +627,9 @@ Provide your critical analysis:"""
return {
"critic_chain": reviews,
"final_review": (reviews[-1]["review"] if reviews and "review" in reviews[-1] else None),
"final_review": (
reviews[-1]["review"] if reviews and "review" in reviews[-1] else None
),
"models_used": models,
"chain_length": len(reviews),
}
@@ -694,7 +706,11 @@ Provide your critical analysis:"""
if "review" in review:
console.print(
Panel(
(review["review"][:500] + "..." if len(review["review"]) > 500 else review["review"]),
(
review["review"][:500] + "..."
if len(review["review"]) > 500
else review["review"]
),
title=f"[cyan]Critic {review['iteration']}: {review['model']}[/cyan]",
)
)
@@ -769,7 +785,9 @@ Provide your critical analysis:"""
del self.active_tasks[task.id]
# Generate summary
total_duration = sum(t.duration() or 0 for t in self.completed_tasks + self.failed_tasks)
total_duration = sum(
t.duration() or 0 for t in self.completed_tasks + self.failed_tasks
)
summary = {
"total_tasks": len(tasks),
+42 -14
View File
@@ -115,9 +115,15 @@ def serve(ctx, name: str, port: int):
@cli.command()
@click.option("--name", "-n", help="Node name (auto-generated if not provided)")
@click.option("--port", "-p", default=52415, help="Node port (default: 52415 for hanzo/net)")
@click.option("--network", default="local", help="Network to join (mainnet/testnet/local)")
@click.option("--models", "-m", multiple=True, help="Models to serve (e.g., llama-3.2-3b)")
@click.option(
"--port", "-p", default=52415, help="Node port (default: 52415 for hanzo/net)"
)
@click.option(
"--network", default="local", help="Network to join (mainnet/testnet/local)"
)
@click.option(
"--models", "-m", multiple=True, help="Models to serve (e.g., llama-3.2-3b)"
)
@click.option("--max-jobs", type=int, default=10, help="Max concurrent jobs")
@click.pass_context
def net(ctx, name: str, port: int, network: str, models: tuple, max_jobs: int):
@@ -131,9 +137,15 @@ def net(ctx, name: str, port: int, network: str, models: tuple, max_jobs: int):
@cli.command()
@click.option("--name", "-n", help="Node name (auto-generated if not provided)")
@click.option("--port", "-p", default=52415, help="Node port (default: 52415 for hanzo/net)")
@click.option("--network", default="local", help="Network to join (mainnet/testnet/local)")
@click.option("--models", "-m", multiple=True, help="Models to serve (e.g., llama-3.2-3b)")
@click.option(
"--port", "-p", default=52415, help="Node port (default: 52415 for hanzo/net)"
)
@click.option(
"--network", default="local", help="Network to join (mainnet/testnet/local)"
)
@click.option(
"--models", "-m", multiple=True, help="Models to serve (e.g., llama-3.2-3b)"
)
@click.option("--max-jobs", type=int, default=10, help="Max concurrent jobs")
@click.pass_context
def node(ctx, name: str, port: int, network: str, models: tuple, max_jobs: int):
@@ -168,9 +180,15 @@ def node(ctx, name: str, port: int, network: str, models: tuple, max_jobs: int):
@click.option("--repl", is_flag=True, help="Start REPL interface (default)")
@click.option("--instances", type=int, default=2, help="Number of worker agents")
@click.option("--mcp-tools", is_flag=True, default=True, help="Enable all MCP tools")
@click.option("--network-mode", is_flag=True, default=True, help="Network agents together")
@click.option("--guardrails", is_flag=True, default=True, help="Enable code quality guardrails")
@click.option("--use-network/--no-network", default=True, help="Use hanzo-network if available")
@click.option(
"--network-mode", is_flag=True, default=True, help="Network agents together"
)
@click.option(
"--guardrails", is_flag=True, default=True, help="Enable code quality guardrails"
)
@click.option(
"--use-network/--no-network", default=True, help="Use hanzo-network if available"
)
@click.option(
"--use-hanzo-net",
is_flag=True,
@@ -248,7 +266,9 @@ def dev(
console.print(f" Router Endpoint: {orch_config.router.endpoint}")
if orch_config.codex:
console.print(f" Codex Model: {orch_config.codex.model}")
console.print(f" Cost Optimization: {'Enabled' if orch_config.enable_cost_optimization else 'Disabled'}")
console.print(
f" Cost Optimization: {'Enabled' if orch_config.enable_cost_optimization else 'Disabled'}"
)
console.print()
asyncio.run(
@@ -331,7 +351,9 @@ async def start_compute_node(
console.print(f"\n[green]✓[/green] Node initialized")
console.print(f" Port: {port}")
console.print(f" Models: {', '.join(models) if models else 'auto-detect'}")
console.print(
f" Models: {', '.join(models) if models else 'auto-detect'}"
)
console.print("\n[bold green]Hanzo Net is running![/bold green]")
console.print("WebUI: http://localhost:52415")
console.print("API: http://localhost:52415/v1/chat/completions")
@@ -353,7 +375,9 @@ async def start_compute_node(
stop_task = asyncio.create_task(stop_event.wait())
# Wait for either net to complete or stop signal
done, pending = await asyncio.wait([net_task, stop_task], return_when=asyncio.FIRST_COMPLETED)
done, pending = await asyncio.wait(
[net_task, stop_task], return_when=asyncio.FIRST_COMPLETED
)
# Cancel pending tasks
for task in pending:
@@ -389,11 +413,15 @@ async def start_compute_node(
env["NET_MODELS"] = ",".join(models)
if name:
env["NET_NODE_NAME"] = name
env["PYTHONPATH"] = os.path.join(net_path, "src") + ":" + env.get("PYTHONPATH", "")
env["PYTHONPATH"] = (
os.path.join(net_path, "src") + ":" + env.get("PYTHONPATH", "")
)
console.print(f"\n[green]✓[/green] Starting net node")
console.print(f" Port: {port}")
console.print(f" Models: {', '.join(models) if models else 'auto-detect'}")
console.print(
f" Models: {', '.join(models) if models else 'auto-detect'}"
)
console.print("\n[bold green]Hanzo Net is running![/bold green]")
console.print("WebUI: http://localhost:52415")
console.print("API: http://localhost:52415/v1/chat/completions")
+5 -1
View File
@@ -88,7 +88,11 @@ async def run(ctx, agents: tuple, task: str, parallel: bool, timeout: Optional[i
network = create_network(agents=agent_list)
# Run task
result = await asyncio.wait_for(network.run(task), timeout=timeout) if timeout else await network.run(task)
result = (
await asyncio.wait_for(network.run(task), timeout=timeout)
if timeout
else await network.run(task)
)
console.print("[green]Task completed![/green]")
console.print(result)
+6 -2
View File
@@ -127,7 +127,9 @@ def login(ctx, email: str, password: str, api_key: str, sso: bool):
hanzo_auth = HanzoAuth()
# Email auth not implemented yet
console.print("[yellow]Email authentication not yet implemented[/yellow]")
console.print(
"[yellow]Email authentication not yet implemented[/yellow]"
)
console.print("[dim]Saving credentials locally for development[/dim]")
auth = {
@@ -247,7 +249,9 @@ def whoami():
content = "\n".join(lines) if lines else "[dim]No user information available[/dim]"
console.print(Panel(content, title="[bold cyan]User Information[/bold cyan]", box=box.ROUNDED))
console.print(
Panel(content, title="[bold cyan]User Information[/bold cyan]", box=box.ROUNDED)
)
@auth_group.command(name="set-key")
+9 -3
View File
@@ -18,7 +18,9 @@ from ..utils.output import console
@click.option("--local/--cloud", default=True, help="Use local or cloud model")
@click.option("--once", is_flag=True, help="Single question mode")
@click.option("--system", "-s", help="System prompt")
@click.option("--repl", is_flag=True, help="Start full REPL interface (like Claude Code)")
@click.option(
"--repl", is_flag=True, help="Start full REPL interface (like Claude Code)"
)
@click.option("--ipython", is_flag=True, help="Use IPython REPL interface")
@click.option("--tui", is_flag=True, help="Use beautiful TUI interface")
@click.argument("prompt", nargs=-1)
@@ -78,7 +80,9 @@ def chat_command(
asyncio.run(interactive_chat(ctx, model, local, system))
async def ask_once(ctx, prompt: str, model: str, local: bool, system: Optional[str] = None):
async def ask_once(
ctx, prompt: str, model: str, local: bool, system: Optional[str] = None
):
"""Ask a single question."""
messages = []
if system:
@@ -168,7 +172,9 @@ async def interactive_chat(ctx, model: str, local: bool, system: Optional[str]):
from prompt_toolkit import PromptSession
from prompt_toolkit.history import FileHistory
console.print(f"[cyan]Chat session started[/cyan] (model: {model}, mode: {'local' if local else 'cloud'})")
console.print(
f"[cyan]Chat session started[/cyan] (model: {model}, mode: {'local' if local else 'cloud'})"
)
console.print("Type 'exit' or Ctrl+D to quit\n")
session = PromptSession(history=FileHistory(".hanzo_chat_history"))
+3 -1
View File
@@ -115,7 +115,9 @@ def set(ctx, key: str, value: str, is_global: bool, is_local: bool):
# Save config
save_config(config_path, config)
console.print(f"[green]✓[/green] Set {key} = {parsed_value} in {config_name} config")
console.print(
f"[green]✓[/green] Set {key} = {parsed_value} in {config_name} config"
)
@config_group.command()
+6 -2
View File
@@ -96,7 +96,9 @@ async def tools(ctx, category: str):
# Display tools
for cat, tools in sorted(categories.items()):
table = Table(title=f"{cat.title()} Tools" if len(categories) > 1 else "MCP Tools")
table = Table(
title=f"{cat.title()} Tools" if len(categories) > 1 else "MCP Tools"
)
table.add_column("Name", style="cyan", no_wrap=True)
table.add_column("Description")
@@ -239,7 +241,9 @@ def install(ctx, path: str):
existing["mcpServers"]["hanzo-mcp"] = config["mcpServers"]["hanzo-mcp"]
config = existing
except Exception as e:
console.print(f"[yellow]Warning: Could not read existing config: {e}[/yellow]")
console.print(
f"[yellow]Warning: Could not read existing config: {e}[/yellow]"
)
# Write config
config_path.parent.mkdir(parents=True, exist_ok=True)
+21 -7
View File
@@ -50,7 +50,9 @@ async def start(
if not click.confirm("Continue without wallet?"):
return
miner = HanzoMiner(name=name, wallet=wallet, device=device, network=network, min_stake=min_stake)
miner = HanzoMiner(
name=name, wallet=wallet, device=device, network=network, min_stake=min_stake
)
with Progress(
SpinnerColumn(),
@@ -92,7 +94,9 @@ async def start(
if event["type"] == "log":
console.print(event["message"], end="")
elif event["type"] == "job":
console.print(f"[green]Job completed:[/green] {event['job_id']} (+{event['tokens']} tokens)")
console.print(
f"[green]Job completed:[/green] {event['job_id']} (+{event['tokens']} tokens)"
)
elif event["type"] == "error":
console.print(f"[red]Error:[/red] {event['message']}")
except KeyboardInterrupt:
@@ -105,7 +109,9 @@ async def start(
console.print("\n[cyan]Session Summary:[/cyan]")
console.print(f" Jobs completed: {final_stats.get('jobs_completed', 0)}")
console.print(f" Tokens earned: {final_stats.get('tokens_earned', 0)}")
console.print(f" Average job time: {final_stats.get('avg_job_time', 'N/A')}")
console.print(
f" Average job time: {final_stats.get('avg_job_time', 'N/A')}"
)
console.print("[green]✓[/green] Miner stopped")
@@ -201,11 +207,15 @@ async def status(ctx, name: str, detailed: bool):
console.print(f" Network: {stats.get('network', 'unknown')}")
console.print(f" Wallet: {stats.get('wallet', 'Not set')}")
console.print(f" Models: {', '.join(stats.get('models', []))}")
console.print(f" Memory: {stats.get('memory_used', 0)} / {stats.get('memory_total', 0)} MB")
console.print(
f" Memory: {stats.get('memory_used', 0)} / {stats.get('memory_total', 0)} MB"
)
console.print(f" CPU: {stats.get('cpu_percent', 0)}%")
if gpu := stats.get("gpu"):
console.print(f" GPU: {gpu['name']} ({gpu['memory_used']} / {gpu['memory_total']} MB)")
console.print(
f" GPU: {gpu['name']} ({gpu['memory_used']} / {gpu['memory_total']} MB)"
)
console.print(f"\n[cyan]Performance:[/cyan]")
console.print(f" Average job time: {stats.get('avg_job_time', 'N/A')}")
@@ -282,7 +292,9 @@ async def earnings(ctx, wallet: str, network: str):
if history := data.get("recent_jobs"):
console.print("\n[cyan]Recent Jobs:[/cyan]")
for job in history[:5]:
console.print(f" • {job['timestamp']}: +{job['tokens']} tokens ({job['model']})")
console.print(
f" • {job['timestamp']}: +{job['tokens']} tokens ({job['model']})"
)
@miner_group.command()
@@ -311,7 +323,9 @@ async def withdraw(ctx, amount: float, wallet: str, to: str, network: str):
with console.status("Processing withdrawal..."):
try:
result = await withdraw_earnings(wallet=wallet, amount=amount, destination=to, network=network)
result = await withdraw_earnings(
wallet=wallet, amount=amount, destination=to, network=network
)
console.print(f"[green]✓[/green] Withdrawal successful")
console.print(f" Transaction: {result['tx_hash']}")
+25 -8
View File
@@ -26,7 +26,9 @@ def network_group():
@click.option("--consensus", is_flag=True, help="Require consensus")
@click.option("--timeout", "-t", type=int, default=300, help="Timeout in seconds")
@click.pass_context
async def dispatch(ctx, prompt: str, agents: int, model: str, mode: str, consensus: bool, timeout: int):
async def dispatch(
ctx, prompt: str, agents: int, model: str, mode: str, consensus: bool, timeout: int
):
"""Dispatch work to agent network."""
try:
from hanzo_network import NetworkDispatcher
@@ -389,9 +391,13 @@ def topology(ctx, json: bool):
)
if gpu_available:
console.print("[green]✓[/green] GPU/Accelerator detected and available for AI workloads")
console.print(
"[green]✓[/green] GPU/Accelerator detected and available for AI workloads"
)
if "APPLE M" in caps.chip.upper():
console.print(" [dim]Apple Silicon Neural Engine available for on-device AI[/dim]")
console.print(
" [dim]Apple Silicon Neural Engine available for on-device AI[/dim]"
)
else:
console.print("[yellow]⚠[/yellow] No GPU detected - using CPU only")
console.print(" To enable GPU support:")
@@ -541,7 +547,9 @@ def topology_add(
try:
import qrcode
except ImportError:
console.print("\n[yellow]QR code generation requires qrcode library[/yellow]")
console.print(
"\n[yellow]QR code generation requires qrcode library[/yellow]"
)
console.print("Install with: pip install qrcode[pil]")
return
@@ -692,7 +700,10 @@ def models(ctx, endpoint: str):
models_list = [
model
for model in all_models
if not any(keyword in model.get("id", "").lower() for keyword in embedding_keywords)
if not any(
keyword in model.get("id", "").lower()
for keyword in embedding_keywords
)
]
if models_list:
@@ -730,12 +741,16 @@ def models(ctx, endpoint: str):
table.add_row(str(i), model_id, tier, provider)
console.print(table)
console.print(f"\n[dim]Total LLMs: {len(models_list)} (embedding models filtered out)[/dim]")
console.print(
f"\n[dim]Total LLMs: {len(models_list)} (embedding models filtered out)[/dim]"
)
else:
console.print("[yellow]No models available on gateway[/yellow]")
else:
# Show static list of known models
console.print("[yellow]Could not fetch live models, showing known models:[/yellow]\n")
console.print(
"[yellow]Could not fetch live models, showing known models:[/yellow]\n"
)
_show_static_models()
except Exception as e:
console.print(f"[yellow]Error fetching models: {e}[/yellow]\n")
@@ -743,7 +758,9 @@ def models(ctx, endpoint: str):
console.print("\n[dim]To use a model:[/dim]")
console.print(" [cyan]hanzo dev --model llama3-8b-instruct[/cyan]")
console.print("\n[dim cyan]Free tier:[/dim cyan] [green]Most models available without login![/green]")
console.print(
"\n[dim cyan]Free tier:[/dim cyan] [green]Most models available without login![/green]"
)
console.print(
"[dim cyan]Premium models:[/dim cyan] [yellow]gpt-4, claude-3-opus, o1-preview[/yellow] - [cyan]hanzo auth login[/cyan]"
)
+18 -6
View File
@@ -31,7 +31,9 @@ async def start(ctx, name: str, port: int, models: tuple, device: str):
await start_node(ctx, name, port, list(models) if models else None, device)
async def start_node(ctx, name: str, port: int, models: Optional[List[str]] = None, device: str = "auto"):
async def start_node(
ctx, name: str, port: int, models: Optional[List[str]] = None, device: str = "auto"
):
"""Start a local node via hanzo-cluster."""
try:
from hanzo_cluster import HanzoCluster
@@ -138,7 +140,9 @@ async def status(ctx, name: str):
if workers := status.get("node_details", []):
console.print("\n[cyan]Workers:[/cyan]")
for worker in workers:
console.print(f" • {worker.get('name', 'unknown')} ({worker.get('state', 'unknown')})")
console.print(
f" • {worker.get('name', 'unknown')} ({worker.get('state', 'unknown')})"
)
if device := worker.get("device"):
console.print(f" Device: {device}")
else:
@@ -252,7 +256,9 @@ def worker_group():
default="auto",
help="Device to use",
)
@click.option("--port", "-p", type=int, help="Worker port (auto-assigned if not specified)")
@click.option(
"--port", "-p", type=int, help="Worker port (auto-assigned if not specified)"
)
@click.option("--blockchain", is_flag=True, help="Enable blockchain features")
@click.option("--network", is_flag=True, help="Enable network discovery")
@click.pass_context
@@ -425,7 +431,9 @@ async def worker_info(ctx, name: str):
if resources := info.get("resources"):
console.print("\n[cyan]Resources:[/cyan]")
console.print(f" CPU: {resources.get('cpu_percent', 'N/A')}%")
console.print(f" Memory: {resources.get('memory_used', 'N/A')} / {resources.get('memory_total', 'N/A')}")
console.print(
f" Memory: {resources.get('memory_used', 'N/A')} / {resources.get('memory_total', 'N/A')}"
)
if gpu := resources.get("gpu"):
console.print(
f" GPU: {gpu.get('name', 'N/A')} ({gpu.get('memory_used', 'N/A')} / {gpu.get('memory_total', 'N/A')})"
@@ -438,8 +446,12 @@ async def worker_info(ctx, name: str):
if network := info.get("network"):
console.print("\n[cyan]Network:[/cyan]")
console.print(f" Blockchain: {'enabled' if network.get('blockchain') else 'disabled'}")
console.print(f" Discovery: {'enabled' if network.get('discovery') else 'disabled'}")
console.print(
f" Blockchain: {'enabled' if network.get('blockchain') else 'disabled'}"
)
console.print(
f" Discovery: {'enabled' if network.get('discovery') else 'disabled'}"
)
if peers := network.get("peers"):
console.print(f" Peers: {len(peers)}")
+9 -3
View File
@@ -105,14 +105,18 @@ def info(ctx):
if not providers:
console.print("\n[yellow]No LLM providers configured[/yellow]")
console.print("Set one of: OPENAI_API_KEY, ANTHROPIC_API_KEY, HANZO_API_KEY")
console.print(
"Set one of: OPENAI_API_KEY, ANTHROPIC_API_KEY, HANZO_API_KEY"
)
except ImportError:
console.print("[red]Error:[/red] hanzo-repl not installed")
@repl_group.command()
@click.option("--interface", type=click.Choice(["all", "ipython", "tui", "voice"]), default="all")
@click.option(
"--interface", type=click.Choice(["all", "ipython", "tui", "voice"]), default="all"
)
@click.pass_context
def install_extras(ctx, interface: str):
"""Install optional REPL components."""
@@ -165,7 +169,9 @@ def exec(ctx, command: tuple, model: str):
except ImportError as e:
console.print(f"[red]Import Error:[/red] {e}")
console.print("[yellow]Note:[/yellow] hanzo-repl may not be installed correctly")
console.print(
"[yellow]Note:[/yellow] hanzo-repl may not be installed correctly"
)
except Exception as e:
console.print(f"[red]Error: {e}[/red]")
+20 -6
View File
@@ -40,7 +40,9 @@ def start_router(ctx, port: int, config: Optional[str], detach: bool):
if not router_dir:
console.print("[red]Error:[/red] Hanzo router not found")
console.print("\nPlease clone the router:")
console.print(" git clone https://github.com/hanzoai/router.git ~/work/hanzo/router")
console.print(
" git clone https://github.com/hanzoai/router.git ~/work/hanzo/router"
)
return
console.print(f"[green]✓[/green] Found router at {router_dir}")
@@ -89,7 +91,9 @@ def start_router(ctx, port: int, config: Optional[str], detach: bool):
stderr=subprocess.DEVNULL,
start_new_session=True,
)
console.print(f"[green]✓[/green] Router started in background (PID: {process.pid})")
console.print(
f"[green]✓[/green] Router started in background (PID: {process.pid})"
)
console.print(f"Check status: curl http://localhost:{port}/health")
else:
# Run in foreground
@@ -112,8 +116,14 @@ def stop_router(port: int):
for proc in psutil.process_iter(["pid", "cmdline"]):
try:
cmdline = proc.info["cmdline"]
if cmdline and "proxy_server" in " ".join(cmdline) and str(port) in " ".join(cmdline):
console.print(f"[yellow]Stopping router (PID: {proc.info['pid']})[/yellow]")
if (
cmdline
and "proxy_server" in " ".join(cmdline)
and str(port) in " ".join(cmdline)
):
console.print(
f"[yellow]Stopping router (PID: {proc.info['pid']})[/yellow]"
)
proc.send_signal(signal.SIGTERM)
proc.wait(timeout=5)
found = True
@@ -138,7 +148,9 @@ def router_status(port: int):
# Try to get models
try:
models_response = httpx.get(f"http://localhost:{port}/models", timeout=2.0)
models_response = httpx.get(
f"http://localhost:{port}/models", timeout=2.0
)
if models_response.status_code == 200:
data = models_response.json()
if "data" in data:
@@ -146,7 +158,9 @@ def router_status(port: int):
except Exception:
pass
else:
console.print(f"[yellow]Router responding but unhealthy (status: {response.status_code})[/yellow]")
console.print(
f"[yellow]Router responding but unhealthy (status: {response.status_code})[/yellow]"
)
except httpx.ConnectError:
console.print(f"[red]Router not running on port {port}[/red]")
console.print("\nStart with: hanzo router start")
+6 -2
View File
@@ -30,7 +30,9 @@ async def list_tools(ctx, category: str, installed: bool):
with console.status("Loading tools..."):
try:
tools = await registry.list_tools(category=category, installed_only=installed)
tools = await registry.list_tools(
category=category, installed_only=installed
)
except Exception as e:
console.print(f"[red]Failed to load tools: {e}[/red]")
return
@@ -86,7 +88,9 @@ async def install(ctx, tool_name: str, version: str):
try:
result = await registry.install_tool(name=tool_name, version=version)
console.print(f"[green]✓[/green] Installed {tool_name} v{result['version']}")
console.print(
f"[green]✓[/green] Installed {tool_name} v{result['version']}"
)
if deps := result.get("dependencies_installed"):
console.print(f" Dependencies: {', '.join(deps)}")
+215 -69
View File
@@ -221,7 +221,9 @@ class HanzoDevOrchestrator:
grpo_dir = self.workspace_dir / "grpo"
grpo_dir.mkdir(exist_ok=True)
self.experience_manager = ExperienceManager(str(grpo_dir / "experience_library.json"))
self.experience_manager = ExperienceManager(
str(grpo_dir / "experience_library.json")
)
# Initialize with DeepSeek if API key available
deepseek_key = os.getenv("DEEPSEEK_API_KEY")
@@ -252,7 +254,9 @@ class HanzoDevOrchestrator:
for path in possible_paths:
expanded = Path(path).expanduser()
if expanded.exists() or (path == "claude" and os.system(f"which {path} >/dev/null 2>&1") == 0):
if expanded.exists() or (
path == "claude" and os.system(f"which {path} >/dev/null 2>&1") == 0
):
return str(expanded) if expanded.exists() else path
raise RuntimeError("Claude Code not found. Please specify path.")
@@ -339,7 +343,9 @@ class HanzoDevOrchestrator:
else:
return "Execute standard workflow with monitoring"
def _assess_risks(self, problem: str, alternatives: List[str], context: Dict[str, Any]) -> List[str]:
def _assess_risks(
self, problem: str, alternatives: List[str], context: Dict[str, Any]
) -> List[str]:
"""Assess risks of different approaches."""
risks = []
@@ -363,7 +369,9 @@ class HanzoDevOrchestrator:
) -> str:
"""Synthesize a decision from analysis."""
if len(risks) > 2:
return "Proceed cautiously with incremental approach and rollback capability"
return (
"Proceed cautiously with incremental approach and rollback capability"
)
elif alternatives:
return f"Execute primary approach: {alternatives[0]}"
else:
@@ -408,7 +416,9 @@ class HanzoDevOrchestrator:
if resume:
checkpoint_file = self._get_latest_checkpoint()
if checkpoint_file:
console.print(f"[green]Resuming from checkpoint: {checkpoint_file.name}[/green]")
console.print(
f"[green]Resuming from checkpoint: {checkpoint_file.name}[/green]"
)
# Prepare command
cmd = [self.claude_code_path]
@@ -526,8 +536,12 @@ class HanzoDevOrchestrator:
"timestamp": datetime.now().isoformat(),
"agent_state": self.agent_state.value,
"runtime_health": asdict(self.runtime_health),
"current_context": (asdict(self.current_context) if self.current_context else None),
"thinking_history": [asdict(t) for t in self.thinking_history[-10:]], # Last 10
"current_context": (
asdict(self.current_context) if self.current_context else None
),
"thinking_history": [
asdict(t) for t in self.thinking_history[-10:]
], # Last 10
}
with open(checkpoint_file, "w") as f:
@@ -545,7 +559,9 @@ class HanzoDevOrchestrator:
self.agent_state = AgentState(data["agent_state"])
# Restore other state as needed
console.print(f"[green]✓ Restored from checkpoint: {checkpoint_file.name}[/green]")
console.print(
f"[green]✓ Restored from checkpoint: {checkpoint_file.name}[/green]"
)
return True
except Exception as e:
console.print(f"[red]Failed to restore checkpoint: {e}[/red]")
@@ -561,7 +577,9 @@ class HanzoDevOrchestrator:
healthy = await self.health_check()
if not healthy:
console.print(f"[yellow]Health check failed. State: {self.runtime_health.state.value}[/yellow]")
console.print(
f"[yellow]Health check failed. State: {self.runtime_health.state.value}[/yellow]"
)
# Use System 2 thinking to decide what to do
thinking_result = await self.think(
@@ -570,7 +588,9 @@ class HanzoDevOrchestrator:
)
console.print(f"[cyan]Decision: {thinking_result.decision}[/cyan]")
console.print(f"[cyan]Confidence: {thinking_result.confidence:.2f}[/cyan]")
console.print(
f"[cyan]Confidence: {thinking_result.confidence:.2f}[/cyan]"
)
# Execute decision
if thinking_result.confidence > 0.6:
@@ -724,10 +744,14 @@ class HanzoDevOrchestrator:
experiences_after = len(self.experience_manager.experiences)
# Save updated library
self.experience_manager.save(str(self.workspace_dir / "grpo" / "experience_library.json"))
self.experience_manager.save(
str(self.workspace_dir / "grpo" / "experience_library.json")
)
learned_count = experiences_after - experiences_before
console.print(f"[green]✓ Learned {learned_count} new experiences (total: {experiences_after})[/green]")
console.print(
f"[green]✓ Learned {learned_count} new experiences (total: {experiences_after})[/green]"
)
return learned_count
@@ -830,7 +854,9 @@ class HanzoDevREPL:
handler = FallbackHandler()
if not handler.fallback_order:
console.print("[yellow]⚠️ No API keys detected[/yellow]")
console.print("[dim]Set OPENAI_API_KEY or ANTHROPIC_API_KEY to enable AI[/dim]")
console.print(
"[dim]Set OPENAI_API_KEY or ANTHROPIC_API_KEY to enable AI[/dim]"
)
console.print()
else:
primary = handler.fallback_order[0][1]
@@ -876,9 +902,13 @@ class HanzoDevREPL:
# Handle memory/context commands
from .memory_manager import handle_memory_command
handled = handle_memory_command(user_input, self.memory_manager, console)
handled = handle_memory_command(
user_input, self.memory_manager, console
)
if not handled:
console.print("[yellow]Unknown memory command. Use #memory help[/yellow]")
console.print(
"[yellow]Unknown memory command. Use #memory help[/yellow]"
)
else:
# Natural chat - send directly to AI agents
@@ -921,13 +951,17 @@ class HanzoDevREPL:
table.add_row("Agent State", self.orchestrator.agent_state.value)
table.add_row("Runtime State", self.orchestrator.runtime_health.state.value)
table.add_row("Last Response", str(self.orchestrator.runtime_health.last_response))
table.add_row(
"Last Response", str(self.orchestrator.runtime_health.last_response)
)
table.add_row(
"Response Time",
f"{self.orchestrator.runtime_health.response_time_ms:.2f}ms",
)
table.add_row("Error Count", str(self.orchestrator.runtime_health.error_count))
table.add_row("Restart Count", str(self.orchestrator.runtime_health.restart_count))
table.add_row(
"Restart Count", str(self.orchestrator.runtime_health.restart_count)
)
console.print(table)
@@ -974,7 +1008,9 @@ class HanzoDevREPL:
"""Restore from checkpoint."""
if not args:
# Show available checkpoints
checkpoints = list(self.orchestrator.checkpoint_dir.glob("checkpoint_*.json"))
checkpoints = list(
self.orchestrator.checkpoint_dir.glob("checkpoint_*.json")
)
if checkpoints:
console.print("[cyan]Available checkpoints:[/cyan]")
for cp in checkpoints:
@@ -1051,7 +1087,10 @@ Examples:
enhanced_message = message
# Try smart fallback if no specific model configured
if not hasattr(self.orchestrator, "orchestrator_model") or self.orchestrator.orchestrator_model == "auto":
if (
not hasattr(self.orchestrator, "orchestrator_model")
or self.orchestrator.orchestrator_model == "auto"
):
# Use streaming if available
from .streaming import stream_with_fallback
@@ -1063,7 +1102,9 @@ Examples:
# Response already displayed by streaming handler
return
else:
console.print("[red]No AI options available. Please configure API keys or install tools.[/red]")
console.print(
"[red]No AI options available. Please configure API keys or install tools.[/red]"
)
return
# For codex and other CLI tools, go straight to direct API chat
@@ -1304,14 +1345,26 @@ Examples:
# No API keys available
console.print("[red]No AI API keys configured![/red]")
console.print("[yellow]Try these options that don't need your API key:[/yellow]")
console.print(
"[yellow]Try these options that don't need your API key:[/yellow]"
)
console.print("\n[bold]CLI Tools (use existing tools):[/bold]")
console.print(" • hanzo dev --orchestrator codex # OpenAI CLI (if installed)")
console.print(" • hanzo dev --orchestrator claude # Claude Desktop (if installed)")
console.print(" • hanzo dev --orchestrator gemini # Gemini CLI (if installed)")
console.print(" • hanzo dev --orchestrator hanzo-ide # Hanzo IDE from ~/work/hanzo/ide")
console.print(
" • hanzo dev --orchestrator codex # OpenAI CLI (if installed)"
)
console.print(
" • hanzo dev --orchestrator claude # Claude Desktop (if installed)"
)
console.print(
" • hanzo dev --orchestrator gemini # Gemini CLI (if installed)"
)
console.print(
" • hanzo dev --orchestrator hanzo-ide # Hanzo IDE from ~/work/hanzo/ide"
)
console.print("\n[bold]Free APIs (rate limited):[/bold]")
console.print(" • hanzo dev --orchestrator codestral # Free Mistral Codestral")
console.print(
" • hanzo dev --orchestrator codestral # Free Mistral Codestral"
)
console.print(" • hanzo dev --orchestrator starcoder # Free StarCoder")
console.print("\n[bold]Local Models (unlimited):[/bold]")
console.print(" • hanzo dev --orchestrator local:llama3.2 # Via Ollama")
@@ -1354,10 +1407,16 @@ Examples:
if response.status_code == 200:
data = response.json()
if data.get("choices"):
console.print(f"[cyan]Codestral:[/cyan] {data['choices'][0]['message']['content']}")
console.print(
f"[cyan]Codestral:[/cyan] {data['choices'][0]['message']['content']}"
)
else:
console.print("[yellow]Free tier limit reached. Try local models instead:[/yellow]")
console.print(" • Install Ollama: curl -fsSL https://ollama.com/install.sh | sh")
console.print(
"[yellow]Free tier limit reached. Try local models instead:[/yellow]"
)
console.print(
" • Install Ollama: curl -fsSL https://ollama.com/install.sh | sh"
)
console.print(" • Run: ollama pull codellama")
console.print(" • Use: hanzo dev --orchestrator local:codellama")
@@ -1394,9 +1453,13 @@ Examples:
if response.status_code == 200:
data = response.json()
if isinstance(data, list) and data:
console.print(f"[cyan]StarCoder:[/cyan] {data[0].get('generated_text', '')}")
console.print(
f"[cyan]StarCoder:[/cyan] {data[0].get('generated_text', '')}"
)
else:
console.print("[yellow]API limit reached. Install local models:[/yellow]")
console.print(
"[yellow]API limit reached. Install local models:[/yellow]"
)
console.print(" • brew install ollama")
console.print(" • ollama pull starcoder2")
console.print(" • hanzo dev --orchestrator local:starcoder2")
@@ -1433,7 +1496,9 @@ Examples:
message,
]
process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
process = subprocess.Popen(
cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True
)
stdout, stderr = process.communicate(timeout=30)
@@ -1465,7 +1530,11 @@ Examples:
claude_path = None
for path in claude_paths:
if os.path.exists(path) or subprocess.run(["which", path], capture_output=True).returncode == 0:
if (
os.path.exists(path)
or subprocess.run(["which", path], capture_output=True).returncode
== 0
):
claude_path = path
break
@@ -1494,7 +1563,9 @@ Examples:
"""
subprocess.run(["osascript", "-e", script])
console.print("[cyan]Sent to Claude Desktop. Check the app for response.[/cyan]")
console.print(
"[cyan]Sent to Claude Desktop. Check the app for response.[/cyan]"
)
else:
# Try direct CLI invocation
process = subprocess.Popen(
@@ -1533,7 +1604,9 @@ Examples:
# Use gemini CLI
cmd = ["gemini", "chat", message]
process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
process = subprocess.Popen(
cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True
)
stdout, stderr = process.communicate(timeout=30)
@@ -1561,7 +1634,9 @@ Examples:
console.print("[red]Hanzo Dev IDE not found![/red]")
console.print("[yellow]Expected location: ~/work/hanzo/ide[/yellow]")
console.print("To set up:")
console.print(" • git clone https://github.com/hanzoai/ide ~/work/hanzo/ide")
console.print(
" • git clone https://github.com/hanzoai/ide ~/work/hanzo/ide"
)
console.print(" • cd ~/work/hanzo/ide && npm install")
return
@@ -1597,7 +1672,9 @@ Examples:
cmd = [cli_path, "chat", message]
cwd = None
process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, cwd=cwd)
process = subprocess.Popen(
cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, cwd=cwd
)
stdout, stderr = process.communicate(timeout=30)
@@ -1644,7 +1721,9 @@ Examples:
if response.status_code == 200:
data = response.json()
if data.get("message"):
console.print(f"[cyan]{model_name}:[/cyan] {data['message']['content']}")
console.print(
f"[cyan]{model_name}:[/cyan] {data['message']['content']}"
)
return
except Exception:
@@ -1675,7 +1754,9 @@ Examples:
if response.status_code == 200:
data = response.json()
if data.get("choices"):
console.print(f"[cyan]{model_name}:[/cyan] {data['choices'][0]['message']['content']}")
console.print(
f"[cyan]{model_name}:[/cyan] {data['choices'][0]['message']['content']}"
)
return
except Exception:
@@ -1753,7 +1834,9 @@ async def run_dev_orchestrator(**kwargs):
# Check if we should use network mode
# For now, disable network mode since hanzo-network isn't available
if False and use_network and NETWORK_AVAILABLE:
console_obj.print(f"[cyan]Mode: Network Orchestration with hanzo-network[/cyan]")
console_obj.print(
f"[cyan]Mode: Network Orchestration with hanzo-network[/cyan]"
)
console_obj.print(f"Orchestrator: {orchestrator_model}")
console_obj.print(f"Workers: {instances} agents")
console_obj.print(f"Critics: {max(1, instances // 2)} agents")
@@ -1859,19 +1942,25 @@ class NetworkOrchestrator(HanzoDevOrchestrator):
# Check if we can use hanzo-network
if not NETWORK_AVAILABLE:
self.console.print("[yellow]Warning: hanzo-network not available, falling back to basic mode[/yellow]")
self.console.print(
"[yellow]Warning: hanzo-network not available, falling back to basic mode[/yellow]"
)
async def initialize(self):
"""Initialize the agent network with orchestrator and workers."""
if not NETWORK_AVAILABLE:
self.console.print("[red]Cannot initialize network mode without hanzo-network[/red]")
self.console.print(
"[red]Cannot initialize network mode without hanzo-network[/red]"
)
return False
# Start hanzo net if requested for local orchestration
if self.use_hanzo_net or self.orchestrator_model.startswith("local:"):
await self._start_hanzo_net()
self.console.print(f"[cyan]Initializing agent network with {self.orchestrator_model} orchestrator...[/cyan]")
self.console.print(
f"[cyan]Initializing agent network with {self.orchestrator_model} orchestrator...[/cyan]"
)
# Create orchestrator agent (GPT-5, local, or other model)
self.orchestrator_agent = await self._create_orchestrator_agent()
@@ -1888,7 +1977,9 @@ class NetworkOrchestrator(HanzoDevOrchestrator):
for i in range(num_local_workers):
local_worker = await self._create_local_worker_agent(i)
self.worker_agents.append(local_worker)
self.console.print(f"[green]Added {num_local_workers} local workers for cost optimization[/green]")
self.console.print(
f"[green]Added {num_local_workers} local workers for cost optimization[/green]"
)
# Create critic agents for System 2 thinking
if self.enable_guardrails:
@@ -1911,15 +2002,21 @@ class NetworkOrchestrator(HanzoDevOrchestrator):
self.agent_network = create_network(
agents=all_agents,
router=router,
default_agent=(self.orchestrator_agent.name if self.orchestrator_agent else None),
default_agent=(
self.orchestrator_agent.name if self.orchestrator_agent else None
),
)
self.console.print(f"[green]✓ Agent network initialized with {len(all_agents)} agents[/green]")
self.console.print(
f"[green]✓ Agent network initialized with {len(all_agents)} agents[/green]"
)
return True
async def _start_hanzo_net(self):
"""Start hanzo net for local AI orchestration."""
self.console.print("[cyan]Starting hanzo/net for local AI orchestration...[/cyan]")
self.console.print(
"[cyan]Starting hanzo/net for local AI orchestration...[/cyan]"
)
# Check if hanzo net is already running
import socket
@@ -1929,7 +2026,9 @@ class NetworkOrchestrator(HanzoDevOrchestrator):
sock.close()
if result == 0:
self.console.print(f"[yellow]hanzo/net already running on port {self.hanzo_net_port}[/yellow]")
self.console.print(
f"[yellow]hanzo/net already running on port {self.hanzo_net_port}[/yellow]"
)
return
# Start hanzo net
@@ -1990,7 +2089,9 @@ class NetworkOrchestrator(HanzoDevOrchestrator):
tools=[],
)
self.console.print(f"[green]✓ Created local {model_name} orchestrator via hanzo/net[/green]")
self.console.print(
f"[green]✓ Created local {model_name} orchestrator via hanzo/net[/green]"
)
return orchestrator
# Parse model string to get provider and model
@@ -2046,7 +2147,9 @@ class NetworkOrchestrator(HanzoDevOrchestrator):
tools=[], # Orchestrator tools will be added
)
self.console.print(f"[green]✓ Created {self.orchestrator_model} orchestrator[/green]")
self.console.print(
f"[green]✓ Created {self.orchestrator_model} orchestrator[/green]"
)
return orchestrator
def _get_orchestrator_system_prompt(self) -> str:
@@ -2179,7 +2282,10 @@ class NetworkOrchestrator(HanzoDevOrchestrator):
"count",
"find",
]
if any(keyword in prompt_lower for keyword in simple_keywords) and self.local_workers:
if (
any(keyword in prompt_lower for keyword in simple_keywords)
and self.local_workers
):
return self.local_workers[0].name
# Complex implementation → API workers (Claude)
@@ -2191,7 +2297,10 @@ class NetworkOrchestrator(HanzoDevOrchestrator):
"design",
"architect",
]
if any(keyword in prompt_lower for keyword in complex_keywords) and self.api_workers:
if (
any(keyword in prompt_lower for keyword in complex_keywords)
and self.api_workers
):
return self.api_workers[0].name
# Review tasks → Critics
@@ -2202,7 +2311,10 @@ class NetworkOrchestrator(HanzoDevOrchestrator):
"improve",
"validate code",
]
if any(keyword in prompt_lower for keyword in review_keywords) and self.critics:
if (
any(keyword in prompt_lower for keyword in review_keywords)
and self.critics
):
return self.critics[0].name
# Strategic decisions → Orchestrator
@@ -2223,12 +2335,20 @@ class NetworkOrchestrator(HanzoDevOrchestrator):
return self.local_workers[0].name
# Fall back to API workers for complex tasks
return self.api_workers[0].name if self.api_workers else self.orchestrator.name
return (
self.api_workers[0].name
if self.api_workers
else self.orchestrator.name
)
# Create the cost-optimized router
router = CostOptimizedRouter(self.orchestrator_agent, self.worker_agents, self.critic_agents)
router = CostOptimizedRouter(
self.orchestrator_agent, self.worker_agents, self.critic_agents
)
self.console.print("[green]✓ Created cost-optimized router (local models preferred)[/green]")
self.console.print(
"[green]✓ Created cost-optimized router (local models preferred)[/green]"
)
return router
async def _create_intelligent_router(self) -> Router:
@@ -2263,7 +2383,9 @@ class NetworkOrchestrator(HanzoDevOrchestrator):
return router
async def execute_with_network(self, task: str, context: Optional[Dict] = None) -> Dict:
async def execute_with_network(
self, task: str, context: Optional[Dict] = None
) -> Dict:
"""Execute a task using the agent network.
Args:
@@ -2401,7 +2523,9 @@ class MultiClaudeOrchestrator(HanzoDevOrchestrator):
config = await self._create_instance_config(i, role)
self.instance_configs.append(config)
self.console.print(f" [{i + 1}/{self.num_instances}] {role} instance configured")
self.console.print(
f" [{i + 1}/{self.num_instances}] {role} instance configured"
)
# If networking enabled, configure MCP connections between instances
if self.enable_networking:
@@ -2463,7 +2587,9 @@ class MultiClaudeOrchestrator(HanzoDevOrchestrator):
async def _setup_mcp_networking(self):
"""Set up MCP networking between Claude instances."""
self.console.print("[cyan]Setting up MCP networking between instances...[/cyan]")
self.console.print(
"[cyan]Setting up MCP networking between instances...[/cyan]"
)
# Each instance gets MCP servers for all other instances
for i, config in enumerate(self.instance_configs):
@@ -2606,10 +2732,14 @@ class MultiClaudeOrchestrator(HanzoDevOrchestrator):
# Validate improvement didn't degrade quality
if await self._validate_improvement(result, improved):
self.console.print("[green]✓ Solution improved with System 2 feedback[/green]")
self.console.print(
"[green]✓ Solution improved with System 2 feedback[/green]"
)
return improved
else:
self.console.print("[yellow]⚠ Keeping original solution (improvement validation failed)[/yellow]")
self.console.print(
"[yellow]⚠ Keeping original solution (improvement validation failed)[/yellow]"
)
return result
@@ -2686,7 +2816,9 @@ class MultiClaudeOrchestrator(HanzoDevOrchestrator):
try:
import subprocess
result = subprocess.run(["gemini", "chat", prompt], capture_output=True, text=True, timeout=30)
result = subprocess.run(
["gemini", "chat", prompt], capture_output=True, text=True, timeout=30
)
if result.returncode == 0 and result.stdout:
return {"output": result.stdout.strip(), "success": True}
except Exception as e:
@@ -2750,7 +2882,9 @@ class MultiClaudeOrchestrator(HanzoDevOrchestrator):
logger.error(f"OpenAI API error: {e}")
# Try Anthropic
anthropic_key = os.environ.get("ANTHROPIC_API_KEY") or os.getenv("ANTHROPIC_API_KEY")
anthropic_key = os.environ.get("ANTHROPIC_API_KEY") or os.getenv(
"ANTHROPIC_API_KEY"
)
if anthropic_key:
try:
from anthropic import AsyncAnthropic
@@ -2769,7 +2903,9 @@ class MultiClaudeOrchestrator(HanzoDevOrchestrator):
# Try fallback handler as last resort
from .fallback_handler import smart_chat
response = await smart_chat(prompt, console=None) # No console to avoid duplicate messages
response = await smart_chat(
prompt, console=None
) # No console to avoid duplicate messages
if response:
return {"output": response, "success": True}
@@ -2812,15 +2948,25 @@ async def main():
"""Main entry point for hanzo-dev."""
import argparse
parser = argparse.ArgumentParser(description="Hanzo Dev - System 2 Meta-AI Orchestrator")
parser.add_argument("--workspace", default="~/.hanzo/dev", help="Workspace directory")
parser = argparse.ArgumentParser(
description="Hanzo Dev - System 2 Meta-AI Orchestrator"
)
parser.add_argument(
"--workspace", default="~/.hanzo/dev", help="Workspace directory"
)
parser.add_argument("--claude-path", help="Path to Claude Code executable")
parser.add_argument("--monitor", action="store_true", help="Start in monitor mode")
parser.add_argument("--repl", action="store_true", help="Start REPL interface")
parser.add_argument("--instances", type=int, default=2, help="Number of Claude instances")
parser.add_argument(
"--instances", type=int, default=2, help="Number of Claude instances"
)
parser.add_argument("--no-mcp", action="store_true", help="Disable MCP tools")
parser.add_argument("--no-network", action="store_true", help="Disable instance networking")
parser.add_argument("--no-guardrails", action="store_true", help="Disable guardrails")
parser.add_argument(
"--no-network", action="store_true", help="Disable instance networking"
)
parser.add_argument(
"--no-guardrails", action="store_true", help="Disable guardrails"
)
args = parser.parse_args()
+21 -7
View File
@@ -23,7 +23,9 @@ class FallbackHandler:
"deepseek_api": bool(os.getenv("DEEPSEEK_API_KEY")), # Added DeepSeek
"openai_api": bool(os.getenv("OPENAI_API_KEY")),
"anthropic_api": bool(os.getenv("ANTHROPIC_API_KEY")),
"google_api": bool(os.getenv("GOOGLE_API_KEY") or os.getenv("GEMINI_API_KEY")),
"google_api": bool(
os.getenv("GOOGLE_API_KEY") or os.getenv("GEMINI_API_KEY")
),
"openai_cli": shutil.which("openai") is not None,
"claude_cli": shutil.which("claude") is not None,
"gemini_cli": shutil.which("gemini") is not None,
@@ -101,7 +103,9 @@ class FallbackHandler:
suggestions = []
if not self.available_options["deepseek_api"]:
suggestions.append("• Set DEEPSEEK_API_KEY for cost-effective DeepSeek access ($0.14/M tokens)")
suggestions.append(
"• Set DEEPSEEK_API_KEY for cost-effective DeepSeek access ($0.14/M tokens)"
)
if not self.available_options["openai_api"]:
suggestions.append("• Set OPENAI_API_KEY for GPT-4/GPT-5 access")
@@ -110,22 +114,30 @@ class FallbackHandler:
suggestions.append("• Set ANTHROPIC_API_KEY for Claude access")
if not self.available_options["ollama"]:
suggestions.append("• Install Ollama: curl -fsSL https://ollama.com/install.sh | sh")
suggestions.append(
"• Install Ollama: curl -fsSL https://ollama.com/install.sh | sh"
)
suggestions.append(" Then run: ollama pull llama3.2")
if not self.available_options["openai_cli"]:
suggestions.append("• Install OpenAI CLI: pip install openai-cli")
if not self.available_options["claude_cli"]:
suggestions.append("• Install Claude Desktop from https://claude.ai/download")
suggestions.append(
"• Install Claude Desktop from https://claude.ai/download"
)
return "\n".join(suggestions) if suggestions else "All AI options are available!"
return (
"\n".join(suggestions) if suggestions else "All AI options are available!"
)
def print_status(self, console):
"""Print the current status of available AI options."""
from rich.table import Table
table = Table(title="Available AI Options", show_header=True, header_style="bold magenta")
table = Table(
title="Available AI Options", show_header=True, header_style="bold magenta"
)
table.add_column("Option", style="cyan", width=20)
table.add_column("Status", width=10)
table.add_column("Model", width=20)
@@ -152,7 +164,9 @@ class FallbackHandler:
console.print(table)
if self.fallback_order:
console.print(f"\n[green]Primary option: {self.fallback_order[0][1]}[/green]")
console.print(
f"\n[green]Primary option: {self.fallback_order[0][1]}[/green]"
)
if len(self.fallback_order) > 1:
fallbacks = ", ".join([opt[1] for opt in self.fallback_order[1:]])
console.print(f"[yellow]Fallback options: {fallbacks}[/yellow]")
+112 -34
View File
@@ -98,7 +98,9 @@ class EnhancedHanzoREPL:
self.failed_tools = set() # Track tools that have failed this session
# Initialize background task manager
self.task_manager = BackgroundTaskManager(console) if BackgroundTaskManager else None
self.task_manager = (
BackgroundTaskManager(console) if BackgroundTaskManager else None
)
# Initialize todo manager
self.todo_manager = TodoManager(console) if TodoManager else None
@@ -112,7 +114,9 @@ class EnhancedHanzoREPL:
if default_tool:
self.current_model = f"tool:{default_tool.name}"
self.current_tool = default_tool
self.console.print(f"[green]✓ Detected {default_tool.display_name} as default AI assistant[/green]")
self.console.print(
f"[green]✓ Detected {default_tool.display_name} as default AI assistant[/green]"
)
else:
# Fallback to regular models
self.current_model = self.config.get("default_model", "gpt-3.5-turbo")
@@ -351,7 +355,9 @@ class EnhancedHanzoREPL:
# Router status
try:
response = httpx.get("http://localhost:4000/health", timeout=1)
router_status = "✅ Running" if response.status_code == 200 else "⚠️ Unhealthy"
router_status = (
"✅ Running" if response.status_code == 200 else "⚠️ Unhealthy"
)
router_details = "Port 4000"
except Exception:
router_status = "❌ Offline"
@@ -404,7 +410,11 @@ class EnhancedHanzoREPL:
args = f"tool:{tool.name}"
else:
# It's a model selection
model_idx = num - len(self.detected_tools) - 1 if self.detected_tools else num - 1
model_idx = (
num - len(self.detected_tools) - 1
if self.detected_tools
else num - 1
)
models_list = list(self.MODELS.keys())
if 0 <= model_idx < len(models_list):
args = models_list[model_idx]
@@ -417,7 +427,11 @@ class EnhancedHanzoREPL:
return
# Check if it's a tool
if args.startswith("tool:") or args in [t.name for t in self.detected_tools] if self.detected_tools else False:
if (
args.startswith("tool:") or args in [t.name for t in self.detected_tools]
if self.detected_tools
else False
):
# Handle tool selection
tool_name = args.replace("tool:", "") if args.startswith("tool:") else args
@@ -451,7 +465,9 @@ class EnhancedHanzoREPL:
# Show hint if it's a gateway model (not in our predefined list)
if args not in self.MODELS and not args.startswith("local:"):
self.console.print("[dim]Using gateway model - all models free! Use /models to see full list.[/dim]")
self.console.print(
"[dim]Using gateway model - all models free! Use /models to see full list.[/dim]"
)
async def list_tools(self, args: str = ""):
"""List available AI tools."""
@@ -464,10 +480,14 @@ class EnhancedHanzoREPL:
"""List available models from gateway.hanzo.ai."""
# Show tools first if available
if self.detected_tools:
self.console.print("[bold cyan]AI Coding Assistants (Detected):[/bold cyan]")
self.console.print(
"[bold cyan]AI Coding Assistants (Detected):[/bold cyan]"
)
for i, tool in enumerate(self.detected_tools, 1):
marker = "→" if self.current_model == f"tool:{tool.name}" else " "
self.console.print(f" {marker} {i}. {tool.display_name} ({tool.provider})")
self.console.print(
f" {marker} {i}. {tool.display_name} ({tool.provider})"
)
self.console.print()
# Try to fetch live models from gateway
@@ -487,14 +507,20 @@ class EnhancedHanzoREPL:
models_list = [
model
for model in all_models
if not any(keyword in model.get("id", "").lower() for keyword in embedding_keywords)
if not any(
keyword in model.get("id", "").lower()
for keyword in embedding_keywords
)
]
except Exception:
pass
# Fallback to static models if gateway fetch fails
if not models_list:
models_list = [{"id": model_id, "name": model_name} for model_id, model_name in self.MODELS.items()]
models_list = [
{"id": model_id, "name": model_name}
for model_id, model_name in self.MODELS.items()
]
# Create table
table = Table(title="Gateway LLMs (Chat Models)", box=box.ROUNDED)
@@ -533,16 +559,22 @@ class EnhancedHanzoREPL:
# Highlight current model
if model_id == self.current_model:
table.add_row(str(i), f"[bold green]→ {model_id}[/bold green]", tier, provider)
table.add_row(
str(i), f"[bold green]→ {model_id}[/bold green]", tier, provider
)
else:
table.add_row(str(i), model_id, tier, provider)
self.console.print(table)
self.console.print("\n[dim cyan]Free tier:[/dim cyan] [green]Most models available without login![/green]")
self.console.print(
"\n[dim cyan]Free tier:[/dim cyan] [green]Most models available without login![/green]"
)
self.console.print(
"[dim cyan]Premium models:[/dim cyan] [yellow]gpt-4, claude-3-opus, o1-preview[/yellow] - [cyan]hanzo auth login[/cyan]"
)
self.console.print("\n[dim]Use /model <name> or /model <number> to switch[/dim]")
self.console.print(
"\n[dim]Use /model <name> or /model <number> to switch[/dim]"
)
async def login(self, args: str = ""):
"""Login to Hanzo."""
@@ -665,7 +697,9 @@ class EnhancedHanzoREPL:
self.console.print(f"[dim]Executing: {full_cmd}[/dim]")
try:
result = subprocess.run(full_cmd, shell=True, capture_output=True, text=True)
result = subprocess.run(
full_cmd, shell=True, capture_output=True, text=True
)
if result.stdout:
self.console.print(result.stdout)
@@ -686,13 +720,19 @@ class EnhancedHanzoREPL:
if tool.name not in self.failed_tools:
self.current_tool = tool
self.current_model = f"tool:{tool.name}"
self.console.print(f"[yellow]Switched to {tool.display_name}[/yellow]")
self.console.print(
f"[yellow]Switched to {tool.display_name}[/yellow]"
)
break
# Try the current tool
if self.current_tool.name not in self.failed_tools:
self.console.print(f"[dim]Using {self.current_tool.display_name}...[/dim]")
success, output = self.tool_detector.execute_with_tool(self.current_tool, message)
self.console.print(
f"[dim]Using {self.current_tool.display_name}...[/dim]"
)
success, output = self.tool_detector.execute_with_tool(
self.current_tool, message
)
if success:
self.console.print(output)
@@ -707,8 +747,12 @@ class EnhancedHanzoREPL:
if self.tool_detector and self.tool_detector.detected_tools:
for fallback_tool in self.tool_detector.detected_tools:
if fallback_tool.name not in self.failed_tools:
self.console.print(f"[yellow]Trying {fallback_tool.display_name}...[/yellow]")
success, output = self.tool_detector.execute_with_tool(fallback_tool, message)
self.console.print(
f"[yellow]Trying {fallback_tool.display_name}...[/yellow]"
)
success, output = self.tool_detector.execute_with_tool(
fallback_tool, message
)
if success:
self.console.print(output)
# Automatically switch to this working tool
@@ -724,15 +768,21 @@ class EnhancedHanzoREPL:
else:
# Mark as failed
self.failed_tools.add(fallback_tool.name)
self.console.print(f"[red]{fallback_tool.display_name} also failed[/red]")
self.console.print(
f"[red]{fallback_tool.display_name} also failed[/red]"
)
if not found_working:
# Final fallback to cloud model
self.console.print(f"[yellow]Falling back to cloud model...[/yellow]")
await self.execute_command("ask", f"--cloud --model gpt-3.5-turbo {message}")
await self.execute_command(
"ask", f"--cloud --model gpt-3.5-turbo {message}"
)
else:
# Use regular model through hanzo ask
await self.execute_command("ask", f"--cloud --model {self.current_model} {message}")
await self.execute_command(
"ask", f"--cloud --model {self.current_model} {message}"
)
async def quick_model_select(self, args: str = ""):
"""Quick model selector with arrow keys."""
@@ -741,7 +791,11 @@ class EnhancedHanzoREPL:
return
# Prepare tools and models
tools = [(f"tool:{t.name}", t.display_name) for t in self.detected_tools] if self.detected_tools else []
tools = (
[(f"tool:{t.name}", t.display_name) for t in self.detected_tools]
if self.detected_tools
else []
)
models = list(self.MODELS.items())
selector = QuickModelSelector(models, tools, self.current_model)
@@ -772,7 +826,9 @@ class EnhancedHanzoREPL:
else:
# Show tasks and prompt for selection
self.task_manager.list_tasks()
self.console.print("\n[cyan]Enter task ID to kill (or 'all' for all tasks):[/cyan]")
self.console.print(
"\n[cyan]Enter task ID to kill (or 'all' for all tasks):[/cyan]"
)
try:
task_id = await self.session.prompt_async("> ")
if task_id:
@@ -807,7 +863,9 @@ class EnhancedHanzoREPL:
# Quick add
try:
todo = self.todo_manager.quick_add(rest)
self.console.print(f"[green]✅ Added todo: {todo.title} (ID: {todo.id})[/green]")
self.console.print(
f"[green]✅ Added todo: {todo.title} (ID: {todo.id})[/green]"
)
except ValueError as e:
self.console.print(f"[red]Error: {e}[/red]")
else:
@@ -833,7 +891,9 @@ class EnhancedHanzoREPL:
elif key in ["tag", "t"]:
tag = value
todos = self.todo_manager.list_todos(status=status, priority=priority, tag=tag)
todos = self.todo_manager.list_todos(
status=status, priority=priority, tag=tag
)
title = "Filtered Todos" if (status or priority or tag) else "All Todos"
self.todo_manager.display_todos(todos, title)
@@ -842,7 +902,9 @@ class EnhancedHanzoREPL:
if rest:
todo = self.todo_manager.update_todo(rest, status="done")
if todo:
self.console.print(f"[green]✅ Marked as done: {todo.title}[/green]")
self.console.print(
f"[green]✅ Marked as done: {todo.title}[/green]"
)
else:
self.console.print(f"[red]Todo not found: {rest}[/red]")
else:
@@ -898,7 +960,9 @@ class EnhancedHanzoREPL:
elif subcommand in ["clear", "reset"]:
# Clear all todos (with confirmation)
try:
confirm = await self.session.prompt_async("Are you sure you want to delete ALL todos? (yes/no): ")
confirm = await self.session.prompt_async(
"Are you sure you want to delete ALL todos? (yes/no): "
)
if confirm.lower() in ["yes", "y"]:
self.todo_manager.todos = []
self.todo_manager.save_todos()
@@ -916,9 +980,13 @@ class EnhancedHanzoREPL:
# Unknown subcommand, treat as quick add
try:
todo = self.todo_manager.quick_add(args)
self.console.print(f"[green]✅ Added todo: {todo.title} (ID: {todo.id})[/green]")
self.console.print(
f"[green]✅ Added todo: {todo.title} (ID: {todo.id})[/green]"
)
except ValueError:
self.console.print(f"[yellow]Unknown todo command: {subcommand}[/yellow]")
self.console.print(
f"[yellow]Unknown todo command: {subcommand}[/yellow]"
)
self.console.print("[dim]Use /todo help for available commands[/dim]")
async def add_todo_interactive(self):
@@ -934,13 +1002,21 @@ class EnhancedHanzoREPL:
description = await self.session.prompt_async("Description (optional): ")
# Get priority
priority = await self.session.prompt_async("Priority (low/medium/high/urgent) [medium]: ")
priority = await self.session.prompt_async(
"Priority (low/medium/high/urgent) [medium]: "
)
if not priority:
priority = "medium"
# Get tags
tags_input = await self.session.prompt_async("Tags (comma-separated, optional): ")
tags = [t.strip() for t in tags_input.split(",") if t.strip()] if tags_input else []
tags_input = await self.session.prompt_async(
"Tags (comma-separated, optional): "
)
tags = (
[t.strip() for t in tags_input.split(",") if t.strip()]
if tags_input
else []
)
# Get due date
due_date = await self.session.prompt_async("Due date (optional): ")
@@ -954,7 +1030,9 @@ class EnhancedHanzoREPL:
due_date=due_date if due_date else None,
)
self.console.print(f"[green]✅ Added todo: {todo.title} (ID: {todo.id})[/green]")
self.console.print(
f"[green]✅ Added todo: {todo.title} (ID: {todo.id})[/green]"
)
except (KeyboardInterrupt, EOFError):
self.console.print("[yellow]Cancelled[/yellow]")
@@ -14,7 +14,9 @@ from prompt_toolkit.layout.containers import HSplit, Window
class QuickModelSelector:
"""Quick model selector with arrow navigation."""
def __init__(self, models: List[Tuple[str, str]], tools: List[Tuple[str, str]], current: str):
def __init__(
self, models: List[Tuple[str, str]], tools: List[Tuple[str, str]], current: str
):
self.models = models
self.tools = tools
self.current = current
@@ -97,7 +99,9 @@ class QuickModelSelector:
layout = Layout(Window(FormattedTextControl(get_text), wrap_lines=False))
app = Application(layout=layout, key_bindings=kb, full_screen=False, mouse_support=True)
app = Application(
layout=layout, key_bindings=kb, full_screen=False, mouse_support=True
)
return await app.run_async()
@@ -134,7 +138,9 @@ class BackgroundTaskManager:
task = self.tasks[task_id]
if task["process"].poll() is None:
task["process"].terminate()
self.console.print(f"[yellow]Terminated {task_id}: {task['name']}[/yellow]")
self.console.print(
f"[yellow]Terminated {task_id}: {task['name']}[/yellow]"
)
else:
self.console.print(f"[dim]Task {task_id} already stopped[/dim]")
del self.tasks[task_id]
+3 -1
View File
@@ -196,7 +196,9 @@ hanzo> mcp run read_file --arg path=README.md
except subprocess.TimeoutExpired:
self.console.print("[red]Command timed out[/red]")
except FileNotFoundError:
self.console.print("[red]Command not found. Make sure 'hanzo' is installed.[/red]")
self.console.print(
"[red]Command not found. Make sure 'hanzo' is installed.[/red]"
)
except Exception as e:
self.console.print(f"[red]Command error: {e}[/red]")
@@ -293,7 +293,9 @@ class TodoManager:
TodoPriority.LOW: "green",
}.get(todo.priority, "white")
priority_display = f"[{priority_color}]{todo.priority.value.upper()}[/{priority_color}]"
priority_display = (
f"[{priority_color}]{todo.priority.value.upper()}[/{priority_color}]"
)
# Tags
tags_display = ", ".join(todo.tags) if todo.tags else "-"
@@ -387,7 +389,9 @@ class TodoManager:
if not title:
raise ValueError("Todo title cannot be empty")
return self.add_todo(title=title, priority=priority, tags=tags, due_date=due_date)
return self.add_todo(
title=title, priority=priority, tags=tags, due_date=due_date
)
def get_statistics(self) -> Dict[str, Any]:
"""Get todo statistics."""
+9 -3
View File
@@ -57,7 +57,9 @@ class MemoryManager:
try:
with open(self.memory_file, "r") as f:
data = json.load(f)
self.memories = [MemoryItem.from_dict(item) for item in data.get("memories", [])]
self.memories = [
MemoryItem.from_dict(item) for item in data.get("memories", [])
]
except Exception as e:
print(f"Error loading memories: {e}")
self.memories = []
@@ -178,7 +180,9 @@ class MemoryManager:
self.memories = []
self.save_memories()
def get_memories(self, type: str = None, tags: List[str] = None) -> List[MemoryItem]:
def get_memories(
self, type: str = None, tags: List[str] = None
) -> List[MemoryItem]:
"""Get memories filtered by type or tags."""
result = self.memories
@@ -331,7 +335,9 @@ def handle_memory_command(command: str, memory_manager: MemoryManager, console)
console.print("[yellow]No memories stored.[/yellow]")
return True
table = Table(title="Current Memories", show_header=True, header_style="bold magenta")
table = Table(
title="Current Memories", show_header=True, header_style="bold magenta"
)
table.add_column("ID", style="cyan", width=10)
table.add_column("Type", width=12)
table.add_column("Content", width=50)
+3 -1
View File
@@ -16,7 +16,9 @@ from typing import Any, Dict, List, Optional
from dataclasses import field, dataclass
def check_local_node(host: str = "localhost", port: int = 4000, timeout: float = 0.5) -> bool:
def check_local_node(
host: str = "localhost", port: int = 4000, timeout: float = 0.5
) -> bool:
"""Check if hanzo-node is running locally.
Args:
+19 -6
View File
@@ -275,10 +275,18 @@ class SmartRateLimiter:
# Default configs for known APIs
self.configs = {
"openai": RateLimitConfig(requests_per_minute=60, requests_per_hour=1000, burst_size=10),
"anthropic": RateLimitConfig(requests_per_minute=50, requests_per_hour=1000, burst_size=5),
"local": RateLimitConfig(requests_per_minute=100, requests_per_hour=10000, burst_size=20),
"free": RateLimitConfig(requests_per_minute=10, requests_per_hour=100, burst_size=2),
"openai": RateLimitConfig(
requests_per_minute=60, requests_per_hour=1000, burst_size=10
),
"anthropic": RateLimitConfig(
requests_per_minute=50, requests_per_hour=1000, burst_size=5
),
"local": RateLimitConfig(
requests_per_minute=100, requests_per_hour=10000, burst_size=20
),
"free": RateLimitConfig(
requests_per_minute=10, requests_per_hour=100, burst_size=2
),
}
def get_limiter(self, api_type: str) -> RateLimiter:
@@ -288,7 +296,9 @@ class SmartRateLimiter:
self.limiters[api_type] = RateLimiter(config)
return self.limiters[api_type]
async def execute_with_limit(self, api_type: str, func: Callable, *args, **kwargs) -> Any:
async def execute_with_limit(
self, api_type: str, func: Callable, *args, **kwargs
) -> Any:
"""Execute function with appropriate rate limiting."""
limiter = self.get_limiter(api_type)
recovery = ErrorRecovery(limiter)
@@ -297,7 +307,10 @@ class SmartRateLimiter:
def get_all_status(self) -> Dict[str, Dict[str, Any]]:
"""Get status of all limiters."""
return {api_type: limiter.get_status() for api_type, limiter in self.limiters.items()}
return {
api_type: limiter.get_status()
for api_type, limiter in self.limiters.items()
}
# Global instance for easy use
+21 -7
View File
@@ -25,7 +25,9 @@ class StreamingHandler:
async def stream_openai(self, client, messages: list, model: str = "gpt-4") -> str:
"""Stream response from OpenAI API."""
try:
stream = await client.chat.completions.create(model=model, messages=messages, stream=True, max_tokens=1000)
stream = await client.chat.completions.create(
model=model, messages=messages, stream=True, max_tokens=1000
)
self.current_response = ""
self.is_streaming = True
@@ -61,7 +63,9 @@ class StreamingHandler:
self.is_streaming = False
return None
async def stream_anthropic(self, client, messages: list, model: str = "claude-3-5-sonnet-20241022") -> str:
async def stream_anthropic(
self, client, messages: list, model: str = "claude-3-5-sonnet-20241022"
) -> str:
"""Stream response from Anthropic API."""
try:
self.current_response = ""
@@ -77,7 +81,9 @@ class StreamingHandler:
console=self.console,
refresh_per_second=10,
) as live:
async with client.messages.stream(model=model, messages=messages, max_tokens=1000) as stream:
async with client.messages.stream(
model=model, messages=messages, max_tokens=1000
) as stream:
async for text in stream.text_stream:
self.current_response += text
live.update(
@@ -218,11 +224,15 @@ class TypewriterEffect:
for line in lines:
for char in line:
current_code += char
syntax = Syntax(current_code, language, theme="monokai", line_numbers=True)
syntax = Syntax(
current_code, language, theme="monokai", line_numbers=True
)
live.update(syntax)
await asyncio.sleep(speed)
current_code += "\n"
syntax = Syntax(current_code, language, theme="monokai", line_numbers=True)
syntax = Syntax(
current_code, language, theme="monokai", line_numbers=True
)
live.update(syntax)
@@ -240,7 +250,9 @@ async def stream_with_fallback(message: str, console: Console = None) -> Optiona
from openai import AsyncOpenAI
client = AsyncOpenAI()
return await handler.stream_openai(client, [{"role": "user", "content": message}])
return await handler.stream_openai(
client, [{"role": "user", "content": message}]
)
except Exception as e:
if console:
console.print(f"[yellow]OpenAI streaming failed: {e}[/yellow]")
@@ -251,7 +263,9 @@ async def stream_with_fallback(message: str, console: Console = None) -> Optiona
from anthropic import AsyncAnthropic
client = AsyncAnthropic()
return await handler.stream_anthropic(client, [{"role": "user", "content": message}])
return await handler.stream_anthropic(
client, [{"role": "user", "content": message}]
)
except Exception as e:
if console:
console.print(f"[yellow]Anthropic streaming failed: {e}[/yellow]")
+23 -7
View File
@@ -217,7 +217,9 @@ class ToolDetector:
]: # 400/422 means endpoint exists but params wrong
tool.detected = True
tool.version = f"Running (Port {port})"
tool.api_endpoint = f"http://localhost:{port}/health" # Update port
tool.api_endpoint = (
f"http://localhost:{port}/health" # Update port
)
# Try to get model info
try:
@@ -314,7 +316,11 @@ class ToolDetector:
version = tool.version or "Unknown" if tool.detected else "-"
# Highlight the default tool
if tool.detected and tool == self.detected_tools[0] if self.detected_tools else False:
if (
tool.detected and tool == self.detected_tools[0]
if self.detected_tools
else False
):
table.add_row(
str(i),
f"[bold green]→ {tool.display_name}[/bold green]",
@@ -341,11 +347,15 @@ class ToolDetector:
# Special message for Hanzo Node
if default.name == "hanzod":
self.console.print("[cyan]🔒 Using local private AI - your data stays on your machine[/cyan]")
self.console.print(
"[cyan]🔒 Using local private AI - your data stays on your machine[/cyan]"
)
self.console.print("[dim]Manage models with: hanzo node models[/dim]")
else:
self.console.print("\n[yellow]No AI coding tools detected.[/yellow]")
self.console.print("[dim]Start Hanzo Node for local AI: hanzo node start[/dim]")
self.console.print(
"[dim]Start Hanzo Node for local AI: hanzo node start[/dim]"
)
self.console.print("[dim]Or install Claude Code, OpenAI CLI, etc.[/dim]")
def get_tool_command(self, tool: AITool, prompt: str) -> List[str]:
@@ -407,7 +417,9 @@ class ToolDetector:
)
if response.status_code == 200:
result = response.json()
return True, result.get("choices", [{}])[0].get("message", {}).get("content", "")
return True, result.get("choices", [{}])[0].get(
"message", {}
).get("content", "")
else:
return (
False,
@@ -431,7 +443,9 @@ class ToolDetector:
)
if response.status_code == 200:
result = response.json()
return True, result.get("choices", [{}])[0].get("message", {}).get("content", "")
return True, result.get("choices", [{}])[0].get(
"message", {}
).get("content", "")
except Exception as e:
return False, f"Router error: {e}"
@@ -460,6 +474,8 @@ class ToolDetector:
if success:
return True, output, tool
else:
self.console.print(f"[yellow]{tool.display_name} failed: {output}[/yellow]")
self.console.print(
f"[yellow]{tool.display_name} failed: {output}[/yellow]"
)
return False, "No available tools could handle the request", None
+6 -2
View File
@@ -33,7 +33,9 @@ class InlineStartup:
# Check last shown time
if self.last_shown_file.exists():
try:
last_shown = datetime.fromisoformat(self.last_shown_file.read_text().strip())
last_shown = datetime.fromisoformat(
self.last_shown_file.read_text().strip()
)
if datetime.now() - last_shown < self.show_interval:
return False
except Exception:
@@ -63,7 +65,9 @@ class InlineStartup:
message.append("Router management, improved docs", style="yellow dim")
# Show panel
console.print(Panel(message, box=box.MINIMAL, border_style="cyan", padding=(0, 1)))
console.print(
Panel(message, box=box.MINIMAL, border_style="cyan", padding=(0, 1))
)
self.mark_shown()
+9 -3
View File
@@ -232,7 +232,9 @@ class StartupUI:
# Check router status
try:
response = httpx.get("http://localhost:4000/health", timeout=1)
router_status = "🟢 Running" if response.status_code == 200 else "🔴 Offline"
router_status = (
"🟢 Running" if response.status_code == 200 else "🔴 Offline"
)
except Exception:
router_status = "⚫ Offline"
@@ -256,7 +258,9 @@ class StartupUI:
status.append("API: ", style="bold")
status.append(api_status, style="white")
return Panel(Align.center(status), box=box.ROUNDED, border_style="blue", padding=(0, 1))
return Panel(
Align.center(status), box=box.ROUNDED, border_style="blue", padding=(0, 1)
)
def _create_qr_panel(self) -> Panel:
"""Create compact QR code panel for device connection."""
@@ -299,7 +303,9 @@ class StartupUI:
gpu_info = caps.chip[:20]
except Exception:
# Fallback - basic connection info
connection_data = json.dumps({"id": socket.gethostname(), "host": local_ip, "type": "hanzo-node"})
connection_data = json.dumps(
{"id": socket.gethostname(), "host": local_ip, "type": "hanzo-node"}
)
gpu_info = "CPU"
# Generate smallest possible QR code
+11 -3
View File
@@ -14,7 +14,11 @@ def get_config_paths() -> Dict[str, Path]:
# System config
if os.name == "nt": # Windows
paths["system"] = Path(os.environ.get("PROGRAMDATA", "C:\\ProgramData")) / "hanzo" / "config.yaml"
paths["system"] = (
Path(os.environ.get("PROGRAMDATA", "C:\\ProgramData"))
/ "hanzo"
/ "config.yaml"
)
else: # Unix-like
paths["system"] = Path("/etc/hanzo/config.yaml")
@@ -138,7 +142,9 @@ def init_config() -> Dict[str, Path]:
def get_default_model() -> str:
"""Get default model from config or environment."""
return os.environ.get("HANZO_DEFAULT_MODEL") or get_config_value("default_model", "llama-3.2-3b")
return os.environ.get("HANZO_DEFAULT_MODEL") or get_config_value(
"default_model", "llama-3.2-3b"
)
def get_api_key(provider: str) -> Optional[str]:
@@ -161,4 +167,6 @@ def get_api_key(provider: str) -> Optional[str]:
def is_local_preferred() -> bool:
"""Check if local execution is preferred."""
return os.environ.get("HANZO_USE_LOCAL", "").lower() == "true" or get_config_value("prefer_local", False)
return os.environ.get("HANZO_USE_LOCAL", "").lower() == "true" or get_config_value(
"prefer_local", False
)
+6 -2
View File
@@ -44,7 +44,9 @@ def check_net_installation() -> Tuple[bool, Optional[str], Optional[str]]:
return False, str(net_path), str(venv_python)
# No venv, check system Python
result = subprocess.run([sys.executable, "-c", "import scapy"], capture_output=True, text=True)
result = subprocess.run(
[sys.executable, "-c", "import scapy"], capture_output=True, text=True
)
if result.returncode == 0:
return True, str(net_path), sys.executable
@@ -102,7 +104,9 @@ def get_missing_dependencies(python_exe: str = None) -> list:
missing = []
for package in required_packages:
result = subprocess.run([python_exe, "-c", f"import {package}"], capture_output=True, text=True)
result = subprocess.run(
[python_exe, "-c", f"import {package}"], capture_output=True, text=True
)
if result.returncode != 0:
missing.append(package)