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