aline-ai 0.5.7__py3-none-any.whl → 0.5.8__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aline-ai
3
- Version: 0.5.7
3
+ Version: 0.5.8
4
4
  Summary: Shared AI memory; everyone knows everything in teams
5
5
  Author: Sharemind
6
6
  License: MIT
@@ -1,7 +1,7 @@
1
- aline_ai-0.5.7.dist-info/licenses/LICENSE,sha256=H8wTqV5IF1oHw_HbBtS1PSDU8G_q81yblEIL_JfV8Vo,1077
2
- realign/__init__.py,sha256=yVsHlHyZpDRPyaFzYXTltDHKp6XXpiIGe-TlrznKALo,1623
1
+ aline_ai-0.5.8.dist-info/licenses/LICENSE,sha256=H8wTqV5IF1oHw_HbBtS1PSDU8G_q81yblEIL_JfV8Vo,1077
2
+ realign/__init__.py,sha256=r_zeipazrpik0MPgEERMIzrPFCcbvLypS_erc6wytjc,1623
3
3
  realign/claude_detector.py,sha256=ZLSJacMo6zzQclXByABKA70UNpstxqIv3fPGqdpA934,2792
4
- realign/cli.py,sha256=yeq_a3Peoqx8N13Jo2etjJtbTCZYpuqwoMMyAPdrANs,30569
4
+ realign/cli.py,sha256=9VS3WbysZ78NRK5EvkJVg8s6Uh2TQjsGX1E9Pl81pHc,31234
5
5
  realign/codex_detector.py,sha256=N9ulgMgvTzDfXE4s4vLd6OoS0hT7R6h2bDFFXWa-2hE,4183
6
6
  realign/config.py,sha256=lIKZqeOwYc_gHo760lYYX6PnapuKrCWGqT5SA8-PbeA,12044
7
7
  realign/context.py,sha256=S1YEUn5HWSDTerDDMsSsRV871IZxoaxDjPTPI2z6-Xs,9976
@@ -39,12 +39,12 @@ realign/commands/export_shares.py,sha256=Djy1aO7MoU1_ewzn6CZ43oNhSEEonV3sTkSQbHg
39
39
  realign/commands/import_shares.py,sha256=ukX8huvLvEM5g0qEIoqrV1-imz1g-r0Jj2FqD-ojrIA,25297
40
40
  realign/commands/init.py,sha256=ef-q3Qz5D_0Eqld8qjtX26X2QrovBSYcva3uAjiJuwk,33015
41
41
  realign/commands/restore.py,sha256=s2BxQZHxQw9r12NzRVsK20KlGafy5AIoSjWMo5PcnHY,11173
42
- realign/commands/search.py,sha256=RUdseQsjy-SNfKFkGLWrE4IhxkzgkW9IIxAX33XnCHk,24589
42
+ realign/commands/search.py,sha256=QJrC0hln9sCDFxXbpo0nPGMHXrud18qA5QfRyD0z6fQ,25926
43
43
  realign/commands/upgrade.py,sha256=L3PLOUIN5qAQTbkfoVtSsIbbzEezA_xjjk9F1GMVfjw,12781
44
44
  realign/commands/watcher.py,sha256=fWL3kaRkqE03-NtFLaXlx93hJAQrAuNPSoYhOyQZfq8,136273
45
45
  realign/commands/worker.py,sha256=K1DG1uZ--ebKwklHCyIFdN_axoLjL9Onx8Naq-DOZBs,23078
46
46
  realign/dashboard/__init__.py,sha256=QZkHTsGityH8UkF8rmvA3xW7dMXNe0swEWr443qfgCM,128
47
- realign/dashboard/app.py,sha256=LPgDXCqXHtJXzgFiwJvWolLJZ34EU4gejLrBIrgEk4Y,12233
47
+ realign/dashboard/app.py,sha256=jyW6mqmItTy253CPSqInxctkWzkrGEikdy-ikuShQ14,13299
48
48
  realign/dashboard/tmux_manager.py,sha256=DdCiumQ7YQZnje5VfOQ60585C0X6Va_AhBQi_zmhE0Y,24035
49
49
  realign/dashboard/screens/__init__.py,sha256=US6sAmQs5VVkH2tFkH_z0WDT4H8cVhLL-JckfSR1yQY,446
50
50
  realign/dashboard/screens/create_agent.py,sha256=ugEs3IHrT7FsbuMEwyrqY3eoylp_pbftw42_Fu07tF4,7419
