hanzo 0.3.22__py3-none-any.whl → 0.3.23__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/dev.py CHANGED
@@ -661,10 +661,11 @@ class HanzoDevREPL:
661
661
  "help": self.cmd_help,
662
662
  "exit": self.cmd_exit,
663
663
  }
664
-
664
+
665
665
  # Initialize memory manager
666
666
  from .memory_manager import MemoryManager
667
- workspace = getattr(orchestrator, 'workspace_dir', '/tmp/hanzo')
667
+
668
+ workspace = getattr(orchestrator, "workspace_dir", "/tmp/hanzo")
668
669
  self.memory_manager = MemoryManager(workspace)
669
670
 
670
671
  async def run(self):
@@ -676,34 +677,42 @@ class HanzoDevREPL:
676
677
  from rich.console import Group
677
678
  from prompt_toolkit import prompt
678
679
  from prompt_toolkit.styles import Style
679
-
680
+
680
681
  # Define Claude-like style for prompt_toolkit
681
- claude_style = Style.from_dict({
682
- '': '#333333', # Default text color
683
- 'prompt': '#666666', # Gray prompt arrow
684
- })
685
-
682
+ claude_style = Style.from_dict(
683
+ {
684
+ "": "#333333", # Default text color
685
+ "prompt": "#666666", # Gray prompt arrow
686
+ }
687
+ )
688
+
686
689
  # Use a predefined box style that's similar to Claude
687
690
  from rich.box import ROUNDED
691
+
688
692
  LIGHT_GRAY_BOX = ROUNDED
689
-
693
+
690
694
  # Header
691
695
  console.print()
692
- console.print(Panel(
693
- "[bold cyan]Hanzo Dev - AI Chat[/bold cyan]\n"
694
- "[dim]Chat naturally or use /commands • Type /help for available commands[/dim]",
695
- box=LIGHT_GRAY_BOX,
696
- style="dim white",
697
- padding=(0, 1)
698
- ))
696
+ console.print(
697
+ Panel(
698
+ "[bold cyan]Hanzo Dev - AI Chat[/bold cyan]\n"
699
+ "[dim]Chat naturally or use /commands • Type /help for available commands[/dim]",
700
+ box=LIGHT_GRAY_BOX,
701
+ style="dim white",
702
+ padding=(0, 1),
703
+ )
704
+ )
699
705
  console.print()
700
-
706
+
701
707
  # Check for available API keys and show status
702
708
  from .fallback_handler import FallbackHandler
709
+
703
710
  handler = FallbackHandler()
704
711
  if not handler.fallback_order:
705
712
  console.print("[yellow]⚠️ No API keys detected[/yellow]")
706
- console.print("[dim]Set OPENAI_API_KEY or ANTHROPIC_API_KEY to enable AI[/dim]")
713
+ console.print(
714
+ "[dim]Set OPENAI_API_KEY or ANTHROPIC_API_KEY to enable AI[/dim]"
715
+ )
707
716
  console.print()
708
717
  else:
709
718
  primary = handler.fallback_order[0][1]
@@ -718,10 +727,10 @@ class HanzoDevREPL:
718
727
  user_input = await asyncio.get_event_loop().run_in_executor(
719
728
  None,
720
729
  input,
721
- '' # Clean prompt
730
+ "", # Clean prompt
722
731
  )
723
732
  console.print() # Add spacing after input
724
-
733
+
725
734
  except EOFError:
726
735
  console.print() # New line before exit
727
736
  break
@@ -738,20 +747,25 @@ class HanzoDevREPL:
738
747
  parts = user_input[1:].strip().split(maxsplit=1)
739
748
  cmd = parts[0].lower()
740
749
  args = parts[1] if len(parts) > 1 else ""
741
-
750
+
742
751
  if cmd in self.commands:
743
752
  await self.commands[cmd](args)
744
753
  else:
745
754
  console.print(f"[yellow]Unknown command: /{cmd}[/yellow]")
746
755
  console.print("Type /help for available commands")
747
-
756
+
748
757
  elif user_input.startswith("#"):
749
758
  # Handle memory/context commands
750
759
  from .memory_manager import handle_memory_command
751
- handled = handle_memory_command(user_input, self.memory_manager, console)
760
+
761
+ handled = handle_memory_command(
762
+ user_input, self.memory_manager, console
763
+ )
752
764
  if not handled:
753
- console.print("[yellow]Unknown memory command. Use #memory help[/yellow]")
754
-
765
+ console.print(
766
+ "[yellow]Unknown memory command. Use #memory help[/yellow]"
767
+ )
768
+
755
769
  else:
756
770
  # Natural chat - send directly to AI agents
757
771
  await self.chat_with_agents(user_input)
@@ -911,132 +925,183 @@ Examples:
911
925
  self.orchestrator.shutdown()
912
926
  console.print("[green]Goodbye![/green]")
913
927
  sys.exit(0)
914
-
928
+
915
929
  async def chat_with_agents(self, message: str):
916
930
  """Send message to AI agents for natural chat."""
917
931
  try:
918
932
  # Add message to memory
919
933
  self.memory_manager.add_message("user", message)
920
-
934
+
921
935
  # Get memory context
922
936
  memory_context = self.memory_manager.summarize_for_ai()
923
-
937
+
924
938
  # Enhance message with context
925
939
  if memory_context:
926
940
  enhanced_message = f"{memory_context}\n\nUser: {message}"
927
941
  else:
928
942
  enhanced_message = message
929
-
943
+
930
944
  # Try smart fallback if no specific model configured
931
- if not hasattr(self.orchestrator, 'orchestrator_model') or \
932
- self.orchestrator.orchestrator_model == "auto":
945
+ if (
946
+ not hasattr(self.orchestrator, "orchestrator_model")
947
+ or self.orchestrator.orchestrator_model == "auto"
948
+ ):
933
949
  # Use streaming if available
934
950
  from .streaming import stream_with_fallback
951
+
935
952
  response = await stream_with_fallback(enhanced_message, console)
936
-
953
+
937
954
  if response:
938
955
  # Save AI response to memory
939
956
  self.memory_manager.add_message("assistant", response)
940
957
  # Response already displayed by streaming handler
941
958
  return
942
959
  else:
