claude-mpm 4.2.7__py3-none-any.whl → 4.2.9__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.
@@ -39,8 +39,7 @@ except ImportError:
39
39
 
40
40
  # Set up logging
41
41
  logging.basicConfig(
42
- level=logging.INFO,
43
- format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
42
+ level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
44
43
  )
45
44
  logger = logging.getLogger(__name__)
46
45
 
@@ -184,23 +183,25 @@ class StableDashboardServer:
184
183
  self.sio = None
185
184
  self.server_runner = None
186
185
  self.server_site = None
187
-
186
+
188
187
  # Event storage with circular buffer (keep last 500 events)
189
188
  self.event_history: Deque[Dict[str, Any]] = deque(maxlen=500)
190
189
  self.event_count = 0
191
190
  self.server_start_time = time.time()
192
191
  self.last_event_time = None
193
192
  self.connected_clients = set()
194
-
193
+
195
194
  # Resilience features
196
195
  self.retry_count = 0
197
196
  self.max_retries = 3
198
197
  self.health_check_failures = 0
199
198
  self.is_healthy = True
200
-
199
+
201
200
  # Persistent event storage (optional)
202
- self.persist_events = os.environ.get('CLAUDE_MPM_PERSIST_EVENTS', 'false').lower() == 'true'
203
- self.event_log_path = Path.home() / '.claude' / 'dashboard_events.jsonl'
201
+ self.persist_events = (
202
+ os.environ.get("CLAUDE_MPM_PERSIST_EVENTS", "false").lower() == "true"
203
+ )
204
+ self.event_log_path = Path.home() / ".claude" / "dashboard_events.jsonl"
204
205
  if self.persist_events:
205
206
  self.event_log_path.parent.mkdir(parents=True, exist_ok=True)
206
207
 
@@ -219,16 +220,16 @@ class StableDashboardServer:
219
220
  print("❌ Error: Could not find dashboard files")
220
221
  print("Please ensure Claude MPM is properly installed")
221
222
  return False
222
-
223
+
223
224
  # Validate that the dashboard path has the required files
224
225
  template_path = self.dashboard_path / "templates" / "index.html"
225
226
  static_path = self.dashboard_path / "static"
226
-
227
+
227
228
  if not template_path.exists():
228
229
  print(f"❌ Error: Dashboard template not found at {template_path}")
229
230
  print("Please ensure Claude MPM dashboard files are properly installed")
230
231
  return False
231
-
232
+
232
233
  if not static_path.exists():
233
234
  print(f"❌ Error: Dashboard static files not found at {static_path}")
234
235
  print("Please ensure Claude MPM dashboard files are properly installed")
@@ -236,8 +237,10 @@ class StableDashboardServer:
236
237
 
237
238
  if self.debug:
238
239
  print(f"🔍 Debug: Dashboard path resolved to: {self.dashboard_path}")
239
- print(f"🔍 Debug: Checking for required files...")
240
- template_exists = (self.dashboard_path / "templates" / "index.html").exists()
240
+ print("🔍 Debug: Checking for required files...")
241
+ template_exists = (
242
+ self.dashboard_path / "templates" / "index.html"
243
+ ).exists()
241
244
  static_exists = (self.dashboard_path / "static").exists()
242
245
  print(f" - templates/index.html: {template_exists}")
243
246
  print(f" - static directory: {static_exists}")
@@ -273,17 +276,19 @@ class StableDashboardServer:
273
276
  # IMPORTANT: Only add explicit routes, never add static file serving for root
274
277
  # This prevents aiohttp from serving directory listings
275
278
  self.app.router.add_get("/", self._serve_dashboard)
276
- self.app.router.add_get("/index.html", self._serve_dashboard) # Also handle /index.html
279
+ self.app.router.add_get(
280
+ "/index.html", self._serve_dashboard
281
+ ) # Also handle /index.html
277
282
  self.app.router.add_get("/static/{path:.*}", self._serve_static)
