claude-mpm 4.3.6__py3-none-any.whl → 4.3.12__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.
- claude_mpm/VERSION +1 -1
- claude_mpm/agents/BASE_PM.md +41 -8
- claude_mpm/agents/PM_INSTRUCTIONS.md +85 -43
- claude_mpm/agents/templates/clerk-ops.json +223 -0
- claude_mpm/agents/templates/data_engineer.json +41 -5
- claude_mpm/agents/templates/php-engineer.json +185 -0
- claude_mpm/agents/templates/research.json +20 -8
- claude_mpm/agents/templates/web_qa.json +25 -10
- claude_mpm/cli/__init__.py +41 -2
- claude_mpm/cli/commands/agents.py +2 -2
- claude_mpm/cli/commands/analyze.py +4 -4
- claude_mpm/cli/commands/cleanup.py +7 -7
- claude_mpm/cli/commands/configure_tui.py +2 -2
- claude_mpm/cli/commands/debug.py +2 -2
- claude_mpm/cli/commands/info.py +3 -4
- claude_mpm/cli/commands/mcp.py +8 -6
- claude_mpm/cli/commands/mcp_command_router.py +11 -0
- claude_mpm/cli/commands/mcp_config.py +157 -0
- claude_mpm/cli/commands/mcp_external_commands.py +241 -0
- claude_mpm/cli/commands/mcp_install_commands.py +73 -32
- claude_mpm/cli/commands/mcp_setup_external.py +829 -0
- claude_mpm/cli/commands/run.py +73 -3
- claude_mpm/cli/commands/search.py +285 -0
- claude_mpm/cli/parsers/base_parser.py +13 -0
- claude_mpm/cli/parsers/mcp_parser.py +17 -0
- claude_mpm/cli/parsers/run_parser.py +5 -0
- claude_mpm/cli/parsers/search_parser.py +239 -0
- claude_mpm/cli/startup_logging.py +20 -7
- claude_mpm/constants.py +1 -0
- claude_mpm/core/unified_agent_registry.py +7 -0
- claude_mpm/hooks/instruction_reinforcement.py +295 -0
- claude_mpm/services/agents/deployment/agent_deployment.py +28 -13
- claude_mpm/services/agents/deployment/agent_discovery_service.py +16 -6
- claude_mpm/services/agents/deployment/deployment_wrapper.py +59 -0
- claude_mpm/services/agents/deployment/multi_source_deployment_service.py +6 -4
- claude_mpm/services/cli/agent_cleanup_service.py +5 -0
- claude_mpm/services/mcp_config_manager.py +294 -0
- claude_mpm/services/mcp_gateway/config/configuration.py +17 -0
- claude_mpm/services/mcp_gateway/main.py +38 -0
- claude_mpm/services/mcp_gateway/tools/external_mcp_services.py +390 -0
- claude_mpm/utils/log_cleanup.py +17 -17
- claude_mpm/utils/subprocess_utils.py +6 -6
- {claude_mpm-4.3.6.dist-info → claude_mpm-4.3.12.dist-info}/METADATA +24 -1
- {claude_mpm-4.3.6.dist-info → claude_mpm-4.3.12.dist-info}/RECORD +48 -39
- claude_mpm/agents/templates/agent-manager.md +0 -619
- {claude_mpm-4.3.6.dist-info → claude_mpm-4.3.12.dist-info}/WHEEL +0 -0
- {claude_mpm-4.3.6.dist-info → claude_mpm-4.3.12.dist-info}/entry_points.txt +0 -0
- {claude_mpm-4.3.6.dist-info → claude_mpm-4.3.12.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-4.3.6.dist-info → claude_mpm-4.3.12.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
"""
|
|
2
|
+
External MCP Services Integration
|
|
3
|
+
==================================
|
|
4
|
+
|
|
5
|
+
Manages installation and basic setup of external MCP services like mcp-vector-search
|
|
6
|
+
and mcp-browser. These services run as separate MCP servers in Claude Desktop,
|
|
7
|
+
not as part of the Claude MPM MCP Gateway.
|
|
8
|
+
|
|
9
|
+
Note: As of the latest architecture, external services are registered as separate
|
|
10
|
+
MCP servers in Claude Desktop configuration, not as tools within the gateway.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import asyncio
|
|
14
|
+
import json
|
|
15
|
+
import subprocess
|
|
16
|
+
import sys
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
from typing import Any, Dict, List, Optional
|
|
19
|
+
|
|
20
|
+
from claude_mpm.services.mcp_gateway.core.base import BaseMCPService
|
|
21
|
+
from claude_mpm.services.mcp_gateway.tools.base_adapter import BaseMCPToolAdapter
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class ExternalMCPService(BaseMCPToolAdapter):
|
|
25
|
+
"""Base class for external MCP service integration."""
|
|
26
|
+
|
|
27
|
+
def __init__(self, service_name: str, package_name: str):
|
|
28
|
+
"""
|
|
29
|
+
Initialize external MCP service.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
service_name: Name of the service for MCP
|
|
33
|
+
package_name: Python package name to install/run
|
|
34
|
+
"""
|
|
35
|
+
super().__init__()
|
|
36
|
+
self.service_name = service_name
|
|
37
|
+
self.package_name = package_name
|
|
38
|
+
self.process = None
|
|
39
|
+
self._is_installed = False
|
|
40
|
+
|
|
41
|
+
async def initialize(self) -> bool:
|
|
42
|
+
"""Initialize the external service."""
|
|
43
|
+
try:
|
|
44
|
+
# Check if package is installed
|
|
45
|
+
self._is_installed = await self._check_installation()
|
|
46
|
+
|
|
47
|
+
if not self._is_installed:
|
|
48
|
+
self.logger.warning(f"{self.package_name} not installed, attempting installation...")
|
|
49
|
+
await self._install_package()
|
|
50
|
+
self._is_installed = await self._check_installation()
|
|
51
|
+
|
|
52
|
+
if not self._is_installed:
|
|
53
|
+
self.logger.error(f"Failed to install {self.package_name}")
|
|
54
|
+
return False
|
|
55
|
+
|
|
56
|
+
self.logger.info(f"{self.package_name} is available")
|
|
57
|
+
return True
|
|
58
|
+
|
|
59
|
+
except Exception as e:
|
|
60
|
+
self.logger.error(f"Failed to initialize {self.service_name}: {e}")
|
|
61
|
+
return False
|
|
62
|
+
|
|
63
|
+
async def _check_installation(self) -> bool:
|
|
64
|
+
"""Check if the package is installed."""
|
|
65
|
+
try:
|
|
66
|
+
result = subprocess.run(
|
|
67
|
+
[sys.executable, "-m", self.package_name.replace("-", "_"), "--help"],
|
|
68
|
+
capture_output=True,
|
|
69
|
+
text=True,
|
|
70
|
+
timeout=5
|
|
71
|
+
)
|
|
72
|
+
return result.returncode == 0
|
|
73
|
+
except (subprocess.TimeoutExpired, FileNotFoundError, subprocess.CalledProcessError):
|
|
74
|
+
return False
|
|
75
|
+
|
|
76
|
+
async def _install_package(self) -> bool:
|
|
77
|
+
"""Install the package using pip."""
|
|
78
|
+
try:
|
|
79
|
+
self.logger.info(f"Installing {self.package_name}...")
|
|
80
|
+
result = subprocess.run(
|
|
81
|
+
[sys.executable, "-m", "pip", "install", self.package_name],
|
|
82
|
+
capture_output=True,
|
|
83
|
+
text=True,
|
|
84
|
+
timeout=30
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
if result.returncode == 0:
|
|
88
|
+
self.logger.info(f"Successfully installed {self.package_name}")
|
|
89
|
+
return True
|
|
90
|
+
else:
|
|
91
|
+
self.logger.error(f"Failed to install {self.package_name}: {result.stderr}")
|
|
92
|
+
return False
|
|
93
|
+
|
|
94
|
+
except Exception as e:
|
|
95
|
+
self.logger.error(f"Error installing {self.package_name}: {e}")
|
|
96
|
+
return False
|
|
97
|
+
|
|
98
|
+
def get_definition(self) -> Dict[str, Any]:
|
|
99
|
+
"""Get service definition for MCP registration."""
|
|
100
|
+
return {
|
|
101
|
+
"name": self.service_name,
|
|
102
|
+
"description": f"External MCP service: {self.package_name}",
|
|
103
|
+
"type": "external_service",
|
|
104
|
+
"package": self.package_name,
|
|
105
|
+
"installed": self._is_installed
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
class MCPVectorSearchService(ExternalMCPService):
|
|
110
|
+
"""MCP Vector Search service integration."""
|
|
111
|
+
|
|
112
|
+
def __init__(self):
|
|
113
|
+
"""Initialize MCP Vector Search service."""
|
|
114
|
+
super().__init__("mcp-vector-search", "mcp-vector-search")
|
|
115
|
+
|
|
116
|
+
def get_definition(self) -> Dict[str, Any]:
|
|
117
|
+
"""Get tool definition for MCP registration."""
|
|
118
|
+
base_def = super().get_definition()
|
|
119
|
+
base_def.update({
|
|
120
|
+
"description": "Semantic code search powered by vector embeddings",
|
|
121
|
+
"tools": [
|
|
122
|
+
{
|
|
123
|
+
"name": "mcp__mcp-vector-search__search_code",
|
|
124
|
+
"description": "Search for code using semantic similarity",
|
|
125
|
+
"inputSchema": {
|
|
126
|
+
"type": "object",
|
|
127
|
+
"properties": {
|
|
128
|
+
"query": {"type": "string", "description": "The search query"},
|
|
129
|
+
"limit": {"type": "integer", "default": 10},
|
|
130
|
+
"similarity_threshold": {"type": "number", "default": 0.3},
|
|
131
|
+
"language": {"type": "string"},
|
|
132
|
+
"file_extensions": {"type": "array", "items": {"type": "string"}},
|
|
133
|
+
"files": {"type": "string"},
|
|
134
|
+
"class_name": {"type": "string"},
|
|
135
|
+
"function_name": {"type": "string"}
|
|
136
|
+
},
|
|
137
|
+
"required": ["query"]
|
|
138
|
+
}
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
"name": "mcp__mcp-vector-search__search_similar",
|
|
142
|
+
"description": "Find code similar to a specific file or function",
|
|
143
|
+
"inputSchema": {
|
|
144
|
+
"type": "object",
|
|
145
|
+
"properties": {
|
|
146
|
+
"file_path": {"type": "string", "description": "Path to the file"},
|
|
147
|
+
"function_name": {"type": "string"},
|
|
148
|
+
"limit": {"type": "integer", "default": 10},
|
|
149
|
+
"similarity_threshold": {"type": "number", "default": 0.3}
|
|
150
|
+
},
|
|
151
|
+
"required": ["file_path"]
|
|
152
|
+
}
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
"name": "mcp__mcp-vector-search__search_context",
|
|
156
|
+
"description": "Search for code based on contextual description",
|
|
157
|
+
"inputSchema": {
|
|
158
|
+
"type": "object",
|
|
159
|
+
"properties": {
|
|
160
|
+
"description": {"type": "string", "description": "Contextual description"},
|
|
161
|
+
"focus_areas": {"type": "array", "items": {"type": "string"}},
|
|
162
|
+
"limit": {"type": "integer", "default": 10}
|
|
163
|
+
},
|
|
164
|
+
"required": ["description"]
|
|
165
|
+
}
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
"name": "mcp__mcp-vector-search__get_project_status",
|
|
169
|
+
"description": "Get project indexing status and statistics",
|
|
170
|
+
"inputSchema": {
|
|
171
|
+
"type": "object",
|
|
172
|
+
"properties": {},
|
|
173
|
+
"required": []
|
|
174
|
+
}
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
"name": "mcp__mcp-vector-search__index_project",
|
|
178
|
+
"description": "Index or reindex the project codebase",
|
|
179
|
+
"inputSchema": {
|
|
180
|
+
"type": "object",
|
|
181
|
+
"properties": {
|
|
182
|
+
"force": {"type": "boolean", "default": False},
|
|
183
|
+
"file_extensions": {"type": "array", "items": {"type": "string"}}
|
|
184
|
+
},
|
|
185
|
+
"required": []
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
]
|
|
189
|
+
})
|
|
190
|
+
return base_def
|
|
191
|
+
|
|
192
|
+
async def invoke(self, tool_name: str, arguments: Dict[str, Any]) -> Dict[str, Any]:
|
|
193
|
+
"""Invoke a tool from mcp-vector-search."""
|
|
194
|
+
try:
|
|
195
|
+
# Extract the actual tool name (remove prefix)
|
|
196
|
+
actual_tool = tool_name.replace("mcp__mcp-vector-search__", "")
|
|
197
|
+
|
|
198
|
+
# Prepare the command
|
|
199
|
+
cmd = [
|
|
200
|
+
sys.executable, "-m", "mcp_vector_search",
|
|
201
|
+
"--tool", actual_tool,
|
|
202
|
+
"--args", json.dumps(arguments)
|
|
203
|
+
]
|
|
204
|
+
|
|
205
|
+
# Run the command
|
|
206
|
+
result = subprocess.run(
|
|
207
|
+
cmd,
|
|
208
|
+
capture_output=True,
|
|
209
|
+
text=True,
|
|
210
|
+
timeout=30,
|
|
211
|
+
cwd=Path.cwd() # Use current working directory for project context
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
if result.returncode == 0:
|
|
215
|
+
try:
|
|
216
|
+
return json.loads(result.stdout)
|
|
217
|
+
except json.JSONDecodeError:
|
|
218
|
+
return {"result": result.stdout}
|
|
219
|
+
else:
|
|
220
|
+
return {"error": result.stderr or "Tool invocation failed"}
|
|
221
|
+
|
|
222
|
+
except subprocess.TimeoutExpired:
|
|
223
|
+
return {"error": "Tool invocation timed out"}
|
|
224
|
+
except Exception as e:
|
|
225
|
+
return {"error": str(e)}
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
class MCPBrowserService(ExternalMCPService):
|
|
229
|
+
"""MCP Browser service integration."""
|
|
230
|
+
|
|
231
|
+
def __init__(self):
|
|
232
|
+
"""Initialize MCP Browser service."""
|
|
233
|
+
super().__init__("mcp-browser", "mcp-browser")
|
|
234
|
+
|
|
235
|
+
def get_definition(self) -> Dict[str, Any]:
|
|
236
|
+
"""Get tool definition for MCP registration."""
|
|
237
|
+
base_def = super().get_definition()
|
|
238
|
+
base_def.update({
|
|
239
|
+
"description": "Web browsing and content extraction capabilities",
|
|
240
|
+
"tools": [
|
|
241
|
+
{
|
|
242
|
+
"name": "mcp__mcp-browser__browse",
|
|
243
|
+
"description": "Browse a webpage and extract content",
|
|
244
|
+
"inputSchema": {
|
|
245
|
+
"type": "object",
|
|
246
|
+
"properties": {
|
|
247
|
+
"url": {"type": "string", "description": "URL to browse"},
|
|
248
|
+
"extract": {"type": "string", "description": "What to extract"}
|
|
249
|
+
},
|
|
250
|
+
"required": ["url"]
|
|
251
|
+
}
|
|
252
|
+
},
|
|
253
|
+
{
|
|
254
|
+
"name": "mcp__mcp-browser__search",
|
|
255
|
+
"description": "Search the web",
|
|
256
|
+
"inputSchema": {
|
|
257
|
+
"type": "object",
|
|
258
|
+
"properties": {
|
|
259
|
+
"query": {"type": "string", "description": "Search query"},
|
|
260
|
+
"num_results": {"type": "integer", "default": 10}
|
|
261
|
+
},
|
|
262
|
+
"required": ["query"]
|
|
263
|
+
}
|
|
264
|
+
},
|
|
265
|
+
{
|
|
266
|
+
"name": "mcp__mcp-browser__screenshot",
|
|
267
|
+
"description": "Take a screenshot of a webpage",
|
|
268
|
+
"inputSchema": {
|
|
269
|
+
"type": "object",
|
|
270
|
+
"properties": {
|
|
271
|
+
"url": {"type": "string", "description": "URL to screenshot"},
|
|
272
|
+
"full_page": {"type": "boolean", "default": False}
|
|
273
|
+
},
|
|
274
|
+
"required": ["url"]
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
]
|
|
278
|
+
})
|
|
279
|
+
return base_def
|
|
280
|
+
|
|
281
|
+
async def invoke(self, tool_name: str, arguments: Dict[str, Any]) -> Dict[str, Any]:
|
|
282
|
+
"""Invoke a tool from mcp-browser."""
|
|
283
|
+
try:
|
|
284
|
+
# Extract the actual tool name (remove prefix)
|
|
285
|
+
actual_tool = tool_name.replace("mcp__mcp-browser__", "")
|
|
286
|
+
|
|
287
|
+
# Prepare the command
|
|
288
|
+
cmd = [
|
|
289
|
+
sys.executable, "-m", "mcp_browser",
|
|
290
|
+
"--tool", actual_tool,
|
|
291
|
+
"--args", json.dumps(arguments)
|
|
292
|
+
]
|
|
293
|
+
|
|
294
|
+
# Run the command
|
|
295
|
+
result = subprocess.run(
|
|
296
|
+
cmd,
|
|
297
|
+
capture_output=True,
|
|
298
|
+
text=True,
|
|
299
|
+
timeout=30
|
|
300
|
+
)
|
|
301
|
+
|
|
302
|
+
if result.returncode == 0:
|
|
303
|
+
try:
|
|
304
|
+
return json.loads(result.stdout)
|
|
305
|
+
except json.JSONDecodeError:
|
|
306
|
+
return {"result": result.stdout}
|
|
307
|
+
else:
|
|
308
|
+
return {"error": result.stderr or "Tool invocation failed"}
|
|
309
|
+
|
|
310
|
+
except subprocess.TimeoutExpired:
|
|
311
|
+
return {"error": "Tool invocation timed out"}
|
|
312
|
+
except Exception as e:
|
|
313
|
+
return {"error": str(e)}
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
class ExternalMCPServiceManager:
|
|
317
|
+
"""Manager for external MCP services.
|
|
318
|
+
|
|
319
|
+
This manager is responsible for checking and installing Python packages
|
|
320
|
+
for external MCP services. The actual registration of these services
|
|
321
|
+
happens in Claude Desktop configuration as separate MCP servers.
|
|
322
|
+
|
|
323
|
+
Note: This class is maintained for backward compatibility and package
|
|
324
|
+
management. The actual tool registration is handled by separate MCP
|
|
325
|
+
server instances in Claude Desktop.
|
|
326
|
+
"""
|
|
327
|
+
|
|
328
|
+
def __init__(self):
|
|
329
|
+
"""Initialize the service manager."""
|
|
330
|
+
self.services: List[ExternalMCPService] = []
|
|
331
|
+
self.logger = None
|
|
332
|
+
|
|
333
|
+
async def initialize_services(self) -> List[ExternalMCPService]:
|
|
334
|
+
"""Initialize all external MCP services.
|
|
335
|
+
|
|
336
|
+
This method checks if external service packages are installed
|
|
337
|
+
and attempts to install them if missing. It does NOT register
|
|
338
|
+
them as tools in the gateway - they run as separate MCP servers.
|
|
339
|
+
"""
|
|
340
|
+
# Create service instances
|
|
341
|
+
services = [
|
|
342
|
+
MCPVectorSearchService(),
|
|
343
|
+
MCPBrowserService()
|
|
344
|
+
]
|
|
345
|
+
|
|
346
|
+
# Initialize each service
|
|
347
|
+
initialized_services = []
|
|
348
|
+
for service in services:
|
|
349
|
+
try:
|
|
350
|
+
if await service.initialize():
|
|
351
|
+
initialized_services.append(service)
|
|
352
|
+
if self.logger:
|
|
353
|
+
self.logger.info(f"Initialized external service: {service.service_name}")
|
|
354
|
+
else:
|
|
355
|
+
if self.logger:
|
|
356
|
+
self.logger.warning(f"Failed to initialize: {service.service_name}")
|
|
357
|
+
except Exception as e:
|
|
358
|
+
if self.logger:
|
|
359
|
+
self.logger.error(f"Error initializing {service.service_name}: {e}")
|
|
360
|
+
|
|
361
|
+
self.services = initialized_services
|
|
362
|
+
return initialized_services
|
|
363
|
+
|
|
364
|
+
def get_all_tools(self) -> List[Dict[str, Any]]:
|
|
365
|
+
"""Get all tool definitions from external services."""
|
|
366
|
+
all_tools = []
|
|
367
|
+
for service in self.services:
|
|
368
|
+
service_def = service.get_definition()
|
|
369
|
+
if "tools" in service_def:
|
|
370
|
+
all_tools.extend(service_def["tools"])
|
|
371
|
+
return all_tools
|
|
372
|
+
|
|
373
|
+
async def invoke_tool(self, tool_name: str, arguments: Dict[str, Any]) -> Dict[str, Any]:
|
|
374
|
+
"""Invoke a tool from any registered external service."""
|
|
375
|
+
# Find the service that handles this tool
|
|
376
|
+
for service in self.services:
|
|
377
|
+
if tool_name.startswith(f"mcp__{service.service_name}__"):
|
|
378
|
+
if isinstance(service, (MCPVectorSearchService, MCPBrowserService)):
|
|
379
|
+
return await service.invoke(tool_name, arguments)
|
|
380
|
+
|
|
381
|
+
return {"error": f"No service found for tool: {tool_name}"}
|
|
382
|
+
|
|
383
|
+
async def shutdown(self):
|
|
384
|
+
"""Shutdown all external services."""
|
|
385
|
+
for service in self.services:
|
|
386
|
+
try:
|
|
387
|
+
await service.shutdown()
|
|
388
|
+
except Exception as e:
|
|
389
|
+
if self.logger:
|
|
390
|
+
self.logger.warning(f"Error shutting down {service.service_name}: {e}")
|
claude_mpm/utils/log_cleanup.py
CHANGED
|
@@ -9,7 +9,7 @@ import gzip
|
|
|
9
9
|
import logging
|
|
10
10
|
import os
|
|
11
11
|
import shutil
|
|
12
|
-
from datetime import datetime, timedelta
|
|
12
|
+
from datetime import datetime, timedelta, timezone
|
|
13
13
|
from pathlib import Path
|
|
14
14
|
from typing import Dict, Optional, Tuple
|
|
15
15
|
|
|
@@ -92,7 +92,7 @@ class LogCleanupUtility:
|
|
|
92
92
|
logger.info(f"Sessions directory not found: {sessions_dir}")
|
|
93
93
|
return 0, 0.0
|
|
94
94
|
|
|
95
|
-
cutoff_time = datetime.now() - timedelta(days=max_age_days)
|
|
95
|
+
cutoff_time = datetime.now(timezone.utc) - timedelta(days=max_age_days)
|
|
96
96
|
removed_count = 0
|
|
97
97
|
total_size = 0.0
|
|
98
98
|
|
|
@@ -107,7 +107,7 @@ class LogCleanupUtility:
|
|
|
107
107
|
|
|
108
108
|
try:
|
|
109
109
|
# Check directory modification time
|
|
110
|
-
mtime = datetime.fromtimestamp(session_dir.stat().st_mtime)
|
|
110
|
+
mtime = datetime.fromtimestamp(session_dir.stat().st_mtime, tz=timezone.utc)
|
|
111
111
|
|
|
112
112
|
if mtime < cutoff_time:
|
|
113
113
|
# Calculate directory size
|
|
@@ -117,14 +117,14 @@ class LogCleanupUtility:
|
|
|
117
117
|
if dry_run:
|
|
118
118
|
logger.info(
|
|
119
119
|
f"[DRY RUN] Would remove session: {session_dir.name} "
|
|
120
|
-
f"(age: {(datetime.now() - mtime).days} days, "
|
|
120
|
+
f"(age: {(datetime.now(timezone.utc) - mtime).days} days, "
|
|
121
121
|
f"size: {dir_size:.2f} MB)"
|
|
122
122
|
)
|
|
123
123
|
else:
|
|
124
124
|
shutil.rmtree(session_dir)
|
|
125
125
|
logger.info(
|
|
126
126
|
f"Removed session: {session_dir.name} "
|
|
127
|
-
f"(age: {(datetime.now() - mtime).days} days, "
|
|
127
|
+
f"(age: {(datetime.now(timezone.utc) - mtime).days} days, "
|
|
128
128
|
f"size: {dir_size:.2f} MB)"
|
|
129
129
|
)
|
|
130
130
|
|
|
@@ -159,7 +159,7 @@ class LogCleanupUtility:
|
|
|
159
159
|
Returns:
|
|
160
160
|
Tuple of (files removed, space freed in MB)
|
|
161
161
|
"""
|
|
162
|
-
cutoff_time = datetime.now() - timedelta(days=max_age_days)
|
|
162
|
+
cutoff_time = datetime.now(timezone.utc) - timedelta(days=max_age_days)
|
|
163
163
|
removed_count = 0
|
|
164
164
|
total_size = 0.0
|
|
165
165
|
|
|
@@ -169,7 +169,7 @@ class LogCleanupUtility:
|
|
|
169
169
|
for ext in LogCleanupConfig.ARCHIVE_EXTENSIONS:
|
|
170
170
|
for archive_file in self.base_log_dir.rglob(f"*{ext}"):
|
|
171
171
|
try:
|
|
172
|
-
mtime = datetime.fromtimestamp(archive_file.stat().st_mtime)
|
|
172
|
+
mtime = datetime.fromtimestamp(archive_file.stat().st_mtime, tz=timezone.utc)
|
|
173
173
|
|
|
174
174
|
if mtime < cutoff_time:
|
|
175
175
|
file_size = archive_file.stat().st_size / (1024 * 1024) # MB
|
|
@@ -178,14 +178,14 @@ class LogCleanupUtility:
|
|
|
178
178
|
if dry_run:
|
|
179
179
|
logger.info(
|
|
180
180
|
f"[DRY RUN] Would remove archive: {archive_file.name} "
|
|
181
|
-
f"(age: {(datetime.now() - mtime).days} days, "
|
|
181
|
+
f"(age: {(datetime.now(timezone.utc) - mtime).days} days, "
|
|
182
182
|
f"size: {file_size:.2f} MB)"
|
|
183
183
|
)
|
|
184
184
|
else:
|
|
185
185
|
archive_file.unlink()
|
|
186
186
|
logger.info(
|
|
187
187
|
f"Removed archive: {archive_file.name} "
|
|
188
|
-
f"(age: {(datetime.now() - mtime).days} days, "
|
|
188
|
+
f"(age: {(datetime.now(timezone.utc) - mtime).days} days, "
|
|
189
189
|
f"size: {file_size:.2f} MB)"
|
|
190
190
|
)
|
|
191
191
|
|
|
@@ -218,7 +218,7 @@ class LogCleanupUtility:
|
|
|
218
218
|
Returns:
|
|
219
219
|
Tuple of (files removed, space freed in MB)
|
|
220
220
|
"""
|
|
221
|
-
cutoff_time = datetime.now() - timedelta(days=max_age_days)
|
|
221
|
+
cutoff_time = datetime.now(timezone.utc) - timedelta(days=max_age_days)
|
|
222
222
|
removed_count = 0
|
|
223
223
|
total_size = 0.0
|
|
224
224
|
|
|
@@ -238,7 +238,7 @@ class LogCleanupUtility:
|
|
|
238
238
|
|
|
239
239
|
for log_file in log_dir.glob(pattern):
|
|
240
240
|
try:
|
|
241
|
-
mtime = datetime.fromtimestamp(log_file.stat().st_mtime)
|
|
241
|
+
mtime = datetime.fromtimestamp(log_file.stat().st_mtime, tz=timezone.utc)
|
|
242
242
|
|
|
243
243
|
if mtime < cutoff_time:
|
|
244
244
|
file_size = log_file.stat().st_size / (1024 * 1024) # MB
|
|
@@ -247,14 +247,14 @@ class LogCleanupUtility:
|
|
|
247
247
|
if dry_run:
|
|
248
248
|
logger.info(
|
|
249
249
|
f"[DRY RUN] Would remove log: {log_file.name} "
|
|
250
|
-
f"(age: {(datetime.now() - mtime).days} days, "
|
|
250
|
+
f"(age: {(datetime.now(timezone.utc) - mtime).days} days, "
|
|
251
251
|
f"size: {file_size:.2f} MB)"
|
|
252
252
|
)
|
|
253
253
|
else:
|
|
254
254
|
log_file.unlink()
|
|
255
255
|
logger.info(
|
|
256
256
|
f"Removed log: {log_file.name} "
|
|
257
|
-
f"(age: {(datetime.now() - mtime).days} days, "
|
|
257
|
+
f"(age: {(datetime.now(timezone.utc) - mtime).days} days, "
|
|
258
258
|
f"size: {file_size:.2f} MB)"
|
|
259
259
|
)
|
|
260
260
|
|
|
@@ -321,7 +321,7 @@ class LogCleanupUtility:
|
|
|
321
321
|
Returns:
|
|
322
322
|
Tuple of (files compressed, space saved in MB)
|
|
323
323
|
"""
|
|
324
|
-
cutoff_time = datetime.now() - timedelta(days=age_days)
|
|
324
|
+
cutoff_time = datetime.now(timezone.utc) - timedelta(days=age_days)
|
|
325
325
|
compressed_count = 0
|
|
326
326
|
space_saved = 0.0
|
|
327
327
|
|
|
@@ -331,7 +331,7 @@ class LogCleanupUtility:
|
|
|
331
331
|
continue
|
|
332
332
|
|
|
333
333
|
try:
|
|
334
|
-
mtime = datetime.fromtimestamp(log_file.stat().st_mtime)
|
|
334
|
+
mtime = datetime.fromtimestamp(log_file.stat().st_mtime, tz=timezone.utc)
|
|
335
335
|
|
|
336
336
|
if mtime < cutoff_time:
|
|
337
337
|
original_size = log_file.stat().st_size / (1024 * 1024) # MB
|
|
@@ -408,7 +408,7 @@ class LogCleanupUtility:
|
|
|
408
408
|
stats["oldest_session"] = {
|
|
409
409
|
"name": oldest.name,
|
|
410
410
|
"age_days": (
|
|
411
|
-
datetime.now() - datetime.fromtimestamp(oldest.stat().st_mtime)
|
|
411
|
+
datetime.now(timezone.utc) - datetime.fromtimestamp(oldest.stat().st_mtime, tz=timezone.utc)
|
|
412
412
|
).days,
|
|
413
413
|
}
|
|
414
414
|
|
|
@@ -429,7 +429,7 @@ class LogCleanupUtility:
|
|
|
429
429
|
"name": oldest_log.name,
|
|
430
430
|
"path": str(oldest_log.relative_to(self.base_log_dir)),
|
|
431
431
|
"age_days": (
|
|
432
|
-
datetime.now() - datetime.fromtimestamp(oldest_log.stat().st_mtime)
|
|
432
|
+
datetime.now(timezone.utc) - datetime.fromtimestamp(oldest_log.stat().st_mtime, tz=timezone.utc)
|
|
433
433
|
).days,
|
|
434
434
|
}
|
|
435
435
|
|
|
@@ -83,7 +83,7 @@ def run_command(command_string: str, timeout: float = 60) -> str:
|
|
|
83
83
|
returncode=getattr(e, "returncode", None),
|
|
84
84
|
stdout=getattr(e, "stdout", ""),
|
|
85
85
|
stderr=stderr,
|
|
86
|
-
)
|
|
86
|
+
) from e
|
|
87
87
|
|
|
88
88
|
|
|
89
89
|
def run_subprocess(
|
|
@@ -137,16 +137,16 @@ def run_subprocess(
|
|
|
137
137
|
returncode=None,
|
|
138
138
|
stdout=e.stdout.decode() if e.stdout else "",
|
|
139
139
|
stderr=e.stderr.decode() if e.stderr else "",
|
|
140
|
-
)
|
|
140
|
+
) from e
|
|
141
141
|
except subprocess.CalledProcessError as e:
|
|
142
142
|
raise SubprocessError(
|
|
143
143
|
f"Command failed with return code {e.returncode}: {' '.join(cmd)}",
|
|
144
144
|
returncode=e.returncode,
|
|
145
145
|
stdout=e.stdout if e.stdout else "",
|
|
146
146
|
stderr=e.stderr if e.stderr else "",
|
|
147
|
-
)
|
|
147
|
+
) from e
|
|
148
148
|
except Exception as e:
|
|
149
|
-
raise SubprocessError(f"Subprocess execution failed: {e}")
|
|
149
|
+
raise SubprocessError(f"Subprocess execution failed: {e}") from e
|
|
150
150
|
|
|
151
151
|
|
|
152
152
|
async def run_subprocess_async(
|
|
@@ -195,7 +195,7 @@ async def run_subprocess_async(
|
|
|
195
195
|
await process.wait()
|
|
196
196
|
raise SubprocessError(
|
|
197
197
|
f"Command timed out after {timeout}s: {' '.join(cmd)}", returncode=None
|
|
198
|
-
)
|
|
198
|
+
) from None
|
|
199
199
|
|
|
200
200
|
return SubprocessResult(
|
|
201
201
|
returncode=process.returncode,
|
|
@@ -205,7 +205,7 @@ async def run_subprocess_async(
|
|
|
205
205
|
|
|
206
206
|
except Exception as e:
|
|
207
207
|
if not isinstance(e, SubprocessError):
|
|
208
|
-
raise SubprocessError(f"Async subprocess execution failed: {e}")
|
|
208
|
+
raise SubprocessError(f"Async subprocess execution failed: {e}") from e
|
|
209
209
|
raise
|
|
210
210
|
|
|
211
211
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: claude-mpm
|
|
3
|
-
Version: 4.3.
|
|
3
|
+
Version: 4.3.12
|
|
4
4
|
Summary: Claude Multi-Agent Project Manager - Orchestrate Claude with agent delegation and ticket tracking
|
|
5
5
|
Author-email: Bob Matsuoka <bob@matsuoka.com>
|
|
6
6
|
Maintainer: Claude MPM Team
|
|
@@ -42,6 +42,9 @@ Requires-Dist: mistune>=3.0.0
|
|
|
42
42
|
Requires-Dist: tree-sitter>=0.21.0
|
|
43
43
|
Requires-Dist: ijson>=3.2.0
|
|
44
44
|
Requires-Dist: mcp>=0.1.0
|
|
45
|
+
Requires-Dist: mcp-vector-search>=0.1.0
|
|
46
|
+
Requires-Dist: mcp-browser>=0.1.0
|
|
47
|
+
Requires-Dist: mcp-ticketer>=0.1.0
|
|
45
48
|
Requires-Dist: toml>=0.10.2
|
|
46
49
|
Requires-Dist: packaging>=21.0
|
|
47
50
|
Requires-Dist: pydantic>=2.0.0
|
|
@@ -120,6 +123,26 @@ Requires-Dist: tree-sitter-cpp>=0.21.0; extra == "agents"
|
|
|
120
123
|
Requires-Dist: tree-sitter-c>=0.21.0; extra == "agents"
|
|
121
124
|
Requires-Dist: tree-sitter-ruby>=0.21.0; extra == "agents"
|
|
122
125
|
Requires-Dist: tree-sitter-php>=0.21.0; extra == "agents"
|
|
126
|
+
Provides-Extra: data-processing
|
|
127
|
+
Requires-Dist: pandas>=2.1.0; extra == "data-processing"
|
|
128
|
+
Requires-Dist: openpyxl>=3.1.0; extra == "data-processing"
|
|
129
|
+
Requires-Dist: xlsxwriter>=3.1.0; extra == "data-processing"
|
|
130
|
+
Requires-Dist: numpy>=1.24.0; extra == "data-processing"
|
|
131
|
+
Requires-Dist: pyarrow>=14.0.0; extra == "data-processing"
|
|
132
|
+
Requires-Dist: dask>=2023.12.0; extra == "data-processing"
|
|
133
|
+
Requires-Dist: polars>=0.19.0; extra == "data-processing"
|
|
134
|
+
Requires-Dist: xlrd>=2.0.0; extra == "data-processing"
|
|
135
|
+
Requires-Dist: xlwt>=1.3.0; extra == "data-processing"
|
|
136
|
+
Requires-Dist: csvkit>=1.3.0; extra == "data-processing"
|
|
137
|
+
Requires-Dist: tabulate>=0.9.0; extra == "data-processing"
|
|
138
|
+
Requires-Dist: python-dateutil>=2.8.0; extra == "data-processing"
|
|
139
|
+
Requires-Dist: lxml>=4.9.0; extra == "data-processing"
|
|
140
|
+
Requires-Dist: sqlalchemy>=2.0.0; extra == "data-processing"
|
|
141
|
+
Requires-Dist: psycopg2-binary>=2.9.0; extra == "data-processing"
|
|
142
|
+
Requires-Dist: pymongo>=4.5.0; extra == "data-processing"
|
|
143
|
+
Requires-Dist: redis>=5.0.0; extra == "data-processing"
|
|
144
|
+
Requires-Dist: beautifulsoup4>=4.12.0; extra == "data-processing"
|
|
145
|
+
Requires-Dist: jsonschema>=4.19.0; extra == "data-processing"
|
|
123
146
|
Dynamic: license-file
|
|
124
147
|
|
|
125
148
|
# Claude MPM - Multi-Agent Project Manager
|