aline-ai 0.5.4__py3-none-any.whl → 0.5.5__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 (79) hide show
  1. {aline_ai-0.5.4.dist-info → aline_ai-0.5.5.dist-info}/METADATA +1 -1
  2. aline_ai-0.5.5.dist-info/RECORD +93 -0
  3. realign/__init__.py +1 -1
  4. realign/adapters/antigravity.py +28 -20
  5. realign/adapters/base.py +46 -50
  6. realign/adapters/claude.py +14 -14
  7. realign/adapters/codex.py +7 -7
  8. realign/adapters/gemini.py +11 -11
  9. realign/adapters/registry.py +14 -10
  10. realign/claude_detector.py +2 -2
  11. realign/claude_hooks/__init__.py +3 -3
  12. realign/claude_hooks/permission_request_hook_installer.py +31 -32
  13. realign/claude_hooks/stop_hook.py +4 -1
  14. realign/claude_hooks/stop_hook_installer.py +30 -31
  15. realign/cli.py +7 -0
  16. realign/codex_detector.py +11 -11
  17. realign/commands/add.py +88 -65
  18. realign/commands/config.py +3 -12
  19. realign/commands/context.py +3 -1
  20. realign/commands/export_shares.py +86 -127
  21. realign/commands/import_shares.py +145 -155
  22. realign/commands/init.py +166 -30
  23. realign/commands/restore.py +18 -6
  24. realign/commands/search.py +14 -42
  25. realign/commands/upgrade.py +155 -11
  26. realign/commands/watcher.py +98 -219
  27. realign/commands/worker.py +29 -6
  28. realign/config.py +25 -20
  29. realign/context.py +1 -3
  30. realign/dashboard/app.py +4 -4
  31. realign/dashboard/screens/create_event.py +3 -1
  32. realign/dashboard/screens/event_detail.py +14 -6
  33. realign/dashboard/screens/session_detail.py +3 -1
  34. realign/dashboard/screens/share_import.py +7 -3
  35. realign/dashboard/tmux_manager.py +54 -9
  36. realign/dashboard/widgets/config_panel.py +85 -1
  37. realign/dashboard/widgets/events_table.py +3 -1
  38. realign/dashboard/widgets/header.py +1 -0
  39. realign/dashboard/widgets/search_panel.py +37 -27
  40. realign/dashboard/widgets/sessions_table.py +24 -15
  41. realign/dashboard/widgets/terminal_panel.py +66 -22
  42. realign/dashboard/widgets/watcher_panel.py +6 -2
  43. realign/dashboard/widgets/worker_panel.py +10 -1
  44. realign/db/__init__.py +1 -1
  45. realign/db/base.py +5 -15
  46. realign/db/locks.py +0 -1
  47. realign/db/migration.py +82 -76
  48. realign/db/schema.py +2 -6
  49. realign/db/sqlite_db.py +23 -41
  50. realign/events/__init__.py +0 -1
  51. realign/events/event_summarizer.py +27 -15
  52. realign/events/session_summarizer.py +29 -15
  53. realign/file_lock.py +1 -0
  54. realign/hooks.py +150 -60
  55. realign/logging_config.py +12 -15
  56. realign/mcp_server.py +30 -51
  57. realign/mcp_watcher.py +0 -1
  58. realign/models/event.py +29 -20
  59. realign/prompts/__init__.py +7 -7
  60. realign/prompts/presets.py +15 -11
  61. realign/redactor.py +99 -59
  62. realign/triggers/__init__.py +9 -9
  63. realign/triggers/antigravity_trigger.py +30 -28
  64. realign/triggers/base.py +4 -3
  65. realign/triggers/claude_trigger.py +104 -85
  66. realign/triggers/codex_trigger.py +15 -5
  67. realign/triggers/gemini_trigger.py +57 -47
  68. realign/triggers/next_turn_trigger.py +3 -1
  69. realign/triggers/registry.py +6 -2
  70. realign/triggers/turn_status.py +3 -1
  71. realign/watcher_core.py +306 -131
  72. realign/watcher_daemon.py +8 -8
  73. realign/worker_core.py +3 -1
  74. realign/worker_daemon.py +3 -1
  75. aline_ai-0.5.4.dist-info/RECORD +0 -93
  76. {aline_ai-0.5.4.dist-info → aline_ai-0.5.5.dist-info}/WHEEL +0 -0
  77. {aline_ai-0.5.4.dist-info → aline_ai-0.5.5.dist-info}/entry_points.txt +0 -0
  78. {aline_ai-0.5.4.dist-info → aline_ai-0.5.5.dist-info}/licenses/LICENSE +0 -0
  79. {aline_ai-0.5.4.dist-info → aline_ai-0.5.5.dist-info}/top_level.txt +0 -0
@@ -72,9 +72,7 @@ def detect_watcher_process() -> tuple[bool, int | None, str]:
72
72
  # Then check for orphaned daemon process (PID file missing)
73
73
  limited_detection = False
74
74
  try:
75
- ps_output = subprocess.run(
76
- ["ps", "aux"], capture_output=True, text=True, timeout=2
77
- )
75
+ ps_output = subprocess.run(["ps", "aux"], capture_output=True, text=True, timeout=2)
78
76
  if ps_output.returncode == 0:
79
77
  for line in ps_output.stdout.split("\n"):
80
78
  if "watcher_daemon.py" in line and "grep" not in line:
@@ -118,9 +116,7 @@ def detect_all_watcher_processes() -> list[tuple[int, str]]:
118
116
 
119
117
  try:
120
118
  # Use ps to find all watcher_daemon.py processes
