hanzo-mcp 0.7.3__py3-none-any.whl → 0.7.7__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 (36) hide show
  1. hanzo_mcp/__init__.py +1 -1
  2. hanzo_mcp/cli.py +10 -0
  3. hanzo_mcp/prompts/__init__.py +43 -0
  4. hanzo_mcp/prompts/example_custom_prompt.py +40 -0
  5. hanzo_mcp/prompts/tool_explorer.py +603 -0
  6. hanzo_mcp/tools/__init__.py +52 -51
  7. hanzo_mcp/tools/agent/__init__.py +3 -16
  8. hanzo_mcp/tools/agent/agent_tool.py +365 -525
  9. hanzo_mcp/tools/agent/agent_tool_v1_deprecated.py +641 -0
  10. hanzo_mcp/tools/agent/network_tool.py +3 -5
  11. hanzo_mcp/tools/agent/swarm_tool.py +447 -349
  12. hanzo_mcp/tools/agent/swarm_tool_v1_deprecated.py +535 -0
  13. hanzo_mcp/tools/agent/tool_adapter.py +21 -2
  14. hanzo_mcp/tools/common/forgiving_edit.py +24 -14
  15. hanzo_mcp/tools/common/permissions.py +8 -0
  16. hanzo_mcp/tools/filesystem/__init__.py +5 -5
  17. hanzo_mcp/tools/filesystem/{symbols.py → ast_tool.py} +8 -8
  18. hanzo_mcp/tools/filesystem/batch_search.py +2 -2
  19. hanzo_mcp/tools/filesystem/directory_tree.py +8 -1
  20. hanzo_mcp/tools/filesystem/find.py +1 -0
  21. hanzo_mcp/tools/filesystem/grep.py +11 -2
  22. hanzo_mcp/tools/filesystem/read.py +8 -1
  23. hanzo_mcp/tools/filesystem/search_tool.py +1 -1
  24. hanzo_mcp/tools/jupyter/__init__.py +5 -1
  25. hanzo_mcp/tools/jupyter/base.py +2 -2
  26. hanzo_mcp/tools/jupyter/jupyter.py +89 -18
  27. hanzo_mcp/tools/search/find_tool.py +49 -8
  28. hanzo_mcp/tools/shell/base_process.py +7 -1
  29. hanzo_mcp/tools/shell/streaming_command.py +34 -1
  30. {hanzo_mcp-0.7.3.dist-info → hanzo_mcp-0.7.7.dist-info}/METADATA +7 -1
  31. {hanzo_mcp-0.7.3.dist-info → hanzo_mcp-0.7.7.dist-info}/RECORD +34 -32
  32. hanzo_mcp/tools/agent/agent_tool_v2.py +0 -492
  33. hanzo_mcp/tools/agent/swarm_tool_v2.py +0 -654
  34. {hanzo_mcp-0.7.3.dist-info → hanzo_mcp-0.7.7.dist-info}/WHEEL +0 -0
  35. {hanzo_mcp-0.7.3.dist-info → hanzo_mcp-0.7.7.dist-info}/entry_points.txt +0 -0
  36. {hanzo_mcp-0.7.3.dist-info → hanzo_mcp-0.7.7.dist-info}/top_level.txt +0 -0
@@ -66,8 +66,8 @@ class GrepAstToolParams(TypedDict):
66
66
 
67
67
 
68
68
  @final
69
- class SymbolsTool(FilesystemBaseTool):
70
- """Tool for searching and querying code symbols using tree-sitter AST parsing."""
69
+ class ASTTool(FilesystemBaseTool):
70
+ """Tool for searching and querying code structures using tree-sitter AST parsing."""
71
71
 
72
72
  @property
73
73
  @override
@@ -77,7 +77,7 @@ class SymbolsTool(FilesystemBaseTool):
77
77
  Returns:
78
78
  Tool name
79
79
  """
80
- return "symbols"
80
+ return "ast"
81
81
 
82
82
  @property
83
83
  @override
@@ -87,14 +87,14 @@ class SymbolsTool(FilesystemBaseTool):
87
87
  Returns:
88
88
  Tool description
89
89
  """
90
- return """Code symbols search with tree-sitter AST. Actions: search (default), index, query.
90
+ return """AST-based code structure search using tree-sitter. Find functions, classes, methods with full context.
91
91
 
92
92
  Usage:
93
- symbols "function_name" ./src
94
- symbols --action index --path ./src
95
- symbols --action query --type function --path ./src
93
+ ast "function_name" ./src
94
+ ast "class.*Service" ./src
95
+ ast "def test_" ./tests
96
96
 
97
- Finds code structures (functions, classes, methods) with full context."""
97
+ Searches code structure intelligently, understanding syntax and providing semantic context."""
98
98
 
99
99
  @override
100
100
  async def call(
@@ -26,7 +26,7 @@ from typing_extensions import Annotated, TypedDict, Unpack, final, override
26
26
 
27
27
  from hanzo_mcp.tools.filesystem.base import FilesystemBaseTool
28
28
  from hanzo_mcp.tools.filesystem.grep import Grep
29
- from hanzo_mcp.tools.filesystem.symbols import SymbolsTool
29
+ from hanzo_mcp.tools.filesystem.ast_tool import ASTTool
30
30
  from hanzo_mcp.tools.filesystem.git_search import GitSearchTool
31
31
  from hanzo_mcp.tools.vector.vector_search import VectorSearchTool
32
32
  from hanzo_mcp.tools.vector.ast_analyzer import ASTAnalyzer, Symbol
@@ -122,7 +122,7 @@ class BatchSearchTool(FilesystemBaseTool):
122
122
 
123
123
  # Initialize component search tools
124
124
  self.grep_tool = Grep(permission_manager)
125
- self.grep_ast_tool = SymbolsTool(permission_manager)
125
+ self.grep_ast_tool = ASTTool(permission_manager)
126
126
  self.git_search_tool = GitSearchTool(permission_manager)
127
127
  self.ast_analyzer = ASTAnalyzer()
128
128
 
@@ -11,6 +11,7 @@ from mcp.server import FastMCP
11
11
  from pydantic import Field
12
12
 
13
13
  from hanzo_mcp.tools.filesystem.base import FilesystemBaseTool
14
+ from hanzo_mcp.tools.common.truncate import truncate_response
14
15
 
15
16
  DirectoryPath = Annotated[
16
17
  str,
@@ -279,7 +280,13 @@ requested. Only works within allowed directories."""
279
280
  f"Generated directory tree for {path} (depth: {depth}, include_filtered: {include_filtered})"
280
281
  )
281
282
 
282
- return formatted_output + summary
283
+ # Truncate response to stay within token limits
284
+ full_response = formatted_output + summary
285
+ return truncate_response(
286
+ full_response,
287
+ max_tokens=25000,
288
+ truncation_message="\n\n[Response truncated due to token limit. Please use a smaller depth, specific subdirectory, or the paginated version of this tool.]"
289
+ )
283
290
  except Exception as e:
284
291
  await tool_ctx.error(f"Error generating directory tree: {str(e)}")
285
292
  return f"Error generating directory tree: {str(e)}"
@@ -17,6 +17,7 @@ from mcp.server.fastmcp import Context as MCPContext
17
17
  from pydantic import Field
18
18
 
19
19
  from hanzo_mcp.tools.common.context import ToolContext
20
+ from hanzo_mcp.tools.common.truncate import truncate_response
20
21
  from hanzo_mcp.tools.filesystem.base import FilesystemBaseTool
21
22
 
22
23
 
@@ -17,6 +17,7 @@ from mcp.server import FastMCP
17
17
  from pydantic import Field
18
18
 
19
19
  from hanzo_mcp.tools.common.context import ToolContext
20
+ from hanzo_mcp.tools.common.truncate import truncate_response
20
21
  from hanzo_mcp.tools.filesystem.base import FilesystemBaseTool
21
22
 
22
23
  Pattern = Annotated[
@@ -422,13 +423,21 @@ When you are doing an open ended search that may require multiple rounds of glob
422
423
  if self.is_ripgrep_installed():
423
424
  await tool_ctx.info("ripgrep is installed, using ripgrep for search")
424
425
  result = await self.run_ripgrep(pattern, path, tool_ctx, include)
425
- return result
426
+ return truncate_response(
427
+ result,
428
+ max_tokens=25000,
429
+ truncation_message="\n\n[Grep results truncated due to token limit. Use more specific patterns or paths to reduce output.]"
430
+ )
426
431
  else:
427
432
  await tool_ctx.info(
428
433
  "ripgrep is not installed, using fallback implementation"
429
434
  )
430
435
  result = await self.fallback_grep(pattern, path, tool_ctx, include)
431
- return result
436
+ return truncate_response(
437
+ result,
438
+ max_tokens=25000,
439
+ truncation_message="\n\n[Grep results truncated due to token limit. Use more specific patterns or paths to reduce output.]"
440
+ )
432
441
  except Exception as e:
433
442
  await tool_ctx.error(f"Error in grep tool: {str(e)}")
434
443
  return f"Error in grep tool: {str(e)}"
@@ -11,6 +11,7 @@ from mcp.server import FastMCP
11
11
  from pydantic import Field
12
12
 
13
13
  from hanzo_mcp.tools.filesystem.base import FilesystemBaseTool
14
+ from hanzo_mcp.tools.common.truncate import truncate_response
14
15
 
15
16
  FilePath = Annotated[
16
17
  str,
@@ -219,7 +220,13 @@ Usage:
219
220
  result += f"\n... (output truncated, showing {limit} of {limit + truncated_lines}+ lines)"
220
221
 
221
222
  await tool_ctx.info(f"Successfully read file: {file_path}")
222
- return result
223
+
224
+ # Apply token limit to prevent excessive output
225
+ return truncate_response(
226
+ result,
227
+ max_tokens=25000,
228
+ truncation_message="\n\n[File content truncated due to token limit. Use offset/limit parameters to read specific sections.]"
229
+ )
223
230
 
224
231
  except Exception as e:
225
232
  await tool_ctx.error(f"Error reading file: {str(e)}")
@@ -23,7 +23,7 @@ from pydantic import Field
23
23
 
24
24
  from hanzo_mcp.tools.filesystem.base import FilesystemBaseTool
25
25
  from hanzo_mcp.tools.filesystem.grep import Grep
26
- from hanzo_mcp.tools.filesystem.symbols import SymbolsTool
26
+ from hanzo_mcp.tools.filesystem.symbols_tool import SymbolsTool
27
27
  from hanzo_mcp.tools.filesystem.git_search import GitSearchTool
28
28
  from hanzo_mcp.tools.vector.vector_search import VectorSearchTool
29
29
  from hanzo_mcp.tools.common.permissions import PermissionManager
@@ -70,13 +70,17 @@ def register_jupyter_tools(
70
70
  }
71
71
 
72
72
  tools = []
73
+ added_classes = set() # Track which tool classes have been added
73
74
 
74
75
  if enabled_tools:
75
76
  # Use individual tool configuration
76
77
  for tool_name, enabled in enabled_tools.items():
77
78
  if enabled and tool_name in tool_classes:
78
79
  tool_class = tool_classes[tool_name]
79
- tools.append(tool_class(permission_manager))
80
+ # Avoid adding the same tool class multiple times
81
+ if tool_class not in added_classes:
82
+ tools.append(tool_class(permission_manager))
83
+ added_classes.add(tool_class)
80
84
  else:
81
85
  # Use all tools (backward compatibility)
82
86
  tools = get_jupyter_tools(permission_manager)
@@ -12,7 +12,7 @@ from typing import Any, final
12
12
 
13
13
  from mcp.server.fastmcp import Context as MCPContext
14
14
 
15
- from hanzo_mcp.tools.common.base import FileSystemTool
15
+ from hanzo_mcp.tools.filesystem.base import FilesystemBaseTool
16
16
  from hanzo_mcp.tools.common.context import ToolContext, create_tool_context
17
17
 
18
18
 
@@ -101,7 +101,7 @@ class NotebookCellSource:
101
101
  self.outputs = outputs or []
102
102
 
103
103
 
104
- class JupyterBaseTool(FileSystemTool, ABC):
104
+ class JupyterBaseTool(FilesystemBaseTool, ABC):
105
105
  """Base class for Jupyter notebook tools.
106
106
 
107
107
  Provides common functionality for working with Jupyter notebooks, including
@@ -185,10 +185,11 @@ jupyter --action create "new.ipynb"
185
185
  return error_msg
186
186
 
187
187
  source = params.get("source")
188
- if not source:
189
- return "Error: source is required for edit action"
190
-
191
188
  edit_mode = params.get("edit_mode", "replace")
189
+
190
+ # Only require source for non-delete operations
191
+ if edit_mode != "delete" and not source:
192
+ return "Error: source is required for edit action"
192
193
  cell_id = params.get("cell_id")
193
194
  cell_index = params.get("cell_index")
194
195
  cell_type = params.get("cell_type")
@@ -233,18 +234,18 @@ jupyter --action create "new.ipynb"
233
234
  if cell_id:
234
235
  for cell in nb.cells:
235
236
  if cell.get("id") == cell_id:
236
- cell.source = source
237
+ cell["source"] = source
237
238
  if cell_type:
238
- cell.cell_type = cell_type
239
+ cell["cell_type"] = cell_type
239
240
  self.write_notebook(nb, notebook_path)
240
241
  return f"Successfully updated cell with ID '{cell_id}'"
241
242
  return f"Error: Cell with ID '{cell_id}' not found"
242
243
 
243
244
  elif cell_index is not None:
244
245
  if 0 <= cell_index < len(nb.cells):
245
- nb.cells[cell_index].source = source
246
+ nb.cells[cell_index]["source"] = source
246
247
  if cell_type:
247
- nb.cells[cell_index].cell_type = cell_type
248
+ nb.cells[cell_index]["cell_type"] = cell_type
248
249
  self.write_notebook(nb, notebook_path)
249
250
  return f"Successfully updated cell at index {cell_index}"
250
251
  else:
@@ -303,21 +304,91 @@ jupyter --action create "new.ipynb"
303
304
 
304
305
  def _format_cell(self, cell: dict, index: int) -> str:
305
306
  """Format a single cell for display."""
306
- output = [f"Cell {index} ({cell.cell_type})"]
307
+ output = [f"Cell {index} ({cell.get('cell_type', 'unknown')})"]
307
308
  if cell.get("id"):
308
- output.append(f"ID: {cell.id}")
309
+ output.append(f"ID: {cell.get('id')}")
309
310
  output.append("-" * 40)
310
- output.append(cell.source)
311
+ # Get source content
312
+ source = cell.get("source", "")
313
+ if isinstance(source, list):
314
+ source = "".join(source)
315
+ output.append(source)
311
316
 
312
- if cell.cell_type == "code" and cell.get("outputs"):
317
+ if cell.get("cell_type") == "code" and cell.get("outputs"):
313
318
  output.append("\nOutputs:")
314
- for out in cell.outputs:
315
- if out.output_type == "stream":
316
- output.append(f"[{out.name}]: {out.text}")
317
- elif out.output_type == "execute_result":
318
- output.append(f"[Out {out.execution_count}]: {out.data}")
319
- elif out.output_type == "error":
320
- output.append(f"[Error]: {out.ename}: {out.evalue}")
319
+ for out in cell.get("outputs", []):
320
+ out_type = out.get("output_type", "")
321
+
322
+ if out_type == "stream":
323
+ text = out.get("text", "")
324
+ if isinstance(text, list):
325
+ text = "".join(text)
326
+ name = out.get("name", "stdout")
327
+ output.append(f"[{name}]: {text}")
328
+
329
+ elif out_type == "execute_result":
330
+ exec_count = out.get("execution_count", "?")
331
+ data = out.get("data", {})
332
+ # Try to get plain text representation
333
+ if "text/plain" in data:
334
+ text_data = data["text/plain"]
335
+ if isinstance(text_data, list):
336
+ text_data = "".join(text_data)
337
+ output.append(f"[Out {exec_count}]: {text_data}")
338
+ else:
339
+ output.append(f"[Out {exec_count}]: {data}")
340
+
341
+ elif out_type == "error":
342
+ ename = out.get("ename", "Error")
343
+ evalue = out.get("evalue", "")
344
+ output.append(f"[Error]: {ename}: {evalue}")
345
+ # Include traceback if available
346
+ traceback = out.get("traceback", [])
347
+ if traceback:
348
+ output.append("Traceback:")
349
+ for line in traceback:
350
+ output.append(f" {line}")
351
+
352
+ return "\n".join(output)
353
+
354
+ def read_notebook(self, notebook_path: str) -> Any:
355
+ """Read a notebook from disk using nbformat.
356
+
357
+ Args:
358
+ notebook_path: Path to the notebook file
359
+
360
+ Returns:
361
+ Notebook object
362
+ """
363
+ with open(notebook_path, 'r') as f:
364
+ return nbformat.read(f, as_version=4)
365
+
366
+ def write_notebook(self, nb: Any, notebook_path: str) -> None:
367
+ """Write a notebook to disk using nbformat.
368
+
369
+ Args:
370
+ nb: Notebook object to write
371
+ notebook_path: Path to write the notebook to
372
+ """
373
+ with open(notebook_path, 'w') as f:
374
+ nbformat.write(nb, f)
375
+
376
+ def format_notebook(self, nb: Any) -> str:
377
+ """Format an entire notebook for display.
378
+
379
+ Args:
380
+ nb: Notebook object
381
+
382
+ Returns:
383
+ Formatted string representation of the notebook
384
+ """
385
+ output = []
386
+ output.append(f"Notebook with {len(nb.cells)} cells")
387
+ output.append("=" * 50)
388
+
389
+ for i, cell in enumerate(nb.cells):
390
+ output.append("")
391
+ output.append(self._format_cell(cell, i))
321
392
 
322
393
  return "\n".join(output)
323
394
 
@@ -3,22 +3,25 @@
3
3
  import os
4
4
  import time
5
5
  import fnmatch
6
+ import re
6
7
  from typing import List, Optional, Dict, Any, Set
7
8
  from pathlib import Path
8
9
  from dataclasses import dataclass
9
10
  import subprocess
10
11
  import json
11
12
  from datetime import datetime
13
+ from difflib import SequenceMatcher
12
14
 
13
15
  from hanzo_mcp.tools.common.base import BaseTool
14
16
  from hanzo_mcp.tools.common.paginated_response import AutoPaginatedResponse
15
17
  from hanzo_mcp.tools.common.decorators import with_context_normalization
16
18
  from hanzo_mcp.types import MCPResourceDocument
17
19
 
20
+ # Check if ffind command is available
18
21
  try:
19
- import ffind
22
+ subprocess.run(['ffind', '--version'], capture_output=True, check=True)
20
23
  FFIND_AVAILABLE = True
21
- except ImportError:
24
+ except (subprocess.CalledProcessError, FileNotFoundError):
22
25
  FFIND_AVAILABLE = False
23
26
 
24
27
 
@@ -256,8 +259,8 @@ class FindTool(BaseTool):
256
259
  if FFIND_AVAILABLE and not in_content:
257
260
  # Use ffind for fast file discovery
258
261
  matches = await self._find_with_ffind(
259
- pattern, root_path, type, case_sensitive, regex,
260
- max_depth, follow_symlinks, ignore_patterns
262
+ pattern, root_path, type, case_sensitive, regex, fuzzy,
263
+ max_depth, follow_symlinks, respect_gitignore, ignore_patterns
261
264
  )
262
265
  else:
263
266
  # Fall back to Python implementation
@@ -392,8 +395,10 @@ class FindTool(BaseTool):
392
395
  file_type: Optional[str],
393
396
  case_sensitive: bool,
394
397
  regex: bool,
398
+ fuzzy: bool,
395
399
  max_depth: Optional[int],
396
400
  follow_symlinks: bool,
401
+ respect_gitignore: bool,
397
402
  ignore_patterns: Set[str]) -> List[FileMatch]:
398
403
  """Use ffind for fast file discovery."""
399
404
  matches = []
@@ -407,15 +412,51 @@ class FindTool(BaseTool):
407
412
  'follow_symlinks': follow_symlinks,
408
413
  }