278
283
  self.app.router.add_get("/api/directory/list", self._list_directory)
279
284
  self.app.router.add_get("/api/file/read", self._read_file)
280
285
  self.app.router.add_get("/version.json", self._serve_version)
281
-
286
+
282
287
  # New resilience endpoints
283
288
  self.app.router.add_get("/health", self._health_check)
284
289
  self.app.router.add_get("/api/status", self._serve_status)
285
290
  self.app.router.add_get("/api/events/history", self._serve_event_history)
286
-
291
+
287
292
  # CRITICAL: Add the missing /api/events endpoint for receiving events
288
293
  self.app.router.add_post("/api/events", self._receive_event)
289
294
 
@@ -295,17 +300,17 @@ class StableDashboardServer:
295
300
  self.connected_clients.add(sid)
296
301
  if self.debug:
297
302
  print(f"✅ SocketIO client connected: {sid}")
298
- user_agent = environ.get('HTTP_USER_AGENT', 'Unknown')
303
+ user_agent = environ.get("HTTP_USER_AGENT", "Unknown")
299
304
  # Truncate long user agents for readability
300
305
  if len(user_agent) > 80:
301
306
  user_agent = user_agent[:77] + "..."
302
307
  print(f" Client info: {user_agent}")
303
-
308
+
304
309
  # Send connection confirmation
305
310
  await self.sio.emit(
306
311
  "connection_test", {"status": "connected", "server": "stable"}, room=sid
307
312
  )
308
-
313
+
309
314
  # Send recent event history to new client
310
315
  if self.event_history:
311
316
  # Send last 20 events to catch up new client
@@ -333,7 +338,9 @@ class StableDashboardServer:
333
338
  response = create_mock_ast_data(file_path, file_name)
334
339
 
335
340
  if self.debug:
336
- print(f"📤 Sending analysis response: {len(response['elements'])} elements")
341
+ print(
342
+ f"📤 Sending analysis response: {len(response['elements'])} elements"
343
+ )
337
344
  await self.sio.emit("code:file:analyzed", response, room=sid)
338
345
 
339
346
  # CRITICAL: Handle the actual event name with colons that the client sends
@@ -351,7 +358,9 @@ class StableDashboardServer:
351
358
  response = create_mock_ast_data(file_path, file_name)
352
359
 
353
360
  if self.debug:
354
- print(f"📤 Sending analysis response: {len(response['elements'])} elements")
361
+ print(
362
+ f"📤 Sending analysis response: {len(response['elements'])} elements"
363
+ )
355
364
  await self.sio.emit("code:file:analyzed", response, room=sid)
356
365
 
357
366
  # Handle other events the dashboard sends
@@ -385,28 +394,28 @@ class StableDashboardServer:
385
394
  if self.debug:
386
395
  print(f"📡 Received top-level discovery request from {sid}")
387
396
  await self.sio.emit("code:top_level:discovered", {"status": "ok"}, room=sid)
388
-
397
+
389
398
  # Mock event generator when no real events
390
399
  @self.sio.event
391
400
  async def request_mock_event(sid, data):
392
401
  """Generate a mock event for testing."""
393
402
  if self.debug:
394
403
  print(f"📡 Mock event requested by {sid}")
395
-
404
+
396
405
  mock_event = self._create_mock_event()
397
406
  # Store and broadcast like a real event
398
407
  self.event_count += 1
399
408
  self.last_event_time = datetime.now()
400
409
  self.event_history.append(mock_event)
401
410
  await self.sio.emit("claude_event", mock_event)
402
-
411
+
403
412
  def _create_mock_event(self) -> Dict[str, Any]:
404
413
  """Create a mock event for testing/demo purposes."""
405
414
  import random
406
-
415
+
407
416
  event_types = ["file", "command", "test", "build", "deploy"]
408
417
  event_subtypes = ["start", "progress", "complete", "error", "warning"]
