claude-mpm 4.2.7__py3-none-any.whl → 4.2.11__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.
Files changed (57) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/cli/commands/dashboard.py +62 -120
  3. claude_mpm/cli/commands/monitor.py +71 -212
  4. claude_mpm/cli/commands/run.py +33 -33
  5. claude_mpm/cli/parser.py +79 -2
  6. claude_mpm/cli/parsers/__init__.py +29 -0
  7. claude_mpm/dashboard/static/css/code-tree.css +16 -4
  8. claude_mpm/dashboard/static/css/dashboard.css +15 -1
  9. claude_mpm/dashboard/static/dist/components/code-tree.js +1 -1
  10. claude_mpm/dashboard/static/dist/components/file-viewer.js +2 -0
  11. claude_mpm/dashboard/static/dist/components/module-viewer.js +1 -1
  12. claude_mpm/dashboard/static/dist/components/unified-data-viewer.js +1 -1
  13. claude_mpm/dashboard/static/dist/dashboard.js +1 -1
  14. claude_mpm/dashboard/static/dist/socket-client.js +1 -1
  15. claude_mpm/dashboard/static/js/components/code-tree.js +775 -142
  16. claude_mpm/dashboard/static/js/components/file-viewer.js +538 -0
  17. claude_mpm/dashboard/static/js/components/module-viewer.js +26 -0
  18. claude_mpm/dashboard/static/js/components/unified-data-viewer.js +166 -14
  19. claude_mpm/dashboard/static/js/dashboard.js +108 -91
  20. claude_mpm/dashboard/static/js/socket-client.js +9 -7
  21. claude_mpm/dashboard/templates/index.html +5 -2
  22. claude_mpm/hooks/claude_hooks/hook_handler.py +1 -11
  23. claude_mpm/hooks/claude_hooks/services/connection_manager.py +54 -59
  24. claude_mpm/hooks/claude_hooks/services/connection_manager_http.py +112 -72
  25. claude_mpm/services/agents/deployment/agent_format_converter.py +3 -3
  26. claude_mpm/services/agents/deployment/agent_template_builder.py +3 -5
  27. claude_mpm/services/cli/unified_dashboard_manager.py +354 -0
  28. claude_mpm/services/monitor/__init__.py +20 -0
  29. claude_mpm/services/monitor/daemon.py +256 -0
  30. claude_mpm/services/monitor/event_emitter.py +279 -0
  31. claude_mpm/services/monitor/handlers/__init__.py +20 -0
  32. claude_mpm/services/monitor/handlers/code_analysis.py +334 -0
  33. claude_mpm/services/monitor/handlers/dashboard.py +298 -0
  34. claude_mpm/services/monitor/handlers/hooks.py +491 -0
  35. claude_mpm/services/monitor/management/__init__.py +18 -0
  36. claude_mpm/services/monitor/management/health.py +124 -0
  37. claude_mpm/services/monitor/management/lifecycle.py +298 -0
  38. claude_mpm/services/monitor/server.py +442 -0
  39. claude_mpm/services/socketio/client_proxy.py +20 -12
  40. claude_mpm/services/socketio/dashboard_server.py +4 -4
  41. claude_mpm/services/socketio/monitor_client.py +4 -6
  42. claude_mpm/tools/code_tree_analyzer.py +33 -17
  43. {claude_mpm-4.2.7.dist-info → claude_mpm-4.2.11.dist-info}/METADATA +1 -1
  44. {claude_mpm-4.2.7.dist-info → claude_mpm-4.2.11.dist-info}/RECORD +48 -43
  45. claude_mpm/cli/commands/socketio_monitor.py +0 -233
  46. claude_mpm/scripts/socketio_daemon.py +0 -571
  47. claude_mpm/scripts/socketio_daemon_hardened.py +0 -937
  48. claude_mpm/scripts/socketio_daemon_wrapper.py +0 -78
  49. claude_mpm/scripts/socketio_server_manager.py +0 -349
  50. claude_mpm/services/cli/dashboard_launcher.py +0 -423
  51. claude_mpm/services/cli/socketio_manager.py +0 -595
  52. claude_mpm/services/dashboard/stable_server.py +0 -962
  53. claude_mpm/services/socketio/monitor_server.py +0 -505
  54. {claude_mpm-4.2.7.dist-info → claude_mpm-4.2.11.dist-info}/WHEEL +0 -0
  55. {claude_mpm-4.2.7.dist-info → claude_mpm-4.2.11.dist-info}/entry_points.txt +0 -0
  56. {claude_mpm-4.2.7.dist-info → claude_mpm-4.2.11.dist-info}/licenses/LICENSE +0 -0
  57. {claude_mpm-4.2.7.dist-info → claude_mpm-4.2.11.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()