claude-mpm 4.2.9__py3-none-any.whl → 4.2.12__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- claude_mpm/VERSION +1 -1
- claude_mpm/cli/commands/dashboard.py +59 -126
- claude_mpm/cli/commands/monitor.py +82 -212
- claude_mpm/cli/commands/run.py +33 -33
- claude_mpm/cli/parsers/monitor_parser.py +12 -2
- claude_mpm/dashboard/static/css/code-tree.css +8 -16
- claude_mpm/dashboard/static/dist/components/code-tree.js +1 -1
- claude_mpm/dashboard/static/dist/components/file-viewer.js +2 -0
- claude_mpm/dashboard/static/dist/components/module-viewer.js +1 -1
- claude_mpm/dashboard/static/dist/components/unified-data-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/code-tree.js +692 -114
- claude_mpm/dashboard/static/js/components/file-viewer.js +538 -0
- claude_mpm/dashboard/static/js/components/module-viewer.js +26 -0
- claude_mpm/dashboard/static/js/components/unified-data-viewer.js +166 -14
- claude_mpm/dashboard/static/js/dashboard.js +108 -91
- claude_mpm/dashboard/static/js/socket-client.js +9 -7
- claude_mpm/dashboard/templates/index.html +2 -7
- claude_mpm/hooks/claude_hooks/hook_handler.py +1 -11
- claude_mpm/hooks/claude_hooks/services/connection_manager.py +54 -59
- claude_mpm/hooks/claude_hooks/services/connection_manager_http.py +112 -72
- claude_mpm/services/agents/deployment/agent_template_builder.py +0 -1
- claude_mpm/services/cli/unified_dashboard_manager.py +354 -0
- claude_mpm/services/monitor/__init__.py +20 -0
- claude_mpm/services/monitor/daemon.py +378 -0
- claude_mpm/services/monitor/event_emitter.py +342 -0
- claude_mpm/services/monitor/handlers/__init__.py +20 -0
- claude_mpm/services/monitor/handlers/code_analysis.py +334 -0
- claude_mpm/services/monitor/handlers/dashboard.py +298 -0
- claude_mpm/services/monitor/handlers/hooks.py +491 -0
- claude_mpm/services/monitor/management/__init__.py +18 -0
- claude_mpm/services/monitor/management/health.py +124 -0
- claude_mpm/services/monitor/management/lifecycle.py +338 -0
- claude_mpm/services/monitor/server.py +596 -0
- claude_mpm/tools/code_tree_analyzer.py +33 -17
- {claude_mpm-4.2.9.dist-info → claude_mpm-4.2.12.dist-info}/METADATA +1 -1
- {claude_mpm-4.2.9.dist-info → claude_mpm-4.2.12.dist-info}/RECORD +42 -37
- claude_mpm/cli/commands/socketio_monitor.py +0 -233
- claude_mpm/scripts/socketio_daemon.py +0 -571
- claude_mpm/scripts/socketio_daemon_hardened.py +0 -937
- claude_mpm/scripts/socketio_daemon_wrapper.py +0 -78
- claude_mpm/scripts/socketio_server_manager.py +0 -349
- claude_mpm/services/cli/dashboard_launcher.py +0 -423
- claude_mpm/services/cli/socketio_manager.py +0 -595
- claude_mpm/services/dashboard/stable_server.py +0 -1020
- claude_mpm/services/socketio/monitor_server.py +0 -505
- {claude_mpm-4.2.9.dist-info → claude_mpm-4.2.12.dist-info}/WHEEL +0 -0
- {claude_mpm-4.2.9.dist-info → claude_mpm-4.2.12.dist-info}/entry_points.txt +0 -0
- {claude_mpm-4.2.9.dist-info → claude_mpm-4.2.12.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-4.2.9.dist-info → claude_mpm-4.2.12.dist-info}/top_level.txt +0 -0
|
@@ -1,571 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""
|
|
3
|
-
Socket.IO Daemon Management for Claude MPM.
|
|
4
|
-
|
|
5
|
-
This module provides pure Python daemon management for the Claude MPM Socket.IO server
|
|
6
|
-
without requiring external process management dependencies. It handles server lifecycle,
|
|
7
|
-
process detection, and virtual environment discovery.
|
|
8
|
-
|
|
9
|
-
Key Features:
|
|
10
|
-
- Pure Python implementation (no external deps)
|
|
11
|
-
- Virtual environment auto-detection
|
|
12
|
-
- Process management with PID tracking
|
|
13
|
-
- Signal handling for clean shutdown
|
|
14
|
-
- Port availability checking
|
|
15
|
-
- Background daemon execution
|
|
16
|
-
|
|
17
|
-
Architecture:
|
|
18
|
-
- Uses subprocess for server execution
|
|
19
|
-
- Implements daemon pattern with double-fork
|
|
20
|
-
- Maintains PID files for process tracking
|
|
21
|
-
- Auto-detects Python environment (venv/conda)
|
|
22
|
-
|
|
23
|
-
Thread Safety:
|
|
24
|
-
- Signal handlers are async-signal-safe
|
|
25
|
-
- PID file operations use atomic writes
|
|
26
|
-
- Process checks use system-level primitives
|
|
27
|
-
|
|
28
|
-
Performance Considerations:
|
|
29
|
-
- Minimal memory footprint for daemon mode
|
|
30
|
-
- Fast process detection using PID files
|
|
31
|
-
- Lazy loading of heavy imports
|
|
32
|
-
- Efficient port scanning
|
|
33
|
-
|
|
34
|
-
Security:
|
|
35
|
-
- Localhost-only binding for server
|
|
36
|
-
- PID file permissions restrict access
|
|
37
|
-
- Process ownership validation
|
|
38
|
-
- Signal handling prevents orphans
|
|
39
|
-
|
|
40
|
-
@author Claude MPM Team
|
|
41
|
-
@version 1.0
|
|
42
|
-
@since v4.0.25
|
|
43
|
-
"""
|
|
44
|
-
|
|
45
|
-
import os
|
|
46
|
-
import signal
|
|
47
|
-
import subprocess
|
|
48
|
-
import sys
|
|
49
|
-
import time
|
|
50
|
-
from pathlib import Path
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
# Detect and use virtual environment Python if available
|
|
54
|
-
def get_python_executable() -> str:
|
|
55
|
-
"""
|
|
56
|
-
Detect and return the appropriate Python executable for Socket.IO daemon.
|
|
57
|
-
|
|
58
|
-
Intelligently detects virtual environments (venv, conda, poetry, pipenv)
|
|
59
|
-
and returns the correct Python path to ensure dependency availability.
|
|
60
|
-
|
|
61
|
-
Detection Strategy:
|
|
62
|
-
1. Check if already running in virtual environment
|
|
63
|
-
2. Look for VIRTUAL_ENV environment variable
|
|
64
|
-
3. Analyze executable path structure
|
|
65
|
-
4. Search for project-specific virtual environments
|
|
66
|
-
5. Fall back to system Python
|
|
67
|
-
|
|
68
|
-
WHY this complex detection:
|
|
69
|
-
- Socket.IO server requires specific Python packages (socketio, eventlet)
|
|
70
|
-
- System Python rarely has these packages installed
|
|
71
|
-
- Virtual environments contain isolated dependencies
|
|
72
|
-
- Multiple venv tools have different conventions
|
|
73
|
-
|
|
74
|
-
Thread Safety:
|
|
75
|
-
- Read-only operations on sys and os modules
|
|
76
|
-
- File system checks are atomic
|
|
77
|
-
- No shared state modification
|
|
78
|
-
|
|
79
|
-
Performance:
|
|
80
|
-
- Early returns for common cases
|
|
81
|
-
- Minimal file system operations
|
|
82
|
-
- Cached in practice by Python import system
|
|
83
|
-
|
|
84
|
-
Returns:
|
|
85
|
-
str: Path to Python executable with required dependencies
|
|
86
|
-
|
|
87
|
-
Raises:
|
|
88
|
-
FileNotFoundError: If no suitable Python executable found
|
|
89
|
-
|
|
90
|
-
Examples:
|
|
91
|
-
>>> python_path = get_python_executable()
|
|
92
|
-
>>> # Returns: '/path/to/venv/bin/python' or '/usr/bin/python3'
|
|
93
|
-
"""
|
|
94
|
-
# First, check if we're already in a virtual environment
|
|
95
|
-
if hasattr(sys, "real_prefix") or (
|
|
96
|
-
hasattr(sys, "base_prefix") and sys.base_prefix != sys.prefix
|
|
97
|
-
):
|
|
98
|
-
# We're in a virtual environment, use its Python
|
|
99
|
-
return sys.executable
|
|
100
|
-
|
|
101
|
-
# Check for common virtual environment indicators
|
|
102
|
-
# 1. VIRTUAL_ENV environment variable (most common)
|
|
103
|
-
venv_path = os.environ.get("VIRTUAL_ENV")
|
|
104
|
-
if venv_path:
|
|
105
|
-
venv_python = Path(venv_path) / "bin" / "python"
|
|
106
|
-
if venv_python.exists():
|
|
107
|
-
return str(venv_python)
|
|
108
|
-
|
|
109
|
-
# 2. Check if current executable is in a venv directory structure
|
|
110
|
-
exe_path = Path(sys.executable).resolve()
|
|
111
|
-
for parent in exe_path.parents:
|
|
112
|
-
# Check for common venv directory names
|
|
113
|
-
if parent.name in ("venv", ".venv", "env", ".env"):
|
|
114
|
-
# This looks like a virtual environment
|
|
115
|
-
return sys.executable
|
|
116
|
-
|
|
117
|
-
# Check for typical venv structure (bin/python or Scripts/python.exe)
|
|
118
|
-
if parent.name == "bin" and (parent.parent / "pyvenv.cfg").exists():
|
|
119
|
-
return sys.executable
|
|
120
|
-
if parent.name == "Scripts" and (parent.parent / "pyvenv.cfg").exists():
|
|
121
|
-
return sys.executable
|
|
122
|
-
|
|
123
|
-
# 3. Try to detect project-specific venv
|
|
124
|
-
# Look for venv in the project root (going up from script location)
|
|
125
|
-
script_path = Path(__file__).resolve()
|
|
126
|
-
for parent in script_path.parents:
|
|
127
|
-
# Stop at src or when we've gone too far up
|
|
128
|
-
if parent.name == "src" or not (parent / "src").exists():
|
|
129
|
-
# Check for venv directories
|
|
130
|
-
for venv_name in ("venv", ".venv", "env", ".env"):
|
|
131
|
-
venv_dir = parent / venv_name
|
|
132
|
-
if venv_dir.exists():
|
|
133
|
-
venv_python = venv_dir / "bin" / "python"
|
|
134
|
-
if venv_python.exists():
|
|
135
|
-
return str(venv_python)
|
|
136
|
-
break
|
|
137
|
-
|
|
138
|
-
# Fall back to current Python executable
|
|
139
|
-
return sys.executable
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
# Store the detected Python executable for daemon usage
|
|
143
|
-
PYTHON_EXECUTABLE = get_python_executable()
|
|
144
|
-
|
|
145
|
-
import psutil
|
|
146
|
-
|
|
147
|
-
# Handle imports for both development and installed scenarios
|
|
148
|
-
script_dir = Path(__file__).parent
|
|
149
|
-
try:
|
|
150
|
-
# When installed as package, this should work directly
|
|
151
|
-
from claude_mpm.services.port_manager import PortManager
|
|
152
|
-
from claude_mpm.services.socketio.server.main import SocketIOServer
|
|
153
|
-
except ImportError:
|
|
154
|
-
# Need to add the appropriate directory to sys.path
|
|
155
|
-
import sys
|
|
156
|
-
|
|
157
|
-
# Get the absolute path of this script
|
|
158
|
-
script_path = Path(__file__).resolve()
|
|
159
|
-
|
|
160
|
-
# Determine if we're in development or installed environment
|
|
161
|
-
if "site-packages" in str(script_path):
|
|
162
|
-
# Installed environment: ~/.local/pipx/venvs/claude-mpm/lib/python3.13/site-packages/claude_mpm/scripts/socketio_daemon.py
|
|
163
|
-
# Need to add site-packages directory to path
|
|
164
|
-
parts = script_path.parts
|
|
165
|
-
site_packages_idx = next(
|
|
166
|
-
i for i, part in enumerate(parts) if part == "site-packages"
|
|
167
|
-
)
|
|
168
|
-
site_packages_path = Path(*parts[: site_packages_idx + 1])
|
|
169
|
-
|
|
170
|
-
if site_packages_path.exists() and str(site_packages_path) not in sys.path:
|
|
171
|
-
sys.path.insert(0, str(site_packages_path))
|
|
172
|
-
else:
|
|
173
|
-
# Development environment: Project/src/claude_mpm/scripts/socketio_daemon.py
|
|
174
|
-
# Need to add src directory to path
|
|
175
|
-
# Go up: scripts -> claude_mpm -> src
|
|
176
|
-
src_path = script_path.parent.parent.parent
|
|
177
|
-
|
|
178
|
-
if (
|
|
179
|
-
src_path.exists()
|
|
180
|
-
and (src_path / "claude_mpm").exists()
|
|
181
|
-
and str(src_path) not in sys.path
|
|
182
|
-
):
|
|
183
|
-
sys.path.insert(0, str(src_path))
|
|
184
|
-
|
|
185
|
-
# Try importing again after path modification
|
|
186
|
-
try:
|
|
187
|
-
from claude_mpm.services.port_manager import PortManager
|
|
188
|
-
from claude_mpm.services.socketio.server.main import SocketIOServer
|
|
189
|
-
except ImportError as e:
|
|
190
|
-
print(f"❌ Failed to import SocketIOServer after path adjustment: {e}")
|
|
191
|
-
print(f"📍 Script path: {script_path}")
|
|
192
|
-
print(f"🐍 Python path entries: {len(sys.path)}")
|
|
193
|
-
for i, path in enumerate(sys.path):
|
|
194
|
-
print(f" [{i}] {path}")
|
|
195
|
-
|
|
196
|
-
# Check if claude_mpm directory exists in any path
|
|
197
|
-
claude_mpm_found = False
|
|
198
|
-
for path_str in sys.path:
|
|
199
|
-
claude_mpm_path = Path(path_str) / "claude_mpm"
|
|
200
|
-
if claude_mpm_path.exists():
|
|
201
|
-
print(f"✅ Found claude_mpm at: {claude_mpm_path}")
|
|
202
|
-
claude_mpm_found = True
|
|
203
|
-
|
|
204
|
-
if not claude_mpm_found:
|
|
205
|
-
print("❌ claude_mpm directory not found in any sys.path entry")
|
|
206
|
-
|
|
207
|
-
print("\n💡 Troubleshooting tips:")
|
|
208
|
-
print(" 1. Ensure claude-mpm is properly installed: pip install claude-mpm")
|
|
209
|
-
print(" 2. If in development, ensure you're in the project root directory")
|
|
210
|
-
print(" 3. Check that PYTHONPATH includes the package location")
|
|
211
|
-
sys.exit(1)
|
|
212
|
-
|
|
213
|
-
# Use deployment root for daemon files to keep everything centralized
|
|
214
|
-
from claude_mpm.core.unified_paths import get_project_root
|
|
215
|
-
|
|
216
|
-
deployment_root = get_project_root()
|
|
217
|
-
PID_FILE = deployment_root / ".claude-mpm" / "socketio-server.pid"
|
|
218
|
-
LOG_FILE = deployment_root / ".claude-mpm" / "socketio-server.log"
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
def ensure_dirs():
|
|
222
|
-
"""Ensure required directories exist."""
|
|
223
|
-
PID_FILE.parent.mkdir(parents=True, exist_ok=True)
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
def is_running():
|
|
227
|
-
"""Check if server is already running."""
|
|
228
|
-
if not PID_FILE.exists():
|
|
229
|
-
return False
|
|
230
|
-
|
|
231
|
-
try:
|
|
232
|
-
with open(PID_FILE) as f:
|
|
233
|
-
pid = int(f.read().strip())
|
|
234
|
-
|
|
235
|
-
# Check if process exists
|
|
236
|
-
process = psutil.Process(pid)
|
|
237
|
-
return process.is_running()
|
|
238
|
-
except (ValueError, psutil.NoSuchProcess, psutil.AccessDenied):
|
|
239
|
-
# Clean up stale PID file
|
|
240
|
-
PID_FILE.unlink(missing_ok=True)
|
|
241
|
-
return False
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
def start_server():
|
|
245
|
-
"""Start the Socket.IO server as a daemon with dynamic port selection."""
|
|
246
|
-
# Initialize port manager
|
|
247
|
-
port_manager = PortManager()
|
|
248
|
-
|
|
249
|
-
# Clean up any dead instances first
|
|
250
|
-
port_manager.cleanup_dead_instances()
|
|
251
|
-
|
|
252
|
-
# Check if we already have a running instance
|
|
253
|
-
if is_running():
|
|
254
|
-
print("Socket.IO daemon server is already running.")
|
|
255
|
-
print(f"Use '{__file__} status' for details")
|
|
256
|
-
return
|
|
257
|
-
|
|
258
|
-
# Find an available port
|
|
259
|
-
selected_port = port_manager.find_available_port()
|
|
260
|
-
if not selected_port:
|
|
261
|
-
print("❌ No available ports in range 8765-8785")
|
|
262
|
-
print(" All ports are either in use or blocked")
|
|
263
|
-
return
|
|
264
|
-
|
|
265
|
-
print(f"🔍 Selected port: {selected_port}")
|
|
266
|
-
|
|
267
|
-
# Check for existing instances on this port
|
|
268
|
-
existing_instance = port_manager.get_instance_by_port(selected_port)
|
|
269
|
-
if existing_instance:
|
|
270
|
-
print(f"⚠️ Port {selected_port} is already used by claude-mpm instance:")
|
|
271
|
-
print(f" PID: {existing_instance.get('pid')}")
|
|
272
|
-
print(f" Started: {time.ctime(existing_instance.get('start_time', 0))}")
|
|
273
|
-
return
|
|
274
|
-
|
|
275
|
-
ensure_dirs()
|
|
276
|
-
|
|
277
|
-
# Fork to create daemon using the correct Python environment
|
|
278
|
-
pid = os.fork()
|
|
279
|
-
if pid > 0:
|
|
280
|
-
# Parent process
|
|
281
|
-
print(f"Starting Socket.IO server on port {selected_port} (PID: {pid})...")
|
|
282
|
-
print(f"Using Python: {PYTHON_EXECUTABLE}")
|
|
283
|
-
|
|
284
|
-
# Register the instance
|
|
285
|
-
instance_id = port_manager.register_instance(selected_port, pid)
|
|
286
|
-
|
|
287
|
-
# Save PID and port info
|
|
288
|
-
with open(PID_FILE, "w") as f:
|
|
289
|
-
f.write(str(pid))
|
|
290
|
-
|
|
291
|
-
# Save port info for other tools
|
|
292
|
-
port_file = PID_FILE.parent / "socketio-port"
|
|
293
|
-
with open(port_file, "w") as f:
|
|
294
|
-
f.write(str(selected_port))
|
|
295
|
-
|
|
296
|
-
print("Socket.IO server started successfully.")
|
|
297
|
-
print(f"Port: {selected_port}")
|
|
298
|
-
print(f"Instance ID: {instance_id}")
|
|
299
|
-
print(f"PID file: {PID_FILE}")
|
|
300
|
-
print(f"Log file: {LOG_FILE}")
|
|
301
|
-
sys.exit(0)
|
|
302
|
-
|
|
303
|
-
# Child process - become daemon
|
|
304
|
-
os.setsid()
|
|
305
|
-
os.umask(0)
|
|
306
|
-
|
|
307
|
-
# Redirect stdout/stderr to log file
|
|
308
|
-
with open(LOG_FILE, "a") as log:
|
|
309
|
-
os.dup2(log.fileno(), sys.stdout.fileno())
|
|
310
|
-
os.dup2(log.fileno(), sys.stderr.fileno())
|
|
311
|
-
|
|
312
|
-
# Log environment information for debugging
|
|
313
|
-
print(
|
|
314
|
-
f"[{time.strftime('%Y-%m-%d %H:%M:%S')}] Starting Socket.IO server on port {selected_port}..."
|
|
315
|
-
)
|
|
316
|
-
print(f"[{time.strftime('%Y-%m-%d %H:%M:%S')}] Python executable: {sys.executable}")
|
|
317
|
-
print(f"[{time.strftime('%Y-%m-%d %H:%M:%S')}] Python version: {sys.version}")
|
|
318
|
-
print(
|
|
319
|
-
f"[{time.strftime('%Y-%m-%d %H:%M:%S')}] Python path: {sys.path[:3]}..."
|
|
320
|
-
) # Show first 3 entries
|
|
321
|
-
server = SocketIOServer(host="localhost", port=selected_port)
|
|
322
|
-
|
|
323
|
-
# Handle signals
|
|
324
|
-
def signal_handler(signum, frame):
|
|
325
|
-
print(
|
|
326
|
-
f"[{time.strftime('%Y-%m-%d %H:%M:%S')}] Received signal {signum}, shutting down..."
|
|
327
|
-
)
|
|
328
|
-
server.stop_sync()
|
|
329
|
-
|
|
330
|
-
# Clean up instance registration
|
|
331
|
-
port_manager_cleanup = PortManager()
|
|
332
|
-
instances = port_manager_cleanup.load_instances()
|
|
333
|
-
for instance_id, instance_info in instances.items():
|
|
334
|
-
if instance_info.get("pid") == os.getpid():
|
|
335
|
-
port_manager_cleanup.remove_instance(instance_id)
|
|
336
|
-
break
|
|
337
|
-
|
|
338
|
-
PID_FILE.unlink(missing_ok=True)
|
|
339
|
-
sys.exit(0)
|
|
340
|
-
|
|
341
|
-
signal.signal(signal.SIGTERM, signal_handler)
|
|
342
|
-
signal.signal(signal.SIGINT, signal_handler)
|
|
343
|
-
|
|
344
|
-
# Start server using synchronous method
|
|
345
|
-
server.start_sync()
|
|
346
|
-
|
|
347
|
-
# Debug: Check if handlers are registered (write to file for daemon)
|
|
348
|
-
with open(LOG_FILE, "a") as f:
|
|
349
|
-
f.write(
|
|
350
|
-
f"\n[{time.strftime('%Y-%m-%d %H:%M:%S')}] Server started, checking handlers...\n"
|
|
351
|
-
)
|
|
352
|
-
if server.event_registry:
|
|
353
|
-
f.write(
|
|
354
|
-
f"[{time.strftime('%Y-%m-%d %H:%M:%S')}] Event registry exists with {len(server.event_registry.handlers)} handlers\n"
|
|
355
|
-
)
|
|
356
|
-
for handler in server.event_registry.handlers:
|
|
357
|
-
f.write(
|
|
358
|
-
f"[{time.strftime('%Y-%m-%d %H:%M:%S')}] - {handler.__class__.__name__}\n"
|
|
359
|
-
)
|
|
360
|
-
else:
|
|
361
|
-
f.write(
|
|
362
|
-
f"[{time.strftime('%Y-%m-%d %H:%M:%S')}] WARNING: No event registry found!\n"
|
|
363
|
-
)
|
|
364
|
-
|
|
365
|
-
# Check Socket.IO events
|
|
366
|
-
if server.core and server.core.sio:
|
|
367
|
-
handlers = getattr(server.core.sio, "handlers", {})
|
|
368
|
-
f.write(
|
|
369
|
-
f"[{time.strftime('%Y-%m-%d %H:%M:%S')}] Socket.IO has {len(handlers)} namespaces\n"
|
|
370
|
-
)
|
|
371
|
-
for namespace, events in handlers.items():
|
|
372
|
-
f.write(
|
|
373
|
-
f"[{time.strftime('%Y-%m-%d %H:%M:%S')}] Namespace '{namespace}': {len(events)} events\n"
|
|
374
|
-
)
|
|
375
|
-
# List all events to debug
|
|
376
|
-
event_list = list(events.keys())
|
|
377
|
-
f.write(
|
|
378
|
-
f"[{time.strftime('%Y-%m-%d %H:%M:%S')}] Events: {event_list}\n"
|
|
379
|
-
)
|
|
380
|
-
if "code:analyze:request" in events:
|
|
381
|
-
f.write(
|
|
382
|
-
f"[{time.strftime('%Y-%m-%d %H:%M:%S')}] ✅ code:analyze:request is registered!\n"
|
|
383
|
-
)
|
|
384
|
-
else:
|
|
385
|
-
f.write(
|
|
386
|
-
f"[{time.strftime('%Y-%m-%d %H:%M:%S')}] ❌ code:analyze:request NOT found\n"
|
|
387
|
-
)
|
|
388
|
-
f.flush()
|
|
389
|
-
|
|
390
|
-
# Keep running
|
|
391
|
-
try:
|
|
392
|
-
while True:
|
|
393
|
-
time.sleep(1)
|
|
394
|
-
except KeyboardInterrupt:
|
|
395
|
-
signal_handler(signal.SIGINT, None)
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
def stop_server():
|
|
399
|
-
"""Stop the Socket.IO daemon server."""
|
|
400
|
-
if not is_running():
|
|
401
|
-
print("Socket.IO daemon server is not running.")
|
|
402
|
-
print("Check for other servers: socketio_server_manager.py status")
|
|
403
|
-
return
|
|
404
|
-
|
|
405
|
-
try:
|
|
406
|
-
with open(PID_FILE) as f:
|
|
407
|
-
pid = int(f.read().strip())
|
|
408
|
-
|
|
409
|
-
print(f"Stopping Socket.IO server (PID: {pid})...")
|
|
410
|
-
os.kill(pid, signal.SIGTERM)
|
|
411
|
-
|
|
412
|
-
# Wait for process to stop
|
|
413
|
-
for _ in range(10):
|
|
414
|
-
if not is_running():
|
|
415
|
-
print("Socket.IO server stopped successfully.")
|
|
416
|
-
PID_FILE.unlink(missing_ok=True)
|
|
417
|
-
return
|
|
418
|
-
time.sleep(0.5)
|
|
419
|
-
|
|
420
|
-
# Force kill if still running
|
|
421
|
-
print("Server didn't stop gracefully, forcing...")
|
|
422
|
-
os.kill(pid, signal.SIGKILL)
|
|
423
|
-
PID_FILE.unlink(missing_ok=True)
|
|
424
|
-
|
|
425
|
-
except Exception as e:
|
|
426
|
-
print(f"Error stopping server: {e}")
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
def status_server():
|
|
430
|
-
"""Check server status with port manager integration."""
|
|
431
|
-
port_manager = PortManager()
|
|
432
|
-
|
|
433
|
-
# Clean up dead instances first
|
|
434
|
-
port_manager.cleanup_dead_instances()
|
|
435
|
-
|
|
436
|
-
if is_running():
|
|
437
|
-
with open(PID_FILE) as f:
|
|
438
|
-
pid = int(f.read().strip())
|
|
439
|
-
print(f"Socket.IO daemon server is running (PID: {pid})")
|
|
440
|
-
print(f"PID file: {PID_FILE}")
|
|
441
|
-
|
|
442
|
-
# Get port information
|
|
443
|
-
port_file = PID_FILE.parent / "socketio-port"
|
|
444
|
-
current_port = 8765 # default
|
|
445
|
-
if port_file.exists():
|
|
446
|
-
try:
|
|
447
|
-
with open(port_file) as f:
|
|
448
|
-
current_port = int(f.read().strip())
|
|
449
|
-
except:
|
|
450
|
-
pass
|
|
451
|
-
|
|
452
|
-
# Check if port is listening
|
|
453
|
-
try:
|
|
454
|
-
import socket
|
|
455
|
-
|
|
456
|
-
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
457
|
-
result = sock.connect_ex(("localhost", current_port))
|
|
458
|
-
sock.close()
|
|
459
|
-
if result == 0:
|
|
460
|
-
print(f"✅ Server is listening on port {current_port}")
|
|
461
|
-
print("🔧 Management style: daemon")
|
|
462
|
-
else:
|
|
463
|
-
print(
|
|
464
|
-
f"⚠️ WARNING: Server process exists but port {current_port} is not accessible"
|
|
465
|
-
)
|
|
466
|
-
except:
|
|
467
|
-
pass
|
|
468
|
-
|
|
469
|
-
# Show instance information
|
|
470
|
-
instance = port_manager.get_instance_by_port(current_port)
|
|
471
|
-
if instance:
|
|
472
|
-
print("\n📊 Instance Information:")
|
|
473
|
-
print(f" • Port: {instance.get('port')}")
|
|
474
|
-
print(f" • Started: {time.ctime(instance.get('start_time', 0))}")
|
|
475
|
-
print(f" • Instance ID: {instance.get('instance_id')}")
|
|
476
|
-
|
|
477
|
-
# Show management commands
|
|
478
|
-
print("\n🔧 Management Commands:")
|
|
479
|
-
print(f" • Stop: {__file__} stop")
|
|
480
|
-
print(f" • Restart: {__file__} restart")
|
|
481
|
-
print(f" • List all: {__file__} list")
|
|
482
|
-
|
|
483
|
-
# Check for manager conflicts
|
|
484
|
-
try:
|
|
485
|
-
import requests
|
|
486
|
-
|
|
487
|
-
response = requests.get("http://localhost:8765/health", timeout=1.0)
|
|
488
|
-
if response.status_code == 200:
|
|
489
|
-
data = response.json()
|
|
490
|
-
if "server_id" in data and data.get("server_id") != "daemon-socketio":
|
|
491
|
-
print("\n⚠️ POTENTIAL CONFLICT: HTTP-managed server also detected")
|
|
492
|
-
print(f" Server ID: {data.get('server_id')}")
|
|
493
|
-
print(" Use 'socketio_server_manager.py diagnose' to resolve")
|
|
494
|
-
except:
|
|
495
|
-
pass
|
|
496
|
-
|
|
497
|
-
else:
|
|
498
|
-
print("Socket.IO daemon server is not running")
|
|
499
|
-
print("\n🔧 Start Commands:")
|
|
500
|
-
print(f" • Daemon: {__file__} start")
|
|
501
|
-
print(" • HTTP-managed: socketio_server_manager.py start")
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
def list_instances():
|
|
505
|
-
"""List all active SocketIO instances."""
|
|
506
|
-
port_manager = PortManager()
|
|
507
|
-
|
|
508
|
-
# Clean up dead instances first
|
|
509
|
-
cleaned = port_manager.cleanup_dead_instances()
|
|
510
|
-
if cleaned > 0:
|
|
511
|
-
print(f"🧹 Cleaned up {cleaned} dead instances")
|
|
512
|
-
|
|
513
|
-
instances = port_manager.list_active_instances()
|
|
514
|
-
|
|
515
|
-
if not instances:
|
|
516
|
-
print("No active SocketIO instances found.")
|
|
517
|
-
return
|
|
518
|
-
|
|
519
|
-
print(f"📊 Active SocketIO Instances ({len(instances)}):")
|
|
520
|
-
print()
|
|
521
|
-
|
|
522
|
-
for instance in instances:
|
|
523
|
-
port = instance.get("port")
|
|
524
|
-
pid = instance.get("pid")
|
|
525
|
-
start_time = instance.get("start_time", 0)
|
|
526
|
-
instance_id = instance.get("instance_id", "unknown")
|
|
527
|
-
|
|
528
|
-
print(f"🔌 Port {port}:")
|
|
529
|
-
print(f" • PID: {pid}")
|
|
530
|
-
print(f" • Started: {time.ctime(start_time)}")
|
|
531
|
-
print(f" • Instance ID: {instance_id}")
|
|
532
|
-
print(f" • Project: {instance.get('project_root', 'unknown')}")
|
|
533
|
-
print()
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
def main():
|
|
537
|
-
"""Main entry point."""
|
|
538
|
-
if len(sys.argv) < 2:
|
|
539
|
-
print("Usage: socketio-daemon.py {start|stop|restart|status|list}")
|
|
540
|
-
sys.exit(1)
|
|
541
|
-
|
|
542
|
-
command = sys.argv[1]
|
|
543
|
-
|
|
544
|
-
if command == "start":
|
|
545
|
-
start_server()
|
|
546
|
-
elif command == "stop":
|
|
547
|
-
stop_server()
|
|
548
|
-
elif command == "restart":
|
|
549
|
-
stop_server()
|
|
550
|
-
time.sleep(1)
|
|
551
|
-
start_server()
|
|
552
|
-
elif command == "status":
|
|
553
|
-
status_server()
|
|
554
|
-
elif command == "list":
|
|
555
|
-
list_instances()
|
|
556
|
-
else:
|
|
557
|
-
print(f"Unknown command: {command}")
|
|
558
|
-
print("Usage: socketio-daemon.py {start|stop|restart|status|list}")
|
|
559
|
-
sys.exit(1)
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
if __name__ == "__main__":
|
|
563
|
-
# Install psutil if not available (using correct Python)
|
|
564
|
-
try:
|
|
565
|
-
import psutil
|
|
566
|
-
except ImportError:
|
|
567
|
-
print(f"Installing psutil using {PYTHON_EXECUTABLE}...")
|
|
568
|
-
subprocess.check_call([PYTHON_EXECUTABLE, "-m", "pip", "install", "psutil"])
|
|
569
|
-
import psutil
|
|
570
|
-
|
|
571
|
-
main()
|