hanzo 0.3.22__py3-none-any.whl → 0.3.24__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 +516 -0
- hanzo/batch_orchestrator.py +988 -0
- hanzo/cli.py +7 -5
- hanzo/commands/chat.py +57 -16
- hanzo/commands/{cluster.py → node.py} +128 -128
- hanzo/commands/repl.py +5 -2
- hanzo/commands/router.py +152 -0
- hanzo/dev.py +407 -249
- hanzo/fallback_handler.py +79 -53
- hanzo/interactive/repl.py +52 -32
- hanzo/mcp_server.py +8 -3
- hanzo/memory_manager.py +146 -123
- hanzo/model_registry.py +399 -0
- hanzo/rate_limiter.py +60 -75
- hanzo/streaming.py +92 -71
- hanzo-0.3.24.dist-info/METADATA +276 -0
- {hanzo-0.3.22.dist-info → hanzo-0.3.24.dist-info}/RECORD +19 -15
- hanzo-0.3.22.dist-info/METADATA +0 -137
- {hanzo-0.3.22.dist-info → hanzo-0.3.24.dist-info}/WHEEL +0 -0
- {hanzo-0.3.22.dist-info → hanzo-0.3.24.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,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
|
-
|
|
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
|
+
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
760
|
+
|
|
761
|
+
handled = handle_memory_command(
|
|
762
|
+
user_input, self.memory_manager, console
|
|
763
|
+
)
|
|
752
764
|
if not handled:
|
|
753
|
-
console.print(
|
|
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
|
|
932
|
-
|
|
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(
|
|
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,
|
|
966
|
+
if hasattr(self.orchestrator, "orchestrator_model"):
|
|
948
967
|
model = self.orchestrator.orchestrator_model
|
|
949
|
-
if model in [
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
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,
|
|
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
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
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,
|
|
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
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
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 [
|
|
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 [
|
|
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 [
|
|
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 [
|
|
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 [
|
|
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 [
|
|
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(
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
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
|
-
{
|
|
1075
|
-
|
|
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(
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
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(
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
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(
|
|
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(
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
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
|
+
)
|
|
1131
1219
|
console.print("\n[bold]Free APIs (rate limited):[/bold]")
|
|
1132
|
-
console.print(
|
|
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
|
-
{
|
|
1161
|
-
|
|
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(
|
|
1265
|
+
console.print(
|
|
1266
|
+
f"[cyan]Codestral:[/cyan] {data['choices'][0]['message']['content']}"
|
|
1267
|
+
)
|
|
1173
1268
|
else:
|
|
1174
|
-
console.print(
|
|
1175
|
-
|
|
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(
|
|
1311
|
+
console.print(
|
|
1312
|
+
f"[cyan]StarCoder:[/cyan] {data[0].get('generated_text', '')}"
|
|
1313
|
+
)
|
|
1213
1314
|
else:
|
|
1214
|
-
console.print(
|
|
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 = [
|
|
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
|
|
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(
|
|
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(
|
|
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
|
-
{
|
|
1455
|
-
|
|
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(
|
|
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
|
-
{
|
|
1482
|
-
|
|
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(
|
|
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
|
-
except:
|
|
2362
|
+
except Exception:
|
|
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
|
-
[
|
|
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 {
|
|
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 {
|
|
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 {
|
|
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 {
|
|
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 {
|
|
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 {
|
|
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(
|
|
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
|
-
|
|
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 {
|
|
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."""
|