claude-mpm 4.1.5__py3-none-any.whl → 4.1.6__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 (47) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/templates/research.json +39 -13
  3. claude_mpm/cli/__init__.py +2 -0
  4. claude_mpm/cli/commands/__init__.py +2 -0
  5. claude_mpm/cli/commands/configure.py +1221 -0
  6. claude_mpm/cli/commands/configure_tui.py +1921 -0
  7. claude_mpm/cli/parsers/base_parser.py +7 -0
  8. claude_mpm/cli/parsers/configure_parser.py +119 -0
  9. claude_mpm/cli/startup_logging.py +39 -12
  10. claude_mpm/constants.py +1 -0
  11. claude_mpm/core/socketio_pool.py +35 -3
  12. claude_mpm/dashboard/static/css/connection-status.css +370 -0
  13. claude_mpm/dashboard/static/js/components/connection-debug.js +654 -0
  14. claude_mpm/dashboard/static/js/connection-manager.js +536 -0
  15. claude_mpm/dashboard/templates/index.html +11 -0
  16. claude_mpm/hooks/claude_hooks/services/__init__.py +3 -1
  17. claude_mpm/hooks/claude_hooks/services/connection_manager_http.py +190 -0
  18. claude_mpm/services/diagnostics/checks/__init__.py +2 -0
  19. claude_mpm/services/diagnostics/checks/instructions_check.py +418 -0
  20. claude_mpm/services/diagnostics/diagnostic_runner.py +15 -2
  21. claude_mpm/services/event_bus/direct_relay.py +173 -0
  22. claude_mpm/services/socketio/handlers/connection_handler.py +345 -0
  23. claude_mpm/services/socketio/server/broadcaster.py +32 -1
  24. claude_mpm/services/socketio/server/connection_manager.py +516 -0
  25. claude_mpm/services/socketio/server/core.py +63 -0
  26. claude_mpm/services/socketio/server/eventbus_integration.py +20 -9
  27. claude_mpm/services/socketio/server/main.py +27 -1
  28. {claude_mpm-4.1.5.dist-info → claude_mpm-4.1.6.dist-info}/METADATA +3 -1
  29. {claude_mpm-4.1.5.dist-info → claude_mpm-4.1.6.dist-info}/RECORD +33 -36
  30. claude_mpm/agents/OUTPUT_STYLE.md +0 -73
  31. claude_mpm/agents/backups/INSTRUCTIONS.md +0 -352
  32. claude_mpm/agents/templates/OPTIMIZATION_REPORT.md +0 -156
  33. claude_mpm/agents/templates/backup/data_engineer_agent_20250726_234551.json +0 -79
  34. claude_mpm/agents/templates/backup/documentation_agent_20250726_234551.json +0 -68
  35. claude_mpm/agents/templates/backup/engineer_agent_20250726_234551.json +0 -77
  36. claude_mpm/agents/templates/backup/ops_agent_20250726_234551.json +0 -78
  37. claude_mpm/agents/templates/backup/qa_agent_20250726_234551.json +0 -67
  38. claude_mpm/agents/templates/backup/research_agent_2025011_234551.json +0 -88
  39. claude_mpm/agents/templates/backup/research_agent_20250726_234551.json +0 -72
  40. claude_mpm/agents/templates/backup/research_memory_efficient.json +0 -88
  41. claude_mpm/agents/templates/backup/security_agent_20250726_234551.json +0 -78
  42. claude_mpm/agents/templates/backup/version_control_agent_20250726_234551.json +0 -62
  43. claude_mpm/agents/templates/vercel_ops_instructions.md +0 -582
  44. {claude_mpm-4.1.5.dist-info → claude_mpm-4.1.6.dist-info}/WHEEL +0 -0
  45. {claude_mpm-4.1.5.dist-info → claude_mpm-4.1.6.dist-info}/entry_points.txt +0 -0
  46. {claude_mpm-4.1.5.dist-info → claude_mpm-4.1.6.dist-info}/licenses/LICENSE +0 -0
  47. {claude_mpm-4.1.5.dist-info → claude_mpm-4.1.6.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
 
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
+ }