aline-ai 0.6.0__py3-none-any.whl → 0.6.2__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.
@@ -60,6 +60,23 @@ def _save_claude_permission_mode(mode: str) -> None:
60
60
  _save_state("claude_permission_mode", mode)
61
61
 
62
62
 
63
+ def _load_claude_tracking_mode() -> str:
64
+ """Load the last used Claude tracking mode from state file."""
65
+ try:
66
+ if DASHBOARD_STATE_FILE.exists():
67
+ with open(DASHBOARD_STATE_FILE, "r", encoding="utf-8") as f:
68
+ state = json.load(f)
69
+ return state.get("claude_tracking_mode", "track")
70
+ except Exception:
71
+ pass
72
+ return "track"
73
+
74
+
75
+ def _save_claude_tracking_mode(mode: str) -> None:
76
+ """Save the Claude tracking mode to state file."""
77
+ _save_state("claude_tracking_mode", mode)
78
+
79
+
63
80
  def _save_state(key: str, value: str) -> None:
64
81
  """Save a key-value pair to the state file."""
65
82
  try:
@@ -75,10 +92,10 @@ def _save_state(key: str, value: str) -> None:
75
92
  pass
76
93
 
77
94
 
78
- class CreateAgentScreen(ModalScreen[Optional[tuple[str, str, bool]]]):
95
+ class CreateAgentScreen(ModalScreen[Optional[tuple[str, str, bool, bool]]]):
79
96
  """Modal to create a new agent terminal.
80
97
 
81
- Returns a tuple of (agent_type, workspace_path, skip_permissions) on success, None on cancel.
98
+ Returns a tuple of (agent_type, workspace_path, skip_permissions, no_track) on success, None on cancel.
82
99
  """
83
100
 
84
101
  BINDINGS = [
@@ -175,6 +192,7 @@ class CreateAgentScreen(ModalScreen[Optional[tuple[str, str, bool]]]):
175
192
  super().__init__()
176
193
  self._workspace_path = _load_last_workspace()
177
194
  self._permission_mode = _load_claude_permission_mode()
195
+ self._tracking_mode = _load_claude_tracking_mode()
178
196
 
179
197
  def compose(self) -> ComposeResult:
180
198
  with Container(id="create-agent-root"):
@@ -199,6 +217,11 @@ class CreateAgentScreen(ModalScreen[Optional[tuple[str, str, bool]]]):
199
217
  yield RadioButton("Normal", id="perm-normal", value=True)
200
218
  yield RadioButton("Skip (--dangerously-skip-permissions)", id="perm-skip")
201
219
 
220
+ yield Label("Tracking", classes="section-label")
221
+ with RadioSet(id="tracking-mode"):
222
+ yield RadioButton("Track", id="track-track", value=True)
223
+ yield RadioButton("No Track (skip LLM summaries)", id="track-notrack")
224
+
202
225
  with Horizontal(id="buttons"):
203
226
  yield Button("Cancel", id="cancel")
204
227
  yield Button("Create", id="create", variant="primary")
@@ -209,6 +232,11 @@ class CreateAgentScreen(ModalScreen[Optional[tuple[str, str, bool]]]):
209
232
  self.query_one("#perm-skip", RadioButton).value = True
210
233
  else:
211
234
  self.query_one("#perm-normal", RadioButton).value = True
235
+ # Set the saved tracking mode
236
+ if self._tracking_mode == "notrack":
237
+ self.query_one("#track-notrack", RadioButton).value = True
238
+ else:
239
+ self.query_one("#track-track", RadioButton).value = True
212
240
  self.query_one("#create", Button).focus()
213
241
 
214
242
  def action_close(self) -> None:
@@ -292,8 +320,9 @@ class CreateAgentScreen(ModalScreen[Optional[tuple[str, str, bool]]]):
292
320
  }
293
321
  agent_type = agent_type_map.get(pressed_button.id or "", "claude")
294
322
 
295
- # Get permission mode (only relevant for Claude)
323
+ # Get permission mode and tracking mode (only relevant for Claude)
296
324
  skip_permissions = False
