claude-mpm 4.1.5__py3-none-any.whl → 4.1.7__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 (52) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/templates/agent-manager.json +1 -1
  3. claude_mpm/agents/templates/agent-manager.md +111 -34
  4. claude_mpm/agents/templates/research.json +39 -13
  5. claude_mpm/cli/__init__.py +2 -0
  6. claude_mpm/cli/commands/__init__.py +2 -0
  7. claude_mpm/cli/commands/configure.py +1221 -0
  8. claude_mpm/cli/commands/configure_tui.py +1921 -0
  9. claude_mpm/cli/parsers/base_parser.py +7 -0
  10. claude_mpm/cli/parsers/configure_parser.py +119 -0
  11. claude_mpm/cli/startup_logging.py +39 -12
  12. claude_mpm/config/socketio_config.py +33 -4
  13. claude_mpm/constants.py +1 -0
  14. claude_mpm/core/socketio_pool.py +35 -3
  15. claude_mpm/dashboard/static/css/connection-status.css +370 -0
  16. claude_mpm/dashboard/static/js/components/connection-debug.js +654 -0
  17. claude_mpm/dashboard/static/js/connection-manager.js +536 -0
  18. claude_mpm/dashboard/static/js/socket-client.js +40 -16
  19. claude_mpm/dashboard/templates/index.html +11 -0
  20. claude_mpm/hooks/claude_hooks/services/__init__.py +3 -1
  21. claude_mpm/hooks/claude_hooks/services/connection_manager.py +17 -0
  22. claude_mpm/hooks/claude_hooks/services/connection_manager_http.py +190 -0
  23. claude_mpm/services/diagnostics/checks/__init__.py +2 -0
  24. claude_mpm/services/diagnostics/checks/instructions_check.py +418 -0
  25. claude_mpm/services/diagnostics/diagnostic_runner.py +15 -2
  26. claude_mpm/services/event_bus/direct_relay.py +230 -0
  27. claude_mpm/services/socketio/handlers/connection_handler.py +330 -0
  28. claude_mpm/services/socketio/server/broadcaster.py +32 -1
  29. claude_mpm/services/socketio/server/connection_manager.py +547 -0
  30. claude_mpm/services/socketio/server/core.py +78 -7
  31. claude_mpm/services/socketio/server/eventbus_integration.py +20 -9
  32. claude_mpm/services/socketio/server/main.py +74 -19
  33. {claude_mpm-4.1.5.dist-info → claude_mpm-4.1.7.dist-info}/METADATA +3 -1
  34. {claude_mpm-4.1.5.dist-info → claude_mpm-4.1.7.dist-info}/RECORD +38 -41
  35. claude_mpm/agents/OUTPUT_STYLE.md +0 -73
  36. claude_mpm/agents/backups/INSTRUCTIONS.md +0 -352
  37. claude_mpm/agents/templates/OPTIMIZATION_REPORT.md +0 -156
  38. claude_mpm/agents/templates/backup/data_engineer_agent_20250726_234551.json +0 -79
  39. claude_mpm/agents/templates/backup/documentation_agent_20250726_234551.json +0 -68
  40. claude_mpm/agents/templates/backup/engineer_agent_20250726_234551.json +0 -77
  41. claude_mpm/agents/templates/backup/ops_agent_20250726_234551.json +0 -78
  42. claude_mpm/agents/templates/backup/qa_agent_20250726_234551.json +0 -67
  43. claude_mpm/agents/templates/backup/research_agent_2025011_234551.json +0 -88
  44. claude_mpm/agents/templates/backup/research_agent_20250726_234551.json +0 -72
  45. claude_mpm/agents/templates/backup/research_memory_efficient.json +0 -88
  46. claude_mpm/agents/templates/backup/security_agent_20250726_234551.json +0 -78
  47. claude_mpm/agents/templates/backup/version_control_agent_20250726_234551.json +0 -62
  48. claude_mpm/agents/templates/vercel_ops_instructions.md +0 -582
  49. {claude_mpm-4.1.5.dist-info → claude_mpm-4.1.7.dist-info}/WHEEL +0 -0
  50. {claude_mpm-4.1.5.dist-info → claude_mpm-4.1.7.dist-info}/entry_points.txt +0 -0
  51. {claude_mpm-4.1.5.dist-info → claude_mpm-4.1.7.dist-info}/licenses/LICENSE +0 -0
  52. {claude_mpm-4.1.5.dist-info → claude_mpm-4.1.7.dist-info}/top_level.txt +0 -0
