hanzo-mcp 0.8.11__py3-none-any.whl → 0.8.14__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-mcp might be problematic. Click here for more details.
- hanzo_mcp/__init__.py +2 -4
- hanzo_mcp/analytics/posthog_analytics.py +3 -9
- hanzo_mcp/bridge.py +9 -25
- hanzo_mcp/cli.py +6 -15
- hanzo_mcp/cli_enhanced.py +5 -14
- hanzo_mcp/cli_plugin.py +3 -9
- hanzo_mcp/config/settings.py +6 -20
- hanzo_mcp/config/tool_config.py +1 -3
- hanzo_mcp/core/base_agent.py +88 -88
- hanzo_mcp/core/model_registry.py +238 -210
- hanzo_mcp/dev_server.py +5 -15
- hanzo_mcp/prompts/__init__.py +2 -6
- hanzo_mcp/prompts/project_todo_reminder.py +3 -9
- hanzo_mcp/prompts/tool_explorer.py +1 -3
- hanzo_mcp/prompts/utils.py +7 -21
- hanzo_mcp/server.py +13 -6
- hanzo_mcp/tools/__init__.py +10 -24
- hanzo_mcp/tools/agent/__init__.py +2 -1
- hanzo_mcp/tools/agent/agent.py +10 -30
- hanzo_mcp/tools/agent/agent_tool.py +5 -15
- hanzo_mcp/tools/agent/agent_tool_v1_deprecated.py +14 -41
- hanzo_mcp/tools/agent/claude_desktop_auth.py +3 -9
- hanzo_mcp/tools/agent/cli_agent_base.py +7 -24
- hanzo_mcp/tools/agent/cli_tools.py +75 -74
- hanzo_mcp/tools/agent/code_auth.py +1 -3
- hanzo_mcp/tools/agent/code_auth_tool.py +2 -6
- hanzo_mcp/tools/agent/critic_tool.py +8 -24
- hanzo_mcp/tools/agent/iching_tool.py +12 -36
- hanzo_mcp/tools/agent/network_tool.py +7 -18
- hanzo_mcp/tools/agent/prompt.py +1 -5
- hanzo_mcp/tools/agent/review_tool.py +10 -25
- hanzo_mcp/tools/agent/swarm_alias.py +1 -3
- hanzo_mcp/tools/agent/swarm_tool.py +9 -29
- hanzo_mcp/tools/agent/swarm_tool_v1_deprecated.py +11 -39
- hanzo_mcp/tools/agent/unified_cli_tools.py +38 -38
- hanzo_mcp/tools/common/batch_tool.py +15 -45
- hanzo_mcp/tools/common/config_tool.py +9 -28
- hanzo_mcp/tools/common/context.py +1 -3
- hanzo_mcp/tools/common/critic_tool.py +1 -3
- hanzo_mcp/tools/common/decorators.py +2 -6
- hanzo_mcp/tools/common/enhanced_base.py +2 -6
- hanzo_mcp/tools/common/fastmcp_pagination.py +4 -12
- hanzo_mcp/tools/common/forgiving_edit.py +9 -28
- hanzo_mcp/tools/common/mode.py +1 -5
- hanzo_mcp/tools/common/paginated_base.py +3 -11
- hanzo_mcp/tools/common/paginated_response.py +10 -30
- hanzo_mcp/tools/common/pagination.py +3 -9
- hanzo_mcp/tools/common/permissions.py +38 -11
- hanzo_mcp/tools/common/personality.py +9 -34
- hanzo_mcp/tools/common/plugin_loader.py +3 -15
- hanzo_mcp/tools/common/stats.py +6 -18
- hanzo_mcp/tools/common/thinking_tool.py +1 -3
- hanzo_mcp/tools/common/tool_disable.py +2 -6
- hanzo_mcp/tools/common/tool_list.py +2 -6
- hanzo_mcp/tools/common/validation.py +1 -3
- hanzo_mcp/tools/config/config_tool.py +7 -13
- hanzo_mcp/tools/config/index_config.py +1 -3
- hanzo_mcp/tools/config/mode_tool.py +5 -15
- hanzo_mcp/tools/database/database_manager.py +3 -9
- hanzo_mcp/tools/database/graph.py +1 -3
- hanzo_mcp/tools/database/graph_add.py +3 -9
- hanzo_mcp/tools/database/graph_query.py +11 -34
- hanzo_mcp/tools/database/graph_remove.py +3 -9
- hanzo_mcp/tools/database/graph_search.py +6 -20
- hanzo_mcp/tools/database/graph_stats.py +11 -33
- hanzo_mcp/tools/database/sql.py +4 -12
- hanzo_mcp/tools/database/sql_query.py +6 -10
- hanzo_mcp/tools/database/sql_search.py +2 -6
- hanzo_mcp/tools/database/sql_stats.py +5 -15
- hanzo_mcp/tools/editor/neovim_command.py +1 -3
- hanzo_mcp/tools/editor/neovim_edit.py +2 -2
- hanzo_mcp/tools/editor/neovim_session.py +7 -13
- hanzo_mcp/tools/filesystem/__init__.py +2 -3
- hanzo_mcp/tools/filesystem/ast_multi_edit.py +14 -43
- hanzo_mcp/tools/filesystem/base.py +4 -12
- hanzo_mcp/tools/filesystem/batch_search.py +35 -115
- hanzo_mcp/tools/filesystem/content_replace.py +4 -12
- hanzo_mcp/tools/filesystem/diff.py +2 -10
- hanzo_mcp/tools/filesystem/directory_tree.py +9 -27
- hanzo_mcp/tools/filesystem/directory_tree_paginated.py +5 -15
- hanzo_mcp/tools/filesystem/edit.py +6 -18
- hanzo_mcp/tools/filesystem/find.py +3 -9
- hanzo_mcp/tools/filesystem/find_files.py +2 -6
- hanzo_mcp/tools/filesystem/git_search.py +9 -24
- hanzo_mcp/tools/filesystem/grep.py +9 -27
- hanzo_mcp/tools/filesystem/multi_edit.py +6 -18
- hanzo_mcp/tools/filesystem/read.py +8 -26
- hanzo_mcp/tools/filesystem/rules_tool.py +6 -17
- hanzo_mcp/tools/filesystem/search_tool.py +18 -62
- hanzo_mcp/tools/filesystem/symbols_tool.py +5 -15
- hanzo_mcp/tools/filesystem/tree.py +1 -3
- hanzo_mcp/tools/filesystem/watch.py +1 -3
- hanzo_mcp/tools/filesystem/write.py +1 -3
- hanzo_mcp/tools/jupyter/base.py +6 -20
- hanzo_mcp/tools/jupyter/jupyter.py +4 -12
- hanzo_mcp/tools/jupyter/notebook_edit.py +11 -35
- hanzo_mcp/tools/jupyter/notebook_read.py +2 -6
- hanzo_mcp/tools/llm/consensus_tool.py +8 -24
- hanzo_mcp/tools/llm/llm_manage.py +2 -6
- hanzo_mcp/tools/llm/llm_tool.py +17 -58
- hanzo_mcp/tools/llm/llm_unified.py +18 -59
- hanzo_mcp/tools/llm/provider_tools.py +1 -3
- hanzo_mcp/tools/lsp/lsp_tool.py +5 -17
- hanzo_mcp/tools/mcp/mcp_add.py +1 -3
- hanzo_mcp/tools/mcp/mcp_stats.py +1 -3
- hanzo_mcp/tools/mcp/mcp_tool.py +9 -23
- hanzo_mcp/tools/memory/__init__.py +10 -27
- hanzo_mcp/tools/memory/knowledge_tools.py +7 -25
- hanzo_mcp/tools/memory/memory_tools.py +6 -18
- hanzo_mcp/tools/search/find_tool.py +10 -32
- hanzo_mcp/tools/search/unified_search.py +24 -78
- hanzo_mcp/tools/shell/__init__.py +2 -2
- hanzo_mcp/tools/shell/auto_background.py +2 -6
- hanzo_mcp/tools/shell/base.py +1 -5
- hanzo_mcp/tools/shell/base_process.py +5 -7
- hanzo_mcp/tools/shell/bash_session.py +7 -24
- hanzo_mcp/tools/shell/bash_session_executor.py +5 -15
- hanzo_mcp/tools/shell/bash_tool.py +3 -7
- hanzo_mcp/tools/shell/command_executor.py +33 -86
- hanzo_mcp/tools/shell/logs.py +4 -16
- hanzo_mcp/tools/shell/npx.py +2 -8
- hanzo_mcp/tools/shell/npx_tool.py +1 -3
- hanzo_mcp/tools/shell/pkill.py +4 -12
- hanzo_mcp/tools/shell/process_tool.py +2 -8
- hanzo_mcp/tools/shell/processes.py +5 -17
- hanzo_mcp/tools/shell/run_background.py +1 -3
- hanzo_mcp/tools/shell/run_command.py +1 -3
- hanzo_mcp/tools/shell/run_command_windows.py +1 -3
- hanzo_mcp/tools/shell/session_manager.py +2 -6
- hanzo_mcp/tools/shell/session_storage.py +2 -6
- hanzo_mcp/tools/shell/streaming_command.py +7 -23
- hanzo_mcp/tools/shell/uvx.py +4 -14
- hanzo_mcp/tools/shell/uvx_background.py +2 -6
- hanzo_mcp/tools/shell/uvx_tool.py +1 -3
- hanzo_mcp/tools/shell/zsh_tool.py +12 -20
- hanzo_mcp/tools/todo/todo.py +1 -3
- hanzo_mcp/tools/todo/todo_read.py +3 -9
- hanzo_mcp/tools/todo/todo_write.py +6 -18
- hanzo_mcp/tools/vector/__init__.py +3 -9
- hanzo_mcp/tools/vector/ast_analyzer.py +6 -20
- hanzo_mcp/tools/vector/git_ingester.py +10 -30
- hanzo_mcp/tools/vector/index_tool.py +3 -9
- hanzo_mcp/tools/vector/infinity_store.py +7 -27
- hanzo_mcp/tools/vector/mock_infinity.py +1 -3
- hanzo_mcp/tools/vector/project_manager.py +4 -12
- hanzo_mcp/tools/vector/vector.py +2 -6
- hanzo_mcp/tools/vector/vector_index.py +8 -8
- hanzo_mcp/tools/vector/vector_search.py +7 -21
- {hanzo_mcp-0.8.11.dist-info → hanzo_mcp-0.8.14.dist-info}/METADATA +2 -2
- hanzo_mcp-0.8.14.dist-info/RECORD +193 -0
- hanzo_mcp-0.8.11.dist-info/RECORD +0 -193
- {hanzo_mcp-0.8.11.dist-info → hanzo_mcp-0.8.14.dist-info}/WHEEL +0 -0
- {hanzo_mcp-0.8.11.dist-info → hanzo_mcp-0.8.14.dist-info}/entry_points.txt +0 -0
- {hanzo_mcp-0.8.11.dist-info → hanzo_mcp-0.8.14.dist-info}/top_level.txt +0 -0
|
@@ -97,9 +97,7 @@ class CLIAgentBase(BaseTool):
|
|
|
97
97
|
|
|
98
98
|
# Check if installed
|
|
99
99
|
if not self.is_installed():
|
|
100
|
-
error_msg = (
|
|
101
|
-
f"{self.provider_name} CLI ({self.command_name}) is not installed. "
|
|
102
|
-
)
|
|
100
|
+
error_msg = f"{self.provider_name} CLI ({self.command_name}) is not installed. "
|
|
103
101
|
error_msg += f"Please install it first: https://github.com/anthropics/{self.command_name}"
|
|
104
102
|
await tool_ctx.error(error_msg)
|
|
105
103
|
return f"Error: {error_msg}"
|
|
@@ -115,15 +113,11 @@ class CLIAgentBase(BaseTool):
|
|
|
115
113
|
cli_args = self.get_cli_args(prompt, **kwargs)
|
|
116
114
|
|
|
117
115
|
# Log command
|
|
118
|
-
await tool_ctx.info(
|
|
119
|
-
f"Executing {self.provider_name}: {self.command_name} {' '.join(cli_args[:3])}..."
|
|
120
|
-
)
|
|
116
|
+
await tool_ctx.info(f"Executing {self.provider_name}: {self.command_name} {' '.join(cli_args[:3])}...")
|
|
121
117
|
|
|
122
118
|
try:
|
|
123
119
|
# Create temp file for prompt if needed
|
|
124
|
-
with tempfile.NamedTemporaryFile(
|
|
125
|
-
mode="w", suffix=".txt", delete=False
|
|
126
|
-
) as f:
|
|
120
|
+
with tempfile.NamedTemporaryFile(mode="w", suffix=".txt", delete=False) as f:
|
|
127
121
|
f.write(prompt)
|
|
128
122
|
prompt_file = f.name
|
|
129
123
|
|
|
@@ -131,12 +125,7 @@ class CLIAgentBase(BaseTool):
|
|
|
131
125
|
if "--prompt-file" in cli_args:
|
|
132
126
|
# Replace placeholder with actual file
|
|
133
127
|
cli_args = [
|
|
134
|
-
(
|
|
135
|
-
arg.replace("--prompt-file", prompt_file)
|
|
136
|
-
if arg == "--prompt-file"
|
|
137
|
-
else arg
|
|
138
|
-
)
|
|
139
|
-
for arg in cli_args
|
|
128
|
+
(arg.replace("--prompt-file", prompt_file) if arg == "--prompt-file" else arg) for arg in cli_args
|
|
140
129
|
]
|
|
141
130
|
|
|
142
131
|
# Execute command
|
|
@@ -151,13 +140,9 @@ class CLIAgentBase(BaseTool):
|
|
|
151
140
|
|
|
152
141
|
# Send prompt via stdin if not using file
|
|
153
142
|
if "--prompt-file" not in cli_args:
|
|
154
|
-
stdout, stderr = await asyncio.wait_for(
|
|
155
|
-
process.communicate(input=prompt.encode()), timeout=timeout
|
|
156
|
-
)
|
|
143
|
+
stdout, stderr = await asyncio.wait_for(process.communicate(input=prompt.encode()), timeout=timeout)
|
|
157
144
|
else:
|
|
158
|
-
stdout, stderr = await asyncio.wait_for(
|
|
159
|
-
process.communicate(), timeout=timeout
|
|
160
|
-
)
|
|
145
|
+
stdout, stderr = await asyncio.wait_for(process.communicate(), timeout=timeout)
|
|
161
146
|
|
|
162
147
|
# Clean up temp file
|
|
163
148
|
try:
|
|
@@ -175,9 +160,7 @@ class CLIAgentBase(BaseTool):
|
|
|
175
160
|
return result
|
|
176
161
|
|
|
177
162
|
except asyncio.TimeoutError:
|
|
178
|
-
await tool_ctx.error(
|
|
179
|
-
f"{self.provider_name} timed out after {timeout} seconds"
|
|
180
|
-
)
|
|
163
|
+
await tool_ctx.error(f"{self.provider_name} timed out after {timeout} seconds")
|
|
181
164
|
return f"Error: Command timed out after {timeout} seconds"
|
|
182
165
|
except Exception as e:
|
|
183
166
|
await tool_ctx.error(f"{self.provider_name} error: {str(e)}")
|
|
@@ -55,6 +55,7 @@ Timeout = Annotated[
|
|
|
55
55
|
|
|
56
56
|
class CLIToolParams(TypedDict, total=False):
|
|
57
57
|
"""Common parameters for CLI tools."""
|
|
58
|
+
|
|
58
59
|
prompt: str
|
|
59
60
|
model: Optional[str]
|
|
60
61
|
working_dir: Optional[str]
|
|
@@ -63,7 +64,7 @@ class CLIToolParams(TypedDict, total=False):
|
|
|
63
64
|
|
|
64
65
|
class BaseCLITool(BaseTool):
|
|
65
66
|
"""Base class for CLI tool implementations."""
|
|
66
|
-
|
|
67
|
+
|
|
67
68
|
def __init__(
|
|
68
69
|
self,
|
|
69
70
|
permission_manager: Optional[PermissionManager] = None,
|
|
@@ -71,7 +72,7 @@ class BaseCLITool(BaseTool):
|
|
|
71
72
|
api_key_env: Optional[str] = None,
|
|
72
73
|
):
|
|
73
74
|
"""Initialize CLI tool.
|
|
74
|
-
|
|
75
|
+
|
|
75
76
|
Args:
|
|
76
77
|
permission_manager: Permission manager for access control
|
|
77
78
|
default_model: Default model to use
|
|
@@ -80,21 +81,21 @@ class BaseCLITool(BaseTool):
|
|
|
80
81
|
self.permission_manager = permission_manager
|
|
81
82
|
self.default_model = default_model
|
|
82
83
|
self.api_key_env = api_key_env
|
|
83
|
-
|
|
84
|
+
|
|
84
85
|
def get_auth_env(self) -> dict[str, str]:
|
|
85
86
|
"""Get authentication environment variables."""
|
|
86
87
|
env = os.environ.copy()
|
|
87
|
-
|
|
88
|
+
|
|
88
89
|
# Add API key if configured
|
|
89
90
|
if self.api_key_env and self.api_key_env in os.environ:
|
|
90
91
|
env[self.api_key_env] = os.environ[self.api_key_env]
|
|
91
|
-
|
|
92
|
+
|
|
92
93
|
# Add Hanzo API key for unified auth
|
|
93
94
|
if "HANZO_API_KEY" in os.environ:
|
|
94
95
|
env["HANZO_API_KEY"] = os.environ["HANZO_API_KEY"]
|
|
95
|
-
|
|
96
|
+
|
|
96
97
|
return env
|
|
97
|
-
|
|
98
|
+
|
|
98
99
|
async def execute_cli(
|
|
99
100
|
self,
|
|
100
101
|
command: list[str],
|
|
@@ -103,20 +104,20 @@ class BaseCLITool(BaseTool):
|
|
|
103
104
|
timeout: int = 300,
|
|
104
105
|
) -> str:
|
|
105
106
|
"""Execute CLI command with proper error handling.
|
|
106
|
-
|
|
107
|
+
|
|
107
108
|
Args:
|
|
108
109
|
command: Command and arguments
|
|
109
110
|
input_text: Optional stdin input
|
|
110
111
|
working_dir: Working directory
|
|
111
112
|
timeout: Timeout in seconds
|
|
112
|
-
|
|
113
|
+
|
|
113
114
|
Returns:
|
|
114
115
|
Command output
|
|
115
116
|
"""
|
|
116
117
|
try:
|
|
117
118
|
# Set up environment with auth
|
|
118
119
|
env = self.get_auth_env()
|
|
119
|
-
|
|
120
|
+
|
|
120
121
|
# Execute command
|
|
121
122
|
process = await asyncio.create_subprocess_exec(
|
|
122
123
|
*command,
|
|
@@ -126,33 +127,33 @@ class BaseCLITool(BaseTool):
|
|
|
126
127
|
cwd=working_dir,
|
|
127
128
|
env=env,
|
|
128
129
|
)
|
|
129
|
-
|
|
130
|
+
|
|
130
131
|
# Send input and get output
|
|
131
132
|
stdout, stderr = await asyncio.wait_for(
|
|
132
133
|
process.communicate(input_text.encode() if input_text else None),
|
|
133
134
|
timeout=timeout,
|
|
134
135
|
)
|
|
135
|
-
|
|
136
|
+
|
|
136
137
|
# Check for errors
|
|
137
138
|
if process.returncode != 0:
|
|
138
139
|
error_msg = stderr.decode() if stderr else "Unknown error"
|
|
139
140
|
return f"Error: {error_msg}"
|
|
140
|
-
|
|
141
|
+
|
|
141
142
|
return stdout.decode()
|
|
142
|
-
|
|
143
|
+
|
|
143
144
|
except asyncio.TimeoutError:
|
|
144
145
|
return f"Error: Command timed out after {timeout} seconds"
|
|
145
146
|
except Exception as e:
|
|
146
147
|
return f"Error executing command: {str(e)}"
|
|
147
|
-
|
|
148
|
+
|
|
148
149
|
def register(self, mcp_server: FastMCP) -> None:
|
|
149
150
|
"""Register this tool with the MCP server.
|
|
150
|
-
|
|
151
|
+
|
|
151
152
|
Args:
|
|
152
153
|
mcp_server: The FastMCP server instance
|
|
153
154
|
"""
|
|
154
155
|
tool_self = self # Create a reference to self for use in the closure
|
|
155
|
-
|
|
156
|
+
|
|
156
157
|
@mcp_server.tool(name=self.name, description=self.description)
|
|
157
158
|
async def tool_wrapper(
|
|
158
159
|
prompt: str,
|
|
@@ -169,33 +170,33 @@ class BaseCLITool(BaseTool):
|
|
|
169
170
|
|
|
170
171
|
class ClaudeCLITool(BaseCLITool):
|
|
171
172
|
"""Claude CLI tool (also available as 'cc' alias)."""
|
|
172
|
-
|
|
173
|
+
|
|
173
174
|
def __init__(self, permission_manager: Optional[PermissionManager] = None):
|
|
174
175
|
super().__init__(
|
|
175
176
|
permission_manager=permission_manager,
|
|
176
177
|
default_model="claude-3-5-sonnet-20241022",
|
|
177
178
|
api_key_env="ANTHROPIC_API_KEY",
|
|
178
179
|
)
|
|
179
|
-
|
|
180
|
+
|
|
180
181
|
@property
|
|
181
182
|
def name(self) -> str:
|
|
182
183
|
return "claude"
|
|
183
|
-
|
|
184
|
+
|
|
184
185
|
@property
|
|
185
186
|
def description(self) -> str:
|
|
186
187
|
return "Execute Claude CLI for AI assistance using Anthropic's models"
|
|
187
|
-
|
|
188
|
+
|
|
188
189
|
async def call(self, ctx: Context[Any, Any, Any], **params: Any) -> str:
|
|
189
190
|
prompt: str = params.get("prompt", "")
|
|
190
191
|
model: Optional[str] = params.get("model") or self.default_model
|
|
191
192
|
working_dir: Optional[str] = params.get("working_dir")
|
|
192
193
|
timeout: int = params.get("timeout", 300)
|
|
193
|
-
|
|
194
|
+
|
|
194
195
|
# Build command
|
|
195
196
|
command: list[str] = ["claude"]
|
|
196
197
|
if model:
|
|
197
198
|
command.extend(["--model", model])
|
|
198
|
-
|
|
199
|
+
|
|
199
200
|
# Execute
|
|
200
201
|
return await self.execute_cli(
|
|
201
202
|
command,
|
|
@@ -207,11 +208,11 @@ class ClaudeCLITool(BaseCLITool):
|
|
|
207
208
|
|
|
208
209
|
class ClaudeCodeCLITool(ClaudeCLITool):
|
|
209
210
|
"""Claude Code CLI tool (cc alias)."""
|
|
210
|
-
|
|
211
|
+
|
|
211
212
|
@property
|
|
212
213
|
def name(self) -> str:
|
|
213
214
|
return "cc"
|
|
214
|
-
|
|
215
|
+
|
|
215
216
|
@property
|
|
216
217
|
def description(self) -> str:
|
|
217
218
|
return "Claude Code CLI (alias for claude)"
|
|
@@ -219,34 +220,34 @@ class ClaudeCodeCLITool(ClaudeCLITool):
|
|
|
219
220
|
|
|
220
221
|
class CodexCLITool(BaseCLITool):
|
|
221
222
|
"""OpenAI Codex/GPT-4 CLI tool."""
|
|
222
|
-
|
|
223
|
+
|
|
223
224
|
def __init__(self, permission_manager: Optional[PermissionManager] = None):
|
|
224
225
|
super().__init__(
|
|
225
226
|
permission_manager=permission_manager,
|
|
226
227
|
default_model="gpt-4-turbo",
|
|
227
228
|
api_key_env="OPENAI_API_KEY",
|
|
228
229
|
)
|
|
229
|
-
|
|
230
|
+
|
|
230
231
|
@property
|
|
231
232
|
def name(self) -> str:
|
|
232
233
|
return "codex"
|
|
233
|
-
|
|
234
|
+
|
|
234
235
|
@property
|
|
235
236
|
def description(self) -> str:
|
|
236
237
|
return "Execute OpenAI Codex/GPT-4 CLI for code generation and AI assistance"
|
|
237
|
-
|
|
238
|
+
|
|
238
239
|
async def call(self, ctx: Context[Any, Any, Any], **params: Any) -> str:
|
|
239
240
|
prompt: str = params.get("prompt", "")
|
|
240
241
|
model: Optional[str] = params.get("model") or self.default_model
|
|
241
242
|
working_dir: Optional[str] = params.get("working_dir")
|
|
242
243
|
timeout: int = params.get("timeout", 300)
|
|
243
|
-
|
|
244
|
+
|
|
244
245
|
# Build command (using openai CLI or custom wrapper)
|
|
245
246
|
command: list[str] = ["openai", "api", "chat.completions.create"]
|
|
246
247
|
if model:
|
|
247
248
|
command.extend(["-m", model])
|
|
248
249
|
command.extend(["-g", "user", prompt])
|
|
249
|
-
|
|
250
|
+
|
|
250
251
|
# Execute
|
|
251
252
|
return await self.execute_cli(
|
|
252
253
|
command,
|
|
@@ -257,34 +258,34 @@ class CodexCLITool(BaseCLITool):
|
|
|
257
258
|
|
|
258
259
|
class GeminiCLITool(BaseCLITool):
|
|
259
260
|
"""Google Gemini CLI tool."""
|
|
260
|
-
|
|
261
|
+
|
|
261
262
|
def __init__(self, permission_manager: Optional[PermissionManager] = None):
|
|
262
263
|
super().__init__(
|
|
263
264
|
permission_manager=permission_manager,
|
|
264
265
|
default_model="gemini-1.5-pro",
|
|
265
266
|
api_key_env="GEMINI_API_KEY",
|
|
266
267
|
)
|
|
267
|
-
|
|
268
|
+
|
|
268
269
|
@property
|
|
269
270
|
def name(self) -> str:
|
|
270
271
|
return "gemini"
|
|
271
|
-
|
|
272
|
+
|
|
272
273
|
@property
|
|
273
274
|
def description(self) -> str:
|
|
274
275
|
return "Execute Google Gemini CLI for multimodal AI assistance"
|
|
275
|
-
|
|
276
|
+
|
|
276
277
|
async def call(self, ctx: Context[Any, Any, Any], **params: Any) -> str:
|
|
277
278
|
prompt: str = params.get("prompt", "")
|
|
278
279
|
model: Optional[str] = params.get("model") or self.default_model
|
|
279
280
|
working_dir: Optional[str] = params.get("working_dir")
|
|
280
281
|
timeout: int = params.get("timeout", 300)
|
|
281
|
-
|
|
282
|
+
|
|
282
283
|
# Build command
|
|
283
284
|
command: list[str] = ["gemini"]
|
|
284
285
|
if model:
|
|
285
286
|
command.extend(["--model", model])
|
|
286
287
|
command.append(prompt)
|
|
287
|
-
|
|
288
|
+
|
|
288
289
|
# Execute
|
|
289
290
|
return await self.execute_cli(
|
|
290
291
|
command,
|
|
@@ -295,34 +296,34 @@ class GeminiCLITool(BaseCLITool):
|
|
|
295
296
|
|
|
296
297
|
class GrokCLITool(BaseCLITool):
|
|
297
298
|
"""xAI Grok CLI tool."""
|
|
298
|
-
|
|
299
|
+
|
|
299
300
|
def __init__(self, permission_manager: Optional[PermissionManager] = None):
|
|
300
301
|
super().__init__(
|
|
301
302
|
permission_manager=permission_manager,
|
|
302
303
|
default_model="grok-4",
|
|
303
304
|
api_key_env="XAI_API_KEY",
|
|
304
305
|
)
|
|
305
|
-
|
|
306
|
+
|
|
306
307
|
@property
|
|
307
308
|
def name(self) -> str:
|
|
308
309
|
return "grok"
|
|
309
|
-
|
|
310
|
+
|
|
310
311
|
@property
|
|
311
312
|
def description(self) -> str:
|
|
312
313
|
return "Execute xAI Grok CLI for real-time AI assistance"
|
|
313
|
-
|
|
314
|
+
|
|
314
315
|
async def call(self, ctx: Context[Any, Any, Any], **params: Any) -> str:
|
|
315
316
|
prompt: str = params.get("prompt", "")
|
|
316
317
|
model: Optional[str] = params.get("model") or self.default_model
|
|
317
318
|
working_dir: Optional[str] = params.get("working_dir")
|
|
318
319
|
timeout: int = params.get("timeout", 300)
|
|
319
|
-
|
|
320
|
+
|
|
320
321
|
# Build command
|
|
321
322
|
command: list[str] = ["grok"]
|
|
322
323
|
if model:
|
|
323
324
|
command.extend(["--model", model])
|
|
324
325
|
command.append(prompt)
|
|
325
|
-
|
|
326
|
+
|
|
326
327
|
# Execute
|
|
327
328
|
return await self.execute_cli(
|
|
328
329
|
command,
|
|
@@ -333,34 +334,34 @@ class GrokCLITool(BaseCLITool):
|
|
|
333
334
|
|
|
334
335
|
class OpenHandsCLITool(BaseCLITool):
|
|
335
336
|
"""OpenHands (OpenDevin) CLI tool."""
|
|
336
|
-
|
|
337
|
+
|
|
337
338
|
def __init__(self, permission_manager: Optional[PermissionManager] = None):
|
|
338
339
|
super().__init__(
|
|
339
340
|
permission_manager=permission_manager,
|
|
340
341
|
default_model="claude-3-5-sonnet-20241022",
|
|
341
342
|
api_key_env="OPENAI_API_KEY",
|
|
342
343
|
)
|
|
343
|
-
|
|
344
|
+
|
|
344
345
|
@property
|
|
345
346
|
def name(self) -> str:
|
|
346
347
|
return "openhands"
|
|
347
|
-
|
|
348
|
+
|
|
348
349
|
@property
|
|
349
350
|
def description(self) -> str:
|
|
350
351
|
return "Execute OpenHands (OpenDevin) for autonomous coding assistance"
|
|
351
|
-
|
|
352
|
+
|
|
352
353
|
async def call(self, ctx: Context[Any, Any, Any], **params: Any) -> str:
|
|
353
354
|
prompt = params.get("prompt", "")
|
|
354
355
|
model = params.get("model") or self.default_model
|
|
355
356
|
working_dir: str = params.get("working_dir") or os.getcwd()
|
|
356
357
|
timeout: int = params.get("timeout", 600) # 10 minutes for OpenHands
|
|
357
|
-
|
|
358
|
+
|
|
358
359
|
# Build command
|
|
359
360
|
command: list[str] = ["openhands", "run", prompt]
|
|
360
361
|
if model:
|
|
361
362
|
command.extend(["--model", model])
|
|
362
363
|
command.extend(["--workspace", working_dir])
|
|
363
|
-
|
|
364
|
+
|
|
364
365
|
# Execute
|
|
365
366
|
return await self.execute_cli(
|
|
366
367
|
command,
|
|
@@ -371,11 +372,11 @@ class OpenHandsCLITool(BaseCLITool):
|
|
|
371
372
|
|
|
372
373
|
class OpenHandsShortCLITool(OpenHandsCLITool):
|
|
373
374
|
"""OpenHands CLI tool (oh alias)."""
|
|
374
|
-
|
|
375
|
+
|
|
375
376
|
@property
|
|
376
377
|
def name(self) -> str:
|
|
377
378
|
return "oh"
|
|
378
|
-
|
|
379
|
+
|
|
379
380
|
@property
|
|
380
381
|
def description(self) -> str:
|
|
381
382
|
return "OpenHands CLI (alias for openhands)"
|
|
@@ -383,34 +384,34 @@ class OpenHandsShortCLITool(OpenHandsCLITool):
|
|
|
383
384
|
|
|
384
385
|
class HanzoDevCLITool(BaseCLITool):
|
|
385
386
|
"""Hanzo Dev AI coding assistant."""
|
|
386
|
-
|
|
387
|
+
|
|
387
388
|
def __init__(self, permission_manager: Optional[PermissionManager] = None):
|
|
388
389
|
super().__init__(
|
|
389
390
|
permission_manager=permission_manager,
|
|
390
391
|
default_model="claude-3-5-sonnet-20241022",
|
|
391
392
|
api_key_env="HANZO_API_KEY",
|
|
392
393
|
)
|
|
393
|
-
|
|
394
|
+
|
|
394
395
|
@property
|
|
395
396
|
def name(self) -> str:
|
|
396
397
|
return "hanzo_dev"
|
|
397
|
-
|
|
398
|
+
|
|
398
399
|
@property
|
|
399
400
|
def description(self) -> str:
|
|
400
401
|
return "Execute Hanzo Dev for AI-powered code editing and development"
|
|
401
|
-
|
|
402
|
+
|
|
402
403
|
async def call(self, ctx: Context[Any, Any, Any], **params: Any) -> str:
|
|
403
404
|
prompt = params.get("prompt", "")
|
|
404
405
|
model = params.get("model") or self.default_model
|
|
405
406
|
working_dir: str = params.get("working_dir") or os.getcwd()
|
|
406
407
|
timeout: int = params.get("timeout", 600)
|
|
407
|
-
|
|
408
|
+
|
|
408
409
|
# Build command
|
|
409
410
|
command: list[str] = ["dev"]
|
|
410
411
|
if model:
|
|
411
412
|
command.extend(["--model", model])
|
|
412
413
|
command.extend(["--prompt", prompt])
|
|
413
|
-
|
|
414
|
+
|
|
414
415
|
# Execute
|
|
415
416
|
return await self.execute_cli(
|
|
416
417
|
command,
|
|
@@ -421,31 +422,31 @@ class HanzoDevCLITool(BaseCLITool):
|
|
|
421
422
|
|
|
422
423
|
class ClineCLITool(BaseCLITool):
|
|
423
424
|
"""Cline (formerly Claude Engineer) CLI tool."""
|
|
424
|
-
|
|
425
|
+
|
|
425
426
|
def __init__(self, permission_manager: Optional[PermissionManager] = None):
|
|
426
427
|
super().__init__(
|
|
427
428
|
permission_manager=permission_manager,
|
|
428
429
|
default_model="claude-3-5-sonnet-20241022",
|
|
429
430
|
api_key_env="ANTHROPIC_API_KEY",
|
|
430
431
|
)
|
|
431
|
-
|
|
432
|
+
|
|
432
433
|
@property
|
|
433
434
|
def name(self) -> str:
|
|
434
435
|
return "cline"
|
|
435
|
-
|
|
436
|
+
|
|
436
437
|
@property
|
|
437
438
|
def description(self) -> str:
|
|
438
439
|
return "Execute Cline for autonomous coding with Claude"
|
|
439
|
-
|
|
440
|
+
|
|
440
441
|
async def call(self, ctx: Context[Any, Any, Any], **params: Any) -> str:
|
|
441
442
|
prompt = params.get("prompt", "")
|
|
442
443
|
working_dir: str = params.get("working_dir") or os.getcwd()
|
|
443
444
|
timeout: int = params.get("timeout", 600)
|
|
444
|
-
|
|
445
|
+
|
|
445
446
|
# Build command
|
|
446
447
|
command: list[str] = ["cline", prompt]
|
|
447
448
|
command.extend(["--no-interactive"]) # Non-interactive mode for batch
|
|
448
|
-
|
|
449
|
+
|
|
449
450
|
# Execute
|
|
450
451
|
return await self.execute_cli(
|
|
451
452
|
command,
|
|
@@ -456,28 +457,28 @@ class ClineCLITool(BaseCLITool):
|
|
|
456
457
|
|
|
457
458
|
class AiderCLITool(BaseCLITool):
|
|
458
459
|
"""Aider AI pair programming tool."""
|
|
459
|
-
|
|
460
|
+
|
|
460
461
|
def __init__(self, permission_manager: Optional[PermissionManager] = None):
|
|
461
462
|
super().__init__(
|
|
462
463
|
permission_manager=permission_manager,
|
|
463
464
|
default_model="gpt-4-turbo",
|
|
464
465
|
api_key_env="OPENAI_API_KEY",
|
|
465
466
|
)
|
|
466
|
-
|
|
467
|
+
|
|
467
468
|
@property
|
|
468
469
|
def name(self) -> str:
|
|
469
470
|
return "aider"
|
|
470
|
-
|
|
471
|
+
|
|
471
472
|
@property
|
|
472
473
|
def description(self) -> str:
|
|
473
474
|
return "Execute Aider for AI pair programming"
|
|
474
|
-
|
|
475
|
+
|
|
475
476
|
async def call(self, ctx: Context[Any, Any, Any], **params: Any) -> str:
|
|
476
477
|
prompt = params.get("prompt", "")
|
|
477
478
|
model = params.get("model") or self.default_model
|
|
478
479
|
working_dir: str = params.get("working_dir") or os.getcwd()
|
|
479
480
|
timeout: int = params.get("timeout", 600)
|
|
480
|
-
|
|
481
|
+
|
|
481
482
|
# Build command
|
|
482
483
|
command: list[str] = ["aider"]
|
|
483
484
|
if model:
|
|
@@ -485,7 +486,7 @@ class AiderCLITool(BaseCLITool):
|
|
|
485
486
|
command.extend(["--message", prompt])
|
|
486
487
|
command.extend(["--yes"]) # Auto-approve changes
|
|
487
488
|
command.extend(["--no-stream"]) # No streaming for batch
|
|
488
|
-
|
|
489
|
+
|
|
489
490
|
# Execute
|
|
490
491
|
return await self.execute_cli(
|
|
491
492
|
command,
|
|
@@ -499,11 +500,11 @@ def register_cli_tools(
|
|
|
499
500
|
permission_manager: Optional[PermissionManager] = None,
|
|
500
501
|
) -> list[BaseTool]:
|
|
501
502
|
"""Register all CLI tools with the MCP server.
|
|
502
|
-
|
|
503
|
+
|
|
503
504
|
Args:
|
|
504
505
|
mcp_server: The FastMCP server instance
|
|
505
506
|
permission_manager: Permission manager for access control
|
|
506
|
-
|
|
507
|
+
|
|
507
508
|
Returns:
|
|
508
509
|
List of registered CLI tools
|
|
509
510
|
"""
|
|
@@ -519,11 +520,11 @@ def register_cli_tools(
|
|
|
519
520
|
ClineCLITool(permission_manager),
|
|
520
521
|
AiderCLITool(permission_manager),
|
|
521
522
|
]
|
|
522
|
-
|
|
523
|
+
|
|
523
524
|
# Register each tool
|
|
524
525
|
for tool in tools:
|
|
525
526
|
tool.register(mcp_server)
|
|
526
|
-
|
|
527
|
+
|
|
527
528
|
return tools
|
|
528
529
|
|
|
529
530
|
|
|
@@ -540,4 +541,4 @@ __all__ = [
|
|
|
540
541
|
"ClineCLITool",
|
|
541
542
|
"AiderCLITool",
|
|
542
543
|
"register_cli_tools",
|
|
543
|
-
]
|
|
544
|
+
]
|
|
@@ -322,9 +322,7 @@ class CodeAuthManager:
|
|
|
322
322
|
api_key = None
|
|
323
323
|
if parent_info.get("has_keyring"):
|
|
324
324
|
try:
|
|
325
|
-
api_key = keyring.get_password(
|
|
326
|
-
f"hanzo-{parent_info['provider']}", parent_account
|
|
327
|
-
)
|
|
325
|
+
api_key = keyring.get_password(f"hanzo-{parent_info['provider']}", parent_account)
|
|
328
326
|
except Exception:
|
|
329
327
|
pass
|
|
330
328
|
|
|
@@ -117,9 +117,7 @@ Providers: claude, openai, azure, deepseek, google, groq"""
|
|
|
117
117
|
model = params.get("model")
|
|
118
118
|
description = params.get("description")
|
|
119
119
|
|
|
120
|
-
success, msg = self.auth_manager.create_account(
|
|
121
|
-
account, provider, api_key, model, description
|
|
122
|
-
)
|
|
120
|
+
success, msg = self.auth_manager.create_account(account, provider, api_key, model, description)
|
|
123
121
|
return msg
|
|
124
122
|
|
|
125
123
|
elif action == "login":
|
|
@@ -148,9 +146,7 @@ Providers: claude, openai, azure, deepseek, google, groq"""
|
|
|
148
146
|
parent_account = params.get("parent_account")
|
|
149
147
|
|
|
150
148
|
# Try to create agent account
|
|
151
|
-
success, result = self.auth_manager.create_agent_account(
|
|
152
|
-
agent_id, provider, parent_account
|
|
153
|
-
)
|
|
149
|
+
success, result = self.auth_manager.create_agent_account(agent_id, provider, parent_account)
|
|
154
150
|
|
|
155
151
|
if success:
|
|
156
152
|
# Get credentials
|
|
@@ -117,9 +117,7 @@ class AutoCritic:
|
|
|
117
117
|
) -> str:
|
|
118
118
|
"""Perform automated critical review."""
|
|
119
119
|
review_func = self.review_patterns.get(review_type, self._review_general)
|
|
120
|
-
return review_func(
|
|
121
|
-
work_description, code_snippets, file_paths, specific_concerns
|
|
122
|
-
)
|
|
120
|
+
return review_func(work_description, code_snippets, file_paths, specific_concerns)
|
|
123
121
|
|
|
124
122
|
def _review_code_quality(
|
|
125
123
|
self,
|
|
@@ -137,15 +135,11 @@ class AutoCritic:
|
|
|
137
135
|
for snippet in code_snippets:
|
|
138
136
|
# Check for proper error handling
|
|
139
137
|
if "error" in snippet.lower() and "if err" not in snippet:
|
|
140
|
-
issues.append(
|
|
141
|
-
"❌ Missing error handling - always check errors in Go"
|
|
142
|
-
)
|
|
138
|
+
issues.append("❌ Missing error handling - always check errors in Go")
|
|
143
139
|
|
|
144
140
|
# Check for magic numbers
|
|
145
141
|
if any(char.isdigit() for char in snippet) and "const" not in snippet:
|
|
146
|
-
suggestions.append(
|
|
147
|
-
"💡 Consider extracting magic numbers to named constants"
|
|
148
|
-
)
|
|
142
|
+
suggestions.append("💡 Consider extracting magic numbers to named constants")
|
|
149
143
|
|
|
150
144
|
# Check for proper imports
|
|
151
145
|
if "import" in snippet:
|
|
@@ -160,9 +154,7 @@ class AutoCritic:
|
|
|
160
154
|
suggestions.append("💡 Consider edge cases and error scenarios")
|
|
161
155
|
|
|
162
156
|
if file_paths and len(file_paths) > 5:
|
|
163
|
-
suggestions.append(
|
|
164
|
-
"💡 Large number of files modified - consider breaking into smaller PRs"
|
|
165
|
-
)
|
|
157
|
+
suggestions.append("💡 Large number of files modified - consider breaking into smaller PRs")
|
|
166
158
|
|
|
167
159
|
# Build response
|
|
168
160
|
response = "🔍 CODE QUALITY REVIEW:\n\n"
|
|
@@ -173,9 +165,7 @@ class AutoCritic:
|
|
|
173
165
|
response += "✅ No major code quality issues detected.\n\n"
|
|
174
166
|
|
|
175
167
|
if suggestions:
|
|
176
|
-
response += (
|
|
177
|
-
"Suggestions for Improvement:\n" + "\n".join(suggestions) + "\n\n"
|
|
178
|
-
)
|
|
168
|
+
response += "Suggestions for Improvement:\n" + "\n".join(suggestions) + "\n\n"
|
|
179
169
|
|
|
180
170
|
if specific_concerns:
|
|
181
171
|
response += f"Regarding your concern: '{specific_concerns}'\n"
|
|
@@ -183,9 +173,7 @@ class AutoCritic:
|
|
|
183
173
|
response += "→ Imports look properly formatted. Ensure they're in the standard order: stdlib, external, internal.\n"
|
|
184
174
|
|
|
185
175
|
response += "\nOverall: " + (
|
|
186
|
-
"⚠️ Address the issues before proceeding."
|
|
187
|
-
if issues
|
|
188
|
-
else "✅ Good work, but always room for improvement!"
|
|
176
|
+
"⚠️ Address the issues before proceeding." if issues else "✅ Good work, but always room for improvement!"
|
|
189
177
|
)
|
|
190
178
|
|
|
191
179
|
return response
|
|
@@ -269,9 +257,7 @@ Security Checklist:
|
|
|
269
257
|
response = "🔍 COMPLETENESS REVIEW:\n\n"
|
|
270
258
|
|
|
271
259
|
if "fix" in tasks_mentioned and "test" not in tasks_mentioned:
|
|
272
|
-
response +=
|
|
273
|
-
"❌ No mention of tests - have you verified the fix with tests?\n"
|
|
274
|
-
)
|
|
260
|
+
response += "❌ No mention of tests - have you verified the fix with tests?\n"
|
|
275
261
|
|
|
276
262
|
if "import" in tasks_mentioned:
|
|
277
263
|
response += "✓ Import fixes mentioned\n"
|
|
@@ -382,8 +368,6 @@ class CriticProtocol:
|
|
|
382
368
|
except KeyError:
|
|
383
369
|
review_enum = ReviewType.GENERAL
|
|
384
370
|
|
|
385
|
-
review = self.auto_critic.review(
|
|
386
|
-
review_enum, work_description, code_snippets, file_paths, specific_concerns
|
|
387
|
-
)
|
|
371
|
+
review = self.auto_critic.review(review_enum, work_description, code_snippets, file_paths, specific_concerns)
|
|
388
372
|
|
|
389
373
|
return f"Review {self.review_count}/{self.max_reviews}:\n\n{review}"
|