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