121
- ps_output = subprocess.run(
122
- ["ps", "aux"], capture_output=True, text=True, timeout=2
123
- )
119
+ ps_output = subprocess.run(["ps", "aux"], capture_output=True, text=True, timeout=2)
124
120
 
125
121
  if ps_output.returncode == 0:
126
122
  for line in ps_output.stdout.split("\n"):
@@ -303,10 +299,7 @@ def _count_claude_turns(session_file: Path) -> int:
303
299
  is_tool_result = False
304
300
  if isinstance(content, list):
305
301
  for item in content:
306
- if (
307
- isinstance(item, dict)
308
- and item.get("type") == "tool_result"
309
- ):
302
+ if isinstance(item, dict) and item.get("type") == "tool_result":
310
303
  is_tool_result = True
311
304
  break
312
305
 
@@ -417,9 +410,7 @@ def get_all_tracked_sessions() -> List[Dict]:
417
410
  session_details.append(details)
418
411
 
419
412
  # Sort by mtime (most recent first)
420
- session_details.sort(
421
- key=lambda x: x["mtime"] if x["mtime"] else datetime.min, reverse=True
422
- )
413
+ session_details.sort(key=lambda x: x["mtime"] if x["mtime"] else datetime.min, reverse=True)
423
414
 
424
415
  return session_details
425
416
  except Exception as e:
@@ -461,9 +452,7 @@ def watcher_status_command(verbose: bool = False) -> int:
461
452
  console.print(f"Mode: Standalone (SQLite)")
462
453
 
463
454
  # Active Sessions (New!)
464
- console.print(
465
- f"\n[bold cyan]Active Sessions (Currently Monitoring)[/bold cyan]"
466
- )
455
+ console.print(f"\n[bold cyan]Active Sessions (Currently Monitoring)[/bold cyan]")
467
456
  try:
468
457
  from ..hooks import find_all_active_sessions
469
458
  from ..adapters import get_adapter_registry
@@ -475,9 +464,7 @@ def watcher_status_command(verbose: bool = False) -> int:
475
464
  for s in active_sessions[:5]: # Show top 5
476
465
  adapter = registry.auto_detect_adapter(s)
477
466
  source = adapter.name.capitalize() if adapter else "Unknown"
478
- mtime = datetime.fromtimestamp(s.stat().st_mtime).strftime(
479
- "%Y-%m-%d %H:%M:%S"
480
- )
467
+ mtime = datetime.fromtimestamp(s.stat().st_mtime).strftime("%Y-%m-%d %H:%M:%S")
481
468
 
482
469
  # Try to get project name
483
470
  project_name = "-"
@@ -512,12 +499,14 @@ def watcher_status_command(verbose: bool = False) -> int:
512
499
  # Recent Sessions (3)
513
500
  console.print(f"\n[bold]Recent Sessions[/bold]")
514
501
  sessions = list(
515
- conn.execute("""
502
+ conn.execute(
503
+ """
516
504
  SELECT id, session_type, workspace_path, last_activity_at
517
505
  FROM sessions
518
506
  ORDER BY last_activity_at DESC
519
507
  LIMIT 3
520
- """)
508
+ """
509
+ )
521
510
  )
522
511
 
523
512
  if sessions:
@@ -555,23 +544,23 @@ def watcher_status_command(verbose: bool = False) -> int:
555
544
  except:
556
545
  pass
557
546
 
558
- console.print(
559
- f" {session_name} | {source} | {workspace} | {last_activity}"
560
- )
547
+ console.print(f" {session_name} | {source} | {workspace} | {last_activity}")
561
548
  else:
562
549
  console.print(f" [dim](no sessions yet)[/dim]")
563
550
 
564
551
  # Recent Conversations (5)
565
552
  console.print(f"\n[bold]Recent Conversations[/bold]")
566
553
  turns = list(
567
- conn.execute("""
554
+ conn.execute(
555
+ """
568
556
  SELECT t.turn_number, t.llm_title, t.timestamp,
569
557
  s.id, s.session_type, s.workspace_path
570
558
  FROM turns t
571
559
  JOIN sessions s ON t.session_id = s.id
572
560
  ORDER BY t.timestamp DESC
573
561
  LIMIT 5
574
- """)
562
+ """
563
+ )
575
564
  )
576
565
 
577
566
  if turns:
@@ -647,9 +636,7 @@ def watcher_status_command(verbose: bool = False) -> int:
647
636
 
648
637
  # Suggestions
649
638
  if status == "Stopped":
650
- console.print(
651
- f"\n[dim]Run 'aline watcher start' to start the watcher[/dim]"
652
- )
639
+ console.print(f"\n[dim]Run 'aline watcher start' to start the watcher[/dim]")
653
640
 
654
641
  console.print()
655
642
  return 0
@@ -675,9 +662,7 @@ def watcher_start_command() -> int:
675
662
  is_running, pid, mode = detect_watcher_process()
676
663
 
677
664
  if is_running:
678
- console.print(
679
- f"[yellow]Watcher is already running (PID: {pid}, mode: {mode})[/yellow]"
680
- )
665
+ console.print(f"[yellow]Watcher is already running (PID: {pid}, mode: {mode})[/yellow]")
681
666
  console.print(f"[dim]Use 'aline watcher stop' to stop it first[/dim]")
682
667
  return 0
683
668
 
@@ -724,9 +709,7 @@ def watcher_start_command() -> int:
724
709
 
725
710
  if is_running:
726
711
  console.print(f"[green]✓ Watcher started successfully (PID: {pid})[/green]")
