hanzo 0.3.12__py3-none-any.whl → 0.3.14__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of hanzo might be problematic. Click here for more details.

hanzo/cli.py CHANGED
@@ -1,9 +1,9 @@
1
1
  """Main CLI entry point for Hanzo."""
2
2
 
3
+ import os
3
4
  import sys
4
- import asyncio
5
5
  import signal
6
- import os
6
+ import asyncio
7
7
  import subprocess
8
8
  from typing import Optional
9
9
 
@@ -26,7 +26,7 @@ from .utils.output import console
26
26
  from .interactive.repl import HanzoREPL
27
27
 
28
28
  # Version
29
- __version__ = "0.2.10"
29
+ __version__ = "0.3.14"
30
30
 
31
31
 
32
32
  @click.group(invoke_without_command=True)
@@ -149,34 +149,78 @@ def node(ctx, name: str, port: int, network: str, models: tuple, max_jobs: int):
149
149
 
150
150
  @cli.command()
151
151
  @click.option("--workspace", default="~/.hanzo/dev", help="Workspace directory")
152
- @click.option("--orchestrator", default="gpt-5", help="Orchestrator model (e.g., gpt-5, gpt-4, claude-3-5-sonnet, local:llama3.2)")
153
- @click.option("--claude-path", help="Path to Claude Code executable")
152
+ @click.option(
153
+ "--orchestrator",
154
+ default="gpt-5",
155
+ help="Orchestrator: gpt-5, router:gpt-4o, direct:claude, codex, gpt-5-pro-codex, cost-optimized",
156
+ )
157
+ @click.option(
158
+ "--orchestrator-mode",
159
+ type=click.Choice(["router", "direct", "codex", "hybrid", "local"]),
160
+ default=None,
161
+ help="Force orchestrator mode (router via hanzo-router, direct API, codex, hybrid, local)",
162
+ )
163
+ @click.option(
164
+ "--router-endpoint",
165
+ default=None,
166
+ help="Hanzo router endpoint (default: http://localhost:4000)",
167
+ )
168
+ @click.option("--claude-path", help="Path to Claude Code executable")
154
169
  @click.option("--monitor", is_flag=True, help="Start in monitor mode")
155
170
  @click.option("--repl", is_flag=True, help="Start REPL interface (default)")
156
171
  @click.option("--instances", type=int, default=2, help="Number of worker agents")
157
172
  @click.option("--mcp-tools", is_flag=True, default=True, help="Enable all MCP tools")
158
- @click.option("--network-mode", is_flag=True, default=True, help="Network agents together")
159
- @click.option("--guardrails", is_flag=True, default=True, help="Enable code quality guardrails")
160
- @click.option("--use-network/--no-network", default=True, help="Use hanzo-network if available")
161
- @click.option("--use-hanzo-net", is_flag=True, help="Use hanzo/net for local AI (auto-enabled with local: models)")
162
- @click.option("--hanzo-net-port", type=int, default=52415, help="Port for hanzo/net (default: 52415)")
163
- @click.pass_context
164
- def dev(ctx, workspace: str, orchestrator: str, claude_path: str, monitor: bool, repl: bool,
165
- instances: int, mcp_tools: bool, network_mode: bool, guardrails: bool, use_network: bool,
166
- use_hanzo_net: bool, hanzo_net_port: int):
173
+ @click.option(
174
+ "--network-mode", is_flag=True, default=True, help="Network agents together"
175
+ )
176
+ @click.option(
177
+ "--guardrails", is_flag=True, default=True, help="Enable code quality guardrails"
178
+ )
179
+ @click.option(
180
+ "--use-network/--no-network", default=True, help="Use hanzo-network if available"
181
+ )
182
+ @click.option(
183
+ "--use-hanzo-net",
184
+ is_flag=True,
185
+ help="Use hanzo/net for local AI (auto-enabled with local: models)",
186
+ )
187
+ @click.option(
188
+ "--hanzo-net-port",
189
+ type=int,
190
+ default=52415,
191
+ help="Port for hanzo/net (default: 52415)",
192
+ )
193
+ @click.pass_context
194
+ def dev(
195
+ ctx,
196
+ workspace: str,
197
+ orchestrator: str,
198
+ orchestrator_mode: str,
199
+ router_endpoint: str,
200
+ claude_path: str,
201
+ monitor: bool,
202
+ repl: bool,
203
+ instances: int,
204
+ mcp_tools: bool,
205
+ network_mode: bool,
206
+ guardrails: bool,
207
+ use_network: bool,
208
+ use_hanzo_net: bool,
209
+ hanzo_net_port: int,
210
+ ):
167
211
  """Start Hanzo Dev - AI Coding OS with configurable orchestrator.
168
-
212
+
169
213
  This creates a multi-agent system where:
170
214
  - Configurable orchestrator (GPT-5, GPT-4, Claude, or LOCAL) manages the network
171
215
  - Local AI via hanzo/net for cost-effective orchestration
172
- - Worker agents (Claude + local) handle code implementation
216
+ - Worker agents (Claude + local) handle code implementation
173
217
  - Critic agents review and improve code (System 2 thinking)
174
218
  - Cost-optimized routing (local models for simple tasks)
175
219
  - All agents can use MCP tools
176
220
  - Agents can call each other recursively
177
221
  - Guardrails prevent code degradation
178
222
  - Auto-recovery from failures
179
-
223
+
180
224
  Examples:
181
225
  hanzo dev # GPT-5 orchestrator (default)
182
226
  hanzo dev --orchestrator gpt-4 # GPT-4 orchestrator
@@ -187,26 +231,54 @@ def dev(ctx, workspace: str, orchestrator: str, claude_path: str, monitor: bool,
187
231
  hanzo dev --monitor # Auto-monitor and restart mode
188
232
  """