409
-
418
+
410
419
  return {
411
420
  "type": random.choice(event_types),
412
421
  "subtype": random.choice(event_subtypes),
@@ -416,31 +425,31 @@ class StableDashboardServer:
416
425
  "message": f"Mock {random.choice(['operation', 'task', 'process'])} {random.choice(['started', 'completed', 'in progress'])}",
417
426
  "file": f"/path/to/file_{random.randint(1, 100)}.py",
418
427
  "line": random.randint(1, 500),
419
- "progress": random.randint(0, 100)
428
+ "progress": random.randint(0, 100),
420
429
  },
421
430
  "session_id": "mock-session",
422
- "server_event_id": self.event_count + 1
431
+ "server_event_id": self.event_count + 1,
423
432
  }
424
-
433
+
425
434
  async def _start_mock_event_generator(self):
426
435
  """Start generating mock events if no real events for a while."""
427
436
  try:
428
437
  while True:
429
438
  await asyncio.sleep(30) # Check every 30 seconds
430
-
439
+
431
440
  # If no events in last 60 seconds and clients connected, generate mock
432
441
  if self.connected_clients and (
433
- not self.last_event_time or
434
- (datetime.now() - self.last_event_time).total_seconds() > 60
442
+ not self.last_event_time
443
+ or (datetime.now() - self.last_event_time).total_seconds() > 60
435
444
  ):
436
445
  if self.debug:
437
446
  print("⏰ No recent events, generating mock event")
438
-
447
+
439
448
  mock_event = self._create_mock_event()
440
449
  self.event_count += 1
441
450
  self.last_event_time = datetime.now()
442
451
  self.event_history.append(mock_event)
443
-
452
+
444
453
  await self.sio.emit("claude_event", mock_event)
445
454
  except asyncio.CancelledError:
446
455
  pass
@@ -449,20 +458,24 @@ class StableDashboardServer:
449
458
 
450
459
  async def _serve_dashboard(self, request):
451
460
  """Serve the main dashboard HTML with fallback."""
452
- dashboard_file = self.dashboard_path / "templates" / "index.html" if self.dashboard_path else None
453
-
461
+ dashboard_file = (
462
+ self.dashboard_path / "templates" / "index.html"
463
+ if self.dashboard_path
464
+ else None
465
+ )
466
+
454
467
  # Try to serve actual dashboard
455
468
  if dashboard_file and dashboard_file.exists():
456
469
  try:
457
- with open(dashboard_file, 'r', encoding='utf-8') as f:
470
+ with open(dashboard_file, encoding="utf-8") as f:
458
471
  content = f.read()
459
472
  return web.Response(text=content, content_type="text/html")
460
473
  except Exception as e:
461
474
  logger.error(f"Error reading dashboard template: {e}")
462
475
  # Fall through to fallback HTML
463
-
476
+
464
477
  # Fallback HTML if template missing or error