943
- console.print("[red]No AI options available. Please configure API keys or install tools.[/red]")
960
+ console.print(
961
+ "[red]No AI options available. Please configure API keys or install tools.[/red]"
962
+ )
944
963
  return
945
-
964
+
946
965
  # For codex and other CLI tools, go straight to direct API chat
947
- if hasattr(self.orchestrator, 'orchestrator_model'):
966
+ if hasattr(self.orchestrator, "orchestrator_model"):
948
967
  model = self.orchestrator.orchestrator_model
949
- if model in ["codex", "openai-cli", "openai-codex", "claude", "claude-code",
950
- "claude-desktop", "gemini", "gemini-cli", "google-gemini",
951
- "hanzo-ide", "hanzo-dev-ide", "ide", "codestral", "codestral-free",
952
- "free", "mistral-free", "starcoder", "starcoder2", "free-starcoder"] or \
953
- model.startswith("local:"):
968
+ if model in [
969
+ "codex",
970
+ "openai-cli",
971
+ "openai-codex",
972
+ "claude",
973
+ "claude-code",
974
+ "claude-desktop",
975
+ "gemini",
976
+ "gemini-cli",
977
+ "google-gemini",
978
+ "hanzo-ide",
979
+ "hanzo-dev-ide",
980
+ "ide",
981
+ "codestral",
982
+ "codestral-free",
983
+ "free",
984
+ "mistral-free",
985
+ "starcoder",
986
+ "starcoder2",
987
+ "free-starcoder",
988
+ ] or model.startswith("local:"):
954
989
  # Use direct API/CLI chat for these models
955
990
  await self._direct_api_chat(message)
956
991
  return
957
-
992
+
958
993
  # Show thinking indicator for network orchestrators
959
994
  console.print("[dim]Thinking...[/dim]")
960
-
995
+
961
996
  # Check if we have a network orchestrator with actual AI
962
- if hasattr(self.orchestrator, 'execute_with_network'):
997
+ if hasattr(self.orchestrator, "execute_with_network"):
963
998
  # Use the network orchestrator (GPT-4, GPT-5, etc.)
964
999
  result = await self.orchestrator.execute_with_network(
965
- task=message,
966
- context={"mode": "chat", "interactive": True}
1000
+ task=message, context={"mode": "chat", "interactive": True}
967
1001
  )
968
-
1002
+
969
1003
  if result.get("output"):
970
1004
  # Display AI response in a styled panel
971
1005
  console.print()
972
1006
  from rich.panel import Panel
973
- console.print(Panel(
974
- result['output'],
975
- title="[bold cyan]AI Response[/bold cyan]",
976
- title_align="left",
977
- border_style="dim cyan",
978
- padding=(1, 2)
979
- ))
1007
+
1008
+ console.print(
1009
+ Panel(
1010
+ result["output"],
1011
+ title="[bold cyan]AI Response[/bold cyan]",
1012
+ title_align="left",
1013
+ border_style="dim cyan",
1014
+ padding=(1, 2),
1015
+ )
1016
+ )
980
1017
  elif result.get("error"):
981
1018
  console.print(f"\n[red]Error:[/red] {result['error']}")
982
1019
  else:
983
1020
  console.print("\n[yellow]No response from agent[/yellow]")
984
-
985
- elif hasattr(self.orchestrator, 'execute_with_critique'):
1021
+
1022
+ elif hasattr(self.orchestrator, "execute_with_critique"):
986
1023
  # Use multi-Claude orchestrator - but now it will use real AI!
987
1024
  result = await self.orchestrator.execute_with_critique(message)
988
-
1025
+
989
1026
  if result.get("output"):
990
1027
  # Display AI response in a styled panel
991
1028
  console.print()
992
1029
  from rich.panel import Panel
993
- console.print(Panel(
994
- result['output'],
995
- title="[bold cyan]AI Response[/bold cyan]",
996
- title_align="left",
997
- border_style="dim cyan",
998
- padding=(1, 2)
999
- ))
1030
+
1031
+ console.print(
1032
+ Panel(
1033
+ result["output"],
1034
+ title="[bold cyan]AI Response[/bold cyan]",
1035
+ title_align="left",
1036
+ border_style="dim cyan",
1037
+ padding=(1, 2),
1038
+ )
1039
+ )
1000
1040
  else:
1001
1041
  console.print("\n[yellow]No response from agent[/yellow]")
1002
-
1042
+
1003
1043
  else:
1004
1044
  # Fallback to direct API call if available
1005
1045
  await self._direct_api_chat(message)
1006
-
1046
+
1007
1047
  except Exception as e:
1008
1048
  console.print(f"[red]Error connecting to AI: {e}[/red]")
1009
1049
  console.print("[yellow]Make sure you have API keys configured:[/yellow]")
1010
1050
  console.print(" • OPENAI_API_KEY for GPT models")
1011
1051
  console.print(" • ANTHROPIC_API_KEY for Claude")
1012
1052
  console.print(" • Or use --orchestrator local:llama3.2 for local models")
1013
-
1053
+
1014
1054
  async def _direct_api_chat(self, message: str):
1015
1055
  """Direct API chat fallback when network orchestrator isn't available."""
1016
1056
  import os
1017
-
1057
+
1018
1058
  # Check for CLI tools and free/local options first
1019
- if self.orchestrator.orchestrator_model in ["codex", "openai-cli", "openai-codex"]:
1059
+ if self.orchestrator.orchestrator_model in [
1060
+ "codex",
1061
+ "openai-cli",
1062
+ "openai-codex",
1063
+ ]:
1020
1064
  # Use OpenAI CLI (Codex)
1021
1065
  await self._use_openai_cli(message)
1022
1066
  return
1023
- elif self.orchestrator.orchestrator_model in ["claude", "claude-code", "claude-desktop"]:
1067
+ elif self.orchestrator.orchestrator_model in [
1068
+ "claude",
1069
+ "claude-code",
1070
+ "claude-desktop",
1071
+ ]:
1024
1072
  # Use Claude Desktop/Code
1025
1073
  await self._use_claude_cli(message)
1026
1074
  return
