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/commands/auth.py ADDED
@@ -0,0 +1,324 @@
1
+ """Authentication commands."""
2
+
3
+ import asyncio
4
+ import os
5
+ from pathlib import Path
6
+ from typing import Optional
7
+
8
+ import click
9
+ import httpx
10
+ from rich.prompt import Prompt
11
+
12
+ from ..utils.output import console, handle_errors
13
+
14
+
15
+ @click.group(name="auth")
16
+ def auth_group():
17
+ """Manage Hanzo authentication."""
18
+ pass
19
+
20
+
21
+ @auth_group.command()
22
+ @click.option("--email", "-e", help="Email address")
23
+ @click.option("--password", "-p", help="Password (not recommended, use prompt)")
24
+ @click.option("--api-key", "-k", help="API key for direct authentication")
25
+ @click.option("--sso", is_flag=True, help="Use SSO authentication")
26
+ @click.pass_context
27
+ async def login(ctx, email: str, password: str, api_key: str, sso: bool):
28
+ """Login to Hanzo AI."""
29
+ try:
30
+ from hanzoai.auth import HanzoAuth
31
+ except ImportError:
32
+ console.print("[red]Error:[/red] hanzoai not installed")
33
+ console.print("Install with: pip install hanzo")
34
+ return
35
+
36
+ auth = HanzoAuth()
37
+
38
+ try:
39
+ if api_key:
40
+ # Direct API key authentication
41
+ console.print("Authenticating with API key...")
42
+ result = await auth.login_with_api_key(api_key)
43
+ elif sso:
44
+ # SSO authentication via browser
45
+ console.print("Opening browser for SSO login...")
46
+ console.print("If browser doesn't open, visit: https://iam.hanzo.ai/login")
47
+ result = await auth.login_with_sso()
48
+ else:
49
+ # Email/password authentication
50
+ if not email:
51
+ email = Prompt.ask("Email")
52
+ if not password:
53
+ password = Prompt.ask("Password", password=True)
54
+
55
+ console.print("Authenticating...")
56
+ result = await auth.login(email, password)
57
+
58
+ # Save credentials
59
+ config_dir = Path.home() / ".hanzo"
60
+ config_dir.mkdir(exist_ok=True)
61
+
62
+ config_file = config_dir / "auth.json"
63
+ await auth.save_credentials(config_file)
64
+
65
+ # Also set environment variable if API key is available
66
+ if api_key or result.get("api_key"):
67
+ key = api_key or result.get("api_key")
68
+ console.print(f"\n[dim]To use in environment:[/dim]")
69
+ console.print(f"export HANZO_API_KEY={key}")
70
+
71
+ console.print(f"[green]✓[/green] Logged in as {result.get('email', 'user')}")
72
+
73
+ # Check organization
74
+ if org := result.get("organization"):
75
+ console.print(f" Organization: {org}")
76
+
77
+ # Check permissions
78
+ if permissions := result.get("permissions"):
79
+ console.print(f" Permissions: {', '.join(permissions)}")
80
+
81
+ except Exception as e:
82
+ console.print(f"[red]Login failed: {e}[/red]")
83
+
84
+
85
+ @auth_group.command()
86
+ @click.pass_context
87
+ async def logout(ctx):
88
+ """Logout from Hanzo AI."""
89
+ try:
90
+ from hanzoai.auth import HanzoAuth
91
+ except ImportError:
92
+ console.print("[red]Error:[/red] hanzoai not installed")
93
+ return
94
+
95
+ auth = HanzoAuth()
96
+
97
+ try:
98
+ await auth.logout()
99
+
100
+ # Remove saved credentials
101
+ config_file = Path.home() / ".hanzo" / "auth.json"
102
+ if config_file.exists():
103
+ config_file.unlink()
104
+
105
+ console.print("[green]✓[/green] Logged out successfully")
106
+
107
+ except Exception as e:
108
+ console.print(f"[red]Logout failed: {e}[/red]")
109
+
110
+
111
+ @auth_group.command()
112
+ @click.pass_context
113
+ async def status(ctx):
114
+ """Show authentication status."""
115
+ # Check environment variable
116
+ if api_key := os.environ.get("HANZO_API_KEY"):
117
+ console.print("[green]✓[/green] HANZO_API_KEY environment variable set")
118
+ console.print(f" Key: {api_key[:8]}...{api_key[-4:]}")
119
+
120
+ # Check saved credentials
121
+ config_file = Path.home() / ".hanzo" / "auth.json"
122
+ if config_file.exists():
123
+ try:
124
+ from hanzoai.auth import HanzoAuth
125
+ auth = HanzoAuth()
126
+ creds = await auth.load_credentials(config_file)
127
+
128
+ console.print("[green]✓[/green] Saved credentials found")
129
+ if email := creds.get("email"):
130
+ console.print(f" Email: {email}")
131
+ if org := creds.get("organization"):
132
+ console.print(f" Organization: {org}")
133
+
134
+ # Verify credentials are still valid
135
+ with console.status("Verifying credentials..."):
136
+ try:
137
+ user_info = await auth.get_user_info()
138
+ console.print("[green]✓[/green] Credentials are valid")
139
+
140
+ # Show usage stats if available
141
+ if usage := user_info.get("usage"):
142
+ console.print("\n[cyan]Usage:[/cyan]")
143
+ console.print(f" API calls: {usage.get('api_calls', 0)}")
144
+ console.print(f" Tokens: {usage.get('tokens', 0)}")
145
+ if quota := usage.get('quota'):
146
+ console.print(f" Quota: {usage.get('tokens', 0)} / {quota}")
147
+ except:
148
+ console.print("[yellow]![/yellow] Credentials may be expired")
149
+ console.print("Run 'hanzo auth login' to refresh")
150
+
151
+ except Exception as e:
152
+ console.print(f"[red]Error reading credentials: {e}[/red]")
153
+ else:
154
+ console.print("[yellow]![/yellow] Not logged in")
155
+ console.print("Run 'hanzo auth login' to authenticate")
156
+
157
+
158
+ @auth_group.command()
159
+ @click.option("--name", "-n", required=True, help="API key name")
160
+ @click.option("--permissions", "-p", multiple=True, help="Permissions (e.g., read, write, admin)")
161
+ @click.option("--expires", "-e", help="Expiration (e.g., 30d, 1y, never)")
162
+ @click.pass_context
163
+ async def create_key(ctx, name: str, permissions: tuple, expires: str):
164
+ """Create a new API key."""
165
+ try:
166
+ from hanzoai.auth import HanzoAuth
167
+ except ImportError:
168
+ console.print("[red]Error:[/red] hanzoai not installed")
169
+ return
170
+
171
+ auth = HanzoAuth()
172
+
173
+ # Ensure authenticated
174
+ if not await auth.is_authenticated():
175
+ console.print("[red]Error:[/red] Not authenticated")
176
+ console.print("Run 'hanzo auth login' first")
177
+ return
178
+
179
+ with console.status(f"Creating API key '{name}'..."):
180
+ try:
181
+ result = await auth.create_api_key(
182
+ name=name,
183
+ permissions=list(permissions) if permissions else None,
184
+ expires=expires
185
+ )
186
+
187
+ key = result.get("key")
188
+ console.print(f"[green]✓[/green] Created API key: {name}")
189
+ console.print(f"\n[yellow]Save this key - it won't be shown again:[/yellow]")
190
+ console.print(f"{key}")
191
+ console.print(f"\nTo use:")
192
+ console.print(f"export HANZO_API_KEY={key}")
193
+
194
+ except Exception as e:
195
+ console.print(f"[red]Failed to create key: {e}[/red]")
196
+
197
+
198
+ @auth_group.command()
199
+ @click.pass_context
200
+ async def list_keys(ctx):
201
+ """List your API keys."""
202
+ try:
203
+ from hanzoai.auth import HanzoAuth
204
+ except ImportError:
205
+ console.print("[red]Error:[/red] hanzoai not installed")
206
+ return
207
+
208
+ auth = HanzoAuth()
209
+
210
+ # Ensure authenticated
211
+ if not await auth.is_authenticated():
212
+ console.print("[red]Error:[/red] Not authenticated")
213
+ console.print("Run 'hanzo auth login' first")
214
+ return
215
+
216
+ with console.status("Loading API keys..."):
217
+ try:
218
+ keys = await auth.list_api_keys()
219
+
220
+ if keys:
221
+ from rich.table import Table
222
+
223
+ table = Table(title="API Keys")
224
+ table.add_column("Name", style="cyan")
225
+ table.add_column("Created", style="green")
226
+ table.add_column("Last Used", style="yellow")
227
+ table.add_column("Permissions", style="blue")
228
+ table.add_column("Status", style="magenta")
229
+
230
+ for key in keys:
231
+ table.add_row(
232
+ key.get("name", "unknown"),
233
+ key.get("created_at", "unknown"),
234
+ key.get("last_used", "never"),
235
+ ", ".join(key.get("permissions", [])),
236
+ key.get("status", "active")
237
+ )
238
+
239
+ console.print(table)
240
+ else:
241
+ console.print("[yellow]No API keys found[/yellow]")
242
+ console.print("Create one with: hanzo auth create-key")
243
+
244
+ except Exception as e:
245
+ console.print(f"[red]Failed to list keys: {e}[/red]")
246
+
247
+
248
+ @auth_group.command()
249
+ @click.argument("name")
250
+ @click.pass_context
251
+ async def revoke_key(ctx, name: str):
252
+ """Revoke an API key."""
253
+ try:
254
+ from hanzoai.auth import HanzoAuth
255
+ except ImportError:
256
+ console.print("[red]Error:[/red] hanzoai not installed")
257
+ return
258
+
259
+ auth = HanzoAuth()
260
+
261
+ # Ensure authenticated
262
+ if not await auth.is_authenticated():
263
+ console.print("[red]Error:[/red] Not authenticated")
264
+ console.print("Run 'hanzo auth login' first")
265
+ return
266
+
267
+ if click.confirm(f"Revoke API key '{name}'?"):
268
+ with console.status(f"Revoking key '{name}'..."):
269
+ try:
270
+ await auth.revoke_api_key(name)
271
+ console.print(f"[green]✓[/green] Revoked API key: {name}")
272
+ except Exception as e:
273
+ console.print(f"[red]Failed to revoke key: {e}[/red]")
274
+
275
+
276
+ @auth_group.command()
277
+ @click.pass_context
278
+ async def whoami(ctx):
279
+ """Show current user information."""
280
+ try:
281
+ from hanzoai.auth import HanzoAuth
282
+ except ImportError:
283
+ console.print("[red]Error:[/red] hanzoai not installed")
284
+ return
285
+
286
+ auth = HanzoAuth()
287
+
288
+ # Check if authenticated
289
+ if not await auth.is_authenticated():
290
+ console.print("[yellow]Not authenticated[/yellow]")
291
+
292
+ # Check if API key is set
293
+ if os.environ.get("HANZO_API_KEY"):
294
+ console.print("HANZO_API_KEY is set but may be invalid")
295
+ else:
296
+ console.print("Run 'hanzo auth login' to authenticate")
297
+ return
298
+
299
+ with console.status("Loading user information..."):
300
+ try:
301
+ user = await auth.get_user_info()
302
+
303
+ console.print(f"[cyan]User Information:[/cyan]")
304
+ console.print(f" ID: {user.get('id', 'unknown')}")
305
+ console.print(f" Email: {user.get('email', 'unknown')}")
306
+ console.print(f" Name: {user.get('name', 'unknown')}")
307
+
308
+ if org := user.get('organization'):
309
+ console.print(f"\n[cyan]Organization:[/cyan]")
310
+ console.print(f" Name: {org.get('name', 'unknown')}")
311
+ console.print(f" Role: {org.get('role', 'member')}")
312
+
313
+ if teams := user.get('teams'):
314
+ console.print(f"\n[cyan]Teams:[/cyan]")
315
+ for team in teams:
316
+ console.print(f" • {team}")
317
+
318
+ if perms := user.get('permissions'):
319
+ console.print(f"\n[cyan]Permissions:[/cyan]")
320
+ for perm in sorted(perms):
321
+ console.print(f" • {perm}")
322
+
323
+ except Exception as e:
324
+ console.print(f"[red]Error: {e}[/red]")
hanzo/commands/chat.py ADDED
@@ -0,0 +1,183 @@
1
+ """Chat command for interactive AI conversations."""
2
+
3
+ import asyncio
4
+ from typing import Optional
5
+
6
+ import click
7
+ import httpx
8
+ from rich.markdown import Markdown
9
+
10
+ from ..utils.output import console, handle_errors
11
+
12
+
13
+ @click.command(name="chat")
14
+ @click.option("--model", "-m", default="llama-3.2-3b", help="Model to use")
15
+ @click.option("--local/--cloud", default=True, help="Use local or cloud model")
16
+ @click.option("--once", is_flag=True, help="Single question mode")
17
+ @click.option("--system", "-s", help="System prompt")
18
+ @click.option("--repl", is_flag=True, help="Start full REPL interface (like Claude Code)")
19
+ @click.option("--ipython", is_flag=True, help="Use IPython REPL interface")
20
+ @click.option("--tui", is_flag=True, help="Use beautiful TUI interface")
21
+ @click.argument("prompt", nargs=-1)
22
+ @click.pass_context
23
+ def chat_command(ctx, model: str, local: bool, once: bool, system: Optional[str], repl: bool, ipython: bool, tui: bool, prompt: tuple):
24
+ """Interactive AI chat."""
25
+ # Check if REPL mode requested
26
+ if repl or ipython or tui:
27
+ try:
28
+ import sys
29
+ import os
30
+
31
+ # Set up environment
32
+ if model:
33
+ os.environ["HANZO_DEFAULT_MODEL"] = model
34
+ if local:
35
+ os.environ["HANZO_USE_LOCAL"] = "true"
36
+ if system:
37
+ os.environ["HANZO_SYSTEM_PROMPT"] = system
38
+
39
+ if ipython:
40
+ from hanzo_repl.ipython_repl import main
41
+ sys.exit(main())
42
+ elif tui:
43
+ from hanzo_repl.textual_repl import main
44
+ sys.exit(main())
45
+ else:
46
+ from hanzo_repl.cli import main
47
+ sys.exit(main())
48
+ except ImportError:
49
+ console.print("[red]Error:[/red] hanzo-repl not installed")
50
+ console.print("Install with: pip install hanzo[repl]")
51
+ console.print("\nAlternatively:")
52
+ console.print(" pip install hanzo-repl")
53
+ return
54
+
55
+ prompt_text = " ".join(prompt) if prompt else None
56
+
57
+ if once or prompt_text:
58
+ # Single question mode
59
+ asyncio.run(ask_once(ctx, prompt_text or "Hello", model, local, system))
60
+ else:
61
+ # Interactive chat
62
+ asyncio.run(interactive_chat(ctx, model, local, system))
63
+
64
+
65
+ async def ask_once(ctx, prompt: str, model: str, local: bool, system: Optional[str] = None):
66
+ """Ask a single question."""
67
+ messages = []
68
+ if system:
69
+ messages.append({"role": "system", "content": system})
70
+ messages.append({"role": "user", "content": prompt})
71
+
72
+ try:
73
+ if local:
74
+ # Use local cluster
75
+ base_url = "http://localhost:8000"
76
+
77
+ # Check if cluster is running
78
+ try:
79
+ async with httpx.AsyncClient() as client:
80
+ await client.get(f"{base_url}/health")
81
+ except httpx.ConnectError:
82
+ console.print("[yellow]Local cluster not running. Start with: hanzo serve[/yellow]")
83
+ return
84
+
85
+ # Make request to local cluster
86
+ async with httpx.AsyncClient() as client:
87
+ response = await client.post(
88
+ f"{base_url}/v1/chat/completions",
89
+ json={
90
+ "model": model,
91
+ "messages": messages,
92
+ "stream": False
93
+ }
94
+ )
95
+ response.raise_for_status()
96
+ result = response.json()
97
+ content = result["choices"][0]["message"]["content"]
98
+ else:
99
+ # Use cloud API
100
+ try:
101
+ from hanzoai import completion
102
+ result = completion(
103
+ model=f"anthropic/{model}" if "claude" in model else model,
104
+ messages=messages
105
+ )
106
+ content = result.choices[0].message.content
107
+ except ImportError:
108
+ console.print("[red]Error:[/red] hanzoai not installed")
109
+ console.print("Install with: pip install hanzo[all]")
110
+ return
111
+
112
+ # Display response
113
+ if ctx.obj.get("json"):
114
+ console.print_json(data={"response": content})
115
+ else:
116
+ console.print(Markdown(content))
117
+
118
+ except Exception as e:
119
+ console.print(f"[red]Error: {e}[/red]")
120
+
121
+
122
+ async def interactive_chat(ctx, model: str, local: bool, system: Optional[str]):
123
+ """Run interactive chat session."""
124
+ from prompt_toolkit import PromptSession
125
+ from prompt_toolkit.history import FileHistory
126
+
127
+ console.print(f"[cyan]Chat session started[/cyan] (model: {model}, mode: {'local' if local else 'cloud'})")
128
+ console.print("Type 'exit' or Ctrl+D to quit\n")
129
+
130
+ session = PromptSession(history=FileHistory(".hanzo_chat_history"))
131
+ messages = []
132
+
133
+ if system:
134
+ messages.append({"role": "system", "content": system})
135
+
136
+ while True:
137
+ try:
138
+ # Get user input
139
+ user_input = await session.prompt_async("You: ")
140
+
141
+ if user_input.lower() in ["exit", "quit"]:
142
+ break
143
+
144
+ # Add to messages
145
+ messages.append({"role": "user", "content": user_input})
146
+
147
+ # Get response
148
+ console.print("AI: ", end="")
149
+ with console.status(""):
150
+ if local:
151
+ # Use local cluster
152
+ async with httpx.AsyncClient() as client:
153
+ response = await client.post(
154
+ "http://localhost:8000/v1/chat/completions",
155
+ json={
156
+ "model": model,
157
+ "messages": messages,
158
+ "stream": False
159
+ }
160
+ )
161
+ response.raise_for_status()
162
+ result = response.json()
163
+ content = result["choices"][0]["message"]["content"]
164
+ else:
165
+ # Use cloud API
166
+ from hanzoai import completion
167
+ result = completion(
168
+ model=f"anthropic/{model}" if "claude" in model else model,
169
+ messages=messages
170
+ )
171
+ content = result.choices[0].message.content
172
+
173
+ # Display and save response
174
+ console.print(Markdown(content))
175
+ messages.append({"role": "assistant", "content": content})
176
+ console.print()
177
+
178
+ except KeyboardInterrupt:
179
+ continue
180
+ except EOFError:
181
+ break
182
+ except Exception as e:
183
+ console.print(f"\n[red]Error: {e}[/red]\n")