aline-ai 0.6.2__py3-none-any.whl → 0.6.4__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 (40) hide show
  1. {aline_ai-0.6.2.dist-info → aline_ai-0.6.4.dist-info}/METADATA +1 -1
  2. {aline_ai-0.6.2.dist-info → aline_ai-0.6.4.dist-info}/RECORD +38 -37
  3. realign/__init__.py +1 -1
  4. realign/adapters/__init__.py +0 -3
  5. realign/adapters/codex.py +14 -9
  6. realign/cli.py +42 -236
  7. realign/codex_detector.py +72 -32
  8. realign/codex_home.py +85 -0
  9. realign/codex_terminal_linker.py +172 -0
  10. realign/commands/__init__.py +2 -2
  11. realign/commands/add.py +89 -9
  12. realign/commands/doctor.py +495 -0
  13. realign/commands/export_shares.py +154 -226
  14. realign/commands/init.py +66 -4
  15. realign/commands/watcher.py +30 -80
  16. realign/config.py +9 -46
  17. realign/dashboard/app.py +7 -11
  18. realign/dashboard/screens/event_detail.py +0 -3
  19. realign/dashboard/screens/session_detail.py +0 -1
  20. realign/dashboard/tmux_manager.py +129 -4
  21. realign/dashboard/widgets/config_panel.py +175 -241
  22. realign/dashboard/widgets/events_table.py +71 -128
  23. realign/dashboard/widgets/sessions_table.py +77 -136
  24. realign/dashboard/widgets/terminal_panel.py +349 -27
  25. realign/dashboard/widgets/watcher_panel.py +0 -2
  26. realign/db/sqlite_db.py +77 -2
  27. realign/events/event_summarizer.py +76 -35
  28. realign/events/session_summarizer.py +73 -32
  29. realign/hooks.py +334 -647
  30. realign/llm_client.py +201 -520
  31. realign/triggers/__init__.py +0 -2
  32. realign/triggers/next_turn_trigger.py +4 -5
  33. realign/triggers/registry.py +1 -4
  34. realign/watcher_core.py +53 -35
  35. realign/adapters/antigravity.py +0 -159
  36. realign/triggers/antigravity_trigger.py +0 -140
  37. {aline_ai-0.6.2.dist-info → aline_ai-0.6.4.dist-info}/WHEEL +0 -0
  38. {aline_ai-0.6.2.dist-info → aline_ai-0.6.4.dist-info}/entry_points.txt +0 -0
  39. {aline_ai-0.6.2.dist-info → aline_ai-0.6.4.dist-info}/licenses/LICENSE +0 -0
  40. {aline_ai-0.6.2.dist-info → aline_ai-0.6.4.dist-info}/top_level.txt +0 -0
@@ -17,7 +17,7 @@ from textual.binding import Binding
17
17
  from textual.containers import Container, Horizontal, Vertical
18
18
  from textual.reactive import reactive
19
19
  from textual.worker import Worker, WorkerState
20
- from textual.widgets import Button, DataTable, Static
20
+ from textual.widgets import Button, DataTable, Select, Static
21
21
 
22
22
  from ...logging_config import setup_logger
23
23
  from .openable_table import OpenableDataTable
@@ -80,23 +80,16 @@ class SessionsTable(Container):
80
80
  }
81
81
 
82
82
  SessionsTable .table-container {
83
- height: auto;
83
+ height: 1fr;
84
84
  overflow-x: auto;
85
- overflow-y: hidden;
85
+ overflow-y: auto;
86
86
  }
87
87
 
88
88
  SessionsTable DataTable {
89
89
  height: auto;
90
90
  max-height: 100%;
91
91
  overflow-x: auto;
92
- overflow-y: hidden;
93
- }
94
-
95
- SessionsTable .pagination-info {
96
- height: 1;
97
- margin-top: 1;
98
- color: $text-muted;
99
- text-align: center;
92
+ overflow-y: auto;
100
93
  }