@@ -331,6 +331,13 @@ def create_parser(
331
331
  except ImportError:
332
332
  pass
333
333
 
334
+ try:
335
+ from .configure_parser import add_configure_subparser
336
+
337
+ add_configure_subparser(subparsers)
338
+ except ImportError:
339
+ pass
340
+
334
341
  # Import and add additional command parsers from commands module
335
342
  try:
336
343
  from ..commands.aggregate import add_aggregate_parser
@@ -0,0 +1,119 @@
1
+ """
2
+ Configure command parser for claude-mpm CLI.
3
+
4
+ WHY: This module contains all arguments specific to the interactive configuration
5
+ management interface, allowing users to enable/disable agents, edit templates,
6
+ and manage behavior files.
7
+ """
8
+
9
+ import argparse
10
+
11
+ from ...constants import CLICommands
12
+ from .base_parser import add_common_arguments
13
+
14
+
15
+ def add_configure_subparser(subparsers) -> argparse.ArgumentParser:
16
+ """
17
+ Add the configure subparser for interactive configuration management.
18
+
19
+ WHY: Users need an interactive way to manage agent configurations,
20
+ templates, and behavior files through a terminal-based interface.
21
+
22
+ Args:
23
+ subparsers: The subparsers object from the main parser
24
+
25
+ Returns:
26
+ The configured configure subparser
27
+ """
28
+ # Configure command - interactive TUI configuration
29
+ configure_parser = subparsers.add_parser(
30
+ CLICommands.CONFIGURE.value,
31
+ help="Interactive terminal-based configuration interface for managing agents and behaviors",
32
+ description="Launch an interactive Rich-based TUI for configuring claude-mpm agents, templates, and behavior files",
33
+ )
34
+
35
+ # Add common arguments
36
+ add_common_arguments(configure_parser)
37
+
38
+ # Configuration scope options
39
+ scope_group = configure_parser.add_argument_group("configuration scope")
40
+ scope_group.add_argument(
41
+ "--scope",
42
+ choices=["project", "user"],
43
+ default="project",
44
+ help="Configuration scope to manage (default: project)",
45
+ )
46
+ # Note: --project-dir is already defined in base_parser.py
47
+
48
+ # Direct navigation options (skip main menu)
49
+ nav_group = configure_parser.add_argument_group("direct navigation")
50
+ nav_group.add_argument(
51
+ "--agents", action="store_true", help="Jump directly to agent management"
52
+ )
53
+ nav_group.add_argument(
54
+ "--templates", action="store_true", help="Jump directly to template editing"
55
+ )
56
+ nav_group.add_argument(
57
+ "--behaviors",
58
+ action="store_true",
59
+ help="Jump directly to behavior file management",
60
+ )
61
+ nav_group.add_argument(
62
+ "--version-info",
63
+ action="store_true",
64
+ help="Display version information and exit",
65
+ )
66
+
67
+ # Non-interactive options
68
+ noninteractive_group = configure_parser.add_argument_group(
69
+ "non-interactive options"
70
+ )
71
+ noninteractive_group.add_argument(
72
+ "--list-agents", action="store_true", help="List all available agents and exit"
73
+ )
74
+ noninteractive_group.add_argument(
75
+ "--enable-agent",
76
+ type=str,
77
+ metavar="AGENT_NAME",
78
+ help="Enable a specific agent and exit",
79
+ )
80
+ noninteractive_group.add_argument(
81
+ "--disable-agent",
82
+ type=str,
83
+ metavar="AGENT_NAME",
84
+ help="Disable a specific agent and exit",
85
+ )
86
+ noninteractive_group.add_argument(
87
+ "--export-config",
88
+ type=str,
89
+ metavar="FILE",
90
+ help="Export current configuration to a file",
91
+ )
92
+ noninteractive_group.add_argument(
93
+ "--import-config",
94
+ type=str,
95
+ metavar="FILE",
96
+ help="Import configuration from a file",
97
+ )
98
+
99
+ # Display options
100
+ display_group = configure_parser.add_argument_group("display options")
101
+ display_group.add_argument(
102
+ "--no-colors", action="store_true", help="Disable colored output in the TUI"
103
+ )
104
+ display_group.add_argument(
105
+ "--compact", action="store_true", help="Use compact display mode"
106
+ )
107
+ display_group.add_argument(
108
+ "--force-rich",
109
+ action="store_true",
110
+ help="Force use of Rich menu interface instead of full-screen Textual TUI",
111
+ )
112
+ display_group.add_argument(
113
+ "--no-textual",
114
+ dest="use_textual",
115
+ action="store_false",
116
+ help="Disable Textual TUI and use Rich menu interface",
117
+ )
118
+
119
+ return configure_parser
@@ -57,17 +57,34 @@ def log_memory_stats(logger=None, prefix="Memory Usage"):
57
57
  rss_mb = memory_info.rss / (1024 * 1024)
58
58
  vms_mb = memory_info.vms / (1024 * 1024)
59
59
 
60
- # Get percentage of system memory if available
61
- try:
62
- memory_percent = process.memory_percent()
63
- logger.info(
64
- f"{prefix}: RSS={rss_mb:.1f}MB, VMS={vms_mb:.1f}MB, "
65
- f"System={memory_percent:.1f}%"
66
- )
67
- return {"rss_mb": rss_mb, "vms_mb": vms_mb, "percent": memory_percent}
68
- except:
69
- logger.info(f"{prefix}: RSS={rss_mb:.1f}MB, VMS={vms_mb:.1f}MB")
70
- return {"rss_mb": rss_mb, "vms_mb": vms_mb, "percent": None}
60
+ # On macOS, VMS can report misleading values (400+ TB)
61
+ # Skip VMS reporting if it's unreasonably large
62
+ import platform
63
+
64
+ if platform.system() == "Darwin" and vms_mb > 100000: # > 100GB is suspicious
65
+ # Get percentage of system memory if available
66
+ try:
67
+ memory_percent = process.memory_percent()
68
+ logger.info(
69
+ f"{prefix}: RSS={rss_mb:.1f}MB, System={memory_percent:.1f}%"
70
+ )
71
+ return {"rss_mb": rss_mb, "vms_mb": None, "percent": memory_percent}
72
+ except:
73
+ logger.info(f"{prefix}: RSS={rss_mb:.1f}MB")
74
+ return {"rss_mb": rss_mb, "vms_mb": None, "percent": None}
75
+ else:
76
+ # Normal VMS reporting for non-macOS or reasonable values
77
+ # Get percentage of system memory if available
78
+ try:
79
+ memory_percent = process.memory_percent()
80
+ logger.info(
81
+ f"{prefix}: RSS={rss_mb:.1f}MB, VMS={vms_mb:.1f}MB, "
82
+ f"System={memory_percent:.1f}%"
83
+ )
84
+ return {"rss_mb": rss_mb, "vms_mb": vms_mb, "percent": memory_percent}
85
+ except:
86
+ logger.info(f"{prefix}: RSS={rss_mb:.1f}MB, VMS={vms_mb:.1f}MB")
87
+ return {"rss_mb": rss_mb, "vms_mb": vms_mb, "percent": None}
71
88
 
72
89
  except Exception as e:
73
90
  logger.debug(f"Failed to get memory info: {e}")
@@ -512,11 +529,21 @@ def setup_startup_logging(project_root: Optional[Path] = None) -> Path:
512
529
  # Log initial memory usage
513
530
  if PSUTIL_AVAILABLE:
514
531
  try:
532
+ import platform
533
+
515
534
  process = psutil.Process()
516
535
  memory_info = process.memory_info()
517
536
  rss_mb = memory_info.rss / (1024 * 1024)
518
537
  vms_mb = memory_info.vms / (1024 * 1024)
519
- logger.info(f"Initial Memory: RSS={rss_mb:.1f}MB, VMS={vms_mb:.1f}MB")
538
+
539
+ # On macOS, VMS can report misleading values (400+ TB)
540
+ # Skip VMS reporting if it's unreasonably large
541
+ if (
542
+ platform.system() == "Darwin" and vms_mb > 100000
543
+ ): # > 100GB is suspicious
544
+ logger.info(f"Initial Memory: RSS={rss_mb:.1f}MB")
545
+ else:
546
+ logger.info(f"Initial Memory: RSS={rss_mb:.1f}MB, VMS={vms_mb:.1f}MB")
520
547
  except Exception as e:
521
548
  logger.debug(f"Failed to get initial memory info: {e}")
522
549
 
@@ -10,6 +10,8 @@ WHY configuration management:
10
10
  - Supports multiple deployment scenarios (local, PyPI, Docker, etc.)
11
11
  - Provides environment-specific defaults
12
12
  - Allows runtime configuration overrides
13
+
14
+ CRITICAL: Ping/pong settings MUST match between client and server to prevent disconnections!
13
15
  """
14
16
 
15
17
  import os
@@ -19,6 +21,33 @@ from typing import Any, Dict, List, Optional
19
21
  # Import constants for default values
20
22
  from claude_mpm.core.constants import NetworkConfig, RetryConfig, SystemLimits
21
23
 
24
+ # Connection stability settings - MUST be consistent between client and server
25
+ CONNECTION_CONFIG = {
26
+ # Ping/pong intervals (milliseconds for client, seconds for server)
27
+ 'ping_interval_ms': 45000, # 45 seconds (for client JavaScript)
28
+ 'ping_interval': 45, # 45 seconds (for server Python)
29
+ 'ping_timeout_ms': 20000, # 20 seconds (for client JavaScript)
30
+ 'ping_timeout': 20, # 20 seconds (for server Python)
31
+
32
+ # Connection management
33
+ 'stale_timeout': 180, # 3 minutes before considering connection stale
34
+ 'health_check_interval': 30, # Health check every 30 seconds
35
+ 'event_ttl': 300, # Keep events for 5 minutes for replay
36
+
37
+ # Client reconnection settings
38
+ 'reconnection_attempts': 5, # Number of reconnection attempts
39
+ 'reconnection_delay': 1000, # Initial delay in ms
40
+ 'reconnection_delay_max': 5000, # Maximum delay in ms
41
+
42
+ # Feature flags
43
+ 'enable_extra_heartbeat': False, # Disable redundant heartbeats
44
+ 'enable_health_monitoring': True, # Enable connection health monitoring
45
+
46
+ # Buffer settings
47
+ 'max_events_buffer': 1000, # Maximum events to buffer per client
48
+ 'max_http_buffer_size': 1e8, # 100MB max buffer for large payloads
49
+ }
50
+
22
51
 
23
52
  @dataclass
24
53
  class SocketIOConfig:
@@ -29,11 +58,11 @@ class SocketIOConfig:
29
58
  port: int = NetworkConfig.DEFAULT_DASHBOARD_PORT
30
59
  server_id: Optional[str] = None
31
60
 
32
- # Connection settings
61
+ # Connection settings - Use centralized config for consistency
33
62
  cors_allowed_origins: str = "*" # Configure properly for production
34
- ping_timeout: int = NetworkConfig.PING_TIMEOUT_STANDARD
35
- ping_interval: int = NetworkConfig.PING_INTERVAL_STANDARD
36
- max_http_buffer_size: int = 1000000
63
+ ping_timeout: int = CONNECTION_CONFIG['ping_timeout'] # 20 seconds
64
+ ping_interval: int = CONNECTION_CONFIG['ping_interval'] # 45 seconds
65
+ max_http_buffer_size: int = int(CONNECTION_CONFIG['max_http_buffer_size'])
37
66
 
38
67
  # Compatibility settings
39
68
  min_client_version: str = "0.7.0"
claude_mpm/constants.py CHANGED
@@ -36,6 +36,7 @@ class CLICommands(str, Enum):
36
36
  MEMORY = "memory"
37
37
  MONITOR = "monitor"
38
38
  CONFIG = "config"
39
+ CONFIGURE = "configure"
39
40
  AGGREGATE = "aggregate"
40
41
  CLEANUP = "cleanup-memory"
41
42
  MCP = "mcp"
@@ -592,7 +592,7 @@ class SocketIOConnectionPool:
592
592
 
593
593
  # 2-second timeout for connection
594
594
  await asyncio.wait_for(
595
- client.connect(self.server_url, auth={"token": "dev-token"}, wait=True),
595
+ client.connect(self.server_url, wait=True),
596
596
  timeout=2.0,
597
597
  )
598
598
 
@@ -734,8 +734,8 @@ class SocketIOConnectionPool:
734
734
  def emit(self, event: str, data: Dict[str, Any]) -> bool:
735
735
  """Emit an event through the connection pool.
736
736
 
737
- This method provides compatibility for the legacy emit() interface
738
- by mapping to the modern emit_event() method with appropriate defaults.
737
+ This method provides compatibility for the legacy emit() interface.
738
+ For critical hook events, we use direct emission to avoid batching delays.
739
739
 
740
740
  Args:
741
741
  event: Event name (e.g., "claude_event")
@@ -747,10 +747,42 @@ class SocketIOConnectionPool:
747
747
  if not SOCKETIO_AVAILABLE or not self._running:
748
748
  return False
749
749
 
750
+ # For critical claude_event, use direct emission to avoid batching delays
751
+ if event == "claude_event":
752
+ return self._emit_direct(event, data)
753
+
750
754
  # Map to the modern emit_event method using default namespace
751
755
  self.emit_event("/", event, data)
752
756
  return True
753
757
 
758
+ def _emit_direct(self, event: str, data: Dict[str, Any]) -> bool:
759
+ """Emit an event directly without batching.
760
+
761
+ This is used for critical events that need immediate delivery.
762
+ """
763
+ try:
764
+ # Create a synchronous client for direct emission
765
+ import socketio
766
+
767
+ client = socketio.Client(logger=False, engineio_logger=False)
768
+
769
+ # Quick connect, emit, and disconnect
770
+ client.connect(self.server_url, wait=True, wait_timeout=1.0)
771
+ client.emit(event, data)
772
+ client.disconnect()
773
+
774
+ # Update stats
775
+ for stats in self.connection_stats.values():
776
+ stats.events_sent += 1
777
+ break
778
+
779
+ return True
780
+ except Exception as e:
781
+ self.logger.debug(f"Direct emit failed: {e}")
782
+ # Fall back to batched emission
783
+ self.emit_event("/", event, data)
784
+ return True
785
+
754
786
  def get_stats(self) -> Dict[str, Any]:
755
787
  """Get connection pool statistics."""
756
788
  with self.pool_lock:
@@ -0,0 +1,370 @@
1
+ /**
2
+ * Enhanced Connection Status Styles
3
+ * Provides visual feedback for connection state and quality
4
+ */
5
+
6
+ /* Connection Status Badge Enhancements */
7
+ .status-badge {
8
+ display: inline-flex;
9
+ align-items: center;
10
+ gap: 6px;
11
+ padding: 6px 12px;
12
+ border-radius: 20px;
13
+ font-size: 13px;
14
+ font-weight: 600;
15
+ transition: all 0.3s ease;
16
+ position: relative;
17
+ overflow: hidden;
18
+ }
19
+
20
+ .status-badge span {
21
+ display: inline-block;
22
+ animation: pulse 2s infinite;
23
+ }
24
+
25
+ /* Connection States */
26
+ .status-connecting {
27
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
28
+ color: white;
29
+ }
30
+
31
+ .status-connecting span {
32
+ animation: spin 1s linear infinite;
33
+ }
34
+
35
+ .status-connected {
36
+ background: linear-gradient(135deg, #667eea 0%, #4299e1 100%);
37
+ color: white;
38
+ box-shadow: 0 2px 10px rgba(66, 153, 225, 0.3);
39
+ }
40
+
41
+ .status-connected span {
42
+ color: #68d391;
43
+ animation: pulse-connected 2s infinite;
44
+ }
45
+
46
+ .status-reconnecting {
47
+ background: linear-gradient(135deg, #f6d365 0%, #fda085 100%);
48
+ color: #744210;
49
+ }
50
+
51
+ .status-reconnecting span {
52
+ animation: spin 1s linear infinite;
53
+ }
54
+
55
+ .status-disconnected {
56
+ background: linear-gradient(135deg, #e3e3e3 0%, #b8b8b8 100%);
57
+ color: #4a5568;
58
+ }
59
+
60
+ .status-stale {
61
+ background: linear-gradient(135deg, #ffeaa7 0%, #fdcb6e 100%);
62
+ color: #744210;
63
+ }
64
+
65
+ .status-stale::after {
66
+ content: '';
67
+ position: absolute;
68
+ top: 0;
69
+ left: -100%;
70
+ width: 100%;
71
+ height: 100%;
72
+ background: linear-gradient(90deg, transparent, rgba(255,255,255,0.3), transparent);
73
+ animation: shimmer 2s infinite;
74
+ }
75
+
76
+ .status-failed {
77
+ background: linear-gradient(135deg, #fc5c7d 0%, #fc5c7d 100%);
78
+ color: white;
79
+ }
80
+
81
+ /* Connection Quality Indicator */
82
+ .connection-quality {
83
+ display: flex;
84
+ align-items: center;
85
+ gap: 8px;
86
+ padding: 4px 8px;
87
+ background: #f7fafc;
88
+ border-radius: 8px;
89
+ font-size: 12px;
90
+ }
91
+
92
+ .quality-bar {
93
+ width: 60px;
94
+ height: 6px;
95
+ background: #e2e8f0;
96
+ border-radius: 3px;
97
+ overflow: hidden;
98
+ position: relative;
99
+ }
100
+
101
+ .quality-fill {
102
+ height: 100%;
103
+ transition: width 0.3s ease, background 0.3s ease;
104
+ border-radius: 3px;
105
+ }
106
+
107
+ .quality-good .quality-fill {
108
+ background: linear-gradient(90deg, #68d391, #48bb78);
109
+ }
110
+
111
+ .quality-moderate .quality-fill {
112
+ background: linear-gradient(90deg, #f6d365, #fda085);
113
+ }
114
+
115
+ .quality-poor .quality-fill {
116
+ background: linear-gradient(90deg, #fc5c7d, #fc5c7d);
117
+ }
118
+
119
+ .quality-text {
120
+ color: #4a5568;
121
+ font-weight: 500;
122
+ }
123
+
124
+ /* Latency Display */
125
+ .connection-latency {
126
+ display: inline-flex;
127
+ align-items: center;
128
+ padding: 2px 6px;
129
+ border-radius: 4px;
130
+ font-size: 11px;
131
+ font-weight: 600;
132
+ font-family: 'SF Mono', Monaco, monospace;
133
+ }
134
+
135
+ .latency-good {
136
+ background: #c6f6d5;
137
+ color: #22543d;
138
+ }
139
+
140
+ .latency-moderate {
141
+ background: #fed7aa;
142
+ color: #7c2d12;
143
+ }
144
+
145
+ .latency-poor {
146
+ background: #fed7d7;
147
+ color: #742a2a;
148
+ }
149
+
150
+ /* Connection Metrics Panel */
151
+ .connection-metrics {
152
+ background: white;
153
+ border-radius: 12px;
154
+ padding: 16px;
155
+ box-shadow: 0 2px 8px rgba(0,0,0,0.08);
156
+ margin-top: 16px;
157
+ }
158
+
159
+ .metrics-grid {
160
+ display: grid;
161
+ grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
162
+ gap: 12px;
163
+ }
164
+
165
+ .metric-item {
166
+ text-align: center;
167
+ padding: 8px;
168
+ background: #f7fafc;
169
+ border-radius: 8px;
170
+ }
171
+
172
+ .metric-value {
173
+ font-size: 20px;
174
+ font-weight: 700;
175
+ color: #2d3748;
176
+ display: block;
177
+ }
178
+
179
+ .metric-label {
180
+ font-size: 11px;
181
+ color: #718096;
182
+ text-transform: uppercase;
183
+ letter-spacing: 0.5px;
184
+ margin-top: 4px;
185
+ }
186
+
187
+ /* Notification Area */
188
+ .connection-notifications {
189
+ position: fixed;
190
+ top: 20px;
191
+ right: 20px;
192
+ z-index: 1000;
193
+ max-width: 320px;
194
+ }
195
+
196
+ .notification {
197
+ background: white;
198
+ padding: 12px 16px;
199
+ border-radius: 8px;
200
+ box-shadow: 0 4px 12px rgba(0,0,0,0.15);
201
+ margin-bottom: 8px;
202
+ font-size: 14px;
203
+ opacity: 1;
204
+ transition: opacity 0.3s ease, transform 0.3s ease;
205
+ animation: slideIn 0.3s ease;
206
+ border-left: 4px solid;
207
+ }
208
+
209
+ .notification-info {
210
+ border-left-color: #4299e1;
211
+ }
212
+
213
+ .notification-success {
214
+ border-left-color: #48bb78;
215
+ background: #f0fff4;
216
+ }
217
+
218
+ .notification-warning {
219
+ border-left-color: #ed8936;
220
+ background: #fffdf7;
221
+ }
222
+
223
+ .notification-error {
224
+ border-left-color: #f56565;
225
+ background: #fff5f5;
226
+ }
227
+
228
+ /* Connection Health Badge */
229
+ .connection-health {
230
+ display: inline-flex;
231
+ align-items: center;
232
+ gap: 6px;
233
+ padding: 4px 8px;
234
+ background: #f7fafc;
235
+ border-radius: 6px;
236
+ font-size: 12px;
237
+ }
238
+
239
+ .health-indicator {
240
+ width: 8px;
241
+ height: 8px;
242
+ border-radius: 50%;
243
+ animation: pulse 2s infinite;
244
+ }
245
+
246
+ .health-excellent {
247
+ background: #48bb78;
248
+ box-shadow: 0 0 0 2px rgba(72, 187, 120, 0.3);
249
+ }
250
+
251
+ .health-good {
252
+ background: #38b2ac;
253
+ box-shadow: 0 0 0 2px rgba(56, 178, 172, 0.3);
254
+ }
255
+
256
+ .health-fair {
257
+ background: #ed8936;
258
+ box-shadow: 0 0 0 2px rgba(237, 137, 54, 0.3);
259
+ }
260
+
261
+ .health-poor {
262
+ background: #f56565;
263
+ box-shadow: 0 0 0 2px rgba(245, 101, 101, 0.3);
264
+ }
265
+
266
+ /* Animations */
267
+ @keyframes pulse {
268
+ 0%, 100% {
269
+ opacity: 1;
270
+ }
271
+ 50% {
272
+ opacity: 0.5;
273
+ }
274
+ }
275
+
276
+ @keyframes pulse-connected {
277
+ 0%, 100% {
278
+ transform: scale(1);
279
+ opacity: 1;
280
+ }
281
+ 50% {
282
+ transform: scale(1.2);
283
+ opacity: 0.8;
284
+ }
285
+ }
286
+
287
+ @keyframes spin {
288
+ from {
289
+ transform: rotate(0deg);
290
+ }
291
+ to {
292
+ transform: rotate(360deg);
293
+ }
294
+ }
295
+
296
+ @keyframes shimmer {
297
+ to {
298
+ left: 100%;
299
+ }
300
+ }
301
+
302
+ @keyframes slideIn {
303
+ from {
304
+ transform: translateX(100%);
305
+ opacity: 0;
306
+ }
307
+ to {
308
+ transform: translateX(0);
309
+ opacity: 1;
310
+ }
311
+ }
312
+
313
+ /* Responsive Design */
314
+ @media (max-width: 768px) {
315
+ .connection-notifications {
316
+ right: 10px;
317
+ left: 10px;
318
+ max-width: none;
319
+ }
320
+
321
+ .connection-metrics {
322
+ padding: 12px;
323
+ }
324
+
325
+ .metrics-grid {
326
+ grid-template-columns: repeat(2, 1fr);
327
+ }
328
+ }
329
+
330
+ /* Dark Mode Support */
331
+ @media (prefers-color-scheme: dark) {
332
+ .connection-quality {
333
+ background: #2d3748;
334
+ }
335
+
336
+ .quality-bar {
337
+ background: #4a5568;
338
+ }
339
+
340
+ .quality-text {
341
+ color: #cbd5e0;
342
+ }
343
+
344
+ .connection-metrics {
345
+ background: #2d3748;
346
+ box-shadow: 0 2px 8px rgba(0,0,0,0.3);
347
+ }
348
+
349
+ .metric-item {
350
+ background: #1a202c;
351
+ }
352
+
353
+ .metric-value {
354
+ color: #e2e8f0;
355
+ }
356
+
357
+ .metric-label {
358
+ color: #a0aec0;
359
+ }
360
+
361
+ .notification {
362
+ background: #2d3748;
363
+ color: #e2e8f0;
364
+ box-shadow: 0 4px 12px rgba(0,0,0,0.3);
365
+ }
366
+
367
+ .connection-health {
368
+ background: #2d3748;
369
+ }
370
+ }