409
414
 
415
+ if fuzzy:
416
+ ffind_args['fuzzy'] = True
417
+
410
418
  if max_depth:
411
419
  ffind_args['max_depth'] = max_depth
412
420
 
413
421
  try:
414
- # Run ffind
415
- results = ffind.find(**ffind_args)
422
+ # Build ffind command
423
+ cmd = ['ffind']
424
+
425
+ if not case_sensitive:
426
+ cmd.append('-i')
416
427
 
417
- for result in results:
418
- path = result['path']
428
+ if regex:
429
+ cmd.append('-E')
430
+
431
+ if fuzzy:
432
+ cmd.append('-f')
433
+
434
+ if follow_symlinks:
435
+ cmd.append('-L')
436
+
437
+ if max_depth:
438
+ cmd.extend(['-D', str(max_depth)])
439
+
440
+ # Add pattern and path
441
+ cmd.append(pattern)
442
+ cmd.append(str(root))
443
+
444
+ # Run ffind command
445
+ result = subprocess.run(cmd, capture_output=True, text=True, check=False)
446
+
447
+ if result.returncode != 0:
448
+ # Fall back to Python implementation on error
449
+ return await self._find_with_python(
450
+ pattern, root, file_type, case_sensitive, regex, fuzzy,
451
+ False, max_depth, follow_symlinks, respect_gitignore, ignore_patterns
452
+ )
453
+
454
+ # Parse results
455
+ results = result.stdout.strip().split('\n') if result.stdout else []
456
+
457
+ for path in results:
458
+ if not path: # Skip empty lines
459
+ continue
419
460
 
