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.

Files changed (154) hide show
  1. hanzo_mcp/__init__.py +2 -4
  2. hanzo_mcp/analytics/posthog_analytics.py +3 -9
  3. hanzo_mcp/bridge.py +9 -25
  4. hanzo_mcp/cli.py +6 -15
  5. hanzo_mcp/cli_enhanced.py +5 -14
  6. hanzo_mcp/cli_plugin.py +3 -9
  7. hanzo_mcp/config/settings.py +6 -20
  8. hanzo_mcp/config/tool_config.py +1 -3
  9. hanzo_mcp/core/base_agent.py +88 -88
  10. hanzo_mcp/core/model_registry.py +238 -210
  11. hanzo_mcp/dev_server.py +5 -15
  12. hanzo_mcp/prompts/__init__.py +2 -6
  13. hanzo_mcp/prompts/project_todo_reminder.py +3 -9
  14. hanzo_mcp/prompts/tool_explorer.py +1 -3
  15. hanzo_mcp/prompts/utils.py +7 -21
  16. hanzo_mcp/server.py +13 -6
  17. hanzo_mcp/tools/__init__.py +10 -24
  18. hanzo_mcp/tools/agent/__init__.py +2 -1
  19. hanzo_mcp/tools/agent/agent.py +10 -30
  20. hanzo_mcp/tools/agent/agent_tool.py +5 -15
  21. hanzo_mcp/tools/agent/agent_tool_v1_deprecated.py +14 -41
  22. hanzo_mcp/tools/agent/claude_desktop_auth.py +3 -9
  23. hanzo_mcp/tools/agent/cli_agent_base.py +7 -24
  24. hanzo_mcp/tools/agent/cli_tools.py +75 -74
  25. hanzo_mcp/tools/agent/code_auth.py +1 -3
  26. hanzo_mcp/tools/agent/code_auth_tool.py +2 -6
  27. hanzo_mcp/tools/agent/critic_tool.py +8 -24
  28. hanzo_mcp/tools/agent/iching_tool.py +12 -36
  29. hanzo_mcp/tools/agent/network_tool.py +7 -18
  30. hanzo_mcp/tools/agent/prompt.py +1 -5
  31. hanzo_mcp/tools/agent/review_tool.py +10 -25
  32. hanzo_mcp/tools/agent/swarm_alias.py +1 -3
  33. hanzo_mcp/tools/agent/swarm_tool.py +9 -29
  34. hanzo_mcp/tools/agent/swarm_tool_v1_deprecated.py +11 -39
  35. hanzo_mcp/tools/agent/unified_cli_tools.py +38 -38
  36. hanzo_mcp/tools/common/batch_tool.py +15 -45
  37. hanzo_mcp/tools/common/config_tool.py +9 -28
  38. hanzo_mcp/tools/common/context.py +1 -3
  39. hanzo_mcp/tools/common/critic_tool.py +1 -3
  40. hanzo_mcp/tools/common/decorators.py +2 -6
  41. hanzo_mcp/tools/common/enhanced_base.py +2 -6
  42. hanzo_mcp/tools/common/fastmcp_pagination.py +4 -12
  43. hanzo_mcp/tools/common/forgiving_edit.py +9 -28
  44. hanzo_mcp/tools/common/mode.py +1 -5
  45. hanzo_mcp/tools/common/paginated_base.py +3 -11
  46. hanzo_mcp/tools/common/paginated_response.py +10 -30
  47. hanzo_mcp/tools/common/pagination.py +3 -9
  48. hanzo_mcp/tools/common/permissions.py +38 -11
  49. hanzo_mcp/tools/common/personality.py +9 -34
  50. hanzo_mcp/tools/common/plugin_loader.py +3 -15
  51. hanzo_mcp/tools/common/stats.py +6 -18
  52. hanzo_mcp/tools/common/thinking_tool.py +1 -3
  53. hanzo_mcp/tools/common/tool_disable.py +2 -6
  54. hanzo_mcp/tools/common/tool_list.py +2 -6
  55. hanzo_mcp/tools/common/validation.py +1 -3
  56. hanzo_mcp/tools/config/config_tool.py +7 -13
  57. hanzo_mcp/tools/config/index_config.py +1 -3
  58. hanzo_mcp/tools/config/mode_tool.py +5 -15
  59. hanzo_mcp/tools/database/database_manager.py +3 -9
  60. hanzo_mcp/tools/database/graph.py +1 -3
  61. hanzo_mcp/tools/database/graph_add.py +3 -9
  62. hanzo_mcp/tools/database/graph_query.py +11 -34
  63. hanzo_mcp/tools/database/graph_remove.py +3 -9
  64. hanzo_mcp/tools/database/graph_search.py +6 -20
  65. hanzo_mcp/tools/database/graph_stats.py +11 -33
  66. hanzo_mcp/tools/database/sql.py +4 -12
  67. hanzo_mcp/tools/database/sql_query.py +6 -10
  68. hanzo_mcp/tools/database/sql_search.py +2 -6
  69. hanzo_mcp/tools/database/sql_stats.py +5 -15
  70. hanzo_mcp/tools/editor/neovim_command.py +1 -3
  71. hanzo_mcp/tools/editor/neovim_edit.py +2 -2
  72. hanzo_mcp/tools/editor/neovim_session.py +7 -13
  73. hanzo_mcp/tools/filesystem/__init__.py +2 -3
  74. hanzo_mcp/tools/filesystem/ast_multi_edit.py +14 -43
  75. hanzo_mcp/tools/filesystem/base.py +4 -12
  76. hanzo_mcp/tools/filesystem/batch_search.py +35 -115
  77. hanzo_mcp/tools/filesystem/content_replace.py +4 -12
  78. hanzo_mcp/tools/filesystem/diff.py +2 -10
  79. hanzo_mcp/tools/filesystem/directory_tree.py +9 -27
  80. hanzo_mcp/tools/filesystem/directory_tree_paginated.py +5 -15
  81. hanzo_mcp/tools/filesystem/edit.py +6 -18
  82. hanzo_mcp/tools/filesystem/find.py +3 -9
  83. hanzo_mcp/tools/filesystem/find_files.py +2 -6
  84. hanzo_mcp/tools/filesystem/git_search.py +9 -24
  85. hanzo_mcp/tools/filesystem/grep.py +9 -27
  86. hanzo_mcp/tools/filesystem/multi_edit.py +6 -18
  87. hanzo_mcp/tools/filesystem/read.py +8 -26
  88. hanzo_mcp/tools/filesystem/rules_tool.py +6 -17
  89. hanzo_mcp/tools/filesystem/search_tool.py +18 -62
  90. hanzo_mcp/tools/filesystem/symbols_tool.py +5 -15
  91. hanzo_mcp/tools/filesystem/tree.py +1 -3
  92. hanzo_mcp/tools/filesystem/watch.py +1 -3
  93. hanzo_mcp/tools/filesystem/write.py +1 -3
  94. hanzo_mcp/tools/jupyter/base.py +6 -20
  95. hanzo_mcp/tools/jupyter/jupyter.py +4 -12
  96. hanzo_mcp/tools/jupyter/notebook_edit.py +11 -35
  97. hanzo_mcp/tools/jupyter/notebook_read.py +2 -6
  98. hanzo_mcp/tools/llm/consensus_tool.py +8 -24
  99. hanzo_mcp/tools/llm/llm_manage.py +2 -6
  100. hanzo_mcp/tools/llm/llm_tool.py +17 -58
  101. hanzo_mcp/tools/llm/llm_unified.py +18 -59
  102. hanzo_mcp/tools/llm/provider_tools.py +1 -3
  103. hanzo_mcp/tools/lsp/lsp_tool.py +5 -17
  104. hanzo_mcp/tools/mcp/mcp_add.py +1 -3
  105. hanzo_mcp/tools/mcp/mcp_stats.py +1 -3
  106. hanzo_mcp/tools/mcp/mcp_tool.py +9 -23
  107. hanzo_mcp/tools/memory/__init__.py +10 -27
  108. hanzo_mcp/tools/memory/knowledge_tools.py +7 -25
  109. hanzo_mcp/tools/memory/memory_tools.py +6 -18
  110. hanzo_mcp/tools/search/find_tool.py +10 -32
  111. hanzo_mcp/tools/search/unified_search.py +24 -78
  112. hanzo_mcp/tools/shell/__init__.py +2 -2
  113. hanzo_mcp/tools/shell/auto_background.py +2 -6
  114. hanzo_mcp/tools/shell/base.py +1 -5
  115. hanzo_mcp/tools/shell/base_process.py +5 -7
  116. hanzo_mcp/tools/shell/bash_session.py +7 -24
  117. hanzo_mcp/tools/shell/bash_session_executor.py +5 -15
  118. hanzo_mcp/tools/shell/bash_tool.py +3 -7
  119. hanzo_mcp/tools/shell/command_executor.py +33 -86
  120. hanzo_mcp/tools/shell/logs.py +4 -16
  121. hanzo_mcp/tools/shell/npx.py +2 -8
  122. hanzo_mcp/tools/shell/npx_tool.py +1 -3
  123. hanzo_mcp/tools/shell/pkill.py +4 -12
  124. hanzo_mcp/tools/shell/process_tool.py +2 -8
  125. hanzo_mcp/tools/shell/processes.py +5 -17
  126. hanzo_mcp/tools/shell/run_background.py +1 -3
  127. hanzo_mcp/tools/shell/run_command.py +1 -3
  128. hanzo_mcp/tools/shell/run_command_windows.py +1 -3
  129. hanzo_mcp/tools/shell/session_manager.py +2 -6
  130. hanzo_mcp/tools/shell/session_storage.py +2 -6
  131. hanzo_mcp/tools/shell/streaming_command.py +7 -23
  132. hanzo_mcp/tools/shell/uvx.py +4 -14
  133. hanzo_mcp/tools/shell/uvx_background.py +2 -6
  134. hanzo_mcp/tools/shell/uvx_tool.py +1 -3
  135. hanzo_mcp/tools/shell/zsh_tool.py +12 -20
  136. hanzo_mcp/tools/todo/todo.py +1 -3
  137. hanzo_mcp/tools/todo/todo_read.py +3 -9
  138. hanzo_mcp/tools/todo/todo_write.py +6 -18
  139. hanzo_mcp/tools/vector/__init__.py +3 -9
  140. hanzo_mcp/tools/vector/ast_analyzer.py +6 -20
  141. hanzo_mcp/tools/vector/git_ingester.py +10 -30
  142. hanzo_mcp/tools/vector/index_tool.py +3 -9
  143. hanzo_mcp/tools/vector/infinity_store.py +7 -27
  144. hanzo_mcp/tools/vector/mock_infinity.py +1 -3
  145. hanzo_mcp/tools/vector/project_manager.py +4 -12
  146. hanzo_mcp/tools/vector/vector.py +2 -6
  147. hanzo_mcp/tools/vector/vector_index.py +8 -8
  148. hanzo_mcp/tools/vector/vector_search.py +7 -21
  149. {hanzo_mcp-0.8.11.dist-info → hanzo_mcp-0.8.14.dist-info}/METADATA +2 -2
  150. hanzo_mcp-0.8.14.dist-info/RECORD +193 -0
  151. hanzo_mcp-0.8.11.dist-info/RECORD +0 -193
  152. {hanzo_mcp-0.8.11.dist-info → hanzo_mcp-0.8.14.dist-info}/WHEEL +0 -0
  153. {hanzo_mcp-0.8.11.dist-info → hanzo_mcp-0.8.14.dist-info}/entry_points.txt +0 -0
  154. {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}"