101
94
 
102
95
  SessionsTable .stats-info {
@@ -107,9 +100,9 @@ class SessionsTable(Container):
107
100
  """
108
101
 
109
102
  # Reactive properties
110
- current_page: reactive[int] = reactive(1)
111
- rows_per_page: reactive[int] = reactive(10)
112
103
  wrap_mode: reactive[bool] = reactive(False)
104
+ # Maximum sessions to load (for scrollable list)
105
+ MAX_SESSIONS: int = 500
113
106
 
114
107
  def __init__(self) -> None:
115
108
  super().__init__()
@@ -123,8 +116,9 @@ class SessionsTable(Container):
123
116
  self._refresh_worker: Optional[Worker] = None
124
117
  self._share_export_worker: Optional[Worker] = None
125
118
  self._refresh_timer = None
126
- self._active_refresh_snapshot: Optional[tuple[int, int]] = None
127
- self._pending_refresh_snapshot: Optional[tuple[int, int]] = None
119
+ self._is_refreshing: bool = False
120
+ self._pending_refresh: bool = False
121
+ self._saved_cursor_session_id: Optional[str] = None
128
122
 
129
123
  def compose(self) -> ComposeResult:
130
124
  """Compose the sessions table layout."""
@@ -144,7 +138,6 @@ class SessionsTable(Container):
144
138
  yield Static(id="section-header", classes="section-header")
145
139
  with Container(classes="table-container"):
146
140
  yield SessionsListTable(id="sessions-table")
147
- yield Static(id="pagination-info", classes="pagination-info")
148
141
  yield Static(id="stats-info", classes="stats-info")
149
142
 
150
143
  def on_mount(self) -> None:
@@ -155,22 +148,19 @@ class SessionsTable(Container):
155
148
  table.owner = self
156
149
 
157
150
  self._setup_table_columns(table)
158
-
159
- # Calculate initial rows per page
160
- self._calculate_rows_per_page()
161
151
  logger.debug("SessionsTable.on_mount() completed")
162
152
  except Exception as e:
163
153
  logger.error(f"SessionsTable.on_mount() failed: {e}\n{traceback.format_exc()}")
164
154
  raise
165
155
 
166
156
  def on_resize(self) -> None:
167
- """Handle window resize to adjust rows per page."""
168
- self._sync_to_available_height()
157
+ """Handle window resize."""
158
+ pass # No longer need to recalculate pagination
169
159
 
170
160
  def on_show(self) -> None:
171
- """Re-sync pagination when the tab becomes visible."""
161
+ """Refresh data when the tab becomes visible."""
172
162
  if self._refresh_timer is None:
173
- self._refresh_timer = self.set_interval(5.0, self._on_refresh_timer)
163
+ self._refresh_timer = self.set_interval(60.0, self._on_refresh_timer)
174
164
  else:
175
165
  try:
176
166
  self._refresh_timer.resume()
@@ -188,9 +178,7 @@ class SessionsTable(Container):
188
178
  pass
189
179
 
190
180
  def _on_became_visible(self) -> None:
191
- self._sync_to_available_height()
192
181
  self._load_sessions()
193
- self._update_display()
194
182
  try:
195
183
  self.query_one("#sessions-table", DataTable).focus()
196
184
  except Exception:
@@ -229,19 +217,6 @@ class SessionsTable(Container):
229
217
  table.add_column("Last Activity", key="last_activity", width=12)
230
218
  table.cursor_type = "row"
231
219
 
232
- def _sync_to_available_height(self) -> None:
233
- """Recalculate rows per page and reload if the page size changed."""
234
- old_rows_per_page = self.rows_per_page
235
- self._calculate_rows_per_page()
236
-
237
- if self.rows_per_page != old_rows_per_page:
238
- total_pages = self._get_total_pages()
239
- if self.current_page > total_pages:
240
- self.current_page = total_pages
241
- self._load_sessions()
242
-
243
- self._update_display()
244
-
245
220
  def get_selected_session_ids(self) -> list[str]:
246
221
  return sorted(self._selected_session_ids)
247
222
 
@@ -423,47 +398,6 @@ class SessionsTable(Container):
423
398
  self._start_share_export()
424
399
  return
425
400
 
426
- def _calculate_rows_per_page(self) -> None:
427
- """Calculate rows per page based on available height."""
428
- try:
429
- panel_height = self.size.height
430
-
431
- # Calculate exact space needed:
432
- # - Summary section: ~2 lines content + 2 border + 2 padding + 1 margin = 7
433
- # - Section header: 1 line + 1 margin = 2
434
- # - Table header row: 1
435
- # - Pagination info: 1 line + 1 margin = 2
436
- # - Panel padding: 2 (top + bottom)
437
- # - Extra buffer: 3
438
-
439
- fixed_height = 7 + 2 + 1 + 2 + 2 + 3 # = 17
440
-
441
- available_for_rows = panel_height - fixed_height
442
- rows = max(available_for_rows, 3) # At least 3 rows
443
-
444
- self.rows_per_page = rows
445
- except Exception:
446
- self.rows_per_page = 10
447
-
448
- def _get_total_pages(self) -> int:
449
- """Calculate total pages."""
450
- if self._total_sessions == 0:
451
- return 1
452
- return (self._total_sessions + self.rows_per_page - 1) // self.rows_per_page
453
-
454
- def action_next_page(self) -> None:
455
- """Go to next page."""
456
- total_pages = self._get_total_pages()
457
- if self.current_page < total_pages:
458
- self.current_page += 1
459
- self._load_sessions()
460
-
461
- def action_prev_page(self) -> None:
462
- """Go to previous page."""
463
- if self.current_page > 1:
464
- self.current_page -= 1
465
- self._load_sessions()
466
-
467
401
  def _on_refresh_timer(self) -> None:
468
402
  self.refresh_data(force=False)
469
403
 
@@ -472,26 +406,37 @@ class SessionsTable(Container):
472
406
  if not self.display:
473
407
  return
474
408
 
475
- snapshot = (int(self.current_page), int(self.rows_per_page))
476
409
  if self._refresh_worker is not None and self._refresh_worker.state in (
477
410
  WorkerState.PENDING,
478
411
  WorkerState.RUNNING,
479
412
  ):
480
413
  if force:
481
- self._pending_refresh_snapshot = snapshot
414
+ self._pending_refresh = True
482
415
  return
483
416
 
484
- if force and self._pending_refresh_snapshot is not None:
485
- snapshot = self._pending_refresh_snapshot
486
- self._pending_refresh_snapshot = None
417
+ # Save current cursor position before refresh
418
+ self._save_cursor_position()
487
419
 
488
420
  def work() -> dict:
489
- return self._collect_snapshot(*snapshot)
421
+ return self._collect_all_sessions()
490
422
 
491
- self._active_refresh_snapshot = snapshot
492
- self._pending_refresh_snapshot = None
423
+ self._is_refreshing = True
424
+ self._pending_refresh = False
493
425
  self._refresh_worker = self.run_worker(work, thread=True, exit_on_error=False)
494
426
 
427
+ def _save_cursor_position(self) -> None:
428
+ """Save the current cursor session ID to restore after refresh."""
429
+ try:
430
+ table = self.query_one("#sessions-table", DataTable)
431
+ if table.row_count > 0:
432
+ self._saved_cursor_session_id = str(
433
+ table.coordinate_to_cell_key(table.cursor_coordinate)[0].value
434
+ )
435
+ else:
436
+ self._saved_cursor_session_id = None
437
+ except Exception:
438
+ self._saved_cursor_session_id = None
439
+
495
440
  def _load_sessions(self) -> None:
496
441
  """Compatibility hook (tests stub this); default triggers async refresh."""
497
442
  if not self.is_mounted:
@@ -513,35 +458,34 @@ class SessionsTable(Container):
513
458
 
514
459
  if event.state == WorkerState.ERROR:
515
460
  result = {
516
- "snapshot": self._active_refresh_snapshot,
517
461
  "total_sessions": 0,
518
462
  "stats": {},
519
463
  "sessions": [],
520
464
  }
465
+ self._is_refreshing = False
521
466
  elif event.state != WorkerState.SUCCESS:
522
467
  return
523
468
  else:
524
469
  result = self._refresh_worker.result or {}
470
+ self._is_refreshing = False
525
471
 
526
- snapshot = result.get("snapshot")
527
- if snapshot == (int(self.current_page), int(self.rows_per_page)):
528
- try:
529
- self._total_sessions = int(result.get("total_sessions") or 0)
530
- except Exception:
531
- self._total_sessions = 0
532
- try:
533
- self._stats = dict(result.get("stats") or {})
534
- except Exception:
535
- self._stats = {}
536
- try:
537
- self._sessions = list(result.get("sessions") or [])
538
- self._sessions_by_id = {s["id"]: s for s in self._sessions}
539
- except Exception:
540
- self._sessions = []
541
- self._sessions_by_id = {}
542
- self._update_display()
472
+ try:
473
+ self._total_sessions = int(result.get("total_sessions") or 0)
474
+ except Exception:
475
+ self._total_sessions = 0
476
+ try:
477
+ self._stats = dict(result.get("stats") or {})
478
+ except Exception:
479
+ self._stats = {}
480
+ try:
481
+ self._sessions = list(result.get("sessions") or [])
482
+ self._sessions_by_id = {s["id"]: s for s in self._sessions}
483
+ except Exception:
484
+ self._sessions = []
485
+ self._sessions_by_id = {}
486
+ self._update_display()
543
487
 
544
- if self._pending_refresh_snapshot is not None:
488
+ if self._pending_refresh:
545
489
  self.refresh_data()
546
490
 
547
491
  def _start_share_export(self) -> None:
@@ -772,8 +716,8 @@ class SessionsTable(Container):
772
716
  suffix = " (copied to clipboard)" if copied else ""
773
717
  self.app.notify(f"Share link created{suffix}", title="Share", timeout=4)
774
718
 
775
- def _collect_snapshot(self, page: int, rows_per_page: int) -> dict:
776
- """Collect sessions + stats for a single page (background thread)."""
719
+ def _collect_all_sessions(self) -> dict:
720
+ """Collect all sessions + stats (background thread)."""
777
721
  total_sessions: int = 0
778
722
  stats: dict = {}
779
723
  sessions: list[dict] = []
@@ -810,9 +754,7 @@ class SessionsTable(Container):
810
754
  "gemini": stats_row[3] if stats_row else 0,
811
755
  }
812
756
 
813
- # Get paginated sessions
814
- # Use cached total_turns instead of subquery for performance
815
- offset = (int(page) - 1) * int(rows_per_page)
757
+ # Get all sessions (up to MAX_SESSIONS)
816
758
  try:
817
759
  rows = conn.execute(
818
760
  """
@@ -827,9 +769,9 @@ class SessionsTable(Container):
827
769
  s.shared_by
828
770
  FROM sessions s
829
771
  ORDER BY s.last_activity_at DESC
830
- LIMIT ? OFFSET ?
772
+ LIMIT ?
831
773
  """,
832
- (int(rows_per_page), int(offset)),
774
+ (self.MAX_SESSIONS,),
833
775
  ).fetchall()
834
776
  has_new_columns = True
835
777
  except Exception:
@@ -845,9 +787,9 @@ class SessionsTable(Container):
845
787
  s.total_turns
846
788
  FROM sessions s
847
789
  ORDER BY s.last_activity_at DESC
848
- LIMIT ? OFFSET ?
790
+ LIMIT ?
849
791
  """,
850
- (int(rows_per_page), int(offset)),
792
+ (self.MAX_SESSIONS,),
851
793
  ).fetchall()