420
461
  # Check ignore patterns
421
462
  if self._should_ignore(path, ignore_patterns):
@@ -14,6 +14,7 @@ from mcp.server.fastmcp import Context as MCPContext
14
14
 
15
15
  from hanzo_mcp.tools.common.base import BaseTool
16
16
  from hanzo_mcp.tools.common.permissions import PermissionManager
17
+ from hanzo_mcp.tools.common.truncate import truncate_response
17
18
  # Import moved to __init__ to avoid circular import
18
19
 
19
20
 
@@ -184,7 +185,12 @@ class BaseProcessTool(BaseTool):
184
185
  else:
185
186
  if output.startswith("Command failed"):
186
187
  raise RuntimeError(output)
187
- return output
188
+ # Truncate output to prevent token limit issues
189
+ return truncate_response(
190
+ output,
191
+ max_tokens=25000,
192
+ truncation_message="\n\n[Command output truncated due to token limit. Output may be available in logs or files.]"
193
+ )
188
194
 
189
195
  async def execute_background(
190
196
  self,
@@ -174,6 +174,18 @@ class StreamingCommandTool(BaseProcessTool):
174
174
 
175
175
  return None
176
176
 
177
+ async def call(self, ctx: Any, **kwargs) -> Dict[str, Any]:
178
+ """MCP tool entry point.
179
+
180
+ Args:
181
+ ctx: MCP context
182
+ **kwargs: Tool arguments
183
+
184
+ Returns:
185
+ Tool result
186
+ """
187
+ return await self.run(**kwargs)
188
+
177
189
  async def run(
178
190
  self,
179
191
  command: Optional[str] = None,
@@ -591,4 +603,25 @@ class StreamingCommandTool(BaseProcessTool):
591
603
  },
592
604
  },