@@ -60,8 +60,8 @@ realign/dashboard/widgets/events_table.py,sha256=OG9RjwU4c50-RUMmdhXzmIMnYrt6_mC
60
60
  realign/dashboard/widgets/header.py,sha256=0HHCFXX7F3C6HII-WDwOJwWkJrajmKPWmdoMWyOkn9E,1587
61
61
  realign/dashboard/widgets/openable_table.py,sha256=GeJPDEYp0kRHShqvmPMzAePpYXRZHUNqcWNnxqsqxjA,1963
62
62
  realign/dashboard/widgets/search_panel.py,sha256=ZNJDfwDSxUFnCeltYQYsQsPJ6t4HDeNWpENoTOoBdVM,8951
63
- realign/dashboard/widgets/sessions_table.py,sha256=toHE96RLwddqXE9Ykocy6loqoGld_6gFawLwdiiJ2cA,32877
64
- realign/dashboard/widgets/terminal_panel.py,sha256=uXgPcgjWaQ2tTD6Mx6ikCXzq6wYqh-ft0Bait83_DKE,28290
63
+ realign/dashboard/widgets/sessions_table.py,sha256=PohOkg-ESLBa-Sq0PdLPhV-YzVXOGpUo5ETs0MYO4u8,33415
64
+ realign/dashboard/widgets/terminal_panel.py,sha256=S4UUMlFaBWDKZB_MR0jhBNvdwdGvPuRNHNxweFGDfks,28751
65
65
  realign/dashboard/widgets/watcher_panel.py,sha256=O_mdDacgc87xA-5KEfta53Ik_Xsk_B2OfwenMOTtGw8,19722
66
66
  realign/dashboard/widgets/worker_panel.py,sha256=F_jKWABuCNmjQgeeuCr4KnFRKdY4CLTNcEXMYwsNaSk,18691
67
67
  realign/db/__init__.py,sha256=-1d-Zc4IOUVokbdTXi3R-bIwlkFEPAz_qTHAdcsdp6g,1870
@@ -88,8 +88,8 @@ realign/triggers/next_turn_trigger.py,sha256=BpP0PWn4mU1MZd6mv89jWcjs8Jtv0zEWapW
88
88
  realign/triggers/registry.py,sha256=cb-AVLbYB2pqwfWL3q1DQxLv4kOw7g7m-GshTdfFESc,3827
89
89
  realign/triggers/turn_status.py,sha256=wAZEhXDAmDoX5F-ohWfSnZZ0eA6DAJ9svSPiSv_f6sg,6041
90
90
  realign/triggers/turn_summary.py,sha256=f3hEUshgv9skJ9AbfWpoYs417lsv_HK2A_vpPjgryO4,4467
91
- aline_ai-0.5.7.dist-info/METADATA,sha256=SG0PLhxEgWQIrjE3rCiSk9DFURyfEkxXteRKqdFhijU,1597
92
- aline_ai-0.5.7.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
93
- aline_ai-0.5.7.dist-info/entry_points.txt,sha256=TvYELpMoWsUTcQdMV8tBHxCbEf_LbK4sESqK3r8PM6Y,78
94
- aline_ai-0.5.7.dist-info/top_level.txt,sha256=yIL3s2xv9nf1GwD5n71Aq_JEIV4AfzCIDNKBzewuRm4,8
95
- aline_ai-0.5.7.dist-info/RECORD,,
91
+ aline_ai-0.5.8.dist-info/METADATA,sha256=S2cnGF2C84A28xQzAGz7nszWbBKErn4LazeX6KhAiBA,1597
92
+ aline_ai-0.5.8.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
93
+ aline_ai-0.5.8.dist-info/entry_points.txt,sha256=TvYELpMoWsUTcQdMV8tBHxCbEf_LbK4sESqK3r8PM6Y,78
94
+ aline_ai-0.5.8.dist-info/top_level.txt,sha256=yIL3s2xv9nf1GwD5n71Aq_JEIV4AfzCIDNKBzewuRm4,8
95
+ aline_ai-0.5.8.dist-info/RECORD,,
realign/__init__.py CHANGED
@@ -3,7 +3,7 @@
3
3
  import hashlib
4
4
  from pathlib import Path
5
5
 
6
- __version__ = "0.5.7"
6
+ __version__ = "0.5.8"
7
7
 
8
8
 
9
9
  def get_realign_dir(project_root: Path) -> Path:
realign/cli.py CHANGED
@@ -819,18 +819,36 @@ def version():
819
819
  def dashboard(
820
820
  ctx: typer.Context,
821
821
  dev: bool = typer.Option(False, "--dev", help="Enable developer mode (shows Watcher and Worker tabs)"),
822
+ debug: bool = typer.Option(False, "--debug", help="Enable debug logging to ~/.aline/.logs/dashboard.log"),
822
823
  ):
