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.
- claude_mpm/VERSION +1 -1
- claude_mpm/cli/commands/dashboard.py +62 -120
- claude_mpm/cli/commands/monitor.py +71 -212
- claude_mpm/cli/commands/run.py +33 -33
- claude_mpm/cli/parser.py +79 -2
- claude_mpm/cli/parsers/__init__.py +29 -0
- claude_mpm/dashboard/static/css/code-tree.css +16 -4
- claude_mpm/dashboard/static/css/dashboard.css +15 -1
- 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 +775 -142
- 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 +5 -2
- 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_format_converter.py +3 -3
- claude_mpm/services/agents/deployment/agent_template_builder.py +3 -5
- claude_mpm/services/cli/unified_dashboard_manager.py +354 -0
- claude_mpm/services/monitor/__init__.py +20 -0
- claude_mpm/services/monitor/daemon.py +256 -0
- claude_mpm/services/monitor/event_emitter.py +279 -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 +298 -0
- claude_mpm/services/monitor/server.py +442 -0
- claude_mpm/services/socketio/client_proxy.py +20 -12
- claude_mpm/services/socketio/dashboard_server.py +4 -4
- claude_mpm/services/socketio/monitor_client.py +4 -6
- claude_mpm/tools/code_tree_analyzer.py +33 -17
- {claude_mpm-4.2.7.dist-info → claude_mpm-4.2.11.dist-info}/METADATA +1 -1
- {claude_mpm-4.2.7.dist-info → claude_mpm-4.2.11.dist-info}/RECORD +48 -43
- 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 -962
- claude_mpm/services/socketio/monitor_server.py +0 -505
- {claude_mpm-4.2.7.dist-info → claude_mpm-4.2.11.dist-info}/WHEEL +0 -0
- {claude_mpm-4.2.7.dist-info → claude_mpm-4.2.11.dist-info}/entry_points.txt +0 -0
- {claude_mpm-4.2.7.dist-info → claude_mpm-4.2.11.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-4.2.7.dist-info → claude_mpm-4.2.11.dist-info}/top_level.txt +0 -0
|
@@ -1,423 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Dashboard Launcher Service
|
|
3
|
-
===========================
|
|
4
|
-
|
|
5
|
-
WHY: This service provides a centralized way to manage dashboard launching across
|
|
6
|
-
the application, particularly for Socket.IO monitoring and other web dashboards.
|
|
7
|
-
By extracting this logic from run.py, we reduce complexity and create a reusable
|
|
8
|
-
service for any command that needs to launch a dashboard.
|
|
9
|
-
|
|
10
|
-
DESIGN DECISIONS:
|
|
11
|
-
- Interface-based design (IDashboardLauncher) for testability and flexibility
|
|
12
|
-
- Support for multiple browser types and launch methods
|
|
13
|
-
- Port management integration for finding available ports
|
|
14
|
-
- Graceful fallback when browser launch fails
|
|
15
|
-
- Platform-specific optimizations for tab reuse
|
|
16
|
-
- Process management for standalone servers
|
|
17
|
-
"""
|
|
18
|
-
|
|
19
|
-
import os
|
|
20
|
-
import platform
|
|
21
|
-
import socket
|
|
22
|
-
import subprocess
|
|
23
|
-
import sys
|
|
24
|
-
import time
|
|
25
|
-
import urllib.error
|
|
26
|
-
import urllib.request
|
|
27
|
-
import webbrowser
|
|
28
|
-
from abc import ABC, abstractmethod
|
|
29
|
-
from typing import Tuple
|
|
30
|
-
|
|
31
|
-
from ...core.logger import get_logger
|
|
32
|
-
from ...core.unified_paths import get_package_root
|
|
33
|
-
from ...services.port_manager import PortManager
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
# Interface
|
|
37
|
-
class IDashboardLauncher(ABC):
|
|
38
|
-
"""Interface for dashboard launching service."""
|
|
39
|
-
|
|
40
|
-
@abstractmethod
|
|
41
|
-
def launch_dashboard(
|
|
42
|
-
self, port: int = 8765, monitor_mode: bool = True
|
|
43
|
-
) -> Tuple[bool, bool]:
|
|
44
|
-
"""
|
|
45
|
-
Launch the web dashboard.
|
|
46
|
-
|
|
47
|
-
Args:
|
|
48
|
-
port: Port number for the dashboard server
|
|
49
|
-
monitor_mode: Whether to open in monitor mode
|
|
50
|
-
|
|
51
|
-
Returns:
|
|
52
|
-
Tuple of (success, browser_opened)
|
|
53
|
-
"""
|
|
54
|
-
|
|
55
|
-
@abstractmethod
|
|
56
|
-
def is_dashboard_running(self, port: int = 8765) -> bool:
|
|
57
|
-
"""
|
|
58
|
-
Check if dashboard server is running.
|
|
59
|
-
|
|
60
|
-
Args:
|
|
61
|
-
port: Port to check
|
|
62
|
-
|
|
63
|
-
Returns:
|
|
64
|
-
True if dashboard is running on the specified port
|
|
65
|
-
"""
|
|
66
|
-
|
|
67
|
-
@abstractmethod
|
|
68
|
-
def get_dashboard_url(self, port: int = 8765) -> str:
|
|
69
|
-
"""
|
|
70
|
-
Get the dashboard URL.
|
|
71
|
-
|
|
72
|
-
Args:
|
|
73
|
-
port: Port number
|
|
74
|
-
|
|
75
|
-
Returns:
|
|
76
|
-
Dashboard URL string
|
|
77
|
-
"""
|
|
78
|
-
|
|
79
|
-
@abstractmethod
|
|
80
|
-
def stop_dashboard(self, port: int = 8765) -> bool:
|
|
81
|
-
"""
|
|
82
|
-
Stop the dashboard server.
|
|
83
|
-
|
|
84
|
-
Args:
|
|
85
|
-
port: Port of the server to stop
|
|
86
|
-
|
|
87
|
-
Returns:
|
|
88
|
-
True if successfully stopped
|
|
89
|
-
"""
|
|
90
|
-
|
|
91
|
-
@abstractmethod
|
|
92
|
-
def wait_for_dashboard(self, port: int = 8765, timeout: int = 30) -> bool:
|
|
93
|
-
"""
|
|
94
|
-
Wait for dashboard to be ready.
|
|
95
|
-
|
|
96
|
-
Args:
|
|
97
|
-
port: Port to check
|
|
98
|
-
timeout: Maximum time to wait in seconds
|
|
99
|
-
|
|
100
|
-
Returns:
|
|
101
|
-
True if dashboard became ready within timeout
|
|
102
|
-
"""
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
# Implementation
|
|
106
|
-
class DashboardLauncher(IDashboardLauncher):
|
|
107
|
-
"""Dashboard launcher service implementation."""
|
|
108
|
-
|
|
109
|
-
def __init__(self, logger=None):
|
|
110
|
-
"""
|
|
111
|
-
Initialize the dashboard launcher.
|
|
112
|
-
|
|
113
|
-
Args:
|
|
114
|
-
logger: Optional logger instance
|
|
115
|
-
"""
|
|
116
|
-
self.logger = logger or get_logger("DashboardLauncher")
|
|
117
|
-
self.port_manager = PortManager()
|
|
118
|
-
|
|
119
|
-
def launch_dashboard(
|
|
120
|
-
self, port: int = 8765, monitor_mode: bool = True
|
|
121
|
-
) -> Tuple[bool, bool]:
|
|
122
|
-
"""
|
|
123
|
-
Launch the web dashboard.
|
|
124
|
-
|
|
125
|
-
WHY: Provides a unified way to launch dashboards with proper error handling,
|
|
126
|
-
browser management, and server lifecycle control.
|
|
127
|
-
|
|
128
|
-
Args:
|
|
129
|
-
port: Port number for the dashboard server
|
|
130
|
-
monitor_mode: Whether to open in monitor mode
|
|
131
|
-
|
|
132
|
-
Returns:
|
|
133
|
-
Tuple of (success, browser_opened)
|
|
134
|
-
"""
|
|
135
|
-
try:
|
|
136
|
-
# Verify dependencies for Socket.IO dashboard
|
|
137
|
-
if monitor_mode and not self._verify_socketio_dependencies():
|
|
138
|
-
return False, False
|
|
139
|
-
|
|
140
|
-
self.logger.info(
|
|
141
|
-
f"Launching dashboard (port: {port}, monitor: {monitor_mode})"
|
|
142
|
-
)
|
|
143
|
-
|
|
144
|
-
# Clean up dead instances and check for existing servers
|
|
145
|
-
self.port_manager.cleanup_dead_instances()
|
|
146
|
-
active_instances = self.port_manager.list_active_instances()
|
|
147
|
-
|
|
148
|
-
# Determine the port to use
|
|
149
|
-
server_port = self._determine_server_port(port, active_instances)
|
|
150
|
-
server_running = self.is_dashboard_running(server_port)
|
|
151
|
-
|
|
152
|
-
# Get dashboard URL
|
|
153
|
-
dashboard_url = self.get_dashboard_url(server_port)
|
|
154
|
-
|
|
155
|
-
if server_running:
|
|
156
|
-
self.logger.info(
|
|
157
|
-
f"Dashboard server already running on port {server_port}"
|
|
158
|
-
)
|
|
159
|
-
print(f"✅ Dashboard server already running on port {server_port}")
|
|
160
|
-
print(f"📊 Dashboard: {dashboard_url}")
|
|
161
|
-
else:
|
|
162
|
-
# Start the server
|
|
163
|
-
print("🔧 Starting dashboard server...")
|
|
164
|
-
if not self._start_dashboard_server(server_port):
|
|
165
|
-
print("❌ Failed to start dashboard server")
|
|
166
|
-
self._print_troubleshooting_tips(server_port)
|
|
167
|
-
return False, False
|
|
168
|
-
|
|
169
|
-
print("✅ Dashboard server started successfully")
|
|
170
|
-
print(f"📊 Dashboard: {dashboard_url}")
|
|
171
|
-
|
|
172
|
-
# Open browser unless suppressed
|
|
173
|
-
browser_opened = False
|
|
174
|
-
if not self._is_browser_suppressed():
|
|
175
|
-
print("🌐 Opening dashboard in browser...")
|
|
176
|
-
browser_opened = self._open_browser(dashboard_url)
|
|
177
|
-
if not browser_opened:
|
|
178
|
-
print("⚠️ Could not open browser automatically")
|
|
179
|
-
print(f"📊 Please open manually: {dashboard_url}")
|
|
180
|
-
else:
|
|
181
|
-
print("🌐 Browser opening suppressed (CLAUDE_MPM_NO_BROWSER=1)")
|
|
182
|
-
self.logger.info("Browser opening suppressed by environment variable")
|
|
183
|
-
|
|
184
|
-
return True, browser_opened
|
|
185
|
-
|
|
186
|
-
except Exception as e:
|
|
187
|
-
self.logger.error(f"Failed to launch dashboard: {e}")
|
|
188
|
-
print(f"❌ Failed to launch dashboard: {e}")
|
|
189
|
-
return False, False
|
|
190
|
-
|
|
191
|
-
def is_dashboard_running(self, port: int = 8765) -> bool:
|
|
192
|
-
"""
|
|
193
|
-
Check if dashboard server is running.
|
|
194
|
-
|
|
195
|
-
WHY: Prevents duplicate server launches and helps determine if we need
|
|
196
|
-
to start a new server or connect to an existing one.
|
|
197
|
-
|
|
198
|
-
Args:
|
|
199
|
-
port: Port to check
|
|
200
|
-
|
|
201
|
-
Returns:
|
|
202
|
-
True if dashboard is running on the specified port
|
|
203
|
-
"""
|
|
204
|
-
try:
|
|
205
|
-
# First, do a basic TCP connection check
|
|
206
|
-
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
|
207
|
-
s.settimeout(2.0)
|
|
208
|
-
result = s.connect_ex(("127.0.0.1", port))
|
|
209
|
-
if result != 0:
|
|
210
|
-
self.logger.debug(f"TCP connection to port {port} failed")
|
|
211
|
-
return False
|
|
212
|
-
|
|
213
|
-
# If TCP connection succeeds, try HTTP health check
|
|
214
|
-
try:
|
|
215
|
-
response = urllib.request.urlopen(
|
|
216
|
-
f"http://localhost:{port}/status", timeout=5
|
|
217
|
-
)
|
|
218
|
-
if response.getcode() == 200:
|
|
219
|
-
self.logger.debug(f"Dashboard health check passed on port {port}")
|
|
220
|
-
return True
|
|
221
|
-
except Exception as e:
|
|
222
|
-
self.logger.debug(f"HTTP health check failed for port {port}: {e}")
|
|
223
|
-
# Server is listening but may not be fully ready yet
|
|
224
|
-
return True # Still consider it running if TCP works
|
|
225
|
-
|
|
226
|
-
except Exception as e:
|
|
227
|
-
self.logger.debug(f"Error checking dashboard on port {port}: {e}")
|
|
228
|
-
|
|
229
|
-
return False
|
|
230
|
-
|
|
231
|
-
def get_dashboard_url(self, port: int = 8765) -> str:
|
|
232
|
-
"""
|
|
233
|
-
Get the dashboard URL.
|
|
234
|
-
|
|
235
|
-
Args:
|
|
236
|
-
port: Port number
|
|
237
|
-
|
|
238
|
-
Returns:
|
|
239
|
-
Dashboard URL string
|
|
240
|
-
"""
|
|
241
|
-
return f"http://localhost:{port}"
|
|
242
|
-
|
|
243
|
-
def stop_dashboard(self, port: int = 8765) -> bool:
|
|
244
|
-
"""
|
|
245
|
-
Stop the dashboard server.
|
|
246
|
-
|
|
247
|
-
WHY: Provides clean shutdown of dashboard servers to free up ports
|
|
248
|
-
and resources.
|
|
249
|
-
|
|
250
|
-
Args:
|
|
251
|
-
port: Port of the server to stop
|
|
252
|
-
|
|
253
|
-
Returns:
|
|
254
|
-
True if successfully stopped
|
|
255
|
-
"""
|
|
256
|
-
try:
|
|
257
|
-
daemon_script = get_package_root() / "scripts" / "socketio_daemon.py"
|
|
258
|
-
if not daemon_script.exists():
|
|
259
|
-
self.logger.error(f"Daemon script not found: {daemon_script}")
|
|
260
|
-
return False
|
|
261
|
-
|
|
262
|
-
# Stop the daemon
|
|
263
|
-
result = subprocess.run(
|
|
264
|
-
[sys.executable, str(daemon_script), "stop", "--port", str(port)],
|
|
265
|
-
capture_output=True,
|
|
266
|
-
text=True,
|
|
267
|
-
timeout=10,
|
|
268
|
-
check=False,
|
|
269
|
-
)
|
|
270
|
-
|
|
271
|
-
if result.returncode == 0:
|
|
272
|
-
self.logger.info(f"Dashboard server stopped on port {port}")
|
|
273
|
-
return True
|
|
274
|
-
|
|
275
|
-
self.logger.warning(f"Failed to stop dashboard server: {result.stderr}")
|
|
276
|
-
return False
|
|
277
|
-
|
|
278
|
-
except Exception as e:
|
|
279
|
-
self.logger.error(f"Error stopping dashboard server: {e}")
|
|
280
|
-
return False
|
|
281
|
-
|
|
282
|
-
def wait_for_dashboard(self, port: int = 8765, timeout: int = 30) -> bool:
|
|
283
|
-
"""
|
|
284
|
-
Wait for dashboard to be ready.
|
|
285
|
-
|
|
286
|
-
WHY: Ensures the dashboard is fully operational before attempting to
|
|
287
|
-
open it in a browser, preventing "connection refused" errors.
|
|
288
|
-
|
|
289
|
-
Args:
|
|
290
|
-
port: Port to check
|
|
291
|
-
timeout: Maximum time to wait in seconds
|
|
292
|
-
|
|
293
|
-
Returns:
|
|
294
|
-
True if dashboard became ready within timeout
|
|
295
|
-
"""
|
|
296
|
-
start_time = time.time()
|
|
297
|
-
while time.time() - start_time < timeout:
|
|
298
|
-
if self.is_dashboard_running(port):
|
|
299
|
-
return True
|
|
300
|
-
time.sleep(0.5)
|
|
301
|
-
return False
|
|
302
|
-
|
|
303
|
-
# Private helper methods
|
|
304
|
-
def _verify_socketio_dependencies(self) -> bool:
|
|
305
|
-
"""Verify Socket.IO dependencies are available."""
|
|
306
|
-
try:
|
|
307
|
-
import aiohttp
|
|
308
|
-
import engineio
|
|
309
|
-
import socketio
|
|
310
|
-
|
|
311
|
-
self.logger.debug("Socket.IO dependencies verified")
|
|
312
|
-
return True
|
|
313
|
-
except ImportError as e:
|
|
314
|
-
self.logger.error(f"Socket.IO dependencies not available: {e}")
|
|
315
|
-
print(f"❌ Socket.IO dependencies missing: {e}")
|
|
316
|
-
print(" Install with: pip install python-socketio aiohttp python-engineio")
|
|
317
|
-
return False
|
|
318
|
-
|
|
319
|
-
def _determine_server_port(
|
|
320
|
-
self, requested_port: int, active_instances: list
|
|
321
|
-
) -> int:
|
|
322
|
-
"""Determine which port to use for the server."""
|
|
323
|
-
if active_instances:
|
|
324
|
-
# Prefer port 8765 if available
|
|
325
|
-
for instance in active_instances:
|
|
326
|
-
if instance.get("port") == 8765:
|
|
327
|
-
return 8765
|
|
328
|
-
# Otherwise use first active instance
|
|
329
|
-
return active_instances[0].get("port", requested_port)
|
|
330
|
-
return requested_port
|
|
331
|
-
|
|
332
|
-
def _start_dashboard_server(self, port: int) -> bool:
|
|
333
|
-
"""Start the dashboard server."""
|
|
334
|
-
try:
|
|
335
|
-
daemon_script = get_package_root() / "scripts" / "socketio_daemon.py"
|
|
336
|
-
if not daemon_script.exists():
|
|
337
|
-
self.logger.error(f"Daemon script not found: {daemon_script}")
|
|
338
|
-
return False
|
|
339
|
-
|
|
340
|
-
# Start the daemon
|
|
341
|
-
result = subprocess.run(
|
|
342
|
-
[sys.executable, str(daemon_script), "start", "--port", str(port)],
|
|
343
|
-
capture_output=True,
|
|
344
|
-
text=True,
|
|
345
|
-
timeout=30,
|
|
346
|
-
check=False,
|
|
347
|
-
)
|
|
348
|
-
|
|
349
|
-
if result.returncode == 0:
|
|
350
|
-
self.logger.info(f"Dashboard server started on port {port}")
|
|
351
|
-
# Wait for server to be ready
|
|
352
|
-
return self.wait_for_dashboard(port, timeout=10)
|
|
353
|
-
|
|
354
|
-
self.logger.error(f"Failed to start dashboard server: {result.stderr}")
|
|
355
|
-
return False
|
|
356
|
-
|
|
357
|
-
except Exception as e:
|
|
358
|
-
self.logger.error(f"Error starting dashboard server: {e}")
|
|
359
|
-
return False
|
|
360
|
-
|
|
361
|
-
def _is_browser_suppressed(self) -> bool:
|
|
362
|
-
"""Check if browser opening is suppressed."""
|
|
363
|
-
return os.environ.get("CLAUDE_MPM_NO_BROWSER") == "1"
|
|
364
|
-
|
|
365
|
-
def _open_browser(self, url: str) -> bool:
|
|
366
|
-
"""
|
|
367
|
-
Open URL in browser with platform-specific optimizations.
|
|
368
|
-
|
|
369
|
-
WHY: Different platforms have different ways to reuse browser tabs.
|
|
370
|
-
This method tries platform-specific approaches before falling back
|
|
371
|
-
to the standard webbrowser module.
|
|
372
|
-
"""
|
|
373
|
-
try:
|
|
374
|
-
system = platform.system().lower()
|
|
375
|
-
|
|
376
|
-
if system == "darwin": # macOS
|
|
377
|
-
try:
|
|
378
|
-
# Try to open in existing tab with -g flag (background)
|
|
379
|
-
subprocess.run(["open", "-g", url], check=True, timeout=5)
|
|
380
|
-
self.logger.info("Opened browser on macOS")
|
|
381
|
-
return True
|
|
382
|
-
except Exception:
|
|
383
|
-
pass
|
|
384
|
-
|
|
385
|
-
elif system == "linux":
|
|
386
|
-
try:
|
|
387
|
-
# Try xdg-open for Linux
|
|
388
|
-
subprocess.run(["xdg-open", url], check=True, timeout=5)
|
|
389
|
-
self.logger.info("Opened browser on Linux")
|
|
390
|
-
return True
|
|
391
|
-
except Exception:
|
|
392
|
-
pass
|
|
393
|
-
|
|
394
|
-
elif system == "windows":
|
|
395
|
-
try:
|
|
396
|
-
# Try to use existing browser window
|
|
397
|
-
webbrowser.get().open(url, new=0)
|
|
398
|
-
self.logger.info("Opened browser on Windows")
|
|
399
|
-
return True
|
|
400
|
-
except Exception:
|
|
401
|
-
pass
|
|
402
|
-
|
|
403
|
-
# Fallback to standard webbrowser module
|
|
404
|
-
webbrowser.open(url, new=0, autoraise=True)
|
|
405
|
-
self.logger.info("Opened browser using webbrowser module")
|
|
406
|
-
return True
|
|
407
|
-
|
|
408
|
-
except Exception as e:
|
|
409
|
-
self.logger.warning(f"Browser opening failed: {e}")
|
|
410
|
-
try:
|
|
411
|
-
# Final fallback
|
|
412
|
-
webbrowser.open(url)
|
|
413
|
-
return True
|
|
414
|
-
except Exception:
|
|
415
|
-
return False
|
|
416
|
-
|
|
417
|
-
def _print_troubleshooting_tips(self, port: int):
|
|
418
|
-
"""Print troubleshooting tips for dashboard launch failures."""
|
|
419
|
-
print("💡 Troubleshooting tips:")
|
|
420
|
-
print(f" - Check if port {port} is already in use")
|
|
421
|
-
print(" - Verify Socket.IO dependencies: pip install python-socketio aiohttp")
|
|
422
|
-
print(" - Try a different port with --websocket-port")
|
|
423
|
-
print(" - Check firewall settings for localhost connections")
|