hanzo 0.3.23__py3-none-any.whl → 0.3.25__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/__init__.py +2 -2
- hanzo/base_agent.py +4 -5
- hanzo/batch_orchestrator.py +11 -11
- hanzo/cli.py +20 -10
- hanzo/commands/auth.py +206 -266
- hanzo/commands/auth_broken.py +377 -0
- hanzo/commands/chat.py +60 -16
- hanzo/commands/{cluster.py → node.py} +128 -128
- hanzo/commands/router.py +152 -0
- hanzo/dev.py +1 -1
- hanzo/fallback_handler.py +1 -1
- hanzo/interactive/enhanced_repl.py +513 -0
- hanzo/interactive/repl.py +54 -34
- hanzo/mcp_server.py +8 -3
- hanzo/memory_manager.py +1 -1
- hanzo/model_registry.py +2 -2
- hanzo/rate_limiter.py +1 -1
- hanzo/streaming.py +1 -1
- hanzo/ui/__init__.py +13 -0
- hanzo/ui/inline_startup.py +136 -0
- hanzo/ui/startup.py +350 -0
- hanzo-0.3.25.dist-info/METADATA +276 -0
- hanzo-0.3.25.dist-info/RECORD +43 -0
- hanzo-0.3.23.dist-info/METADATA +0 -137
- hanzo-0.3.23.dist-info/RECORD +0 -37
- {hanzo-0.3.23.dist-info → hanzo-0.3.25.dist-info}/WHEEL +0 -0
- {hanzo-0.3.23.dist-info → hanzo-0.3.25.dist-info}/entry_points.txt +0 -0
hanzo/interactive/repl.py
CHANGED
|
@@ -35,16 +35,19 @@ class HanzoREPL:
|
|
|
35
35
|
# Don't print welcome message here since it's already printed in cli.py
|
|
36
36
|
|
|
37
37
|
# Set up command completer
|
|
38
|
+
cli_commands = ["chat", "ask", "agent", "node", "mcp", "network",
|
|
39
|
+
"auth", "config", "tools", "miner", "serve", "net",
|
|
40
|
+
"dev", "router"]
|
|
38
41
|
completer = WordCompleter(
|
|
39
|
-
list(self.commands.keys()) +
|
|
42
|
+
list(self.commands.keys()) + cli_commands,
|
|
40
43
|
ignore_case=True,
|
|
41
44
|
)
|
|
42
45
|
|
|
43
46
|
while self.running:
|
|
44
47
|
try:
|
|
45
|
-
# Get input
|
|
48
|
+
# Get input with simple prompt
|
|
46
49
|
command = await self.session.prompt_async(
|
|
47
|
-
"
|
|
50
|
+
"> ", completer=completer
|
|
48
51
|
)
|
|
49
52
|
|
|
50
53
|
if not command.strip():
|
|
@@ -58,8 +61,12 @@ class HanzoREPL:
|
|
|
58
61
|
# Execute command
|
|
59
62
|
if cmd in self.commands:
|
|
60
63
|
await self.commands[cmd](args)
|
|
61
|
-
|
|
64
|
+
elif cmd in ["chat", "ask", "agent", "node", "mcp", "network", "auth", "config", "tools", "miner", "serve", "net", "dev", "router"]:
|
|
65
|
+
# Execute known CLI commands
|
|
62
66
|
await self.execute_command(cmd, args)
|
|
67
|
+
else:
|
|
68
|
+
# Treat as chat message if not a known command
|
|
69
|
+
await self.chat_with_ai(command)
|
|
63
70
|
|
|
64
71
|
except KeyboardInterrupt:
|
|
65
72
|
continue
|
|
@@ -83,7 +90,7 @@ class HanzoREPL:
|
|
|
83
90
|
All Hanzo CLI commands are available:
|
|
84
91
|
- `chat <message>` - Chat with AI
|
|
85
92
|
- `agent start` - Start an agent
|
|
86
|
-
- `
|
|
93
|
+
- `node status` - Check node status
|
|
87
94
|
- `mcp tools` - List MCP tools
|
|
88
95
|
- `network agents` - List network agents
|
|
89
96
|
|
|
@@ -91,7 +98,7 @@ All Hanzo CLI commands are available:
|
|
|
91
98
|
```
|
|
92
99
|
hanzo> chat How do I create a Python web server?
|
|
93
100
|
hanzo> agent list
|
|
94
|
-
hanzo>
|
|
101
|
+
hanzo> node start --models llama-3.2-3b
|
|
95
102
|
hanzo> mcp run read_file --arg path=README.md
|
|
96
103
|
```
|
|
97
104
|
|
|
@@ -114,53 +121,60 @@ hanzo> mcp run read_file --arg path=README.md
|
|
|
114
121
|
async def show_status(self, args: str = ""):
|
|
115
122
|
"""Show system status."""
|
|
116
123
|
status = {
|
|
117
|
-
"
|
|
124
|
+
"node": await self.check_node_status(),
|
|
118
125
|
"agents": await self.count_agents(),
|
|
119
126
|
"auth": self.check_auth_status(),
|
|
120
127
|
}
|
|
121
128
|
|
|
122
129
|
self.console.print("[cyan]System Status:[/cyan]")
|
|
123
|
-
self.console.print(f"
|
|
130
|
+
self.console.print(f" Node: {status['node']}")
|
|
124
131
|
self.console.print(f" Agents: {status['agents']}")
|
|
125
132
|
self.console.print(f" Auth: {status['auth']}")
|
|
126
133
|
|
|
127
134
|
async def execute_command(self, cmd: str, args: str):
|
|
128
135
|
"""Execute a CLI command."""
|
|
129
|
-
|
|
136
|
+
import os
|
|
130
137
|
import sys
|
|
131
|
-
|
|
132
|
-
import
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
+
import shutil
|
|
139
|
+
import subprocess
|
|
140
|
+
|
|
141
|
+
# Find hanzo executable
|
|
142
|
+
hanzo_cmd = shutil.which("hanzo")
|
|
143
|
+
if not hanzo_cmd:
|
|
144
|
+
# Try using Python module directly
|
|
145
|
+
hanzo_cmd = sys.executable
|
|
146
|
+
argv = [hanzo_cmd, "-m", "hanzo", cmd]
|
|
147
|
+
else:
|
|
148
|
+
argv = [hanzo_cmd, cmd]
|
|
149
|
+
|
|
138
150
|
if args:
|
|
139
151
|
import shlex
|
|
140
|
-
|
|
141
152
|
argv.extend(shlex.split(args))
|
|
142
153
|
|
|
143
|
-
#
|
|
154
|
+
# Execute as subprocess to avoid context issues
|
|
144
155
|
try:
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
+
result = subprocess.run(
|
|
157
|
+
argv,
|
|
158
|
+
capture_output=True,
|
|
159
|
+
text=True,
|
|
160
|
+
timeout=30,
|
|
161
|
+
env=os.environ.copy() # Pass environment variables
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
if result.stdout:
|
|
165
|
+
self.console.print(result.stdout.rstrip())
|
|
166
|
+
if result.stderr and result.returncode != 0:
|
|
167
|
+
self.console.print(f"[red]{result.stderr.rstrip()}[/red]")
|
|
168
|
+
|
|
169
|
+
except subprocess.TimeoutExpired:
|
|
170
|
+
self.console.print("[red]Command timed out[/red]")
|
|
171
|
+
except FileNotFoundError:
|
|
172
|
+
self.console.print("[red]Command not found. Make sure 'hanzo' is installed.[/red]")
|
|
156
173
|
except Exception as e:
|
|
157
174
|
self.console.print(f"[red]Command error: {e}[/red]")
|
|
158
|
-
finally:
|
|
159
|
-
# Restore argv
|
|
160
|
-
sys.argv = orig_argv
|
|
161
175
|
|
|
162
|
-
async def
|
|
163
|
-
"""Check if
|
|
176
|
+
async def check_node_status(self) -> str:
|
|
177
|
+
"""Check if node is running."""
|
|
164
178
|
try:
|
|
165
179
|
import httpx
|
|
166
180
|
|
|
@@ -185,3 +199,9 @@ hanzo> mcp run read_file --arg path=README.md
|
|
|
185
199
|
return "authenticated (saved)"
|
|
186
200
|
else:
|
|
187
201
|
return "not authenticated"
|
|
202
|
+
|
|
203
|
+
async def chat_with_ai(self, message: str):
|
|
204
|
+
"""Chat with AI when user types natural language."""
|
|
205
|
+
# For natural language input, try to use it as a chat message
|
|
206
|
+
# Default to cloud mode to avoid needing local server
|
|
207
|
+
await self.execute_command("ask", f"--cloud {message}")
|
hanzo/mcp_server.py
CHANGED
|
@@ -6,11 +6,16 @@ import click
|
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
def main():
|
|
9
|
-
"""Start the Hanzo MCP server.
|
|
9
|
+
"""Start the Hanzo MCP server.
|
|
10
|
+
|
|
11
|
+
This wrapper defers to hanzo_mcp.cli:main so that the CLI can parse
|
|
12
|
+
transport flags and configure logging BEFORE importing any heavy modules,
|
|
13
|
+
preventing stdio protocol corruption.
|
|
14
|
+
"""
|
|
10
15
|
try:
|
|
11
|
-
from hanzo_mcp.
|
|
16
|
+
from hanzo_mcp.cli import main as cli_main
|
|
12
17
|
|
|
13
|
-
|
|
18
|
+
cli_main()
|
|
14
19
|
except ImportError:
|
|
15
20
|
click.echo(
|
|
16
21
|
"Error: hanzo-mcp is not installed. Please run: pip install hanzo[mcp] or pip install hanzo[all]",
|
hanzo/memory_manager.py
CHANGED
hanzo/model_registry.py
CHANGED
|
@@ -6,9 +6,9 @@ eliminating duplication and ensuring consistency across the codebase.
|
|
|
6
6
|
|
|
7
7
|
from __future__ import annotations
|
|
8
8
|
|
|
9
|
-
from dataclasses import dataclass, field
|
|
10
|
-
from typing import Dict, List, Optional, Set
|
|
11
9
|
from enum import Enum
|
|
10
|
+
from typing import Set, Dict, List, Optional
|
|
11
|
+
from dataclasses import field, dataclass
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
class ModelProvider(Enum):
|
hanzo/rate_limiter.py
CHANGED
hanzo/streaming.py
CHANGED
|
@@ -273,7 +273,7 @@ async def stream_with_fallback(message: str, console: Console = None) -> Optiona
|
|
|
273
273
|
# Try Ollama streaming
|
|
274
274
|
try:
|
|
275
275
|
return await handler.stream_ollama(message)
|
|
276
|
-
except:
|
|
276
|
+
except Exception:
|
|
277
277
|
pass
|
|
278
278
|
|
|
279
279
|
# Fallback to non-streaming with simulated effect
|
hanzo/ui/__init__.py
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Inline startup notifications for Hanzo commands.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import json
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from datetime import datetime, timedelta
|
|
9
|
+
from typing import Optional
|
|
10
|
+
from rich.console import Console
|
|
11
|
+
from rich.panel import Panel
|
|
12
|
+
from rich.text import Text
|
|
13
|
+
from rich import box
|
|
14
|
+
|
|
15
|
+
console = Console()
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class InlineStartup:
|
|
19
|
+
"""Lightweight inline startup notifications."""
|
|
20
|
+
|
|
21
|
+
def __init__(self):
|
|
22
|
+
self.config_dir = Path.home() / ".hanzo"
|
|
23
|
+
self.last_shown_file = self.config_dir / ".last_inline_shown"
|
|
24
|
+
self.show_interval = timedelta(hours=24) # Show once per day
|
|
25
|
+
|
|
26
|
+
def should_show(self) -> bool:
|
|
27
|
+
"""Check if we should show inline startup."""
|
|
28
|
+
# Check environment variable
|
|
29
|
+
if os.environ.get("HANZO_NO_STARTUP") == "1":
|
|
30
|
+
return False
|
|
31
|
+
|
|
32
|
+
# Check last shown time
|
|
33
|
+
if self.last_shown_file.exists():
|
|
34
|
+
try:
|
|
35
|
+
last_shown = datetime.fromisoformat(
|
|
36
|
+
self.last_shown_file.read_text().strip()
|
|
37
|
+
)
|
|
38
|
+
if datetime.now() - last_shown < self.show_interval:
|
|
39
|
+
return False
|
|
40
|
+
except:
|
|
41
|
+
pass
|
|
42
|
+
|
|
43
|
+
return True
|
|
44
|
+
|
|
45
|
+
def mark_shown(self):
|
|
46
|
+
"""Mark inline startup as shown."""
|
|
47
|
+
self.config_dir.mkdir(exist_ok=True)
|
|
48
|
+
self.last_shown_file.write_text(datetime.now().isoformat())
|
|
49
|
+
|
|
50
|
+
def show_mini(self, command: str = None):
|
|
51
|
+
"""Show mini inline startup."""
|
|
52
|
+
if not self.should_show():
|
|
53
|
+
return
|
|
54
|
+
|
|
55
|
+
# Build message
|
|
56
|
+
message = Text()
|
|
57
|
+
message.append("✨ ", style="yellow")
|
|
58
|
+
message.append("Hanzo AI ", style="bold cyan")
|
|
59
|
+
message.append("v0.3.23", style="green")
|
|
60
|
+
|
|
61
|
+
# Add what's new teaser
|
|
62
|
+
message.append(" • ", style="dim")
|
|
63
|
+
message.append("What's new: ", style="dim")
|
|
64
|
+
message.append("Router management, improved docs", style="yellow dim")
|
|
65
|
+
|
|
66
|
+
# Show panel
|
|
67
|
+
console.print(
|
|
68
|
+
Panel(
|
|
69
|
+
message,
|
|
70
|
+
box=box.MINIMAL,
|
|
71
|
+
border_style="cyan",
|
|
72
|
+
padding=(0, 1)
|
|
73
|
+
)
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
self.mark_shown()
|
|
77
|
+
|
|
78
|
+
def show_command_hint(self, command: str):
|
|
79
|
+
"""Show command-specific hints."""
|
|
80
|
+
hints = {
|
|
81
|
+
"chat": "💡 Tip: Use --model to change AI model, --router for local proxy",
|
|
82
|
+
"node": "💡 Tip: Run 'hanzo node start' to enable local AI inference",
|
|
83
|
+
"router": "💡 Tip: Router provides unified access to 100+ LLM providers",
|
|
84
|
+
"repl": "💡 Tip: REPL combines Python with AI assistance",
|
|
85
|
+
"agent": "💡 Tip: Agents can work in parallel with 'hanzo agent swarm'"
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
hint = hints.get(command)
|
|
89
|
+
if hint and os.environ.get("HANZO_SHOW_HINTS") != "0":
|
|
90
|
+
console.print(f"[dim]{hint}[/dim]")
|
|
91
|
+
|
|
92
|
+
def show_status_bar(self):
|
|
93
|
+
"""Show a compact status bar."""
|
|
94
|
+
items = []
|
|
95
|
+
|
|
96
|
+
# Check router
|
|
97
|
+
try:
|
|
98
|
+
import httpx
|
|
99
|
+
response = httpx.get("http://localhost:4000/health", timeout=0.5)
|
|
100
|
+
if response.status_code == 200:
|
|
101
|
+
items.append("[green]Router ✓[/green]")
|
|
102
|
+
except:
|
|
103
|
+
pass
|
|
104
|
+
|
|
105
|
+
# Check node
|
|
106
|
+
try:
|
|
107
|
+
import httpx
|
|
108
|
+
response = httpx.get("http://localhost:8000/health", timeout=0.5)
|
|
109
|
+
if response.status_code == 200:
|
|
110
|
+
items.append("[green]Node ✓[/green]")
|
|
111
|
+
except:
|
|
112
|
+
pass
|
|
113
|
+
|
|
114
|
+
# Check API key
|
|
115
|
+
if os.environ.get("HANZO_API_KEY"):
|
|
116
|
+
items.append("[green]API ✓[/green]")
|
|
117
|
+
else:
|
|
118
|
+
items.append("[yellow]API ⚠[/yellow]")
|
|
119
|
+
|
|
120
|
+
if items:
|
|
121
|
+
status = " • ".join(items)
|
|
122
|
+
console.print(f"[dim]Status: {status}[/dim]")
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def show_inline_startup(command: str = None):
|
|
126
|
+
"""Show inline startup notification."""
|
|
127
|
+
startup = InlineStartup()
|
|
128
|
+
startup.show_mini(command)
|
|
129
|
+
if command:
|
|
130
|
+
startup.show_command_hint(command)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def show_status():
|
|
134
|
+
"""Show compact status bar."""
|
|
135
|
+
startup = InlineStartup()
|
|
136
|
+
startup.show_status_bar()
|