823
824
  """Open the interactive TUI dashboard."""
825
+ import os
826
+ import traceback
827
+
828
+ # Set debug log level if requested
829
+ if debug:
830
+ os.environ["REALIGN_LOG_LEVEL"] = "DEBUG"
831
+
824
832
  from .dashboard.tmux_manager import bootstrap_dashboard_into_tmux
833
+ from .logging_config import setup_logger
834
+
835
+ # Initialize logger before dashboard
836
+ logger = setup_logger("realign.dashboard", "dashboard.log")
837
+ logger.info(f"Dashboard command invoked (dev={dev}, debug={debug})")
825
838
 
826
- bootstrap_dashboard_into_tmux()
839
+ try:
840
+ bootstrap_dashboard_into_tmux()
827
841
 
828
- from .dashboard.app import AlineDashboard
842
+ from .dashboard.app import AlineDashboard
829
843
 
830
- # Use dev flag from this command or inherit from parent context
831
- dev_mode = dev or (ctx.obj.get("dev", False) if ctx.obj else False)
832
- dash = AlineDashboard(dev_mode=dev_mode)
833
- dash.run()
844
+ # Use dev flag from this command or inherit from parent context
845
+ dev_mode = dev or (ctx.obj.get("dev", False) if ctx.obj else False)
846
+ dash = AlineDashboard(dev_mode=dev_mode)
847
+ dash.run()
848
+ except Exception as e:
849
+ logger.error(f"Dashboard crashed: {e}\n{traceback.format_exc()}")
850
+ # Re-raise so user sees the error
851
+ raise
834
852
 
835
853
 
836
854
  # Restore command group