189
233
  from .dev import run_dev_orchestrator
190
-
234
+ from .orchestrator_config import OrchestratorMode, get_orchestrator_config
235
+
236
+ # Get orchestrator configuration
237
+ orch_config = get_orchestrator_config(orchestrator)
238
+
239
+ # Override mode if specified
240
+ if orchestrator_mode:
241
+ orch_config.mode = OrchestratorMode(orchestrator_mode)
242
+
243
+ # Override router endpoint if specified
244
+ if router_endpoint and orch_config.router:
245
+ orch_config.router.endpoint = router_endpoint
246
+
191
247
  # Auto-enable hanzo net if using local orchestrator
192
- if orchestrator.startswith("local:"):
248
+ if orchestrator.startswith("local:") or orch_config.mode == OrchestratorMode.LOCAL:
193
249
  use_hanzo_net = True
194
-
195
- asyncio.run(run_dev_orchestrator(
196
- workspace=workspace,
197
- orchestrator_model=orchestrator,
198
- claude_path=claude_path,
199
- monitor=monitor,
200
- repl=repl or not monitor, # Default to REPL if not monitoring
201
- instances=instances,
202
- mcp_tools=mcp_tools,
203
- network_mode=network_mode,
204
- guardrails=guardrails,
205
- use_network=use_network,
206
- use_hanzo_net=use_hanzo_net,
207
- hanzo_net_port=hanzo_net_port,
208
- console=ctx.obj.get("console", console)
209
- ))
250
+
251
+ # Show configuration
252
+ console.print(f"[bold cyan]Orchestrator Configuration[/bold cyan]")
253
+ console.print(f" Mode: {orch_config.mode.value}")
254
+ console.print(f" Primary Model: {orch_config.primary_model}")
255
+ if orch_config.router:
256
+ console.print(f" Router Endpoint: {orch_config.router.endpoint}")
257
+ if orch_config.codex:
258
+ console.print(f" Codex Model: {orch_config.codex.model}")
259
+ console.print(
260
+ f" Cost Optimization: {'Enabled' if orch_config.enable_cost_optimization else 'Disabled'}"
261
+ )
262
+ console.print()
263
+
264
+ asyncio.run(
265
+ run_dev_orchestrator(
266
+ workspace=workspace,
267
+ orchestrator_model=orchestrator,
268
+ orchestrator_config=orch_config, # Pass the config
269
+ claude_path=claude_path,
270
+ monitor=monitor,
271
+ repl=repl or not monitor, # Default to REPL if not monitoring
272
+ instances=instances,
273
+ mcp_tools=mcp_tools,
274
+ network_mode=network_mode,
275
+ guardrails=guardrails,
276
+ use_network=use_network,
277
+ use_hanzo_net=use_hanzo_net,
278
+ hanzo_net_port=hanzo_net_port,
279
+ console=ctx.obj.get("console", console),
280
+ )
281
+ )
210
282
 