1027
- elif self.orchestrator.orchestrator_model in ["gemini", "gemini-cli", "google-gemini"]:
1075
+ elif self.orchestrator.orchestrator_model in [
1076
+ "gemini",
1077
+ "gemini-cli",
1078
+ "google-gemini",
1079
+ ]:
1028
1080
  # Use Gemini CLI
1029
1081
  await self._use_gemini_cli(message)
1030
1082
  return
1031
- elif self.orchestrator.orchestrator_model in ["hanzo-ide", "hanzo-dev-ide", "ide"]:
1083
+ elif self.orchestrator.orchestrator_model in [
1084
+ "hanzo-ide",
1085
+ "hanzo-dev-ide",
1086
+ "ide",
1087
+ ]:
1032
1088
  # Use Hanzo Dev IDE from ~/work/hanzo/ide
1033
1089
  await self._use_hanzo_ide(message)
1034
1090
  return
1035
- elif self.orchestrator.orchestrator_model in ["codestral", "codestral-free", "free", "mistral-free"]:
1091
+ elif self.orchestrator.orchestrator_model in [
1092
+ "codestral",
1093
+ "codestral-free",
1094
+ "free",
1095
+ "mistral-free",
1096
+ ]:
1036
1097
  # Use free Mistral Codestral API
1037
1098
  await self._use_free_codestral(message)
1038
1099
  return
1039
- elif self.orchestrator.orchestrator_model in ["starcoder", "starcoder2", "free-starcoder"]:
1100
+ elif self.orchestrator.orchestrator_model in [
1101
+ "starcoder",
1102
+ "starcoder2",
1103
+ "free-starcoder",
1104
+ ]:
1040
1105
  # Use free StarCoder via HuggingFace
1041
1106
  await self._use_free_starcoder(message)
1042
1107
  return
@@ -1044,92 +1109,117 @@ Examples:
1044
1109
  # Use local model via Ollama or LM Studio
1045
1110
  await self._use_local_model(message)
1046
1111
  return
1047
-
1112
+
1048
1113
  # Use the fallback handler to intelligently try available options
1049
1114
  from .fallback_handler import smart_chat
1115
+
1050
1116
  response = await smart_chat(message, console=console)
1051
-
1117
+
1052
1118
  if response:
1053
1119
  from rich.panel import Panel
1120
+
1054
1121
  console.print()
1055
- console.print(Panel(
1056
- response,
1057
- title="[bold cyan]AI Response[/bold cyan]",
1058
- title_align="left",
1059
- border_style="dim cyan",
1060
- padding=(1, 2)
1061
- ))
1122
+ console.print(
1123
+ Panel(
1124
+ response,
1125
+ title="[bold cyan]AI Response[/bold cyan]",
1126
+ title_align="left",
1127
+ border_style="dim cyan",
1128
+ padding=(1, 2),
1129
+ )
1130
+ )
1062
1131
  return
1063
-
1132
+
1064
1133
  # Try OpenAI first explicitly (in case fallback handler missed it)
1065
1134
  openai_key = os.environ.get("OPENAI_API_KEY") or os.getenv("OPENAI_API_KEY")
1066
1135
  if openai_key:
1067
1136
  try:
1068
1137
  from openai import AsyncOpenAI
1069
-
1138
+
1070
1139
  client = AsyncOpenAI()
1071
1140
  response = await client.chat.completions.create(
1072
1141
  model=self.orchestrator.orchestrator_model or "gpt-4",
1073
1142
  messages=[
1074
- {"role": "system", "content": "You are a helpful AI coding assistant."},
1075
- {"role": "user", "content": message}
1143
+ {
1144
+ "role": "system",
1145
+ "content": "You are a helpful AI coding assistant.",
1146
+ },
1147
+ {"role": "user", "content": message},
1076
1148
  ],
1077
1149
  temperature=0.7,
1078
- max_tokens=2000
1150
+ max_tokens=2000,
1079
1151
  )
1080
-
1152
+
1081
1153
  if response.choices:
1082
1154
  from rich.panel import Panel
1155
+
1083
1156
  console.print()
1084
- console.print(Panel(
1085
- response.choices[0].message.content,
1086
- title="[bold cyan]GPT-4[/bold cyan]",
1087
- title_align="left",
1088
- border_style="dim cyan",
1089
- padding=(1, 2)
1090
- ))
1157
+ console.print(
1158
+ Panel(
1159
+ response.choices[0].message.content,
1160
+ title="[bold cyan]GPT-4[/bold cyan]",
1161
+ title_align="left",
1162
+ border_style="dim cyan",
1163
+ padding=(1, 2),
1164
+ )
1165
+ )
1091
1166
  return
1092
-
1167
+
1093
1168
  except Exception as e:
1094
1169
  console.print(f"[yellow]OpenAI error: {e}[/yellow]")
1095
-
1170
+
1096
1171
  # Try Anthropic
1097
1172
  if os.getenv("ANTHROPIC_API_KEY"):
1098
1173
  try:
1099
1174
  from anthropic import AsyncAnthropic
1100
-
1175
+
1101
1176
  client = AsyncAnthropic()
1102
1177
  response = await client.messages.create(
1103
1178
  model="claude-3-5-sonnet-20241022",
1104
1179
  messages=[{"role": "user", "content": message}],
1105
- max_tokens=2000
1180
+ max_tokens=2000,
1106
1181
  )
1107
-
1182
+
1108
1183
  if response.content:
1109
1184
  from rich.panel import Panel
1185
+
1110
1186
  console.print()
1111
- console.print(Panel(
1112
- response.content[0].text,
1113
- title="[bold cyan]Claude[/bold cyan]",
1114
- title_align="left",
1115
- border_style="dim cyan",
1116
- padding=(1, 2)
1117
- ))
1187
+ console.print(
1188
+ Panel(
1189
+ response.content[0].text,
1190
+ title="[bold cyan]Claude[/bold cyan]",
1191
+ title_align="left",
1192
+ border_style="dim cyan",
1193
+ padding=(1, 2),
1194
+ )
1195
+ )
1118
1196
  return
1119
-
1197
+
1120
1198
  except Exception as e:
1121
1199
  console.print(f"[yellow]Anthropic error: {e}[/yellow]")
1122
-
1200
+
1123
1201
  # No API keys available
