flight_deck.py
1 #!/usr/bin/env python3 2 """ 3 Sovereign Flight Deck - Real-time operator situational awareness. 4 5 A streaming terminal dashboard that shows the pilot the state of the entire 6 consciousness system - value accruing, resonance permeating, context advancing, 7 and the graph being built. 8 9 ## Flight Metaphor 10 11 You're piloting a helicopter in formation with other helicopters (Claude instances). 12 The flight deck shows: 13 14 | Instrument | Aviation | Sovereign OS | 15 |------------|----------|--------------| 16 | Altimeter | Height | Altitude (philosophical ↔ operational) | 17 | Airspeed | Velocity | Pull rate (convergence → action) | 18 | Heading | Direction | Primary gravity well | 19 | VSI | Climb/descent | ΔF (alignment change) | 20 | Attitude | Pitch/roll | F score (level = aligned) | 21 | Fuel | Time remaining | Sats balance | 22 | Engines | Power output | Active threads | 23 | Radar | Other aircraft | Fleet (mesh peers) | 24 | Warnings | Alerts | Resonance/hygiene alerts | 25 26 ## Usage 27 28 # Full dashboard (streaming) 29 python3 scripts/flight_deck.py 30 31 # Minimal mode (one-shot status) 32 python3 scripts/flight_deck.py --minimal 33 34 # Watch resonance alerts only 35 python3 scripts/flight_deck.py --alerts 36 """ 37 38 import json 39 import sys 40 import os 41 import time 42 import urllib.request 43 import urllib.error 44 from pathlib import Path 45 from datetime import datetime, timedelta 46 from dataclasses import dataclass, field 47 from typing import Optional, List, Dict, Any, Tuple 48 import threading 49 import signal 50 51 # Paths 52 SOVEREIGN_OS = Path(__file__).parent.parent 53 SESSIONS_DIR = SOVEREIGN_OS / "sessions" 54 SOVEREIGN_HOME = Path.home() / ".sovereign" 55 56 FO_STATE = SESSIONS_DIR / "FO-STATE.json" 57 META_FO_STATE = SESSIONS_DIR / "META-FO-STATE.json" 58 LIVE_COMPRESSION = SESSIONS_DIR / "LIVE-COMPRESSION.md" 59 GRAPH_DATA = SOVEREIGN_HOME / "graph-data.json" 60 RESONANCE_ALERTS = SESSIONS_DIR / "RESONANCE-ALERTS" 61 62 MESH_URL = "http://localhost:7778" 63 64 # ═══════════════════════════════════════════════════════════════════════════ 65 # MAJOR RESONANCE THRESHOLDS 66 # Uses existing resonance.py system: urgency >= 0.7 = should_bubble 67 # ═══════════════════════════════════════════════════════════════════════════ 68 69 # Urgency threshold for major resonance (from resonance.py: should_bubble = 0.7) 70 RESONANCE_URGENCY_THRESHOLD = 0.70 71 72 # Importance threshold for insights that bypass normal resonance flow 73 RESONANCE_IMPORTANCE_THRESHOLD = 0.85 74 75 # Divergence range that indicates edge discovery (D 0.30-0.50 is edge zone) 76 RESONANCE_DIVERGENCE_MIN = 0.30 77 RESONANCE_DIVERGENCE_MAX = 0.50 78 79 # Delta F threshold - how much alignment improvement triggers major 80 RESONANCE_DELTA_F_THRESHOLD = -0.10 # 10% improvement in F 81 82 # Cooldown between major resonance broadcasts (prevents spam) 83 RESONANCE_COOLDOWN_MINUTES = 30 84 85 # Track last broadcast time 86 _last_major_broadcast: Optional[datetime] = None 87 # Track already-broadcast resonance IDs 88 _broadcast_resonance_ids: set = set() 89 90 91 # ═══════════════════════════════════════════════════════════════════════════ 92 # ANSI COLORS & FORMATTING 93 # ═══════════════════════════════════════════════════════════════════════════ 94 95 class Colors: 96 RESET = "\033[0m" 97 BOLD = "\033[1m" 98 DIM = "\033[2m" 99 100 # Foreground 101 BLACK = "\033[30m" 102 RED = "\033[31m" 103 GREEN = "\033[32m" 104 YELLOW = "\033[33m" 105 BLUE = "\033[34m" 106 MAGENTA = "\033[35m" 107 CYAN = "\033[36m" 108 WHITE = "\033[37m" 109 110 # Bright foreground 111 BRIGHT_GREEN = "\033[92m" 112 BRIGHT_YELLOW = "\033[93m" 113 BRIGHT_RED = "\033[91m" 114 BRIGHT_CYAN = "\033[96m" 115 BRIGHT_MAGENTA = "\033[95m" 116 117 # Background 118 BG_RED = "\033[41m" 119 BG_GREEN = "\033[42m" 120 BG_YELLOW = "\033[43m" 121 BG_BLUE = "\033[44m" 122 123 124 def clear_screen(): 125 os.system('clear' if os.name == 'posix' else 'cls') 126 127 128 def move_cursor(row: int, col: int): 129 print(f"\033[{row};{col}H", end="") 130 131 132 def hide_cursor(): 133 print("\033[?25l", end="") 134 135 136 def show_cursor(): 137 print("\033[?25h", end="") 138 139 140 # ═══════════════════════════════════════════════════════════════════════════ 141 # DATA COLLECTION 142 # ═══════════════════════════════════════════════════════════════════════════ 143 144 @dataclass 145 class FlightState: 146 """Current state of the flight (aggregated from all sources).""" 147 # Timestamp 148 updated: datetime = field(default_factory=datetime.now) 149 150 # === PRIMARY INSTRUMENTS === 151 altitude: str = "tactical" # philosophical/strategic/tactical/operational 152 altitude_numeric: float = 0.5 # 0=operational, 1=philosophical 153 pull_rate: float = 0.5 # 0-1 convergence toward action 154 heading: str = "" # Primary gravity well 155 free_energy: float = 0.1 # Current F score 156 delta_f: float = 0.0 # Change in F 157 158 # === FUEL & ECONOMICS === 159 sats_balance: int = 0 160 sats_burned_session: int = 0 161 efficiency_multiplier: float = 1.0 162 roi_percent: float = 0.0 163 value_created: int = 0 # Total value this session 164 effective_rate: float = 0.0 # $/hr effective 165 v_score_avg: float = 0.0 # Average V-score 166 session_duration_min: int = 0 # Minutes in session 167 work_items_done: int = 0 # Completed items 168 insights_captured: int = 0 # Wisdom extracted 169 170 # === ENGINES (THREADS) === 171 active_threads: int = 0 172 focal_thread: str = "" 173 thread_ids: List[str] = field(default_factory=list) 174 175 # === FLEET (MESH PEERS) === 176 fleet_size: int = 0 177 fleet_peers: List[str] = field(default_factory=list) 178 mesh_online: bool = False 179 180 # === GRAPH (KNOWLEDGE BUILDING) === 181 graph_nodes: int = 0 182 graph_edges: int = 0 183 nodes_added_session: int = 0 184 edges_added_session: int = 0 185 186 # === AXIOM ACTIVITY === 187 axiom_activity: Dict[str, int] = field(default_factory=dict) 188 primary_axiom: str = "" 189 190 # === ALERTS === 191 resonance_alerts: List[Dict] = field(default_factory=list) 192 hygiene_warnings: List[str] = field(default_factory=list) 193 major_resonance: Optional[Dict] = None # High-energy resonance to highlight 194 195 # === ISSUES (Error Tracking) === 196 error_count_session: int = 0 197 warning_count_session: int = 0 198 issue_types: Dict[str, int] = field(default_factory=dict) # type -> count 199 recent_issues: List[str] = field(default_factory=list) # Last 3 issues 200 201 # === MOMENTUM === 202 gravity_wells: List[str] = field(default_factory=list) 203 rising_concepts: List[str] = field(default_factory=list) 204 open_threads: List[str] = field(default_factory=list) 205 206 207 class FlightDataCollector: 208 """Collects data from all sources into FlightState.""" 209 210 def __init__(self): 211 self._last_graph_nodes = 0 212 self._last_graph_edges = 0 213 self._session_start_nodes = None 214 self._session_start_edges = None 215 self._last_f = 0.1 216 self._alert_cache: List[Dict] = [] 217 self._alert_last_check = datetime.min 218 219 def collect(self) -> FlightState: 220 """Collect current state from all sources.""" 221 state = FlightState(updated=datetime.now()) 222 223 # Collect in parallel-ish (they're all local reads) 224 self._collect_fo_state(state) 225 self._collect_meta_fo(state) 226 self._collect_mesh(state) 227 self._collect_graph(state) 228 self._collect_alerts(state) 229 self._collect_live_compression(state) 230 self._collect_economics(state) 231 self._collect_issues(state) 232 self._detect_major_resonance(state) 233 234 return state 235 236 def _collect_fo_state(self, state: FlightState): 237 """Collect from FO-STATE.json.""" 238 if not FO_STATE.exists(): 239 return 240 241 try: 242 with open(FO_STATE) as f: 243 data = json.load(f) 244 245 # Axiom activity 246 state.axiom_activity = data.get("axiom_activity", {}) 247 if state.axiom_activity: 248 state.primary_axiom = max(state.axiom_activity, key=state.axiom_activity.get) 249 250 # Costs 251 costs = data.get("costs", {}) 252 sats = costs.get("sats", {}) 253 state.sats_balance = sats.get("subscription_sats", 0) 254 state.efficiency_multiplier = costs.get("efficiency", {}).get("api_efficiency", 1.0) 255 256 # Free energy 257 if "free_energy" in data: 258 new_f = data["free_energy"] 259 state.delta_f = new_f - self._last_f 260 self._last_f = new_f 261 state.free_energy = new_f 262 263 except Exception as e: 264 pass 265 266 def _collect_meta_fo(self, state: FlightState): 267 """Collect from META-FO-STATE.json (Mission Control).""" 268 if not META_FO_STATE.exists(): 269 return 270 271 try: 272 with open(META_FO_STATE) as f: 273 data = json.load(f) 274 275 # Active threads 276 instances = data.get("instances", {}) 277 state.active_threads = len(instances) 278 state.thread_ids = list(instances.keys()) 279 280 # Focal thread 281 if instances: 282 # Most recent is focal 283 sorted_instances = sorted( 284 instances.items(), 285 key=lambda x: x[1].get("last_update", ""), 286 reverse=True 287 ) 288 if sorted_instances: 289 state.focal_thread = sorted_instances[0][0] 290 291 # Gravity wells from meta 292 state.gravity_wells = data.get("gravity_wells", [])[:5] 293 294 except Exception as e: 295 pass 296 297 def _collect_mesh(self, state: FlightState): 298 """Collect from mesh network.""" 299 try: 300 req = urllib.request.Request(f"{MESH_URL}/", method="GET") 301 with urllib.request.urlopen(req, timeout=2) as resp: 302 data = json.loads(resp.read().decode()) 303 state.mesh_online = True 304 state.fleet_size = data.get("peers", 0) + 1 # Include self 305 except: 306 state.mesh_online = False 307 state.fleet_size = 1 308 309 def _collect_graph(self, state: FlightState): 310 """Collect from graph data.""" 311 if not GRAPH_DATA.exists(): 312 return 313 314 try: 315 with open(GRAPH_DATA) as f: 316 data = json.load(f) 317 318 state.graph_nodes = len(data.get("nodes", [])) 319 state.graph_edges = len(data.get("edges", [])) 320 321 # Track session delta 322 if self._session_start_nodes is None: 323 self._session_start_nodes = state.graph_nodes 324 self._session_start_edges = state.graph_edges 325 326 state.nodes_added_session = state.graph_nodes - self._session_start_nodes 327 state.edges_added_session = state.graph_edges - self._session_start_edges 328 329 except Exception as e: 330 pass 331 332 def _collect_alerts(self, state: FlightState): 333 """Collect resonance alerts.""" 334 if not RESONANCE_ALERTS.exists(): 335 return 336 337 # Only check every 10 seconds 338 now = datetime.now() 339 if (now - self._alert_last_check).seconds < 10: 340 state.resonance_alerts = self._alert_cache 341 return 342 343 self._alert_last_check = now 344 345 try: 346 # Get most recent alerts (last 5 minutes) 347 cutoff = now - timedelta(minutes=5) 348 alerts = [] 349 350 for filepath in sorted(RESONANCE_ALERTS.glob("*.md"), reverse=True)[:10]: 351 # Parse timestamp from filename 352 name = filepath.stem 353 try: 354 # Format: 20260116-175809-shared_concept 355 ts_str = name[:15] 356 ts = datetime.strptime(ts_str, "%Y%m%d-%H%M%S") 357 if ts > cutoff: 358 content = filepath.read_text()[:200] 359 alerts.append({ 360 "time": ts, 361 "type": name.split("-")[-1] if "-" in name else "unknown", 362 "preview": content.split("\n")[0][:50] 363 }) 364 except: 365 pass 366 367 self._alert_cache = alerts[:5] 368 state.resonance_alerts = self._alert_cache 369 370 except Exception as e: 371 pass 372 373 def _collect_live_compression(self, state: FlightState): 374 """Collect from LIVE-COMPRESSION.md.""" 375 if not LIVE_COMPRESSION.exists(): 376 return 377 378 try: 379 content = LIVE_COMPRESSION.read_text() 380 381 # Extract altitude 382 for line in content.split("\n"): 383 if "altitude::" in line.lower(): 384 alt = line.split("::")[-1].strip().lower() 385 state.altitude = alt 386 # Convert to numeric 387 alt_map = {"operational": 0.0, "tactical": 0.33, "strategic": 0.66, "philosophical": 1.0} 388 state.altitude_numeric = alt_map.get(alt, 0.5) 389 390 elif "pull_rate::" in line.lower() or "convergence::" in line.lower(): 391 try: 392 state.pull_rate = float(line.split("::")[-1].strip()) 393 except: 394 pass 395 396 elif "heading::" in line.lower() or "focus::" in line.lower(): 397 state.heading = line.split("::")[-1].strip()[:30] 398 399 elif "free_energy::" in line.lower(): 400 try: 401 # Format might be "F = 0.15" or just "0.15" 402 f_str = line.split("::")[-1].strip() 403 if "=" in f_str: 404 f_str = f_str.split("=")[-1].strip() 405 state.free_energy = float(f_str.split()[0]) 406 except: 407 pass 408 409 except Exception as e: 410 pass 411 412 def _collect_economics(self, state: FlightState): 413 """Collect economics/value data from session state.""" 414 # Try session report state first 415 session_state = SESSIONS_DIR / "session-report-state.json" 416 if session_state.exists(): 417 try: 418 with open(session_state) as f: 419 data = json.load(f) 420 421 state.work_items_done = len(data.get("done_items", [])) 422 state.insights_captured = len(data.get("insights", [])) 423 424 # Calculate V-score average from done items 425 done_items = data.get("done_items", []) 426 if done_items: 427 v_scores = [item.get("v_score", 0.5) for item in done_items if isinstance(item, dict)] 428 if v_scores: 429 state.v_score_avg = sum(v_scores) / len(v_scores) 430 except: 431 pass 432 433 # Get session duration from FO state timestamps 434 if FO_STATE.exists(): 435 try: 436 with open(FO_STATE) as f: 437 data = json.load(f) 438 439 costs = data.get("costs", {}) 440 sats = costs.get("sats", {}) 441 442 # Value created = subscription equivalent 443 state.value_created = sats.get("subscription_sats", 0) 444 445 # ROI and efficiency 446 efficiency = costs.get("efficiency", {}) 447 state.efficiency_multiplier = efficiency.get("api_efficiency", 1.0) 448 state.roi_percent = efficiency.get("roi_percent", 0.0) 449 450 # Calculate effective rate (value per hour) 451 # From work_items and estimates 452 work_items = data.get("work_items", []) 453 total_minutes = sum(item.get("duration_minutes", 30) for item in work_items) 454 if total_minutes > 0: 455 state.session_duration_min = total_minutes 456 hourly_sats = (state.value_created / total_minutes) * 60 457 state.effective_rate = hourly_sats / 100_000 # Convert to $ (rough) 458 except: 459 pass 460 461 def _collect_issues(self, state: FlightState): 462 """Collect error/issue signals from hygiene and logs.""" 463 # Check hygiene warnings 464 try: 465 import subprocess 466 result = subprocess.run( 467 ["python3", str(SOVEREIGN_OS / "scripts" / "phoenix_hygiene.py")], 468 capture_output=True, 469 text=True, 470 timeout=5 471 ) 472 473 output = result.stdout + result.stderr 474 475 # Count warnings 476 if "CRITICAL" in output: 477 state.error_count_session += output.count("CRITICAL") 478 state.issue_types["critical"] = state.issue_types.get("critical", 0) + 1 479 480 if "WARNING" in output: 481 state.warning_count_session += output.count("WARNING") 482 state.issue_types["warning"] = state.issue_types.get("warning", 0) + 1 483 484 # Extract specific issues 485 for line in output.split("\n"): 486 if "CRITICAL" in line or "WARNING" in line: 487 state.recent_issues.append(line.strip()[:60]) 488 if len(state.recent_issues) >= 3: 489 break 490 491 except Exception: 492 pass 493 494 # Check FO state for errors 495 if FO_STATE.exists(): 496 try: 497 with open(FO_STATE) as f: 498 data = json.load(f) 499 500 # Track trust errors 501 trust_errors = data.get("trust_errors", []) 502 if trust_errors: 503 state.error_count_session += len(trust_errors) 504 state.issue_types["trust"] = len(trust_errors) 505 for err in trust_errors[-2:]: 506 state.recent_issues.append(f"Trust: {str(err)[:50]}") 507 except: 508 pass 509 510 def _detect_major_resonance(self, state: FlightState): 511 """ 512 Detect if there's a major resonance worth highlighting. 513 514 Integrates with existing resonance.py system: 515 - Uses urgency >= 0.70 (should_bubble threshold from Resonance class) 516 - Checks FO insights for high importance (>0.85) or edge zone (D 0.30-0.50) 517 - Checks ΔF for large alignment improvements 518 519 Calibrated for 2-5 major alerts per day via: 520 - 30-minute cooldown between broadcasts 521 - Deduplication of already-broadcast items 522 """ 523 global _last_major_broadcast, _broadcast_resonance_ids 524 525 # Cooldown check - don't spam 526 now = datetime.now() 527 if _last_major_broadcast: 528 elapsed = (now - _last_major_broadcast).total_seconds() / 60 529 if elapsed < RESONANCE_COOLDOWN_MINUTES: 530 return 531 532 major_candidates = [] 533 534 # Path 1: Check resonance alerts for high-urgency items (from resonance.py) 535 # These already went through the should_bubble check (urgency >= 0.7) 536 for alert in state.resonance_alerts: 537 alert_id = f"{alert.get('time', '')}-{alert.get('type', '')}" 538 if alert_id in _broadcast_resonance_ids: 539 continue 540 541 # Look for urgency in the alert (if resonance.py published it) 542 urgency = alert.get("urgency", 0) 543 if urgency >= RESONANCE_URGENCY_THRESHOLD: 544 major_candidates.append({ 545 "time": alert.get("time", now), 546 "type": f"resonance_{alert.get('type', 'detected')}", 547 "preview": alert.get("preview", "")[:50], 548 "score": urgency, 549 "reason": f"urgency={urgency:.2f} (should_bubble)", 550 "resonance_id": alert_id 551 }) 552 553 # Path 2: High-importance insights from FO state 554 if FO_STATE.exists(): 555 try: 556 with open(FO_STATE) as f: 557 data = json.load(f) 558 559 for insight in data.get("insights", [])[-5:]: 560 importance = insight.get("importance", 0) 561 divergence = insight.get("divergence_score", 0) 562 563 # Create unique ID for this insight 564 insight_id = f"{insight.get('timestamp', '')}-{insight.get('content', '')[:20]}" 565 566 # Skip already-broadcast 567 if insight_id in _broadcast_resonance_ids: 568 continue 569 570 # Check importance threshold 571 if importance >= RESONANCE_IMPORTANCE_THRESHOLD: 572 major_candidates.append({ 573 "time": datetime.fromisoformat(insight.get("timestamp", now.isoformat())), 574 "type": "major_insight", 575 "preview": insight.get("content", "")[:50], 576 "score": importance, 577 "reason": f"importance={importance:.2f}", 578 "resonance_id": insight_id 579 }) 580 581 # Check edge zone (D 0.30-0.50) 582 elif RESONANCE_DIVERGENCE_MIN <= divergence <= RESONANCE_DIVERGENCE_MAX: 583 major_candidates.append({ 584 "time": datetime.fromisoformat(insight.get("timestamp", now.isoformat())), 585 "type": "edge_discovery", 586 "preview": insight.get("content", "")[:50], 587 "score": 0.7 + (0.5 - abs(divergence - 0.4)) * 0.6, # Peak at D=0.4 588 "reason": f"edge_zone D={divergence:.2f}", 589 "resonance_id": insight_id 590 }) 591 except: 592 pass 593 594 # Path 2: Large alignment improvement 595 if state.delta_f <= RESONANCE_DELTA_F_THRESHOLD: 596 major_candidates.append({ 597 "time": now, 598 "type": "alignment_breakthrough", 599 "preview": f"Alignment improved by {abs(state.delta_f):.0%}", 600 "score": min(1.0, abs(state.delta_f) * 5), 601 "reason": f"delta_f={state.delta_f:.2f}" 602 }) 603 604 # Path 3: Convergence alerts in resonance feed 605 for alert in state.resonance_alerts: 606 alert_type = alert.get("type", "") 607 preview = alert.get("preview", "").lower() 608 609 if alert_type == "convergence" or "breakthrough" in preview: 610 major_candidates.append({ 611 "time": alert.get("time", now), 612 "type": "convergence", 613 "preview": alert.get("preview", "")[:50], 614 "score": 0.80, 615 "reason": "convergence_detected" 616 }) 617 618 # Select highest-scoring candidate 619 if major_candidates: 620 best = max(major_candidates, key=lambda x: x.get("score", 0)) 621 state.major_resonance = best 622 623 # Mark as broadcast so we don't repeat 624 if "resonance_id" in best: 625 _broadcast_resonance_ids.add(best["resonance_id"]) 626 627 # Broadcast to mesh 628 self._broadcast_major_resonance(best) 629 _last_major_broadcast = now 630 631 def _broadcast_major_resonance(self, resonance: Dict): 632 """Broadcast major resonance to all mesh peers.""" 633 try: 634 import socket 635 hostname = socket.gethostname() 636 637 msg = json.dumps({ 638 "type": "major_resonance", 639 "from": hostname, 640 "payload": { 641 "time": resonance.get("time", datetime.now()).isoformat() if isinstance(resonance.get("time"), datetime) else str(resonance.get("time", "")), 642 "resonance_type": resonance.get("type", "unknown"), 643 "preview": resonance.get("preview", ""), 644 "score": resonance.get("score", 0), 645 "reason": resonance.get("reason", "") 646 }, 647 "timestamp": datetime.now().isoformat() 648 }).encode('utf-8') 649 650 req = urllib.request.Request( 651 f"{MESH_URL}/broadcast", 652 data=msg, 653 headers={"Content-Type": "application/json"}, 654 method="POST" 655 ) 656 657 with urllib.request.urlopen(req, timeout=3) as resp: 658 pass # Fire and forget 659 660 except Exception as e: 661 pass # Mesh may be unavailable 662 663 664 # ═══════════════════════════════════════════════════════════════════════════ 665 # DISPLAY RENDERING 666 # ═══════════════════════════════════════════════════════════════════════════ 667 668 class FlightDeckRenderer: 669 """Renders the flight deck to terminal.""" 670 671 WIDTH = 80 672 HEIGHT = 35 673 674 def __init__(self): 675 self._frame_count = 0 676 677 def render(self, state: FlightState) -> str: 678 """Render full flight deck.""" 679 lines = [] 680 681 # Major resonance banner (if any) - TOP PRIORITY VISIBILITY 682 if state.major_resonance: 683 lines.extend(self._render_major_resonance(state)) 684 lines.append("") 685 686 # Header 687 lines.extend(self._render_header(state)) 688 lines.append("") 689 690 # Main instruments (top row) 691 lines.extend(self._render_primary_instruments(state)) 692 lines.append("") 693 694 # Economics & Value (new section) 695 lines.extend(self._render_economics(state)) 696 lines.append("") 697 698 # Fleet & Graph (middle row) 699 lines.extend(self._render_fleet_and_graph(state)) 700 lines.append("") 701 702 # Issues / Errors (new section) 703 if state.error_count_session > 0 or state.warning_count_session > 0: 704 lines.extend(self._render_issues(state)) 705 lines.append("") 706 707 # Axiom activity 708 lines.extend(self._render_axioms(state)) 709 lines.append("") 710 711 # Alerts & Resonance 712 lines.extend(self._render_alerts(state)) 713 lines.append("") 714 715 # Footer 716 lines.extend(self._render_footer(state)) 717 718 self._frame_count += 1 719 return "\n".join(lines) 720 721 def _render_header(self, state: FlightState) -> List[str]: 722 C = Colors 723 mesh_status = f"{C.GREEN}MESH{C.RESET}" if state.mesh_online else f"{C.RED}MESH{C.RESET}" 724 fleet_str = f"Fleet: {state.fleet_size}" if state.mesh_online else "SOLO" 725 726 return [ 727 f"{C.CYAN}{C.BOLD}{'═' * self.WIDTH}{C.RESET}", 728 f"{C.CYAN}{C.BOLD} SOVEREIGN FLIGHT DECK{C.RESET} │ {mesh_status} {fleet_str} │ {state.updated.strftime('%H:%M:%S')}", 729 f"{C.CYAN}{C.BOLD}{'═' * self.WIDTH}{C.RESET}", 730 ] 731 732 def _render_primary_instruments(self, state: FlightState) -> List[str]: 733 """Render the main flight instruments.""" 734 C = Colors 735 lines = [] 736 737 # Row 1: Altimeter | Airspeed | Heading | VSI 738 lines.append(f" {C.BOLD}ALTIMETER{C.RESET} {C.BOLD}AIRSPEED{C.RESET} {C.BOLD}HEADING{C.RESET} {C.BOLD}VSI{C.RESET}") 739 740 # Altimeter visualization 741 alt_bar = self._altitude_bar(state.altitude_numeric) 742 alt_label = state.altitude.upper()[:4] 743 744 # Airspeed (pull rate) 745 speed_bar = self._horizontal_bar(state.pull_rate, 10) 746 speed_pct = f"{state.pull_rate * 100:.0f}%" 747 748 # Heading (gravity well) 749 heading = state.heading[:15] if state.heading else "---" 750 751 # VSI (delta F) 752 if state.delta_f < -0.01: 753 vsi = f"{C.GREEN}▲ {abs(state.delta_f):.2f}{C.RESET}" 754 elif state.delta_f > 0.01: 755 vsi = f"{C.RED}▼ {state.delta_f:.2f}{C.RESET}" 756 else: 757 vsi = f"{C.DIM}─ 0.00{C.RESET}" 758 759 lines.append(f" {alt_bar} {alt_label:4} │ {speed_bar} {speed_pct} │ {heading:15} │ {vsi}") 760 761 # Row 2: Attitude (F score) | Fuel (sats) 762 lines.append("") 763 lines.append(f" {C.BOLD}ATTITUDE (F){C.RESET} {C.BOLD}FUEL (sats){C.RESET}") 764 765 # Attitude indicator (F score) 766 attitude = self._attitude_indicator(state.free_energy) 767 768 # Fuel gauge 769 fuel_bar = self._horizontal_bar(min(1.0, state.sats_balance / 500000), 15) 770 fuel_str = f"{state.sats_balance:,}" 771 772 lines.append(f" {attitude} │ {fuel_bar} {fuel_str}") 773 774 return lines 775 776 def _render_fleet_and_graph(self, state: FlightState) -> List[str]: 777 """Render fleet status and graph building.""" 778 C = Colors 779 lines = [] 780 781 lines.append(f" {C.BOLD}FLEET{C.RESET} {C.BOLD}GRAPH{C.RESET}") 782 lines.append(f" {'─' * 30} │ {'─' * 30}") 783 784 # Fleet visualization 785 fleet_icons = [] 786 for i in range(min(5, state.fleet_size)): 787 if i == 0: 788 fleet_icons.append(f"{C.BRIGHT_GREEN}◉{C.RESET}") # Self (lead helicopter) 789 else: 790 fleet_icons.append(f"{C.CYAN}◉{C.RESET}") # Fleet members 791 792 fleet_display = " ".join(fleet_icons) if fleet_icons else f"{C.DIM}solo{C.RESET}" 793 794 # Graph stats 795 node_delta = f"+{state.nodes_added_session}" if state.nodes_added_session > 0 else f"{C.DIM}+0{C.RESET}" 796 edge_delta = f"+{state.edges_added_session}" if state.edges_added_session > 0 else f"{C.DIM}+0{C.RESET}" 797 798 lines.append(f" {fleet_display:30} │ Nodes: {state.graph_nodes:,} ({node_delta})") 799 lines.append(f" {C.DIM}Threads: {state.active_threads}{C.RESET} │ Edges: {state.graph_edges:,} ({edge_delta})") 800 801 # Show focal thread 802 if state.focal_thread: 803 focal = state.focal_thread[:25] 804 lines.append(f" {C.YELLOW}► {focal}{C.RESET}") 805 806 return lines 807 808 def _render_axioms(self, state: FlightState) -> List[str]: 809 """Render axiom activity.""" 810 C = Colors 811 lines = [] 812 813 lines.append(f" {C.BOLD}AXIOM ACTIVITY{C.RESET}") 814 lines.append(f" {'─' * 60}") 815 816 if not state.axiom_activity: 817 lines.append(f" {C.DIM}No axiom activity detected{C.RESET}") 818 return lines 819 820 # Find max for scaling 821 max_val = max(state.axiom_activity.values()) if state.axiom_activity else 1 822 823 axiom_names = { 824 "A0": "Boundary", 825 "A1": "Integration", 826 "A2": "Life", 827 "A3": "Navigation", 828 "A4": "Ergodicity" 829 } 830 831 axiom_colors = { 832 "A0": C.BLUE, 833 "A1": C.GREEN, 834 "A2": C.MAGENTA, 835 "A3": C.YELLOW, 836 "A4": C.RED 837 } 838 839 for axiom in ["A0", "A1", "A2", "A3", "A4"]: 840 val = state.axiom_activity.get(axiom, 0) 841 pct = val / max_val if max_val > 0 else 0 842 bar = self._horizontal_bar(pct, 20) 843 color = axiom_colors.get(axiom, C.WHITE) 844 name = axiom_names.get(axiom, axiom) 845 846 highlight = f"{C.BOLD}" if axiom == state.primary_axiom else "" 847 lines.append(f" {highlight}{color}{axiom}{C.RESET} {name:11} {bar} {val:3}") 848 849 return lines 850 851 def _render_alerts(self, state: FlightState) -> List[str]: 852 """Render resonance alerts.""" 853 C = Colors 854 lines = [] 855 856 lines.append(f" {C.BOLD}RESONANCE ALERTS{C.RESET}") 857 lines.append(f" {'─' * 60}") 858 859 if not state.resonance_alerts: 860 lines.append(f" {C.DIM}No recent resonance{C.RESET}") 861 return lines 862 863 for alert in state.resonance_alerts[:3]: 864 time_str = alert["time"].strftime("%H:%M") 865 alert_type = alert.get("type", "unknown") 866 preview = alert.get("preview", "")[:40] 867 868 if alert_type == "shared_concept": 869 icon = f"{C.BRIGHT_CYAN}◆{C.RESET}" 870 elif alert_type == "convergence": 871 icon = f"{C.BRIGHT_GREEN}◆{C.RESET}" 872 else: 873 icon = f"{C.YELLOW}◆{C.RESET}" 874 875 lines.append(f" {icon} {time_str} {preview}") 876 877 return lines 878 879 def _render_major_resonance(self, state: FlightState) -> List[str]: 880 """Render major resonance banner - high visibility.""" 881 C = Colors 882 lines = [] 883 884 if not state.major_resonance: 885 return lines 886 887 # Animated border (alternates each frame) 888 border_char = "█" if self._frame_count % 2 == 0 else "▓" 889 890 resonance = state.major_resonance 891 res_type = resonance.get("type", "resonance").upper().replace("_", " ") 892 preview = resonance.get("preview", "")[:55] 893 score = resonance.get("score", 0) 894 895 # Color based on type 896 if "breakthrough" in res_type.lower() or "convergence" in res_type.lower(): 897 color = C.BRIGHT_GREEN 898 bg = C.BG_GREEN 899 elif "edge" in res_type.lower(): 900 color = C.BRIGHT_CYAN 901 bg = "" 902 else: 903 color = C.BRIGHT_YELLOW 904 bg = C.BG_YELLOW 905 906 lines.append(f"{color}{border_char * self.WIDTH}{C.RESET}") 907 lines.append(f"{color}{C.BOLD} ◆ MAJOR RESONANCE: {res_type} [{score:.0%}]{C.RESET}") 908 lines.append(f"{color} {preview}{C.RESET}") 909 lines.append(f"{color}{border_char * self.WIDTH}{C.RESET}") 910 911 return lines 912 913 def _render_economics(self, state: FlightState) -> List[str]: 914 """Render economics/value section.""" 915 C = Colors 916 lines = [] 917 918 lines.append(f" {C.BOLD}VALUE CREATED{C.RESET} {C.BOLD}PRODUCTIVITY{C.RESET}") 919 lines.append(f" {'─' * 30} │ {'─' * 30}") 920 921 # Value bar (scale to 500k sats as "full day") 922 value_pct = min(1.0, state.value_created / 500_000) 923 value_bar = self._horizontal_bar(value_pct, 15) 924 value_str = f"{state.value_created:,} sats" 925 926 # Work items and insights 927 items_str = f"{state.work_items_done} items" 928 insights_str = f"{state.insights_captured} insights" 929 930 lines.append(f" {value_bar} {value_str:15} │ {items_str} | {insights_str}") 931 932 # ROI and efficiency 933 roi_str = f"ROI: {state.roi_percent:.0f}%" if state.roi_percent > 0 else "ROI: ---" 934 eff_str = f"{state.efficiency_multiplier:.1f}x API" 935 v_score = f"V-avg: {state.v_score_avg:.2f}" if state.v_score_avg > 0 else "V-avg: ---" 936 937 # Effective rate 938 rate_str = f"${state.effective_rate:.0f}/hr" if state.effective_rate > 0 else "---" 939 940 lines.append(f" {C.GREEN}{roi_str}{C.RESET} | {eff_str} │ {v_score} | {rate_str}") 941 942 return lines 943 944 def _render_issues(self, state: FlightState) -> List[str]: 945 """Render error/issue tracking section.""" 946 C = Colors 947 lines = [] 948 949 # Header with counts 950 error_icon = f"{C.RED}●{C.RESET}" if state.error_count_session > 0 else f"{C.DIM}○{C.RESET}" 951 warn_icon = f"{C.YELLOW}●{C.RESET}" if state.warning_count_session > 0 else f"{C.DIM}○{C.RESET}" 952 953 lines.append(f" {C.BOLD}ISSUES{C.RESET} {error_icon} {state.error_count_session} errors {warn_icon} {state.warning_count_session} warnings") 954 lines.append(f" {'─' * 60}") 955 956 # Issue types breakdown 957 if state.issue_types: 958 type_strs = [f"{k}: {v}" for k, v in state.issue_types.items()] 959 lines.append(f" {C.DIM}Types: {', '.join(type_strs)}{C.RESET}") 960 961 # Recent issues 962 for issue in state.recent_issues[:3]: 963 if "CRITICAL" in issue: 964 lines.append(f" {C.RED}! {issue}{C.RESET}") 965 elif "WARNING" in issue: 966 lines.append(f" {C.YELLOW}⚠ {issue}{C.RESET}") 967 else: 968 lines.append(f" {C.DIM}• {issue}{C.RESET}") 969 970 return lines 971 972 def _render_footer(self, state: FlightState) -> List[str]: 973 C = Colors 974 efficiency = f"{state.efficiency_multiplier:.1f}x" if state.efficiency_multiplier > 0 else "---" 975 976 return [ 977 f"{C.DIM}{'─' * self.WIDTH}{C.RESET}", 978 f"{C.DIM} Efficiency: {efficiency} API │ Frame: {self._frame_count} │ Press Ctrl+C to exit{C.RESET}", 979 ] 980 981 # === VISUALIZATION HELPERS === 982 983 def _altitude_bar(self, value: float) -> str: 984 """Vertical altitude indicator.""" 985 C = Colors 986 levels = ["▁", "▂", "▃", "▄", "▅", "▆", "▇", "█"] 987 idx = min(7, int(value * 8)) 988 return f"{C.CYAN}{levels[idx]}{C.RESET}" 989 990 def _horizontal_bar(self, value: float, width: int = 10) -> str: 991 """Horizontal progress bar.""" 992 C = Colors 993 filled = int(value * width) 994 empty = width - filled 995 996 if value < 0.3: 997 color = C.RED 998 elif value < 0.7: 999 color = C.YELLOW 1000 else: 1001 color = C.GREEN 1002 1003 return f"{color}{'█' * filled}{C.DIM}{'░' * empty}{C.RESET}" 1004 1005 def _attitude_indicator(self, f_score: float) -> str: 1006 """Attitude indicator showing F score as deviation from level.""" 1007 C = Colors 1008 1009 # F = 0 is perfect (level), F = 1 is max deviation 1010 # Show as a horizon line that tilts 1011 1012 if f_score < 0.1: 1013 return f"{C.GREEN} ══════════ {C.RESET} F={f_score:.2f}" 1014 elif f_score < 0.25: 1015 return f"{C.YELLOW} ═══════╱ {C.RESET} F={f_score:.2f}" 1016 elif f_score < 0.5: 1017 return f"{C.YELLOW} ════╱══ {C.RESET} F={f_score:.2f}" 1018 else: 1019 return f"{C.RED} ═╱═══════ {C.RESET} F={f_score:.2f}" 1020 1021 1022 # ═══════════════════════════════════════════════════════════════════════════ 1023 # STREAMING LOOP 1024 # ═══════════════════════════════════════════════════════════════════════════ 1025 1026 class FlightDeck: 1027 """Main flight deck controller.""" 1028 1029 def __init__(self, refresh_rate: float = 2.0): 1030 self.collector = FlightDataCollector() 1031 self.renderer = FlightDeckRenderer() 1032 self.refresh_rate = refresh_rate 1033 self._running = False 1034 1035 def run(self): 1036 """Run the streaming dashboard.""" 1037 self._running = True 1038 1039 # Handle Ctrl+C gracefully 1040 def signal_handler(sig, frame): 1041 self._running = False 1042 1043 signal.signal(signal.SIGINT, signal_handler) 1044 1045 hide_cursor() 1046 try: 1047 while self._running: 1048 # Collect state 1049 state = self.collector.collect() 1050 1051 # Render 1052 output = self.renderer.render(state) 1053 1054 # Display 1055 clear_screen() 1056 print(output) 1057 1058 # Wait 1059 time.sleep(self.refresh_rate) 1060 1061 finally: 1062 show_cursor() 1063 print("\n\nFlight deck closed.") 1064 1065 def one_shot(self) -> str: 1066 """Single status display (non-streaming).""" 1067 state = self.collector.collect() 1068 return self.renderer.render(state) 1069 1070 1071 # ═══════════════════════════════════════════════════════════════════════════ 1072 # CLI 1073 # ═══════════════════════════════════════════════════════════════════════════ 1074 1075 def main(): 1076 import argparse 1077 1078 parser = argparse.ArgumentParser(description="Sovereign Flight Deck") 1079 parser.add_argument("--minimal", "-m", action="store_true", help="One-shot minimal display") 1080 parser.add_argument("--refresh", "-r", type=float, default=2.0, help="Refresh rate in seconds") 1081 parser.add_argument("--alerts", "-a", action="store_true", help="Show only resonance alerts") 1082 1083 args = parser.parse_args() 1084 1085 deck = FlightDeck(refresh_rate=args.refresh) 1086 1087 if args.minimal: 1088 print(deck.one_shot()) 1089 elif args.alerts: 1090 # Just watch alerts 1091 state = deck.collector.collect() 1092 print(f"Resonance Alerts (last 5 minutes):") 1093 print("-" * 40) 1094 for alert in state.resonance_alerts: 1095 print(f" {alert['time'].strftime('%H:%M:%S')} - {alert.get('preview', '')}") 1096 else: 1097 deck.run() 1098 1099 1100 if __name__ == "__main__": 1101 main()