211
283
 
212
284
  async def start_compute_node(
@@ -279,25 +351,24 @@ async def start_compute_node(
279
351
 
280
352
  # Set up signal handlers for async version
281
353
  stop_event = asyncio.Event()
282
-
354
+
283
355
  def async_signal_handler(signum, frame):
284
356
  console.print("\n[yellow]Stopping hanzo net...[/yellow]")
285
357
  stop_event.set()
286
-
358
+
287
359
  signal.signal(signal.SIGINT, async_signal_handler)
288
360
  signal.signal(signal.SIGTERM, async_signal_handler)
289
-
361
+
290
362
  # Run net with proper signal handling
291
363
  try:
292
364
  net_task = asyncio.create_task(net_run())
293
365
  stop_task = asyncio.create_task(stop_event.wait())
294
-
366
+
295
367
  # Wait for either net to complete or stop signal
296
368
  done, pending = await asyncio.wait(
297
- [net_task, stop_task],
298
- return_when=asyncio.FIRST_COMPLETED
369
+ [net_task, stop_task], return_when=asyncio.FIRST_COMPLETED
299
370
  )
300
-
371
+
301
372
  # Cancel pending tasks
302
373
  for task in pending:
303
374
  task.cancel()
@@ -305,7 +376,7 @@ async def start_compute_node(
305
376
  await task
306
377
  except asyncio.CancelledError:
307
378
  pass
308
-
379
+
309
380
  # Check if we stopped due to signal
310
381
  if stop_task in done:
311
382
  console.print("[green]✓[/green] Node stopped gracefully")
@@ -360,25 +431,25 @@ async def start_compute_node(
360
431
  # Run net command with detected python in a more signal-friendly way
361
432
  # Create new process group for better signal handling
362
433
  process = subprocess.Popen(
363
- cmd_args,
434
+ cmd_args,
364
435
  env=env,
365
- preexec_fn=os.setsid if hasattr(os, 'setsid') else None
436
+ preexec_fn=os.setsid if hasattr(os, "setsid") else None,
366
437
  )
367
-
438
+
368
439
  # Set up signal handlers to forward to subprocess group
369
440
  def signal_handler(signum, frame):
370
441
  if process.poll() is None: # Process is still running
371
442
  console.print("\n[yellow]Stopping hanzo net...[/yellow]")
372
443
  try:
373
444
  # Send signal to entire process group
374
- if hasattr(os, 'killpg'):
445
+ if hasattr(os, "killpg"):
375
446
  os.killpg(os.getpgid(process.pid), signal.SIGTERM)
376
447
  else:
377
448
  process.terminate()
378
449
  process.wait(timeout=5) # Wait up to 5 seconds
379
450
  except subprocess.TimeoutExpired:
380
451
  console.print("[yellow]Force stopping...[/yellow]")
381
- if hasattr(os, 'killpg'):
452
+ if hasattr(os, "killpg"):
382
453
  os.killpg(os.getpgid(process.pid), signal.SIGKILL)
383
454
  else:
384
455
  process.kill()
@@ -386,18 +457,16 @@ async def start_compute_node(
386
457
  except ProcessLookupError:
387
458
  pass # Process already terminated
388
459
  raise KeyboardInterrupt
389
-
460
+
390
461
  # Register signal handlers
391
462
  signal.signal(signal.SIGINT, signal_handler)
392
463
  signal.signal(signal.SIGTERM, signal_handler)
393
-
464
+
394
465
  # Wait for process to complete
395
466
  returncode = process.wait()
396
-
467
+
397
468
  if returncode != 0 and returncode != -2: # -2 is Ctrl+C
398
- console.print(
399
- f"[red]Net exited with code {returncode}[/red]"
400
- )
469
+ console.print(f"[red]Net exited with code {returncode}[/red]")
401
470
 
402
471
  finally:
403
472
  os.chdir(original_cwd)