593
605
  "required": [], # No required fields for maximum forgiveness
594
- }
606
+ }
607
+
608
+ def get_command_args(self, command: str, **kwargs) -> List[str]:
609
+ """Get the command arguments for subprocess.
610
+
611
+ Args:
612
+ command: The command or script to run
613
+ **kwargs: Additional arguments (not used for shell commands)
614
+
615
+ Returns:
616
+ List of command arguments for subprocess
617
+ """
618
+ # For shell commands, we use shell=True, so return the command as-is
619
+ return [command]
620
+
621
+ def get_tool_name(self) -> str:
622
+ """Get the name of the tool being used.
623
+
624
+ Returns:
625
+ Tool name
626
+ """
627
+ return "streaming_command"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hanzo-mcp
3
- Version: 0.7.3
3
+ Version: 0.7.7
4
4
  Summary: The Zen of Hanzo MCP: One server to rule them all. The ultimate MCP that orchestrates all others.
5
5
  Author-email: Hanzo Industries Inc <dev@hanzo.ai>
6
6
  License: MIT
@@ -40,6 +40,10 @@ Requires-Dist: sphinx>=8.0.0; extra == "dev"
40
40
  Requires-Dist: sphinx-rtd-theme>=3.0.0; extra == "dev"
41
41
  Requires-Dist: myst-parser>=4.0.0; extra == "dev"