465
- fallback_html = '''
478
+ fallback_html = """
466
479
  <!DOCTYPE html>
467
480
  <html lang="en">
468
481
  <head>
@@ -491,7 +504,7 @@ class StableDashboardServer:
491
504
  <h1>Claude MPM Dashboard</h1>
492
505
  <div class="subtitle">Fallback Mode - Template not found</div>
493
506
  </div>
494
-
507
+
495
508
  <div id="status" class="status healthy">
496
509
  <h3>Server Status</h3>
497
510
  <div class="metric">
@@ -507,7 +520,7 @@ class StableDashboardServer:
507
520
  <div class="metric-value" id="events">Loading...</div>
508
521
  </div>
509
522
  </div>
510
-
523
+
511
524
  <div class="events">
512
525
  <h3>Recent Events</h3>
513
526
  <div id="event-list">
@@ -515,29 +528,29 @@ class StableDashboardServer:
515
528
  </div>
516
529
  </div>
517
530
  </div>
518
-
531
+
519
532
  <script src="https://cdn.socket.io/4.5.4/socket.io.min.js"></script>
520
533
  <script>
521
534
  // Fallback dashboard JavaScript
522
535
  const socket = io();
523
-
536
+
524
537
  // Update status periodically
525
538
  async function updateStatus() {
526
539
  try {
527
540
  const response = await fetch('/api/status');
528
541
  const data = await response.json();
529
-
542
+
530
543
  document.getElementById('health').textContent = data.status;
531
544
  document.getElementById('uptime').textContent = data.uptime.human;
532
545
  document.getElementById('events').textContent = data.events.total;
533
-
546
+
534
547
  const statusDiv = document.getElementById('status');
535
548
  statusDiv.className = data.status === 'running' ? 'status healthy' : 'status degraded';
536
549
  } catch (e) {
537
550
  console.error('Failed to fetch status:', e);
538
551
  }
539
552
  }
540
-
553
+
541
554
  // Listen for events
542
555
  socket.on('claude_event', (event) => {
543
556
  const eventList = document.getElementById('event-list');
@@ -545,25 +558,25 @@ class StableDashboardServer:
545
558
  eventDiv.className = 'event';
546
559
  eventDiv.textContent = JSON.stringify(event, null, 2);
547
560
  eventList.insertBefore(eventDiv, eventList.firstChild);
548
-
561
+
549
562
  // Keep only last 10 events
550
563
  while (eventList.children.length > 10) {
551
564
  eventList.removeChild(eventList.lastChild);
552
565
  }
553
566
  });
554
-
567
+
555
568
  socket.on('connect', () => {
556
569
  console.log('Connected to dashboard server');
557
570
  });
558
-
571
+
559
572
  // Initial load and periodic updates
560
573
  updateStatus();
561
574
  setInterval(updateStatus, 5000);
562
575
  </script>
563
576
  </body>
564
577
  </html>
565
- '''
566
-
578
+ """
579
+
567
580
  logger.warning("Serving fallback dashboard HTML")
568
581
  return web.Response(text=fallback_html, content_type="text/html")
569
582
 
@@ -636,6 +649,10 @@ class StableDashboardServer:
636
649
  return web.json_response({"error": "Not a file"}, status=400)
637
650
 
638
651
  try:
652
+ # Determine file type
653
+ file_ext = os.path.splitext(abs_path)[1].lower()
654
+ is_json = file_ext in [".json", ".jsonl", ".geojson"]
655
+
639
656
  # Read file with appropriate encoding
640
657
  encodings = ["utf-8", "latin-1", "cp1252"]
641
658
  content = None
@@ -651,13 +668,29 @@ class StableDashboardServer:
651
668
  if content is None:
652
669
  return web.json_response({"error": "Could not decode file"}, status=400)
653
670
 
671
+ # Format JSON files for better readability
672
+ formatted_content = content
673
+ is_valid_json = False
674
+ if is_json:
675
+ try:
676
+ import json
677
+
678
+ parsed = json.loads(content)
679
+ formatted_content = json.dumps(parsed, indent=2, sort_keys=False)
680
+ is_valid_json = True
681
+ except json.JSONDecodeError:
682
+ # Not valid JSON, return as-is
683
+ is_valid_json = False
684
+
654
685
  return web.json_response(
655
686
  {
656
687
  "path": abs_path,
657
688
  "name": os.path.basename(abs_path),
658
- "content": content,
659
- "lines": len(content.splitlines()),
689
+ "content": formatted_content,
690
+ "lines": len(formatted_content.splitlines()),
660
691
  "size": os.path.getsize(abs_path),
692
+ "type": "json" if is_json else "text",
693
+ "is_valid_json": is_valid_json,
661
694
  }
662
695
  )
663
696
 
@@ -670,121 +703,130 @@ class StableDashboardServer:
670
703
  """Health check endpoint for monitoring."""
671
704
  uptime = time.time() - self.server_start_time
672
705
  status = "healthy" if self.is_healthy else "degraded"
673
-
706
+
674
707
  health_info = {
675
708
  "status": status,
676
709
  "uptime_seconds": round(uptime, 2),
677
710
  "connected_clients": len(self.connected_clients),
678
711
  "event_count": self.event_count,
679
- "last_event": self.last_event_time.isoformat() if self.last_event_time else None,
712
+ "last_event": (
713
+ self.last_event_time.isoformat() if self.last_event_time else None
714
+ ),
680
715
  "retry_count": self.retry_count,
681
716
  "health_check_failures": self.health_check_failures,
682
- "event_history_size": len(self.event_history)
717
+ "event_history_size": len(self.event_history),
683
718
  }
684
-
719
+
685
720
  status_code = 200 if self.is_healthy else 503
686
721
  return web.json_response(health_info, status=status_code)
687
-
722
+
688
723
  async def _serve_status(self, request):
689
724
  """Detailed server status endpoint."""
690
725
  uptime = time.time() - self.server_start_time
691
-
726
+
692
727
  status_info = {
693
728
  "server": "stable",
694
729
  "version": "4.2.3",
695
730
  "status": "running" if self.is_healthy else "degraded",
696
731
  "uptime": {
697
732
  "seconds": round(uptime, 2),
698
- "human": self._format_uptime(uptime)
733
+ "human": self._format_uptime(uptime),
699
734
  },
700
735
  "connections": {
701
736
  "active": len(self.connected_clients),
702
- "clients": list(self.connected_clients)
737
+ "clients": list(self.connected_clients),
703
738
  },
704
739
  "events": {
705
740
  "total": self.event_count,
706
741
  "buffered": len(self.event_history),
707
- "last_received": self.last_event_time.isoformat() if self.last_event_time else None
742
+ "last_received": (
743
+ self.last_event_time.isoformat() if self.last_event_time else None
744
+ ),
708
745
  },
709
746
  "features": [
710
- "http", "socketio", "event_bridge", "health_monitoring",
711
- "auto_retry", "event_history", "graceful_degradation"
747
+ "http",
748
+ "socketio",
749
+ "event_bridge",
750
+ "health_monitoring",
751
+ "auto_retry",
752
+ "event_history",
753
+ "graceful_degradation",
712
754
  ],
713
755
  "resilience": {
714
756
  "retry_count": self.retry_count,
715
757
  "max_retries": self.max_retries,
716
758
  "health_failures": self.health_check_failures,
717
- "persist_events": self.persist_events
718
- }
759
+ "persist_events": self.persist_events,
760
+ },
719
761
  }
720
762
  return web.json_response(status_info)
721
-
763
+
722
764
  async def _serve_event_history(self, request):
723
765
  """Serve recent event history."""
724
- limit = int(request.query.get('limit', '100'))
766
+ limit = int(request.query.get("limit", "100"))
725
767
  events = list(self.event_history)[-limit:]
726
- return web.json_response({
727
- "events": events,
728
- "count": len(events),
729
- "total_events": self.event_count
730
- })
731
-
768
+ return web.json_response(
769
+ {"events": events, "count": len(events), "total_events": self.event_count}
770
+ )
771
+
732
772
  async def _receive_event(self, request):
733
773
  """Receive events from hook system via HTTP POST."""
734
774
  try:
735
775
  # Parse event data
736
776
  data = await request.json()
737
-
777
+
738
778
  # Add server metadata
739
779
  event = {
740
780
  **data,
741
781
  "received_at": datetime.now().isoformat(),
742
- "server_event_id": self.event_count + 1
782
+ "server_event_id": self.event_count + 1,
743
783
  }
744
-
784
+
745
785
  # Update tracking
746
786
  self.event_count += 1
747
787
  self.last_event_time = datetime.now()
748
-
788
+
749
789
  # Store in circular buffer
750
790
  self.event_history.append(event)
751
-
791
+
752
792
  # Persist to disk if enabled
753
793
  if self.persist_events:
754
794
  try:
755
- with open(self.event_log_path, 'a') as f:
756
- f.write(json.dumps(event) + '\n')
795
+ with open(self.event_log_path, "a") as f:
796
+ f.write(json.dumps(event) + "\n")
757
797
  except Exception as e:
758
798
  logger.error(f"Failed to persist event: {e}")
759
-
799
+
760
800
  # Emit to all connected SocketIO clients
761
801
  if self.sio and self.connected_clients:
762
802
  await self.sio.emit("claude_event", event)
763
803
  if self.debug:
764
- print(f"📡 Forwarded event to {len(self.connected_clients)} clients")
765
-
804
+ print(
805
+ f"📡 Forwarded event to {len(self.connected_clients)} clients"
806
+ )
807
+
766
808
  # Return success response
767
- return web.json_response({
768
- "status": "received",
769
- "event_id": event["server_event_id"],
770
- "clients_notified": len(self.connected_clients)
771
- })
772
-
809
+ return web.json_response(
810
+ {
811
+ "status": "received",
812
+ "event_id": event["server_event_id"],
813
+ "clients_notified": len(self.connected_clients),
814
+ }
815
+ )
816
+
773
817
  except json.JSONDecodeError as e:
774
818
  logger.error(f"Invalid JSON in event request: {e}")
775
819
  return web.json_response(
776
- {"error": "Invalid JSON", "details": str(e)},
777
- status=400
820
+ {"error": "Invalid JSON", "details": str(e)}, status=400
778
821
  )
779
822
  except Exception as e:
780
823
  logger.error(f"Error processing event: {e}")
781
824
  if self.debug:
782
825
  traceback.print_exc()
783
826
  return web.json_response(
784
- {"error": "Failed to process event", "details": str(e)},
785
- status=500
827
+ {"error": "Failed to process event", "details": str(e)}, status=500
786
828
  )
787
-
829
+
788
830
  async def _serve_version(self, request):
789
831
  """Serve version information."""
790
832
  version_info = {
@@ -794,14 +836,14 @@ class StableDashboardServer:
794
836
  "status": "running" if self.is_healthy else "degraded",
795
837
  }
796
838
  return web.json_response(version_info)
797
-
839
+
798
840
  def _format_uptime(self, seconds: float) -> str:
799
841
  """Format uptime in human-readable format."""
800
842
  days = int(seconds // 86400)
801
843
  hours = int((seconds % 86400) // 3600)
802
844
  minutes = int((seconds % 3600) // 60)
803
845
  secs = int(seconds % 60)
804
-
846
+
805
847
  parts = []
806
848
  if days > 0:
807
849
  parts.append(f"{days}d")
@@ -810,31 +852,35 @@ class StableDashboardServer:
810
852
  if minutes > 0:
811
853
  parts.append(f"{minutes}m")
812
854
  parts.append(f"{secs}s")
813
-
855
+
814
856
  return " ".join(parts)
815
857
 
816
858
  def run(self):
817
859
  """Run the server with automatic restart on crash."""
818
860
  restart_attempts = 0
819
861
  max_restart_attempts = 5
820
-
862
+
821
863
  while restart_attempts < max_restart_attempts:
822
864
  try:
823
- print(f"🔧 Setting up server... (attempt {restart_attempts + 1}/{max_restart_attempts})")
824
-
865
+ print(
866
+ f"🔧 Setting up server... (attempt {restart_attempts + 1}/{max_restart_attempts})"
867
+ )
868
+
825
869
  # Reset health status on restart
826
870
  self.is_healthy = True
827
871
  self.health_check_failures = 0
828
-
872
+
829
873
  if not self.setup():
830
874
  if not DEPENDENCIES_AVAILABLE:
831
875
  print("❌ Missing required dependencies")
832
876
  return False
833
-
877
+
834
878
  # Continue with fallback mode even if dashboard files not found
835
879
  print("⚠️ Dashboard files not found - running in fallback mode")
836
- print(" Server will provide basic functionality and receive events")
837
-
880
+ print(
881
+ " Server will provide basic functionality and receive events"
882
+ )
883
+
838
884
  # Set up minimal server without dashboard files
839
885
  self.sio = socketio.AsyncServer(
840
886
  cors_allowed_origins="*",
@@ -848,25 +894,29 @@ class StableDashboardServer:
848
894
  self.sio.attach(self.app)
849
895
  self._setup_routes()
850
896
  self._setup_socketio_events()
851
-
897
+
852
898
  return self._run_with_resilience()
853
-
899
+
854
900
  except Exception as e:
855
901
  restart_attempts += 1
856
902
  logger.error(f"Server crashed: {e}")
857
903
  if self.debug:
858
904
  traceback.print_exc()
859
-
905
+
860
906
  if restart_attempts < max_restart_attempts:
861
- wait_time = min(2 ** restart_attempts, 30) # Exponential backoff, max 30s
907
+ wait_time = min(
908
+ 2**restart_attempts, 30
909
+ ) # Exponential backoff, max 30s
862
910
  print(f"🔄 Restarting server in {wait_time} seconds...")
863
911
  time.sleep(wait_time)
864
912
  else:
865
- print(f"❌ Server failed after {max_restart_attempts} restart attempts")
913
+ print(
914
+ f"❌ Server failed after {max_restart_attempts} restart attempts"
915
+ )
866
916
  return False
867
-
917
+
868
918
  return False
869
-
919
+
870
920
  def _run_with_resilience(self):
871
921
  """Run server with port conflict resolution and error handling."""
872
922
 
@@ -904,11 +954,11 @@ class StableDashboardServer:
904
954
  web.run_app(self.app, host=self.host, port=self.port)
905
955
  else:
906
956
  web.run_app(
907
- self.app,
908
- host=self.host,
909
- port=self.port,
957
+ self.app,
958
+ host=self.host,
959
+ port=self.port,
910
960
  access_log=None,
911
- print=lambda *args: None # Suppress startup messages in non-debug mode
961
+ print=lambda *args: None, # Suppress startup messages in non-debug mode
912
962
  )
913
963
  return True # Server started successfully
914
964
  except KeyboardInterrupt:
@@ -916,7 +966,11 @@ class StableDashboardServer:
916
966
  return True
917
967
  except OSError as e:
918
968
  error_str = str(e)
919
- if "[Errno 48]" in error_str or "Address already in use" in error_str or "address already in use" in error_str.lower():
969
+ if (
970
+ "[Errno 48]" in error_str
971
+ or "Address already in use" in error_str
972
+ or "address already in use" in error_str.lower()
973
+ ):
920
974
  # Port is already in use
921
975
  if attempt < max_port_attempts - 1:
922
976
  self.port += 1
@@ -930,7 +984,9 @@ class StableDashboardServer:
930
984
  f"❌ Could not find available port after {max_port_attempts} attempts"
931
985
  )
932
986
  print(f" Ports {original_port} to {self.port} are all in use")
933
- print("\n💡 Tip: Check if another dashboard instance is running")
987
+ print(
988
+ "\n💡 Tip: Check if another dashboard instance is running"
989
+ )
934
990
  print(" You can stop it with: claude-mpm dashboard stop")
935
991
  return False
936
992
  else:
@@ -938,12 +994,14 @@ class StableDashboardServer:
938
994
  print(f"❌ Server error: {e}")
939
995
  if self.debug:
940
996
  import traceback
997
+
941
998
  traceback.print_exc()
942
999
  return False
943
1000
  except Exception as e:
944
1001
  print(f"❌ Unexpected server error: {e}")
945
1002
  if self.debug:
946
1003
  import traceback
1004
+
947
1005
  traceback.print_exc()
948
1006
  else:
949
1007
  print("\n💡 Run with --debug flag for more details")