1124
1202
  console.print("[red]No AI API keys configured![/red]")
1125
- console.print("[yellow]Try these options that don't need your API key:[/yellow]")
1203
+ console.print(
1204
+ "[yellow]Try these options that don't need your API key:[/yellow]"
1205
+ )
1126
1206
  console.print("\n[bold]CLI Tools (use existing tools):[/bold]")
1127
- console.print(" • hanzo dev --orchestrator codex # OpenAI CLI (if installed)")
1128
- console.print(" • hanzo dev --orchestrator claude # Claude Desktop (if installed)")
1129
- console.print(" • hanzo dev --orchestrator gemini # Gemini CLI (if installed)")
1130
- console.print(" • hanzo dev --orchestrator hanzo-ide # Hanzo IDE from ~/work/hanzo/ide")
1207
+ console.print(
1208
+ " • hanzo dev --orchestrator codex # OpenAI CLI (if installed)"
1209
+ )
1210
+ console.print(
1211
+ " • hanzo dev --orchestrator claude # Claude Desktop (if installed)"
1212
+ )
1213
+ console.print(
1214
+ " • hanzo dev --orchestrator gemini # Gemini CLI (if installed)"
1215
+ )
1216
+ console.print(
1217
+ " • hanzo dev --orchestrator hanzo-ide # Hanzo IDE from ~/work/hanzo/ide"
1218
+ )
1131
1219
  console.print("\n[bold]Free APIs (rate limited):[/bold]")
1132
- console.print(" • hanzo dev --orchestrator codestral # Free Mistral Codestral")
1220
+ console.print(
1221
+ " • hanzo dev --orchestrator codestral # Free Mistral Codestral"
1222
+ )
1133
1223
  console.print(" • hanzo dev --orchestrator starcoder # Free StarCoder")
1134
1224
  console.print("\n[bold]Local Models (unlimited):[/bold]")
1135
1225
  console.print(" • hanzo dev --orchestrator local:llama3.2 # Via Ollama")
@@ -1138,14 +1228,14 @@ Examples:
1138
1228
  console.print("\n[dim]Or set API keys for full access:[/dim]")
1139
1229
  console.print(" • export OPENAI_API_KEY=sk-...")
1140
1230
  console.print(" • export ANTHROPIC_API_KEY=sk-ant-...")
1141
-
1231
+
1142
1232
  async def _use_free_codestral(self, message: str):
1143
1233
  """Use free Mistral Codestral API (no API key needed for trial)."""
1144
1234
  try:
1145
1235
  import httpx
1146
-
1236
+
1147
1237
  console.print("[dim]Using free Codestral API (rate limited)...[/dim]")
1148
-
1238
+
1149
1239
  async with httpx.AsyncClient() as client:
1150
1240
  # Mistral offers free tier with rate limits
1151
1241
  response = await client.post(
@@ -1157,37 +1247,46 @@ Examples:
1157
1247
  json={
1158
1248
  "model": "codestral-latest",
1159
1249
  "messages": [
1160
- {"role": "system", "content": "You are Codestral, an AI coding assistant."},
1161
- {"role": "user", "content": message}
1250
+ {
1251
+ "role": "system",
1252
+ "content": "You are Codestral, an AI coding assistant.",
1253
+ },
1254
+ {"role": "user", "content": message},
1162
1255
  ],
1163
1256
  "temperature": 0.7,
1164
- "max_tokens": 2000
1257
+ "max_tokens": 2000,
1165
1258
  },
1166
- timeout=30.0
1259
+ timeout=30.0,
1167
1260
  )
1168
-
1261
+
1169
1262
  if response.status_code == 200:
1170
1263
  data = response.json()
1171
1264
  if data.get("choices"):
1172
- console.print(f"[cyan]Codestral:[/cyan] {data['choices'][0]['message']['content']}")
1265
+ console.print(
1266
+ f"[cyan]Codestral:[/cyan] {data['choices'][0]['message']['content']}"
1267
+ )
1173
1268
  else:
1174
- console.print("[yellow]Free tier limit reached. Try local models instead:[/yellow]")
1175
- console.print(" Install Ollama: curl -fsSL https://ollama.com/install.sh | sh")
1269
+ console.print(
1270
+ "[yellow]Free tier limit reached. Try local models instead:[/yellow]"
1271
+ )
1272
+ console.print(
1273
+ " • Install Ollama: curl -fsSL https://ollama.com/install.sh | sh"
1274
+ )
1176
1275
  console.print(" • Run: ollama pull codellama")
1177
1276
  console.print(" • Use: hanzo dev --orchestrator local:codellama")
1178
-
1277
+
1179
1278
  except Exception as e:
1180
1279
  console.print(f"[red]Codestral error: {e}[/red]")
1181
1280
  console.print("[yellow]Try local models instead (no limits):[/yellow]")
1182
1281
  console.print(" • hanzo dev --orchestrator local:codellama")
1183
-
1282
+
1184
1283
  async def _use_free_starcoder(self, message: str):
1185
1284
  """Use free StarCoder via HuggingFace Inference API."""
1186
1285
  try:
1187
1286
  import httpx
1188
-
1287
+
1189
1288
  console.print("[dim]Using free StarCoder API...[/dim]")
1190
-
1289
+
1191
1290
  async with httpx.AsyncClient() as client:
1192
1291
  # HuggingFace offers free inference API
1193
1292
  response = await client.post(
@@ -1200,33 +1299,37 @@ Examples:
1200
1299
  "parameters": {
1201
1300
  "temperature": 0.7,
1202
1301
  "max_new_tokens": 2000,
1203
- "return_full_text": False
1204
- }
1302
+ "return_full_text": False,
1303
+ },
1205
1304
  },
1206
- timeout=30.0
1305
+ timeout=30.0,
1207
1306
  )
1208
-
1307
+
1209
1308
  if response.status_code == 200:
1210
1309
  data = response.json()
1211
1310
  if isinstance(data, list) and data:
1212
- console.print(f"[cyan]StarCoder:[/cyan] {data[0].get('generated_text', '')}")
1311
+ console.print(
1312
+ f"[cyan]StarCoder:[/cyan] {data[0].get('generated_text', '')}"
1313
+ )
1213
1314
  else:
1214
- console.print("[yellow]API limit reached. Install local models:[/yellow]")
1315
+ console.print(
1316
+ "[yellow]API limit reached. Install local models:[/yellow]"
1317
+ )
1215
1318
  console.print(" • brew install ollama")
1216
1319
  console.print(" • ollama pull starcoder2")
1217
1320
  console.print(" • hanzo dev --orchestrator local:starcoder2")
1218
-
1321
+
1219
1322
  except Exception as e:
1220
1323
  console.print(f"[red]StarCoder error: {e}[/red]")
1221
-
1324
+
1222
1325
  async def _use_openai_cli(self, message: str):
1223
1326
  """Use OpenAI CLI (Codex) - the official OpenAI CLI tool."""
1224
1327
  try:
1225
1328
  import json
1226
1329
  import subprocess
1227
-
1330
+
1228
1331
  console.print("[dim]Using OpenAI CLI (Codex)...[/dim]")
1229
-
1332
+
1230
1333
  # Check if openai CLI is installed
1231
1334
  result = subprocess.run(["which", "openai"], capture_output=True, text=True)
1232
1335
  if result.returncode != 0:
@@ -1236,37 +1339,42 @@ Examples:
1236
1339
  console.print(" • openai login")
1237
1340
  console.print("Then use: hanzo dev --orchestrator codex")
1238
1341
  return
1239
-
1342
+
1240
1343
  # Use openai CLI to chat - correct syntax
1241
- cmd = ["openai", "api", "chat.completions.create", "-m", "gpt-4", "-g", message]
1242
-
1344
+ cmd = [
1345
+ "openai",
1346
+ "api",
1347
+ "chat.completions.create",
1348
+ "-m",
1349
+ "gpt-4",
1350
+ "-g",
1351
+ message,
1352
+ ]
1353
+
1243
1354
  process = subprocess.Popen(
1244
- cmd,
1245
- stdout=subprocess.PIPE,
1246
- stderr=subprocess.PIPE,
1247
- text=True
1355
+ cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True
1248
1356
  )
1249
-
1357
+
1250
1358
  stdout, stderr = process.communicate(timeout=30)
1251
-
1359
+
1252
1360
  if process.returncode == 0 and stdout:
1253
1361
  console.print(f"[cyan]Codex:[/cyan] {stdout.strip()}")
1254
1362
  else:
1255
1363
  console.print(f"[red]OpenAI CLI error: {stderr}[/red]")
1256
-
1364
+
1257
1365
  except subprocess.TimeoutExpired:
1258
1366
  console.print("[yellow]OpenAI CLI timed out[/yellow]")
1259
1367
  except Exception as e:
1260
1368
  console.print(f"[red]Error using OpenAI CLI: {e}[/red]")
1261
-
1369
+
1262
1370
  async def _use_claude_cli(self, message: str):
1263
1371
  """Use Claude Desktop/Code CLI."""
1264
1372
  try:
1265
1373
  import os
1266
1374
  import subprocess
1267
-
1375
+
1268
1376
  console.print("[dim]Using Claude Desktop...[/dim]")
1269
-
1377
+
1270
1378
  # Check for Claude Code or Claude Desktop
1271
1379
  claude_paths = [
1272
1380
  "/usr/local/bin/claude",
@@ -1274,13 +1382,17 @@ Examples:
1274
1382
  os.path.expanduser("~/Applications/Claude.app/Contents/MacOS/Claude"),
1275
1383
  "claude", # In PATH
1276
1384
  ]
1277
-
1385
+
1278
1386
  claude_path = None
1279
1387
  for path in claude_paths:
1280
- if os.path.exists(path) or subprocess.run(["which", path], capture_output=True).returncode == 0:
1388
+ if (
1389
+ os.path.exists(path)
1390
+ or subprocess.run(["which", path], capture_output=True).returncode
1391
+ == 0
1392
+ ):
1281
1393
  claude_path = path
1282
1394
  break
1283
-
1395
+
1284
1396
  if not claude_path:
1285
1397
  console.print("[red]Claude Desktop not found![/red]")
1286
1398
  console.print("[yellow]To install:[/yellow]")
@@ -1288,11 +1400,11 @@ Examples:
1288
1400
  console.print(" • Or: brew install --cask claude")
1289
1401
  console.print("Then use: hanzo dev --orchestrator claude")
1290
1402
  return
1291
-
1403
+
1292
1404
  # Send message to Claude via CLI or AppleScript on macOS
1293
1405
  if sys.platform == "darwin":
1294
1406
  # Use AppleScript to interact with Claude Desktop
