attention_tracker.py
1 #!/usr/bin/env python3 2 """ 3 Cross-Session Attention Tracker CLI 4 5 View and manage attention tracking across all sessions for the day. 6 Integrates eye tracking (if available) with attention economics. 7 8 Usage: 9 python3 scripts/attention_tracker.py # Show today's summary 10 python3 scripts/attention_tracker.py --live # Live view (updates every 30s) 11 python3 scripts/attention_tracker.py --sessions # List all tracked sessions 12 python3 scripts/attention_tracker.py --daily # Show daily ledger 13 """ 14 15 import sys 16 import json 17 import time 18 from pathlib import Path 19 from datetime import datetime, date 20 21 # Add project root to path 22 SOVEREIGN_OS = Path(__file__).parent.parent 23 sys.path.insert(0, str(SOVEREIGN_OS)) 24 25 26 def load_daily_ledger() -> dict: 27 """Load today's attention ledger.""" 28 ledger_dir = Path.home() / ".sovereign" / "attention-ledger" 29 today = date.today().isoformat() 30 ledger_path = ledger_dir / f"{today}.yaml" 31 32 if not ledger_path.exists(): 33 # Try JSON 34 ledger_path = ledger_dir / f"{today}.json" 35 36 if not ledger_path.exists(): 37 return None 38 39 with open(ledger_path) as f: 40 content = f.read() 41 42 try: 43 import yaml 44 return yaml.safe_load(content) 45 except ImportError: 46 try: 47 return json.loads(content) 48 except: 49 return None 50 51 52 def format_duration(hours: float) -> str: 53 """Format hours as HH:MM.""" 54 h = int(hours) 55 m = int((hours - h) * 60) 56 return f"{h}:{m:02d}" 57 58 59 def show_summary(): 60 """Show today's attention summary.""" 61 ledger = load_daily_ledger() 62 63 print() 64 print("╔══════════════════════════════════════════════════════════════════╗") 65 print("║ CROSS-SESSION ATTENTION TRACKING ║") 66 print("║ Today's Summary ║") 67 print("╚══════════════════════════════════════════════════════════════════╝") 68 print() 69 70 if not ledger: 71 print("No attention data for today.") 72 print("Attention tracking starts when sessions are recorded.") 73 print() 74 print("To record a session:") 75 print(" python3 scripts/costs.py --record 'description' v_score duration_min") 76 return 77 78 # Budget info 79 budget = ledger.get('budget', {}) 80 usage = ledger.get('usage', {}) 81 82 print(f"Date: {ledger.get('date', 'unknown')}") 83 print() 84 85 print("┌─────────────────────────────────────────────────────────────────┐") 86 print("│ ATTENTION BUDGET │") 87 print("├─────────────────────────────────────────────────────────────────┤") 88 print(f"│ Budget: {budget.get('total_hours', 8):.1f} hours = {budget.get('total_sats', 0):,} sats") 89 print(f"│ Used: {usage.get('hours_used', 0):.2f} hours = {usage.get('sats_used', 0):,} sats") 90 print(f"│ Remaining: {usage.get('remaining_hours', 0):.2f} hours = {usage.get('remaining_sats', 0):,} sats") 91 print(f"│ Utilization: {usage.get('utilization', 0)*100:.1f}%") 92 print("└─────────────────────────────────────────────────────────────────┘") 93 print() 94 95 # Sessions 96 sessions = ledger.get('sessions', {}) 97 if sessions: 98 print("┌─────────────────────────────────────────────────────────────────┐") 99 print("│ SESSIONS │") 100 print("├─────────────────────────────────────────────────────────────────┤") 101 102 # Sort by duration 103 sorted_sessions = sorted( 104 sessions.items(), 105 key=lambda x: x[1].get('duration_hours', 0), 106 reverse=True 107 ) 108 109 for sid, sdata in sorted_sessions[:10]: 110 hours = sdata.get('duration_hours', 0) 111 sats = sdata.get('sats_allocated', 0) 112 active = "◉" if sdata.get('active') else "○" 113 context = sdata.get('context', 'unknown')[:12] 114 v_weighted = sdata.get('weighted_value', 0) 115 116 print(f"│ {active} {sid[:18]:18} | {format_duration(hours):>5} | {sats:>8,} sats | V={v_weighted:.2f} | {context}") 117 118 print("└─────────────────────────────────────────────────────────────────┘") 119 120 # Work items 121 total_items = sum( 122 sdata.get('work_items_count', len(sdata.get('work_items', []))) 123 for sdata in sessions.values() 124 ) 125 if total_items > 0: 126 print() 127 print(f"Total work items tracked: {total_items}") 128 129 print() 130 131 132 def show_sessions(): 133 """Show detailed session list.""" 134 ledger = load_daily_ledger() 135 136 if not ledger: 137 print("No sessions tracked today.") 138 return 139 140 sessions = ledger.get('sessions', {}) 141 142 print() 143 print("Sessions tracked today:") 144 print() 145 146 for sid, sdata in sessions.items(): 147 print(f" {sid}") 148 print(f" Context: {sdata.get('context', 'unknown')}") 149 print(f" Duration: {sdata.get('duration_hours', 0):.2f} hours") 150 print(f" Sats: {sdata.get('sats_allocated', 0):,}") 151 print(f" Active: {sdata.get('active', False)}") 152 153 work_items = sdata.get('work_items', []) 154 if work_items: 155 print(f" Work items: {len(work_items)}") 156 for item in work_items[-3:]: # Last 3 157 v = item.get('v_score', 0) 158 desc = item.get('description', '')[:40] 159 print(f" - V={v:.2f} | {desc}") 160 161 print() 162 163 164 def show_daily_ledger(): 165 """Show the raw daily ledger.""" 166 ledger = load_daily_ledger() 167 168 if not ledger: 169 print("No ledger for today.") 170 return 171 172 print(json.dumps(ledger, indent=2, default=str)) 173 174 175 def live_view(): 176 """Show live updating view.""" 177 print("Live attention tracking (Ctrl+C to exit)") 178 print() 179 180 try: 181 while True: 182 # Clear screen (works on most terminals) 183 print("\033[H\033[J", end="") 184 185 show_summary() 186 187 print(f"Last updated: {datetime.now().strftime('%H:%M:%S')}") 188 print("Refreshing in 30 seconds... (Ctrl+C to exit)") 189 190 time.sleep(30) 191 except KeyboardInterrupt: 192 print("\nExited.") 193 194 195 if __name__ == "__main__": 196 if len(sys.argv) > 1: 197 arg = sys.argv[1] 198 199 if arg == "--live": 200 live_view() 201 elif arg == "--sessions": 202 show_sessions() 203 elif arg == "--daily": 204 show_daily_ledger() 205 elif arg in ["--help", "-h"]: 206 print(__doc__) 207 else: 208 print(f"Unknown option: {arg}") 209 print(__doc__) 210 else: 211 show_summary()