hanzo 0.2.3__py3-none-any.whl → 0.2.6__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/commands/mcp.py ADDED
@@ -0,0 +1,235 @@
1
+ """MCP (Model Context Protocol) commands."""
2
+
3
+ import asyncio
4
+ import json
5
+ from typing import Dict, Any
6
+
7
+ import click
8
+ from rich.table import Table
9
+ from rich.syntax import Syntax
10
+
11
+ from ..utils.output import console, handle_errors
12
+
13
+
14
+ @click.group(name="mcp")
15
+ def mcp_group():
16
+ """Manage MCP servers and tools."""
17
+ pass
18
+
19
+
20
+ @mcp_group.command()
21
+ @click.option("--name", "-n", default="hanzo-mcp", help="Server name")
22
+ @click.option("--transport", "-t", type=click.Choice(["stdio", "sse"]), default="stdio", help="Transport protocol")
23
+ @click.option("--allow-path", "-p", multiple=True, help="Allowed paths")
24
+ @click.option("--enable-agent", is_flag=True, help="Enable agent tools")
25
+ @click.option("--host", default="127.0.0.1", help="Host for SSE transport")
26
+ @click.option("--port", default=3000, type=int, help="Port for SSE transport")
27
+ @click.pass_context
28
+ def serve(ctx, name: str, transport: str, allow_path: tuple, enable_agent: bool, host: str, port: int):
29
+ """Start MCP server."""
30
+ try:
31
+ from hanzoai.mcp import run_mcp_server
32
+ except ImportError:
33
+ console.print("[red]Error:[/red] hanzo-mcp not installed")
34
+ console.print("Install with: pip install hanzo[mcp]")
35
+ return
36
+
37
+ allowed_paths = list(allow_path) if allow_path else ["."]
38
+
39
+ console.print(f"[cyan]Starting MCP server[/cyan]")
40
+ console.print(f" Name: {name}")
41
+ console.print(f" Transport: {transport}")
42
+ console.print(f" Allowed paths: {', '.join(allowed_paths)}")
43
+
44
+ if transport == "sse":
45
+ console.print(f" Endpoint: http://{host}:{port}")
46
+
47
+ try:
48
+ run_mcp_server(
49
+ name=name,
50
+ transport=transport,
51
+ allowed_paths=allowed_paths,
52
+ enable_agent_tool=enable_agent,
53
+ host=host,
54
+ port=port
55
+ )
56
+ except KeyboardInterrupt:
57
+ console.print("\n[yellow]Server stopped[/yellow]")
58
+
59
+
60
+ @mcp_group.command()
61
+ @click.option("--category", "-c", help="Filter by category")
62
+ @click.pass_context
63
+ async def tools(ctx, category: str):
64
+ """List available MCP tools."""
65
+ try:
66
+ from hanzoai.mcp import create_server
67
+ except ImportError:
68
+ console.print("[red]Error:[/red] hanzo-mcp not installed")
69
+ console.print("Install with: pip install hanzo[mcp]")
70
+ return
71
+
72
+ with console.status("Loading tools..."):
73
+ server = create_server(enable_all_tools=True)
74
+ tools_list = await server.mcp.list_tools()
75
+
76
+ # Group by category if available
77
+ categories = {}
78
+ for tool in tools_list:
79
+ cat = getattr(tool, 'category', 'general')
80
+ if category and cat != category:
81
+ continue
82
+ if cat not in categories:
83
+ categories[cat] = []
84
+ categories[cat].append(tool)
85
+
86
+ # Display tools
87
+ for cat, tools in sorted(categories.items()):
88
+ table = Table(title=f"{cat.title()} Tools" if len(categories) > 1 else "MCP Tools")
89
+ table.add_column("Name", style="cyan", no_wrap=True)
90
+ table.add_column("Description")
91
+
92
+ for tool in sorted(tools, key=lambda t: t.name):
93
+ table.add_row(tool.name, tool.description)
94
+
95
+ console.print(table)
96
+ if len(categories) > 1:
97
+ console.print()
98
+
99
+
100
+ @mcp_group.command()
101
+ @click.argument("tool")
102
+ @click.option("--arg", "-a", multiple=True, help="Tool arguments (key=value)")
103
+ @click.option("--json-args", "-j", help="JSON arguments")
104
+ @click.pass_context
105
+ async def run(ctx, tool: str, arg: tuple, json_args: str):
106
+ """Run an MCP tool."""
107
+ try:
108
+ from hanzoai.mcp import create_server
109
+ except ImportError:
110
+ console.print("[red]Error:[/red] hanzo-mcp not installed")
111
+ console.print("Install with: pip install hanzo[mcp]")
112
+ return
113
+
114
+ # Parse arguments
115
+ args = {}
116
+
117
+ if json_args:
118
+ try:
119
+ args = json.loads(json_args)
120
+ except json.JSONDecodeError as e:
121
+ console.print(f"[red]Invalid JSON: {e}[/red]")
122
+ return
123
+ else:
124
+ for a in arg:
125
+ if "=" not in a:
126
+ console.print(f"[red]Invalid argument format: {a}[/red]")
127
+ console.print("Use: --arg key=value")
128
+ return
129
+ key, value = a.split("=", 1)
130
+
131
+ # Try to parse value as JSON first
132
+ try:
133
+ args[key] = json.loads(value)
134
+ except:
135
+ args[key] = value
136
+
137
+ # Create server and run tool
138
+ with console.status(f"Running tool '{tool}'..."):
139
+ server = create_server(enable_all_tools=True)
140
+
141
+ # Find tool
142
+ tools_list = await server.mcp.list_tools()
143
+ tool_obj = None
144
+ for t in tools_list:
145
+ if t.name == tool:
146
+ tool_obj = t
147
+ break
148
+
149
+ if not tool_obj:
150
+ console.print(f"[red]Tool not found: {tool}[/red]")
151
+ console.print("Use 'hanzo mcp tools' to list available tools")
152
+ return
153
+
154
+ # Run tool
155
+ try:
156
+ # Mock context for now
157
+ from mcp.server.fastmcp import Context
158
+ context = Context()
159
+
160
+ # Get tool function
161
+ tool_func = server.mcp._tool_map.get(tool)
162
+ if tool_func:
163
+ result = await tool_func(**args)
164
+ else:
165
+ console.print(f"[red]Tool function not found: {tool}[/red]")
166
+ return
167
+
168
+ except Exception as e:
169
+ console.print(f"[red]Tool error: {e}[/red]")
170
+ return
171
+
172
+ # Display result
173
+ if isinstance(result, str):
174
+ try:
175
+ # Try to parse as JSON for pretty printing
176
+ data = json.loads(result)
177
+ console.print_json(data=data)
178
+ except:
179
+ # Display as text
180
+ console.print(result)
181
+ else:
182
+ console.print(result)
183
+
184
+
185
+ @mcp_group.command()
186
+ @click.option("--path", "-p", default="~/.config/claude/claude_desktop_config.json", help="Config file path")
187
+ @click.pass_context
188
+ def install(ctx, path: str):
189
+ """Install MCP server in Claude Desktop."""
190
+ try:
191
+ from hanzoai.mcp import create_server
192
+ except ImportError:
193
+ console.print("[red]Error:[/red] hanzo-mcp not installed")
194
+ console.print("Install with: pip install hanzo[mcp]")
195
+ return
196
+
197
+ import os
198
+ import json
199
+ from pathlib import Path
200
+
201
+ config_path = Path(os.path.expanduser(path))
202
+
203
+ # Create config
204
+ config = {
205
+ "mcpServers": {
206
+ "hanzo-mcp": {
207
+ "command": "hanzo",
208
+ "args": ["mcp", "serve", "--transport", "stdio"]
209
+ }
210
+ }
211
+ }
212
+
213
+ # Check if file exists
214
+ if config_path.exists():
215
+ try:
216
+ with open(config_path, "r") as f:
217
+ existing = json.load(f)
218
+
219
+ if "mcpServers" not in existing:
220
+ existing["mcpServers"] = {}
221
+
222
+ existing["mcpServers"]["hanzo-mcp"] = config["mcpServers"]["hanzo-mcp"]
223
+ config = existing
224
+ except Exception as e:
225
+ console.print(f"[yellow]Warning: Could not read existing config: {e}[/yellow]")
226
+
227
+ # Write config
228
+ config_path.parent.mkdir(parents=True, exist_ok=True)
229
+
230
+ with open(config_path, "w") as f:
231
+ json.dump(config, f, indent=2)
232
+
233
+ console.print(f"[green]✓[/green] Installed hanzo-mcp in Claude Desktop")
234
+ console.print(f" Config: {config_path}")
235
+ console.print("\nRestart Claude Desktop for changes to take effect")
@@ -0,0 +1,323 @@
1
+ """Mining commands for distributed AI compute."""
2
+
3
+ import asyncio
4
+ from typing import Optional, List
5
+
6
+ import click
7
+ from rich.progress import Progress, SpinnerColumn, TextColumn
8
+ from rich.table import Table
9
+
10
+ from ..utils.output import console, handle_errors
11
+
12
+
13
+ @click.group(name="miner")
14
+ def miner_group():
15
+ """Manage Hanzo AI mining (contribute compute)."""
16
+ pass
17
+
18
+
19
+ @miner_group.command()
20
+ @click.option("--name", "-n", help="Miner name (auto-generated if not provided)")
21
+ @click.option("--wallet", "-w", help="Wallet address for rewards")
22
+ @click.option("--models", "-m", multiple=True, help="Models to support")
23
+ @click.option("--device", type=click.Choice(["cpu", "gpu", "auto"]), default="auto", help="Device to use")
24
+ @click.option("--network", default="mainnet", help="Network to join")
25
+ @click.option("--min-stake", type=float, help="Minimum stake requirement")
26
+ @click.pass_context
27
+ async def start(ctx, name: str, wallet: str, models: tuple, device: str, network: str, min_stake: float):
28
+ """Start mining (contribute compute to network)."""
29
+ try:
30
+ from hanzo_miner import HanzoMiner
31
+ except ImportError:
32
+ console.print("[red]Error:[/red] hanzo-miner not installed")
33
+ console.print("Install with: pip install hanzo[miner]")
34
+ return
35
+
36
+ # Check wallet
37
+ if not wallet:
38
+ console.print("[yellow]Warning:[/yellow] No wallet address provided")
39
+ console.print("You won't earn rewards without a wallet")
40
+ if not click.confirm("Continue without wallet?"):
41
+ return
42
+
43
+ miner = HanzoMiner(
44
+ name=name,
45
+ wallet=wallet,
46
+ device=device,
47
+ network=network,
48
+ min_stake=min_stake
49
+ )
50
+
51
+ with Progress(
52
+ SpinnerColumn(),
53
+ TextColumn("[progress.description]{task.description}"),
54
+ console=console,
55
+ ) as progress:
56
+ task = progress.add_task("Starting miner...", total=None)
57
+
58
+ try:
59
+ # Start miner
60
+ await miner.start(models=list(models) if models else None)
61
+ progress.update(task, completed=True)
62
+ except Exception as e:
63
+ progress.stop()
64
+ console.print(f"[red]Failed to start miner: {e}[/red]")
65
+ return
66
+
67
+ console.print(f"[green]✓[/green] Miner started")
68
+ console.print(f" Name: {miner.name}")
69
+ console.print(f" Network: {network}")
70
+ console.print(f" Device: {device}")
71
+ if wallet:
72
+ console.print(f" Wallet: {wallet[:8]}...{wallet[-4:]}")
73
+
74
+ # Show initial stats
75
+ stats = await miner.get_stats()
76
+ if stats:
77
+ console.print("\n[cyan]Initial Stats:[/cyan]")
78
+ console.print(f" Jobs completed: {stats.get('jobs_completed', 0)}")
79
+ console.print(f" Tokens earned: {stats.get('tokens_earned', 0)}")
80
+ console.print(f" Uptime: {stats.get('uptime', '0s')}")
81
+
82
+ console.print("\nPress Ctrl+C to stop mining\n")
83
+ console.print("[dim]Logs:[/dim]")
84
+
85
+ try:
86
+ # Stream logs and stats
87
+ async for event in miner.stream_events():
88
+ if event["type"] == "log":
89
+ console.print(event["message"], end="")
90
+ elif event["type"] == "job":
91
+ console.print(f"[green]Job completed:[/green] {event['job_id']} (+{event['tokens']} tokens)")
92
+ elif event["type"] == "error":
93
+ console.print(f"[red]Error:[/red] {event['message']}")
94
+ except KeyboardInterrupt:
95
+ console.print("\n[yellow]Stopping miner...[/yellow]")
96
+ await miner.stop()
97
+
98
+ # Show final stats
99
+ final_stats = await miner.get_stats()
100
+ if final_stats:
101
+ console.print("\n[cyan]Session Summary:[/cyan]")
102
+ console.print(f" Jobs completed: {final_stats.get('jobs_completed', 0)}")
103
+ console.print(f" Tokens earned: {final_stats.get('tokens_earned', 0)}")
104
+ console.print(f" Average job time: {final_stats.get('avg_job_time', 'N/A')}")
105
+
106
+ console.print("[green]✓[/green] Miner stopped")
107
+
108
+
109
+ @miner_group.command()
110
+ @click.option("--name", "-n", help="Miner name")
111
+ @click.pass_context
112
+ async def stop(ctx, name: str):
113
+ """Stop mining."""
114
+ try:
115
+ from hanzo_miner import HanzoMiner
116
+ except ImportError:
117
+ console.print("[red]Error:[/red] hanzo-miner not installed")
118
+ return
119
+
120
+ if name:
121
+ miner = HanzoMiner.get_by_name(name)
122
+ if miner:
123
+ console.print(f"[yellow]Stopping miner '{name}'...[/yellow]")
124
+ await miner.stop()
125
+ console.print(f"[green]✓[/green] Miner stopped")
126
+ else:
127
+ console.print(f"[red]Miner not found: {name}[/red]")
128
+ else:
129
+ # Stop all miners
130
+ miners = HanzoMiner.get_all()
131
+ if miners:
132
+ if click.confirm(f"Stop all {len(miners)} miners?"):
133
+ for miner in miners:
134
+ await miner.stop()
135
+ console.print(f"[green]✓[/green] Stopped {len(miners)} miners")
136
+ else:
137
+ console.print("[yellow]No miners running[/yellow]")
138
+
139
+
140
+ @miner_group.command()
141
+ @click.option("--name", "-n", help="Miner name")
142
+ @click.option("--detailed", "-d", is_flag=True, help="Show detailed stats")
143
+ @click.pass_context
144
+ async def status(ctx, name: str, detailed: bool):
145
+ """Show mining status."""
146
+ try:
147
+ from hanzo_miner import HanzoMiner
148
+ except ImportError:
149
+ console.print("[red]Error:[/red] hanzo-miner not installed")
150
+ return
151
+
152
+ if name:
153
+ # Show specific miner
154
+ miner = HanzoMiner.get_by_name(name)
155
+ if not miner:
156
+ console.print(f"[red]Miner not found: {name}[/red]")
157
+ return
158
+
159
+ miners = [miner]
160
+ else:
161
+ # Show all miners
162
+ miners = HanzoMiner.get_all()
163
+
164
+ if not miners:
165
+ console.print("[yellow]No miners running[/yellow]")
166
+ console.print("Start mining with: hanzo miner start")
167
+ return
168
+
169
+ # Create table
170
+ table = Table(title="Active Miners")
171
+ table.add_column("Name", style="cyan")
172
+ table.add_column("Status", style="green")
173
+ table.add_column("Device", style="yellow")
174
+ table.add_column("Jobs", style="blue")
175
+ table.add_column("Tokens", style="magenta")
176
+ table.add_column("Uptime", style="white")
177
+
178
+ for miner in miners:
179
+ stats = await miner.get_stats()
180
+ table.add_row(
181
+ miner.name,
182
+ stats.get("status", "unknown"),
183
+ stats.get("device", "unknown"),
184
+ str(stats.get("jobs_completed", 0)),
185
+ f"{stats.get('tokens_earned', 0):.2f}",
186
+ stats.get("uptime", "0s")
187
+ )
188
+
189
+ console.print(table)
190
+
191
+ if detailed and len(miners) == 1:
192
+ # Show detailed stats for single miner
193
+ miner = miners[0]
194
+ stats = await miner.get_stats()
195
+
196
+ console.print("\n[cyan]Detailed Statistics:[/cyan]")
197
+ console.print(f" Network: {stats.get('network', 'unknown')}")
198
+ console.print(f" Wallet: {stats.get('wallet', 'Not set')}")
199
+ console.print(f" Models: {', '.join(stats.get('models', []))}")
200
+ console.print(f" Memory: {stats.get('memory_used', 0)} / {stats.get('memory_total', 0)} MB")
201
+ console.print(f" CPU: {stats.get('cpu_percent', 0)}%")
202
+
203
+ if gpu := stats.get("gpu"):
204
+ console.print(f" GPU: {gpu['name']} ({gpu['memory_used']} / {gpu['memory_total']} MB)")
205
+
206
+ console.print(f"\n[cyan]Performance:[/cyan]")
207
+ console.print(f" Average job time: {stats.get('avg_job_time', 'N/A')}")
208
+ console.print(f" Success rate: {stats.get('success_rate', 0)}%")
209
+ console.print(f" Tokens/hour: {stats.get('tokens_per_hour', 0):.2f}")
210
+
211
+
212
+ @miner_group.command()
213
+ @click.option("--network", default="mainnet", help="Network to check")
214
+ @click.pass_context
215
+ async def leaderboard(ctx, network: str):
216
+ """Show mining leaderboard."""
217
+ try:
218
+ from hanzo_miner import get_leaderboard
219
+ except ImportError:
220
+ console.print("[red]Error:[/red] hanzo-miner not installed")
221
+ return
222
+
223
+ with console.status("Loading leaderboard..."):
224
+ try:
225
+ leaders = await get_leaderboard(network=network)
226
+ except Exception as e:
227
+ console.print(f"[red]Failed to load leaderboard: {e}[/red]")
228
+ return
229
+
230
+ if not leaders:
231
+ console.print("[yellow]No data available[/yellow]")
232
+ return
233
+
234
+ table = Table(title=f"Mining Leaderboard - {network}")
235
+ table.add_column("Rank", style="cyan")
236
+ table.add_column("Miner", style="green")
237
+ table.add_column("Jobs", style="yellow")
238
+ table.add_column("Tokens", style="magenta")
239
+ table.add_column("Success Rate", style="blue")
240
+
241
+ for i, leader in enumerate(leaders[:20], 1):
242
+ table.add_row(
243
+ str(i),
244
+ leader["name"],
245
+ str(leader["jobs"]),
246
+ f"{leader['tokens']:.2f}",
247
+ f"{leader['success_rate']}%"
248
+ )
249
+
250
+ console.print(table)
251
+
252
+
253
+ @miner_group.command()
254
+ @click.option("--wallet", "-w", required=True, help="Wallet address")
255
+ @click.option("--network", default="mainnet", help="Network")
256
+ @click.pass_context
257
+ async def earnings(ctx, wallet: str, network: str):
258
+ """Check mining earnings."""
259
+ try:
260
+ from hanzo_miner import check_earnings
261
+ except ImportError:
262
+ console.print("[red]Error:[/red] hanzo-miner not installed")
263
+ return
264
+
265
+ with console.status("Checking earnings..."):
266
+ try:
267
+ data = await check_earnings(wallet=wallet, network=network)
268
+ except Exception as e:
269
+ console.print(f"[red]Failed to check earnings: {e}[/red]")
270
+ return
271
+
272
+ console.print(f"[cyan]Earnings for {wallet[:8]}...{wallet[-4:]}[/cyan]")
273
+ console.print(f" Network: {network}")
274
+ console.print(f" Total earned: {data.get('total_earned', 0):.2f} tokens")
275
+ console.print(f" Available: {data.get('available', 0):.2f} tokens")
276
+ console.print(f" Pending: {data.get('pending', 0):.2f} tokens")
277
+
278
+ if history := data.get("recent_jobs"):
279
+ console.print("\n[cyan]Recent Jobs:[/cyan]")
280
+ for job in history[:5]:
281
+ console.print(f" • {job['timestamp']}: +{job['tokens']} tokens ({job['model']})")
282
+
283
+
284
+ @miner_group.command()
285
+ @click.argument("amount", type=float)
286
+ @click.option("--wallet", "-w", required=True, help="Wallet address")
287
+ @click.option("--to", required=True, help="Destination address")
288
+ @click.option("--network", default="mainnet", help="Network")
289
+ @click.pass_context
290
+ async def withdraw(ctx, amount: float, wallet: str, to: str, network: str):
291
+ """Withdraw mining earnings."""
292
+ try:
293
+ from hanzo_miner import withdraw_earnings
294
+ except ImportError:
295
+ console.print("[red]Error:[/red] hanzo-miner not installed")
296
+ return
297
+
298
+ # Confirm withdrawal
299
+ console.print(f"[yellow]Withdrawal Request:[/yellow]")
300
+ console.print(f" Amount: {amount} tokens")
301
+ console.print(f" From: {wallet[:8]}...{wallet[-4:]}")
302
+ console.print(f" To: {to[:8]}...{to[-4:]}")
303
+ console.print(f" Network: {network}")
304
+
305
+ if not click.confirm("Proceed with withdrawal?"):
306
+ return
307
+
308
+ with console.status("Processing withdrawal..."):
309
+ try:
310
+ result = await withdraw_earnings(
311
+ wallet=wallet,
312
+ amount=amount,
313
+ destination=to,
314
+ network=network
315
+ )
316
+
317
+ console.print(f"[green]✓[/green] Withdrawal successful")
318
+ console.print(f" Transaction: {result['tx_hash']}")
319
+ console.print(f" Amount: {result['amount']} tokens")
320
+ console.print(f" Fee: {result['fee']} tokens")
321
+
322
+ except Exception as e:
323
+ console.print(f"[red]Withdrawal failed: {e}[/red]")