claude-mpm 4.0.28__py3-none-any.whl → 4.0.30__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/agents/BASE_AGENT_TEMPLATE.md +48 -3
- claude_mpm/agents/BASE_PM.md +20 -15
- claude_mpm/agents/INSTRUCTIONS.md +12 -2
- claude_mpm/agents/templates/agent-manager.json +24 -0
- claude_mpm/agents/templates/agent-manager.md +304 -0
- claude_mpm/agents/templates/documentation.json +16 -3
- claude_mpm/agents/templates/engineer.json +19 -5
- claude_mpm/agents/templates/ops.json +19 -5
- claude_mpm/agents/templates/qa.json +16 -3
- claude_mpm/agents/templates/refactoring_engineer.json +25 -7
- claude_mpm/agents/templates/research.json +19 -5
- claude_mpm/cli/__init__.py +4 -0
- claude_mpm/cli/commands/__init__.py +4 -0
- claude_mpm/cli/commands/agent_manager.py +521 -0
- claude_mpm/cli/commands/agents.py +2 -1
- claude_mpm/cli/commands/cleanup.py +1 -1
- claude_mpm/cli/commands/doctor.py +209 -0
- claude_mpm/cli/commands/mcp.py +3 -3
- claude_mpm/cli/commands/mcp_install_commands.py +12 -30
- claude_mpm/cli/commands/mcp_server_commands.py +9 -9
- claude_mpm/cli/commands/memory.py +1 -1
- claude_mpm/cli/commands/run.py +31 -2
- claude_mpm/cli/commands/run_config_checker.py +1 -1
- claude_mpm/cli/parsers/agent_manager_parser.py +247 -0
- claude_mpm/cli/parsers/base_parser.py +12 -1
- claude_mpm/cli/parsers/mcp_parser.py +1 -1
- claude_mpm/cli/parsers/run_parser.py +1 -1
- claude_mpm/cli/shared/__init__.py +1 -1
- claude_mpm/cli/startup_logging.py +463 -0
- claude_mpm/constants.py +2 -0
- claude_mpm/core/claude_runner.py +81 -2
- claude_mpm/core/constants.py +2 -2
- claude_mpm/core/framework_loader.py +45 -11
- claude_mpm/core/interactive_session.py +82 -3
- claude_mpm/core/output_style_manager.py +6 -6
- claude_mpm/core/socketio_pool.py +2 -2
- claude_mpm/core/unified_paths.py +128 -0
- claude_mpm/dashboard/static/built/components/event-viewer.js +1 -1
- claude_mpm/dashboard/static/built/components/module-viewer.js +1 -1
- claude_mpm/dashboard/static/built/dashboard.js +1 -1
- claude_mpm/dashboard/static/built/socket-client.js +1 -1
- claude_mpm/dashboard/static/css/dashboard.css +170 -0
- claude_mpm/dashboard/static/dist/components/module-viewer.js +1 -1
- claude_mpm/dashboard/static/dist/dashboard.js +1 -1
- claude_mpm/dashboard/static/dist/socket-client.js +1 -1
- claude_mpm/dashboard/static/js/components/file-tool-tracker.js +21 -3
- claude_mpm/dashboard/static/js/components/module-viewer.js +129 -1
- claude_mpm/dashboard/static/js/dashboard.js +116 -0
- claude_mpm/dashboard/static/js/socket-client.js +0 -1
- claude_mpm/hooks/claude_hooks/connection_pool.py +1 -1
- claude_mpm/hooks/claude_hooks/hook_handler.py +1 -1
- claude_mpm/scripts/mcp_server.py +2 -2
- claude_mpm/services/agents/agent_builder.py +455 -0
- claude_mpm/services/agents/deployment/agent_template_builder.py +10 -3
- claude_mpm/services/agents/deployment/agent_validator.py +1 -0
- claude_mpm/services/agents/deployment/multi_source_deployment_service.py +69 -1
- claude_mpm/services/diagnostics/__init__.py +18 -0
- claude_mpm/services/diagnostics/checks/__init__.py +30 -0
- claude_mpm/services/diagnostics/checks/agent_check.py +319 -0
- claude_mpm/services/diagnostics/checks/base_check.py +64 -0
- claude_mpm/services/diagnostics/checks/claude_desktop_check.py +283 -0
- claude_mpm/services/diagnostics/checks/common_issues_check.py +354 -0
- claude_mpm/services/diagnostics/checks/configuration_check.py +300 -0
- claude_mpm/services/diagnostics/checks/filesystem_check.py +233 -0
- claude_mpm/services/diagnostics/checks/installation_check.py +255 -0
- claude_mpm/services/diagnostics/checks/mcp_check.py +315 -0
- claude_mpm/services/diagnostics/checks/monitor_check.py +282 -0
- claude_mpm/services/diagnostics/checks/startup_log_check.py +322 -0
- claude_mpm/services/diagnostics/diagnostic_runner.py +247 -0
- claude_mpm/services/diagnostics/doctor_reporter.py +283 -0
- claude_mpm/services/diagnostics/models.py +120 -0
- claude_mpm/services/mcp_gateway/core/interfaces.py +1 -1
- claude_mpm/services/mcp_gateway/main.py +1 -1
- claude_mpm/services/mcp_gateway/server/mcp_gateway.py +3 -3
- claude_mpm/services/mcp_gateway/server/stdio_handler.py +1 -1
- claude_mpm/services/mcp_gateway/server/stdio_server.py +3 -3
- claude_mpm/services/mcp_gateway/tools/ticket_tools.py +2 -2
- claude_mpm/services/memory/__init__.py +2 -0
- claude_mpm/services/socketio/handlers/connection.py +27 -33
- claude_mpm/services/socketio/handlers/registry.py +39 -7
- claude_mpm/services/socketio/server/core.py +72 -22
- claude_mpm/validation/frontmatter_validator.py +1 -1
- {claude_mpm-4.0.28.dist-info → claude_mpm-4.0.30.dist-info}/METADATA +4 -1
- {claude_mpm-4.0.28.dist-info → claude_mpm-4.0.30.dist-info}/RECORD +89 -67
- /claude_mpm/cli/shared/{command_base.py → base_command.py} +0 -0
- {claude_mpm-4.0.28.dist-info → claude_mpm-4.0.30.dist-info}/WHEEL +0 -0
- {claude_mpm-4.0.28.dist-info → claude_mpm-4.0.30.dist-info}/entry_points.txt +0 -0
- {claude_mpm-4.0.28.dist-info → claude_mpm-4.0.30.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-4.0.28.dist-info → claude_mpm-4.0.30.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Check agent deployment and health.
|
|
3
|
+
|
|
4
|
+
WHY: Verify that agents are properly deployed, up-to-date, and functioning correctly.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Dict, Any, List
|
|
9
|
+
|
|
10
|
+
from ..models import DiagnosticResult, DiagnosticStatus
|
|
11
|
+
from .base_check import BaseDiagnosticCheck
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class AgentCheck(BaseDiagnosticCheck):
|
|
15
|
+
"""Check agent deployment and configuration."""
|
|
16
|
+
|
|
17
|
+
@property
|
|
18
|
+
def name(self) -> str:
|
|
19
|
+
return "agent_check"
|
|
20
|
+
|
|
21
|
+
@property
|
|
22
|
+
def category(self) -> str:
|
|
23
|
+
return "Agents"
|
|
24
|
+
|
|
25
|
+
def run(self) -> DiagnosticResult:
|
|
26
|
+
"""Run agent diagnostics."""
|
|
27
|
+
try:
|
|
28
|
+
from ....services.agents.deployment.agent_validator import AgentValidator
|
|
29
|
+
from ....services.agents.deployment.agent_discovery_service import AgentDiscoveryService
|
|
30
|
+
|
|
31
|
+
sub_results = []
|
|
32
|
+
details = {}
|
|
33
|
+
|
|
34
|
+
# Get available agents
|
|
35
|
+
discovery = AgentDiscoveryService()
|
|
36
|
+
available_agents = discovery.discover_available_agents()
|
|
37
|
+
details["available_count"] = len(available_agents)
|
|
38
|
+
details["available_agents"] = [a.name for a in available_agents]
|
|
39
|
+
|
|
40
|
+
# Check deployed agents
|
|
41
|
+
deployed_result = self._check_deployed_agents()
|
|
42
|
+
sub_results.append(deployed_result)
|
|
43
|
+
details["deployed_count"] = deployed_result.details.get("count", 0)
|
|
44
|
+
|
|
45
|
+
# Check agent versions
|
|
46
|
+
version_result = self._check_agent_versions()
|
|
47
|
+
sub_results.append(version_result)
|
|
48
|
+
details["outdated_agents"] = version_result.details.get("outdated", [])
|
|
49
|
+
|
|
50
|
+
# Validate agent configurations
|
|
51
|
+
validation_result = self._validate_agents()
|
|
52
|
+
sub_results.append(validation_result)
|
|
53
|
+
|
|
54
|
+
# Check for common agent issues
|
|
55
|
+
issues_result = self._check_common_issues()
|
|
56
|
+
sub_results.append(issues_result)
|
|
57
|
+
|
|
58
|
+
# Determine overall status
|
|
59
|
+
deployed_count = details["deployed_count"]
|
|
60
|
+
available_count = details["available_count"]
|
|
61
|
+
|
|
62
|
+
if deployed_count == 0:
|
|
63
|
+
status = DiagnosticStatus.ERROR
|
|
64
|
+
message = f"No agents deployed (0/{available_count} available)"
|
|
65
|
+
fix_command = "claude-mpm agents deploy"
|
|
66
|
+
fix_description = "Deploy all available agents"
|
|
67
|
+
elif deployed_count < available_count:
|
|
68
|
+
status = DiagnosticStatus.WARNING
|
|
69
|
+
message = f"{deployed_count}/{available_count} agents deployed"
|
|
70
|
+
fix_command = "claude-mpm agents deploy"
|
|
71
|
+
fix_description = f"Deploy remaining {available_count - deployed_count} agents"
|
|
72
|
+
elif any(r.status == DiagnosticStatus.ERROR for r in sub_results):
|
|
73
|
+
status = DiagnosticStatus.ERROR
|
|
74
|
+
message = "Agents have critical issues"
|
|
75
|
+
fix_command = None
|
|
76
|
+
fix_description = None
|
|
77
|
+
elif any(r.status == DiagnosticStatus.WARNING for r in sub_results):
|
|
78
|
+
status = DiagnosticStatus.WARNING
|
|
79
|
+
message = "Agents have minor issues"
|
|
80
|
+
fix_command = None
|
|
81
|
+
fix_description = None
|
|
82
|
+
else:
|
|
83
|
+
status = DiagnosticStatus.OK
|
|
84
|
+
message = f"All {deployed_count} agents properly deployed"
|
|
85
|
+
fix_command = None
|
|
86
|
+
fix_description = None
|
|
87
|
+
|
|
88
|
+
return DiagnosticResult(
|
|
89
|
+
category=self.category,
|
|
90
|
+
status=status,
|
|
91
|
+
message=message,
|
|
92
|
+
details=details,
|
|
93
|
+
fix_command=fix_command,
|
|
94
|
+
fix_description=fix_description,
|
|
95
|
+
sub_results=sub_results if self.verbose else []
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
except Exception as e:
|
|
99
|
+
return DiagnosticResult(
|
|
100
|
+
category=self.category,
|
|
101
|
+
status=DiagnosticStatus.ERROR,
|
|
102
|
+
message=f"Agent check failed: {str(e)}",
|
|
103
|
+
details={"error": str(e)}
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
def _check_deployed_agents(self) -> DiagnosticResult:
|
|
107
|
+
"""Check deployed agents in user directory."""
|
|
108
|
+
agents_dir = Path.home() / ".claude" / "agents"
|
|
109
|
+
|
|
110
|
+
if not agents_dir.exists():
|
|
111
|
+
return DiagnosticResult(
|
|
112
|
+
category="Deployed Agents",
|
|
113
|
+
status=DiagnosticStatus.ERROR,
|
|
114
|
+
message="Agents directory does not exist",
|
|
115
|
+
details={"path": str(agents_dir), "count": 0},
|
|
116
|
+
fix_command="claude-mpm agents deploy",
|
|
117
|
+
fix_description="Create agents directory and deploy agents"
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
# Count deployed agent files
|
|
121
|
+
agent_files = list(agents_dir.glob("*.md"))
|
|
122
|
+
|
|
123
|
+
if not agent_files:
|
|
124
|
+
return DiagnosticResult(
|
|
125
|
+
category="Deployed Agents",
|
|
126
|
+
status=DiagnosticStatus.ERROR,
|
|
127
|
+
message="No agents deployed",
|
|
128
|
+
details={"path": str(agents_dir), "count": 0},
|
|
129
|
+
fix_command="claude-mpm agents deploy",
|
|
130
|
+
fix_description="Deploy available agents"
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
# Check for required core agents
|
|
134
|
+
core_agents = ["research.md", "engineer.md", "qa.md", "documentation.md"]
|
|
135
|
+
deployed_names = [f.name for f in agent_files]
|
|
136
|
+
missing_core = [a for a in core_agents if a not in deployed_names]
|
|
137
|
+
|
|
138
|
+
if missing_core:
|
|
139
|
+
return DiagnosticResult(
|
|
140
|
+
category="Deployed Agents",
|
|
141
|
+
status=DiagnosticStatus.WARNING,
|
|
142
|
+
message=f"Missing core agents: {', '.join(missing_core)}",
|
|
143
|
+
details={
|
|
144
|
+
"path": str(agents_dir),
|
|
145
|
+
"count": len(agent_files),
|
|
146
|
+
"deployed": deployed_names,
|
|
147
|
+
"missing_core": missing_core
|
|
148
|
+
},
|
|
149
|
+
fix_command="claude-mpm agents deploy",
|
|
150
|
+
fix_description="Deploy missing core agents"
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
return DiagnosticResult(
|
|
154
|
+
category="Deployed Agents",
|
|
155
|
+
status=DiagnosticStatus.OK,
|
|
156
|
+
message=f"{len(agent_files)} agents deployed",
|
|
157
|
+
details={
|
|
158
|
+
"path": str(agents_dir),
|
|
159
|
+
"count": len(agent_files),
|
|
160
|
+
"deployed": deployed_names
|
|
161
|
+
}
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
def _check_agent_versions(self) -> DiagnosticResult:
|
|
165
|
+
"""Check if deployed agents are up-to-date."""
|
|
166
|
+
try:
|
|
167
|
+
from ....services.agents.deployment.agent_version_manager import AgentVersionManager
|
|
168
|
+
|
|
169
|
+
version_manager = AgentVersionManager()
|
|
170
|
+
agents_dir = Path.home() / ".claude" / "agents"
|
|
171
|
+
|
|
172
|
+
if not agents_dir.exists():
|
|
173
|
+
return DiagnosticResult(
|
|
174
|
+
category="Agent Versions",
|
|
175
|
+
status=DiagnosticStatus.SKIPPED,
|
|
176
|
+
message="No agents to check",
|
|
177
|
+
details={}
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
outdated = []
|
|
181
|
+
checked = 0
|
|
182
|
+
|
|
183
|
+
for agent_file in agents_dir.glob("*.md"):
|
|
184
|
+
checked += 1
|
|
185
|
+
agent_name = agent_file.stem
|
|
186
|
+
|
|
187
|
+
# Check if agent needs update (simplified check)
|
|
188
|
+
if version_manager.needs_update(agent_name):
|
|
189
|
+
outdated.append(agent_name)
|
|
190
|
+
|
|
191
|
+
if outdated:
|
|
192
|
+
return DiagnosticResult(
|
|
193
|
+
category="Agent Versions",
|
|
194
|
+
status=DiagnosticStatus.WARNING,
|
|
195
|
+
message=f"{len(outdated)} agent(s) outdated",
|
|
196
|
+
details={"outdated": outdated, "checked": checked},
|
|
197
|
+
fix_command="claude-mpm agents update",
|
|
198
|
+
fix_description="Update agents to latest versions"
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
if checked == 0:
|
|
202
|
+
return DiagnosticResult(
|
|
203
|
+
category="Agent Versions",
|
|
204
|
+
status=DiagnosticStatus.WARNING,
|
|
205
|
+
message="No agents to check",
|
|
206
|
+
details={"checked": 0}
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
return DiagnosticResult(
|
|
210
|
+
category="Agent Versions",
|
|
211
|
+
status=DiagnosticStatus.OK,
|
|
212
|
+
message=f"All {checked} agents up-to-date",
|
|
213
|
+
details={"checked": checked}
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
except Exception as e:
|
|
217
|
+
return DiagnosticResult(
|
|
218
|
+
category="Agent Versions",
|
|
219
|
+
status=DiagnosticStatus.WARNING,
|
|
220
|
+
message=f"Could not check versions: {str(e)}",
|
|
221
|
+
details={"error": str(e)}
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
def _validate_agents(self) -> DiagnosticResult:
|
|
225
|
+
"""Validate agent configurations."""
|
|
226
|
+
try:
|
|
227
|
+
from ....services.agents.deployment.agent_validator import AgentValidator
|
|
228
|
+
|
|
229
|
+
validator = AgentValidator()
|
|
230
|
+
agents_dir = Path.home() / ".claude" / "agents"
|
|
231
|
+
|
|
232
|
+
if not agents_dir.exists():
|
|
233
|
+
return DiagnosticResult(
|
|
234
|
+
category="Agent Validation",
|
|
235
|
+
status=DiagnosticStatus.SKIPPED,
|
|
236
|
+
message="No agents to validate",
|
|
237
|
+
details={}
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
invalid = []
|
|
241
|
+
validated = 0
|
|
242
|
+
|
|
243
|
+
for agent_file in agents_dir.glob("*.md"):
|
|
244
|
+
validated += 1
|
|
245
|
+
|
|
246
|
+
# Basic validation
|
|
247
|
+
try:
|
|
248
|
+
with open(agent_file, 'r') as f:
|
|
249
|
+
content = f.read()
|
|
250
|
+
|
|
251
|
+
# Check for required sections
|
|
252
|
+
if "## Core Identity" not in content:
|
|
253
|
+
invalid.append(f"{agent_file.stem}: missing Core Identity")
|
|
254
|
+
elif len(content) < 100:
|
|
255
|
+
invalid.append(f"{agent_file.stem}: file too small")
|
|
256
|
+
|
|
257
|
+
except Exception as e:
|
|
258
|
+
invalid.append(f"{agent_file.stem}: {str(e)}")
|
|
259
|
+
|
|
260
|
+
if invalid:
|
|
261
|
+
return DiagnosticResult(
|
|
262
|
+
category="Agent Validation",
|
|
263
|
+
status=DiagnosticStatus.WARNING,
|
|
264
|
+
message=f"{len(invalid)} validation issue(s)",
|
|
265
|
+
details={"issues": invalid, "validated": validated}
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
return DiagnosticResult(
|
|
269
|
+
category="Agent Validation",
|
|
270
|
+
status=DiagnosticStatus.OK,
|
|
271
|
+
message=f"All {validated} agents valid",
|
|
272
|
+
details={"validated": validated}
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
except Exception as e:
|
|
276
|
+
return DiagnosticResult(
|
|
277
|
+
category="Agent Validation",
|
|
278
|
+
status=DiagnosticStatus.WARNING,
|
|
279
|
+
message=f"Validation failed: {str(e)}",
|
|
280
|
+
details={"error": str(e)}
|
|
281
|
+
)
|
|
282
|
+
|
|
283
|
+
def _check_common_issues(self) -> DiagnosticResult:
|
|
284
|
+
"""Check for common agent-related issues."""
|
|
285
|
+
issues = []
|
|
286
|
+
|
|
287
|
+
# Check for duplicate agents
|
|
288
|
+
agents_dir = Path.home() / ".claude" / "agents"
|
|
289
|
+
if agents_dir.exists():
|
|
290
|
+
agent_names = {}
|
|
291
|
+
for agent_file in agents_dir.glob("*.md"):
|
|
292
|
+
name = agent_file.stem.lower()
|
|
293
|
+
if name in agent_names:
|
|
294
|
+
issues.append(f"Duplicate agent: {agent_file.stem}")
|
|
295
|
+
else:
|
|
296
|
+
agent_names[name] = agent_file
|
|
297
|
+
|
|
298
|
+
# Check permissions
|
|
299
|
+
if agents_dir.exists():
|
|
300
|
+
import os
|
|
301
|
+
if not os.access(agents_dir, os.R_OK):
|
|
302
|
+
issues.append("Agents directory not readable")
|
|
303
|
+
if not os.access(agents_dir, os.W_OK):
|
|
304
|
+
issues.append("Agents directory not writable")
|
|
305
|
+
|
|
306
|
+
if issues:
|
|
307
|
+
return DiagnosticResult(
|
|
308
|
+
category="Common Issues",
|
|
309
|
+
status=DiagnosticStatus.WARNING,
|
|
310
|
+
message=f"{len(issues)} issue(s) found",
|
|
311
|
+
details={"issues": issues}
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
return DiagnosticResult(
|
|
315
|
+
category="Common Issues",
|
|
316
|
+
status=DiagnosticStatus.OK,
|
|
317
|
+
message="No common issues detected",
|
|
318
|
+
details={}
|
|
319
|
+
)
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Base interface for diagnostic checks.
|
|
3
|
+
|
|
4
|
+
WHY: Define a consistent interface for all diagnostic checks to ensure
|
|
5
|
+
they can be easily added, tested, and executed by the diagnostic runner.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from abc import ABC, abstractmethod
|
|
9
|
+
from typing import Optional
|
|
10
|
+
|
|
11
|
+
from ..models import DiagnosticResult
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class BaseDiagnosticCheck(ABC):
|
|
15
|
+
"""Base class for all diagnostic checks.
|
|
16
|
+
|
|
17
|
+
WHY: Ensures all checks follow the same pattern and can be
|
|
18
|
+
executed uniformly by the diagnostic runner.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
def __init__(self, verbose: bool = False):
|
|
22
|
+
"""Initialize the check.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
verbose: Whether to include detailed information in results
|
|
26
|
+
"""
|
|
27
|
+
self.verbose = verbose
|
|
28
|
+
|
|
29
|
+
@property
|
|
30
|
+
@abstractmethod
|
|
31
|
+
def name(self) -> str:
|
|
32
|
+
"""Get the name of this check."""
|
|
33
|
+
pass
|
|
34
|
+
|
|
35
|
+
@property
|
|
36
|
+
@abstractmethod
|
|
37
|
+
def category(self) -> str:
|
|
38
|
+
"""Get the category this check belongs to."""
|
|
39
|
+
pass
|
|
40
|
+
|
|
41
|
+
@property
|
|
42
|
+
def description(self) -> str:
|
|
43
|
+
"""Get a description of what this check does."""
|
|
44
|
+
return f"Checking {self.category.lower()} health"
|
|
45
|
+
|
|
46
|
+
@abstractmethod
|
|
47
|
+
def run(self) -> DiagnosticResult:
|
|
48
|
+
"""Run the diagnostic check.
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
DiagnosticResult with the check results
|
|
52
|
+
"""
|
|
53
|
+
pass
|
|
54
|
+
|
|
55
|
+
def should_run(self) -> bool:
|
|
56
|
+
"""Determine if this check should run.
|
|
57
|
+
|
|
58
|
+
WHY: Some checks may not be applicable in certain environments
|
|
59
|
+
or configurations. This allows checks to opt out gracefully.
|
|
60
|
+
|
|
61
|
+
Returns:
|
|
62
|
+
True if the check should run, False to skip
|
|
63
|
+
"""
|
|
64
|
+
return True
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Check Claude Desktop integration.
|
|
3
|
+
|
|
4
|
+
WHY: Verify that Claude Desktop is installed, properly configured,
|
|
5
|
+
and integrated with claude-mpm.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
import subprocess
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Dict, Any, Optional
|
|
12
|
+
|
|
13
|
+
from ..models import DiagnosticResult, DiagnosticStatus
|
|
14
|
+
from .base_check import BaseDiagnosticCheck
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class ClaudeDesktopCheck(BaseDiagnosticCheck):
|
|
18
|
+
"""Check Claude Desktop installation and integration."""
|
|
19
|
+
|
|
20
|
+
@property
|
|
21
|
+
def name(self) -> str:
|
|
22
|
+
return "claude_desktop_check"
|
|
23
|
+
|
|
24
|
+
@property
|
|
25
|
+
def category(self) -> str:
|
|
26
|
+
return "Claude Desktop"
|
|
27
|
+
|
|
28
|
+
def run(self) -> DiagnosticResult:
|
|
29
|
+
"""Run Claude Desktop diagnostics."""
|
|
30
|
+
try:
|
|
31
|
+
sub_results = []
|
|
32
|
+
details = {}
|
|
33
|
+
|
|
34
|
+
# Check if Claude Desktop is installed
|
|
35
|
+
install_result = self._check_installation()
|
|
36
|
+
sub_results.append(install_result)
|
|
37
|
+
details["installed"] = install_result.status == DiagnosticStatus.OK
|
|
38
|
+
|
|
39
|
+
if install_result.status == DiagnosticStatus.OK:
|
|
40
|
+
# Check version compatibility
|
|
41
|
+
version_result = self._check_version()
|
|
42
|
+
sub_results.append(version_result)
|
|
43
|
+
details["version"] = version_result.details.get("version")
|
|
44
|
+
|
|
45
|
+
# Check output style deployment
|
|
46
|
+
style_result = self._check_output_style()
|
|
47
|
+
sub_results.append(style_result)
|
|
48
|
+
details["output_style"] = style_result.details.get("deployed")
|
|
49
|
+
|
|
50
|
+
# Check MCP integration
|
|
51
|
+
mcp_result = self._check_mcp_integration()
|
|
52
|
+
sub_results.append(mcp_result)
|
|
53
|
+
details["mcp_configured"] = mcp_result.status == DiagnosticStatus.OK
|
|
54
|
+
|
|
55
|
+
# Determine overall status
|
|
56
|
+
if any(r.status == DiagnosticStatus.ERROR for r in sub_results):
|
|
57
|
+
status = DiagnosticStatus.ERROR
|
|
58
|
+
message = "Claude Desktop has critical issues"
|
|
59
|
+
elif any(r.status == DiagnosticStatus.WARNING for r in sub_results):
|
|
60
|
+
status = DiagnosticStatus.WARNING
|
|
61
|
+
message = "Claude Desktop needs configuration"
|
|
62
|
+
else:
|
|
63
|
+
status = DiagnosticStatus.OK
|
|
64
|
+
message = "Claude Desktop properly configured"
|
|
65
|
+
|
|
66
|
+
return DiagnosticResult(
|
|
67
|
+
category=self.category,
|
|
68
|
+
status=status,
|
|
69
|
+
message=message,
|
|
70
|
+
details=details,
|
|
71
|
+
sub_results=sub_results if self.verbose else []
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
except Exception as e:
|
|
75
|
+
return DiagnosticResult(
|
|
76
|
+
category=self.category,
|
|
77
|
+
status=DiagnosticStatus.ERROR,
|
|
78
|
+
message=f"Claude Desktop check failed: {str(e)}",
|
|
79
|
+
details={"error": str(e)}
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
def _check_installation(self) -> DiagnosticResult:
|
|
83
|
+
"""Check if Claude Desktop is installed."""
|
|
84
|
+
# Check common installation paths
|
|
85
|
+
mac_path = Path("/Applications/Claude.app")
|
|
86
|
+
linux_paths = [
|
|
87
|
+
Path.home() / ".local/share/applications/claude.desktop",
|
|
88
|
+
Path("/usr/share/applications/claude.desktop"),
|
|
89
|
+
Path("/opt/Claude")
|
|
90
|
+
]
|
|
91
|
+
windows_paths = [
|
|
92
|
+
Path("C:/Program Files/Claude/Claude.exe"),
|
|
93
|
+
Path.home() / "AppData/Local/Claude/Claude.exe"
|
|
94
|
+
]
|
|
95
|
+
|
|
96
|
+
# Check for Claude process
|
|
97
|
+
try:
|
|
98
|
+
result = subprocess.run(
|
|
99
|
+
["pgrep", "-f", "Claude"],
|
|
100
|
+
capture_output=True,
|
|
101
|
+
timeout=2
|
|
102
|
+
)
|
|
103
|
+
if result.returncode == 0:
|
|
104
|
+
return DiagnosticResult(
|
|
105
|
+
category="Claude Desktop Installation",
|
|
106
|
+
status=DiagnosticStatus.OK,
|
|
107
|
+
message="Claude Desktop is running",
|
|
108
|
+
details={"running": True}
|
|
109
|
+
)
|
|
110
|
+
except (subprocess.SubprocessError, FileNotFoundError):
|
|
111
|
+
pass
|
|
112
|
+
|
|
113
|
+
# Check installation paths
|
|
114
|
+
if mac_path.exists():
|
|
115
|
+
return DiagnosticResult(
|
|
116
|
+
category="Claude Desktop Installation",
|
|
117
|
+
status=DiagnosticStatus.OK,
|
|
118
|
+
message="Claude Desktop installed (macOS)",
|
|
119
|
+
details={"path": str(mac_path), "platform": "macos"}
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
for path in linux_paths:
|
|
123
|
+
if path.exists():
|
|
124
|
+
return DiagnosticResult(
|
|
125
|
+
category="Claude Desktop Installation",
|
|
126
|
+
status=DiagnosticStatus.OK,
|
|
127
|
+
message="Claude Desktop installed (Linux)",
|
|
128
|
+
details={"path": str(path), "platform": "linux"}
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
for path in windows_paths:
|
|
132
|
+
if path.exists():
|
|
133
|
+
return DiagnosticResult(
|
|
134
|
+
category="Claude Desktop Installation",
|
|
135
|
+
status=DiagnosticStatus.OK,
|
|
136
|
+
message="Claude Desktop installed (Windows)",
|
|
137
|
+
details={"path": str(path), "platform": "windows"}
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
return DiagnosticResult(
|
|
141
|
+
category="Claude Desktop Installation",
|
|
142
|
+
status=DiagnosticStatus.WARNING,
|
|
143
|
+
message="Claude Desktop not found",
|
|
144
|
+
details={"installed": False},
|
|
145
|
+
fix_description="Install Claude Desktop from https://claude.ai/download"
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
def _check_version(self) -> DiagnosticResult:
|
|
149
|
+
"""Check Claude Desktop version compatibility."""
|
|
150
|
+
# Try to get version from config file
|
|
151
|
+
config_paths = [
|
|
152
|
+
Path.home() / "Library/Application Support/Claude/config.json", # macOS
|
|
153
|
+
Path.home() / ".config/Claude/config.json", # Linux
|
|
154
|
+
Path.home() / "AppData/Roaming/Claude/config.json" # Windows
|
|
155
|
+
]
|
|
156
|
+
|
|
157
|
+
for config_path in config_paths:
|
|
158
|
+
if config_path.exists():
|
|
159
|
+
try:
|
|
160
|
+
with open(config_path, 'r') as f:
|
|
161
|
+
config = json.load(f)
|
|
162
|
+
version = config.get("version", "unknown")
|
|
163
|
+
|
|
164
|
+
# Simple version check (would need real version comparison logic)
|
|
165
|
+
return DiagnosticResult(
|
|
166
|
+
category="Claude Desktop Version",
|
|
167
|
+
status=DiagnosticStatus.OK,
|
|
168
|
+
message=f"Version: {version}",
|
|
169
|
+
details={"version": version, "config_path": str(config_path)}
|
|
170
|
+
)
|
|
171
|
+
except Exception:
|
|
172
|
+
pass
|
|
173
|
+
|
|
174
|
+
return DiagnosticResult(
|
|
175
|
+
category="Claude Desktop Version",
|
|
176
|
+
status=DiagnosticStatus.WARNING,
|
|
177
|
+
message="Could not determine version",
|
|
178
|
+
details={"version": "unknown"}
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
def _check_output_style(self) -> DiagnosticResult:
|
|
182
|
+
"""Check if output style is deployed."""
|
|
183
|
+
style_path = Path.home() / ".claude/responses/OUTPUT_STYLE.md"
|
|
184
|
+
|
|
185
|
+
if not style_path.exists():
|
|
186
|
+
return DiagnosticResult(
|
|
187
|
+
category="Output Style",
|
|
188
|
+
status=DiagnosticStatus.WARNING,
|
|
189
|
+
message="Output style not deployed",
|
|
190
|
+
details={"deployed": False, "path": str(style_path)},
|
|
191
|
+
fix_command="claude-mpm deploy-style",
|
|
192
|
+
fix_description="Deploy claude-mpm output style for better formatting"
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
# Check if it's up to date
|
|
196
|
+
try:
|
|
197
|
+
with open(style_path, 'r') as f:
|
|
198
|
+
content = f.read()
|
|
199
|
+
if "Claude MPM Output Style" in content:
|
|
200
|
+
return DiagnosticResult(
|
|
201
|
+
category="Output Style",
|
|
202
|
+
status=DiagnosticStatus.OK,
|
|
203
|
+
message="Output style deployed",
|
|
204
|
+
details={"deployed": True, "path": str(style_path)}
|
|
205
|
+
)
|
|
206
|
+
else:
|
|
207
|
+
return DiagnosticResult(
|
|
208
|
+
category="Output Style",
|
|
209
|
+
status=DiagnosticStatus.WARNING,
|
|
210
|
+
message="Output style outdated",
|
|
211
|
+
details={"deployed": True, "outdated": True, "path": str(style_path)},
|
|
212
|
+
fix_command="claude-mpm deploy-style --force",
|
|
213
|
+
fix_description="Update output style to latest version"
|
|
214
|
+
)
|
|
215
|
+
except Exception as e:
|
|
216
|
+
return DiagnosticResult(
|
|
217
|
+
category="Output Style",
|
|
218
|
+
status=DiagnosticStatus.WARNING,
|
|
219
|
+
message=f"Could not check output style: {str(e)}",
|
|
220
|
+
details={"error": str(e)}
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
def _check_mcp_integration(self) -> DiagnosticResult:
|
|
224
|
+
"""Check MCP server integration with Claude Desktop."""
|
|
225
|
+
config_path = Path.home() / ".config/claude/claude_desktop_config.json"
|
|
226
|
+
|
|
227
|
+
if not config_path.exists():
|
|
228
|
+
# Try alternate paths
|
|
229
|
+
alt_paths = [
|
|
230
|
+
Path.home() / "Library/Application Support/Claude/claude_desktop_config.json",
|
|
231
|
+
Path.home() / "AppData/Roaming/Claude/claude_desktop_config.json"
|
|
232
|
+
]
|
|
233
|
+
for alt_path in alt_paths:
|
|
234
|
+
if alt_path.exists():
|
|
235
|
+
config_path = alt_path
|
|
236
|
+
break
|
|
237
|
+
else:
|
|
238
|
+
return DiagnosticResult(
|
|
239
|
+
category="MCP Integration",
|
|
240
|
+
status=DiagnosticStatus.WARNING,
|
|
241
|
+
message="Claude Desktop config not found",
|
|
242
|
+
details={"configured": False},
|
|
243
|
+
fix_command="claude-mpm mcp install",
|
|
244
|
+
fix_description="Install MCP server integration"
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
try:
|
|
248
|
+
with open(config_path, 'r') as f:
|
|
249
|
+
config = json.load(f)
|
|
250
|
+
|
|
251
|
+
mcp_servers = config.get("mcpServers", {})
|
|
252
|
+
if "claude-mpm-gateway" in mcp_servers:
|
|
253
|
+
return DiagnosticResult(
|
|
254
|
+
category="MCP Integration",
|
|
255
|
+
status=DiagnosticStatus.OK,
|
|
256
|
+
message="MCP server configured",
|
|
257
|
+
details={
|
|
258
|
+
"configured": True,
|
|
259
|
+
"server_count": len(mcp_servers),
|
|
260
|
+
"config_path": str(config_path)
|
|
261
|
+
}
|
|
262
|
+
)
|
|
263
|
+
else:
|
|
264
|
+
return DiagnosticResult(
|
|
265
|
+
category="MCP Integration",
|
|
266
|
+
status=DiagnosticStatus.WARNING,
|
|
267
|
+
message="MCP server not configured",
|
|
268
|
+
details={
|
|
269
|
+
"configured": False,
|
|
270
|
+
"server_count": len(mcp_servers),
|
|
271
|
+
"config_path": str(config_path)
|
|
272
|
+
},
|
|
273
|
+
fix_command="claude-mpm mcp install",
|
|
274
|
+
fix_description="Configure MCP server for Claude Desktop"
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
except Exception as e:
|
|
278
|
+
return DiagnosticResult(
|
|
279
|
+
category="MCP Integration",
|
|
280
|
+
status=DiagnosticStatus.WARNING,
|
|
281
|
+
message=f"Could not check MCP configuration: {str(e)}",
|
|
282
|
+
details={"error": str(e)}
|
|
283
|
+
)
|