/ scripts / attention_tracker.py
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()