42
42
  Requires-Dist: sphinx-copybutton>=0.5.0; extra == "dev"
43
+ Requires-Dist: mypy>=1.10.0; extra == "dev"
44
+ Requires-Dist: types-aiofiles>=23.2.0; extra == "dev"
45
+ Requires-Dist: types-psutil>=5.9.5; extra == "dev"
46
+ Requires-Dist: types-setuptools>=69.5.0; extra == "dev"
43
47
  Provides-Extra: docs
44
48
  Requires-Dist: sphinx>=8.0.0; extra == "docs"
45
49
  Requires-Dist: sphinx-rtd-theme>=3.0.0; extra == "docs"
@@ -55,6 +59,8 @@ Requires-Dist: pytest-asyncio>=0.25.3; extra == "test"
55
59
  Requires-Dist: twisted; extra == "test"
56
60
  Provides-Extra: agents
57
61
  Requires-Dist: hanzo-agents>=0.1.0; extra == "agents"
62
+ Provides-Extra: memory
63
+ Requires-Dist: hanzo-memory>=1.0.0; extra == "memory"
58
64
  Provides-Extra: performance
59
65
  Requires-Dist: ujson>=5.7.0; extra == "performance"
60
66
  Requires-Dist: orjson>=3.9.0; extra == "performance"