@@ -528,6 +528,19 @@ def search_command(
528
528
 
529
529
  console.print(f"[dim]Found {', '.join(summary_parts)}.[/dim]")
530
530
 
531
+ # Check if any result count hits the limit - suggest increasing limit
532
+ hit_limit = (
533
+ event_count == limit
534
+ or turn_count == limit
535
+ or session_count == limit
536
+ or (type == "content" and len(results.get("content", [])) == limit)
537
+ )
538
+ if hit_limit:
539
+ console.print(
540
+ f"[yellow]Results may be truncated (limit={limit}). "
541
+ f"Use --limit N to see more results.[/yellow]"
542
+ )
543
+
531
544
  # === Original structured output for non-regex mode ===
532
545
  else:
533
546
  if type == "all":
@@ -624,6 +637,23 @@ def search_command(
624
637
  "\n[dim]Tip: Use --verbose to see Markdown previews, or --no-regex for exact keyword match.[/dim]"
625
638
  )
626
639
 
640
+ # Check if any result count hits the limit - suggest increasing limit
641
+ event_count = len(results.get("events", []))
642
+ turn_count = len(results.get("turns", []))
643
+ session_count = len(results.get("sessions", []))
644
+ content_count = len(results.get("content", []))
645
+ hit_limit = (
646
+ event_count == limit
647
+ or turn_count == limit
648
+ or session_count == limit
649
+ or content_count == limit
650
+ )
651
+ if hit_limit:
652
+ console.print(
653
+ f"[yellow]Results may be truncated (limit={limit}). "
654
+ f"Use --limit N to see more results.[/yellow]"
655
+ )
656
+
627
657
  return 0
628
658
 
629
659
  except Exception as e:
realign/dashboard/app.py CHANGED
@@ -3,12 +3,14 @@
3
3
  import subprocess
4
4
  import sys
5
5
  import time
6
+ import traceback
6
7
  from pathlib import Path
7
8
 
8
9
  from textual.app import App, ComposeResult
9
10
  from textual.binding import Binding
10
11
  from textual.widgets import Footer, TabbedContent, TabPane
11
12
 
13
+ from ..logging_config import setup_logger
12
14
  from .widgets import (
13
15
  AlineHeader,
14
16
  WatcherPanel,
@@ -20,6 +22,9 @@ from .widgets import (
20
22
  TerminalPanel,
21
23
  )
22
24
 
25
+ # Set up dashboard logger - logs to ~/.aline/.logs/dashboard.log
26
+ logger = setup_logger("realign.dashboard", "dashboard.log")
27
+
23
28
 
24
29
  def _detect_system_dark_mode() -> bool:
25
30
  """Detect if the system is in dark mode.
@@ -77,28 +82,35 @@ class AlineDashboard(App):
77
82
  """
78
83
  super().__init__()
79
84
  self.dev_mode = dev_mode
85
+ logger.info(f"AlineDashboard initialized (dev_mode={dev_mode})")
80
86
 
81
87
  def compose(self) -> ComposeResult:
82
88
  """Compose the dashboard layout."""
83
- yield AlineHeader()
84
- tab_ids = self._tab_ids()
85
- with TabbedContent(initial=tab_ids[0] if tab_ids else "terminal"):
86
- with TabPane("Agents", id="terminal"):
87
- yield TerminalPanel()
88
- if self.dev_mode:
89
- with TabPane("Watcher", id="watcher"):
90
- yield WatcherPanel()
91
- with TabPane("Worker", id="worker"):
92
- yield WorkerPanel()
93
- with TabPane("Contexts", id="sessions"):
94
- yield SessionsTable()
95
- with TabPane("Share", id="events"):
96
- yield EventsTable()
97
- with TabPane("Config", id="config"):
98
- yield ConfigPanel()
99
- with TabPane("Search", id="search"):
100
- yield SearchPanel()
101
- yield Footer()
89
+ logger.debug("compose() started")
90
+ try:
91
+ yield AlineHeader()
92
+ tab_ids = self._tab_ids()
93
+ with TabbedContent(initial=tab_ids[0] if tab_ids else "terminal"):
94
+ with TabPane("Agents", id="terminal"):
95
+ yield TerminalPanel()
96
+ if self.dev_mode:
97
+ with TabPane("Watcher", id="watcher"):
98
+ yield WatcherPanel()
99
+ with TabPane("Worker", id="worker"):
100
+ yield WorkerPanel()
101
+ with TabPane("Contexts", id="sessions"):
102
+ yield SessionsTable()
103
+ with TabPane("Share", id="events"):
104
+ yield EventsTable()
105
+ with TabPane("Config", id="config"):
106
+ yield ConfigPanel()
107
+ with TabPane("Search", id="search"):
108
+ yield SearchPanel()
109
+ yield Footer()
110
+ logger.debug("compose() completed successfully")
111
+ except Exception as e:
112
+ logger.error(f"compose() failed: {e}\n{traceback.format_exc()}")
113
+ raise
102
114
 
103
115
  def _tab_ids(self) -> list[str]:
104
116
  if self.dev_mode:
@@ -107,10 +119,16 @@ class AlineDashboard(App):
107
119
 
108
120
  def on_mount(self) -> None:
109
121
  """Apply theme based on system settings and watch for changes."""
110
- self._sync_theme()
111
- # Check for system theme changes every 2 seconds
112
- self.set_interval(2, self._sync_theme)
113
- self._quit_confirm_deadline: float | None = None
122
+ logger.info("on_mount() started")
123
+ try:
124
+ self._sync_theme()
125
+ # Check for system theme changes every 2 seconds
126
+ self.set_interval(2, self._sync_theme)
127
+ self._quit_confirm_deadline: float | None = None
128
+ logger.info("on_mount() completed successfully")
129
+ except Exception as e:
130
+ logger.error(f"on_mount() failed: {e}\n{traceback.format_exc()}")
131
+ raise
114
132
 
115
133
  def _sync_theme(self) -> None:
116
134
  """Sync app theme with system theme."""
@@ -341,8 +359,14 @@ class AlineDashboard(App):
341
359
 
342
360
  def run_dashboard() -> None:
343
361
  """Run the Aline Dashboard."""
344
- app = AlineDashboard()
345
- app.run()
362
+ logger.info("Starting Aline Dashboard")
363
+ try:
364
+ app = AlineDashboard()
365
+ app.run()
366
+ logger.info("Aline Dashboard exited normally")
367
+ except Exception as e:
368
+ logger.error(f"Dashboard crashed: {e}\n{traceback.format_exc()}")
369
+ raise
346
370
 
347
371
 
348
372
  if __name__ == "__main__":
@@ -6,6 +6,7 @@ import json
6
6
  import os
7
7
  import shutil
8
8
  import subprocess
9
+ import traceback
9
10
  from datetime import datetime
10
11
  from pathlib import Path
11
12
  from typing import List, Optional, Set
@@ -18,8 +19,11 @@ from textual.reactive import reactive
18
19
  from textual.worker import Worker, WorkerState
19
20
  from textual.widgets import Button, DataTable, Static
20
21
 
22
+ from ...logging_config import setup_logger
21
23
  from .openable_table import OpenableDataTable
22
24
 
25
+ logger = setup_logger("realign.dashboard.sessions", "dashboard.log")
26
+
23
27
 
24
28
  class SessionsListTable(OpenableDataTable):
25
29
  """Sessions list table with multi-select behavior."""
@@ -145,13 +149,19 @@ class SessionsTable(Container):
145
149
 
146
150
  def on_mount(self) -> None:
147
151
  """Set up the table on mount."""
148
- table = self.query_one("#sessions-table", SessionsListTable)
149
- table.owner = self
152
+ logger.debug("SessionsTable.on_mount() started")
153
+ try:
154
+ table = self.query_one("#sessions-table", SessionsListTable)
155
+ table.owner = self
150
156
 
151
- self._setup_table_columns(table)
157
+ self._setup_table_columns(table)
152
158
 
153
- # Calculate initial rows per page
154
- self._calculate_rows_per_page()
159
+ # Calculate initial rows per page
160
+ self._calculate_rows_per_page()
161
+ logger.debug("SessionsTable.on_mount() completed")
162
+ except Exception as e:
163
+ logger.error(f"SessionsTable.on_mount() failed: {e}\n{traceback.format_exc()}")
164
+ raise
155
165
 
156
166
  def on_resize(self) -> None:
157
167
  """Handle window resize to adjust rows per page."""
@@ -793,6 +803,7 @@ class SessionsTable(Container):
793
803
  }
794
804
 
795
805
  # Get paginated sessions
806
+ # Use cached total_turns instead of subquery for performance
796
807
  offset = (int(page) - 1) * int(rows_per_page)
797
808
  rows = conn.execute(
798
809
  """
@@ -802,7 +813,7 @@ class SessionsTable(Container):
802
813
  s.workspace_path,
803
814
  s.session_title,
804
815
  s.last_activity_at,
805
- (SELECT COUNT(*) FROM turns WHERE session_id = s.id) AS turn_count
816
+ s.total_turns
806
817
  FROM sessions s
807
818
  ORDER BY s.last_activity_at DESC
808
819
  LIMIT ? OFFSET ?
@@ -847,7 +858,8 @@ class SessionsTable(Container):
847
858
  "last_activity": activity_str,
848
859
  }
849
860
  )
850
- except Exception:
861
+ except Exception as e:
862
+ logger.error(f"_collect_snapshot failed: {e}\n{traceback.format_exc()}")
851
863
  total_sessions = 0
852
864
  stats = {}
853
865
  sessions = []
@@ -11,6 +11,7 @@ import asyncio
11
11
  import os
12
12
  import re
13
13
  import shlex
14
+ import traceback
14
15
  from pathlib import Path
15
16
  from typing import Callable
16
17
 
@@ -21,6 +22,9 @@ from textual.widgets import Button, Static
21
22
  from rich.text import Text
22
23
 
23
24
  from .. import tmux_manager
25
+ from ...logging_config import setup_logger
26
+
27
+ logger = setup_logger("realign.dashboard.terminal", "dashboard.log")
24
28
 
25
29
 
26
30
  # Signal directory for permission request notifications
@@ -291,21 +295,27 @@ class TerminalPanel(Container, can_focus=True):
291
295
  self._signal_watcher: _SignalFileWatcher | None = None
292
296
 
293
297
  def compose(self) -> ComposeResult:
294
- controls_enabled = self.supported()
295
- with Horizontal(classes="summary"):
296
- yield Button(
297
- "+ Create",
298
- id="new-agent",
299
- variant="primary",
300
- disabled=not controls_enabled,
301
- )
302
- with Vertical(id="terminals", classes="list"):
303
- if controls_enabled:
304
- yield Static(
305
- "No terminals yet. Click 'Create' to open a new agent terminal."
298
+ logger.debug("TerminalPanel.compose() started")
299
+ try:
300
+ controls_enabled = self.supported()
301
+ with Horizontal(classes="summary"):
302
+ yield Button(
303
+ "+ Create",
304
+ id="new-agent",
305
+ variant="primary",
306
+ disabled=not controls_enabled,
306
307
  )
307
- else:
308
- yield Static(self._support_message())
308
+ with Vertical(id="terminals", classes="list"):
309
+ if controls_enabled:
310
+ yield Static(
311
+ "No terminals yet. Click 'Create' to open a new agent terminal."
312
+ )
313
+ else:
314
+ yield Static(self._support_message())
315
+ logger.debug("TerminalPanel.compose() completed")
316
+ except Exception as e:
317
+ logger.error(f"TerminalPanel.compose() failed: {e}\n{traceback.format_exc()}")
318
+ raise
309
319
 
310
320
  def on_show(self) -> None:
311
321
  # Don't `await refresh_data()` directly here: Textual may do an initial layout pass with