aline-ai 0.5.13__py3-none-any.whl → 0.6.1__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.
@@ -0,0 +1,110 @@
1
+ """Terminal backend interface for native terminal support.
2
+
3
+ This module defines the protocol for terminal backends (iTerm2, Kitty)
4
+ that allow the Aline Dashboard to control native terminal windows
5
+ instead of using tmux for rendering.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from dataclasses import dataclass, field
11
+ from typing import Protocol, runtime_checkable
12
+
13
+
14
+ @dataclass
15
+ class TerminalInfo:
16
+ """Information about a terminal tab/window managed by a backend."""
17
+
18
+ terminal_id: str # Aline internal ID (UUID)
19
+ session_id: str # Backend-specific session ID (iTerm2 session_id or Kitty window_id)
20
+ name: str # Display name
21
+ active: bool = False # Whether this terminal is currently focused
22
+ claude_session_id: str | None = None # Claude Code session ID if applicable
23
+ context_id: str | None = None # Aline context ID
24
+ provider: str | None = None # Terminal provider (claude, codex, etc.)
25
+ attention: str | None = None # Attention state (permission_request, stop, etc.)
26
+ created_at: float | None = None # Unix timestamp when terminal was created
27
+ metadata: dict[str, str] = field(default_factory=dict) # Additional metadata
28
+
29
+
30
+ @runtime_checkable
31
+ class TerminalBackend(Protocol):
32
+ """Protocol for terminal backends.
33
+
34
+ Implementations must provide async methods to:
35
+ - Create new terminal tabs
36
+ - Focus/switch to existing tabs
37
+ - Close tabs
38
+ - List all managed tabs
39
+ """
40
+
41
+ async def create_tab(
42
+ self,
43
+ command: str,
44
+ terminal_id: str,
45
+ *,
46
+ name: str | None = None,
47
+ env: dict[str, str] | None = None,
48
+ cwd: str | None = None,
49
+ ) -> str | None:
50
+ """Create a new terminal tab.
51
+
52
+ Args:
53
+ command: The command to run in the new tab
54
+ terminal_id: Aline internal terminal ID
55
+ name: Optional display name for the tab
56
+ env: Optional environment variables to set
57
+ cwd: Optional working directory
58
+
59
+ Returns:
60
+ Backend-specific session ID, or None if creation failed
61
+ """
62
+ ...
63
+
64
+ async def focus_tab(self, session_id: str, *, steal_focus: bool = False) -> bool:
65
+ """Switch to/focus a terminal tab.
66
+
67
+ Args:
68
+ session_id: Backend-specific session ID
69
+ steal_focus: If True, also bring the terminal window to front.
70
+ If False, switch tab but keep focus on Dashboard.
71
+
72
+ Returns:
73
+ True if successful, False otherwise
74
+ """
75
+ ...
76
+
77
+ async def close_tab(self, session_id: str) -> bool:
78
+ """Close a terminal tab.
79
+
80
+ Args:
81
+ session_id: Backend-specific session ID
82
+
83
+ Returns:
84
+ True if successful, False otherwise
85
+ """
86
+ ...
87
+
88
+ async def list_tabs(self) -> list[TerminalInfo]:
89
+ """List all terminal tabs managed by this backend.
90
+
91
+ Returns:
92
+ List of TerminalInfo objects for each managed tab
93
+ """
94
+ ...
95
+
96
+ async def is_available(self) -> bool:
97
+ """Check if this backend is available and usable.
98
+
99
+ Returns:
100
+ True if the backend can be used, False otherwise
101
+ """
102
+ ...
103
+
104
+ def get_backend_name(self) -> str:
105
+ """Get the human-readable name of this backend.
106
+
107
+ Returns:
108
+ Backend name (e.g., "iTerm2", "Kitty")
109
+ """
110
+ ...
@@ -232,6 +232,8 @@ class EventsTable(Container):
232
232
  table.add_column("Share", key="share", width=12)
233
233
  table.add_column("Type", key="type", width=8)
234
234
  table.add_column("Sessions", key="sessions", width=8)
235
+ table.add_column("Created By", key="created_by", width=10)
236
+ table.add_column("Shared By", key="shared_by", width=10)
235
237
  table.add_column("Event ID", key="event_id", width=12)
236
238
  table.add_column("Created", key="created", width=10)
237
239
  table.cursor_type = "row"
@@ -352,6 +354,12 @@ class EventsTable(Container):
352
354
  table.update_cell(
353
355
  eid, "sessions", self._format_cell(str(event["sessions"]), eid)
354
356
  )
357
+ table.update_cell(
358
+ eid, "created_by", self._format_cell(event.get("created_by", "-"), eid)
359
+ )
360
+ table.update_cell(
361
+ eid, "shared_by", self._format_cell(event.get("shared_by", "-"), eid)
362
+ )
355
363
  table.update_cell(
356
364
  eid, "event_id", self._format_cell(event["short_id"], eid)
357
365
  )
@@ -736,6 +744,8 @@ class EventsTable(Container):
736
744
  e.event_type,
737
745
  e.created_at,
738
746
  e.share_url,
747
+ e.created_by,
748
+ e.shared_by,
739
749
  (SELECT COUNT(*) FROM event_sessions WHERE event_id = e.id) AS session_count
740
750
  FROM events e
741
751
  ORDER BY e.created_at DESC
@@ -743,8 +753,9 @@ class EventsTable(Container):
743
753
  """,