325
+ no_track = False
297
326
  if agent_type == "claude":
298
327
  perm_radio_set = self.query_one("#permission-mode", RadioSet)
299
328
  perm_pressed = perm_radio_set.pressed_button
@@ -302,8 +331,16 @@ class CreateAgentScreen(ModalScreen[Optional[tuple[str, str, bool]]]):
302
331
  permission_mode = "skip" if skip_permissions else "normal"
303
332
  _save_claude_permission_mode(permission_mode)
304
333
 
334
+ # Get tracking mode
335
+ track_radio_set = self.query_one("#tracking-mode", RadioSet)
336
+ track_pressed = track_radio_set.pressed_button
337
+ no_track = track_pressed is not None and track_pressed.id == "track-notrack"
338
+ # Save the tracking mode for next time
339
+ tracking_mode = "notrack" if no_track else "track"
340
+ _save_claude_tracking_mode(tracking_mode)
341
+
305
342
  # Save the workspace path for next time
306
343
  _save_last_workspace(self._workspace_path)
307
344
 
308
345
  # Return the result
309
- self.dismiss((agent_type, self._workspace_path, skip_permissions))
346
+ self.dismiss((agent_type, self._workspace_path, skip_permissions, no_track))
@@ -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
+ ...
@@ -45,6 +45,7 @@ OPT_TRANSCRIPT_PATH = "@aline_transcript_path"
45
45
  OPT_CONTEXT_ID = "@aline_context_id"
46
46
  OPT_ATTENTION = "@aline_attention"
47
47
  OPT_CREATED_AT = "@aline_created_at"
48
+ OPT_NO_TRACK = "@aline_no_track"
48
49
 
49
50
 
50
51
  @dataclass(frozen=True)
@@ -60,6 +61,7 @@ class InnerWindow:
60
61
  context_id: str | None = None
61
62
  attention: str | None = None # "permission_request", "stop", or None
62
63
  created_at: float | None = None # Unix timestamp when window was created
64
+ no_track: bool = False # Whether tracking is disabled for this terminal
63
65
 
64
66
 
65
67
  def tmux_available() -> bool:
@@ -604,6 +606,8 @@ def list_inner_windows() -> list[InnerWindow]:
604
606
  + OPT_ATTENTION
605
607
  + "}\t#{"
606
608
  + OPT_CREATED_AT
609
+ + "}\t#{"
610
+ + OPT_NO_TRACK
607
611
  + "}",
608
612
  ],
609
613
  capture=True,
@@ -632,6 +636,8 @@ def list_inner_windows() -> list[InnerWindow]:
632
636
  created_at = float(created_at_str)
633
637
  except ValueError:
634
638
  pass
639
+ no_track_str = parts[11] if len(parts) > 11 and parts[11] else None
640
+ no_track = no_track_str == "1"
635
641
 
636
642
  if terminal_id:
637
643
  persisted = state.get(terminal_id) or {}
@@ -663,6 +669,7 @@ def list_inner_windows() -> list[InnerWindow]:
663
669
  context_id=context_id,
664
670
  attention=attention,
665
671
  created_at=created_at,
672
+ no_track=no_track,
666
673
  )
667
674
  )
668
675
  # Sort by creation time (newest first). Windows without created_at go to the bottom.
@@ -758,6 +765,16 @@ def select_inner_window(window_id: str) -> bool:
758
765
  return _run_inner_tmux(["select-window", "-t", window_id]).returncode == 0
759
766
 
760
767
 
768
+ def focus_right_pane() -> bool:
769
+ """Focus the right pane (terminal area) in the outer tmux layout."""
770
+ return (
771
+ _run_outer_tmux(
772
+ ["select-pane", "-t", f"{OUTER_SESSION}:{OUTER_WINDOW}.1"]
773
+ ).returncode
774
+ == 0
775
+ )
776
+
777
+
761
778
  def clear_attention(window_id: str) -> bool:
762
779
  """Clear the attention state for a window (e.g., after user acknowledges permission request)."""
763
780
  if not ensure_inner_session():