hanzo 0.2.2__py3-none-any.whl → 0.2.5__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 +4 -93
- hanzo/__main__.py +6 -0
- hanzo/cli.py +232 -8
- hanzo/commands/__init__.py +3 -0
- hanzo/commands/agent.py +112 -0
- hanzo/commands/auth.py +324 -0
- hanzo/commands/chat.py +183 -0
- hanzo/commands/cluster.py +428 -0
- hanzo/commands/config.py +240 -0
- hanzo/commands/mcp.py +235 -0
- hanzo/commands/miner.py +323 -0
- hanzo/commands/network.py +333 -0
- hanzo/commands/repl.py +186 -0
- hanzo/commands/tools.py +335 -0
- hanzo/interactive/__init__.py +3 -0
- hanzo/interactive/dashboard.py +125 -0
- hanzo/interactive/repl.py +184 -0
- hanzo/router/__init__.py +13 -7
- hanzo/utils/__init__.py +3 -0
- hanzo/utils/config.py +170 -0
- hanzo/utils/output.py +103 -0
- hanzo-0.2.5.dist-info/METADATA +137 -0
- hanzo-0.2.5.dist-info/RECORD +27 -0
- hanzo-0.2.2.dist-info/METADATA +0 -74
- hanzo-0.2.2.dist-info/RECORD +0 -9
- {hanzo-0.2.2.dist-info → hanzo-0.2.5.dist-info}/WHEEL +0 -0
- {hanzo-0.2.2.dist-info → hanzo-0.2.5.dist-info}/entry_points.txt +0 -0
hanzo/commands/tools.py
ADDED
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
"""Tools management commands."""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
from typing import List, Optional
|
|
5
|
+
|
|
6
|
+
import click
|
|
7
|
+
from rich.table import Table
|
|
8
|
+
from rich.syntax import Syntax
|
|
9
|
+
|
|
10
|
+
from ..utils.output import console, handle_errors
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@click.group(name="tools")
|
|
14
|
+
def tools_group():
|
|
15
|
+
"""Manage Hanzo tools and plugins."""
|
|
16
|
+
pass
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@tools_group.command(name="list")
|
|
20
|
+
@click.option("--category", "-c", help="Filter by category")
|
|
21
|
+
@click.option("--installed", is_flag=True, help="Show only installed tools")
|
|
22
|
+
@click.pass_context
|
|
23
|
+
async def list_tools(ctx, category: str, installed: bool):
|
|
24
|
+
"""List available tools."""
|
|
25
|
+
try:
|
|
26
|
+
from hanzo_tools import get_tool_registry
|
|
27
|
+
except ImportError:
|
|
28
|
+
console.print("[red]Error:[/red] hanzo-tools not installed")
|
|
29
|
+
console.print("Install with: pip install hanzo[tools]")
|
|
30
|
+
return
|
|
31
|
+
|
|
32
|
+
registry = get_tool_registry()
|
|
33
|
+
|
|
34
|
+
with console.status("Loading tools..."):
|
|
35
|
+
try:
|
|
36
|
+
tools = await registry.list_tools(
|
|
37
|
+
category=category,
|
|
38
|
+
installed_only=installed
|
|
39
|
+
)
|
|
40
|
+
except Exception as e:
|
|
41
|
+
console.print(f"[red]Failed to load tools: {e}[/red]")
|
|
42
|
+
return
|
|
43
|
+
|
|
44
|
+
if not tools:
|
|
45
|
+
console.print("[yellow]No tools found[/yellow]")
|
|
46
|
+
return
|
|
47
|
+
|
|
48
|
+
# Group by category
|
|
49
|
+
categories = {}
|
|
50
|
+
for tool in tools:
|
|
51
|
+
cat = tool.get("category", "uncategorized")
|
|
52
|
+
if cat not in categories:
|
|
53
|
+
categories[cat] = []
|
|
54
|
+
categories[cat].append(tool)
|
|
55
|
+
|
|
56
|
+
# Display tools
|
|
57
|
+
for cat, cat_tools in sorted(categories.items()):
|
|
58
|
+
table = Table(title=f"{cat.title()} Tools")
|
|
59
|
+
table.add_column("Name", style="cyan")
|
|
60
|
+
table.add_column("Version", style="green")
|
|
61
|
+
table.add_column("Description", style="white")
|
|
62
|
+
table.add_column("Status", style="yellow")
|
|
63
|
+
|
|
64
|
+
for tool in sorted(cat_tools, key=lambda t: t["name"]):
|
|
65
|
+
table.add_row(
|
|
66
|
+
tool["name"],
|
|
67
|
+
tool.get("version", "latest"),
|
|
68
|
+
tool.get("description", ""),
|
|
69
|
+
"installed" if tool.get("installed") else "available"
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
console.print(table)
|
|
73
|
+
if len(categories) > 1:
|
|
74
|
+
console.print()
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
@tools_group.command()
|
|
78
|
+
@click.argument("tool_name")
|
|
79
|
+
@click.option("--version", "-v", help="Specific version to install")
|
|
80
|
+
@click.pass_context
|
|
81
|
+
async def install(ctx, tool_name: str, version: str):
|
|
82
|
+
"""Install a tool."""
|
|
83
|
+
try:
|
|
84
|
+
from hanzo_tools import get_tool_registry
|
|
85
|
+
except ImportError:
|
|
86
|
+
console.print("[red]Error:[/red] hanzo-tools not installed")
|
|
87
|
+
return
|
|
88
|
+
|
|
89
|
+
registry = get_tool_registry()
|
|
90
|
+
|
|
91
|
+
with console.status(f"Installing {tool_name}..."):
|
|
92
|
+
try:
|
|
93
|
+
result = await registry.install_tool(
|
|
94
|
+
name=tool_name,
|
|
95
|
+
version=version
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
console.print(f"[green]✓[/green] Installed {tool_name} v{result['version']}")
|
|
99
|
+
|
|
100
|
+
if deps := result.get("dependencies_installed"):
|
|
101
|
+
console.print(f" Dependencies: {', '.join(deps)}")
|
|
102
|
+
|
|
103
|
+
if config := result.get("post_install_message"):
|
|
104
|
+
console.print(f"\n[yellow]Configuration:[/yellow]")
|
|
105
|
+
console.print(config)
|
|
106
|
+
|
|
107
|
+
except Exception as e:
|
|
108
|
+
console.print(f"[red]Failed to install {tool_name}: {e}[/red]")
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
@tools_group.command()
|
|
112
|
+
@click.argument("tool_name")
|
|
113
|
+
@click.pass_context
|
|
114
|
+
async def uninstall(ctx, tool_name: str):
|
|
115
|
+
"""Uninstall a tool."""
|
|
116
|
+
try:
|
|
117
|
+
from hanzo_tools import get_tool_registry
|
|
118
|
+
except ImportError:
|
|
119
|
+
console.print("[red]Error:[/red] hanzo-tools not installed")
|
|
120
|
+
return
|
|
121
|
+
|
|
122
|
+
registry = get_tool_registry()
|
|
123
|
+
|
|
124
|
+
if click.confirm(f"Uninstall {tool_name}?"):
|
|
125
|
+
with console.status(f"Uninstalling {tool_name}..."):
|
|
126
|
+
try:
|
|
127
|
+
await registry.uninstall_tool(tool_name)
|
|
128
|
+
console.print(f"[green]✓[/green] Uninstalled {tool_name}")
|
|
129
|
+
except Exception as e:
|
|
130
|
+
console.print(f"[red]Failed to uninstall {tool_name}: {e}[/red]")
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
@tools_group.command()
|
|
134
|
+
@click.argument("tool_name")
|
|
135
|
+
@click.option("--version", "-v", help="Target version")
|
|
136
|
+
@click.pass_context
|
|
137
|
+
async def update(ctx, tool_name: str, version: str):
|
|
138
|
+
"""Update a tool."""
|
|
139
|
+
try:
|
|
140
|
+
from hanzo_tools import get_tool_registry
|
|
141
|
+
except ImportError:
|
|
142
|
+
console.print("[red]Error:[/red] hanzo-tools not installed")
|
|
143
|
+
return
|
|
144
|
+
|
|
145
|
+
registry = get_tool_registry()
|
|
146
|
+
|
|
147
|
+
with console.status(f"Updating {tool_name}..."):
|
|
148
|
+
try:
|
|
149
|
+
result = await registry.update_tool(
|
|
150
|
+
name=tool_name,
|
|
151
|
+
version=version
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
console.print(f"[green]✓[/green] Updated {tool_name}")
|
|
155
|
+
console.print(f" Previous: v{result['previous_version']}")
|
|
156
|
+
console.print(f" Current: v{result['current_version']}")
|
|
157
|
+
|
|
158
|
+
except Exception as e:
|
|
159
|
+
console.print(f"[red]Failed to update {tool_name}: {e}[/red]")
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
@tools_group.command()
|
|
163
|
+
@click.argument("tool_name")
|
|
164
|
+
@click.pass_context
|
|
165
|
+
async def info(ctx, tool_name: str):
|
|
166
|
+
"""Show tool information."""
|
|
167
|
+
try:
|
|
168
|
+
from hanzo_tools import get_tool_registry
|
|
169
|
+
except ImportError:
|
|
170
|
+
console.print("[red]Error:[/red] hanzo-tools not installed")
|
|
171
|
+
return
|
|
172
|
+
|
|
173
|
+
registry = get_tool_registry()
|
|
174
|
+
|
|
175
|
+
with console.status(f"Loading {tool_name} info..."):
|
|
176
|
+
try:
|
|
177
|
+
info = await registry.get_tool_info(tool_name)
|
|
178
|
+
except Exception as e:
|
|
179
|
+
console.print(f"[red]Failed to get info: {e}[/red]")
|
|
180
|
+
return
|
|
181
|
+
|
|
182
|
+
console.print(f"[cyan]{info['name']}[/cyan]")
|
|
183
|
+
console.print(f" Version: {info['version']}")
|
|
184
|
+
console.print(f" Category: {info['category']}")
|
|
185
|
+
console.print(f" Author: {info.get('author', 'Unknown')}")
|
|
186
|
+
console.print(f" License: {info.get('license', 'Unknown')}")
|
|
187
|
+
|
|
188
|
+
if desc := info.get("description"):
|
|
189
|
+
console.print(f"\n{desc}")
|
|
190
|
+
|
|
191
|
+
if features := info.get("features"):
|
|
192
|
+
console.print("\n[cyan]Features:[/cyan]")
|
|
193
|
+
for feature in features:
|
|
194
|
+
console.print(f" • {feature}")
|
|
195
|
+
|
|
196
|
+
if deps := info.get("dependencies"):
|
|
197
|
+
console.print("\n[cyan]Dependencies:[/cyan]")
|
|
198
|
+
for dep in deps:
|
|
199
|
+
console.print(f" • {dep}")
|
|
200
|
+
|
|
201
|
+
if usage := info.get("usage_example"):
|
|
202
|
+
console.print("\n[cyan]Usage Example:[/cyan]")
|
|
203
|
+
syntax = Syntax(usage, "python", theme="monokai", line_numbers=False)
|
|
204
|
+
console.print(syntax)
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
@tools_group.command()
|
|
208
|
+
@click.argument("tool_name")
|
|
209
|
+
@click.argument("args", nargs=-1)
|
|
210
|
+
@click.option("--json", "-j", is_flag=True, help="Output as JSON")
|
|
211
|
+
@click.pass_context
|
|
212
|
+
async def run(ctx, tool_name: str, args: tuple, json: bool):
|
|
213
|
+
"""Run a tool directly."""
|
|
214
|
+
try:
|
|
215
|
+
from hanzo_tools import run_tool
|
|
216
|
+
except ImportError:
|
|
217
|
+
console.print("[red]Error:[/red] hanzo-tools not installed")
|
|
218
|
+
return
|
|
219
|
+
|
|
220
|
+
# Parse arguments
|
|
221
|
+
tool_args = {}
|
|
222
|
+
for arg in args:
|
|
223
|
+
if "=" in arg:
|
|
224
|
+
key, value = arg.split("=", 1)
|
|
225
|
+
tool_args[key] = value
|
|
226
|
+
else:
|
|
227
|
+
console.print(f"[red]Invalid argument format: {arg}[/red]")
|
|
228
|
+
console.print("Use: key=value")
|
|
229
|
+
return
|
|
230
|
+
|
|
231
|
+
with console.status(f"Running {tool_name}..."):
|
|
232
|
+
try:
|
|
233
|
+
result = await run_tool(
|
|
234
|
+
name=tool_name,
|
|
235
|
+
args=tool_args
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
if json:
|
|
239
|
+
import json as json_lib
|
|
240
|
+
console.print_json(data=result)
|
|
241
|
+
else:
|
|
242
|
+
if isinstance(result, str):
|
|
243
|
+
console.print(result)
|
|
244
|
+
elif isinstance(result, dict):
|
|
245
|
+
for key, value in result.items():
|
|
246
|
+
console.print(f"{key}: {value}")
|
|
247
|
+
else:
|
|
248
|
+
console.print(result)
|
|
249
|
+
|
|
250
|
+
except Exception as e:
|
|
251
|
+
console.print(f"[red]Tool execution failed: {e}[/red]")
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
@tools_group.command()
|
|
255
|
+
@click.option("--check", is_flag=True, help="Check for updates only")
|
|
256
|
+
@click.pass_context
|
|
257
|
+
async def upgrade(ctx, check: bool):
|
|
258
|
+
"""Upgrade all tools."""
|
|
259
|
+
try:
|
|
260
|
+
from hanzo_tools import get_tool_registry
|
|
261
|
+
except ImportError:
|
|
262
|
+
console.print("[red]Error:[/red] hanzo-tools not installed")
|
|
263
|
+
return
|
|
264
|
+
|
|
265
|
+
registry = get_tool_registry()
|
|
266
|
+
|
|
267
|
+
with console.status("Checking for updates..."):
|
|
268
|
+
try:
|
|
269
|
+
updates = await registry.check_updates()
|
|
270
|
+
except Exception as e:
|
|
271
|
+
console.print(f"[red]Failed to check updates: {e}[/red]")
|
|
272
|
+
return
|
|
273
|
+
|
|
274
|
+
if not updates:
|
|
275
|
+
console.print("[green]✓[/green] All tools are up to date")
|
|
276
|
+
return
|
|
277
|
+
|
|
278
|
+
# Show available updates
|
|
279
|
+
table = Table(title="Available Updates")
|
|
280
|
+
table.add_column("Tool", style="cyan")
|
|
281
|
+
table.add_column("Current", style="yellow")
|
|
282
|
+
table.add_column("Latest", style="green")
|
|
283
|
+
table.add_column("Changes", style="white")
|
|
284
|
+
|
|
285
|
+
for update in updates:
|
|
286
|
+
table.add_row(
|
|
287
|
+
update["name"],
|
|
288
|
+
update["current_version"],
|
|
289
|
+
update["latest_version"],
|
|
290
|
+
update.get("changelog_summary", "")
|
|
291
|
+
)
|
|
292
|
+
|
|
293
|
+
console.print(table)
|
|
294
|
+
|
|
295
|
+
if check:
|
|
296
|
+
return
|
|
297
|
+
|
|
298
|
+
# Perform updates
|
|
299
|
+
if click.confirm(f"Update {len(updates)} tools?"):
|
|
300
|
+
for update in updates:
|
|
301
|
+
with console.status(f"Updating {update['name']}..."):
|
|
302
|
+
try:
|
|
303
|
+
await registry.update_tool(update["name"])
|
|
304
|
+
console.print(f"[green]✓[/green] Updated {update['name']}")
|
|
305
|
+
except Exception as e:
|
|
306
|
+
console.print(f"[red]Failed to update {update['name']}: {e}[/red]")
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
@tools_group.command()
|
|
310
|
+
@click.argument("name")
|
|
311
|
+
@click.option("--template", "-t", help="Tool template to use")
|
|
312
|
+
@click.pass_context
|
|
313
|
+
async def create(ctx, name: str, template: str):
|
|
314
|
+
"""Create a new custom tool."""
|
|
315
|
+
try:
|
|
316
|
+
from hanzo_tools import create_tool_template
|
|
317
|
+
except ImportError:
|
|
318
|
+
console.print("[red]Error:[/red] hanzo-tools not installed")
|
|
319
|
+
return
|
|
320
|
+
|
|
321
|
+
with console.status(f"Creating tool '{name}'..."):
|
|
322
|
+
try:
|
|
323
|
+
path = await create_tool_template(
|
|
324
|
+
name=name,
|
|
325
|
+
template=template or "basic"
|
|
326
|
+
)
|
|
327
|
+
|
|
328
|
+
console.print(f"[green]✓[/green] Created tool template at: {path}")
|
|
329
|
+
console.print("\nNext steps:")
|
|
330
|
+
console.print("1. Edit the tool implementation")
|
|
331
|
+
console.print("2. Test with: hanzo tools run {name}")
|
|
332
|
+
console.print("3. Package with: hanzo tools package {name}")
|
|
333
|
+
|
|
334
|
+
except Exception as e:
|
|
335
|
+
console.print(f"[red]Failed to create tool: {e}[/red]")
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"""Dashboard interface for Hanzo CLI."""
|
|
2
|
+
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
from rich.console import Console
|
|
6
|
+
from rich.layout import Layout
|
|
7
|
+
from rich.panel import Panel
|
|
8
|
+
from rich.table import Table
|
|
9
|
+
from rich.live import Live
|
|
10
|
+
from rich.text import Text
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def run_dashboard(refresh_rate: float = 1.0):
|
|
14
|
+
"""Run the interactive dashboard."""
|
|
15
|
+
console = Console()
|
|
16
|
+
|
|
17
|
+
layout = Layout()
|
|
18
|
+
layout.split_column(
|
|
19
|
+
Layout(name="header", size=3),
|
|
20
|
+
Layout(name="body"),
|
|
21
|
+
Layout(name="footer", size=3)
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
layout["header"].update(
|
|
25
|
+
Panel(
|
|
26
|
+
Text("Hanzo AI Dashboard", style="bold cyan", justify="center"),
|
|
27
|
+
border_style="cyan"
|
|
28
|
+
)
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
layout["body"].split_row(
|
|
32
|
+
Layout(name="left"),
|
|
33
|
+
Layout(name="right")
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
layout["left"].split_column(
|
|
37
|
+
Layout(name="cluster", size=10),
|
|
38
|
+
Layout(name="agents")
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
layout["right"].split_column(
|
|
42
|
+
Layout(name="jobs", size=15),
|
|
43
|
+
Layout(name="logs")
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
def get_cluster_panel() -> Panel:
|
|
47
|
+
"""Get cluster status panel."""
|
|
48
|
+
table = Table(show_header=False, box=None)
|
|
49
|
+
table.add_column("Key", style="cyan")
|
|
50
|
+
table.add_column("Value", style="white")
|
|
51
|
+
|
|
52
|
+
# Mock data - would be real in production
|
|
53
|
+
table.add_row("Status", "[green]Running[/green]")
|
|
54
|
+
table.add_row("Nodes", "3")
|
|
55
|
+
table.add_row("Models", "llama-3.2-3b, gpt-4")
|
|
56
|
+
table.add_row("Port", "8000")
|
|
57
|
+
|
|
58
|
+
return Panel(table, title="Cluster", border_style="green")
|
|
59
|
+
|
|
60
|
+
def get_agents_panel() -> Panel:
|
|
61
|
+
"""Get agents panel."""
|
|
62
|
+
table = Table()
|
|
63
|
+
table.add_column("ID", style="cyan")
|
|
64
|
+
table.add_column("Name", style="green")
|
|
65
|
+
table.add_column("Status", style="yellow")
|
|
66
|
+
table.add_column("Jobs", style="magenta")
|
|
67
|
+
|
|
68
|
+
# Mock data
|
|
69
|
+
table.add_row("a1b2", "researcher", "idle", "42")
|
|
70
|
+
table.add_row("c3d4", "coder", "busy", "17")
|
|
71
|
+
table.add_row("e5f6", "analyst", "idle", "23")
|
|
72
|
+
|
|
73
|
+
return Panel(table, title="Agents", border_style="blue")
|
|
74
|
+
|
|
75
|
+
def get_jobs_panel() -> Panel:
|
|
76
|
+
"""Get jobs panel."""
|
|
77
|
+
table = Table()
|
|
78
|
+
table.add_column("ID", style="cyan", width=8)
|
|
79
|
+
table.add_column("Type", style="green")
|
|
80
|
+
table.add_column("Status", style="yellow")
|
|
81
|
+
|
|
82
|
+
# Mock data
|
|
83
|
+
table.add_row("j001", "chat", "complete")
|
|
84
|
+
table.add_row("j002", "analysis", "running")
|
|
85
|
+
table.add_row("j003", "search", "queued")
|
|
86
|
+
|
|
87
|
+
return Panel(table, title="Recent Jobs", border_style="yellow")
|
|
88
|
+
|
|
89
|
+
def get_logs_panel() -> Panel:
|
|
90
|
+
"""Get logs panel."""
|
|
91
|
+
logs = """[dim]2024-01-20 10:15:23[/dim] Agent started: researcher
|
|
92
|
+
[dim]2024-01-20 10:15:24[/dim] Job j002 assigned to coder
|
|
93
|
+
[dim]2024-01-20 10:15:25[/dim] Model loaded: llama-3.2-3b
|
|
94
|
+
[dim]2024-01-20 10:15:26[/dim] Network peer connected: node-2
|
|
95
|
+
[dim]2024-01-20 10:15:27[/dim] Job j001 completed (2.3s)"""
|
|
96
|
+
|
|
97
|
+
return Panel(logs, title="Logs", border_style="dim")
|
|
98
|
+
|
|
99
|
+
layout["footer"].update(
|
|
100
|
+
Panel(
|
|
101
|
+
"[bold]Q[/bold] Quit [bold]R[/bold] Refresh [bold]C[/bold] Clear",
|
|
102
|
+
border_style="dim"
|
|
103
|
+
)
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
# Update panels
|
|
107
|
+
layout["cluster"].update(get_cluster_panel())
|
|
108
|
+
layout["agents"].update(get_agents_panel())
|
|
109
|
+
layout["jobs"].update(get_jobs_panel())
|
|
110
|
+
layout["logs"].update(get_logs_panel())
|
|
111
|
+
|
|
112
|
+
try:
|
|
113
|
+
with Live(layout, refresh_per_second=1/refresh_rate, screen=True):
|
|
114
|
+
while True:
|
|
115
|
+
import time
|
|
116
|
+
time.sleep(refresh_rate)
|
|
117
|
+
|
|
118
|
+
# Update dynamic panels
|
|
119
|
+
layout["cluster"].update(get_cluster_panel())
|
|
120
|
+
layout["agents"].update(get_agents_panel())
|
|
121
|
+
layout["jobs"].update(get_jobs_panel())
|
|
122
|
+
layout["logs"].update(get_logs_panel())
|
|
123
|
+
|
|
124
|
+
except KeyboardInterrupt:
|
|
125
|
+
console.print("\n[yellow]Dashboard closed[/yellow]")
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
"""Interactive REPL for Hanzo CLI."""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Dict, Any, Optional
|
|
6
|
+
|
|
7
|
+
from prompt_toolkit import PromptSession
|
|
8
|
+
from prompt_toolkit.history import FileHistory
|
|
9
|
+
from prompt_toolkit.auto_suggest import AutoSuggestFromHistory
|
|
10
|
+
from prompt_toolkit.completion import WordCompleter
|
|
11
|
+
from rich.console import Console
|
|
12
|
+
from rich.markdown import Markdown
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class HanzoREPL:
|
|
16
|
+
"""Interactive REPL for Hanzo CLI."""
|
|
17
|
+
|
|
18
|
+
def __init__(self, console: Optional[Console] = None):
|
|
19
|
+
self.console = console or Console()
|
|
20
|
+
self.session = PromptSession(
|
|
21
|
+
history=FileHistory(".hanzo_repl_history"),
|
|
22
|
+
auto_suggest=AutoSuggestFromHistory(),
|
|
23
|
+
)
|
|
24
|
+
self.commands = {
|
|
25
|
+
"help": self.show_help,
|
|
26
|
+
"exit": self.exit_repl,
|
|
27
|
+
"quit": self.exit_repl,
|
|
28
|
+
"clear": self.clear_screen,
|
|
29
|
+
"status": self.show_status,
|
|
30
|
+
}
|
|
31
|
+
self.running = False
|
|
32
|
+
|
|
33
|
+
async def run(self):
|
|
34
|
+
"""Run the REPL."""
|
|
35
|
+
self.running = True
|
|
36
|
+
# Don't print welcome message here since it's already printed in cli.py
|
|
37
|
+
|
|
38
|
+
# Set up command completer
|
|
39
|
+
completer = WordCompleter(
|
|
40
|
+
list(self.commands.keys()) + ["chat", "agent", "cluster", "mcp", "network"],
|
|
41
|
+
ignore_case=True
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
while self.running:
|
|
45
|
+
try:
|
|
46
|
+
# Get input
|
|
47
|
+
command = await self.session.prompt_async(
|
|
48
|
+
"hanzo> ",
|
|
49
|
+
completer=completer
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
if not command.strip():
|
|
53
|
+
continue
|
|
54
|
+
|
|
55
|
+
# Parse command
|
|
56
|
+
parts = command.strip().split(maxsplit=1)
|
|
57
|
+
cmd = parts[0].lower()
|
|
58
|
+
args = parts[1] if len(parts) > 1 else ""
|
|
59
|
+
|
|
60
|
+
# Execute command
|
|
61
|
+
if cmd in self.commands:
|
|
62
|
+
await self.commands[cmd](args)
|
|
63
|
+
else:
|
|
64
|
+
await self.execute_command(cmd, args)
|
|
65
|
+
|
|
66
|
+
except KeyboardInterrupt:
|
|
67
|
+
continue
|
|
68
|
+
except EOFError:
|
|
69
|
+
break
|
|
70
|
+
except Exception as e:
|
|
71
|
+
self.console.print(f"[red]Error: {e}[/red]")
|
|
72
|
+
|
|
73
|
+
async def show_help(self, args: str = ""):
|
|
74
|
+
"""Show help message."""
|
|
75
|
+
help_text = """
|
|
76
|
+
# Hanzo Interactive Mode
|
|
77
|
+
|
|
78
|
+
## Built-in Commands:
|
|
79
|
+
- `help` - Show this help message
|
|
80
|
+
- `exit/quit` - Exit interactive mode
|
|
81
|
+
- `clear` - Clear the screen
|
|
82
|
+
- `status` - Show system status
|
|
83
|
+
|
|
84
|
+
## CLI Commands:
|
|
85
|
+
All Hanzo CLI commands are available:
|
|
86
|
+
- `chat <message>` - Chat with AI
|
|
87
|
+
- `agent start` - Start an agent
|
|
88
|
+
- `cluster status` - Check cluster status
|
|
89
|
+
- `mcp tools` - List MCP tools
|
|
90
|
+
- `network agents` - List network agents
|
|
91
|
+
|
|
92
|
+
## Examples:
|
|
93
|
+
```
|
|
94
|
+
hanzo> chat How do I create a Python web server?
|
|
95
|
+
hanzo> agent list
|
|
96
|
+
hanzo> cluster start --models llama-3.2-3b
|
|
97
|
+
hanzo> mcp run read_file --arg path=README.md
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Tips:
|
|
101
|
+
- Use Tab for command completion
|
|
102
|
+
- Use ↑/↓ for command history
|
|
103
|
+
- Use Ctrl+R for reverse search
|
|
104
|
+
"""
|
|
105
|
+
self.console.print(Markdown(help_text))
|
|
106
|
+
|
|
107
|
+
def exit_repl(self, args: str = ""):
|
|
108
|
+
"""Exit the REPL."""
|
|
109
|
+
self.running = False
|
|
110
|
+
self.console.print("\n[yellow]Goodbye![/yellow]")
|
|
111
|
+
|
|
112
|
+
def clear_screen(self, args: str = ""):
|
|
113
|
+
"""Clear the screen."""
|
|
114
|
+
self.console.clear()
|
|
115
|
+
|
|
116
|
+
async def show_status(self, args: str = ""):
|
|
117
|
+
"""Show system status."""
|
|
118
|
+
status = {
|
|
119
|
+
"cluster": await self.check_cluster_status(),
|
|
120
|
+
"agents": await self.count_agents(),
|
|
121
|
+
"auth": self.check_auth_status(),
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
self.console.print("[cyan]System Status:[/cyan]")
|
|
125
|
+
self.console.print(f" Cluster: {status['cluster']}")
|
|
126
|
+
self.console.print(f" Agents: {status['agents']}")
|
|
127
|
+
self.console.print(f" Auth: {status['auth']}")
|
|
128
|
+
|
|
129
|
+
async def execute_command(self, cmd: str, args: str):
|
|
130
|
+
"""Execute a CLI command."""
|
|
131
|
+
# Import here to avoid circular imports
|
|
132
|
+
from .. import cli
|
|
133
|
+
import click
|
|
134
|
+
import sys
|
|
135
|
+
|
|
136
|
+
# Build command line
|
|
137
|
+
argv = [cmd]
|
|
138
|
+
if args:
|
|
139
|
+
import shlex
|
|
140
|
+
argv.extend(shlex.split(args))
|
|
141
|
+
|
|
142
|
+
# Create a new context
|
|
143
|
+
try:
|
|
144
|
+
# Save original argv
|
|
145
|
+
orig_argv = sys.argv
|
|
146
|
+
sys.argv = ["hanzo"] + argv
|
|
147
|
+
|
|
148
|
+
# Execute command
|
|
149
|
+
ctx = click.Context(cli.cli)
|
|
150
|
+
cli.cli.invoke(ctx)
|
|
151
|
+
|
|
152
|
+
except SystemExit:
|
|
153
|
+
# Catch exit from commands
|
|
154
|
+
pass
|
|
155
|
+
except Exception as e:
|
|
156
|
+
self.console.print(f"[red]Command error: {e}[/red]")
|
|
157
|
+
finally:
|
|
158
|
+
# Restore argv
|
|
159
|
+
sys.argv = orig_argv
|
|
160
|
+
|
|
161
|
+
async def check_cluster_status(self) -> str:
|
|
162
|
+
"""Check if cluster is running."""
|
|
163
|
+
try:
|
|
164
|
+
import httpx
|
|
165
|
+
async with httpx.AsyncClient() as client:
|
|
166
|
+
response = await client.get("http://localhost:8000/health", timeout=1.0)
|
|
167
|
+
return "running" if response.status_code == 200 else "not responding"
|
|
168
|
+
except:
|
|
169
|
+
return "not running"
|
|
170
|
+
|
|
171
|
+
async def count_agents(self) -> int:
|
|
172
|
+
"""Count running agents."""
|
|
173
|
+
# This would check actual agent status
|
|
174
|
+
return 0
|
|
175
|
+
|
|
176
|
+
def check_auth_status(self) -> str:
|
|
177
|
+
"""Check authentication status."""
|
|
178
|
+
import os
|
|
179
|
+
if os.environ.get("HANZO_API_KEY"):
|
|
180
|
+
return "authenticated (API key)"
|
|
181
|
+
elif (Path.home() / ".hanzo" / "auth.json").exists():
|
|
182
|
+
return "authenticated (saved)"
|
|
183
|
+
else:
|
|
184
|
+
return "not authenticated"
|
hanzo/router/__init__.py
CHANGED
|
@@ -1,19 +1,25 @@
|
|
|
1
|
-
"""Router module - re-exports from
|
|
1
|
+
"""Router module - re-exports from router package."""
|
|
2
2
|
|
|
3
3
|
try:
|
|
4
|
-
# Import
|
|
5
|
-
|
|
4
|
+
# Import directly from the installed router package
|
|
5
|
+
import router
|
|
6
6
|
from router import Router, completion, acompletion, embedding, aembedding
|
|
7
7
|
|
|
8
|
-
#
|
|
9
|
-
__all__ = ["Router", "completion", "acompletion", "embedding", "aembedding"]
|
|
8
|
+
# Re-export the entire router module
|
|
9
|
+
__all__ = ["router", "Router", "completion", "acompletion", "embedding", "aembedding"]
|
|
10
|
+
|
|
11
|
+
# Make router available as a submodule
|
|
12
|
+
import sys
|
|
13
|
+
sys.modules['hanzo.router'] = router
|
|
14
|
+
|
|
10
15
|
except ImportError as e:
|
|
11
|
-
# If
|
|
16
|
+
# If router is not installed, provide helpful error
|
|
12
17
|
import sys
|
|
13
18
|
print(f"Error importing router: {e}", file=sys.stderr)
|
|
14
|
-
print("Please install
|
|
19
|
+
print("Please install router from the main repository: pip install -e /Users/z/work/hanzo/router", file=sys.stderr)
|
|
15
20
|
|
|
16
21
|
# Define placeholders to avoid complete failure
|
|
22
|
+
router = None
|
|
17
23
|
Router = None
|
|
18
24
|
completion = None
|
|
19
25
|
acompletion = None
|
hanzo/utils/__init__.py
ADDED