852
794
  has_new_columns = False
853
795
 
@@ -869,7 +811,6 @@ class SessionsTable(Container):
869
811
  "claude": "Claude",
870
812
  "codex": "Codex",
871
813
  "gemini": "Gemini",
872
- "antigravity": "Antigravity",
873
814
  }
874
815
  source = source_map.get(session_type, session_type)
875
816
  project = Path(workspace).name if workspace else "-"
@@ -904,7 +845,7 @@ class SessionsTable(Container):
904
845
 
905
846
  sessions.append(
906
847
  {
907
- "index": offset + i + 1,
848
+ "index": i + 1,
908
849
  "id": session_id,
909
850
  "short_id": self._shorten_session_id(session_id),
910
851
  "source": source,
@@ -917,13 +858,12 @@ class SessionsTable(Container):
917
858
  }
918
859
  )
919
860
  except Exception as e:
920
- logger.error(f"_collect_snapshot failed: {e}\n{traceback.format_exc()}")
861
+ logger.error(f"_collect_all_sessions failed: {e}\n{traceback.format_exc()}")
921
862
  total_sessions = 0
922
863
  stats = {}
923
864
  sessions = []
924
865
 
925
866
  return {
926
- "snapshot": (int(page), int(rows_per_page)),
927
867
  "total_sessions": total_sessions,
928
868
  "stats": stats,
929
869
  "sessions": sessions,
@@ -939,18 +879,23 @@ class SessionsTable(Container):
939
879
 
940
880
  # Update table
941
881
  table = self.query_one("#sessions-table", DataTable)
942
- selected_session_id: Optional[str] = None
943
- try:
944
- if table.row_count > 0:
945
- selected_session_id = str(
946
- table.coordinate_to_cell_key(table.cursor_coordinate)[0].value
947
- )
948
- except Exception:
949
- selected_session_id = None
882
+
883
+ # Use saved cursor position if available, otherwise try to get current
884
+ restore_session_id = self._saved_cursor_session_id
885
+ if restore_session_id is None:
886
+ try:
887
+ if table.row_count > 0:
888
+ restore_session_id = str(
889
+ table.coordinate_to_cell_key(table.cursor_coordinate)[0].value
890
+ )
891
+ except Exception:
892
+ restore_session_id = None
893
+
950
894
  table.clear()
951
895
 
952
- # Always enable horizontal scrollbar
896
+ # Always enable scrollbars
953
897
  table.styles.overflow_x = "auto"
898
+ table.styles.overflow_y = "auto"
954
899
  table.show_horizontal_scrollbar = True
955
900
 
956
901
  for session in self._sessions:
@@ -979,10 +924,10 @@ class SessionsTable(Container):
979
924
  )
980
925
 
981
926
  if table.row_count > 0:
982
- if selected_session_id:
927
+ if restore_session_id:
983
928
  try:
984
929
  table.cursor_coordinate = (
985
- table.get_row_index(selected_session_id),
930
+ table.get_row_index(restore_session_id),
986
931
  0,
987
932
  )
988
933
  except Exception:
@@ -990,12 +935,8 @@ class SessionsTable(Container):
990
935
  else:
991
936
  table.cursor_coordinate = (0, 0)
992
937
 
993
- # Update pagination info
994
- total_pages = self._get_total_pages()
995
- pagination_widget = self.query_one("#pagination-info", Static)
996
- pagination_widget.update(
997
- f"[dim]Page {self.current_page}/{total_pages} ({self._total_sessions} total) │ (p) prev (n) next[/dim]"
998
- )
938
+ # Clear saved cursor position after restore
939
+ self._saved_cursor_session_id = None
999
940
 
1000
941
  def _shorten_session_id(self, session_id: str) -> str:
1001
942
  """Shorten a session ID for display."""