hanzo 0.3.24__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/cli.py +13 -5
- hanzo/commands/auth.py +206 -266
- hanzo/commands/auth_broken.py +377 -0
- hanzo/commands/chat.py +3 -0
- hanzo/interactive/enhanced_repl.py +513 -0
- hanzo/interactive/repl.py +2 -2
- hanzo/ui/__init__.py +13 -0
- hanzo/ui/inline_startup.py +136 -0
- hanzo/ui/startup.py +350 -0
- {hanzo-0.3.24.dist-info → hanzo-0.3.25.dist-info}/METADATA +1 -1
- {hanzo-0.3.24.dist-info → hanzo-0.3.25.dist-info}/RECORD +14 -9
- {hanzo-0.3.24.dist-info → hanzo-0.3.25.dist-info}/WHEEL +0 -0
- {hanzo-0.3.24.dist-info → hanzo-0.3.25.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
"""Authentication commands."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import json
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from datetime import datetime
|
|
7
|
+
from typing import Optional
|
|
8
|
+
|
|
9
|
+
import click
|
|
10
|
+
from rich.prompt import Prompt
|
|
11
|
+
from rich.panel import Panel
|
|
12
|
+
from rich.table import Table
|
|
13
|
+
from rich import box
|
|
14
|
+
|
|
15
|
+
from ..utils.output import console
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class AuthManager:
|
|
19
|
+
"""Manage Hanzo authentication."""
|
|
20
|
+
|
|
21
|
+
def __init__(self):
|
|
22
|
+
self.config_dir = Path.home() / ".hanzo"
|
|
23
|
+
self.auth_file = self.config_dir / "auth.json"
|
|
24
|
+
|
|
25
|
+
def load_auth(self) -> dict:
|
|
26
|
+
"""Load authentication data."""
|
|
27
|
+
if self.auth_file.exists():
|
|
28
|
+
try:
|
|
29
|
+
return json.loads(self.auth_file.read_text())
|
|
30
|
+
except:
|
|
31
|
+
pass
|
|
32
|
+
return {}
|
|
33
|
+
|
|
34
|
+
def save_auth(self, auth: dict):
|
|
35
|
+
"""Save authentication data."""
|
|
36
|
+
self.config_dir.mkdir(exist_ok=True)
|
|
37
|
+
self.auth_file.write_text(json.dumps(auth, indent=2))
|
|
38
|
+
|
|
39
|
+
def is_authenticated(self) -> bool:
|
|
40
|
+
"""Check if authenticated."""
|
|
41
|
+
if os.getenv("HANZO_API_KEY"):
|
|
42
|
+
return True
|
|
43
|
+
auth = self.load_auth()
|
|
44
|
+
return bool(auth.get("api_key") or auth.get("logged_in"))
|
|
45
|
+
|
|
46
|
+
def get_api_key(self) -> Optional[str]:
|
|
47
|
+
"""Get API key."""
|
|
48
|
+
if os.getenv("HANZO_API_KEY"):
|
|
49
|
+
return os.getenv("HANZO_API_KEY")
|
|
50
|
+
auth = self.load_auth()
|
|
51
|
+
return auth.get("api_key")
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@click.group(name="auth")
|
|
55
|
+
def auth_group():
|
|
56
|
+
"""Manage Hanzo authentication."""
|
|
57
|
+
pass
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@auth_group.command()
|
|
61
|
+
@click.option("--email", "-e", help="Email address")
|
|
62
|
+
@click.option("--password", "-p", help="Password (not recommended, use prompt)")
|
|
63
|
+
@click.option("--api-key", "-k", help="API key for direct authentication")
|
|
64
|
+
@click.option("--sso", is_flag=True, help="Use SSO authentication")
|
|
65
|
+
@click.pass_context
|
|
66
|
+
async def login(ctx, email: str, password: str, api_key: str, sso: bool):
|
|
67
|
+
"""Login to Hanzo AI."""
|
|
68
|
+
try:
|
|
69
|
+
from hanzoai.auth import HanzoAuth
|
|
70
|
+
except ImportError:
|
|
71
|
+
console.print("[red]Error:[/red] hanzoai not installed")
|
|
72
|
+
console.print("Install with: pip install hanzo")
|
|
73
|
+
return
|
|
74
|
+
|
|
75
|
+
auth = HanzoAuth()
|
|
76
|
+
|
|
77
|
+
try:
|
|
78
|
+
if api_key:
|
|
79
|
+
# Direct API key authentication
|
|
80
|
+
console.print("Authenticating with API key...")
|
|
81
|
+
result = await auth.login_with_api_key(api_key)
|
|
82
|
+
elif sso:
|
|
83
|
+
# SSO authentication via browser
|
|
84
|
+
console.print("Opening browser for SSO login...")
|
|
85
|
+
console.print("If browser doesn't open, visit: https://iam.hanzo.ai/login")
|
|
86
|
+
result = await auth.login_with_sso()
|
|
87
|
+
else:
|
|
88
|
+
# Email/password authentication
|
|
89
|
+
if not email:
|
|
90
|
+
email = Prompt.ask("Email")
|
|
91
|
+
if not password:
|
|
92
|
+
password = Prompt.ask("Password", password=True)
|
|
93
|
+
|
|
94
|
+
console.print("Authenticating...")
|
|
95
|
+
result = await auth.login(email, password)
|
|
96
|
+
|
|
97
|
+
# Save credentials
|
|
98
|
+
config_dir = Path.home() / ".hanzo"
|
|
99
|
+
config_dir.mkdir(exist_ok=True)
|
|
100
|
+
|
|
101
|
+
config_file = config_dir / "auth.json"
|
|
102
|
+
await auth.save_credentials(config_file)
|
|
103
|
+
|
|
104
|
+
# Also set environment variable if API key is available
|
|
105
|
+
if api_key or result.get("api_key"):
|
|
106
|
+
key = api_key or result.get("api_key")
|
|
107
|
+
console.print(f"\n[dim]To use in environment:[/dim]")
|
|
108
|
+
console.print(f"export HANZO_API_KEY={key}")
|
|
109
|
+
|
|
110
|
+
console.print(f"[green]✓[/green] Logged in as {result.get('email', 'user')}")
|
|
111
|
+
|
|
112
|
+
# Check organization
|
|
113
|
+
if org := result.get("organization"):
|
|
114
|
+
console.print(f" Organization: {org}")
|
|
115
|
+
|
|
116
|
+
# Check permissions
|
|
117
|
+
if permissions := result.get("permissions"):
|
|
118
|
+
console.print(f" Permissions: {', '.join(permissions)}")
|
|
119
|
+
|
|
120
|
+
except Exception as e:
|
|
121
|
+
console.print(f"[red]Login failed: {e}[/red]")
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
@auth_group.command()
|
|
125
|
+
@click.pass_context
|
|
126
|
+
async def logout(ctx):
|
|
127
|
+
"""Logout from Hanzo AI."""
|
|
128
|
+
auth_mgr = AuthManager()
|
|
129
|
+
|
|
130
|
+
if not auth_mgr.is_authenticated():
|
|
131
|
+
console.print("[yellow]Not logged in[/yellow]")
|
|
132
|
+
return
|
|
133
|
+
|
|
134
|
+
try:
|
|
135
|
+
# Try using hanzoai if available
|
|
136
|
+
try:
|
|
137
|
+
from hanzoai.auth import HanzoAuth
|
|
138
|
+
hanzo_auth = HanzoAuth()
|
|
139
|
+
await hanzo_auth.logout()
|
|
140
|
+
except ImportError:
|
|
141
|
+
pass # hanzoai not installed, just clear local auth
|
|
142
|
+
|
|
143
|
+
# Clear local auth
|
|
144
|
+
auth_mgr.save_auth({})
|
|
145
|
+
|
|
146
|
+
console.print("[green]✓[/green] Logged out successfully")
|
|
147
|
+
|
|
148
|
+
except Exception as e:
|
|
149
|
+
console.print(f"[red]Logout failed: {e}[/red]")
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
@auth_group.command()
|
|
153
|
+
@click.pass_context
|
|
154
|
+
async def status(ctx):
|
|
155
|
+
"""Show authentication status."""
|
|
156
|
+
auth_mgr = AuthManager()
|
|
157
|
+
|
|
158
|
+
# Create status table
|
|
159
|
+
table = Table(title="Authentication Status", box=box.ROUNDED)
|
|
160
|
+
table.add_column("Property", style="cyan")
|
|
161
|
+
table.add_column("Value", style="white")
|
|
162
|
+
|
|
163
|
+
if auth_mgr.is_authenticated():
|
|
164
|
+
auth = auth_mgr.load_auth()
|
|
165
|
+
|
|
166
|
+
table.add_row("Status", "✅ Authenticated")
|
|
167
|
+
|
|
168
|
+
# Show auth method
|
|
169
|
+
if os.getenv("HANZO_API_KEY"):
|
|
170
|
+
table.add_row("Method", "Environment Variable")
|
|
171
|
+
api_key = os.getenv("HANZO_API_KEY")
|
|
172
|
+
table.add_row("API Key", f"{api_key[:8]}...{api_key[-4:]}")
|
|
173
|
+
elif auth.get("api_key"):
|
|
174
|
+
table.add_row("Method", "API Key")
|
|
175
|
+
table.add_row("API Key", f"{auth['api_key'][:8]}...")
|
|
176
|
+
elif auth.get("email"):
|
|
177
|
+
console.print(f" Email: {email}")
|
|
178
|
+
if org := creds.get("organization"):
|
|
179
|
+
console.print(f" Organization: {org}")
|
|
180
|
+
|
|
181
|
+
# Verify credentials are still valid
|
|
182
|
+
with console.status("Verifying credentials..."):
|
|
183
|
+
try:
|
|
184
|
+
user_info = await auth.get_user_info()
|
|
185
|
+
console.print("[green]✓[/green] Credentials are valid")
|
|
186
|
+
|
|
187
|
+
# Show usage stats if available
|
|
188
|
+
if usage := user_info.get("usage"):
|
|
189
|
+
console.print("\n[cyan]Usage:[/cyan]")
|
|
190
|
+
console.print(f" API calls: {usage.get('api_calls', 0)}")
|
|
191
|
+
console.print(f" Tokens: {usage.get('tokens', 0)}")
|
|
192
|
+
if quota := usage.get("quota"):
|
|
193
|
+
console.print(
|
|
194
|
+
f" Quota: {usage.get('tokens', 0)} / {quota}"
|
|
195
|
+
)
|
|
196
|
+
except Exception:
|
|
197
|
+
console.print("[yellow]![/yellow] Credentials may be expired")
|
|
198
|
+
console.print("Run 'hanzo auth login' to refresh")
|
|
199
|
+
|
|
200
|
+
except Exception as e:
|
|
201
|
+
console.print(f"[red]Error reading credentials: {e}[/red]")
|
|
202
|
+
else:
|
|
203
|
+
console.print("[yellow]![/yellow] Not logged in")
|
|
204
|
+
console.print("Run 'hanzo auth login' to authenticate")
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
@auth_group.command()
|
|
208
|
+
@click.option("--name", "-n", required=True, help="API key name")
|
|
209
|
+
@click.option(
|
|
210
|
+
"--permissions", "-p", multiple=True, help="Permissions (e.g., read, write, admin)"
|
|
211
|
+
)
|
|
212
|
+
@click.option("--expires", "-e", help="Expiration (e.g., 30d, 1y, never)")
|
|
213
|
+
@click.pass_context
|
|
214
|
+
async def create_key(ctx, name: str, permissions: tuple, expires: str):
|
|
215
|
+
"""Create a new API key."""
|
|
216
|
+
try:
|
|
217
|
+
from hanzoai.auth import HanzoAuth
|
|
218
|
+
except ImportError:
|
|
219
|
+
console.print("[red]Error:[/red] hanzoai not installed")
|
|
220
|
+
return
|
|
221
|
+
|
|
222
|
+
auth = HanzoAuth()
|
|
223
|
+
|
|
224
|
+
# Ensure authenticated
|
|
225
|
+
if not await auth.is_authenticated():
|
|
226
|
+
console.print("[red]Error:[/red] Not authenticated")
|
|
227
|
+
console.print("Run 'hanzo auth login' first")
|
|
228
|
+
return
|
|
229
|
+
|
|
230
|
+
with console.status(f"Creating API key '{name}'..."):
|
|
231
|
+
try:
|
|
232
|
+
result = await auth.create_api_key(
|
|
233
|
+
name=name,
|
|
234
|
+
permissions=list(permissions) if permissions else None,
|
|
235
|
+
expires=expires,
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
key = result.get("key")
|
|
239
|
+
console.print(f"[green]✓[/green] Created API key: {name}")
|
|
240
|
+
console.print(
|
|
241
|
+
f"\n[yellow]Save this key - it won't be shown again:[/yellow]"
|
|
242
|
+
)
|
|
243
|
+
console.print(f"{key}")
|
|
244
|
+
console.print(f"\nTo use:")
|
|
245
|
+
console.print(f"export HANZO_API_KEY={key}")
|
|
246
|
+
|
|
247
|
+
except Exception as e:
|
|
248
|
+
console.print(f"[red]Failed to create key: {e}[/red]")
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
@auth_group.command()
|
|
252
|
+
@click.pass_context
|
|
253
|
+
async def list_keys(ctx):
|
|
254
|
+
"""List your API keys."""
|
|
255
|
+
try:
|
|
256
|
+
from hanzoai.auth import HanzoAuth
|
|
257
|
+
except ImportError:
|
|
258
|
+
console.print("[red]Error:[/red] hanzoai not installed")
|
|
259
|
+
return
|
|
260
|
+
|
|
261
|
+
auth = HanzoAuth()
|
|
262
|
+
|
|
263
|
+
# Ensure authenticated
|
|
264
|
+
if not await auth.is_authenticated():
|
|
265
|
+
console.print("[red]Error:[/red] Not authenticated")
|
|
266
|
+
console.print("Run 'hanzo auth login' first")
|
|
267
|
+
return
|
|
268
|
+
|
|
269
|
+
with console.status("Loading API keys..."):
|
|
270
|
+
try:
|
|
271
|
+
keys = await auth.list_api_keys()
|
|
272
|
+
|
|
273
|
+
if keys:
|
|
274
|
+
from rich.table import Table
|
|
275
|
+
|
|
276
|
+
table = Table(title="API Keys")
|
|
277
|
+
table.add_column("Name", style="cyan")
|
|
278
|
+
table.add_column("Created", style="green")
|
|
279
|
+
table.add_column("Last Used", style="yellow")
|
|
280
|
+
table.add_column("Permissions", style="blue")
|
|
281
|
+
table.add_column("Status", style="magenta")
|
|
282
|
+
|
|
283
|
+
for key in keys:
|
|
284
|
+
table.add_row(
|
|
285
|
+
key.get("name", "unknown"),
|
|
286
|
+
key.get("created_at", "unknown"),
|
|
287
|
+
key.get("last_used", "never"),
|
|
288
|
+
", ".join(key.get("permissions", [])),
|
|
289
|
+
key.get("status", "active"),
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
console.print(table)
|
|
293
|
+
else:
|
|
294
|
+
console.print("[yellow]No API keys found[/yellow]")
|
|
295
|
+
console.print("Create one with: hanzo auth create-key")
|
|
296
|
+
|
|
297
|
+
except Exception as e:
|
|
298
|
+
console.print(f"[red]Failed to list keys: {e}[/red]")
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
@auth_group.command()
|
|
302
|
+
@click.argument("name")
|
|
303
|
+
@click.pass_context
|
|
304
|
+
async def revoke_key(ctx, name: str):
|
|
305
|
+
"""Revoke an API key."""
|
|
306
|
+
try:
|
|
307
|
+
from hanzoai.auth import HanzoAuth
|
|
308
|
+
except ImportError:
|
|
309
|
+
console.print("[red]Error:[/red] hanzoai not installed")
|
|
310
|
+
return
|
|
311
|
+
|
|
312
|
+
auth = HanzoAuth()
|
|
313
|
+
|
|
314
|
+
# Ensure authenticated
|
|
315
|
+
if not await auth.is_authenticated():
|
|
316
|
+
console.print("[red]Error:[/red] Not authenticated")
|
|
317
|
+
console.print("Run 'hanzo auth login' first")
|
|
318
|
+
return
|
|
319
|
+
|
|
320
|
+
if click.confirm(f"Revoke API key '{name}'?"):
|
|
321
|
+
with console.status(f"Revoking key '{name}'..."):
|
|
322
|
+
try:
|
|
323
|
+
await auth.revoke_api_key(name)
|
|
324
|
+
console.print(f"[green]✓[/green] Revoked API key: {name}")
|
|
325
|
+
except Exception as e:
|
|
326
|
+
console.print(f"[red]Failed to revoke key: {e}[/red]")
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
@auth_group.command()
|
|
330
|
+
@click.pass_context
|
|
331
|
+
async def whoami(ctx):
|
|
332
|
+
"""Show current user information."""
|
|
333
|
+
try:
|
|
334
|
+
from hanzoai.auth import HanzoAuth
|
|
335
|
+
except ImportError:
|
|
336
|
+
console.print("[red]Error:[/red] hanzoai not installed")
|
|
337
|
+
return
|
|
338
|
+
|
|
339
|
+
auth = HanzoAuth()
|
|
340
|
+
|
|
341
|
+
# Check if authenticated
|
|
342
|
+
if not await auth.is_authenticated():
|
|
343
|
+
console.print("[yellow]Not authenticated[/yellow]")
|
|
344
|
+
|
|
345
|
+
# Check if API key is set
|
|
346
|
+
if os.environ.get("HANZO_API_KEY"):
|
|
347
|
+
console.print("HANZO_API_KEY is set but may be invalid")
|
|
348
|
+
else:
|
|
349
|
+
console.print("Run 'hanzo auth login' to authenticate")
|
|
350
|
+
return
|
|
351
|
+
|
|
352
|
+
with console.status("Loading user information..."):
|
|
353
|
+
try:
|
|
354
|
+
user = await auth.get_user_info()
|
|
355
|
+
|
|
356
|
+
console.print(f"[cyan]User Information:[/cyan]")
|
|
357
|
+
console.print(f" ID: {user.get('id', 'unknown')}")
|
|
358
|
+
console.print(f" Email: {user.get('email', 'unknown')}")
|
|
359
|
+
console.print(f" Name: {user.get('name', 'unknown')}")
|
|
360
|
+
|
|
361
|
+
if org := user.get("organization"):
|
|
362
|
+
console.print(f"\n[cyan]Organization:[/cyan]")
|
|
363
|
+
console.print(f" Name: {org.get('name', 'unknown')}")
|
|
364
|
+
console.print(f" Role: {org.get('role', 'member')}")
|
|
365
|
+
|
|
366
|
+
if teams := user.get("teams"):
|
|
367
|
+
console.print(f"\n[cyan]Teams:[/cyan]")
|
|
368
|
+
for team in teams:
|
|
369
|
+
console.print(f" • {team}")
|
|
370
|
+
|
|
371
|
+
if perms := user.get("permissions"):
|
|
372
|
+
console.print(f"\n[cyan]Permissions:[/cyan]")
|
|
373
|
+
for perm in sorted(perms):
|
|
374
|
+
console.print(f" • {perm}")
|
|
375
|
+
|
|
376
|
+
except Exception as e:
|
|
377
|
+
console.print(f"[red]Error: {e}[/red]")
|
hanzo/commands/chat.py
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
"""Chat command for interactive AI conversations."""
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
|
+
import os
|
|
4
5
|
from typing import Optional
|
|
5
6
|
|
|
6
7
|
import click
|
|
7
8
|
import httpx
|
|
8
9
|
from rich.markdown import Markdown
|
|
10
|
+
from rich.panel import Panel
|
|
11
|
+
from rich import box
|
|
9
12
|
|
|
10
13
|
from ..utils.output import console
|
|
11
14
|
|