1295
- script = f'''
1407
+ script = f"""
1296
1408
  tell application "Claude"
1297
1409
  activate
1298
1410
  delay 0.5
@@ -1301,34 +1413,36 @@ Examples:
1301
1413
  key code 36 -- Enter key
1302
1414
  end tell
1303
1415
  end tell
1304
- '''
1305
-
1416
+ """
1417
+
1306
1418
  subprocess.run(["osascript", "-e", script])
1307
- console.print("[cyan]Sent to Claude Desktop. Check the app for response.[/cyan]")
1419
+ console.print(
1420
+ "[cyan]Sent to Claude Desktop. Check the app for response.[/cyan]"
1421
+ )
1308
1422
  else:
1309
1423
  # Try direct CLI invocation
1310
1424
  process = subprocess.Popen(
1311
1425
  [claude_path, "--message", message],
1312
1426
  stdout=subprocess.PIPE,
1313
1427
  stderr=subprocess.PIPE,
1314
- text=True
1428
+ text=True,
1315
1429
  )
1316
-
1430
+
1317
1431
  stdout, stderr = process.communicate(timeout=30)
1318
-
1432
+
1319
1433
  if stdout:
1320
1434
  console.print(f"[cyan]Claude:[/cyan] {stdout.strip()}")
1321
-
1435
+
1322
1436
  except Exception as e:
1323
1437
  console.print(f"[red]Error using Claude Desktop: {e}[/red]")
1324
-
1438
+
1325
1439
  async def _use_gemini_cli(self, message: str):
1326
1440
  """Use Gemini CLI."""
1327
1441
  try:
1328
1442
  import subprocess
1329
-
1443
+
1330
1444
  console.print("[dim]Using Gemini CLI...[/dim]")
1331
-
1445
+
1332
1446
  # Check if gemini CLI is installed
1333
1447
  result = subprocess.run(["which", "gemini"], capture_output=True, text=True)
1334
1448
  if result.returncode != 0:
@@ -1339,47 +1453,46 @@ Examples:
1339
1453
  console.print(" • Set GOOGLE_API_KEY environment variable")
1340
1454
  console.print("Then use: hanzo dev --orchestrator gemini")
1341
1455
  return
1342
-
1456
+
1343
1457
  # Use gemini CLI
1344
1458
  cmd = ["gemini", "chat", message]
1345
-
1459
+
1346
1460
  process = subprocess.Popen(
1347
- cmd,
1348
- stdout=subprocess.PIPE,
1349
- stderr=subprocess.PIPE,
1350
- text=True
1461
+ cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True
1351
1462
  )
1352
-
1463
+
1353
1464
  stdout, stderr = process.communicate(timeout=30)
1354
-
1465
+
1355
1466
  if process.returncode == 0 and stdout:
1356
1467
  console.print(f"[cyan]Gemini:[/cyan] {stdout.strip()}")
1357
1468
  else:
1358
1469
  console.print(f"[red]Gemini CLI error: {stderr}[/red]")
1359
-
1470
+
1360
1471
  except subprocess.TimeoutExpired:
1361
1472
  console.print("[yellow]Gemini CLI timed out[/yellow]")
1362
1473
  except Exception as e:
1363
1474
  console.print(f"[red]Error using Gemini CLI: {e}[/red]")
1364
-
1475
+
1365
1476
  async def _use_hanzo_ide(self, message: str):
1366
1477
  """Use Hanzo Dev IDE from ~/work/hanzo/ide."""
1367
1478
  try:
1368
1479
  import os
1369
1480
  import subprocess
1370
-
1481
+
1371
1482
  console.print("[dim]Using Hanzo Dev IDE...[/dim]")
1372
-
1483
+
1373
1484
  # Check if Hanzo IDE exists
1374
1485
  ide_path = os.path.expanduser("~/work/hanzo/ide")
1375
1486
  if not os.path.exists(ide_path):
1376
1487
  console.print("[red]Hanzo Dev IDE not found![/red]")
1377
1488
  console.print("[yellow]Expected location: ~/work/hanzo/ide[/yellow]")
1378
1489
  console.print("To set up:")
1379
- console.print(" • git clone https://github.com/hanzoai/ide ~/work/hanzo/ide")
1490
+ console.print(
1491
+ " • git clone https://github.com/hanzoai/ide ~/work/hanzo/ide"
1492
+ )
1380
1493
  console.print(" • cd ~/work/hanzo/ide && npm install")
1381
1494
  return
1382
-
1495
+
1383
1496
  # Check for the CLI entry point
1384
1497
  cli_paths = [
1385
1498
  os.path.join(ide_path, "bin", "hanzo-ide"),
@@ -1387,13 +1500,13 @@ Examples:
1387
1500
  os.path.join(ide_path, "cli.js"),
1388
1501
  os.path.join(ide_path, "index.js"),
1389
1502
  ]
1390
-
1503
+
1391
1504
  cli_path = None
1392
1505
  for path in cli_paths:
1393
1506
  if os.path.exists(path):
1394
1507
  cli_path = path
1395
1508
  break
1396
-
1509
+
1397
1510
  if not cli_path:
1398
1511
  # Try to run with npm/node
1399
1512
  package_json = os.path.join(ide_path, "package.json")
@@ -1411,17 +1524,13 @@ Examples:
1411
1524
  else:
1412
1525
  cmd = [cli_path, "chat", message]
1413
1526
  cwd = None
1414
-
1527
+
1415
1528
  process = subprocess.Popen(
1416
- cmd,
1417
- stdout=subprocess.PIPE,
1418
- stderr=subprocess.PIPE,
1419
- text=True,
1420
- cwd=cwd
1529
+ cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, cwd=cwd
1421
1530
  )
1422
-
1531
+
1423
1532
  stdout, stderr = process.communicate(timeout=30)
1424
-
1533
+
1425
1534
  if process.returncode == 0 and stdout:
1426
1535
  console.print(f"[cyan]Hanzo IDE:[/cyan] {stdout.strip()}")
1427
1536
  else:
@@ -1429,73 +1538,83 @@ Examples:
1429
1538
  console.print(f"[yellow]Hanzo IDE: {stderr}[/yellow]")
1430
1539
  else:
1431
1540
  console.print("[yellow]Hanzo IDE: No response[/yellow]")
1432
-
1541
+
1433
1542
  except subprocess.TimeoutExpired:
1434
1543
  console.print("[yellow]Hanzo IDE timed out[/yellow]")
1435
1544
  except Exception as e:
1436
1545
  console.print(f"[red]Error using Hanzo IDE: {e}[/red]")
1437
-
1546
+
1438
1547
  async def _use_local_model(self, message: str):
1439
1548
  """Use local model via Ollama or LM Studio."""
1440
1549
  import httpx
1441
-
1550
+
1442
1551
  model_name = self.orchestrator.orchestrator_model.replace("local:", "")
1443
-
1552
+
1444
1553
  # Try Ollama first (default port 11434)
1445
1554
  try:
1446
1555
  console.print(f"[dim]Using local {model_name} via Ollama...[/dim]")
1447
-
1556
+
1448
1557
  async with httpx.AsyncClient() as client:
1449
1558
  response = await client.post(
1450
1559
  "http://localhost:11434/api/chat",
1451
1560
  json={
1452
1561
  "model": model_name,
1453
1562
  "messages": [
1454
- {"role": "system", "content": "You are a helpful AI coding assistant."},
1455
- {"role": "user", "content": message}
1563
+ {
1564
+ "role": "system",
1565
+ "content": "You are a helpful AI coding assistant.",
1566
+ },
1567
+ {"role": "user", "content": message},
1456
1568
  ],
1457
- "stream": False
1569
+ "stream": False,
1458
1570
  },
1459
- timeout=60.0
1571
+ timeout=60.0,
1460
1572
  )
1461
-
1573
+
1462
1574
  if response.status_code == 200:
1463
1575
  data = response.json()
1464
1576
  if data.get("message"):
1465
- console.print(f"[cyan]{model_name}:[/cyan] {data['message']['content']}")
1577
+ console.print(
1578
+ f"[cyan]{model_name}:[/cyan] {data['message']['content']}"
1579
+ )
1466
1580
  return
1467
-
1581
+
1468
1582
  except Exception:
1469
1583
  pass
1470
-
1584
+
1471
1585
  # Try LM Studio (default port 1234)
1472
1586
  try:
1473
1587
  console.print(f"[dim]Trying LM Studio...[/dim]")
1474
-
1588
+
1475
1589
  async with httpx.AsyncClient() as client:
1476
1590
  response = await client.post(
1477
1591
  "http://localhost:1234/v1/chat/completions",
1478
1592
  json={
1479
1593
  "model": model_name,
1480
1594
  "messages": [
1481
- {"role": "system", "content": "You are a helpful AI coding assistant."},
1482
- {"role": "user", "content": message}
1595
+ {
1596
+ "role": "system",
1597
+ "content": "You are a helpful AI coding assistant.",
1598
+ },
1599
+ {"role": "user", "content": message},
1483
1600
  ],
1484
1601
  "temperature": 0.7,
1485
- "max_tokens": 2000
1602
+ "max_tokens": 2000,
1486
1603
  },
1487
- timeout=60.0
1604
+ timeout=60.0,
1488
1605
  )
1489
-
1606
+
1490
1607
  if response.status_code == 200:
1491
1608
  data = response.json()
1492
1609
  if data.get("choices"):
1493
- console.print(f"[cyan]{model_name}:[/cyan] {data['choices'][0]['message']['content']}")
1610
+ console.print(
1611
+ f"[cyan]{model_name}:[/cyan] {data['choices'][0]['message']['content']}"
1612
+ )
1494
1613
  return
1495
-
1614
+
1496
1615
  except Exception:
1497
1616
  pass
1498
-
1617
+
1499
1618
  # Neither worked
1500
1619
  console.print(f"[red]Local model '{model_name}' not available[/red]")
1501
1620
  console.print("[yellow]To use local models:[/yellow]")
@@ -1507,13 +1626,13 @@ Examples:
1507
1626
  console.print(" • Download from https://lmstudio.ai")
1508
1627
  console.print(f" • Load {model_name} model")
1509
1628
  console.print(" • Start local server (port 1234)")
1510
-
1629
+
1511
1630
  async def handle_memory_command(self, command: str):
1512
1631
  """Handle memory/context commands starting with #."""
1513
1632
  parts = command.split(maxsplit=1)
1514
1633
  cmd = parts[0].lower() if parts else ""
1515
1634
  args = parts[1] if len(parts) > 1 else ""
1516
-
1635
+
1517
1636
  if cmd == "remember":
1518
1637
  if args:
1519
1638
  console.print(f"[green]✓ Remembered: {args}[/green]")
@@ -2235,17 +2354,18 @@ class MultiClaudeOrchestrator(HanzoDevOrchestrator):
2235
2354
  claude_available = False
2236
2355
  try:
2237
2356
  import shutil
2357
+
2238
2358
  if self.claude_code_path and Path(self.claude_code_path).exists():
2239
2359
  claude_available = True
2240
2360
  elif shutil.which("claude"):
2241
2361
  claude_available = True
2242
2362
  except:
2243
2363
  pass
2244
-
2364
+
2245
2365
  if not claude_available:
2246
2366
  # Skip Claude instance initialization - will use API fallback silently
2247
2367
  return
2248
-
2368
+
2249
2369
  self.console.print("[cyan]Initializing Claude instances...[/cyan]")
2250
2370
 
2251
2371
  for i in range(self.num_instances):
@@ -2410,6 +2530,7 @@ class MultiClaudeOrchestrator(HanzoDevOrchestrator):
2410
2530
  if not self.claude_instances:
2411
2531
  # No instances started, use fallback handler for smart routing
2412
2532
  from .fallback_handler import smart_chat
2533
+
2413
2534
  response = await smart_chat(task, console=self.console)
2414
2535
  if response:
2415
2536
  return {"output": response, "success": True}
@@ -2490,121 +2611,158 @@ class MultiClaudeOrchestrator(HanzoDevOrchestrator):
2490
2611
  else:
2491
2612
  # Try API-based models
2492
2613
  return await self._call_api_model(prompt)
2493
-
2614
+
2494
2615
  async def _call_openai_cli(self, prompt: str) -> Dict:
2495
2616
  """Call OpenAI CLI and return structured response."""
2496
2617
  try:
2497
2618
  import subprocess
2619
+
2498
2620
  result = subprocess.run(
2499
- ["openai", "api", "chat.completions.create", "-m", "gpt-4", "-g", prompt],
2621
+ [
2622
+ "openai",
2623
+ "api",
2624
+ "chat.completions.create",
2625
+ "-m",
2626
+ "gpt-4",
2627
+ "-g",
2628
+ prompt,
2629
+ ],
2500
2630
  capture_output=True,
2501
2631
  text=True,
2502
- timeout=30
2632
+ timeout=30,
2503
2633
  )
2504
2634
  if result.returncode == 0 and result.stdout:
2505
2635
  return {"output": result.stdout.strip(), "success": True}
2506
2636
  except Exception as e:
2507
2637
  logger.error(f"OpenAI CLI error: {e}")
2508
- return {"output": "OpenAI CLI not available. Install with: pip install openai-cli", "success": False}
2509
-
2638
+ return {
2639
+ "output": "OpenAI CLI not available. Install with: pip install openai-cli",
2640
+ "success": False,
2641
+ }
2642
+
2510
2643
  async def _call_claude_cli(self, prompt: str) -> Dict:
2511
2644
  """Call Claude Desktop and return structured response."""
2512
2645
  try:
2513
2646
  import sys
2514
2647
  import subprocess
2648
+
2515
2649
  if sys.platform == "darwin":
2516
2650
  # macOS - use AppleScript
2517
2651
  script = f'tell application "Claude" to activate'
2518
2652
  subprocess.run(["osascript", "-e", script])