744
754
  (int(rows_per_page), int(offset)),
745
755
  ).fetchall()
746
- has_share_url = True
756
+ has_new_columns = True
747
757
  except Exception:
758
+ # Fallback for older schema without created_by/shared_by
748
759
  rows = conn.execute(
749
760
  """
750
761
  SELECT
@@ -759,18 +770,22 @@ class EventsTable(Container):
759
770
  """,
760
771
  (int(rows_per_page), int(offset)),
761
772
  ).fetchall()
762
- has_share_url = False
773
+ has_new_columns = False
763
774
 
764
775
  for i, row in enumerate(rows):
765
776
  event_id = row[0]
766
777
  title = row[1] or "(no title)"
767
778
  event_type = row[2] or "unknown"
768
779
  created_at = row[3]
769
- if has_share_url:
780
+ if has_new_columns:
770
781
  share_url = row[4]
771
- session_count = row[5]
782
+ created_by = row[5]
783
+ shared_by = row[6]
784
+ session_count = row[7]
772
785
  else:
773
786
  share_url = None
787
+ created_by = None
788
+ shared_by = None
774
789
  session_count = row[4]
775
790
 
776
791
  # Format event type
@@ -789,6 +804,26 @@ class EventsTable(Container):
789
804
  except Exception:
790
805
  created_str = created_at
791
806
 
807
+ # Look up user names from users table
808
+ created_by_display = "-"
809
+ shared_by_display = "-"
810
+ if created_by:
811
+ try:
812
+ user_row = conn.execute(
813
+ "SELECT user_name FROM users WHERE uid = ?", (created_by,)
814
+ ).fetchone()
815
+ created_by_display = user_row[0] if user_row and user_row[0] else created_by[:8] + "..."
816
+ except Exception:
817
+ created_by_display = created_by[:8] + "..." if len(created_by) > 8 else created_by
818
+ if shared_by:
819
+ try:
820
+ user_row = conn.execute(
821
+ "SELECT user_name FROM users WHERE uid = ?", (shared_by,)
822
+ ).fetchone()
823
+ shared_by_display = user_row[0] if user_row and user_row[0] else shared_by[:8] + "..."
824
+ except Exception:
825
+ shared_by_display = shared_by[:8] + "..." if len(shared_by) > 8 else shared_by
826
+
792
827
  events.append(
793
828
  {
794
829
  "index": offset + i + 1,
@@ -799,6 +834,8 @@ class EventsTable(Container):
799
834
  "sessions": session_count,
800
835
  "share_url": share_url,
801
836
  "share_id": self._extract_share_id(share_url),
837
+ "created_by": created_by_display,
838
+ "shared_by": shared_by_display,
802
839
  "created": created_str,
803
840
  }
804
841
  )
@@ -849,7 +886,7 @@ class EventsTable(Container):
849
886
  if self.wrap_mode and event.get("share_url"):
850
887
  share_val = event.get("share_url")
851
888
 
