claude-mpm 4.2.2__py3-none-any.whl → 4.2.3__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/cli/commands/monitor.py +1 -1
- claude_mpm/core/config.py +2 -2
- claude_mpm/dashboard/static/css/code-tree.css +220 -1
- claude_mpm/dashboard/static/css/dashboard.css +286 -0
- claude_mpm/dashboard/static/js/components/code-tree.js +1821 -88
- claude_mpm/dashboard/static/js/socket-client.js +5 -2
- claude_mpm/dashboard/templates/index.html +41 -40
- claude_mpm/services/agents/deployment/agent_template_builder.py +17 -4
- claude_mpm/services/dashboard/stable_server.py +389 -0
- claude_mpm/services/socketio/client_proxy.py +16 -0
- claude_mpm/services/socketio/handlers/code_analysis.py +27 -5
- claude_mpm/services/socketio/monitor_server.py +2 -2
- claude_mpm/tools/code_tree_analyzer.py +95 -17
- {claude_mpm-4.2.2.dist-info → claude_mpm-4.2.3.dist-info}/METADATA +1 -1
- {claude_mpm-4.2.2.dist-info → claude_mpm-4.2.3.dist-info}/RECORD +20 -19
- {claude_mpm-4.2.2.dist-info → claude_mpm-4.2.3.dist-info}/WHEEL +0 -0
- {claude_mpm-4.2.2.dist-info → claude_mpm-4.2.3.dist-info}/entry_points.txt +0 -0
- {claude_mpm-4.2.2.dist-info → claude_mpm-4.2.3.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-4.2.2.dist-info → claude_mpm-4.2.3.dist-info}/top_level.txt +0 -0
|
@@ -1218,8 +1218,11 @@ class SocketClient {
|
|
|
1218
1218
|
transformedEvent[key] = eventData.data[key];
|
|
1219
1219
|
}
|
|
1220
1220
|
} else {
|
|
1221
|
-
// Log
|
|
1222
|
-
|
|
1221
|
+
// Log debug info if data field would overwrite a protected field
|
|
1222
|
+
// Only log for non-timestamp fields to reduce noise
|
|
1223
|
+
if (key !== 'timestamp') {
|
|
1224
|
+
console.debug(`Protected field '${key}' in data object was not copied to top level to preserve event structure`);
|
|
1225
|
+
}
|
|
1223
1226
|
}
|
|
1224
1227
|
});
|
|
1225
1228
|
|
|
@@ -247,7 +247,6 @@
|
|
|
247
247
|
<button class="tab-button" data-tab="files">📁 Files</button>
|
|
248
248
|
<button class="tab-button" data-tab="activity">🌳 Activity</button>
|
|
249
249
|
<button class="tab-button" data-tab="code">🧬 Code</button>
|
|
250
|
-
<a href="/code-simple" class="tab-button" style="background: #f7fafc; color: #4a5568; text-decoration: none; border-left: 2px solid #e2e8f0;">📁 Simple View</a>
|
|
251
250
|
</div>
|
|
252
251
|
|
|
253
252
|
<!-- Events Tab -->
|
|
@@ -389,45 +388,52 @@
|
|
|
389
388
|
<!-- Code Tab -->
|
|
390
389
|
<div class="tab-content" id="code-tab">
|
|
391
390
|
<div class="code-container">
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
<div class="
|
|
395
|
-
<
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
<span class="stat-compact">⚡ <span id="function-count">0</span></span>
|
|
404
|
-
<span class="stat-compact">📝 <span id="line-count">0</span></span>
|
|
391
|
+
<div id="code-tree-container" class="code-tree-container">
|
|
392
|
+
<!-- Top-left corner: Language selector -->
|
|
393
|
+
<div class="tree-corner-controls top-left">
|
|
394
|
+
<div class="control-group">
|
|
395
|
+
<label class="control-label">Languages:</label>
|
|
396
|
+
<div class="checkbox-group">
|
|
397
|
+
<label class="checkbox-label"><input type="checkbox" class="language-checkbox" value="python" checked> Python</label>
|
|
398
|
+
<label class="checkbox-label"><input type="checkbox" class="language-checkbox" value="javascript" checked> JS</label>
|
|
399
|
+
<label class="checkbox-label"><input type="checkbox" class="language-checkbox" value="typescript" checked> TS</label>
|
|
400
|
+
</div>
|
|
401
|
+
</div>
|
|
405
402
|
</div>
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
<
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
403
|
+
|
|
404
|
+
<!-- Top-right corner: Layout and search -->
|
|
405
|
+
<div class="tree-corner-controls top-right">
|
|
406
|
+
<div class="control-group">
|
|
407
|
+
<select id="code-layout" class="select-compact">
|
|
408
|
+
<option value="tree">Tree</option>
|
|
409
|
+
<option value="radial">Radial</option>
|
|
410
|
+
</select>
|
|
411
|
+
<input type="text" id="code-search" placeholder="Search..." class="search-compact">
|
|
412
|
+
</div>
|
|
414
413
|
</div>
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
<
|
|
421
|
-
<
|
|
422
|
-
<
|
|
423
|
-
<label><input type="checkbox" class="language-checkbox" value="typescript" checked> TS</label>
|
|
414
|
+
|
|
415
|
+
<!-- Bottom-left corner: Stats and Status -->
|
|
416
|
+
<div class="tree-corner-controls bottom-left">
|
|
417
|
+
<div class="stats-display" id="code-stats">
|
|
418
|
+
<span id="stats-files">0 files</span> •
|
|
419
|
+
<span id="stats-classes">0 classes</span> •
|
|
420
|
+
<span id="stats-functions">0 functions</span> •
|
|
421
|
+
<span id="stats-methods">0 methods</span>
|
|
424
422
|
</div>
|
|
425
|
-
<div class="
|
|
426
|
-
<
|
|
423
|
+
<div class="status-display" id="code-breadcrumb">
|
|
424
|
+
<div class="breadcrumb-ticker" id="breadcrumb-ticker">
|
|
425
|
+
<span id="breadcrumb-content">Ready to analyze...</span>
|
|
426
|
+
</div>
|
|
427
|
+
</div>
|
|
428
|
+
</div>
|
|
429
|
+
|
|
430
|
+
<!-- Bottom-right corner: Ignore patterns -->
|
|
431
|
+
<div class="tree-corner-controls bottom-right">
|
|
432
|
+
<div class="control-group">
|
|
433
|
+
<label class="control-label">Ignore:</label>
|
|
434
|
+
<input type="text" id="ignore-patterns" placeholder="test*, *.spec.js, node_modules" class="input-compact">
|
|
427
435
|
</div>
|
|
428
436
|
</div>
|
|
429
|
-
</div>
|
|
430
|
-
<div id="code-tree-container" class="code-tree-container">
|
|
431
437
|
<div id="code-tree"></div>
|
|
432
438
|
<!-- Collapsible legend -->
|
|
433
439
|
<div class="tree-legend collapsed" id="tree-legend" style="display: none;">
|
|
@@ -447,11 +453,6 @@
|
|
|
447
453
|
</div>
|
|
448
454
|
</div>
|
|
449
455
|
</div>
|
|
450
|
-
<div class="code-breadcrumb" id="code-breadcrumb">
|
|
451
|
-
<div class="breadcrumb-ticker" id="breadcrumb-ticker">
|
|
452
|
-
<span id="breadcrumb-content">Ready to analyze...</span>
|
|
453
|
-
</div>
|
|
454
|
-
</div>
|
|
455
456
|
</div>
|
|
456
457
|
</div>
|
|
457
458
|
|
|
@@ -704,16 +704,29 @@ tools:
|
|
|
704
704
|
if triggers and not examples:
|
|
705
705
|
# Convert first trigger to example with commentary
|
|
706
706
|
trigger = triggers[0]
|
|
707
|
+
|
|
708
|
+
# Handle both string and dict trigger formats
|
|
709
|
+
if isinstance(trigger, dict):
|
|
710
|
+
# New format with pattern and confidence
|
|
711
|
+
trigger_text = trigger.get("pattern", "")
|
|
712
|
+
else:
|
|
713
|
+
# Old format with simple string
|
|
714
|
+
trigger_text = str(trigger)
|
|
715
|
+
|
|
716
|
+
# Skip if we don't have valid trigger text
|
|
717
|
+
if not trigger_text:
|
|
718
|
+
return examples
|
|
719
|
+
|
|
707
720
|
agent_type = template_data.get("agent_type", "general")
|
|
708
721
|
|
|
709
722
|
examples.extend(
|
|
710
723
|
[
|
|
711
724
|
"<example>",
|
|
712
|
-
f"Context: When user needs {
|
|
713
|
-
f'user: "{
|
|
714
|
-
f'assistant: "I\'ll use the {agent_name} agent for {
|
|
725
|
+
f"Context: When user needs {trigger_text}",
|
|
726
|
+
f'user: "{trigger_text}"',
|
|
727
|
+
f'assistant: "I\'ll use the {agent_name} agent for {trigger_text}."',
|
|
715
728
|
"<commentary>",
|
|
716
|
-
f"This {agent_type} agent is appropriate because it has specialized capabilities for {
|
|
729
|
+
f"This {agent_type} agent is appropriate because it has specialized capabilities for {trigger_text.lower()} tasks.",
|
|
717
730
|
"</commentary>",
|
|
718
731
|
"</example>",
|
|
719
732
|
]
|
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Stable Dashboard Server for claude-mpm.
|
|
3
|
+
|
|
4
|
+
WHY: This module provides a simple, stable HTTP + SocketIO server that works
|
|
5
|
+
across all installation methods (direct, pip, pipx, homebrew, npm).
|
|
6
|
+
|
|
7
|
+
DESIGN DECISIONS:
|
|
8
|
+
- Uses proven python-socketio + aiohttp combination
|
|
9
|
+
- Automatically finds dashboard files across installation methods
|
|
10
|
+
- Provides both HTTP endpoints and SocketIO real-time features
|
|
11
|
+
- Simple mock AST analysis to avoid complex backend dependencies
|
|
12
|
+
- Graceful fallbacks for missing dependencies
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
import glob
|
|
16
|
+
import os
|
|
17
|
+
import sys
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
from typing import Any, Dict, Optional
|
|
20
|
+
|
|
21
|
+
try:
|
|
22
|
+
import aiohttp
|
|
23
|
+
import socketio
|
|
24
|
+
from aiohttp import web
|
|
25
|
+
|
|
26
|
+
DEPENDENCIES_AVAILABLE = True
|
|
27
|
+
except ImportError:
|
|
28
|
+
DEPENDENCIES_AVAILABLE = False
|
|
29
|
+
socketio = None
|
|
30
|
+
aiohttp = None
|
|
31
|
+
web = None
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def find_dashboard_files() -> Optional[Path]:
|
|
35
|
+
"""Find dashboard files across different installation methods."""
|
|
36
|
+
# Try different possible locations
|
|
37
|
+
possible_locations = [
|
|
38
|
+
# Development/direct install
|
|
39
|
+
Path(__file__).parent.parent.parent / "dashboard",
|
|
40
|
+
# Current working directory (for development)
|
|
41
|
+
Path.cwd() / "src" / "claude_mpm" / "dashboard",
|
|
42
|
+
# Pip install in current Python environment
|
|
43
|
+
Path(sys.prefix)
|
|
44
|
+
/ "lib"
|
|
45
|
+
/ f"python{sys.version_info.major}.{sys.version_info.minor}"
|
|
46
|
+
/ "site-packages"
|
|
47
|
+
/ "claude_mpm"
|
|
48
|
+
/ "dashboard",
|
|
49
|
+
# User site-packages
|
|
50
|
+
Path.home()
|
|
51
|
+
/ ".local"
|
|
52
|
+
/ "lib"
|
|
53
|
+
/ f"python{sys.version_info.major}.{sys.version_info.minor}"
|
|
54
|
+
/ "site-packages"
|
|
55
|
+
/ "claude_mpm"
|
|
56
|
+
/ "dashboard",
|
|
57
|
+
]
|
|
58
|
+
|
|
59
|
+
# Add glob patterns for different Python versions
|
|
60
|
+
python_patterns = [
|
|
61
|
+
f"/opt/homebrew/lib/python{sys.version_info.major}.{sys.version_info.minor}/site-packages/claude_mpm/dashboard",
|
|
62
|
+
f"/usr/local/lib/python{sys.version_info.major}.{sys.version_info.minor}/site-packages/claude_mpm/dashboard",
|
|
63
|
+
]
|
|
64
|
+
|
|
65
|
+
# Check direct paths first
|
|
66
|
+
for location in possible_locations:
|
|
67
|
+
if location.exists() and (location / "templates" / "index.html").exists():
|
|
68
|
+
return location
|
|
69
|
+
|
|
70
|
+
# Check pattern-based paths
|
|
71
|
+
for pattern in python_patterns:
|
|
72
|
+
matches = glob.glob(pattern)
|
|
73
|
+
for match in matches:
|
|
74
|
+
path = Path(match)
|
|
75
|
+
if path.exists() and (path / "templates" / "index.html").exists():
|
|
76
|
+
return path
|
|
77
|
+
|
|
78
|
+
# Fallback: try to find via module import
|
|
79
|
+
try:
|
|
80
|
+
import claude_mpm.dashboard
|
|
81
|
+
|
|
82
|
+
module_path = Path(claude_mpm.dashboard.__file__).parent
|
|
83
|
+
if (module_path / "templates" / "index.html").exists():
|
|
84
|
+
return module_path
|
|
85
|
+
except ImportError:
|
|
86
|
+
pass
|
|
87
|
+
|
|
88
|
+
return None
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def create_mock_ast_data(file_path: str, file_name: str) -> Dict[str, Any]:
|
|
92
|
+
"""Create mock AST analysis data."""
|
|
93
|
+
ext = file_name.split(".")[-1].lower() if "." in file_name else ""
|
|
94
|
+
|
|
95
|
+
elements = []
|
|
96
|
+
if ext == "py":
|
|
97
|
+
elements = [
|
|
98
|
+
{
|
|
99
|
+
"name": "MockClass",
|
|
100
|
+
"type": "class",
|
|
101
|
+
"line": 10,
|
|
102
|
+
"complexity": 2,
|
|
103
|
+
"docstring": "Mock class for demonstration",
|
|
104
|
+
"methods": [
|
|
105
|
+
{"name": "__init__", "type": "method", "line": 11, "complexity": 1},
|
|
106
|
+
{
|
|
107
|
+
"name": "mock_method",
|
|
108
|
+
"type": "method",
|
|
109
|
+
"line": 15,
|
|
110
|
+
"complexity": 1,
|
|
111
|
+
},
|
|
112
|
+
],
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
"name": "mock_function",
|
|
116
|
+
"type": "function",
|
|
117
|
+
"line": 20,
|
|
118
|
+
"complexity": 1,
|
|
119
|
+
"docstring": "Mock function for demonstration",
|
|
120
|
+
},
|
|
121
|
+
]
|
|
122
|
+
elif ext in ["js", "ts", "jsx", "tsx"]:
|
|
123
|
+
elements = [
|
|
124
|
+
{
|
|
125
|
+
"name": "MockClass",
|
|
126
|
+
"type": "class",
|
|
127
|
+
"line": 5,
|
|
128
|
+
"complexity": 2,
|
|
129
|
+
"methods": [
|
|
130
|
+
{
|
|
131
|
+
"name": "constructor",
|
|
132
|
+
"type": "method",
|
|
133
|
+
"line": 6,
|
|
134
|
+
"complexity": 1,
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
"name": "mockMethod",
|
|
138
|
+
"type": "method",
|
|
139
|
+
"line": 10,
|
|
140
|
+
"complexity": 1,
|
|
141
|
+
},
|
|
142
|
+
],
|
|
143
|
+
},
|
|
144
|
+
{"name": "mockFunction", "type": "function", "line": 15, "complexity": 1},
|
|
145
|
+
]
|
|
146
|
+
|
|
147
|
+
return {
|
|
148
|
+
"path": file_path,
|
|
149
|
+
"elements": elements,
|
|
150
|
+
"complexity": sum(e.get("complexity", 1) for e in elements),
|
|
151
|
+
"lines": 50,
|
|
152
|
+
"stats": {
|
|
153
|
+
"classes": len([e for e in elements if e["type"] == "class"]),
|
|
154
|
+
"functions": len([e for e in elements if e["type"] == "function"]),
|
|
155
|
+
"methods": sum(len(e.get("methods", [])) for e in elements),
|
|
156
|
+
"lines": 50,
|
|
157
|
+
},
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
class StableDashboardServer:
|
|
162
|
+
"""Stable dashboard server that works across all installation methods."""
|
|
163
|
+
|
|
164
|
+
def __init__(self, host: str = "localhost", port: int = 8765, debug: bool = False):
|
|
165
|
+
self.host = host
|
|
166
|
+
self.port = port
|
|
167
|
+
self.debug = debug
|
|
168
|
+
self.dashboard_path = None
|
|
169
|
+
self.app = None
|
|
170
|
+
self.sio = None
|
|
171
|
+
|
|
172
|
+
def setup(self) -> bool:
|
|
173
|
+
"""Set up the server components."""
|
|
174
|
+
if not DEPENDENCIES_AVAILABLE:
|
|
175
|
+
print(
|
|
176
|
+
"❌ Error: Missing dependencies. Install with: pip install aiohttp python-socketio"
|
|
177
|
+
)
|
|
178
|
+
return False
|
|
179
|
+
|
|
180
|
+
# Find dashboard files
|
|
181
|
+
self.dashboard_path = find_dashboard_files()
|
|
182
|
+
if not self.dashboard_path:
|
|
183
|
+
print("❌ Error: Could not find dashboard files")
|
|
184
|
+
print("Please ensure Claude MPM is properly installed")
|
|
185
|
+
return False
|
|
186
|
+
|
|
187
|
+
print(f"📁 Using dashboard files from: {self.dashboard_path}")
|
|
188
|
+
|
|
189
|
+
# Create SocketIO server
|
|
190
|
+
self.sio = socketio.AsyncServer(
|
|
191
|
+
cors_allowed_origins="*", logger=True, engineio_logger=True
|
|
192
|
+
)
|
|
193
|
+
self.app = web.Application()
|
|
194
|
+
self.sio.attach(self.app)
|
|
195
|
+
print("✅ SocketIO server created and attached")
|
|
196
|
+
|
|
197
|
+
# Set up routes
|
|
198
|
+
self._setup_routes()
|
|
199
|
+
self._setup_socketio_events()
|
|
200
|
+
|
|
201
|
+
print("✅ Server setup complete!")
|
|
202
|
+
|
|
203
|
+
return True
|
|
204
|
+
|
|
205
|
+
def _setup_routes(self):
|
|
206
|
+
"""Set up HTTP routes."""
|
|
207
|
+
self.app.router.add_get("/", self._serve_dashboard)
|
|
208
|
+
self.app.router.add_get("/static/{path:.*}", self._serve_static)
|
|
209
|
+
self.app.router.add_get("/api/directory/list", self._list_directory)
|
|
210
|
+
self.app.router.add_get("/version.json", self._serve_version)
|
|
211
|
+
|
|
212
|
+
def _setup_socketio_events(self):
|
|
213
|
+
"""Set up SocketIO event handlers."""
|
|
214
|
+
|
|
215
|
+
@self.sio.event
|
|
216
|
+
async def connect(sid, environ):
|
|
217
|
+
print(f"✅ SocketIO client connected: {sid}")
|
|
218
|
+
print(f" Client info: {environ.get('HTTP_USER_AGENT', 'Unknown')}")
|
|
219
|
+
# Send a test message to confirm connection
|
|
220
|
+
await self.sio.emit(
|
|
221
|
+
"connection_test", {"status": "connected", "server": "stable"}, room=sid
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
@self.sio.event
|
|
225
|
+
async def disconnect(sid):
|
|
226
|
+
print(f"❌ SocketIO client disconnected: {sid}")
|
|
227
|
+
|
|
228
|
+
@self.sio.event
|
|
229
|
+
async def code_analyze_file(sid, data):
|
|
230
|
+
print(
|
|
231
|
+
f"📡 Received file analysis request from {sid}: {data.get('path', 'unknown')}"
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
file_path = data.get("path", "")
|
|
235
|
+
file_name = file_path.split("/")[-1] if file_path else "unknown"
|
|
236
|
+
|
|
237
|
+
# Create mock response
|
|
238
|
+
response = create_mock_ast_data(file_path, file_name)
|
|
239
|
+
|
|
240
|
+
print(f"📤 Sending analysis response: {len(response['elements'])} elements")
|
|
241
|
+
await self.sio.emit("code:file:analyzed", response, room=sid)
|
|
242
|
+
|
|
243
|
+
# CRITICAL: Handle the actual event name with colons that the client sends
|
|
244
|
+
@self.sio.on("code:analyze:file")
|
|
245
|
+
async def handle_code_analyze_file(sid, data):
|
|
246
|
+
print(
|
|
247
|
+
f"📡 Received code:analyze:file from {sid}: {data.get('path', 'unknown')}"
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
file_path = data.get("path", "")
|
|
251
|
+
file_name = file_path.split("/")[-1] if file_path else "unknown"
|
|
252
|
+
|
|
253
|
+
# Create mock response
|
|
254
|
+
response = create_mock_ast_data(file_path, file_name)
|
|
255
|
+
|
|
256
|
+
print(f"📤 Sending analysis response: {len(response['elements'])} elements")
|
|
257
|
+
await self.sio.emit("code:file:analyzed", response, room=sid)
|
|
258
|
+
|
|
259
|
+
# Handle other events the dashboard sends
|
|
260
|
+
@self.sio.event
|
|
261
|
+
async def get_git_branch(sid, data):
|
|
262
|
+
print(f"📡 Received git branch request from {sid}: {data}")
|
|
263
|
+
await self.sio.emit(
|
|
264
|
+
"git_branch_response", {"branch": "main", "path": data}, room=sid
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
@self.sio.event
|
|
268
|
+
async def request_status(sid, data):
|
|
269
|
+
print(f"📡 Received status request from {sid}")
|
|
270
|
+
await self.sio.emit(
|
|
271
|
+
"status_response", {"status": "running", "server": "stable"}, room=sid
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
# Handle the event with dots (SocketIO converts colons to dots sometimes)
|
|
275
|
+
@self.sio.event
|
|
276
|
+
async def request_dot_status(sid, data):
|
|
277
|
+
print(f"📡 Received request.status from {sid}")
|
|
278
|
+
await self.sio.emit(
|
|
279
|
+
"status_response", {"status": "running", "server": "stable"}, room=sid
|
|
280
|
+
)
|
|
281
|
+
|
|
282
|
+
@self.sio.event
|
|
283
|
+
async def code_discover_top_level(sid, data):
|
|
284
|
+
print(f"📡 Received top-level discovery request from {sid}")
|
|
285
|
+
await self.sio.emit("code:top_level:discovered", {"status": "ok"}, room=sid)
|
|
286
|
+
|
|
287
|
+
async def _serve_dashboard(self, request):
|
|
288
|
+
"""Serve the main dashboard HTML."""
|
|
289
|
+
dashboard_file = self.dashboard_path / "templates" / "index.html"
|
|
290
|
+
if dashboard_file.exists():
|
|
291
|
+
with open(dashboard_file) as f:
|
|
292
|
+
content = f.read()
|
|
293
|
+
return web.Response(text=content, content_type="text/html")
|
|
294
|
+
return web.Response(text="Dashboard not found", status=404)
|
|
295
|
+
|
|
296
|
+
async def _serve_static(self, request):
|
|
297
|
+
"""Serve static files."""
|
|
298
|
+
file_path = request.match_info["path"]
|
|
299
|
+
static_file = self.dashboard_path / "static" / file_path
|
|
300
|
+
|
|
301
|
+
if static_file.exists() and static_file.is_file():
|
|
302
|
+
content_type = (
|
|
303
|
+
"text/javascript"
|
|
304
|
+
if file_path.endswith(".js")
|
|
305
|
+
else "text/css" if file_path.endswith(".css") else "text/plain"
|
|
306
|
+
)
|
|
307
|
+
with open(static_file) as f:
|
|
308
|
+
content = f.read()
|
|
309
|
+
return web.Response(text=content, content_type=content_type)
|
|
310
|
+
return web.Response(text="File not found", status=404)
|
|
311
|
+
|
|
312
|
+
async def _list_directory(self, request):
|
|
313
|
+
"""List directory contents."""
|
|
314
|
+
path = request.query.get("path", ".")
|
|
315
|
+
abs_path = os.path.abspath(os.path.expanduser(path))
|
|
316
|
+
|
|
317
|
+
result = {"path": abs_path, "exists": os.path.exists(abs_path), "contents": []}
|
|
318
|
+
|
|
319
|
+
if os.path.exists(abs_path) and os.path.isdir(abs_path):
|
|
320
|
+
try:
|
|
321
|
+
for item in sorted(os.listdir(abs_path)):
|
|
322
|
+
item_path = os.path.join(abs_path, item)
|
|
323
|
+
result["contents"].append(
|
|
324
|
+
{
|
|
325
|
+
"name": item,
|
|
326
|
+
"path": item_path,
|
|
327
|
+
"is_directory": os.path.isdir(item_path),
|
|
328
|
+
"is_file": os.path.isfile(item_path),
|
|
329
|
+
"is_code_file": item.endswith(
|
|
330
|
+
(".py", ".js", ".ts", ".jsx", ".tsx")
|
|
331
|
+
),
|
|
332
|
+
}
|
|
333
|
+
)
|
|
334
|
+
except PermissionError:
|
|
335
|
+
result["error"] = "Permission denied"
|
|
336
|
+
|
|
337
|
+
return web.json_response(result)
|
|
338
|
+
|
|
339
|
+
async def _serve_version(self, request):
|
|
340
|
+
"""Serve version information."""
|
|
341
|
+
version_info = {
|
|
342
|
+
"version": "4.2.2",
|
|
343
|
+
"server": "stable",
|
|
344
|
+
"features": ["http", "socketio", "mock_ast"],
|
|
345
|
+
"status": "running",
|
|
346
|
+
}
|
|
347
|
+
return web.json_response(version_info)
|
|
348
|
+
|
|
349
|
+
def run(self):
|
|
350
|
+
"""Run the server."""
|
|
351
|
+
print("🔧 Setting up server...")
|
|
352
|
+
if not self.setup():
|
|
353
|
+
print("❌ Server setup failed")
|
|
354
|
+
return False
|
|
355
|
+
|
|
356
|
+
print(f"🚀 Starting stable dashboard server at http://{self.host}:{self.port}")
|
|
357
|
+
print("✅ Server ready: HTTP + SocketIO on same port")
|
|
358
|
+
print("📡 SocketIO events registered:")
|
|
359
|
+
print(" - connect/disconnect")
|
|
360
|
+
print(" - code_analyze_file (from 'code:analyze:file')")
|
|
361
|
+
print("🌐 HTTP endpoints available:")
|
|
362
|
+
print(" - GET / (dashboard)")
|
|
363
|
+
print(" - GET /static/* (static files)")
|
|
364
|
+
print(" - GET /api/directory/list (directory API)")
|
|
365
|
+
print(f"🔗 Open in browser: http://{self.host}:{self.port}")
|
|
366
|
+
|
|
367
|
+
try:
|
|
368
|
+
web.run_app(self.app, host=self.host, port=self.port, access_log=None)
|
|
369
|
+
except KeyboardInterrupt:
|
|
370
|
+
print("\n🛑 Server stopped by user")
|
|
371
|
+
except Exception as e:
|
|
372
|
+
print(f"❌ Server error: {e}")
|
|
373
|
+
if self.debug:
|
|
374
|
+
import traceback
|
|
375
|
+
|
|
376
|
+
traceback.print_exc()
|
|
377
|
+
return False
|
|
378
|
+
|
|
379
|
+
return True
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
def create_stable_server(
|
|
383
|
+
dashboard_path: Optional[Path] = None, **kwargs
|
|
384
|
+
) -> StableDashboardServer:
|
|
385
|
+
"""Create a stable dashboard server instance."""
|
|
386
|
+
server = StableDashboardServer(**kwargs)
|
|
387
|
+
if dashboard_path:
|
|
388
|
+
server.dashboard_path = dashboard_path
|
|
389
|
+
return server
|
|
@@ -45,6 +45,22 @@ class SocketIOClientProxy:
|
|
|
45
45
|
self._client_thread = None
|
|
46
46
|
self._client_loop = None
|
|
47
47
|
|
|
48
|
+
def start(self):
|
|
49
|
+
"""Start the Socket.IO client connection (compatibility wrapper).
|
|
50
|
+
|
|
51
|
+
This method exists for backward compatibility with code that expects
|
|
52
|
+
a start() method. It simply calls start_sync().
|
|
53
|
+
"""
|
|
54
|
+
return self.start_sync()
|
|
55
|
+
|
|
56
|
+
def stop(self):
|
|
57
|
+
"""Stop the Socket.IO client connection (compatibility wrapper).
|
|
58
|
+
|
|
59
|
+
This method exists for backward compatibility with code that expects
|
|
60
|
+
a stop() method. It simply calls stop_sync().
|
|
61
|
+
"""
|
|
62
|
+
return self.stop_sync()
|
|
63
|
+
|
|
48
64
|
def start_sync(self):
|
|
49
65
|
"""Start the Socket.IO client connection to the persistent server."""
|
|
50
66
|
self.logger.debug(
|
|
@@ -591,8 +591,11 @@ class CodeAnalysisEventHandler(BaseEventHandler):
|
|
|
591
591
|
return
|
|
592
592
|
|
|
593
593
|
try:
|
|
594
|
+
self.logger.info(f"Starting file analysis for: {path}")
|
|
595
|
+
|
|
594
596
|
# Ensure analyzer exists
|
|
595
597
|
if not self.code_analyzer:
|
|
598
|
+
self.logger.info("Creating new CodeTreeAnalyzer instance")
|
|
596
599
|
emitter = CodeTreeEventEmitter(use_stdout=False)
|
|
597
600
|
# Override emit method to send to Socket.IO
|
|
598
601
|
original_emit = emitter.emit
|
|
@@ -630,23 +633,42 @@ class CodeAnalysisEventHandler(BaseEventHandler):
|
|
|
630
633
|
self.code_analyzer = CodeTreeAnalyzer(
|
|
631
634
|
emit_events=False, emitter=emitter
|
|
632
635
|
)
|
|
636
|
+
self.logger.info("CodeTreeAnalyzer created successfully")
|
|
633
637
|
|
|
634
638
|
# Analyze file
|
|
639
|
+
self.logger.info(f"Calling analyze_file for: {path}")
|
|
635
640
|
result = self.code_analyzer.analyze_file(path)
|
|
641
|
+
self.logger.info(
|
|
642
|
+
f"Analysis complete. Result keys: {list(result.keys()) if result else 'None'}"
|
|
643
|
+
)
|
|
644
|
+
|
|
645
|
+
if result:
|
|
646
|
+
self.logger.info(
|
|
647
|
+
f"Analysis result: elements={len(result.get('elements', []))}, nodes={len(result.get('nodes', []))}"
|
|
648
|
+
)
|
|
649
|
+
else:
|
|
650
|
+
self.logger.warning("Analysis returned None or empty result")
|
|
636
651
|
|
|
637
652
|
# Send result with correct event name (using colons, not dots!)
|
|
653
|
+
response_data = {
|
|
654
|
+
"request_id": request_id,
|
|
655
|
+
"path": path,
|
|
656
|
+
**result,
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
self.logger.info(f"Emitting code:file:analyzed event to {sid}")
|
|
638
660
|
await self.server.core.sio.emit(
|
|
639
661
|
"code:file:analyzed",
|
|
640
|
-
|
|
641
|
-
"request_id": request_id,
|
|
642
|
-
"path": path,
|
|
643
|
-
**result,
|
|
644
|
-
},
|
|
662
|
+
response_data,
|
|
645
663
|
room=sid,
|
|
646
664
|
)
|
|
665
|
+
self.logger.info("Event emitted successfully")
|
|
647
666
|
|
|
648
667
|
except Exception as e:
|
|
649
668
|
self.logger.error(f"Error analyzing file {path}: {e}")
|
|
669
|
+
import traceback
|
|
670
|
+
|
|
671
|
+
self.logger.error(f"Full traceback: {traceback.format_exc()}")
|
|
650
672
|
await self.server.core.sio.emit(
|
|
651
673
|
"code:analysis:error",
|
|
652
674
|
{
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
Lightweight MonitorServer for claude-mpm.
|
|
3
3
|
|
|
4
4
|
WHY: This module provides a minimal, independent monitoring service that:
|
|
5
|
-
- Runs as a stable background service on port
|
|
5
|
+
- Runs as a stable background service on port 8765
|
|
6
6
|
- Only handles event collection and relay (no UI components)
|
|
7
7
|
- Has minimal dependencies and resource usage
|
|
8
8
|
- Can run as always-on background service
|
|
@@ -60,7 +60,7 @@ class MonitorServer(SocketIOServiceInterface):
|
|
|
60
60
|
monitor_config = config.get("monitor_server", {})
|
|
61
61
|
|
|
62
62
|
self.host = host or monitor_config.get("host", "localhost")
|
|
63
|
-
self.port = port or monitor_config.get("port",
|
|
63
|
+
self.port = port or monitor_config.get("port", 8765)
|
|
64
64
|
self.logger = get_logger(__name__ + ".MonitorServer")
|
|
65
65
|
|
|
66
66
|
# Configuration-based settings
|