claude-mpm 5.1.9__py3-none-any.whl → 5.4.22__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 claude-mpm might be problematic. Click here for more details.
- claude_mpm/VERSION +1 -1
- claude_mpm/__init__.py +4 -0
- claude_mpm/agents/CLAUDE_MPM_TEACHER_OUTPUT_STYLE.md +1 -1
- claude_mpm/agents/PM_INSTRUCTIONS.md +290 -34
- claude_mpm/agents/agent_loader.py +13 -44
- claude_mpm/agents/templates/circuit-breakers.md +138 -1
- claude_mpm/cli/__main__.py +4 -0
- claude_mpm/cli/chrome_devtools_installer.py +175 -0
- claude_mpm/cli/commands/agent_state_manager.py +8 -17
- claude_mpm/cli/commands/agents.py +0 -31
- claude_mpm/cli/commands/auto_configure.py +210 -25
- claude_mpm/cli/commands/config.py +88 -2
- claude_mpm/cli/commands/configure.py +1097 -158
- claude_mpm/cli/commands/configure_agent_display.py +15 -6
- claude_mpm/cli/commands/mpm_init/core.py +160 -46
- claude_mpm/cli/commands/mpm_init/knowledge_extractor.py +481 -0
- claude_mpm/cli/commands/mpm_init/prompts.py +280 -0
- claude_mpm/cli/commands/skills.py +214 -189
- claude_mpm/cli/commands/summarize.py +413 -0
- claude_mpm/cli/executor.py +11 -3
- claude_mpm/cli/parsers/agents_parser.py +0 -9
- claude_mpm/cli/parsers/auto_configure_parser.py +0 -138
- claude_mpm/cli/parsers/base_parser.py +5 -0
- claude_mpm/cli/parsers/config_parser.py +153 -83
- claude_mpm/cli/parsers/skills_parser.py +3 -2
- claude_mpm/cli/startup.py +550 -94
- claude_mpm/commands/mpm-config.md +265 -0
- claude_mpm/commands/mpm-help.md +14 -95
- claude_mpm/commands/mpm-organize.md +500 -0
- claude_mpm/config/agent_sources.py +27 -0
- claude_mpm/core/framework/formatters/content_formatter.py +3 -13
- claude_mpm/core/framework/loaders/agent_loader.py +8 -5
- claude_mpm/core/framework_loader.py +4 -2
- claude_mpm/core/logger.py +13 -0
- claude_mpm/core/socketio_pool.py +3 -3
- claude_mpm/core/unified_agent_registry.py +5 -15
- claude_mpm/hooks/claude_hooks/correlation_manager.py +60 -0
- claude_mpm/hooks/claude_hooks/event_handlers.py +211 -78
- claude_mpm/hooks/claude_hooks/hook_handler.py +6 -0
- claude_mpm/hooks/claude_hooks/installer.py +33 -10
- claude_mpm/hooks/claude_hooks/memory_integration.py +26 -9
- claude_mpm/hooks/claude_hooks/response_tracking.py +2 -3
- claude_mpm/hooks/claude_hooks/services/connection_manager.py +4 -0
- claude_mpm/hooks/memory_integration_hook.py +46 -1
- claude_mpm/init.py +0 -19
- claude_mpm/scripts/claude-hook-handler.sh +58 -18
- claude_mpm/scripts/launch_monitor.py +93 -13
- claude_mpm/scripts/start_activity_logging.py +0 -0
- claude_mpm/services/agents/agent_recommendation_service.py +278 -0
- claude_mpm/services/agents/agent_review_service.py +280 -0
- claude_mpm/services/agents/deployment/agent_discovery_service.py +2 -3
- claude_mpm/services/agents/deployment/agent_template_builder.py +4 -2
- claude_mpm/services/agents/deployment/multi_source_deployment_service.py +78 -9
- claude_mpm/services/agents/deployment/remote_agent_discovery_service.py +335 -53
- claude_mpm/services/agents/git_source_manager.py +34 -0
- claude_mpm/services/agents/loading/base_agent_manager.py +1 -13
- claude_mpm/services/agents/sources/git_source_sync_service.py +8 -1
- claude_mpm/services/agents/toolchain_detector.py +10 -6
- claude_mpm/services/analysis/__init__.py +11 -1
- claude_mpm/services/analysis/clone_detector.py +1030 -0
- claude_mpm/services/command_deployment_service.py +81 -10
- claude_mpm/services/event_bus/config.py +3 -1
- claude_mpm/services/git/git_operations_service.py +93 -8
- claude_mpm/services/monitor/daemon.py +9 -2
- claude_mpm/services/monitor/daemon_manager.py +39 -3
- claude_mpm/services/monitor/server.py +225 -19
- claude_mpm/services/self_upgrade_service.py +120 -12
- claude_mpm/services/skills/__init__.py +3 -0
- claude_mpm/services/skills/git_skill_source_manager.py +32 -2
- claude_mpm/services/skills/selective_skill_deployer.py +704 -0
- claude_mpm/services/skills/skill_to_agent_mapper.py +406 -0
- claude_mpm/services/skills_deployer.py +126 -9
- claude_mpm/services/socketio/event_normalizer.py +15 -1
- claude_mpm/services/socketio/server/core.py +160 -21
- claude_mpm/services/version_control/git_operations.py +103 -0
- claude_mpm/utils/agent_filters.py +17 -44
- {claude_mpm-5.1.9.dist-info → claude_mpm-5.4.22.dist-info}/METADATA +47 -84
- {claude_mpm-5.1.9.dist-info → claude_mpm-5.4.22.dist-info}/RECORD +82 -161
- claude_mpm-5.4.22.dist-info/entry_points.txt +5 -0
- claude_mpm-5.4.22.dist-info/licenses/LICENSE +94 -0
- claude_mpm-5.4.22.dist-info/licenses/LICENSE-FAQ.md +153 -0
- claude_mpm/agents/BASE_AGENT_TEMPLATE.md +0 -292
- claude_mpm/agents/BASE_DOCUMENTATION.md +0 -53
- claude_mpm/agents/BASE_ENGINEER.md +0 -658
- claude_mpm/agents/BASE_OPS.md +0 -219
- claude_mpm/agents/BASE_PM.md +0 -480
- claude_mpm/agents/BASE_PROMPT_ENGINEER.md +0 -787
- claude_mpm/agents/BASE_QA.md +0 -167
- claude_mpm/agents/BASE_RESEARCH.md +0 -53
- claude_mpm/agents/base_agent.json +0 -31
- claude_mpm/agents/base_agent_loader.py +0 -601
- claude_mpm/cli/commands/agents_detect.py +0 -380
- claude_mpm/cli/commands/agents_recommend.py +0 -309
- claude_mpm/cli/ticket_cli.py +0 -35
- claude_mpm/commands/mpm-agents-auto-configure.md +0 -278
- claude_mpm/commands/mpm-agents-detect.md +0 -177
- claude_mpm/commands/mpm-agents-list.md +0 -131
- claude_mpm/commands/mpm-agents-recommend.md +0 -223
- claude_mpm/commands/mpm-config-view.md +0 -150
- claude_mpm/commands/mpm-ticket-organize.md +0 -304
- claude_mpm/dashboard/analysis_runner.py +0 -455
- claude_mpm/dashboard/index.html +0 -13
- claude_mpm/dashboard/open_dashboard.py +0 -66
- claude_mpm/dashboard/static/css/activity.css +0 -1958
- claude_mpm/dashboard/static/css/connection-status.css +0 -370
- claude_mpm/dashboard/static/css/dashboard.css +0 -4701
- claude_mpm/dashboard/static/js/components/activity-tree.js +0 -1871
- claude_mpm/dashboard/static/js/components/agent-hierarchy.js +0 -777
- claude_mpm/dashboard/static/js/components/agent-inference.js +0 -956
- claude_mpm/dashboard/static/js/components/build-tracker.js +0 -333
- claude_mpm/dashboard/static/js/components/code-simple.js +0 -857
- claude_mpm/dashboard/static/js/components/connection-debug.js +0 -654
- claude_mpm/dashboard/static/js/components/diff-viewer.js +0 -891
- claude_mpm/dashboard/static/js/components/event-processor.js +0 -542
- claude_mpm/dashboard/static/js/components/event-viewer.js +0 -1155
- claude_mpm/dashboard/static/js/components/export-manager.js +0 -368
- claude_mpm/dashboard/static/js/components/file-change-tracker.js +0 -443
- claude_mpm/dashboard/static/js/components/file-change-viewer.js +0 -690
- claude_mpm/dashboard/static/js/components/file-tool-tracker.js +0 -724
- claude_mpm/dashboard/static/js/components/file-viewer.js +0 -580
- claude_mpm/dashboard/static/js/components/hud-library-loader.js +0 -211
- claude_mpm/dashboard/static/js/components/hud-manager.js +0 -671
- claude_mpm/dashboard/static/js/components/hud-visualizer.js +0 -1718
- claude_mpm/dashboard/static/js/components/module-viewer.js +0 -2764
- claude_mpm/dashboard/static/js/components/session-manager.js +0 -579
- claude_mpm/dashboard/static/js/components/socket-manager.js +0 -368
- claude_mpm/dashboard/static/js/components/ui-state-manager.js +0 -749
- claude_mpm/dashboard/static/js/components/unified-data-viewer.js +0 -1824
- claude_mpm/dashboard/static/js/components/working-directory.js +0 -920
- claude_mpm/dashboard/static/js/connection-manager.js +0 -536
- claude_mpm/dashboard/static/js/dashboard.js +0 -1914
- claude_mpm/dashboard/static/js/extension-error-handler.js +0 -164
- claude_mpm/dashboard/static/js/socket-client.js +0 -1474
- claude_mpm/dashboard/static/js/tab-isolation-fix.js +0 -185
- claude_mpm/dashboard/static/socket.io.min.js +0 -7
- claude_mpm/dashboard/static/socket.io.v4.8.1.backup.js +0 -7
- claude_mpm/dashboard/templates/code_simple.html +0 -153
- claude_mpm/dashboard/templates/index.html +0 -606
- claude_mpm/dashboard/test_dashboard.html +0 -372
- claude_mpm/scripts/mcp_server.py +0 -75
- claude_mpm/scripts/mcp_wrapper.py +0 -39
- claude_mpm/services/mcp_gateway/__init__.py +0 -159
- claude_mpm/services/mcp_gateway/auto_configure.py +0 -369
- claude_mpm/services/mcp_gateway/config/__init__.py +0 -17
- claude_mpm/services/mcp_gateway/config/config_loader.py +0 -296
- claude_mpm/services/mcp_gateway/config/config_schema.py +0 -243
- claude_mpm/services/mcp_gateway/config/configuration.py +0 -429
- claude_mpm/services/mcp_gateway/core/__init__.py +0 -43
- claude_mpm/services/mcp_gateway/core/base.py +0 -312
- claude_mpm/services/mcp_gateway/core/exceptions.py +0 -253
- claude_mpm/services/mcp_gateway/core/interfaces.py +0 -443
- claude_mpm/services/mcp_gateway/core/process_pool.py +0 -977
- claude_mpm/services/mcp_gateway/core/singleton_manager.py +0 -315
- claude_mpm/services/mcp_gateway/core/startup_verification.py +0 -316
- claude_mpm/services/mcp_gateway/main.py +0 -589
- claude_mpm/services/mcp_gateway/registry/__init__.py +0 -12
- claude_mpm/services/mcp_gateway/registry/service_registry.py +0 -412
- claude_mpm/services/mcp_gateway/registry/tool_registry.py +0 -489
- claude_mpm/services/mcp_gateway/server/__init__.py +0 -15
- claude_mpm/services/mcp_gateway/server/mcp_gateway.py +0 -414
- claude_mpm/services/mcp_gateway/server/stdio_handler.py +0 -372
- claude_mpm/services/mcp_gateway/server/stdio_server.py +0 -712
- claude_mpm/services/mcp_gateway/tools/__init__.py +0 -36
- claude_mpm/services/mcp_gateway/tools/base_adapter.py +0 -485
- claude_mpm/services/mcp_gateway/tools/document_summarizer.py +0 -789
- claude_mpm/services/mcp_gateway/tools/external_mcp_services.py +0 -654
- claude_mpm/services/mcp_gateway/tools/health_check_tool.py +0 -456
- claude_mpm/services/mcp_gateway/tools/hello_world.py +0 -551
- claude_mpm/services/mcp_gateway/tools/kuzu_memory_service.py +0 -555
- claude_mpm/services/mcp_gateway/utils/__init__.py +0 -14
- claude_mpm/services/mcp_gateway/utils/package_version_checker.py +0 -160
- claude_mpm/services/mcp_gateway/utils/update_preferences.py +0 -170
- claude_mpm-5.1.9.dist-info/entry_points.txt +0 -10
- claude_mpm-5.1.9.dist-info/licenses/LICENSE +0 -21
- {claude_mpm-5.1.9.dist-info → claude_mpm-5.4.22.dist-info}/WHEEL +0 -0
- {claude_mpm-5.1.9.dist-info → claude_mpm-5.4.22.dist-info}/top_level.txt +0 -0
|
@@ -15,7 +15,6 @@ DESIGN DECISIONS:
|
|
|
15
15
|
"""
|
|
16
16
|
|
|
17
17
|
import asyncio
|
|
18
|
-
import contextlib
|
|
19
18
|
import os
|
|
20
19
|
import threading
|
|
21
20
|
import time
|
|
@@ -25,6 +24,8 @@ from typing import Dict, Optional
|
|
|
25
24
|
|
|
26
25
|
import socketio
|
|
27
26
|
from aiohttp import web
|
|
27
|
+
from watchdog.events import FileSystemEventHandler
|
|
28
|
+
from watchdog.observers import Observer
|
|
28
29
|
|
|
29
30
|
from ...core.enums import ServiceState
|
|
30
31
|
from ...core.logging_config import get_logger
|
|
@@ -45,6 +46,91 @@ except ImportError:
|
|
|
45
46
|
EVENTBUS_AVAILABLE = False
|
|
46
47
|
|
|
47
48
|
|
|
49
|
+
class SvelteBuildWatcher(FileSystemEventHandler):
|
|
50
|
+
"""File watcher for Svelte build directory changes.
|
|
51
|
+
|
|
52
|
+
Watches for file changes in svelte-build directory and triggers
|
|
53
|
+
hot reload via Socket.IO event emission.
|
|
54
|
+
|
|
55
|
+
STABILITY FIX: Added thread lock and stop() method to prevent timer leaks.
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
def __init__(
|
|
59
|
+
self, sio: socketio.AsyncServer, loop: asyncio.AbstractEventLoop, logger
|
|
60
|
+
):
|
|
61
|
+
"""Initialize the file watcher.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
sio: Socket.IO server instance for emitting events
|
|
65
|
+
loop: Event loop for async operations
|
|
66
|
+
logger: Logger instance
|
|
67
|
+
"""
|
|
68
|
+
super().__init__()
|
|
69
|
+
self.sio = sio
|
|
70
|
+
self.loop = loop
|
|
71
|
+
self.logger = logger
|
|
72
|
+
self.debounce_timer = None
|
|
73
|
+
self.debounce_delay = 0.5 # Wait 500ms after last change
|
|
74
|
+
self._timer_lock = threading.Lock() # STABILITY FIX: Prevent race condition
|
|
75
|
+
|
|
76
|
+
def stop(self):
|
|
77
|
+
"""Stop the watcher and cancel any pending timers.
|
|
78
|
+
|
|
79
|
+
STABILITY FIX: Ensures timer is cancelled on shutdown.
|
|
80
|
+
"""
|
|
81
|
+
with self._timer_lock:
|
|
82
|
+
if self.debounce_timer:
|
|
83
|
+
self.debounce_timer.cancel()
|
|
84
|
+
self.debounce_timer = None
|
|
85
|
+
|
|
86
|
+
def on_any_event(self, event):
|
|
87
|
+
"""Handle any file system event.
|
|
88
|
+
|
|
89
|
+
Args:
|
|
90
|
+
event: File system event from watchdog
|
|
91
|
+
"""
|
|
92
|
+
# Ignore directory events and temporary files
|
|
93
|
+
if event.is_directory or event.src_path.endswith((".tmp", ".swp", "~")):
|
|
94
|
+
return
|
|
95
|
+
|
|
96
|
+
self.logger.debug(
|
|
97
|
+
f"File change detected: {event.event_type} - {event.src_path}"
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
# STABILITY FIX: Use lock to prevent timer race condition
|
|
101
|
+
with self._timer_lock:
|
|
102
|
+
# Cancel existing timer
|
|
103
|
+
if self.debounce_timer:
|
|
104
|
+
self.debounce_timer.cancel()
|
|
105
|
+
|
|
106
|
+
# Schedule reload after debounce delay
|
|
107
|
+
self.debounce_timer = threading.Timer(
|
|
108
|
+
self.debounce_delay, self._trigger_reload
|
|
109
|
+
)
|
|
110
|
+
self.debounce_timer.start()
|
|
111
|
+
|
|
112
|
+
def _trigger_reload(self):
|
|
113
|
+
"""Trigger hot reload by emitting Socket.IO event."""
|
|
114
|
+
try:
|
|
115
|
+
# Schedule the async emit in the event loop
|
|
116
|
+
asyncio.run_coroutine_threadsafe(self._emit_reload_event(), self.loop)
|
|
117
|
+
self.logger.info("Hot reload triggered - Svelte build changed")
|
|
118
|
+
except Exception as e:
|
|
119
|
+
self.logger.error(f"Error triggering reload: {e}")
|
|
120
|
+
|
|
121
|
+
async def _emit_reload_event(self):
|
|
122
|
+
"""Emit the reload event to all connected clients."""
|
|
123
|
+
if self.sio:
|
|
124
|
+
await self.sio.emit(
|
|
125
|
+
"reload",
|
|
126
|
+
{
|
|
127
|
+
"type": "reload",
|
|
128
|
+
"timestamp": datetime.now(timezone.utc).isoformat() + "Z",
|
|
129
|
+
"reason": "svelte-build-updated",
|
|
130
|
+
},
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
|
|
48
134
|
class UnifiedMonitorServer:
|
|
49
135
|
"""Unified server that combines HTTP dashboard and Socket.IO functionality.
|
|
50
136
|
|
|
@@ -52,15 +138,19 @@ class UnifiedMonitorServer:
|
|
|
52
138
|
Replaces multiple competing server implementations with one stable solution.
|
|
53
139
|
"""
|
|
54
140
|
|
|
55
|
-
def __init__(
|
|
141
|
+
def __init__(
|
|
142
|
+
self, host: str = "localhost", port: int = 8765, enable_hot_reload: bool = False
|
|
143
|
+
):
|
|
56
144
|
"""Initialize the unified monitor server.
|
|
57
145
|
|
|
58
146
|
Args:
|
|
59
147
|
host: Host to bind to
|
|
60
148
|
port: Port to bind to
|
|
149
|
+
enable_hot_reload: Enable file watching and hot reload for development
|
|
61
150
|
"""
|
|
62
151
|
self.host = host
|
|
63
152
|
self.port = port
|
|
153
|
+
self.enable_hot_reload = enable_hot_reload
|
|
64
154
|
self.logger = get_logger(__name__)
|
|
65
155
|
|
|
66
156
|
# Core components
|
|
@@ -78,6 +168,10 @@ class UnifiedMonitorServer:
|
|
|
78
168
|
# High-performance event emitter
|
|
79
169
|
self.event_emitter = None
|
|
80
170
|
|
|
171
|
+
# File watching (optional for dev mode)
|
|
172
|
+
self.file_observer: Optional[Observer] = None
|
|
173
|
+
self.file_watcher: Optional[SvelteBuildWatcher] = None
|
|
174
|
+
|
|
81
175
|
# State
|
|
82
176
|
self.running = False
|
|
83
177
|
self.loop = None
|
|
@@ -184,6 +278,9 @@ class UnifiedMonitorServer:
|
|
|
184
278
|
|
|
185
279
|
time.sleep(0.1)
|
|
186
280
|
|
|
281
|
+
# STABILITY FIX: Give tasks more time to clean up before closing
|
|
282
|
+
time.sleep(0.5)
|
|
283
|
+
|
|
187
284
|
# Clear the event loop from the thread BEFORE closing
|
|
188
285
|
# This prevents other code from accidentally using it
|
|
189
286
|
asyncio.set_event_loop(None)
|
|
@@ -229,6 +326,10 @@ class UnifiedMonitorServer:
|
|
|
229
326
|
self.heartbeat_task = asyncio.create_task(self._heartbeat_loop())
|
|
230
327
|
self.logger.info("Heartbeat task started (3-minute interval)")
|
|
231
328
|
|
|
329
|
+
# Setup file watching for hot reload (if enabled)
|
|
330
|
+
if self.enable_hot_reload:
|
|
331
|
+
self._setup_file_watcher()
|
|
332
|
+
|
|
232
333
|
# Setup HTTP routes
|
|
233
334
|
self._setup_http_routes()
|
|
234
335
|
|
|
@@ -304,20 +405,64 @@ class UnifiedMonitorServer:
|
|
|
304
405
|
self.logger.error(f"Error setting up event emitter: {e}")
|
|
305
406
|
raise
|
|
306
407
|
|
|
408
|
+
def _setup_file_watcher(self):
|
|
409
|
+
"""Setup file watcher for Svelte build directory.
|
|
410
|
+
|
|
411
|
+
Watches for changes in svelte-build and triggers hot reload.
|
|
412
|
+
Only enabled when enable_hot_reload is True.
|
|
413
|
+
"""
|
|
414
|
+
try:
|
|
415
|
+
dashboard_dir = Path(__file__).resolve().parent.parent.parent / "dashboard"
|
|
416
|
+
svelte_build_dir = dashboard_dir / "static" / "svelte-build"
|
|
417
|
+
|
|
418
|
+
if not svelte_build_dir.exists():
|
|
419
|
+
self.logger.warning(
|
|
420
|
+
f"Svelte build directory not found: {svelte_build_dir}. "
|
|
421
|
+
"Hot reload disabled."
|
|
422
|
+
)
|
|
423
|
+
return
|
|
424
|
+
|
|
425
|
+
# Create file watcher with Socket.IO reference
|
|
426
|
+
self.file_watcher = SvelteBuildWatcher(
|
|
427
|
+
sio=self.sio, loop=self.loop, logger=self.logger
|
|
428
|
+
)
|
|
429
|
+
|
|
430
|
+
# Create observer and schedule watching
|
|
431
|
+
self.file_observer = Observer()
|
|
432
|
+
self.file_observer.schedule(
|
|
433
|
+
self.file_watcher, str(svelte_build_dir), recursive=True
|
|
434
|
+
)
|
|
435
|
+
self.file_observer.start()
|
|
436
|
+
|
|
437
|
+
self.logger.info(f"🔥 Hot reload enabled - watching {svelte_build_dir}")
|
|
438
|
+
|
|
439
|
+
except Exception as e:
|
|
440
|
+
self.logger.error(f"Error setting up file watcher: {e}")
|
|
441
|
+
# Don't raise - hot reload is optional
|
|
442
|
+
|
|
307
443
|
def _setup_http_routes(self):
|
|
308
444
|
"""Setup HTTP routes for the dashboard."""
|
|
309
445
|
try:
|
|
310
|
-
# Dashboard static files
|
|
311
|
-
dashboard_dir = Path(__file__).parent.parent.parent / "dashboard"
|
|
446
|
+
# Dashboard static files - use .resolve() for absolute path
|
|
447
|
+
dashboard_dir = Path(__file__).resolve().parent.parent.parent / "dashboard"
|
|
448
|
+
static_dir = dashboard_dir / "static"
|
|
312
449
|
|
|
313
|
-
# Main dashboard route
|
|
450
|
+
# Main dashboard route - serve Svelte dashboard
|
|
314
451
|
async def dashboard_index(request):
|
|
315
|
-
|
|
316
|
-
if
|
|
317
|
-
with
|
|
452
|
+
svelte_index = static_dir / "svelte-build" / "index.html"
|
|
453
|
+
if svelte_index.exists():
|
|
454
|
+
with svelte_index.open(encoding="utf-8") as f:
|
|
318
455
|
content = f.read()
|
|
319
456
|
return web.Response(text=content, content_type="text/html")
|
|
320
|
-
|
|
457
|
+
|
|
458
|
+
# Log error with path details for debugging
|
|
459
|
+
self.logger.error(
|
|
460
|
+
f"Dashboard index.html not found at: {svelte_index.resolve()}"
|
|
461
|
+
)
|
|
462
|
+
return web.Response(
|
|
463
|
+
text=f"Dashboard not found. Expected location: {svelte_index.resolve()}",
|
|
464
|
+
status=404,
|
|
465
|
+
)
|
|
321
466
|
|
|
322
467
|
# Health check
|
|
323
468
|
async def health_check(request):
|
|
@@ -325,7 +470,8 @@ class UnifiedMonitorServer:
|
|
|
325
470
|
version = "1.0.0"
|
|
326
471
|
try:
|
|
327
472
|
version_file = (
|
|
328
|
-
Path(__file__).parent.parent.parent.parent.parent
|
|
473
|
+
Path(__file__).resolve().parent.parent.parent.parent.parent
|
|
474
|
+
/ "VERSION"
|
|
329
475
|
)
|
|
330
476
|
if version_file.exists():
|
|
331
477
|
version = version_file.read_text().strip()
|
|
@@ -546,12 +692,43 @@ class UnifiedMonitorServer:
|
|
|
546
692
|
"/monitor/events", lambda r: monitor_page_handler(r)
|
|
547
693
|
)
|
|
548
694
|
|
|
549
|
-
#
|
|
550
|
-
|
|
695
|
+
# Serve Svelte _app assets (compiled JS/CSS)
|
|
696
|
+
svelte_build_dir = static_dir / "svelte-build"
|
|
697
|
+
if svelte_build_dir.exists():
|
|
698
|
+
svelte_app_dir = svelte_build_dir / "_app"
|
|
699
|
+
if svelte_app_dir.exists():
|
|
700
|
+
# Serve _app assets with proper caching
|
|
701
|
+
async def app_assets_handler(request):
|
|
702
|
+
"""Serve Svelte _app assets."""
|
|
703
|
+
from aiohttp.web_fileresponse import FileResponse
|
|
704
|
+
|
|
705
|
+
rel_path = request.match_info["filepath"]
|
|
706
|
+
file_path = svelte_app_dir / rel_path
|
|
707
|
+
|
|
708
|
+
if not file_path.exists() or not file_path.is_file():
|
|
709
|
+
raise web.HTTPNotFound()
|
|
710
|
+
|
|
711
|
+
response = FileResponse(file_path)
|
|
712
|
+
|
|
713
|
+
# Add cache headers for immutable assets
|
|
714
|
+
if "/immutable/" in str(rel_path):
|
|
715
|
+
response.headers["Cache-Control"] = (
|
|
716
|
+
"public, max-age=31536000, immutable"
|
|
717
|
+
)
|
|
718
|
+
else:
|
|
719
|
+
response.headers["Cache-Control"] = (
|
|
720
|
+
"no-cache, no-store, must-revalidate"
|
|
721
|
+
)
|
|
722
|
+
|
|
723
|
+
return response
|
|
724
|
+
|
|
725
|
+
self.app.router.add_get("/_app/{filepath:.*}", app_assets_handler)
|
|
726
|
+
|
|
727
|
+
# Legacy static files (for backward compatibility)
|
|
551
728
|
if static_dir.exists():
|
|
552
729
|
|
|
553
730
|
async def static_handler(request):
|
|
554
|
-
"""Serve static files with cache-control headers for development."""
|
|
731
|
+
"""Serve legacy static files with cache-control headers for development."""
|
|
555
732
|
|
|
556
733
|
from aiohttp.web_fileresponse import FileResponse
|
|
557
734
|
|
|
@@ -576,10 +753,13 @@ class UnifiedMonitorServer:
|
|
|
576
753
|
|
|
577
754
|
self.app.router.add_get("/static/{filepath:.*}", static_handler)
|
|
578
755
|
|
|
579
|
-
#
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
756
|
+
# Log dashboard availability
|
|
757
|
+
if svelte_build_dir.exists():
|
|
758
|
+
self.logger.info(
|
|
759
|
+
f"✅ Svelte dashboard available at / (root) (build: {svelte_build_dir})"
|
|
760
|
+
)
|
|
761
|
+
else:
|
|
762
|
+
self.logger.warning(f"Svelte build not found at: {svelte_build_dir}")
|
|
583
763
|
|
|
584
764
|
self.logger.info("HTTP routes registered successfully")
|
|
585
765
|
|
|
@@ -691,11 +871,37 @@ class UnifiedMonitorServer:
|
|
|
691
871
|
async def _cleanup_async(self):
|
|
692
872
|
"""Cleanup async resources."""
|
|
693
873
|
try:
|
|
874
|
+
# Stop file observer if running
|
|
875
|
+
# STABILITY FIX: Ensure watcher is stopped and verify observer termination
|
|
876
|
+
if self.file_observer:
|
|
877
|
+
try:
|
|
878
|
+
# Stop the watcher first to cancel pending timers
|
|
879
|
+
if self.file_watcher:
|
|
880
|
+
self.file_watcher.stop()
|
|
881
|
+
|
|
882
|
+
# Stop the observer
|
|
883
|
+
self.file_observer.stop()
|
|
884
|
+
self.file_observer.join(timeout=2)
|
|
885
|
+
|
|
886
|
+
# Verify observer actually stopped
|
|
887
|
+
if self.file_observer.is_alive():
|
|
888
|
+
self.logger.warning("File observer did not stop cleanly")
|
|
889
|
+
|
|
890
|
+
self.logger.debug("File observer stopped")
|
|
891
|
+
except Exception as e:
|
|
892
|
+
self.logger.debug(f"Error stopping file observer: {e}")
|
|
893
|
+
finally:
|
|
894
|
+
self.file_observer = None
|
|
895
|
+
self.file_watcher = None
|
|
896
|
+
|
|
694
897
|
# Cancel heartbeat task if running
|
|
898
|
+
# STABILITY FIX: Add timeout to prevent infinite wait on cancellation
|
|
695
899
|
if self.heartbeat_task and not self.heartbeat_task.done():
|
|
696
900
|
self.heartbeat_task.cancel()
|
|
697
|
-
|
|
698
|
-
await self.heartbeat_task
|
|
901
|
+
try:
|
|
902
|
+
await asyncio.wait_for(self.heartbeat_task, timeout=2.0)
|
|
903
|
+
except (asyncio.CancelledError, asyncio.TimeoutError):
|
|
904
|
+
pass
|
|
699
905
|
self.logger.debug("Heartbeat task cancelled")
|
|
700
906
|
|
|
701
907
|
# Close the Socket.IO server first to stop accepting new connections
|
|
@@ -3,7 +3,7 @@ Self-Upgrade Service
|
|
|
3
3
|
====================
|
|
4
4
|
|
|
5
5
|
Handles version checking and self-upgrade functionality for claude-mpm.
|
|
6
|
-
Supports pip, pipx, and
|
|
6
|
+
Supports pip, pipx, npm, uv tool, and Homebrew installations with automatic detection.
|
|
7
7
|
Also checks Claude Code version compatibility.
|
|
8
8
|
|
|
9
9
|
WHY: Users should be notified of updates and have an easy way to upgrade
|
|
@@ -11,7 +11,7 @@ without manually running installation commands. Claude Code version checking
|
|
|
11
11
|
ensures compatibility with required features.
|
|
12
12
|
|
|
13
13
|
DESIGN DECISIONS:
|
|
14
|
-
- Detects installation method (pip/pipx/npm/editable)
|
|
14
|
+
- Detects installation method (pip/pipx/npm/uv_tool/homebrew/editable)
|
|
15
15
|
- Non-blocking version checks with caching
|
|
16
16
|
- Interactive upgrade prompts with confirmation
|
|
17
17
|
- Automatic restart after upgrade
|
|
@@ -40,6 +40,8 @@ class InstallationMethod:
|
|
|
40
40
|
PIP = "pip"
|
|
41
41
|
PIPX = "pipx"
|
|
42
42
|
NPM = "npm"
|
|
43
|
+
UV_TOOL = "uv_tool"
|
|
44
|
+
HOMEBREW = "homebrew"
|
|
43
45
|
EDITABLE = "editable"
|
|
44
46
|
UNKNOWN = "unknown"
|
|
45
47
|
|
|
@@ -95,6 +97,14 @@ class SelfUpgradeService:
|
|
|
95
97
|
"""
|
|
96
98
|
Detect how claude-mpm was installed.
|
|
97
99
|
|
|
100
|
+
Detection priority:
|
|
101
|
+
1. Editable (skip auto-upgrade)
|
|
102
|
+
2. UV Tool
|
|
103
|
+
3. Homebrew
|
|
104
|
+
4. Pipx
|
|
105
|
+
5. NPM
|
|
106
|
+
6. Pip (default)
|
|
107
|
+
|
|
98
108
|
Returns:
|
|
99
109
|
Installation method constant
|
|
100
110
|
"""
|
|
@@ -105,6 +115,14 @@ class SelfUpgradeService:
|
|
|
105
115
|
]:
|
|
106
116
|
return InstallationMethod.EDITABLE
|
|
107
117
|
|
|
118
|
+
# Check for UV tool installation
|
|
119
|
+
if self._check_uv_tool_installation():
|
|
120
|
+
return InstallationMethod.UV_TOOL
|
|
121
|
+
|
|
122
|
+
# Check for Homebrew installation
|
|
123
|
+
if self._check_homebrew_installation():
|
|
124
|
+
return InstallationMethod.HOMEBREW
|
|
125
|
+
|
|
108
126
|
# Check for pipx by looking at executable path
|
|
109
127
|
executable = sys.executable
|
|
110
128
|
if "pipx" in executable:
|
|
@@ -127,6 +145,85 @@ class SelfUpgradeService:
|
|
|
127
145
|
# Default to pip
|
|
128
146
|
return InstallationMethod.PIP
|
|
129
147
|
|
|
148
|
+
def _check_uv_tool_installation(self) -> bool:
|
|
149
|
+
"""
|
|
150
|
+
Check if claude-mpm is installed via uv tool.
|
|
151
|
+
|
|
152
|
+
Detection methods:
|
|
153
|
+
1. Check UV_TOOL_DIR environment variable
|
|
154
|
+
2. Check if executable path contains .local/share/uv/tools/
|
|
155
|
+
3. Fallback: Run `uv tool list` and check for claude-mpm
|
|
156
|
+
|
|
157
|
+
Returns:
|
|
158
|
+
True if UV tool installation detected
|
|
159
|
+
"""
|
|
160
|
+
# Method 1: Check UV_TOOL_DIR environment variable
|
|
161
|
+
uv_tool_dir = os.environ.get("UV_TOOL_DIR")
|
|
162
|
+
if uv_tool_dir and "claude-mpm" in uv_tool_dir:
|
|
163
|
+
return True
|
|
164
|
+
|
|
165
|
+
# Method 2: Check executable path
|
|
166
|
+
executable = sys.executable
|
|
167
|
+
if ".local/share/uv/tools/" in executable or "uv/tools/" in executable:
|
|
168
|
+
return True
|
|
169
|
+
|
|
170
|
+
# Method 3: Fallback to `uv tool list` command
|
|
171
|
+
try:
|
|
172
|
+
result = subprocess.run(
|
|
173
|
+
["uv", "tool", "list"],
|
|
174
|
+
check=False,
|
|
175
|
+
capture_output=True,
|
|
176
|
+
text=True,
|
|
177
|
+
timeout=5,
|
|
178
|
+
)
|
|
179
|
+
if result.returncode == 0 and "claude-mpm" in result.stdout:
|
|
180
|
+
return True
|
|
181
|
+
except (FileNotFoundError, subprocess.TimeoutExpired):
|
|
182
|
+
# uv not installed or command failed
|
|
183
|
+
pass
|
|
184
|
+
except Exception as e:
|
|
185
|
+
self.logger.debug(f"UV tool check failed: {e}")
|
|
186
|
+
|
|
187
|
+
return False
|
|
188
|
+
|
|
189
|
+
def _check_homebrew_installation(self) -> bool:
|
|
190
|
+
"""
|
|
191
|
+
Check if claude-mpm is installed via Homebrew.
|
|
192
|
+
|
|
193
|
+
Detection methods:
|
|
194
|
+
1. Check if executable path starts with /opt/homebrew/ or /usr/local/Cellar/
|
|
195
|
+
2. Fallback: Run `brew list claude-mpm` to verify
|
|
196
|
+
|
|
197
|
+
Returns:
|
|
198
|
+
True if Homebrew installation detected
|
|
199
|
+
"""
|
|
200
|
+
# Method 1: Check executable path
|
|
201
|
+
executable = sys.executable
|
|
202
|
+
if executable.startswith("/opt/homebrew/") or executable.startswith(
|
|
203
|
+
"/usr/local/Cellar/"
|
|
204
|
+
):
|
|
205
|
+
return True
|
|
206
|
+
|
|
207
|
+
# Method 2: Fallback to `brew list` command
|
|
208
|
+
try:
|
|
209
|
+
result = subprocess.run(
|
|
210
|
+
["brew", "list", "claude-mpm"],
|
|
211
|
+
check=False,
|
|
212
|
+
capture_output=True,
|
|
213
|
+
text=True,
|
|
214
|
+
timeout=5,
|
|
215
|
+
)
|
|
216
|
+
# brew list returns 0 if package is installed
|
|
217
|
+
if result.returncode == 0:
|
|
218
|
+
return True
|
|
219
|
+
except (FileNotFoundError, subprocess.TimeoutExpired):
|
|
220
|
+
# brew not installed or command failed
|
|
221
|
+
pass
|
|
222
|
+
except Exception as e:
|
|
223
|
+
self.logger.debug(f"Homebrew check failed: {e}")
|
|
224
|
+
|
|
225
|
+
return False
|
|
226
|
+
|
|
130
227
|
def _get_claude_code_version(self) -> Optional[str]:
|
|
131
228
|
"""
|
|
132
229
|
Get the installed Claude Code version.
|
|
@@ -257,6 +354,8 @@ class SelfUpgradeService:
|
|
|
257
354
|
if self.installation_method in [
|
|
258
355
|
InstallationMethod.PIP,
|
|
259
356
|
InstallationMethod.PIPX,
|
|
357
|
+
InstallationMethod.UV_TOOL,
|
|
358
|
+
InstallationMethod.HOMEBREW,
|
|
260
359
|
]:
|
|
261
360
|
result = await self.version_checker.check_for_update(
|
|
262
361
|
"claude-mpm", self.current_version, cache_ttl
|
|
@@ -313,15 +412,18 @@ class SelfUpgradeService:
|
|
|
313
412
|
Returns:
|
|
314
413
|
Shell command string to upgrade claude-mpm
|
|
315
414
|
"""
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
415
|
+
upgrade_commands = {
|
|
416
|
+
InstallationMethod.UV_TOOL: "uv tool upgrade claude-mpm",
|
|
417
|
+
InstallationMethod.HOMEBREW: "brew upgrade claude-mpm",
|
|
418
|
+
InstallationMethod.PIPX: "pipx upgrade claude-mpm",
|
|
419
|
+
InstallationMethod.NPM: "npm update -g claude-mpm",
|
|
420
|
+
InstallationMethod.PIP: f"{sys.executable} -m pip install --upgrade claude-mpm",
|
|
421
|
+
InstallationMethod.EDITABLE: "git pull && pip install -e .",
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
return upgrade_commands.get(
|
|
425
|
+
self.installation_method, "pip install --upgrade claude-mpm"
|
|
426
|
+
)
|
|
325
427
|
|
|
326
428
|
def prompt_for_upgrade(self, update_info: Dict[str, any]) -> bool:
|
|
327
429
|
"""
|
|
@@ -432,7 +534,13 @@ class SelfUpgradeService:
|
|
|
432
534
|
args = sys.argv[:]
|
|
433
535
|
|
|
434
536
|
# Replace current process with new one
|
|
435
|
-
if self.installation_method == InstallationMethod.
|
|
537
|
+
if self.installation_method == InstallationMethod.UV_TOOL:
|
|
538
|
+
# Use uv run
|
|
539
|
+
os.execvp("uv", ["uv", "tool", "run", "claude-mpm", *args[1:]])
|
|
540
|
+
elif self.installation_method == InstallationMethod.HOMEBREW:
|
|
541
|
+
# Use direct executable (installed to PATH by Homebrew)
|
|
542
|
+
os.execvp("claude-mpm", args)
|
|
543
|
+
elif self.installation_method == InstallationMethod.PIPX:
|
|
436
544
|
# Use pipx run
|
|
437
545
|
os.execvp("pipx", ["pipx", "run", "claude-mpm", *args[1:]])
|
|
438
546
|
elif self.installation_method == InstallationMethod.NPM:
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
This package provides services for Git-based skills management:
|
|
4
4
|
- GitSkillSourceManager: Multi-repository orchestration with priority resolution
|
|
5
5
|
- SkillDiscoveryService: Parse skills from Git repositories (Markdown with YAML frontmatter)
|
|
6
|
+
- SkillToAgentMapper: Map skills to agents using YAML configuration
|
|
6
7
|
"""
|
|
7
8
|
|
|
8
9
|
from claude_mpm.services.skills.git_skill_source_manager import GitSkillSourceManager
|
|
@@ -10,9 +11,11 @@ from claude_mpm.services.skills.skill_discovery_service import (
|
|
|
10
11
|
SkillDiscoveryService,
|
|
11
12
|
SkillMetadata,
|
|
12
13
|
)
|
|
14
|
+
from claude_mpm.services.skills.skill_to_agent_mapper import SkillToAgentMapper
|
|
13
15
|
|
|
14
16
|
__all__ = [
|
|
15
17
|
"GitSkillSourceManager",
|
|
16
18
|
"SkillDiscoveryService",
|
|
17
19
|
"SkillMetadata",
|
|
20
|
+
"SkillToAgentMapper",
|
|
18
21
|
]
|
|
@@ -20,7 +20,7 @@ from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
|
20
20
|
from datetime import datetime, timezone
|
|
21
21
|
from pathlib import Path
|
|
22
22
|
from threading import Lock
|
|
23
|
-
from typing import Any, Dict, List, Optional, Tuple
|
|
23
|
+
from typing import Any, Dict, List, Optional, Set, Tuple
|
|
24
24
|
|
|
25
25
|
from claude_mpm.config.skill_sources import SkillSource, SkillSourceConfiguration
|
|
26
26
|
from claude_mpm.core.logging_config import get_logger
|
|
@@ -989,6 +989,7 @@ class GitSkillSourceManager:
|
|
|
989
989
|
target_dir: Optional[Path] = None,
|
|
990
990
|
force: bool = False,
|
|
991
991
|
progress_callback=None,
|
|
992
|
+
skill_filter: Optional[Set[str]] = None,
|
|
992
993
|
) -> Dict[str, Any]:
|
|
993
994
|
"""Deploy skills from cache to target directory with flat structure.
|
|
994
995
|
|
|
@@ -1004,6 +1005,9 @@ class GitSkillSourceManager:
|
|
|
1004
1005
|
target_dir: Target deployment directory (default: ~/.claude/skills/)
|
|
1005
1006
|
force: Overwrite existing skills
|
|
1006
1007
|
progress_callback: Optional callback(increment: int) called for each skill deployed
|
|
1008
|
+
skill_filter: Optional set of skill names to deploy (selective deployment).
|
|
1009
|
+
If None, deploys all skills. If provided, only deploys skills
|
|
1010
|
+
whose name matches an entry in the filter set.
|
|
1007
1011
|
|
|
1008
1012
|
Returns:
|
|
1009
1013
|
Dict with deployment results:
|
|
@@ -1013,13 +1017,19 @@ class GitSkillSourceManager:
|
|
|
1013
1017
|
"failed_count": int,
|
|
1014
1018
|
"deployed_skills": List[str],
|
|
1015
1019
|
"skipped_skills": List[str],
|
|
1016
|
-
"errors": List[str]
|
|
1020
|
+
"errors": List[str],
|
|
1021
|
+
"filtered_count": int # Number of skills filtered out
|
|
1017
1022
|
}
|
|
1018
1023
|
|
|
1019
1024
|
Example:
|
|
1020
1025
|
>>> manager = GitSkillSourceManager(config)
|
|
1021
1026
|
>>> result = manager.deploy_skills()
|
|
1022
1027
|
>>> print(f"Deployed {result['deployed_count']} skills")
|
|
1028
|
+
|
|
1029
|
+
# Selective deployment based on agent requirements:
|
|
1030
|
+
>>> required = {"typescript-core", "react-patterns"}
|
|
1031
|
+
>>> result = manager.deploy_skills(skill_filter=required)
|
|
1032
|
+
>>> print(f"Deployed {result['deployed_count']} of {len(required)} required skills")
|
|
1023
1033
|
"""
|
|
1024
1034
|
if target_dir is None:
|
|
1025
1035
|
target_dir = Path.home() / ".claude" / "skills"
|
|
@@ -1029,10 +1039,29 @@ class GitSkillSourceManager:
|
|
|
1029
1039
|
deployed = []
|
|
1030
1040
|
skipped = []
|
|
1031
1041
|
errors = []
|
|
1042
|
+
filtered_count = 0
|
|
1032
1043
|
|
|
1033
1044
|
# Get all skills from all sources
|
|
1034
1045
|
all_skills = self.get_all_skills()
|
|
1035
1046
|
|
|
1047
|
+
# Apply skill filter if provided (selective deployment)
|
|
1048
|
+
if skill_filter is not None:
|
|
1049
|
+
original_count = len(all_skills)
|
|
1050
|
+
# Normalize filter to lowercase for case-insensitive matching
|
|
1051
|
+
normalized_filter = {s.lower() for s in skill_filter}
|
|
1052
|
+
# Match against deployment_name (not display name) since skill_filter contains
|
|
1053
|
+
# deployment-style names like "toolchains-python-frameworks-django"
|
|
1054
|
+
all_skills = [
|
|
1055
|
+
s
|
|
1056
|
+
for s in all_skills
|
|
1057
|
+
if s.get("deployment_name", "").lower() in normalized_filter
|
|
1058
|
+
]
|
|
1059
|
+
filtered_count = original_count - len(all_skills)
|
|
1060
|
+
self.logger.info(
|
|
1061
|
+
f"Selective deployment: {len(all_skills)} of {original_count} skills "
|
|
1062
|
+
f"match agent requirements ({filtered_count} filtered out)"
|
|
1063
|
+
)
|
|
1064
|
+
|
|
1036
1065
|
self.logger.info(
|
|
1037
1066
|
f"Deploying {len(all_skills)} skills to {target_dir} (force={force})"
|
|
1038
1067
|
)
|
|
@@ -1083,6 +1112,7 @@ class GitSkillSourceManager:
|
|
|
1083
1112
|
"deployed_skills": deployed,
|
|
1084
1113
|
"skipped_skills": skipped,
|
|
1085
1114
|
"errors": errors,
|
|
1115
|
+
"filtered_count": filtered_count,
|
|
1086
1116
|
}
|
|
1087
1117
|
|
|
1088
1118
|
def _deploy_single_skill(
|