claude-mpm 4.2.1__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.
Files changed (57) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/templates/agent-manager.json +1 -1
  3. claude_mpm/agents/templates/agentic_coder_optimizer.json +1 -1
  4. claude_mpm/agents/templates/api_qa.json +1 -1
  5. claude_mpm/agents/templates/code_analyzer.json +1 -1
  6. claude_mpm/agents/templates/data_engineer.json +1 -1
  7. claude_mpm/agents/templates/documentation.json +1 -1
  8. claude_mpm/agents/templates/engineer.json +2 -2
  9. claude_mpm/agents/templates/gcp_ops_agent.json +14 -9
  10. claude_mpm/agents/templates/imagemagick.json +1 -1
  11. claude_mpm/agents/templates/memory_manager.json +1 -1
  12. claude_mpm/agents/templates/ops.json +1 -1
  13. claude_mpm/agents/templates/project_organizer.json +1 -1
  14. claude_mpm/agents/templates/qa.json +2 -2
  15. claude_mpm/agents/templates/refactoring_engineer.json +1 -1
  16. claude_mpm/agents/templates/research.json +3 -3
  17. claude_mpm/agents/templates/security.json +1 -1
  18. claude_mpm/agents/templates/test-non-mpm.json +20 -0
  19. claude_mpm/agents/templates/ticketing.json +1 -1
  20. claude_mpm/agents/templates/vercel_ops_agent.json +2 -2
  21. claude_mpm/agents/templates/version_control.json +1 -1
  22. claude_mpm/agents/templates/web_qa.json +3 -8
  23. claude_mpm/agents/templates/web_ui.json +1 -1
  24. claude_mpm/cli/commands/agents.py +3 -0
  25. claude_mpm/cli/commands/dashboard.py +3 -3
  26. claude_mpm/cli/commands/monitor.py +227 -64
  27. claude_mpm/core/config.py +25 -0
  28. claude_mpm/core/unified_agent_registry.py +2 -2
  29. claude_mpm/dashboard/static/css/code-tree.css +220 -1
  30. claude_mpm/dashboard/static/css/dashboard.css +286 -0
  31. claude_mpm/dashboard/static/dist/components/code-tree.js +1 -1
  32. claude_mpm/dashboard/static/js/components/code-simple.js +507 -15
  33. claude_mpm/dashboard/static/js/components/code-tree.js +2044 -124
  34. claude_mpm/dashboard/static/js/socket-client.js +5 -2
  35. claude_mpm/dashboard/templates/code_simple.html +79 -0
  36. claude_mpm/dashboard/templates/index.html +42 -41
  37. claude_mpm/services/agents/deployment/agent_deployment.py +4 -1
  38. claude_mpm/services/agents/deployment/agent_discovery_service.py +101 -2
  39. claude_mpm/services/agents/deployment/agent_format_converter.py +53 -9
  40. claude_mpm/services/agents/deployment/agent_template_builder.py +355 -25
  41. claude_mpm/services/agents/deployment/agent_validator.py +11 -6
  42. claude_mpm/services/agents/deployment/multi_source_deployment_service.py +83 -15
  43. claude_mpm/services/agents/deployment/validation/template_validator.py +51 -40
  44. claude_mpm/services/cli/agent_listing_service.py +2 -2
  45. claude_mpm/services/dashboard/stable_server.py +389 -0
  46. claude_mpm/services/socketio/client_proxy.py +16 -0
  47. claude_mpm/services/socketio/dashboard_server.py +360 -0
  48. claude_mpm/services/socketio/handlers/code_analysis.py +27 -5
  49. claude_mpm/services/socketio/monitor_client.py +366 -0
  50. claude_mpm/services/socketio/monitor_server.py +505 -0
  51. claude_mpm/tools/code_tree_analyzer.py +95 -17
  52. {claude_mpm-4.2.1.dist-info → claude_mpm-4.2.3.dist-info}/METADATA +1 -1
  53. {claude_mpm-4.2.1.dist-info → claude_mpm-4.2.3.dist-info}/RECORD +57 -52
  54. {claude_mpm-4.2.1.dist-info → claude_mpm-4.2.3.dist-info}/WHEEL +0 -0
  55. {claude_mpm-4.2.1.dist-info → claude_mpm-4.2.3.dist-info}/entry_points.txt +0 -0
  56. {claude_mpm-4.2.1.dist-info → claude_mpm-4.2.3.dist-info}/licenses/LICENSE +0 -0
  57. {claude_mpm-4.2.1.dist-info → claude_mpm-4.2.3.dist-info}/top_level.txt +0 -0
@@ -31,7 +31,7 @@ class TemplateValidator:
31
31
  "agent_type": str,
32
32
  "metadata": dict,
33
33
  "capabilities": dict,
34
- "instructions": dict,
34
+ "instructions": str,
35
35
  }
36
36
 
37
37
  # Required metadata fields
@@ -45,13 +45,10 @@ class TemplateValidator:
45
45
  # Required capabilities fields
46
46
  self.required_capabilities_fields = {
47
47
  "model": str,
48
- "tools": list,
48
+ "resource_tier": str,
49
49
  }
50
50
 