727
- console.print(
728
- f"[dim]Logs: {log_dir}/watcher_*.log, {log_dir}/watcher_core.log[/dim]"
729
- )
712
+ console.print(f"[dim]Logs: {log_dir}/watcher_*.log, {log_dir}/watcher_core.log[/dim]")
730
713
 
731
714
  # Ensure worker is running (turn/session summaries are processed by the worker).
732
715
  try:
@@ -769,9 +752,7 @@ def watcher_stop_command() -> int:
769
752
  # Display all processes that will be stopped
770
753
  if len(all_processes) == 1:
771
754
  pid, mode = all_processes[0]
772
- console.print(
773
- f"[cyan]Stopping watcher (PID: {pid}, mode: {mode})...[/cyan]"
774
- )
755
+ console.print(f"[cyan]Stopping watcher (PID: {pid}, mode: {mode})...[/cyan]")
775
756
  else:
776
757
  console.print(
777
758
  f"[cyan]Found {len(all_processes)} watcher processes, stopping all...[/cyan]"
@@ -854,9 +835,7 @@ def watcher_fresh_command() -> int:
854
835
 
855
836
  # If stop failed, don't try to start
856
837
  if stop_exit_code != 0:
857
- console.print(
858
- f"\n[red]✗ Failed to stop watcher, aborting restart[/red]"
859
- )
838
+ console.print(f"\n[red]✗ Failed to stop watcher, aborting restart[/red]")
860
839
  return stop_exit_code
861
840
 
862
841
  console.print() # Add blank line for readability
@@ -959,9 +938,7 @@ def _get_imported_sessions(db, exclude_session_ids: set) -> list:
959
938
  # Only include sessions that are imported via import_shares command
960
939
  # Check for: 1) metadata.source == 'share_import' OR 2) session_type == 'imported'
961
940
  # AND file_path is '.' (from Path(''))
962
- file_path_str = (
963
- str(session.session_file_path) if session.session_file_path else ""
964
- )
941
+ file_path_str = str(session.session_file_path) if session.session_file_path else ""
965
942
 
966
943
  # Check if file path indicates imported session
967
944
  has_no_file = not session.session_file_path or file_path_str in ("", ".")
@@ -1026,9 +1003,9 @@ def _get_imported_sessions(db, exclude_session_ids: set) -> list:
1026
1003
  "session_id": session.id,
1027
1004
  "source": session.session_type or "imported",
1028
1005
  "project_name": project_name,
1029
- "project_path": Path(session.workspace_path)
1030
- if session.workspace_path
1031
- else None,
1006
+ "project_path": (
1007
+ Path(session.workspace_path) if session.workspace_path else None
1008
+ ),
1032
1009
  "progress_source": "db",
1033
1010
  "created_at": session.created_at, # Use import time, not original session start time
1034
1011
  "last_activity": session.last_activity_at,
@@ -1114,9 +1091,7 @@ def _get_session_tracking_status_batch(
1114
1091
  stat = session_file.stat()
1115
1092
  mtime = stat.st_mtime
1116
1093
  last_activity = datetime.fromtimestamp(stat.st_mtime)
1117
- created_at = datetime.fromtimestamp(
1118
- getattr(stat, "st_birthtime", stat.st_ctime)
1119
- )
1094
+ created_at = datetime.fromtimestamp(getattr(stat, "st_birthtime", stat.st_ctime))
1120
1095
  except Exception:
1121
1096
  mtime = None
1122
1097
  last_activity = datetime.now()
@@ -1266,9 +1241,7 @@ def _get_session_tracking_status_batch(
1266
1241
  return session_infos
1267
1242
 
1268
1243
 
1269
- def _get_session_tracking_status(
1270
- session_file: Path, config: ReAlignConfig, db=None
1271
- ) -> dict:
1244
+ def _get_session_tracking_status(session_file: Path, config: ReAlignConfig, db=None) -> dict:
1272
1245
  """
1273
1246
  Get tracking status for a session file.
1274
1247
 
@@ -1335,9 +1308,7 @@ def _get_session_tracking_status(
1335
1308
  stat = session_file.stat()
1336
1309
  last_activity = datetime.fromtimestamp(stat.st_mtime)
1337
1310
  # Use birthtime on macOS, fallback to ctime
1338
- created_at = datetime.fromtimestamp(
1339
- getattr(stat, "st_birthtime", stat.st_ctime)
1340
- )
1311
+ created_at = datetime.fromtimestamp(getattr(stat, "st_birthtime", stat.st_ctime))
1341
1312
  except Exception:
1342
1313
  last_activity = datetime.now()
1343
1314
  created_at = datetime.now()
@@ -1357,9 +1328,7 @@ def _get_session_tracking_status(
1357
1328
  }
1358
1329
 
1359
1330
 
1360
- def _get_sorted_session_infos(
1361
- detect_turns: bool = False, include_empty: bool = True
1362
- ) -> List[dict]:
1331
+ def _get_sorted_session_infos(detect_turns: bool = False, include_empty: bool = True) -> List[dict]:
1363
1332
  """
1364
1333
  Get sorted session infos list - shared logic between session list and event generate.
1365
1334
 
@@ -1382,9 +1351,7 @@ def _get_sorted_session_infos(
1382
1351
  # Get status for each session (DB is optional; if missing, treat as empty).
1383
1352
  db = None
1384
1353
  try:
1385
- env_db_path = os.getenv("REALIGN_SQLITE_DB_PATH") or os.getenv(
1386
- "REALIGN_DB_PATH"
1387
- )
1354
+ env_db_path = os.getenv("REALIGN_SQLITE_DB_PATH") or os.getenv("REALIGN_DB_PATH")
1388
1355
  resolved_db_path = Path(env_db_path or config.sqlite_db_path).expanduser()
1389
1356
  if resolved_db_path.exists():
1390
1357
  from ..db import get_database
@@ -1484,9 +1451,7 @@ def watcher_session_list_command(
1484
1451
  if records:
1485
1452
  try:
1486
1453
  config = ReAlignConfig.load()
1487
- env_db_path = os.getenv("REALIGN_SQLITE_DB_PATH") or os.getenv(
1488
- "REALIGN_DB_PATH"
1489
- )
1454
+ env_db_path = os.getenv("REALIGN_SQLITE_DB_PATH") or os.getenv("REALIGN_DB_PATH")
1490
1455
  resolved_db_path = Path(env_db_path or config.sqlite_db_path).expanduser()
1491
1456
  if resolved_db_path.exists():
1492
1457
  from ..db import get_database
@@ -1562,24 +1527,18 @@ def watcher_session_list_command(
1562
1527
  "session_title": info.get("session_title"),
1563
1528
  "session_summary": info.get("session_summary"),
1564
1529
  "creator_name": info.get("creator_name"),
1565
- "session_file": str(info.get("session_file"))
1566
- if info.get("session_file")
1567
- else None,
1530
+ "session_file": (
1531
+ str(info.get("session_file")) if info.get("session_file") else None
1532
+ ),
1568
1533
  }
1569
1534
  # Add turn records if requested
1570
1535
  if records:
1571
- session_data["turns"] = session_turns_map.get(
1572
- info["session_id"], []
1573
- )
1536
+ session_data["turns"] = session_turns_map.get(info["session_id"], [])
1574
1537
  json_sessions.append(session_data)
1575
1538
 
1576
1539
  # Count by status
1577
- tracked_count = sum(
1578
- 1 for info in session_infos if info["status"] == "tracked"
1579
- )
1580
- partial_count = sum(
1581
- 1 for info in session_infos if info["status"] == "partial"
1582
- )
1540
+ tracked_count = sum(1 for info in session_infos if info["status"] == "tracked")
1541
+ partial_count = sum(1 for info in session_infos if info["status"] == "partial")
1583
1542
  new_count = sum(1 for info in session_infos if info["status"] == "new")
1584
1543
 
1585
1544
  output = {
@@ -1600,17 +1559,13 @@ def watcher_session_list_command(
1600
1559
  partial_count = sum(1 for info in session_infos if info["status"] == "partial")
1601
1560
  new_count = sum(1 for info in session_infos if info["status"] == "new")
1602
1561
 
1603
- console.print(
1604
- f"\n[bold]Discovered Sessions ({len(session_infos)} total):[/bold]"
1605
- )
1562
+ console.print(f"\n[bold]Discovered Sessions ({len(session_infos)} total):[/bold]")
1606
1563
  if detect_turns:
1607
1564
  console.print(
1608
1565
  f" [green]{tracked_count} tracked[/green], [yellow]{partial_count} partial[/yellow], [dim]{new_count} new[/dim]\n"
1609
1566
  )
1610
1567
  else:
1611
- console.print(
1612
- f" [green]{tracked_count} tracked[/green], [dim]{new_count} new[/dim]\n"
1613
- )
1568
+ console.print(f" [green]{tracked_count} tracked[/green], [dim]{new_count} new[/dim]\n")
1614
1569
 
1615
1570
  import math
1616
1571
 
@@ -1622,9 +1577,7 @@ def watcher_session_list_command(
1622
1577
 
1623
1578
  start_index = (page - 1) * per_page
1624
1579
  if start_index >= total_sessions:
1625
- console.print(
1626
- f"[red]Error: page {page} is out of range (1-{total_pages})[/red]"
1627
- )
1580
+ console.print(f"[red]Error: page {page} is out of range (1-{total_pages})[/red]")
1628
1581
  console.print("[dim]Tip: use --page 1 to start from the beginning[/dim]")
1629
1582
  return 1
1630
1583
 
@@ -1656,9 +1609,7 @@ def watcher_session_list_command(
1656
1609
  "new": "[dim]new[/dim]",
1657
1610
  }.get(info["status"], info["status"])
1658
1611
 
1659
- created_str = info["created_at"].strftime(
1660
- "%m-%d %H:%M"
1661
- ) # Shorter date format
1612
+ created_str = info["created_at"].strftime("%m-%d %H:%M") # Shorter date format
1662
1613
  activity_str = _format_relative_time(info["last_activity"])
1663
1614
 
1664
1615
  # Truncate session ID if too long (show enough for UUID matching)
@@ -1736,24 +1687,14 @@ def watcher_session_list_command(
1736
1687
  )
1737
1688
  if total_pages > 1:
1738
1689
  if page > 1:
1739
- console.print(
1740
- f"[dim]Prev page: aline watcher session list --page {page - 1}[/dim]"
1741
- )
1690
+ console.print(f"[dim]Prev page: aline watcher session list --page {page - 1}[/dim]")
1742
1691
  if page < total_pages:
1743
- console.print(
1744
- f"[dim]Next page: aline watcher session list --page {page + 1}[/dim]"
1745
- )
1692
+ console.print(f"[dim]Next page: aline watcher session list --page {page + 1}[/dim]")
1746
1693
  console.print("[dim]Tip: adjust page size with --per-page[/dim]")
1747
1694
 
1748
- console.print(
1749
- "[dim]Import: aline watcher session import 1 (by number)[/dim]"
1750
- )
1751
- console.print(
1752
- "[dim] aline watcher session import 1-10 (range)[/dim]"
1753
- )
1754
- console.print(
1755
- "[dim] aline watcher session import <id> (by session ID)[/dim]"
1756
- )
1695
+ console.print("[dim]Import: aline watcher session import 1 (by number)[/dim]")
1696
+ console.print("[dim] aline watcher session import 1-10 (range)[/dim]")
1697
+ console.print("[dim] aline watcher session import <id> (by session ID)[/dim]")
1757
1698
  console.print()
1758
1699
 
1759
1700
  return 0
@@ -1764,9 +1705,7 @@ def watcher_session_list_command(
1764
1705
  return 1
1765
1706
 
1766
1707
 
1767
- def watcher_event_generate_command(
1768
- session_selector: str, show_sessions: bool = False
1769
- ) -> int:
1708
+ def watcher_event_generate_command(session_selector: str, show_sessions: bool = False) -> int:
1770
1709
  """
1771
1710
  Generate an event from selected sessions.
1772
1711
 
@@ -1801,16 +1740,12 @@ def watcher_event_generate_command(
1801
1740
  session_infos = _get_sorted_session_infos(detect_turns=False)
1802
1741
  if not session_infos:
1803
1742
  console.print("[yellow]No sessions discovered.[/yellow]")
1804
- console.print(
1805
- "[dim]Use 'aline watcher session import' to import sessions first.[/dim]"
1806
- )
1743
+ console.print("[dim]Use 'aline watcher session import' to import sessions first.[/dim]")
1807
1744
  return 1
1808
1745
 
1809
1746
  # If selector is "list", show available sessions
1810
1747
  if session_selector.lower() == "list" or show_sessions:
1811
- console.print(
1812
- f"\n[bold]Sessions ({len(session_infos)} available):[/bold]\n"
1813
- )
1748
+ console.print(f"\n[bold]Sessions ({len(session_infos)} available):[/bold]\n")
1814
1749
  table = Table(show_header=True, header_style="bold", box=None)
1815
1750
  table.add_column("#", justify="right", style="cyan", no_wrap=True)
1816
1751
  table.add_column("SESSION ID", no_wrap=True)
@@ -1823,7 +1758,9 @@ def watcher_event_generate_command(
1823
1758
  sid = info["session_id"]
1824
1759
  session_id_display = sid[:16] + "..." if len(sid) > 16 else sid
1825
1760
  status = info["status"]
1826
- status_style = {"tracked": "green", "partial": "yellow", "new": "dim"}.get(status, "")
1761
+ status_style = {"tracked": "green", "partial": "yellow", "new": "dim"}.get(
1762
+ status, ""
1763
+ )
1827
1764
  title = info.get("session_title") or "-"
1828
1765
  if len(title) > 50:
1829
1766
  title = title[:47] + "..."
@@ -1840,7 +1777,9 @@ def watcher_event_generate_command(
1840
1777
  console.print()
1841
1778
  console.print("[dim]Usage: aline watcher event generate <selector>[/dim]")
1842
1779
  console.print("[dim]Examples: 1-3, abc123de, abc123de,def456gh[/dim]")
1843
- console.print("[dim]Note: Only 'tracked' sessions can be used to generate events.[/dim]")
1780
+ console.print(
1781
+ "[dim]Note: Only 'tracked' sessions can be used to generate events.[/dim]"
1782
+ )
1844
1783
  return 0
1845
1784
 
1846
1785
  # Check if selector is a UUID (or UUID prefix) for sessions
@@ -1851,9 +1790,7 @@ def watcher_event_generate_command(
1851
1790
 
1852
1791
  if not indices:
1853
1792
  console.print(f"[red]Invalid session selector: {session_selector}[/red]")
1854
- console.print(
1855
- f"[dim]Valid range: 1-{len(session_infos)}, or session UUID/prefix[/dim]"
1856
- )
1793
+ console.print(f"[dim]Valid range: 1-{len(session_infos)}, or session UUID/prefix[/dim]")
1857
1794
  return 1
1858
1795
 
1859
1796
  # Validate indices
@@ -1872,7 +1809,9 @@ def watcher_event_generate_command(
1872
1809
  console.print("[red]Cannot generate event: some sessions are not tracked.[/red]")
1873
1810
  for info in non_tracked:
1874
1811
  console.print(f" • {info['session_id'][:16]}... (status: new)")
1875
- console.print("[dim]Use 'aline watcher session import' to import these sessions first.[/dim]")
1812
+ console.print(
1813
+ "[dim]Use 'aline watcher session import' to import these sessions first.[/dim]"
1814
+ )
1876
1815
  return 1
1877
1816
 
1878
1817
  # Get SessionRecord objects from database for the selected sessions
@@ -1883,9 +1822,7 @@ def watcher_event_generate_command(
1883
1822
  console.print("[red]Failed to retrieve session details from database.[/red]")
1884
1823
  return 1
1885
1824
 
1886
- console.print(
1887
- f"\n[bold]Creating event from {len(selected_sessions)} session(s):[/bold]"
1888
- )
1825
+ console.print(f"\n[bold]Creating event from {len(selected_sessions)} session(s):[/bold]")
1889
1826
  for s in selected_sessions:
1890
1827
  title = s.session_title or s.id[:16]
1891
1828
  console.print(f" • {title}")
@@ -1900,9 +1837,7 @@ def watcher_event_generate_command(
1900
1837
 
1901
1838
  # Calculate time range from sessions
1902
1839
  start_times = [s.started_at for s in selected_sessions if s.started_at]
1903
- end_times = [
1904
- s.last_activity_at for s in selected_sessions if s.last_activity_at
1905
- ]
1840
+ end_times = [s.last_activity_at for s in selected_sessions if s.last_activity_at]
1906
1841
 
1907
1842
  event = EventRecord(
1908
1843
  id=event_id,
@@ -1971,9 +1906,7 @@ def watcher_event_delete_command(event_selector: str) -> int:
1971
1906
 
1972
1907
  # Handle "all" selector
1973
1908
  if event_selector.lower() == "all":
1974
- confirm = (
1975
- input(f"Delete ALL {len(all_events)} events? [y/N]: ").strip().lower()
1976
- )
1909
+ confirm = input(f"Delete ALL {len(all_events)} events? [y/N]: ").strip().lower()
1977
1910
  if confirm != "y":
1978
1911
  console.print("Cancelled.")
1979
1912
  return 0
@@ -1987,9 +1920,7 @@ def watcher_event_delete_command(event_selector: str) -> int:
1987
1920
 
1988
1921
  if not indices:
1989
1922
  console.print(f"[red]Invalid event selector: {event_selector}[/red]")
1990
- console.print(
1991
- f"[dim]Valid range: 1-{len(all_events)}, or event UUID/prefix[/dim]"
1992
- )
1923
+ console.print(f"[dim]Valid range: 1-{len(all_events)}, or event UUID/prefix[/dim]")
1993
1924
  return 1
1994
1925
 
1995
1926
  # Validate indices
@@ -2001,9 +1932,7 @@ def watcher_event_delete_command(event_selector: str) -> int:
2001
1932
 
2002
1933
  # Delete events
2003
1934
  deleted_count = 0
2004
- for idx in sorted(
2005
- indices, reverse=True
2006
- ): # Delete from end to avoid index shift
1935
+ for idx in sorted(indices, reverse=True): # Delete from end to avoid index shift
2007
1936
  event = all_events[idx - 1]
2008
1937
  if db.delete_event(event.id):
2009
1938
  deleted_count += 1
@@ -2049,12 +1978,8 @@ def watcher_event_show_command(event_selector: str) -> int:
2049
1978
 
2050
1979
  if not indices:
2051
1980
  console.print(f"[red]Invalid event selector: {event_selector}[/red]")
2052
- console.print(
2053
- f"[dim]Valid range: 1-{len(all_events)}, or event UUID/prefix[/dim]"
2054
- )
2055
- console.print(
2056
- "[dim]Use 'aline watcher event list' to see available events[/dim]"
2057
- )
1981
+ console.print(f"[dim]Valid range: 1-{len(all_events)}, or event UUID/prefix[/dim]")
1982
+ console.print("[dim]Use 'aline watcher event list' to see available events[/dim]")
2058
1983
  return 1
2059
1984
 
2060
1985
  if len(indices) > 1:
@@ -2081,12 +2006,8 @@ def watcher_event_show_command(event_selector: str) -> int:
2081
2006
  console.print(f" [bold]ID:[/bold] {event.id}")
2082
2007
  console.print(f" [bold]Title:[/bold] {event.title}")
2083
2008
  console.print(f" [bold]Generated by:[/bold] {generated_by_display}")
2084
- console.print(
2085
- f" [bold]Created:[/bold] {event.created_at.strftime('%Y-%m-%d %H:%M:%S')}"
2086
- )
2087
- console.print(
2088
- f" [bold]Updated:[/bold] {event.updated_at.strftime('%Y-%m-%d %H:%M:%S')}"
2089
- )
2009
+ console.print(f" [bold]Created:[/bold] {event.created_at.strftime('%Y-%m-%d %H:%M:%S')}")
2010
+ console.print(f" [bold]Updated:[/bold] {event.updated_at.strftime('%Y-%m-%d %H:%M:%S')}")
2090
2011
 
2091
2012
  if event.description:
2092
2013
  console.print(f"\n[bold]Description:[/bold]")
@@ -2116,9 +2037,7 @@ def watcher_event_show_command(event_selector: str) -> int:
2116
2037
  # Get turn count
2117
2038
  turns = db.get_turns_for_session(s.id)
2118
2039
  turn_count = len(turns) if turns else 0
2119
- activity = (
2120
- _format_relative_time(s.last_activity_at) if s.last_activity_at else "-"
2121
- )
2040
+ activity = _format_relative_time(s.last_activity_at) if s.last_activity_at else "-"
2122
2041
 
2123
2042
  console.print(
2124
2043
  f"\n[bold cyan]Session #{idx}[/bold cyan] [dim]({s.session_type or '-'}, {turn_count} turns, {activity})[/dim]"
@@ -2276,9 +2195,7 @@ def watcher_event_list_command(
2276
2195
 
2277
2196
  console.print(table)
2278
2197
  console.print()
2279
- console.print(
2280
- "[dim]Create events: aline watcher event generate <sessions>[/dim]"
2281
- )
2198
+ console.print("[dim]Create events: aline watcher event generate <sessions>[/dim]")
2282
2199
  console.print()
2283
2200
 
2284
2201
  return 0
@@ -2342,9 +2259,7 @@ def watcher_event_revise_slack_command(
2342
2259
  prompt_path = Path.home() / ".aline" / "prompts" / "slack_share_revise.md"
2343
2260
  if not prompt_path.exists():
2344
2261
  # Fallback to example file
2345
- prompt_path = (
2346
- Path.home() / ".aline" / "prompts" / "slack_share_revise.md.example"
2347
- )
2262
+ prompt_path = Path.home() / ".aline" / "prompts" / "slack_share_revise.md.example"
2348
2263
 
2349
2264
  if not prompt_path.exists():
2350
2265
  if not json_output:
@@ -2691,11 +2606,7 @@ def _create_debug_callback(debug_file: Path) -> Callable[[Dict[str, Any]], None]
2691
2606
  payload_with_ts = {"timestamp": datetime.now().isoformat(), **payload}
2692
2607
  with open(debug_file, "a", encoding="utf-8") as f:
2693
2608
  # Pretty-print JSON for readability
2694
- f.write(
2695
- json.dumps(
2696
- payload_with_ts, ensure_ascii=False, default=str, indent=2
2697
- )
2698
- )
2609
+ f.write(json.dumps(payload_with_ts, ensure_ascii=False, default=str, indent=2))
2699
2610
  f.write("\n---\n") # Separator between events
2700
2611
  except Exception as e:
2701
2612
  logger.debug(f"Debug callback error: {e}")
@@ -2731,9 +2642,7 @@ def _import_single_session(
2731
2642
 
2732
2643
  if show_header:
2733
2644
  console.print(f"\n[bold]Importing: {session_file.name}[/bold]")
2734
- console.print(
2735
- f" Source: {status_info['source']}, Project: {status_info['project_name']}"
2736
- )
2645
+ console.print(f" Source: {status_info['source']}, Project: {status_info['project_name']}")
2737
2646
 
2738
2647
  watcher = DialogueWatcher()
2739
2648
  project_path = watcher._extract_project_path(session_file)
@@ -2899,12 +2808,8 @@ def watcher_session_import_command(
2899
2808
 
2900
2809
  if not indices:
2901
2810
  console.print(f"[red]Session not found: {session_id}[/red]")
2902
- console.print(
2903
- f"[dim]Valid range: 1-{len(session_infos)}, or session UUID/prefix[/dim]"
2904
- )
2905
- console.print(
2906
- "[dim]Use 'aline watcher session list' to see available sessions[/dim]"
2907
- )
2811
+ console.print(f"[dim]Valid range: 1-{len(session_infos)}, or session UUID/prefix[/dim]")
2812
+ console.print("[dim]Use 'aline watcher session list' to see available sessions[/dim]")
2908
2813
  return 1
2909
2814
 
2910
2815
  # Import selected sessions
@@ -2923,9 +2828,7 @@ def watcher_session_import_command(
2923
2828
  )
2924
2829
  continue
2925
2830
 
2926
- console.print(
2927
- f"\n[cyan]({i}/{len(indices)})[/cyan] #{idx} {session_file.name}"
2928
- )
2831
+ console.print(f"\n[cyan]({i}/{len(indices)})[/cyan] #{idx} {session_file.name}")
2929
2832
 
2930
2833
  if _import_single_session(
2931
2834
  session_file,
@@ -3079,9 +2982,7 @@ def watcher_session_refresh_command(session_selector: str) -> int:
3079
2982
  # Get database connection
3080
2983
  db = None
3081
2984
  try:
3082
- env_db_path = os.getenv("REALIGN_SQLITE_DB_PATH") or os.getenv(
3083
- "REALIGN_DB_PATH"
3084
- )
2985
+ env_db_path = os.getenv("REALIGN_SQLITE_DB_PATH") or os.getenv("REALIGN_DB_PATH")
3085
2986
  resolved_db_path = Path(env_db_path or config.sqlite_db_path).expanduser()
3086
2987
  if resolved_db_path.exists():
3087
2988
  from ..db import get_database
@@ -3127,9 +3028,7 @@ def watcher_session_refresh_command(session_selector: str) -> int:
3127
3028
 
3128
3029
  if not indices:
3129
3030
  console.print(f"[red]Invalid session selector: {session_selector}[/red]")
3130
- console.print(
3131
- f"[dim]Valid range: 1-{len(session_infos)}, or session UUID/prefix[/dim]"
3132
- )
3031
+ console.print(f"[dim]Valid range: 1-{len(session_infos)}, or session UUID/prefix[/dim]")
3133
3032
  console.print("[dim]Examples: 1, 1-5, 1,3,5-7, abc123de, abc123de,def456gh[/dim]")
3134
3033
  return 1
3135
3034
 
@@ -3196,9 +3095,7 @@ def watcher_session_show_command(
3196
3095
  # Get database connection
3197
3096
  db = None
3198
3097
  try:
3199
- env_db_path = os.getenv("REALIGN_SQLITE_DB_PATH") or os.getenv(
3200
- "REALIGN_DB_PATH"
3201
- )
3098
+ env_db_path = os.getenv("REALIGN_SQLITE_DB_PATH") or os.getenv("REALIGN_DB_PATH")
3202
3099
  resolved_db_path = Path(env_db_path or config.sqlite_db_path).expanduser()
3203
3100
  if resolved_db_path.exists():
3204
3101
  from ..db import get_database
@@ -3247,12 +3144,8 @@ def watcher_session_show_command(
3247
3144
 
3248
3145
  if not indices:
3249
3146
  console.print(f"[red]Invalid session selector: {session_selector}[/red]")
3250
- console.print(
3251
- f"[dim]Valid range: 1-{len(session_infos)}, or session UUID/prefix[/dim]"
3252
- )
3253
- console.print(
3254
- "[dim]Use 'aline watcher session list' to see available sessions[/dim]"
3255
- )
3147
+ console.print(f"[dim]Valid range: 1-{len(session_infos)}, or session UUID/prefix[/dim]")
3148
+ console.print("[dim]Use 'aline watcher session list' to see available sessions[/dim]")
3256
3149
  return 1
3257
3150
 
3258
3151
  if len(indices) > 1:
@@ -3333,8 +3226,12 @@ def watcher_session_show_command(
3333
3226
  "title": title,
3334
3227
  "temp_title": temp_title or "",
3335
3228
  "timestamp": time_str,
3336
- "user_message": (t["user_message"] if "user_message" in keys else t[3]) or "",
3337
- "assistant_summary": (t["assistant_summary"] if "assistant_summary" in keys else t[4]) or "",
3229
+ "user_message": (t["user_message"] if "user_message" in keys else t[3])
3230
+ or "",
3231
+ "assistant_summary": (
3232
+ t["assistant_summary"] if "assistant_summary" in keys else t[4]
3233
+ )
3234
+ or "",
3338
3235
  }
3339
3236
  )
3340
3237
 
@@ -3348,15 +3245,11 @@ def watcher_session_show_command(
3348
3245
  "last_activity": info["last_activity"].isoformat(),
3349
3246
  "total_turns": info["total_turns"],
3350
3247
  "committed_turns": info["committed_turns"],
3351
- "session_title": session_record.session_title
3352
- if session_record
3353
- else None,
3354
- "session_summary": session_record.session_summary
3355
- if session_record
3356
- else None,
3357
- "summary_status": getattr(session_record, "summary_status", None)
3358
- if session_record
3359
- else None,
3248
+ "session_title": session_record.session_title if session_record else None,
3249
+ "session_summary": session_record.session_summary if session_record else None,
3250
+ "summary_status": (
3251
+ getattr(session_record, "summary_status", None) if session_record else None
3252
+ ),
3360
3253
  "turns": turns_data,
3361
3254
  }
3362
3255
 
@@ -3369,9 +3262,7 @@ def watcher_session_show_command(
3369
3262
  console.print(f" Source: {info['source']}")
3370
3263
  console.print(f" Project: {info['project_name']}")
3371
3264
  console.print(f" Created: {info['created_at'].strftime('%Y-%m-%d %H:%M:%S')}")
3372
- console.print(
3373
- f" Last Activity: {info['last_activity'].strftime('%Y-%m-%d %H:%M:%S')}"
3374
- )
3265
+ console.print(f" Last Activity: {info['last_activity'].strftime('%Y-%m-%d %H:%M:%S')}")
3375
3266
 
3376
3267
  if session_record:
3377
3268
  summary_status = getattr(session_record, "summary_status", None)
@@ -3390,9 +3281,7 @@ def watcher_session_show_command(
3390
3281
  console.print(f" Summary: {session_record.session_summary}")
3391
3282
 
3392
3283
  if not turns:
3393
- console.print(
3394
- f"\n[yellow]No turns found for this session in database.[/yellow]"
3395
- )
3284
+ console.print(f"\n[yellow]No turns found for this session in database.[/yellow]")
3396
3285
  console.print(
3397
3286
  f"[dim]Total turns in file: {info['total_turns']}, Committed: {info['committed_turns']}[/dim]"
3398
3287
  )
@@ -3497,9 +3386,7 @@ def watcher_llm_command(
3497
3386
 
3498
3387
  # Header
3499
3388
  timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
3500
- console.print(
3501
- f"\n[bold cyan]Lock Operations Monitor[/bold cyan] - {timestamp}"
3502
- )
3389
+ console.print(f"\n[bold cyan]Lock Operations Monitor[/bold cyan] - {timestamp}")
3503
3390
  console.print(f"[dim]Log file: {log_file}[/dim]\n")
3504
3391
 
3505
3392
  # Check if log file exists
@@ -3575,9 +3462,7 @@ def watcher_llm_command(
3575
3462
 
3576
3463
  # Extract time only from timestamp (HH:MM:SS)
3577
3464
  time_only = (
3578
- timestamp_str.split()[-1]
3579
- if " " in timestamp_str
3580
- else timestamp_str
3465
+ timestamp_str.split()[-1] if " " in timestamp_str else timestamp_str
3581
3466
  )
3582
3467
 
3583
3468
  # Format and print line
@@ -3701,9 +3586,7 @@ def watcher_session_delete_command(
3701
3586
  # Get database connection
3702
3587
  db = None
3703
3588
  try:
3704
- env_db_path = os.getenv("REALIGN_SQLITE_DB_PATH") or os.getenv(
3705
- "REALIGN_DB_PATH"
3706
- )
3589
+ env_db_path = os.getenv("REALIGN_SQLITE_DB_PATH") or os.getenv("REALIGN_DB_PATH")
3707
3590
  resolved_db_path = Path(env_db_path or config.sqlite_db_path).expanduser()
3708
3591
  if resolved_db_path.exists():
3709
3592
  from ..db import get_database
@@ -3751,12 +3634,8 @@ def watcher_session_delete_command(
3751
3634
 
3752
3635
  if not indices:
3753
3636
  console.print(f"[red]Invalid session selector: {session_selector}[/red]")
3754
- console.print(
3755
- f"[dim]Valid range: 1-{len(session_infos)}, or session UUID/prefix[/dim]"
3756
- )
3757
- console.print(
3758
- "[dim]Use 'aline watcher session list' to see available sessions[/dim]"
3759
- )
3637
+ console.print(f"[dim]Valid range: 1-{len(session_infos)}, or session UUID/prefix[/dim]")
3638
+ console.print("[dim]Use 'aline watcher session list' to see available sessions[/dim]")
3760
3639
  return 1
3761
3640
 
3762
3641
  if len(indices) > 1: