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