51
- # Required instructions fields
52
- self.required_instructions_fields = {
53
- "system_prompt": str,
54
- }
51
+ # Instructions is now a string field, not a dictionary
55
52
 
56
53
  def validate_template_file(self, template_file: Path) -> ValidationResult:
57
54
  """Validate a template file.
@@ -90,9 +87,11 @@ class TemplateValidator:
90
87
  if "capabilities" in template_data:
91
88
  self._validate_capabilities(template_data["capabilities"], result)
92
89
 
93
- # Validate instructions
90
+ # Validate instructions (now a string field)
94
91
  if "instructions" in template_data:
95
- self._validate_instructions(template_data["instructions"], result)
92
+ self._validate_instructions_string(
93
+ template_data["instructions"], result
94
+ )
96
95
 
97
96
  # Validate agent ID format
98
97
  if "agent_id" in template_data:
@@ -230,7 +229,7 @@ class TemplateValidator:
230
229
  suggestion=f"Use one of: {', '.join(valid_models)}",
231
230
  )
232
231
 
233
- # Validate tools
232
+ # Validate tools (optional field but validate if present)
234
233
  if "tools" in capabilities:
235
234
  tools = capabilities["tools"]
236
235
  if not isinstance(tools, list):
@@ -242,43 +241,55 @@ class TemplateValidator:
242
241
  "No tools specified", field_name="capabilities.tools"
243
242
  )
244
243
 
245
- def _validate_instructions(
246
- self, instructions: Dict[str, Any], result: ValidationResult
244
+ # Validate resource_tier
245
+ if "resource_tier" in capabilities:
246
+ resource_tier = capabilities["resource_tier"]
247
+ valid_tiers = ["basic", "standard", "intensive", "lightweight", "high"]
248
+ if resource_tier not in valid_tiers:
249
+ result.add_warning(
250
+ f"Unknown resource tier '{resource_tier}'",
251
+ field_name="capabilities.resource_tier",
252
+ suggestion=f"Use one of: {', '.join(valid_tiers)}",
253
+ )
254
+
255
+ def _validate_instructions_string(
256
+ self, instructions: str, result: ValidationResult
247
257
  ) -> None:
248
- """Validate instructions section.
258
+ """Validate instructions string.
249
259
 
250
260
  Args:
251
- instructions: Instructions dictionary
261
+ instructions: Instructions string
252
262
  result: ValidationResult to update
253
263
  """
254
- for field, expected_type in self.required_instructions_fields.items():
255
- if field not in instructions:
256
- result.add_error(
257
- f"Missing required instructions field: {field}",
258
- field_name=f"instructions.{field}",
259
- )
260
- else:
261
- value = instructions[field]
262
- if not isinstance(value, expected_type):
263
- result.add_error(
264
- f"Instructions field '{field}' should be {expected_type.__name__}, got {type(value).__name__}",
265
- field_name=f"instructions.{field}",
266
- )
264
+ # Check if instructions is actually a string
265
+ if not isinstance(instructions, str):
266
+ result.add_error(
267
+ f"Instructions should be a string, got {type(instructions).__name__}",
268
+ field_name="instructions",
269
+ )
270
+ return
267
271
 
268
- # Validate system prompt
269
- if "system_prompt" in instructions:
270
- system_prompt = instructions["system_prompt"]
271
- if not system_prompt or not system_prompt.strip():
272
- result.add_error(
273
- "System prompt cannot be empty",
274
- field_name="instructions.system_prompt",
275
- )
276
- elif len(system_prompt) < 20:
277
- result.add_warning(
278
- "System prompt is very short",
279
- field_name="instructions.system_prompt",
280
- suggestion="Provide more detailed instructions",
281
- )
272
+ # Check if instructions is not empty
273
+ if not instructions or not instructions.strip():
274
+ result.add_error(
275
+ "Instructions cannot be empty",
276
+ field_name="instructions",
277
+ )
278
+ elif len(instructions) < 20:
279
+ result.add_warning(
280
+ "Instructions are very short",
281
+ field_name="instructions",
282
+ suggestion="Provide more detailed instructions",
283
+ )
284
+
285
+ # Check for file references that might be invalid
286
+ if instructions.startswith("file:"):
287
+ file_ref = instructions[5:] # Remove "file:" prefix
288
+ result.add_warning(
289
+ f"Instructions reference external file: {file_ref}",
290
+ field_name="instructions",
291
+ suggestion="Consider embedding instructions directly or ensure the referenced file exists",
292
+ )
282
293
 
283
294
  def _validate_agent_id(self, agent_id: str, result: ValidationResult) -> None:
284
295
  """Validate agent ID format.
@@ -250,11 +250,11 @@ class AgentListingService(IAgentListingService):
250
250
  type=agent_data.get("type", "agent"),
251
251
  tier=agent_data.get("tier", "system"),
252
252
  path=agent_data.get("path", ""),
253
- description=agent_data.get("description") if verbose else None,
253
+ description=agent_data.get("description"),
254
254
  specializations=(
255
255
  agent_data.get("specializations") if verbose else None
256
256
  ),
257
- version=agent_data.get("version") if verbose else None,
257
+ version=agent_data.get("version"),
258
258
  deployed=True,
259
259
  )
260
260
  agents.append(agent)
@@ -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(