2519
- return {"output": "Sent to Claude Desktop. Check app for response.", "success": True}
2653
+ return {
2654
+ "output": "Sent to Claude Desktop. Check app for response.",
2655
+ "success": True,
2656
+ }
2520
2657
  except Exception as e:
2521
2658
  logger.error(f"Claude CLI error: {e}")
2522
- return {"output": "Claude Desktop not available. Install from https://claude.ai/desktop", "success": False}
2523
-
2659
+ return {
2660
+ "output": "Claude Desktop not available. Install from https://claude.ai/desktop",
2661
+ "success": False,
2662
+ }
2663
+
2524
2664
  async def _call_gemini_cli(self, prompt: str) -> Dict:
2525
2665
  """Call Gemini CLI and return structured response."""
2526
2666
  try:
2527
2667
  import subprocess
2668
+
2528
2669
  result = subprocess.run(
2529
- ["gemini", "chat", prompt],
2530
- capture_output=True,
2531
- text=True,
2532
- timeout=30
2670
+ ["gemini", "chat", prompt], capture_output=True, text=True, timeout=30
2533
2671
  )
2534
2672
  if result.returncode == 0 and result.stdout:
2535
2673
  return {"output": result.stdout.strip(), "success": True}
2536
2674
  except Exception as e:
2537
2675
  logger.error(f"Gemini CLI error: {e}")
2538
- return {"output": "Gemini CLI not available. Install with: pip install google-generativeai-cli", "success": False}
2539
-
2676
+ return {
2677
+ "output": "Gemini CLI not available. Install with: pip install google-generativeai-cli",
2678
+ "success": False,
2679
+ }
2680
+
2540
2681
  async def _call_local_model(self, prompt: str) -> Dict:
2541
2682
  """Call local model via Ollama and return structured response."""
2542
2683
  try:
2543
2684
  import httpx
2685
+
2544
2686
  model_name = self.orchestrator_model.replace("local:", "")
2545
-
2687
+
2546
2688
  async with httpx.AsyncClient() as client:
2547
2689
  response = await client.post(
2548
2690
  "http://localhost:11434/api/chat",
2549
2691
  json={
2550
2692
  "model": model_name,
2551
2693
  "messages": [{"role": "user", "content": prompt}],
2552
- "stream": False
2694
+ "stream": False,
2553
2695
  },
2554
- timeout=60.0
2696
+ timeout=60.0,
2555
2697
  )
2556
-
2698
+
2557
2699
  if response.status_code == 200:
2558
2700
  data = response.json()
2559
2701
  if data.get("message"):
2560
2702
  return {"output": data["message"]["content"], "success": True}
2561
2703
  except Exception as e:
2562
2704
  logger.error(f"Local model error: {e}")
2563
- return {"output": f"Local model not available. Install Ollama and run: ollama pull {self.orchestrator_model.replace('local:', '')}", "success": False}
2564
-
2705
+ return {
2706
+ "output": f"Local model not available. Install Ollama and run: ollama pull {self.orchestrator_model.replace('local:', '')}",
2707
+ "success": False,
2708
+ }
2709
+
2565
2710
  async def _call_api_model(self, prompt: str) -> Dict:
2566
2711
  """Call API-based model and return structured response."""
2567
2712
  import os
2568
-
2713
+
2569
2714
  # Try OpenAI first (check environment variable properly)
2570
2715
  openai_key = os.environ.get("OPENAI_API_KEY") or os.getenv("OPENAI_API_KEY")
2571
2716
  if openai_key:
2572
2717
  try:
2573
2718
  from openai import AsyncOpenAI
2719
+
2574
2720
  client = AsyncOpenAI(api_key=openai_key)
2575
2721
  response = await client.chat.completions.create(
2576
2722
  model="gpt-4",
2577
2723
  messages=[{"role": "user", "content": prompt}],
2578
- max_tokens=2000
2724
+ max_tokens=2000,
2579
2725
  )
2580
2726
  if response.choices:
2581
- return {"output": response.choices[0].message.content, "success": True}
2727
+ return {
2728
+ "output": response.choices[0].message.content,
2729
+ "success": True,
2730
+ }
2582
2731
  except Exception as e:
2583
2732
  logger.error(f"OpenAI API error: {e}")
2584
-
2733
+
2585
2734
  # Try Anthropic
2586
- anthropic_key = os.environ.get("ANTHROPIC_API_KEY") or os.getenv("ANTHROPIC_API_KEY")
2735
+ anthropic_key = os.environ.get("ANTHROPIC_API_KEY") or os.getenv(
2736
+ "ANTHROPIC_API_KEY"
2737
+ )
2587
2738
  if anthropic_key:
2588
2739
  try:
2589
2740
  from anthropic import AsyncAnthropic
2741
+
2590
2742
  client = AsyncAnthropic(api_key=anthropic_key)
2591
2743
  response = await client.messages.create(
2592
2744
  model="claude-3-5-sonnet-20241022",
2593
2745
  messages=[{"role": "user", "content": prompt}],
2594
- max_tokens=2000
2746
+ max_tokens=2000,
2595
2747
  )
2596
2748
  if response.content:
2597
2749
  return {"output": response.content[0].text, "success": True}
2598
2750
  except Exception as e:
2599
2751
  logger.error(f"Anthropic API error: {e}")
2600
-
2752
+
2601
2753
  # Try fallback handler as last resort
2602
2754
  from .fallback_handler import smart_chat
2603
- response = await smart_chat(prompt, console=None) # No console to avoid duplicate messages
2755
+
2756
+ response = await smart_chat(
2757
+ prompt, console=None
2758
+ ) # No console to avoid duplicate messages
2604
2759
  if response:
2605
2760
  return {"output": response, "success": True}
2606
-
2607
- return {"output": "No API keys configured. Set OPENAI_API_KEY or ANTHROPIC_API_KEY", "success": False}
2761
+
2762
+ return {
2763
+ "output": "No API keys configured. Set OPENAI_API_KEY or ANTHROPIC_API_KEY",
2764
+ "success": False,
2765
+ }
2608
2766
 
2609
2767
  async def _validate_improvement(self, original: Dict, improved: Dict) -> bool:
2610
2768
  """Validate that an improvement doesn't degrade quality."""