852
- # Column order: ✓, #, Title, Share, Type, Sessions, Event ID, Created
889
+ # Column order: ✓, #, Title, Share, Type, Sessions, Created By, Shared By, Event ID, Created
853
890
  table.add_row(
854
891
  self._checkbox_cell(eid),
855
892
  self._format_cell(str(event["index"]), eid),
@@ -857,6 +894,8 @@ class EventsTable(Container):
857
894
  self._format_cell(share_val, eid),
858
895
  self._format_cell(event["type"], eid),
859
896
  self._format_cell(str(event["sessions"]), eid),
897
+ self._format_cell(event.get("created_by", "-"), eid),
898
+ self._format_cell(event.get("shared_by", "-"), eid),
860
899
  self._format_cell(event["short_id"], eid),
861
900
  self._format_cell(event["created"], eid),
862
901
  key=eid,
@@ -223,6 +223,8 @@ class SessionsTable(Container):
223
223
  table.add_column("Project", key="project", width=15)
224
224
  table.add_column("Source", key="source", width=10)
225
225
  table.add_column("Turns", key="turns", width=6)
226
+ table.add_column("Created By", key="created_by", width=10)
227
+ table.add_column("Shared By", key="shared_by", width=10)
226
228
  table.add_column("Session ID", key="session_id", width=20)
227
229
  table.add_column("Last Activity", key="last_activity", width=12)
228
230
  table.cursor_type = "row"
@@ -361,6 +363,12 @@ class SessionsTable(Container):
361
363
  table.update_cell(
362
364
  sid, "turns", self._format_cell(str(session["turns"]), sid)
363
365
  )
366
+ table.update_cell(
367
+ sid, "created_by", self._format_cell(session.get("created_by", "-"), sid)
368
+ )
369
+ table.update_cell(
370
+ sid, "shared_by", self._format_cell(session.get("shared_by", "-"), sid)
371
+ )
364
372
  table.update_cell(
365
373
  sid, "session_id", self._format_cell(session["short_id"], sid)
366
374
  )
@@ -805,21 +813,43 @@ class SessionsTable(Container):
805
813
  # Get paginated sessions
806
814
  # Use cached total_turns instead of subquery for performance
807
815
  offset = (int(page) - 1) * int(rows_per_page)
808
- rows = conn.execute(
809
- """
810
- SELECT
811
- s.id,
812
- s.session_type,
813
- s.workspace_path,
814
- s.session_title,
815
- s.last_activity_at,
816
- s.total_turns
817
- FROM sessions s
818
- ORDER BY s.last_activity_at DESC
819
- LIMIT ? OFFSET ?
820
- """,
821
- (int(rows_per_page), int(offset)),
822
- ).fetchall()
816
+ try:
817
+ rows = conn.execute(
818
+ """
819
+ SELECT
820
+ s.id,
821
+ s.session_type,
822
+ s.workspace_path,
823
+ s.session_title,
824
+ s.last_activity_at,
825
+ s.total_turns,
826
+ s.created_by,
827
+ s.shared_by
828
+ FROM sessions s
829
+ ORDER BY s.last_activity_at DESC
830
+ LIMIT ? OFFSET ?
831
+ """,
832
+ (int(rows_per_page), int(offset)),
833
+ ).fetchall()
834
+ has_new_columns = True
835
+ except Exception:
836
+ # Fallback for older schema without created_by/shared_by
837
+ rows = conn.execute(
838
+ """
839
+ SELECT
840
+ s.id,
841
+ s.session_type,
842
+ s.workspace_path,
843
+ s.session_title,
844
+ s.last_activity_at,
845
+ s.total_turns
846
+ FROM sessions s
847
+ ORDER BY s.last_activity_at DESC
848
+ LIMIT ? OFFSET ?
849
+ """,
850
+ (int(rows_per_page), int(offset)),
851
+ ).fetchall()
852
+ has_new_columns = False
823
853
 
824
854
  for i, row in enumerate(rows):
825
855
  session_id = row[0]
@@ -828,6 +858,12 @@ class SessionsTable(Container):
828
858
  title = row[3] or "(no title)"
829
859
  last_activity = row[4]
830
860
  turn_count = row[5]
861
+ if has_new_columns:
862
+ created_by = row[6]
863
+ shared_by = row[7]
864
+ else:
865
+ created_by = None
866
+ shared_by = None
831
867
 
832
868
  source_map = {
833
869
  "claude": "Claude",
@@ -846,6 +882,26 @@ class SessionsTable(Container):
846
882
  except Exception:
847
883
  activity_str = last_activity
848
884
 
885
+ # Look up user names from users table
886
+ created_by_display = "-"
887
+ shared_by_display = "-"
888
+ if created_by:
889
+ try:
890
+ user_row = conn.execute(
891
+ "SELECT user_name FROM users WHERE uid = ?", (created_by,)
892
+ ).fetchone()
893
+ created_by_display = user_row[0] if user_row and user_row[0] else created_by[:8] + "..."
894
+ except Exception:
895
+ created_by_display = created_by[:8] + "..." if len(created_by) > 8 else created_by
896
+ if shared_by:
897
+ try:
898
+ user_row = conn.execute(
899
+ "SELECT user_name FROM users WHERE uid = ?", (shared_by,)
900
+ ).fetchone()
901
+ shared_by_display = user_row[0] if user_row and user_row[0] else shared_by[:8] + "..."
902
+ except Exception:
903
+ shared_by_display = shared_by[:8] + "..." if len(shared_by) > 8 else shared_by
904
+
849
905
  sessions.append(
850
906
  {
851
907
  "index": offset + i + 1,
@@ -855,6 +911,8 @@ class SessionsTable(Container):
855
911
  "project": project,
856
912
  "turns": turn_count,
857
913
  "title": title,
914
+ "created_by": created_by_display,
915
+ "shared_by": shared_by_display,
858
916
  "last_activity": activity_str,
859
917
  }
860
918
  )
@@ -905,7 +963,7 @@ class SessionsTable(Container):
905
963
  if self.wrap_mode:
906
964
  display_id = sid
907
965
 
908
- # Column order: ✓, #, Title, Project, Source, Turns, Session ID, Last Activity
966
+ # Column order: ✓, #, Title, Project, Source, Turns, Created By, Shared By, Session ID, Last Activity
909
967
  table.add_row(
910
968
  self._checkbox_cell(sid),
911
969
  self._format_cell(str(session["index"]), sid),
@@ -913,6 +971,8 @@ class SessionsTable(Container):
913
971
  self._format_cell(session["project"], sid),
914
972
  self._format_cell(session["source"], sid),
915
973
  self._format_cell(str(session["turns"]), sid),
974
+ self._format_cell(session.get("created_by", "-"), sid),
975
+ self._format_cell(session.get("shared_by", "-"), sid),
916
976
  self._format_cell(display_id, sid),
917
977
  self._format_cell(session["last_activity"], sid),
918
978
  key=sid,